feat: add property parsing and ClipEffect for clip property

This fixes reftests/clip
This commit is contained in:
Nick Badal 2022-05-21 20:26:14 -07:00
parent f02d7bd737
commit 83eec2d24a
No known key found for this signature in database
GPG Key ID: 829D8559E7019BAD
6 changed files with 99 additions and 13 deletions

View File

@ -30,6 +30,7 @@ import {
borderRightWidth,
borderTopWidth
} from './property-descriptors/border-width';
import {clip} from './property-descriptors/clip';
import {color} from './property-descriptors/color';
import {direction} from './property-descriptors/direction';
import {display, DISPLAY} from './property-descriptors/display';
@ -107,6 +108,7 @@ export class CSSParsedDeclaration {
borderBottomWidth: ReturnType<typeof borderBottomWidth.parse>;
borderLeftWidth: ReturnType<typeof borderLeftWidth.parse>;
boxShadow: ReturnType<typeof boxShadow.parse>;
clip: ReturnType<typeof clip.parse>;
color: Color;
direction: ReturnType<typeof direction.parse>;
display: ReturnType<typeof display.parse>;
@ -225,6 +227,12 @@ export class CSSParsedDeclaration {
this.webkitTextStrokeWidth = parse(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth);
this.wordBreak = parse(context, wordBreak, declaration.wordBreak);
this.zIndex = parse(context, zIndex, declaration.zIndex);
if (this.position == POSITION.ABSOLUTE || this.position == POSITION.FIXED) {
this.clip = parse(context, clip, declaration.clip);
} else {
this.clip = null;
}
}
isVisible(): boolean {

View File

@ -0,0 +1,38 @@
import {IPropertyValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
import {CSSValue, isDimensionToken, isIdentWithValue, nonFunctionArgSeparator} from '../syntax/parser';
import {Context} from '../../core/context';
import {DimensionToken, TokenType} from '../syntax/tokenizer';
export interface RectClip {
top: DimensionToken;
right: DimensionToken;
bottom: DimensionToken;
left: DimensionToken;
}
export const clip: IPropertyValueDescriptor<RectClip | null> = {
name: 'clip',
initialValue: 'auto',
type: PropertyDescriptorParsingType.VALUE,
prefix: false,
parse: (_context: Context, token: CSSValue) => {
if (isIdentWithValue(token, 'auto')) {
return null;
}
if (token.type !== TokenType.FUNCTION || token.name !== 'rect') {
throw new Error('Clip value must be auto or a rect function');
}
const rectArgs = token.values.filter(nonFunctionArgSeparator).filter(isDimensionToken);
if (rectArgs.length !== 4) {
throw new Error('Rect clip must have 4 dimension elements');
}
return {
top: rectArgs[0],
right: rectArgs[1],
bottom: rectArgs[2],
left: rectArgs[3]
};
}
};

View File

@ -40,15 +40,18 @@ export const getAbsoluteValue = (token: LengthPercentage, parent: number): numbe
}
if (isDimensionToken(token)) {
switch (token.unit) {
case 'rem':
case 'em':
return 16 * token.number; // TODO use correct font-size
case 'px':
default:
return token.number;
}
return getAbsoluteValueForDimension(token);
}
return token.number;
};
export const getAbsoluteValueForDimension = (token: DimensionToken): number => {
switch (token.unit) {
case 'rem':
case 'em':
return 16 * token.number; // TODO use correct font-size
case 'px':
default:
return token.number;
}
};

View File

@ -1,5 +1,5 @@
import {ElementContainer} from '../dom/element-container';
import {getAbsoluteValue, getAbsoluteValueForTuple} from '../css/types/length-percentage';
import {getAbsoluteValue, getAbsoluteValueForTuple, getAbsoluteValueForDimension} from '../css/types/length-percentage';
import {Vector} from './vector';
import {BezierCurve} from './bezier-curve';
import {Path} from './path';
@ -21,6 +21,10 @@ export class BoundCurves {
readonly topRightBorderBox: Path;
readonly bottomRightBorderBox: Path;
readonly bottomLeftBorderBox: Path;
readonly topLeftClipBox: Path;
readonly topRightClipBox: Path;
readonly bottomRightClipBox: Path;
readonly bottomLeftClipBox: Path;
readonly topLeftPaddingBox: Path;
readonly topRightPaddingBox: Path;
readonly bottomRightPaddingBox: Path;
@ -72,6 +76,17 @@ export class BoundCurves {
const paddingBottom = getAbsoluteValue(styles.paddingBottom, element.bounds.width);
const paddingLeft = getAbsoluteValue(styles.paddingLeft, element.bounds.width);
let rectClipTop = 0;
let rectClipRight = 0;
let rectClipBottom = 0;
let rectClipLeft = 0;
if (styles.clip) {
rectClipTop = getAbsoluteValueForDimension(styles.clip.top);
rectClipRight = getAbsoluteValueForDimension(styles.clip.right);
rectClipBottom = getAbsoluteValueForDimension(styles.clip.bottom);
rectClipLeft = getAbsoluteValueForDimension(styles.clip.left);
}
this.topLeftBorderDoubleOuterBox =
tlh > 0 || tlv > 0
? getCurvePoints(
@ -223,6 +238,10 @@ export class BoundCurves {
blh > 0 || blv > 0
? getCurvePoints(bounds.left, bounds.top + leftHeight, blh, blv, CORNER.BOTTOM_LEFT)
: new Vector(bounds.left, bounds.top + bounds.height);
this.topLeftClipBox = new Vector(bounds.left + rectClipLeft, bounds.top + rectClipTop);
this.topRightClipBox = new Vector(bounds.left + rectClipRight, bounds.top + rectClipTop);
this.bottomRightClipBox = new Vector(bounds.left + rectClipRight, bounds.top + rectClipBottom);
this.bottomLeftClipBox = new Vector(bounds.left + rectClipLeft, bounds.top + rectClipBottom);
this.topLeftPaddingBox =
tlh > 0 || tlv > 0
? getCurvePoints(
@ -369,6 +388,10 @@ export const calculateBorderBoxPath = (curves: BoundCurves): Path[] => {
return [curves.topLeftBorderBox, curves.topRightBorderBox, curves.bottomRightBorderBox, curves.bottomLeftBorderBox];
};
export const calculateRectClipPath = (curves: BoundCurves): Path[] => {
return [curves.topLeftClipBox, curves.topRightClipBox, curves.bottomRightClipBox, curves.bottomLeftClipBox];
};
export const calculateContentBoxPath = (curves: BoundCurves): Path[] => {
return [
curves.topLeftContentBox,

View File

@ -1,6 +1,6 @@
import {ElementContainer, FLAGS} from '../dom/element-container';
import {contains} from '../core/bitwise';
import {BoundCurves, calculateBorderBoxPath, calculatePaddingBoxPath} from './bound-curves';
import {BoundCurves, calculateBorderBoxPath, calculatePaddingBoxPath, calculateRectClipPath} from './bound-curves';
import {ClipEffect, EffectTarget, IElementEffect, isClipEffect, OpacityEffect, TransformEffect} from './effects';
import {OVERFLOW} from '../css/property-descriptors/overflow';
import {equalPath} from './path';
@ -61,6 +61,12 @@ export class ElementPaint {
this.effects.push(new ClipEffect(paddingBox, EffectTarget.CONTENT));
}
}
if (this.container.styles.clip) {
const clipBox = calculateRectClipPath(this.curves);
const clipEffect = new ClipEffect(clipBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT);
this.effects.push(clipEffect);
}
}
getEffects(target: EffectTarget): IElementEffect[] {
@ -81,6 +87,11 @@ export class ElementPaint {
);
}
}
if (parent.container.styles.clip) {
const clipBox = calculateRectClipPath(parent.curves);
const clipEffect = new ClipEffect(clipBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT);
effects.unshift(clipEffect);
}
} else {
effects.unshift(...croplessEffects);
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>Inline text in the top element</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
@ -16,7 +16,7 @@
border: 5px solid blue;
}
body {
font-family: Arial;
font-family: Arial, sans-serif;
}
</style>
@ -37,10 +37,13 @@
<div style="clip: rect(0px, 400px, 50px, 200px); position: absolute; top: 250px; left: 500px;">Some inline text <span> followed by text in span </span> followed by more inline text.
<p>Then a block level element.</p>
Then more inline text.</div>
</body>
<div style="clip: rect(0px, 400px, 50px, 200px); position: absolute; top: 500px;">Some inline text <span> followed by text in span </span> followed by more inline text.
<p>Then a block level element.</p>
Then more inline text.</div>
<div style="clip: rect(0px, 400px, 50px, 200px); position: absolute; top: 500px; left: 500px; border-radius: 100%; overflow: hidden;">Some inline text <span> followed by text in span </span> followed by more inline text.
<p>Then a block level element.</p>
Then more inline text.</div>
</body>
</html>