mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
feat: support font color gradient
This commit is contained in:
parent
fc8c95ea74
commit
d7f681a60b
@ -44,7 +44,8 @@ import {overflow, OVERFLOW} from './property-descriptors/overflow';
|
||||
import {overflowWrap} from './property-descriptors/overflow-wrap';
|
||||
import {paddingBottom, paddingLeft, paddingRight, paddingTop} from './property-descriptors/padding';
|
||||
import {textAlign} from './property-descriptors/text-align';
|
||||
import {position, POSITION} from './property-descriptors/position';
|
||||
import { position, POSITION } from './property-descriptors/position';
|
||||
import { textFillColor } from './property-descriptors/text-fill-color';
|
||||
import {textShadow} from './property-descriptors/text-shadow';
|
||||
import {textTransform} from './property-descriptors/text-transform';
|
||||
import {textStrokeColor} from './property-descriptors/text-stroke-color';
|
||||
@ -132,6 +133,7 @@ export class CSSParsedDeclaration {
|
||||
paddingLeft: LengthPercentage;
|
||||
position: ReturnType<typeof position.parse>;
|
||||
textAlign: ReturnType<typeof textAlign.parse>;
|
||||
textFillColor: Color;
|
||||
textDecorationColor: Color;
|
||||
textDecorationLine: ReturnType<typeof textDecorationLine.parse>;
|
||||
textShadow: ReturnType<typeof textShadow.parse>;
|
||||
@ -200,6 +202,7 @@ export class CSSParsedDeclaration {
|
||||
this.paddingLeft = parse(paddingLeft, declaration.paddingLeft);
|
||||
this.position = parse(position, declaration.position);
|
||||
this.textAlign = parse(textAlign, declaration.textAlign);
|
||||
this.textFillColor = parse(textFillColor, declaration.textFillColor)
|
||||
this.textDecorationColor = parse(textDecorationColor, declaration.textDecorationColor || declaration.color);
|
||||
this.textDecorationLine = parse(textDecorationLine, declaration.textDecorationLine);
|
||||
this.textShadow = parse(textShadow, declaration.textShadow);
|
||||
|
@ -3,7 +3,8 @@ import {CSSValue, isIdentToken} from '../syntax/parser';
|
||||
export enum BACKGROUND_CLIP {
|
||||
BORDER_BOX = 0,
|
||||
PADDING_BOX = 1,
|
||||
CONTENT_BOX = 2
|
||||
CONTENT_BOX = 2,
|
||||
TEXT = 3
|
||||
}
|
||||
|
||||
export type BackgroundClip = BACKGROUND_CLIP[];
|
||||
@ -21,6 +22,8 @@ export const backgroundClip: IPropertyListDescriptor<BackgroundClip> = {
|
||||
return BACKGROUND_CLIP.PADDING_BOX;
|
||||
case 'content-box':
|
||||
return BACKGROUND_CLIP.CONTENT_BOX;
|
||||
case 'text':
|
||||
return BACKGROUND_CLIP.TEXT;
|
||||
}
|
||||
}
|
||||
return BACKGROUND_CLIP.BORDER_BOX;
|
||||
|
9
src/css/property-descriptors/text-fill-color.ts
Normal file
9
src/css/property-descriptors/text-fill-color.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {IPropertyTypeValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
|
||||
export const textFillColor: IPropertyTypeValueDescriptor = {
|
||||
name: `text-fill-color`,
|
||||
initialValue: 'transparent',
|
||||
prefix: true,
|
||||
type: PropertyDescriptorParsingType.TYPE_VALUE,
|
||||
format: 'color'
|
||||
};
|
1
src/global.d.ts
vendored
1
src/global.d.ts
vendored
@ -1,6 +1,7 @@
|
||||
interface CSSStyleDeclaration {
|
||||
textDecorationColor: string | null;
|
||||
textDecorationLine: string | null;
|
||||
textFillColor: string | null;
|
||||
overflowWrap: string | null;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {BACKGROUND_CLIP} from '../../css/property-descriptors/background-clip';
|
||||
import {BoundCurves, calculateBorderBoxPath, calculateContentBoxPath, calculatePaddingBoxPath} from '../bound-curves';
|
||||
import {isBezierCurve} from '../bezier-curve';
|
||||
import {Vector} from '../vector';
|
||||
import {CSSImageType, CSSURLImage, isLinearGradient, isRadialGradient} from '../../css/types/image';
|
||||
import {CSSImageType, CSSLinearGradientImage, CSSRadialGradientImage, CSSURLImage, isLinearGradient, isRadialGradient} from '../../css/types/image';
|
||||
import {parsePathForBorder} from '../border';
|
||||
import {Cache} from '../../core/cache-storage';
|
||||
import {calculateBackgroundRendering, getBackgroundValueForIndex} from '../background';
|
||||
@ -38,7 +38,8 @@ 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 {processImage, isSupportedFilter} from '../image-filter';
|
||||
import { processImage, isSupportedFilter } from '../image-filter';
|
||||
import {isFontColorGradient } from '../font-color-gradient'
|
||||
|
||||
export type RenderConfigurations = RenderOptions & {
|
||||
backgroundColor: Color | null;
|
||||
@ -172,13 +173,56 @@ export class CanvasRenderer {
|
||||
];
|
||||
}
|
||||
|
||||
async renderTextNode(text: TextContainer, styles: CSSParsedDeclaration) {
|
||||
async renderTextNode(text: TextContainer, styles: CSSParsedDeclaration, container: ElementContainer) {
|
||||
const [font, fontFamily, fontSize] = this.createFontStyle(styles);
|
||||
|
||||
this.ctx.font = font;
|
||||
let fillStyle: CanvasGradient|string = asString(styles.color);
|
||||
if (isFontColorGradient(styles)) {
|
||||
if (isLinearGradient(styles.backgroundImage[0])) {
|
||||
const backgroundImage = styles.backgroundImage[0] as CSSLinearGradientImage
|
||||
const [, x, y, width, height] = calculateBackgroundRendering(container, 0, [null, null, null]);
|
||||
const [lineLength, x0, x1, y0, y1] = calculateGradientDirection(backgroundImage.angle, width, height);
|
||||
if (Math.round(Math.abs(x0)) === Math.round(Math.abs(x1))) {
|
||||
const gradient = this.ctx.createLinearGradient(x, y0+y, x, y1+y);
|
||||
processColorStops(backgroundImage.stops, lineLength).forEach(colorStop =>
|
||||
gradient.addColorStop(colorStop.stop, asString(colorStop.color))
|
||||
);
|
||||
fillStyle = gradient;
|
||||
} else if (Math.round(Math.abs(y0)) === Math.round(Math.abs(y1))) {
|
||||
const gradient = this.ctx.createLinearGradient(x+x0, y, x+x1, y);
|
||||
processColorStops(backgroundImage.stops, lineLength).forEach(colorStop =>
|
||||
gradient.addColorStop(colorStop.stop, asString(colorStop.color))
|
||||
);
|
||||
fillStyle = gradient;
|
||||
}
|
||||
} else if (isRadialGradient(styles.backgroundImage[0])) {
|
||||
const backgroundImage = styles.backgroundImage[0] as CSSRadialGradientImage
|
||||
const [, left, top, width, height] = calculateBackgroundRendering(container, 0, [
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]);
|
||||
const position = backgroundImage.position.length === 0 ? [FIFTY_PERCENT] : backgroundImage.position;
|
||||
const x = getAbsoluteValue(position[0], width);
|
||||
const y = getAbsoluteValue(position[position.length - 1], height);
|
||||
|
||||
const [rx,] = calculateRadius(backgroundImage, x, y, width, height);
|
||||
if (rx > 0 && rx > 0) {
|
||||
const radialGradient = this.ctx.createRadialGradient(left + x, top + y, 0, left + x, top + y, rx);
|
||||
|
||||
processColorStops(backgroundImage.stops, rx * 2).forEach(colorStop =>
|
||||
radialGradient.addColorStop(colorStop.stop, asString(colorStop.color))
|
||||
);
|
||||
|
||||
fillStyle = radialGradient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
text.textBounds.forEach(text => {
|
||||
this.ctx.fillStyle = asString(styles.color);
|
||||
this.ctx.fillStyle = fillStyle;
|
||||
this.renderTextWithLetterSpacing(text, styles.letterSpacing);
|
||||
const textShadows: TextShadow = styles.textShadow;
|
||||
|
||||
@ -286,7 +330,7 @@ export class CanvasRenderer {
|
||||
const curves = paint.curves;
|
||||
const styles = container.styles;
|
||||
for (const child of container.textNodes) {
|
||||
await this.renderTextNode(child, styles);
|
||||
await this.renderTextNode(child, styles, container);
|
||||
}
|
||||
|
||||
if (container instanceof ImageElementContainer) {
|
||||
@ -563,6 +607,7 @@ export class CanvasRenderer {
|
||||
|
||||
async renderBackgroundImage(container: ElementContainer) {
|
||||
let index = container.styles.backgroundImage.length - 1;
|
||||
if (isFontColorGradient(container.styles)) return false;
|
||||
for (const backgroundImage of container.styles.backgroundImage.slice(0).reverse()) {
|
||||
if (backgroundImage.type === CSSImageType.URL) {
|
||||
let image;
|
||||
@ -588,7 +633,6 @@ export class CanvasRenderer {
|
||||
} else if (isLinearGradient(backgroundImage)) {
|
||||
const [path, x, y, width, height] = calculateBackgroundRendering(container, index, [null, null, null]);
|
||||
const [lineLength, x0, x1, y0, y1] = calculateGradientDirection(backgroundImage.angle, width, height);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
12
src/render/font-color-gradient.ts
Normal file
12
src/render/font-color-gradient.ts
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
import { CSSParsedDeclaration } from '../css/index';
|
||||
import { isLinearGradient, isRadialGradient } from '../css/types/image';
|
||||
import { BACKGROUND_CLIP } from '../css/property-descriptors/background-clip';
|
||||
|
||||
export const isFontColorGradient = (styles: CSSParsedDeclaration) => {
|
||||
if (styles.backgroundImage.length === 1 && (isLinearGradient(styles.backgroundImage[0]) || isRadialGradient(styles.backgroundImage[0]))) {
|
||||
return (styles.textFillColor === 0 || styles.color === 0) && styles.backgroundClip.length === 1 && styles.backgroundClip[0] === BACKGROUND_CLIP.TEXT;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
57
tests/reftests/text/font-color-gradient.html
Normal file
57
tests/reftests/text/font-color-gradient.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Text tests</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
font-size: 40px;
|
||||
font-weight: bolder;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gradient-text-one {
|
||||
background-image: -webkit-linear-gradient(bottom, red, #fd8403, yellow);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.gradient-text-two {
|
||||
background-image: -webkit-linear-gradient(right, red, blue);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.gradient-text-three {
|
||||
background-image: radial-gradient(red, yellow, green);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>font-color-gradient</h1>
|
||||
<div style="margin-top: 10px;">
|
||||
<span class="gradient-text gradient-text-one">Hello world</span>
|
||||
<span>no effects</span>
|
||||
</div>
|
||||
<div style="margin-top: 10px;">
|
||||
<span class="gradient-text gradient-text-two">Hello world</span>
|
||||
</div>
|
||||
<div style="margin-top: 10px;">
|
||||
<span class="gradient-text gradient-text-three">Hello world</span>
|
||||
</div>
|
||||
<div style="font-family: 'Inconsolata', Monaco, Consolas, 'Andale Mono', monospace">npm install --save html2canvas
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user