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()
|
||||
- linear-gradient()
|
||||
- radial-gradient()
|
||||
- background-origin
|
||||
- background-origin
|
||||
- background-position
|
||||
- background-size
|
||||
- background-size
|
||||
- border
|
||||
- border-color
|
||||
- border-radius
|
||||
@ -50,6 +50,7 @@ Below is a list of all the supported CSS properties and values.
|
||||
- overflow
|
||||
- overflow-wrap
|
||||
- padding
|
||||
- paint-order
|
||||
- position
|
||||
- right
|
||||
- text-align
|
||||
@ -58,17 +59,18 @@ Below is a list of all the supported CSS properties and values.
|
||||
- text-decoration-line
|
||||
- text-decoration-style (**Only supports `solid`**)
|
||||
- text-shadow
|
||||
- text-transform
|
||||
- text-transform
|
||||
- top
|
||||
- transform (**Limited support**)
|
||||
- visibility
|
||||
- white-space
|
||||
- width
|
||||
- webkit-text-stroke
|
||||
- word-break
|
||||
- word-spacing
|
||||
- word-wrap
|
||||
- z-index
|
||||
|
||||
|
||||
## Unsupported CSS properties
|
||||
These CSS properties are **NOT** currently supported
|
||||
- [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 {quotes} from './property-descriptors/quotes';
|
||||
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 {
|
||||
backgroundClip: ReturnType<typeof backgroundClip.parse>;
|
||||
@ -125,6 +128,7 @@ export class CSSParsedDeclaration {
|
||||
paddingRight: LengthPercentage;
|
||||
paddingBottom: LengthPercentage;
|
||||
paddingLeft: LengthPercentage;
|
||||
paintOrder: ReturnType<typeof paintOrder.parse>;
|
||||
position: ReturnType<typeof position.parse>;
|
||||
textAlign: ReturnType<typeof textAlign.parse>;
|
||||
textDecorationColor: Color;
|
||||
@ -134,6 +138,8 @@ export class CSSParsedDeclaration {
|
||||
transform: ReturnType<typeof transform.parse>;
|
||||
transformOrigin: ReturnType<typeof transformOrigin.parse>;
|
||||
visibility: ReturnType<typeof visibility.parse>;
|
||||
webkitTextStrokeColor: Color;
|
||||
webkitTextStrokeWidth: ReturnType<typeof webkitTextStrokeWidth.parse>;
|
||||
wordBreak: ReturnType<typeof wordBreak.parse>;
|
||||
zIndex: ReturnType<typeof zIndex.parse>;
|
||||
|
||||
@ -189,6 +195,7 @@ export class CSSParsedDeclaration {
|
||||
this.paddingRight = parse(paddingRight, declaration.paddingRight);
|
||||
this.paddingBottom = parse(paddingBottom, declaration.paddingBottom);
|
||||
this.paddingLeft = parse(paddingLeft, declaration.paddingLeft);
|
||||
this.paintOrder = parse(paintOrder, declaration.paintOrder);
|
||||
this.position = parse(position, declaration.position);
|
||||
this.textAlign = parse(textAlign, declaration.textAlign);
|
||||
this.textDecorationColor = parse(textDecorationColor, declaration.textDecorationColor ?? declaration.color);
|
||||
@ -201,6 +208,8 @@ export class CSSParsedDeclaration {
|
||||
this.transform = parse(transform, declaration.transform);
|
||||
this.transformOrigin = parse(transformOrigin, declaration.transformOrigin);
|
||||
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.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 => {
|
||||
// Edge does not provide value for cssText
|
||||
for (let i = style.length - 1; i >= 0; i--) {
|
||||
const property = style.item(i);
|
||||
// Safari shows pseudoelements if content is set
|
||||
if (property !== 'content') {
|
||||
if (ignoredStyleProperties.indexOf(property) === -1) {
|
||||
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 {IFrameElementContainer} from '../../dom/replaced-elements/iframe-element-container';
|
||||
import {TextShadow} from '../../css/property-descriptors/text-shadow';
|
||||
import {PAINT_ORDER_LAYER} from '../../css/property-descriptors/paint-order';
|
||||
|
||||
export type RenderConfigurations = RenderOptions & {
|
||||
backgroundColor: Color | null;
|
||||
@ -179,65 +180,88 @@ export class CanvasRenderer {
|
||||
const [font, fontFamily, fontSize] = this.createFontStyle(styles);
|
||||
|
||||
this.ctx.font = font;
|
||||
this.ctx.textBaseline = 'alphabetic';
|
||||
|
||||
this.ctx.textBaseline = 'alphabetic';
|
||||
const {baseline, middle} = this.fontMetrics.getMetrics(fontFamily, fontSize);
|
||||
const paintOrder = styles.paintOrder;
|
||||
|
||||
text.textBounds.forEach((text) => {
|
||||
this.ctx.fillStyle = asString(styles.color);
|
||||
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline);
|
||||
const textShadows: TextShadow = styles.textShadow;
|
||||
|
||||
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;
|
||||
|
||||
paintOrder.forEach((paintOrderLayer) => {
|
||||
switch (paintOrderLayer) {
|
||||
case PAINT_ORDER_LAYER.FILL:
|
||||
this.ctx.fillStyle = asString(styles.color);
|
||||
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline);
|
||||
});
|
||||
const textShadows: TextShadow = styles.textShadow;
|
||||
|
||||
this.ctx.shadowColor = '';
|
||||
this.ctx.shadowOffsetX = 0;
|
||||
this.ctx.shadowOffsetY = 0;
|
||||
this.ctx.shadowBlur = 0;
|
||||
}
|
||||
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;
|
||||
|
||||
if (styles.textDecorationLine.length) {
|
||||
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
|
||||
);
|
||||
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.ctx.shadowColor = '';
|
||||
this.ctx.shadowOffsetX = 0;
|
||||
this.ctx.shadowOffsetY = 0;
|
||||
this.ctx.shadowBlur = 0;
|
||||
}
|
||||
|
||||
if (styles.textDecorationLine.length) {
|
||||
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;
|
||||
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.fillStyle = asString(styles.color);
|
||||
|
||||
this.ctx.textBaseline = 'middle';
|
||||
this.ctx.textBaseline = 'alphabetic';
|
||||
this.ctx.textAlign = canvasTextAlign(container.styles.textAlign);
|
||||
|
||||
const bounds = contentBox(container);
|
||||
@ -409,7 +433,7 @@ export class CanvasRenderer {
|
||||
baseline
|
||||
);
|
||||
this.ctx.restore();
|
||||
this.ctx.textBaseline = 'bottom';
|
||||
this.ctx.textBaseline = 'alphabetic';
|
||||
this.ctx.textAlign = 'left';
|
||||
}
|
||||
|
||||
@ -427,7 +451,7 @@ export class CanvasRenderer {
|
||||
}
|
||||
}
|
||||
} 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.fillStyle = asString(styles.color);
|
||||
@ -441,12 +465,10 @@ export class CanvasRenderer {
|
||||
computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 1
|
||||
);
|
||||
|
||||
const {baseline} = this.fontMetrics.getMetrics(fontFamily, fontSize);
|
||||
|
||||
this.renderTextWithLetterSpacing(
|
||||
new TextBounds(paint.listValue, bounds),
|
||||
styles.letterSpacing,
|
||||
baseline
|
||||
computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 2
|
||||
);
|
||||
this.ctx.textBaseline = 'bottom';
|
||||
this.ctx.textAlign = 'left';
|
||||
@ -504,11 +526,14 @@ export class CanvasRenderer {
|
||||
|
||||
mask(paths: Path[]): void {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.save();
|
||||
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
this.ctx.moveTo(0, 0);
|
||||
this.ctx.lineTo(this.canvas.width, 0);
|
||||
this.ctx.lineTo(this.canvas.width, this.canvas.height);
|
||||
this.ctx.lineTo(0, this.canvas.height);
|
||||
this.ctx.lineTo(0, 0);
|
||||
this.ctx.restore();
|
||||
this.formatPath(paths.slice(0).reverse());
|
||||
this.ctx.closePath();
|
||||
}
|
||||
@ -554,7 +579,8 @@ export class CanvasRenderer {
|
||||
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.height = Math.max(1, height);
|
||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||
@ -700,18 +726,19 @@ export class CanvasRenderer {
|
||||
await this.renderBackgroundImage(paint.container);
|
||||
|
||||
this.ctx.restore();
|
||||
const borderBoxArea = calculateBorderBoxPath(paint.curves);
|
||||
|
||||
styles.boxShadow
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.forEach((shadow) => {
|
||||
this.ctx.save();
|
||||
const borderBoxArea = calculateBorderBoxPath(paint.curves);
|
||||
const maskOffset = shadow.inset ? 0 : MASK_OFFSET;
|
||||
const shadowDirection = shadow.inset ? 1 : -1;
|
||||
const shadowPaintingArea = transformPath(
|
||||
borderBoxArea,
|
||||
-maskOffset + (shadow.inset ? 1 : -1) * shadow.spread.number,
|
||||
(shadow.inset ? 1 : -1) * shadow.spread.number,
|
||||
shadow.offsetX.number - maskOffset + shadowDirection * shadow.spread.number,
|
||||
shadow.offsetY.number + shadowDirection * shadow.spread.number,
|
||||
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.ctx.shadowOffsetX = shadow.offsetX.number + maskOffset;
|
||||
this.ctx.shadowOffsetY = shadow.offsetY.number;
|
||||
this.ctx.shadowOffsetX = maskOffset;
|
||||
this.ctx.shadowOffsetY = 0;
|
||||
this.ctx.shadowColor = asString(shadow.color);
|
||||
this.ctx.shadowBlur = shadow.blur.number;
|
||||
this.ctx.fillStyle = shadow.inset ? asString(shadow.color) : 'rgba(0,0,0,1)';
|
||||
|
@ -9,7 +9,7 @@
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
min-height: 50px;
|
||||
// border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -40,5 +40,13 @@
|
||||
<text x="65" y="35" class="canvas">canvas</text>
|
||||
</svg>
|
||||
<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>
|
||||
</html>
|
||||
|
Reference in New Issue
Block a user