From 6554d4c8c860faf91fda0af4d6a9bf7fe9381d07 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Sat, 5 Aug 2017 23:34:12 +0800 Subject: [PATCH] Implement textShadow rendering (Fix #499 and #908) --- src/CanvasRenderer.js | 19 +++++++++++++++- src/NodeContainer.js | 4 ++++ src/parsing/textShadow.js | 42 ++++++++++++++++++++++++++++++++++++ tests/cases/text/shadow.html | 14 ++++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 src/parsing/textShadow.js diff --git a/src/CanvasRenderer.js b/src/CanvasRenderer.js index f0af78f..029f3d0 100644 --- a/src/CanvasRenderer.js +++ b/src/CanvasRenderer.js @@ -6,6 +6,7 @@ import type Size from './drawing/Size'; import type {BackgroundImage} from './parsing/background'; import type {Border, BorderSide} from './parsing/border'; +import type {TextShadow} from './parsing/textShadow'; import type {Path, BoundCurves} from './Bounds'; import type {ImageStore, ImageElement} from './ImageLoader'; @@ -163,7 +164,23 @@ export default class CanvasRenderer { 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); + if (container.style.textShadow && text.text.trim().length) { + container.style.textShadow.slice(0).reverse().forEach(textShadow => { + this.ctx.shadowColor = textShadow.color.toString(); + this.ctx.shadowOffsetX = textShadow.offsetX * this.options.scale; + this.ctx.shadowOffsetY = textShadow.offsetY * this.options.scale; + this.ctx.shadowBlur = textShadow.blur; + + this.ctx.fillText( + text.text, + text.bounds.left, + text.bounds.top + text.bounds.height + ); + }); + } else { + 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 => { diff --git a/src/NodeContainer.js b/src/NodeContainer.js index 218b052..cf62e59 100644 --- a/src/NodeContainer.js +++ b/src/NodeContainer.js @@ -10,6 +10,7 @@ import type {Font} from './parsing/font'; import type {Overflow} from './parsing/overflow'; import type {Padding} from './parsing/padding'; import type {Position} from './parsing/position'; +import type {TextShadow} from './parsing/textShadow'; import type {TextTransform} from './parsing/textTransform'; import type {TextDecoration} from './parsing/textDecoration'; import type {Transform} from './parsing/transform'; @@ -35,6 +36,7 @@ import {parseOverflow, OVERFLOW} from './parsing/overflow'; import {parsePadding} from './parsing/padding'; import {parsePosition, POSITION} from './parsing/position'; import {parseTextDecoration} from './parsing/textDecoration'; +import {parseTextShadow} from './parsing/textShadow'; import {parseTextTransform} from './parsing/textTransform'; import {parseTransform} from './parsing/transform'; import {parseVisibility, VISIBILITY} from './parsing/visibility'; @@ -63,6 +65,7 @@ type StyleDeclaration = { padding: Padding, position: Position, textDecoration: TextDecoration, + textShadow: Array | null, textTransform: TextTransform, transform: Transform, visibility: Visibility, @@ -106,6 +109,7 @@ export default class NodeContainer { padding: parsePadding(style), position: parsePosition(style.position), textDecoration: parseTextDecoration(style), + textShadow: parseTextShadow(style.textShadow), textTransform: parseTextTransform(style.textTransform), transform: parseTransform(style), visibility: parseVisibility(style.visibility), diff --git a/src/parsing/textShadow.js b/src/parsing/textShadow.js new file mode 100644 index 0000000..bc2b056 --- /dev/null +++ b/src/parsing/textShadow.js @@ -0,0 +1,42 @@ +/* @flow */ +'use strict'; + +import Color from '../Color'; + +export type TextShadow = { + color: Color, + offsetX: number, + offsetY: number, + blur: number +}; + +const TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){3})/g; +const TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g; + +export const parseTextShadow = (textShadow: string): Array | null => { + if (textShadow === 'none') { + return null; + } + + const shadows = textShadow.match(TEXT_SHADOW_PROPERTY); + + if (!shadows) { + return null; + } + + const shadowList = []; + + for (let i = 0; i < shadows.length; i++) { + const shadow = shadows[i].match(TEXT_SHADOW_VALUES); + if (shadow) { + shadowList.push({ + color: new Color(shadow[0]), + offsetX: shadow[1] ? parseFloat(shadow[1].replace('px', '')) : 0, + offsetY: shadow[2] ? parseFloat(shadow[2].replace('px', '')) : 0, + blur: shadow[3] ? parseFloat(shadow[3].replace('px', '')) : 0 + }); + } + } + + return shadowList; +}; diff --git a/tests/cases/text/shadow.html b/tests/cases/text/shadow.html index f353bbf..9dd72a0 100644 --- a/tests/cases/text/shadow.html +++ b/tests/cases/text/shadow.html @@ -27,6 +27,14 @@ text-decoration: underline; } + .white-text-with-blue-shadow { + text-shadow: 1px 1px 2px black, 0 0 1em blue, 0 0 0.2em blue; + color: white; + font: 1.5em Georgia, serif; + } + .red-text-shadow { + text-shadow: 0 -2px; + } @@ -39,5 +47,11 @@ testing with transparent testing with low opacity +

Sed ut perspiciatis unde omnis iste + natus error sit voluptatem accusantium doloremque laudantium, + totam rem aperiam, eaque ipsa quae ab illo inventore.

+

Sed ut perspiciatis unde omnis iste + natus error sit voluptatem accusantium doloremque laudantium, + totam rem aperiam, eaque ipsa quae ab illo inventore.