diff --git a/.jshintrc b/.jshintrc index 58bab57..18ad9d5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -14,5 +14,5 @@ "jQuery": true }, "predef": ["NodeParser", "NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise", - "ImageContainer", "ProxyImageContainer", "DummyImageContainer", "log"] + "ImageContainer", "ProxyImageContainer", "DummyImageContainer", "Font", "FontMetrics", "log", "smallImage"] } diff --git a/src/core.js b/src/core.js index b444a56..68ee035 100644 --- a/src/core.js +++ b/src/core.js @@ -37,6 +37,10 @@ function documentHeight () { ); } +function smallImage() { + return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; +} + function createWindowClone(ownerDocument, width, height) { var documentElement = ownerDocument.documentElement.cloneNode(true), container = ownerDocument.createElement("iframe"); @@ -51,7 +55,7 @@ function createWindowClone(ownerDocument, width, height) { return new Promise(function(resolve) { var loadedTimer = function() { /* Chrome doesn't detect relative background-images assigned in style sheets when fetched through getComputedStyle, - before a certain time has passed + before a certain time has passed */ if (container.contentWindow.getComputedStyle(div, null)['backgroundImage'] !== "none") { documentClone.body.removeChild(div); @@ -75,8 +79,8 @@ function createWindowClone(ownerDocument, width, height) { div.className = "html2canvas-ready-test"; documentClone.body.appendChild(div); var style = documentClone.createElement("style"); - style.innerHTML = "body div.html2canvas-ready-test { background-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); }"; + style.innerHTML = "body div.html2canvas-ready-test { background-image:url(" + smallImage() + "); }"; documentClone.body.appendChild(style); - window.setTimeout(loadedTimer, 1000); + window.setTimeout(loadedTimer, 10); }); } diff --git a/src/font.js b/src/font.js new file mode 100644 index 0000000..d9f0dd8 --- /dev/null +++ b/src/font.js @@ -0,0 +1,48 @@ +function Font(family, size) { + var container = document.createElement('div'), + img = document.createElement('img'), + span = document.createElement('span'), + sampleText = 'Hidden Text', + baseline, + middle; + + container.style.visibility = "hidden"; + container.style.fontFamily = family; + container.style.fontSize = size; + container.style.margin = 0; + container.style.padding = 0; + + document.body.appendChild(container); + + img.src = smallImage(); + img.width = 1; + img.height = 1; + + img.style.margin = 0; + img.style.padding = 0; + img.style.verticalAlign = "baseline"; + + span.style.fontFamily = family; + span.style.fontSize = size; + span.style.margin = 0; + span.style.padding = 0; + + span.appendChild(document.createTextNode(sampleText)); + container.appendChild(span); + container.appendChild(img); + baseline = (img.offsetTop - span.offsetTop) + 1; + + container.removeChild(span); + container.appendChild(document.createTextNode(sampleText)); + + container.style.lineHeight = "normal"; + img.style.verticalAlign = "super"; + + middle = (img.offsetTop-container.offsetTop) + 1; + + document.body.removeChild(container); + + this.baseline = baseline; + this.lineWidth = 1; + this.middle = middle; +} diff --git a/src/fontmetrics.js b/src/fontmetrics.js new file mode 100644 index 0000000..a780668 --- /dev/null +++ b/src/fontmetrics.js @@ -0,0 +1,10 @@ +function FontMetrics() { + this.data = {}; +} + +FontMetrics.prototype.getMetrics = function(family, size) { + if (this.data[family + "-" + size] === undefined) { + this.data[family + "-" + size] = new Font(family, size); + } + return this.data[family + "-" + size]; +}; diff --git a/src/nodeparser.js b/src/nodeparser.js index c8f2593..00a2732 100644 --- a/src/nodeparser.js +++ b/src/nodeparser.js @@ -10,6 +10,7 @@ function NodeParser(element, renderer, support, imageLoader, options) { this.nodes = [parent].concat(this.getChildren(parent)).filter(function(container) { return container.visible = container.isElementVisible(); }); + this.fontMetrics = new FontMetrics(); log("Fetched nodes"); this.images = imageLoader.fetch(this.nodes.filter(isElement)); log("Creating stacking contexts"); @@ -196,16 +197,28 @@ NodeParser.prototype.paintText = function(container) { textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) { if (bounds) { this.renderer.text(textList[index], bounds.left, bounds.bottom); - // renderTextDecoration(ctx, textDecoration, bounds, metrics, color); + this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size)); } - /* var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix); - if (bounds) { - drawText(text, bounds.left, bounds.bottom, ctx); - renderTextDecoration(ctx, textDecoration, bounds, metrics, color); - } */ }, this); }; +NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) { + switch(container.css("textDecoration").split(" ")[0]) { + case "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 + this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.css("color")); + break; + case "overline": + this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.css("color")); + break; + case "line-through": + // TODO try and find exact position for line-through + this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.css("color")); + break; + } +}; + NodeParser.prototype.parseBorders = function(container) { var nodeBounds = container.bounds; var radius = getBorderRadiusData(container);