Implement support for multiple text-transforms with independent colors

This commit is contained in:
Niklas von Hertzen 2017-08-03 21:47:35 +08:00
parent ad1119a76c
commit f6a5153d99
5 changed files with 184 additions and 10 deletions

View File

@ -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
View 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}`];
}
}

View File

@ -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);
});

View File

@ -3,7 +3,7 @@
<head>
<title>Inline text in the top element</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../test.js"></script>
<script type="text/javascript" src="../../test.js"></script>
<style>
span {
color:blue;

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<title>Text-decoration:underline tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<script type="text/javascript">
function setUp() {
$('body').empty();
$.each(['arial','verdana','tahoma','courier new'],function(i,e){
var div = $('<div />').css('font-family',e).appendTo('body');
for(var i=0;i<=10;i++){
$('<div />').text('Testing texts').css('margin-top',1).css('border','1px solid black').css('font-size',(16+i*6)).appendTo(div);
}
});
}
</script>
<style>
.small{
font-size:14px;
}
.medium{
font-size:18px;
}
.large{
font-size:24px;
}
div{
text-decoration:underline overline line-through;
text-decoration-color: red;
}
.lineheight{
line-height:40px;
}
h2 {
clear:both;
}
</style>
</head>
<body>
Creating content through JavaScript
</body>
</html>