mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Implement support for multiple text-transforms with independent colors
This commit is contained in:
@ -23,6 +23,7 @@ import {
|
||||
calculateBackgroundSize
|
||||
} from './parsing/background';
|
||||
import {BORDER_STYLE} from './parsing/border';
|
||||
import {TEXT_DECORATION_LINE} from './parsing/textDecoration';
|
||||
import {
|
||||
parsePathForBorder,
|
||||
calculateContentBox,
|
||||
@ -30,10 +31,13 @@ import {
|
||||
calculatePaddingBoxPath
|
||||
} from './Bounds';
|
||||
|
||||
import {FontMetrics} from './Font';
|
||||
|
||||
export type RenderOptions = {
|
||||
scale: number,
|
||||
backgroundColor: ?Color,
|
||||
imageStore: ImageStore
|
||||
imageStore: ImageStore,
|
||||
fontMetrics: FontMetrics
|
||||
};
|
||||
|
||||
export default class CanvasRenderer {
|
||||
@ -142,11 +146,55 @@ export default class CanvasRenderer {
|
||||
}
|
||||
|
||||
renderTextNode(textContainer: TextContainer) {
|
||||
textContainer.bounds.forEach(this.renderText, this);
|
||||
textContainer.bounds.forEach(text => this.renderText(text, textContainer));
|
||||
}
|
||||
|
||||
renderText(text: TextBounds) {
|
||||
renderText(text: TextBounds, textContainer: TextContainer) {
|
||||
const container = textContainer.parent;
|
||||
this.ctx.fillStyle = container.style.color.toString();
|
||||
this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
|
||||
const textDecoration = container.style.textDecoration;
|
||||
if (textDecoration) {
|
||||
textDecoration.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
|
||||
const {baseline} = this.options.fontMetrics.getMetrics(
|
||||
container.style.font
|
||||
);
|
||||
this.rectangle(
|
||||
text.bounds.left,
|
||||
Math.round(text.bounds.top + baseline),
|
||||
text.bounds.width,
|
||||
1,
|
||||
textDecoration.textDecorationColor || container.style.color
|
||||
);
|
||||
break;
|
||||
case TEXT_DECORATION_LINE.OVERLINE:
|
||||
this.rectangle(
|
||||
text.bounds.left,
|
||||
Math.round(text.bounds.top),
|
||||
text.bounds.width,
|
||||
1,
|
||||
textDecoration.textDecorationColor || container.style.color
|
||||
);
|
||||
break;
|
||||
case TEXT_DECORATION_LINE.LINE_THROUGH:
|
||||
// TODO try and find exact position for line-through
|
||||
const {middle} = this.options.fontMetrics.getMetrics(container.style.font);
|
||||
this.rectangle(
|
||||
text.bounds.left,
|
||||
Math.ceil(text.bounds.top + middle),
|
||||
text.bounds.width,
|
||||
1,
|
||||
textDecoration.textDecorationColor || container.style.color
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderBackgroundImage(container: NodeContainer) {
|
||||
|
72
src/Font.js
Normal file
72
src/Font.js
Normal file
@ -0,0 +1,72 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import type {Font} from './parsing/font';
|
||||
|
||||
const SAMPLE_TEXT = 'Hidden Text';
|
||||
const SMALL_IMAGE =
|
||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
|
||||
export class FontMetrics {
|
||||
_data: {};
|
||||
_document: Document;
|
||||
|
||||
constructor(document: Document) {
|
||||
this._data = {};
|
||||
this._document = document;
|
||||
}
|
||||
_parseMetrics(font: Font) {
|
||||
const container = this._document.createElement('div');
|
||||
const img = this._document.createElement('img');
|
||||
const span = this._document.createElement('span');
|
||||
|
||||
const body = this._document.body;
|
||||
if (!body) {
|
||||
throw new Error(__DEV__ ? 'No document found for font metrics' : '');
|
||||
}
|
||||
|
||||
container.style.visibility = 'hidden';
|
||||
container.style.fontFamily = font.fontFamily;
|
||||
container.style.fontSize = font.fontSize;
|
||||
container.style.margin = '0';
|
||||
container.style.padding = '0';
|
||||
|
||||
body.appendChild(container);
|
||||
|
||||
img.src = SMALL_IMAGE;
|
||||
img.width = 1;
|
||||
img.height = 1;
|
||||
|
||||
img.style.margin = '0';
|
||||
img.style.padding = '0';
|
||||
img.style.verticalAlign = 'baseline';
|
||||
|
||||
span.style.fontFamily = font.fontFamily;
|
||||
span.style.fontSize = font.fontSize;
|
||||
span.style.margin = '0';
|
||||
span.style.padding = '0';
|
||||
|
||||
span.appendChild(this._document.createTextNode(SAMPLE_TEXT));
|
||||
container.appendChild(span);
|
||||
container.appendChild(img);
|
||||
const baseline = img.offsetTop - span.offsetTop + 2;
|
||||
|
||||
container.removeChild(span);
|
||||
container.appendChild(this._document.createTextNode(SAMPLE_TEXT));
|
||||
|
||||
container.style.lineHeight = 'normal';
|
||||
img.style.verticalAlign = 'super';
|
||||
|
||||
const middle = img.offsetTop - container.offsetTop + 2;
|
||||
|
||||
body.removeChild(container);
|
||||
|
||||
return {baseline, middle};
|
||||
}
|
||||
getMetrics(font: Font) {
|
||||
if (this._data[`${font.fontFamily} ${font.fontSize}`] === undefined) {
|
||||
this._data[`${font.fontFamily} ${font.fontSize}`] = this._parseMetrics(font);
|
||||
}
|
||||
return this._data[`${font.fontFamily} ${font.fontSize}`];
|
||||
}
|
||||
}
|
16
src/index.js
16
src/index.js
@ -8,6 +8,7 @@ import ImageLoader from './ImageLoader';
|
||||
import {Bounds, parseDocumentSize} from './Bounds';
|
||||
import {cloneWindow} from './Clone';
|
||||
import Color from './Color';
|
||||
import {FontMetrics} from './Font';
|
||||
|
||||
export type Options = {
|
||||
async: ?boolean,
|
||||
@ -62,8 +63,8 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
||||
}
|
||||
const imageLoader = new ImageLoader(options, logger);
|
||||
const stack = NodeParser(clonedElement, imageLoader, logger);
|
||||
const size =
|
||||
options.type === 'view' ? windowBounds : parseDocumentSize(clonedElement.ownerDocument);
|
||||
const clonedDocument = clonedElement.ownerDocument;
|
||||
const size = options.type === 'view' ? windowBounds : parseDocumentSize(clonedDocument);
|
||||
const width = size.width;
|
||||
const height = size.height;
|
||||
canvas.width = Math.floor(width * options.scale);
|
||||
@ -73,10 +74,10 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
||||
|
||||
// http://www.w3.org/TR/css3-background/#special-backgrounds
|
||||
const backgroundColor =
|
||||
clonedElement === clonedElement.ownerDocument.documentElement
|
||||
clonedElement === clonedDocument.documentElement
|
||||
? stack.container.style.background.backgroundColor.isTransparent()
|
||||
? clonedElement.ownerDocument.body
|
||||
? new Color(getComputedStyle(clonedElement.ownerDocument.body).backgroundColor)
|
||||
? clonedDocument.body
|
||||
? new Color(getComputedStyle(clonedDocument.body).backgroundColor)
|
||||
: null
|
||||
: stack.container.style.background.backgroundColor
|
||||
: null;
|
||||
@ -90,10 +91,13 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
||||
}
|
||||
}
|
||||
|
||||
const fontMetrics = new FontMetrics(clonedDocument);
|
||||
|
||||
const renderer = new CanvasRenderer(canvas, {
|
||||
scale: options.scale,
|
||||
backgroundColor,
|
||||
imageStore
|
||||
imageStore,
|
||||
fontMetrics
|
||||
});
|
||||
return renderer.render(stack);
|
||||
});
|
||||
|
Reference in New Issue
Block a user