diff --git a/src/CanvasRenderer.js b/src/CanvasRenderer.js index 82124b2..6d6284a 100644 --- a/src/CanvasRenderer.js +++ b/src/CanvasRenderer.js @@ -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) { diff --git a/src/Font.js b/src/Font.js new file mode 100644 index 0000000..595adac --- /dev/null +++ b/src/Font.js @@ -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}`]; + } +} diff --git a/src/index.js b/src/index.js index d423cbb..a594653 100644 --- a/src/index.js +++ b/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 Inline text in the top element - + + + + + Creating content through JavaScript + +