From 1a30167f6a53d9a403d383115b1592d4ecd95e0e Mon Sep 17 00:00:00 2001 From: Guerric Sloan Date: Wed, 12 Jun 2013 14:54:46 -0700 Subject: [PATCH 1/4] Basic implementation of text-shadow --- src/Parse.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Parse.js b/src/Parse.js index 60f6147..e6fe643 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -79,7 +79,8 @@ _html2canvas.Parse = function (images, options) { var align = false, bold = getCSS(el, "fontWeight"), family = getCSS(el, "fontFamily"), - size = getCSS(el, "fontSize"); + size = getCSS(el, "fontSize"), + shadow = getCSS(el, "textShadow"); switch(parseInt(bold, 10)){ case 401: @@ -94,6 +95,23 @@ _html2canvas.Parse = function (images, options) { ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" ")); ctx.setVariable("textAlign", (align) ? "right" : "left"); + if (shadow !== "none") { + + // TODO: better text-shadow parsing + var parseShadow = /(rgba\([^)]*\))\s([^\s]*)\s([^\s]*)\s([^\s]*)/; + var bits = parseShadow.exec(shadow); + var color = bits[1], + sX = bits[2].replace('px', ''), + sY = bits[3].replace('px', ''), + blur = bits[4].replace('px', ''); + + // apply the text shadow + ctx.setVariable("shadowColor", color); + ctx.setVariable("shadowOffsetX", sX); + ctx.setVariable("shadowOffsetY", sY); + ctx.setVariable("shadowBlur", blur); + } + if (text_decoration !== "none"){ return _html2canvas.Util.Font(family, size, doc); } From 655779743ba32d949d400f12ffa6d6a50c930e2b Mon Sep 17 00:00:00 2001 From: Guerric Sloan Date: Wed, 12 Jun 2013 15:48:00 -0700 Subject: [PATCH 2/4] Better text-shadow parsing --- src/Parse.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Parse.js b/src/Parse.js index e6fe643..946c518 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -75,6 +75,8 @@ _html2canvas.Parse = function (images, options) { } } + // reuse the regex + var PARSE_TEXT_SHADOW = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g; function setTextVariables(ctx, el, text_decoration, color) { var align = false, bold = getCSS(el, "fontWeight"), @@ -96,14 +98,11 @@ _html2canvas.Parse = function (images, options) { ctx.setVariable("textAlign", (align) ? "right" : "left"); if (shadow !== "none") { - - // TODO: better text-shadow parsing - var parseShadow = /(rgba\([^)]*\))\s([^\s]*)\s([^\s]*)\s([^\s]*)/; - var bits = parseShadow.exec(shadow); - var color = bits[1], - sX = bits[2].replace('px', ''), - sY = bits[3].replace('px', ''), - blur = bits[4].replace('px', ''); + var s = shadow.match(PARSE_TEXT_SHADOW), + color = s[0], + sX = s[1] ? s[1].replace('px', '') : 0, + sY = s[2] ? s[2].replace('px', '') : 0, + blur = s[3] ? s[3].replace('px', '') : 0; // apply the text shadow ctx.setVariable("shadowColor", color); From e1573f8aed01a62acb90037dbfdcb262ec9e2dde Mon Sep 17 00:00:00 2001 From: Guerric Sloan Date: Wed, 12 Jun 2013 16:48:23 -0700 Subject: [PATCH 3/4] Parse out multiple text-shadow values and only honor the first one. --- src/Parse.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Parse.js b/src/Parse.js index 946c518..05d697d 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -75,8 +75,8 @@ _html2canvas.Parse = function (images, options) { } } - // reuse the regex - var PARSE_TEXT_SHADOW = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g; + var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g; + var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g; function setTextVariables(ctx, el, text_decoration, color) { var align = false, bold = getCSS(el, "fontWeight"), @@ -98,14 +98,18 @@ _html2canvas.Parse = function (images, options) { ctx.setVariable("textAlign", (align) ? "right" : "left"); if (shadow !== "none") { - var s = shadow.match(PARSE_TEXT_SHADOW), - color = s[0], + + // find multiple shadow declarations + var shadows = shadow.match(TEXT_SHADOW_PROPERTY); + + // we'll only support one shadow for now + var s = shadows[0].match(TEXT_SHADOW_VALUES), sX = s[1] ? s[1].replace('px', '') : 0, sY = s[2] ? s[2].replace('px', '') : 0, blur = s[3] ? s[3].replace('px', '') : 0; // apply the text shadow - ctx.setVariable("shadowColor", color); + ctx.setVariable("shadowColor", s[0]); ctx.setVariable("shadowOffsetX", sX); ctx.setVariable("shadowOffsetY", sY); ctx.setVariable("shadowBlur", blur); From f49e147b2fed2c88e3893ca42108d94705e87e21 Mon Sep 17 00:00:00 2001 From: Guerric Sloan Date: Tue, 18 Jun 2013 23:47:08 -0700 Subject: [PATCH 4/4] Added qunit tests for text-shadow --- src/Core.js | 27 +++++++++++++++++++++++++++ src/Parse.js | 23 +++++++---------------- tests/qunit/index.html | 11 +++++++++++ tests/qunit/unit/css.js | 31 ++++++++++++++++++++++++++++++- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/Core.js b/src/Core.js index 1dc22c9..9caaa5c 100644 --- a/src/Core.js +++ b/src/Core.js @@ -20,6 +20,33 @@ _html2canvas.Util.trimText = (function(isNative){ }; })( String.prototype.trim ); +(function() { + + // TODO: support all possible length values + var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g; + var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g; + _html2canvas.Util.parseTextShadows = function (value) { + if (value === 'none') { + return []; + } + + // find multiple shadow declarations + var shadows = value.match(TEXT_SHADOW_PROPERTY), + results = []; + for (var i = 0; i < shadows.length; i++) { + var s = shadows[i].match(TEXT_SHADOW_VALUES); + results.push({ + color: s[0], + offsetX: s[1] ? s[1].replace('px', '') : 0, + offsetY: s[2] ? s[2].replace('px', '') : 0, + blur: s[3] ? s[3].replace('px', '') : 0 + }); + } + return results; + }; +})(); + + _html2canvas.Util.parseBackgroundImage = function (value) { var whitespace = ' \r\n\t', method, definition, prefix, prefix_i, block, results = [], diff --git a/src/Parse.js b/src/Parse.js index 05d697d..cf320b9 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -75,8 +75,6 @@ _html2canvas.Parse = function (images, options) { } } - var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g; - var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g; function setTextVariables(ctx, el, text_decoration, color) { var align = false, bold = getCSS(el, "fontWeight"), @@ -98,21 +96,14 @@ _html2canvas.Parse = function (images, options) { ctx.setVariable("textAlign", (align) ? "right" : "left"); if (shadow !== "none") { + var shadows = _html2canvas.Util.parseTextShadows(shadow); - // find multiple shadow declarations - var shadows = shadow.match(TEXT_SHADOW_PROPERTY); - - // we'll only support one shadow for now - var s = shadows[0].match(TEXT_SHADOW_VALUES), - sX = s[1] ? s[1].replace('px', '') : 0, - sY = s[2] ? s[2].replace('px', '') : 0, - blur = s[3] ? s[3].replace('px', '') : 0; - - // apply the text shadow - ctx.setVariable("shadowColor", s[0]); - ctx.setVariable("shadowOffsetX", sX); - ctx.setVariable("shadowOffsetY", sY); - ctx.setVariable("shadowBlur", blur); + // TODO: support multiple text shadows + // apply the first text shadow + ctx.setVariable("shadowColor", shadows[0].color); + ctx.setVariable("shadowOffsetX", shadows[0].offsetX); + ctx.setVariable("shadowOffsetY", shadows[0].offsetY); + ctx.setVariable("shadowBlur", shadows[0].blur); } if (text_decoration !== "none"){ diff --git a/tests/qunit/index.html b/tests/qunit/index.html index 4545f06..acef453 100644 --- a/tests/qunit/index.html +++ b/tests/qunit/index.html @@ -70,6 +70,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/qunit/unit/css.js b/tests/qunit/unit/css.js index 0feb7e2..8ecd566 100644 --- a/tests/qunit/unit/css.js +++ b/tests/qunit/unit/css.js @@ -143,7 +143,36 @@ $(function() { }); }); - }); + }); + + test('text-shadow', function() { + + $('#textShadows div').each(function(i, el) { + var index = i+1; + var value = _html2canvas.Util.getCSS(el, 'textShadow'), + shadows = _html2canvas.Util.parseTextShadows(value); + if (i == 0) { + QUnit.equal(shadows.length, 0, 'div #' + index); + } else { + QUnit.equal(shadows.length, (i >= 6 ? 2 : 1), 'div #' + index); + QUnit.equal(shadows[0].offsetX, i, 'div #' + index + ' offsetX'); + QUnit.equal(shadows[0].offsetY, i, 'div #' + index + ' offsetY'); + if (i < 2) { + QUnit.equal(shadows[0].color, 'rgba(0, 0, 0, 0)', 'div #' + index + ' color'); + } else if (i % 2 == 0) { + QUnit.equal(shadows[0].color, 'rgb(2, 2, 2)', 'div #' + index + ' color'); + } else { + var opacity = '0.199219'; + QUnit.equal(shadows[0].color, 'rgba(2, 2, 2, '+opacity+')', 'div #' + index + ' color'); + } + + // only testing blur once + if (i == 1) { + QUnit.equal(shadows[0].blur, '1', 'div #' + index + ' blur'); + } + } + }); + }); test('background-image', function () {