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:
parent
7676112445
commit
243714d299
@ -23,6 +23,7 @@ import {
|
|||||||
calculateBackgroundSize
|
calculateBackgroundSize
|
||||||
} from './parsing/background';
|
} from './parsing/background';
|
||||||
import {BORDER_STYLE} from './parsing/border';
|
import {BORDER_STYLE} from './parsing/border';
|
||||||
|
import {TEXT_DECORATION_LINE} from './parsing/textDecoration';
|
||||||
import {
|
import {
|
||||||
parsePathForBorder,
|
parsePathForBorder,
|
||||||
calculateContentBox,
|
calculateContentBox,
|
||||||
@ -30,10 +31,13 @@ import {
|
|||||||
calculatePaddingBoxPath
|
calculatePaddingBoxPath
|
||||||
} from './Bounds';
|
} from './Bounds';
|
||||||
|
|
||||||
|
import {FontMetrics} from './Font';
|
||||||
|
|
||||||
export type RenderOptions = {
|
export type RenderOptions = {
|
||||||
scale: number,
|
scale: number,
|
||||||
backgroundColor: ?Color,
|
backgroundColor: ?Color,
|
||||||
imageStore: ImageStore
|
imageStore: ImageStore,
|
||||||
|
fontMetrics: FontMetrics
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class CanvasRenderer {
|
export default class CanvasRenderer {
|
||||||
@ -142,11 +146,55 @@ export default class CanvasRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTextNode(textContainer: TextContainer) {
|
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);
|
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) {
|
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 {Bounds, parseDocumentSize} from './Bounds';
|
||||||
import {cloneWindow} from './Clone';
|
import {cloneWindow} from './Clone';
|
||||||
import Color from './Color';
|
import Color from './Color';
|
||||||
|
import {FontMetrics} from './Font';
|
||||||
|
|
||||||
export type Options = {
|
export type Options = {
|
||||||
async: ?boolean,
|
async: ?boolean,
|
||||||
@ -62,8 +63,8 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
|||||||
}
|
}
|
||||||
const imageLoader = new ImageLoader(options, logger);
|
const imageLoader = new ImageLoader(options, logger);
|
||||||
const stack = NodeParser(clonedElement, imageLoader, logger);
|
const stack = NodeParser(clonedElement, imageLoader, logger);
|
||||||
const size =
|
const clonedDocument = clonedElement.ownerDocument;
|
||||||
options.type === 'view' ? windowBounds : parseDocumentSize(clonedElement.ownerDocument);
|
const size = options.type === 'view' ? windowBounds : parseDocumentSize(clonedDocument);
|
||||||
const width = size.width;
|
const width = size.width;
|
||||||
const height = size.height;
|
const height = size.height;
|
||||||
canvas.width = Math.floor(width * options.scale);
|
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
|
// http://www.w3.org/TR/css3-background/#special-backgrounds
|
||||||
const backgroundColor =
|
const backgroundColor =
|
||||||
clonedElement === clonedElement.ownerDocument.documentElement
|
clonedElement === clonedDocument.documentElement
|
||||||
? stack.container.style.background.backgroundColor.isTransparent()
|
? stack.container.style.background.backgroundColor.isTransparent()
|
||||||
? clonedElement.ownerDocument.body
|
? clonedDocument.body
|
||||||
? new Color(getComputedStyle(clonedElement.ownerDocument.body).backgroundColor)
|
? new Color(getComputedStyle(clonedDocument.body).backgroundColor)
|
||||||
: null
|
: null
|
||||||
: stack.container.style.background.backgroundColor
|
: stack.container.style.background.backgroundColor
|
||||||
: null;
|
: null;
|
||||||
@ -90,10 +91,13 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fontMetrics = new FontMetrics(clonedDocument);
|
||||||
|
|
||||||
const renderer = new CanvasRenderer(canvas, {
|
const renderer = new CanvasRenderer(canvas, {
|
||||||
scale: options.scale,
|
scale: options.scale,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
imageStore
|
imageStore,
|
||||||
|
fontMetrics
|
||||||
});
|
});
|
||||||
return renderer.render(stack);
|
return renderer.render(stack);
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Inline text in the top element</title>
|
<title>Inline text in the top element</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<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>
|
<style>
|
||||||
span {
|
span {
|
||||||
color:blue;
|
color:blue;
|
50
tests/cases/text/multiple.html
Normal file
50
tests/cases/text/multiple.html
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user