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