mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
feat: add property parsing and ClipEffect for clip property
This fixes reftests/clip
This commit is contained in:
parent
f02d7bd737
commit
83eec2d24a
@ -30,6 +30,7 @@ import {
|
|||||||
borderRightWidth,
|
borderRightWidth,
|
||||||
borderTopWidth
|
borderTopWidth
|
||||||
} from './property-descriptors/border-width';
|
} from './property-descriptors/border-width';
|
||||||
|
import {clip} from './property-descriptors/clip';
|
||||||
import {color} from './property-descriptors/color';
|
import {color} from './property-descriptors/color';
|
||||||
import {direction} from './property-descriptors/direction';
|
import {direction} from './property-descriptors/direction';
|
||||||
import {display, DISPLAY} from './property-descriptors/display';
|
import {display, DISPLAY} from './property-descriptors/display';
|
||||||
@ -107,6 +108,7 @@ export class CSSParsedDeclaration {
|
|||||||
borderBottomWidth: ReturnType<typeof borderBottomWidth.parse>;
|
borderBottomWidth: ReturnType<typeof borderBottomWidth.parse>;
|
||||||
borderLeftWidth: ReturnType<typeof borderLeftWidth.parse>;
|
borderLeftWidth: ReturnType<typeof borderLeftWidth.parse>;
|
||||||
boxShadow: ReturnType<typeof boxShadow.parse>;
|
boxShadow: ReturnType<typeof boxShadow.parse>;
|
||||||
|
clip: ReturnType<typeof clip.parse>;
|
||||||
color: Color;
|
color: Color;
|
||||||
direction: ReturnType<typeof direction.parse>;
|
direction: ReturnType<typeof direction.parse>;
|
||||||
display: ReturnType<typeof display.parse>;
|
display: ReturnType<typeof display.parse>;
|
||||||
@ -225,6 +227,12 @@ export class CSSParsedDeclaration {
|
|||||||
this.webkitTextStrokeWidth = parse(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth);
|
this.webkitTextStrokeWidth = parse(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth);
|
||||||
this.wordBreak = parse(context, wordBreak, declaration.wordBreak);
|
this.wordBreak = parse(context, wordBreak, declaration.wordBreak);
|
||||||
this.zIndex = parse(context, zIndex, declaration.zIndex);
|
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 {
|
isVisible(): boolean {
|
||||||
|
38
src/css/property-descriptors/clip.ts
Normal file
38
src/css/property-descriptors/clip.ts
Normal 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]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
@ -40,15 +40,18 @@ export const getAbsoluteValue = (token: LengthPercentage, parent: number): numbe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDimensionToken(token)) {
|
if (isDimensionToken(token)) {
|
||||||
switch (token.unit) {
|
return getAbsoluteValueForDimension(token);
|
||||||
case 'rem':
|
|
||||||
case 'em':
|
|
||||||
return 16 * token.number; // TODO use correct font-size
|
|
||||||
case 'px':
|
|
||||||
default:
|
|
||||||
return token.number;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return token.number;
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {ElementContainer} from '../dom/element-container';
|
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 {Vector} from './vector';
|
||||||
import {BezierCurve} from './bezier-curve';
|
import {BezierCurve} from './bezier-curve';
|
||||||
import {Path} from './path';
|
import {Path} from './path';
|
||||||
@ -21,6 +21,10 @@ export class BoundCurves {
|
|||||||
readonly topRightBorderBox: Path;
|
readonly topRightBorderBox: Path;
|
||||||
readonly bottomRightBorderBox: Path;
|
readonly bottomRightBorderBox: Path;
|
||||||
readonly bottomLeftBorderBox: Path;
|
readonly bottomLeftBorderBox: Path;
|
||||||
|
readonly topLeftClipBox: Path;
|
||||||
|
readonly topRightClipBox: Path;
|
||||||
|
readonly bottomRightClipBox: Path;
|
||||||
|
readonly bottomLeftClipBox: Path;
|
||||||
readonly topLeftPaddingBox: Path;
|
readonly topLeftPaddingBox: Path;
|
||||||
readonly topRightPaddingBox: Path;
|
readonly topRightPaddingBox: Path;
|
||||||
readonly bottomRightPaddingBox: Path;
|
readonly bottomRightPaddingBox: Path;
|
||||||
@ -72,6 +76,17 @@ export class BoundCurves {
|
|||||||
const paddingBottom = getAbsoluteValue(styles.paddingBottom, element.bounds.width);
|
const paddingBottom = getAbsoluteValue(styles.paddingBottom, element.bounds.width);
|
||||||
const paddingLeft = getAbsoluteValue(styles.paddingLeft, 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 =
|
this.topLeftBorderDoubleOuterBox =
|
||||||
tlh > 0 || tlv > 0
|
tlh > 0 || tlv > 0
|
||||||
? getCurvePoints(
|
? getCurvePoints(
|
||||||
@ -223,6 +238,10 @@ export class BoundCurves {
|
|||||||
blh > 0 || blv > 0
|
blh > 0 || blv > 0
|
||||||
? getCurvePoints(bounds.left, bounds.top + leftHeight, blh, blv, CORNER.BOTTOM_LEFT)
|
? getCurvePoints(bounds.left, bounds.top + leftHeight, blh, blv, CORNER.BOTTOM_LEFT)
|
||||||
: new Vector(bounds.left, bounds.top + bounds.height);
|
: 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 =
|
this.topLeftPaddingBox =
|
||||||
tlh > 0 || tlv > 0
|
tlh > 0 || tlv > 0
|
||||||
? getCurvePoints(
|
? getCurvePoints(
|
||||||
@ -369,6 +388,10 @@ export const calculateBorderBoxPath = (curves: BoundCurves): Path[] => {
|
|||||||
return [curves.topLeftBorderBox, curves.topRightBorderBox, curves.bottomRightBorderBox, curves.bottomLeftBorderBox];
|
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[] => {
|
export const calculateContentBoxPath = (curves: BoundCurves): Path[] => {
|
||||||
return [
|
return [
|
||||||
curves.topLeftContentBox,
|
curves.topLeftContentBox,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {ElementContainer, FLAGS} from '../dom/element-container';
|
import {ElementContainer, FLAGS} from '../dom/element-container';
|
||||||
import {contains} from '../core/bitwise';
|
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 {ClipEffect, EffectTarget, IElementEffect, isClipEffect, OpacityEffect, TransformEffect} from './effects';
|
||||||
import {OVERFLOW} from '../css/property-descriptors/overflow';
|
import {OVERFLOW} from '../css/property-descriptors/overflow';
|
||||||
import {equalPath} from './path';
|
import {equalPath} from './path';
|
||||||
@ -61,6 +61,12 @@ export class ElementPaint {
|
|||||||
this.effects.push(new ClipEffect(paddingBox, EffectTarget.CONTENT));
|
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[] {
|
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 {
|
} else {
|
||||||
effects.unshift(...croplessEffects);
|
effects.unshift(...croplessEffects);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Inline text in the top element</title>
|
<title>Inline text in the top element</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
@ -16,7 +16,7 @@
|
|||||||
border: 5px solid blue;
|
border: 5px solid blue;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
font-family: Arial;
|
font-family: Arial, sans-serif;
|
||||||
}
|
}
|
||||||
</style>
|
</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.
|
<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>
|
<p>Then a block level element.</p>
|
||||||
Then more inline text.</div>
|
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.
|
<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>
|
<p>Then a block level element.</p>
|
||||||
Then more inline text.</div>
|
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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user