mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
a7f5c99baa | |||
578bb771bf | |||
522e5aac5f | |||
dd6d8856ec | |||
45efe54da8 | |||
cd99f11b1b | |||
fa60716d07 |
@ -12,9 +12,9 @@ Below is a list of all the supported CSS properties and values.
|
|||||||
- url()
|
- url()
|
||||||
- linear-gradient()
|
- linear-gradient()
|
||||||
- radial-gradient()
|
- radial-gradient()
|
||||||
- background-origin
|
- background-origin
|
||||||
- background-position
|
- background-position
|
||||||
- background-size
|
- background-size
|
||||||
- border
|
- border
|
||||||
- border-color
|
- border-color
|
||||||
- border-radius
|
- border-radius
|
||||||
@ -50,6 +50,7 @@ Below is a list of all the supported CSS properties and values.
|
|||||||
- overflow
|
- overflow
|
||||||
- overflow-wrap
|
- overflow-wrap
|
||||||
- padding
|
- padding
|
||||||
|
- paint-order
|
||||||
- position
|
- position
|
||||||
- right
|
- right
|
||||||
- text-align
|
- text-align
|
||||||
@ -58,17 +59,18 @@ Below is a list of all the supported CSS properties and values.
|
|||||||
- text-decoration-line
|
- text-decoration-line
|
||||||
- text-decoration-style (**Only supports `solid`**)
|
- text-decoration-style (**Only supports `solid`**)
|
||||||
- text-shadow
|
- text-shadow
|
||||||
- text-transform
|
- text-transform
|
||||||
- top
|
- top
|
||||||
- transform (**Limited support**)
|
- transform (**Limited support**)
|
||||||
- visibility
|
- visibility
|
||||||
- white-space
|
- white-space
|
||||||
- width
|
- width
|
||||||
|
- webkit-text-stroke
|
||||||
- word-break
|
- word-break
|
||||||
- word-spacing
|
- word-spacing
|
||||||
- word-wrap
|
- word-wrap
|
||||||
- z-index
|
- z-index
|
||||||
|
|
||||||
## Unsupported CSS properties
|
## Unsupported CSS properties
|
||||||
These CSS properties are **NOT** currently supported
|
These CSS properties are **NOT** currently supported
|
||||||
- [background-blend-mode](https://github.com/niklasvh/html2canvas/issues/966)
|
- [background-blend-mode](https://github.com/niklasvh/html2canvas/issues/966)
|
||||||
|
@ -73,6 +73,9 @@ import {counterIncrement} from './property-descriptors/counter-increment';
|
|||||||
import {counterReset} from './property-descriptors/counter-reset';
|
import {counterReset} from './property-descriptors/counter-reset';
|
||||||
import {quotes} from './property-descriptors/quotes';
|
import {quotes} from './property-descriptors/quotes';
|
||||||
import {boxShadow} from './property-descriptors/box-shadow';
|
import {boxShadow} from './property-descriptors/box-shadow';
|
||||||
|
import {paintOrder} from './property-descriptors/paint-order';
|
||||||
|
import {webkitTextStrokeColor} from './property-descriptors/webkit-text-stroke-color';
|
||||||
|
import {webkitTextStrokeWidth} from './property-descriptors/webkit-text-stroke-width';
|
||||||
|
|
||||||
export class CSSParsedDeclaration {
|
export class CSSParsedDeclaration {
|
||||||
backgroundClip: ReturnType<typeof backgroundClip.parse>;
|
backgroundClip: ReturnType<typeof backgroundClip.parse>;
|
||||||
@ -125,6 +128,7 @@ export class CSSParsedDeclaration {
|
|||||||
paddingRight: LengthPercentage;
|
paddingRight: LengthPercentage;
|
||||||
paddingBottom: LengthPercentage;
|
paddingBottom: LengthPercentage;
|
||||||
paddingLeft: LengthPercentage;
|
paddingLeft: LengthPercentage;
|
||||||
|
paintOrder: ReturnType<typeof paintOrder.parse>;
|
||||||
position: ReturnType<typeof position.parse>;
|
position: ReturnType<typeof position.parse>;
|
||||||
textAlign: ReturnType<typeof textAlign.parse>;
|
textAlign: ReturnType<typeof textAlign.parse>;
|
||||||
textDecorationColor: Color;
|
textDecorationColor: Color;
|
||||||
@ -134,6 +138,8 @@ export class CSSParsedDeclaration {
|
|||||||
transform: ReturnType<typeof transform.parse>;
|
transform: ReturnType<typeof transform.parse>;
|
||||||
transformOrigin: ReturnType<typeof transformOrigin.parse>;
|
transformOrigin: ReturnType<typeof transformOrigin.parse>;
|
||||||
visibility: ReturnType<typeof visibility.parse>;
|
visibility: ReturnType<typeof visibility.parse>;
|
||||||
|
webkitTextStrokeColor: Color;
|
||||||
|
webkitTextStrokeWidth: ReturnType<typeof webkitTextStrokeWidth.parse>;
|
||||||
wordBreak: ReturnType<typeof wordBreak.parse>;
|
wordBreak: ReturnType<typeof wordBreak.parse>;
|
||||||
zIndex: ReturnType<typeof zIndex.parse>;
|
zIndex: ReturnType<typeof zIndex.parse>;
|
||||||
|
|
||||||
@ -189,6 +195,7 @@ export class CSSParsedDeclaration {
|
|||||||
this.paddingRight = parse(paddingRight, declaration.paddingRight);
|
this.paddingRight = parse(paddingRight, declaration.paddingRight);
|
||||||
this.paddingBottom = parse(paddingBottom, declaration.paddingBottom);
|
this.paddingBottom = parse(paddingBottom, declaration.paddingBottom);
|
||||||
this.paddingLeft = parse(paddingLeft, declaration.paddingLeft);
|
this.paddingLeft = parse(paddingLeft, declaration.paddingLeft);
|
||||||
|
this.paintOrder = parse(paintOrder, declaration.paintOrder);
|
||||||
this.position = parse(position, declaration.position);
|
this.position = parse(position, declaration.position);
|
||||||
this.textAlign = parse(textAlign, declaration.textAlign);
|
this.textAlign = parse(textAlign, declaration.textAlign);
|
||||||
this.textDecorationColor = parse(textDecorationColor, declaration.textDecorationColor ?? declaration.color);
|
this.textDecorationColor = parse(textDecorationColor, declaration.textDecorationColor ?? declaration.color);
|
||||||
@ -201,6 +208,8 @@ export class CSSParsedDeclaration {
|
|||||||
this.transform = parse(transform, declaration.transform);
|
this.transform = parse(transform, declaration.transform);
|
||||||
this.transformOrigin = parse(transformOrigin, declaration.transformOrigin);
|
this.transformOrigin = parse(transformOrigin, declaration.transformOrigin);
|
||||||
this.visibility = parse(visibility, declaration.visibility);
|
this.visibility = parse(visibility, declaration.visibility);
|
||||||
|
this.webkitTextStrokeColor = parse(webkitTextStrokeColor, declaration.webkitTextStrokeColor);
|
||||||
|
this.webkitTextStrokeWidth = parse(webkitTextStrokeWidth, declaration.webkitTextStrokeWidth);
|
||||||
this.wordBreak = parse(wordBreak, declaration.wordBreak);
|
this.wordBreak = parse(wordBreak, declaration.wordBreak);
|
||||||
this.zIndex = parse(zIndex, declaration.zIndex);
|
this.zIndex = parse(zIndex, declaration.zIndex);
|
||||||
}
|
}
|
||||||
|
86
src/css/property-descriptors/__tests__/paint-order.ts
Normal file
86
src/css/property-descriptors/__tests__/paint-order.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import {deepStrictEqual} from 'assert';
|
||||||
|
import {Parser} from '../../syntax/parser';
|
||||||
|
import {paintOrder, PAINT_ORDER_LAYER} from '../paint-order';
|
||||||
|
|
||||||
|
const paintOrderParse = (value: string) => paintOrder.parse(Parser.parseValues(value));
|
||||||
|
|
||||||
|
describe('property-descriptors', () => {
|
||||||
|
describe('paint-order', () => {
|
||||||
|
it('none', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('none'), [
|
||||||
|
PAINT_ORDER_LAYER.FILL,
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.MARKERS
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('EMPTY', () =>
|
||||||
|
deepStrictEqual(paintOrderParse(''), [
|
||||||
|
PAINT_ORDER_LAYER.FILL,
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.MARKERS
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('other values', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('other values'), [
|
||||||
|
PAINT_ORDER_LAYER.FILL,
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.MARKERS
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('normal', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('normal'), [
|
||||||
|
PAINT_ORDER_LAYER.FILL,
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.MARKERS
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('stroke', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('stroke'), [
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.FILL,
|
||||||
|
PAINT_ORDER_LAYER.MARKERS
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('fill', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('fill'), [
|
||||||
|
PAINT_ORDER_LAYER.FILL,
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.MARKERS
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('markers', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('markers'), [
|
||||||
|
PAINT_ORDER_LAYER.MARKERS,
|
||||||
|
PAINT_ORDER_LAYER.FILL,
|
||||||
|
PAINT_ORDER_LAYER.STROKE
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('stroke fill', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('stroke fill'), [
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.FILL,
|
||||||
|
PAINT_ORDER_LAYER.MARKERS
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('markers stroke', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('markers stroke'), [
|
||||||
|
PAINT_ORDER_LAYER.MARKERS,
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.FILL
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('markers stroke fill', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('markers stroke fill'), [
|
||||||
|
PAINT_ORDER_LAYER.MARKERS,
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.FILL
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('stroke fill markers', () =>
|
||||||
|
deepStrictEqual(paintOrderParse('stroke fill markers'), [
|
||||||
|
PAINT_ORDER_LAYER.STROKE,
|
||||||
|
PAINT_ORDER_LAYER.FILL,
|
||||||
|
PAINT_ORDER_LAYER.MARKERS
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
});
|
41
src/css/property-descriptors/paint-order.ts
Normal file
41
src/css/property-descriptors/paint-order.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||||
|
import {CSSValue, isIdentToken} from '../syntax/parser';
|
||||||
|
export enum PAINT_ORDER_LAYER {
|
||||||
|
FILL,
|
||||||
|
STROKE,
|
||||||
|
MARKERS
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaintOrder = PAINT_ORDER_LAYER[];
|
||||||
|
|
||||||
|
export const paintOrder: IPropertyListDescriptor<PaintOrder> = {
|
||||||
|
name: 'paint-order',
|
||||||
|
initialValue: 'normal',
|
||||||
|
prefix: false,
|
||||||
|
type: PropertyDescriptorParsingType.LIST,
|
||||||
|
parse: (tokens: CSSValue[]): PaintOrder => {
|
||||||
|
const DEFAULT_VALUE = [PAINT_ORDER_LAYER.FILL, PAINT_ORDER_LAYER.STROKE, PAINT_ORDER_LAYER.MARKERS];
|
||||||
|
let layers: PaintOrder = [];
|
||||||
|
|
||||||
|
tokens.filter(isIdentToken).forEach((token) => {
|
||||||
|
switch (token.value) {
|
||||||
|
case 'stroke':
|
||||||
|
layers.push(PAINT_ORDER_LAYER.STROKE);
|
||||||
|
break;
|
||||||
|
case 'fill':
|
||||||
|
layers.push(PAINT_ORDER_LAYER.FILL);
|
||||||
|
break;
|
||||||
|
case 'markers':
|
||||||
|
layers.push(PAINT_ORDER_LAYER.MARKERS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
DEFAULT_VALUE.forEach((value) => {
|
||||||
|
if (layers.indexOf(value) === -1) {
|
||||||
|
layers.push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return layers;
|
||||||
|
}
|
||||||
|
};
|
8
src/css/property-descriptors/webkit-text-stroke-color.ts
Normal file
8
src/css/property-descriptors/webkit-text-stroke-color.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import {IPropertyTypeValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||||
|
export const webkitTextStrokeColor: IPropertyTypeValueDescriptor = {
|
||||||
|
name: `-webkit-text-stroke-color`,
|
||||||
|
initialValue: 'currentcolor',
|
||||||
|
prefix: false,
|
||||||
|
type: PropertyDescriptorParsingType.TYPE_VALUE,
|
||||||
|
format: 'color'
|
||||||
|
};
|
14
src/css/property-descriptors/webkit-text-stroke-width.ts
Normal file
14
src/css/property-descriptors/webkit-text-stroke-width.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {CSSValue, isDimensionToken} from '../syntax/parser';
|
||||||
|
import {IPropertyValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||||
|
export const webkitTextStrokeWidth: IPropertyValueDescriptor<number> = {
|
||||||
|
name: `-webkit-text-stroke-width`,
|
||||||
|
initialValue: '0',
|
||||||
|
type: PropertyDescriptorParsingType.VALUE,
|
||||||
|
prefix: false,
|
||||||
|
parse: (token: CSSValue): number => {
|
||||||
|
if (isDimensionToken(token)) {
|
||||||
|
return token.number;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
@ -443,12 +443,17 @@ const iframeLoader = (iframe: HTMLIFrameElement): Promise<HTMLIFrameElement> =>
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ignoredStyleProperties = [
|
||||||
|
'all', // #2476
|
||||||
|
'd', // #2483
|
||||||
|
'content' // Safari shows pseudoelements if content is set
|
||||||
|
];
|
||||||
|
|
||||||
export const copyCSSStyles = <T extends HTMLElement | SVGElement>(style: CSSStyleDeclaration, target: T): T => {
|
export const copyCSSStyles = <T extends HTMLElement | SVGElement>(style: CSSStyleDeclaration, target: T): T => {
|
||||||
// Edge does not provide value for cssText
|
// Edge does not provide value for cssText
|
||||||
for (let i = style.length - 1; i >= 0; i--) {
|
for (let i = style.length - 1; i >= 0; i--) {
|
||||||
const property = style.item(i);
|
const property = style.item(i);
|
||||||
// Safari shows pseudoelements if content is set
|
if (ignoredStyleProperties.indexOf(property) === -1) {
|
||||||
if (property !== 'content') {
|
|
||||||
target.style.setProperty(property, style.getPropertyValue(property));
|
target.style.setProperty(property, style.getPropertyValue(property));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ import {TextareaElementContainer} from '../../dom/elements/textarea-element-cont
|
|||||||
import {SelectElementContainer} from '../../dom/elements/select-element-container';
|
import {SelectElementContainer} from '../../dom/elements/select-element-container';
|
||||||
import {IFrameElementContainer} from '../../dom/replaced-elements/iframe-element-container';
|
import {IFrameElementContainer} from '../../dom/replaced-elements/iframe-element-container';
|
||||||
import {TextShadow} from '../../css/property-descriptors/text-shadow';
|
import {TextShadow} from '../../css/property-descriptors/text-shadow';
|
||||||
|
import {PAINT_ORDER_LAYER} from '../../css/property-descriptors/paint-order';
|
||||||
|
|
||||||
export type RenderConfigurations = RenderOptions & {
|
export type RenderConfigurations = RenderOptions & {
|
||||||
backgroundColor: Color | null;
|
backgroundColor: Color | null;
|
||||||
@ -179,65 +180,88 @@ export class CanvasRenderer {
|
|||||||
const [font, fontFamily, fontSize] = this.createFontStyle(styles);
|
const [font, fontFamily, fontSize] = this.createFontStyle(styles);
|
||||||
|
|
||||||
this.ctx.font = font;
|
this.ctx.font = font;
|
||||||
this.ctx.textBaseline = 'alphabetic';
|
|
||||||
|
|
||||||
|
this.ctx.textBaseline = 'alphabetic';
|
||||||
const {baseline, middle} = this.fontMetrics.getMetrics(fontFamily, fontSize);
|
const {baseline, middle} = this.fontMetrics.getMetrics(fontFamily, fontSize);
|
||||||
|
const paintOrder = styles.paintOrder;
|
||||||
|
|
||||||
text.textBounds.forEach((text) => {
|
text.textBounds.forEach((text) => {
|
||||||
this.ctx.fillStyle = asString(styles.color);
|
paintOrder.forEach((paintOrderLayer) => {
|
||||||
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline);
|
switch (paintOrderLayer) {
|
||||||
const textShadows: TextShadow = styles.textShadow;
|
case PAINT_ORDER_LAYER.FILL:
|
||||||
|
this.ctx.fillStyle = asString(styles.color);
|
||||||
if (textShadows.length && text.text.trim().length) {
|
|
||||||
textShadows
|
|
||||||
.slice(0)
|
|
||||||
.reverse()
|
|
||||||
.forEach((textShadow) => {
|
|
||||||
this.ctx.shadowColor = asString(textShadow.color);
|
|
||||||
this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
|
|
||||||
this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
|
|
||||||
this.ctx.shadowBlur = textShadow.blur.number;
|
|
||||||
|
|
||||||
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline);
|
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline);
|
||||||
});
|
const textShadows: TextShadow = styles.textShadow;
|
||||||
|
|
||||||
this.ctx.shadowColor = '';
|
if (textShadows.length && text.text.trim().length) {
|
||||||
this.ctx.shadowOffsetX = 0;
|
textShadows
|
||||||
this.ctx.shadowOffsetY = 0;
|
.slice(0)
|
||||||
this.ctx.shadowBlur = 0;
|
.reverse()
|
||||||
}
|
.forEach((textShadow) => {
|
||||||
|
this.ctx.shadowColor = asString(textShadow.color);
|
||||||
|
this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
|
||||||
|
this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
|
||||||
|
this.ctx.shadowBlur = textShadow.blur.number;
|
||||||
|
|
||||||
if (styles.textDecorationLine.length) {
|
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline);
|
||||||
this.ctx.fillStyle = asString(styles.textDecorationColor || styles.color);
|
});
|
||||||
styles.textDecorationLine.forEach((textDecorationLine) => {
|
|
||||||
switch (textDecorationLine) {
|
|
||||||
case TEXT_DECORATION_LINE.UNDERLINE:
|
|
||||||
// Draws a line at the baseline of the font
|
|
||||||
// TODO As some browsers display the line as more than 1px if the font-size is big,
|
|
||||||
// need to take that into account both in position and size
|
|
||||||
this.ctx.fillRect(
|
|
||||||
text.bounds.left,
|
|
||||||
Math.round(text.bounds.top + baseline),
|
|
||||||
text.bounds.width,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
break;
|
this.ctx.shadowColor = '';
|
||||||
case TEXT_DECORATION_LINE.OVERLINE:
|
this.ctx.shadowOffsetX = 0;
|
||||||
this.ctx.fillRect(text.bounds.left, Math.round(text.bounds.top), text.bounds.width, 1);
|
this.ctx.shadowOffsetY = 0;
|
||||||
break;
|
this.ctx.shadowBlur = 0;
|
||||||
case TEXT_DECORATION_LINE.LINE_THROUGH:
|
}
|
||||||
// TODO try and find exact position for line-through
|
|
||||||
this.ctx.fillRect(
|
if (styles.textDecorationLine.length) {
|
||||||
text.bounds.left,
|
this.ctx.fillStyle = asString(styles.textDecorationColor || styles.color);
|
||||||
Math.ceil(text.bounds.top + middle),
|
styles.textDecorationLine.forEach((textDecorationLine) => {
|
||||||
text.bounds.width,
|
switch (textDecorationLine) {
|
||||||
1
|
case TEXT_DECORATION_LINE.UNDERLINE:
|
||||||
);
|
// Draws a line at the baseline of the font
|
||||||
break;
|
// TODO As some browsers display the line as more than 1px if the font-size is big,
|
||||||
}
|
// need to take that into account both in position and size
|
||||||
});
|
this.ctx.fillRect(
|
||||||
}
|
text.bounds.left,
|
||||||
|
Math.round(text.bounds.top + baseline),
|
||||||
|
text.bounds.width,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case TEXT_DECORATION_LINE.OVERLINE:
|
||||||
|
this.ctx.fillRect(
|
||||||
|
text.bounds.left,
|
||||||
|
Math.round(text.bounds.top),
|
||||||
|
text.bounds.width,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case TEXT_DECORATION_LINE.LINE_THROUGH:
|
||||||
|
// TODO try and find exact position for line-through
|
||||||
|
this.ctx.fillRect(
|
||||||
|
text.bounds.left,
|
||||||
|
Math.ceil(text.bounds.top + middle),
|
||||||
|
text.bounds.width,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PAINT_ORDER_LAYER.STROKE:
|
||||||
|
if (styles.webkitTextStrokeWidth && text.text.trim().length) {
|
||||||
|
this.ctx.strokeStyle = asString(styles.webkitTextStrokeColor);
|
||||||
|
this.ctx.lineWidth = styles.webkitTextStrokeWidth;
|
||||||
|
this.ctx.lineJoin = !!(window as any).chrome ? 'miter' : 'round';
|
||||||
|
this.ctx.strokeText(text.text, text.bounds.left, text.bounds.top + baseline);
|
||||||
|
}
|
||||||
|
this.ctx.strokeStyle = '';
|
||||||
|
this.ctx.lineWidth = 0;
|
||||||
|
this.ctx.lineJoin = 'miter';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +400,7 @@ export class CanvasRenderer {
|
|||||||
this.ctx.font = fontFamily;
|
this.ctx.font = fontFamily;
|
||||||
this.ctx.fillStyle = asString(styles.color);
|
this.ctx.fillStyle = asString(styles.color);
|
||||||
|
|
||||||
this.ctx.textBaseline = 'middle';
|
this.ctx.textBaseline = 'alphabetic';
|
||||||
this.ctx.textAlign = canvasTextAlign(container.styles.textAlign);
|
this.ctx.textAlign = canvasTextAlign(container.styles.textAlign);
|
||||||
|
|
||||||
const bounds = contentBox(container);
|
const bounds = contentBox(container);
|
||||||
@ -409,7 +433,7 @@ export class CanvasRenderer {
|
|||||||
baseline
|
baseline
|
||||||
);
|
);
|
||||||
this.ctx.restore();
|
this.ctx.restore();
|
||||||
this.ctx.textBaseline = 'bottom';
|
this.ctx.textBaseline = 'alphabetic';
|
||||||
this.ctx.textAlign = 'left';
|
this.ctx.textAlign = 'left';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,7 +451,7 @@ export class CanvasRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (paint.listValue && container.styles.listStyleType !== LIST_STYLE_TYPE.NONE) {
|
} else if (paint.listValue && container.styles.listStyleType !== LIST_STYLE_TYPE.NONE) {
|
||||||
const [fontFamily, fontSize] = this.createFontStyle(styles);
|
const [fontFamily] = this.createFontStyle(styles);
|
||||||
|
|
||||||
this.ctx.font = fontFamily;
|
this.ctx.font = fontFamily;
|
||||||
this.ctx.fillStyle = asString(styles.color);
|
this.ctx.fillStyle = asString(styles.color);
|
||||||
@ -441,12 +465,10 @@ export class CanvasRenderer {
|
|||||||
computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 1
|
computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 1
|
||||||
);
|
);
|
||||||
|
|
||||||
const {baseline} = this.fontMetrics.getMetrics(fontFamily, fontSize);
|
|
||||||
|
|
||||||
this.renderTextWithLetterSpacing(
|
this.renderTextWithLetterSpacing(
|
||||||
new TextBounds(paint.listValue, bounds),
|
new TextBounds(paint.listValue, bounds),
|
||||||
styles.letterSpacing,
|
styles.letterSpacing,
|
||||||
baseline
|
computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 2
|
||||||
);
|
);
|
||||||
this.ctx.textBaseline = 'bottom';
|
this.ctx.textBaseline = 'bottom';
|
||||||
this.ctx.textAlign = 'left';
|
this.ctx.textAlign = 'left';
|
||||||
@ -504,11 +526,14 @@ export class CanvasRenderer {
|
|||||||
|
|
||||||
mask(paths: Path[]): void {
|
mask(paths: Path[]): void {
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
|
this.ctx.save();
|
||||||
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
this.ctx.moveTo(0, 0);
|
this.ctx.moveTo(0, 0);
|
||||||
this.ctx.lineTo(this.canvas.width, 0);
|
this.ctx.lineTo(this.canvas.width, 0);
|
||||||
this.ctx.lineTo(this.canvas.width, this.canvas.height);
|
this.ctx.lineTo(this.canvas.width, this.canvas.height);
|
||||||
this.ctx.lineTo(0, this.canvas.height);
|
this.ctx.lineTo(0, this.canvas.height);
|
||||||
this.ctx.lineTo(0, 0);
|
this.ctx.lineTo(0, 0);
|
||||||
|
this.ctx.restore();
|
||||||
this.formatPath(paths.slice(0).reverse());
|
this.formatPath(paths.slice(0).reverse());
|
||||||
this.ctx.closePath();
|
this.ctx.closePath();
|
||||||
}
|
}
|
||||||
@ -554,7 +579,8 @@ export class CanvasRenderer {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvas = (this.canvas.ownerDocument as Document).createElement('canvas');
|
const ownerDocument = this.canvas.ownerDocument ?? document;
|
||||||
|
const canvas = ownerDocument.createElement('canvas');
|
||||||
canvas.width = Math.max(1, width);
|
canvas.width = Math.max(1, width);
|
||||||
canvas.height = Math.max(1, height);
|
canvas.height = Math.max(1, height);
|
||||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||||
@ -700,18 +726,19 @@ export class CanvasRenderer {
|
|||||||
await this.renderBackgroundImage(paint.container);
|
await this.renderBackgroundImage(paint.container);
|
||||||
|
|
||||||
this.ctx.restore();
|
this.ctx.restore();
|
||||||
|
const borderBoxArea = calculateBorderBoxPath(paint.curves);
|
||||||
|
|
||||||
styles.boxShadow
|
styles.boxShadow
|
||||||
.slice(0)
|
.slice(0)
|
||||||
.reverse()
|
.reverse()
|
||||||
.forEach((shadow) => {
|
.forEach((shadow) => {
|
||||||
this.ctx.save();
|
this.ctx.save();
|
||||||
const borderBoxArea = calculateBorderBoxPath(paint.curves);
|
|
||||||
const maskOffset = shadow.inset ? 0 : MASK_OFFSET;
|
const maskOffset = shadow.inset ? 0 : MASK_OFFSET;
|
||||||
|
const shadowDirection = shadow.inset ? 1 : -1;
|
||||||
const shadowPaintingArea = transformPath(
|
const shadowPaintingArea = transformPath(
|
||||||
borderBoxArea,
|
borderBoxArea,
|
||||||
-maskOffset + (shadow.inset ? 1 : -1) * shadow.spread.number,
|
shadow.offsetX.number - maskOffset + shadowDirection * shadow.spread.number,
|
||||||
(shadow.inset ? 1 : -1) * shadow.spread.number,
|
shadow.offsetY.number + shadowDirection * shadow.spread.number,
|
||||||
shadow.spread.number * (shadow.inset ? -2 : 2),
|
shadow.spread.number * (shadow.inset ? -2 : 2),
|
||||||
shadow.spread.number * (shadow.inset ? -2 : 2)
|
shadow.spread.number * (shadow.inset ? -2 : 2)
|
||||||
);
|
);
|
||||||
@ -726,8 +753,8 @@ export class CanvasRenderer {
|
|||||||
this.path(shadowPaintingArea);
|
this.path(shadowPaintingArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ctx.shadowOffsetX = shadow.offsetX.number + maskOffset;
|
this.ctx.shadowOffsetX = maskOffset;
|
||||||
this.ctx.shadowOffsetY = shadow.offsetY.number;
|
this.ctx.shadowOffsetY = 0;
|
||||||
this.ctx.shadowColor = asString(shadow.color);
|
this.ctx.shadowColor = asString(shadow.color);
|
||||||
this.ctx.shadowBlur = shadow.blur.number;
|
this.ctx.shadowBlur = shadow.blur.number;
|
||||||
this.ctx.fillStyle = shadow.inset ? asString(shadow.color) : 'rgba(0,0,0,1)';
|
this.ctx.fillStyle = shadow.inset ? asString(shadow.color) : 'rgba(0,0,0,1)';
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
margin: 10px;
|
margin: 10px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
// border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -40,5 +40,13 @@
|
|||||||
<text x="65" y="35" class="canvas">canvas</text>
|
<text x="65" y="35" class="canvas">canvas</text>
|
||||||
</svg>
|
</svg>
|
||||||
<img width="200" height="200" src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjMwNiIgaGVpZ2h0PSIyOTYiPjxkZWZzIGlkPSJkZWZzNCIgLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYyLjQ2OTk1LC00NzcuMjg2MykiIGlkPSJsYXllcjEiPjxwYXRoIGQ9Im0gMzE0LjE1NzQ1LDQ4MS42OTU1OCBjIC01OS4yMDA4OSwwLjUzNzc0IC0xMTQuODA5NzksMzYuNzIyMTkgLTEzNy4zMTI1LDk1LjM0Mzc1IC0yOS4zOTEyOSw3Ni41NjY5MyA4LjgzOTMyLDE2Mi40NTI0NiA4NS40MDYyNSwxOTEuODQzNzUgbCAzNC4wMzEyNSwtODguNjg3NSBjIC0yMC4wNjc4LC03LjcxMzU4IC0zNC4zMTI1LC0yNy4xNTMyNCAtMzQuMzEyNSwtNDkuOTM3NSAwLC0yOS41NDcyMyAyMy45NTI3NywtNTMuNSA1My41LC01My41IDI5LjU0NzIzLDAgNTMuNSwyMy45NTI3NyA1My41LDUzLjUgMCwyMi43ODQyNiAtMTQuMjQ0Nyw0Mi4yMjM5MiAtMzQuMzEyNSw0OS45Mzc1IGwgMzQuMDMxMjUsODguNjg3NSBjIDM5LjI5MDg1LC0xNS4wODIzNCA3MC4zMjM5LC00Ni4xMTU0IDg1LjQwNjI1LC04NS40MDYyNSAyOS4zOTEyOSwtNzYuNTY2OTMgLTguODM5MzIsLTE2Mi40ODM3MSAtODUuNDA2MjUsLTE5MS44NzUgLTE3Ljk0NTM3LC02Ljg4ODU5IC0zNi40MDg1MywtMTAuMDcwODcgLTU0LjUzMTI1LC05LjkwNjI1IHoiIGlkPSJwYXRoMjgzMCIgc3R5bGU9ImZpbGw6IzQwYWE1NDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzIwNTUyYTtzdHJva2Utd2lkdGg6Ny45OTk5OTk1MjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lIiAvPjwvZz48L3N2Zz4=" /></div>
|
<img width="200" height="200" src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjMwNiIgaGVpZ2h0PSIyOTYiPjxkZWZzIGlkPSJkZWZzNCIgLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYyLjQ2OTk1LC00NzcuMjg2MykiIGlkPSJsYXllcjEiPjxwYXRoIGQ9Im0gMzE0LjE1NzQ1LDQ4MS42OTU1OCBjIC01OS4yMDA4OSwwLjUzNzc0IC0xMTQuODA5NzksMzYuNzIyMTkgLTEzNy4zMTI1LDk1LjM0Mzc1IC0yOS4zOTEyOSw3Ni41NjY5MyA4LjgzOTMyLDE2Mi40NTI0NiA4NS40MDYyNSwxOTEuODQzNzUgbCAzNC4wMzEyNSwtODguNjg3NSBjIC0yMC4wNjc4LC03LjcxMzU4IC0zNC4zMTI1LC0yNy4xNTMyNCAtMzQuMzEyNSwtNDkuOTM3NSAwLC0yOS41NDcyMyAyMy45NTI3NywtNTMuNSA1My41LC01My41IDI5LjU0NzIzLDAgNTMuNSwyMy45NTI3NyA1My41LDUzLjUgMCwyMi43ODQyNiAtMTQuMjQ0Nyw0Mi4yMjM5MiAtMzQuMzEyNSw0OS45Mzc1IGwgMzQuMDMxMjUsODguNjg3NSBjIDM5LjI5MDg1LC0xNS4wODIzNCA3MC4zMjM5LC00Ni4xMTU0IDg1LjQwNjI1LC04NS40MDYyNSAyOS4zOTEyOSwtNzYuNTY2OTMgLTguODM5MzIsLTE2Mi40ODM3MSAtODUuNDA2MjUsLTE5MS44NzUgLTE3Ljk0NTM3LC02Ljg4ODU5IC0zNi40MDg1MywtMTAuMDcwODcgLTU0LjUzMTI1LC05LjkwNjI1IHoiIGlkPSJwYXRoMjgzMCIgc3R5bGU9ImZpbGw6IzQwYWE1NDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzIwNTUyYTtzdHJva2Utd2lkdGg6Ny45OTk5OTk1MjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lIiAvPjwvZz48L3N2Zz4=" /></div>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width=50 height=20>
|
||||||
|
<g data-z-index="0.1" opacity="1" transform="translate(-974,-30) scale(1 1)">
|
||||||
|
<path fill="#7cb5ec" d="M 990 37.734399999999994 A 4 4 0 1 1 990.0039999993332 37.73439800000016 Z" opacity="1" stroke-width="0.00015790535835003006"></path>
|
||||||
|
<path fill="#7cb5ec" d="M 999 37.734399999999994 A 4 4 0 1 1 999.0039999993332 37.73439800000016 Z" opacity="1" stroke-width="0.00015790535835003006"></path>
|
||||||
|
<path fill="#7cb5ec" d="M 1009 37.734399999999994 A 4 4 0 1 1 1009.0039999993332 37.73439800000016 Z" opacity="1" stroke-width="0.00015790535835003006"></path>
|
||||||
|
<path fill="#7cb5ec" d="M 1019 37.734399999999994 A 4 4 0 1 1 1019.0039999993332 37.73439800000016 Z" opacity="1" stroke-width="0.00015790535835003006"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Reference in New Issue
Block a user