From 331c057273b6e1970c89a5a8ca517cf4f28e5860 Mon Sep 17 00:00:00 2001 From: Benjamin Lupton Date: Tue, 29 May 2012 11:18:01 +1000 Subject: [PATCH 1/2] Added a makefile to build html2canvas --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e4b4c32 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +build: + ant + +install: + brew install https://raw.github.com/Homebrew/homebrew-dupes/master/ant.rb + +.PHONY: install build \ No newline at end of file From 105931425850f0a7f7d5b0d6febf39191d413aec Mon Sep 17 00:00:00 2001 From: Benjamin Lupton Date: Tue, 29 May 2012 11:26:26 +1000 Subject: [PATCH 2/2] Added a sublime-project file for Sublime Text 2 that contains the detected linting and standards set. Allows Sublime Text 2 to automatically follow the standards and have live linting enabled. Trimmed useless whitespace. --- .gitignore | 1 + html2canvas.sublime-project | 57 ++ src/Core.js | 112 ++-- src/Generate.js | 128 ++-- src/Parse.js | 770 +++++++++++------------ src/Renderer.js | 28 +- src/Util.js | 30 +- src/plugins/jquery.plugin.html2canvas.js | 8 +- src/renderers/Canvas.js | 118 ++-- src/renderers/SVG.js | 150 ++--- tests/test.js | 6 +- 11 files changed, 733 insertions(+), 675 deletions(-) create mode 100644 html2canvas.sublime-project diff --git a/.gitignore b/.gitignore index b439c63..5c09c07 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ image.jpg /.project /.settings/ .envrc +*.sublime-workspace diff --git a/html2canvas.sublime-project b/html2canvas.sublime-project new file mode 100644 index 0000000..c7fb30c --- /dev/null +++ b/html2canvas.sublime-project @@ -0,0 +1,57 @@ +{ + "folders": + [ + { + "path": ".", + "folder_exclude_patterns": [".svn", ".git", ".hg", "CVS", "node_modules"], + "file_exclude_patterns": ["*.sublime-workspace"] + } + ], + "settings": + { + // The number of spaces a tab is considered equal to + "tab_size": 4, + + // Set to true to insert spaces when tab is pressed + "translate_tabs_to_spaces": true, + + // If translate_tabs_to_spaces is true, use_tab_stops will make tab and + // backspace insert/delete up to the next tabstop + "use_tab_stops": false, + + // Set to false to disable detection of tabs vs. spaces on load + "detect_indentation": false, + + // Set to true to removing trailing white space on save + "trim_trailing_white_space_on_save": true, + + // Set to true to ensure the last line of the file ends in a newline + // character when saving + "ensure_newline_at_eof_on_save": false, + + // Linting + "jshint_options": { + "eqeqeq": false, + + "laxbreak": true, + "undef": true, + "newcap": true, + "noarg": true, + "strict": false, + "trailing": true, + "onecase": true, + + "boss": true, + "eqnull": true, + + "onevar": false, + + "evil": true, + "regexdash": true, + "browser": true, + "wsh": true, + "trailing": true, + "sub": true + } + } +} diff --git a/src/Core.js b/src/Core.js index 86e1a73..b1eb13f 100644 --- a/src/Core.js +++ b/src/Core.js @@ -20,62 +20,62 @@ function h2clog(a) { _html2canvas.Util = {}; _html2canvas.Util.backgroundImage = function (src) { - + if (/data:image\/.*;base64,/i.test( src ) || /^(-webkit|-moz|linear-gradient|-o-)/.test( src )) { return src; } - + if (src.toLowerCase().substr( 0, 5 ) === 'url("') { src = src.substr( 5 ); - src = src.substr( 0, src.length - 2 ); + src = src.substr( 0, src.length - 2 ); } else { src = src.substr( 4 ); - src = src.substr( 0, src.length - 1 ); + src = src.substr( 0, src.length - 1 ); } - return src; + return src; }; _html2canvas.Util.Bounds = function getBounds (el) { var clientRect, bounds = {}; - - if (el.getBoundingClientRect){ + + if (el.getBoundingClientRect){ clientRect = el.getBoundingClientRect(); - + // TODO add scroll position to bounds, so no scrolling of window necessary bounds.top = clientRect.top; bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height); bounds.left = clientRect.left; - + // older IE doesn't have width/height, but top/bottom instead bounds.width = clientRect.width || (clientRect.right - clientRect.left); bounds.height = clientRect.height || (clientRect.bottom - clientRect.top); - + return bounds; - - } + + } }; _html2canvas.Util.getCSS = function (el, attribute) { // return $(el).css(attribute); - + var val; - + function toPX( attribute, val ) { var rsLeft = el.runtimeStyle && el.runtimeStyle[ attribute ], left, style = el.style; - + // Check if we are not dealing with pixels, (Opera has issues with this) // Ported from jQuery css.js // From the awesome hack by Dean Edwards // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( val ) && /^-?\d/.test( val ) ) { // Remember the original values @@ -95,37 +95,37 @@ _html2canvas.Util.getCSS = function (el, attribute) { } } - + if (!/^(thin|medium|thick)$/i.test( val )) { return Math.round(parseFloat( val )) + "px"; - } - + } + return val; - + } - - + + if ( window.getComputedStyle ) { val = document.defaultView.getComputedStyle(el, null)[ attribute ]; - + if ( attribute === "backgroundPosition" ) { - + val = (val.split(",")[0] || "0 0").split(" "); - + val[ 0 ] = ( val[0].indexOf( "%" ) === -1 ) ? toPX( attribute + "X", val[ 0 ] ) : val[ 0 ]; val[ 1 ] = ( val[1] === undefined ) ? val[0] : val[1]; // IE 9 doesn't return double digit always val[ 1 ] = ( val[1].indexOf( "%" ) === -1 ) ? toPX( attribute + "Y", val[ 1 ] ) : val[ 1 ]; - } - + } + } else if ( el.currentStyle ) { - // IE 9> + // IE 9> if (attribute === "backgroundPosition") { - // Older IE uses -x and -y - val = [ toPX( attribute + "X", el.currentStyle[ attribute + "X" ] ), toPX( attribute + "Y", el.currentStyle[ attribute + "Y" ] ) ]; + // Older IE uses -x and -y + val = [ toPX( attribute + "X", el.currentStyle[ attribute + "X" ] ), toPX( attribute + "Y", el.currentStyle[ attribute + "Y" ] ) ]; } else { val = toPX( attribute, el.currentStyle[ attribute ] ); - + if (/^(border)/i.test( attribute ) && /^(medium|thin|thick)$/i.test( val )) { switch (val) { case "thin": @@ -138,7 +138,7 @@ _html2canvas.Util.getCSS = function (el, attribute) { val = "5px"; break; } - } + } } @@ -147,61 +147,61 @@ _html2canvas.Util.getCSS = function (el, attribute) { - - return val; - - + return val; + + + //return $(el).css(attribute); - - + + }; _html2canvas.Util.BackgroundPosition = function ( el, bounds, image ) { // TODO add support for multi image backgrounds - + var bgposition = _html2canvas.Util.getCSS( el, "backgroundPosition" ) , topPos, left, percentage, val; - + if (bgposition.length === 1){ val = bgposition; - + bgposition = []; - + bgposition[0] = val; bgposition[1] = val; - } + } - - if (bgposition[0].toString().indexOf("%") !== -1){ - percentage = (parseFloat(bgposition[0])/100); + + if (bgposition[0].toString().indexOf("%") !== -1){ + percentage = (parseFloat(bgposition[0])/100); left = ((bounds.width * percentage)-(image.width*percentage)); - + }else{ left = parseInt(bgposition[0],10); } - if (bgposition[1].toString().indexOf("%") !== -1){ + if (bgposition[1].toString().indexOf("%") !== -1){ - percentage = (parseFloat(bgposition[1])/100); + percentage = (parseFloat(bgposition[1])/100); topPos = ((bounds.height * percentage)-(image.height*percentage)); - }else{ - topPos = parseInt(bgposition[1],10); + }else{ + topPos = parseInt(bgposition[1],10); } - - + + return { top: topPos, left: left }; - + }; _html2canvas.Util.Extend = function (options, defaults) { @@ -219,7 +219,7 @@ _html2canvas.Util.Children = function(el) { try { children = $(el).contents(); //children = (el.nodeName && el.nodeName.toUpperCase() === "IFRAME") ? el.contentDocument || el.contentWindow.document : el.childNodes ; - + } catch (ex) { h2clog("html2canvas.Util.Children failed with exception: " + ex.message); children = []; diff --git a/src/Generate.js b/src/Generate.js index 3dfa3c1..d095510 100644 --- a/src/Generate.js +++ b/src/Generate.js @@ -2,7 +2,7 @@ html2canvas @VERSION@ Copyright (c) 2011 Niklas von Hertzen. All rights reserved. http://www.twitter.com/niklasvh - + Contributor(s): Niklas von Hertzen André Fiedler @@ -11,7 +11,7 @@ */ (function(){ - + _html2canvas.Generate = {}; var reGradients = [ @@ -30,19 +30,19 @@ var reGradients = [ * TODO: Add old Webkit -webkit-gradient(radial, ...) support * TODO: Maybe some RegExp optimizations are possible ;o) */ -_html2canvas.Generate.parseGradient = function(css, bounds) { +_html2canvas.Generate.parseGradient = function(css, bounds) { var gradient, i, len = reGradients.length, m1, stop, m2, m2Len, step, m3; - + for(i = 0; i < len; i+=1){ m1 = css.match(reGradients[i]); if(m1) break; } - + if(m1) { switch(m1[1]) { case '-webkit-linear-gradient': case '-o-linear-gradient': - + gradient = { type: 'linear', x0: null, @@ -51,7 +51,7 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { y1: null, colorStops: [] }; - + // get coordinates m2 = m1[2].match(/\w+/g); if(m2){ @@ -62,17 +62,17 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { gradient.y0 = 0; gradient.y1 = bounds.height; break; - + case 'right': gradient.x0 = bounds.width; gradient.x1 = 0; break; - + case 'bottom': gradient.y0 = bounds.height; gradient.y1 = 0; break; - + case 'left': gradient.x0 = 0; gradient.x1 = bounds.width; @@ -86,7 +86,7 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { if(gradient.y0 === null && gradient.y1 === null){ // center gradient.y0 = gradient.y1 = bounds.height / 2; } - + // get colors and stops m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g); if(m2){ @@ -111,9 +111,9 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { } } break; - + case '-webkit-gradient': - + gradient = { type: m1[2] === 'radial' ? 'circle' : m1[2], // TODO: Add radial gradient support for older mozilla definitions x0: 0, @@ -122,7 +122,7 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { y1: 0, colorStops: [] }; - + // get coordinates m2 = m1[3].match(/(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/); if(m2){ @@ -131,7 +131,7 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { gradient.x1 = (m2[3] * bounds.width) / 100; gradient.y1 = (m2[4] * bounds.height) / 100; } - + // get colors and stops m2 = m1[4].match(/((?:from|to|color-stop)\((?:[0-9\.]+,\s)?(?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)\))+/g); if(m2){ @@ -148,9 +148,9 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { } } break; - + case '-moz-linear-gradient': - + gradient = { type: 'linear', x0: 0, @@ -159,25 +159,25 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { y1: 0, colorStops: [] }; - + // get coordinates m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/); - + // m2[1] == 0% -> left // m2[1] == 50% -> center // m2[1] == 100% -> right - + // m2[2] == 0% -> top // m2[2] == 50% -> center // m2[2] == 100% -> bottom - + if(m2){ gradient.x0 = (m2[1] * bounds.width) / 100; gradient.y0 = (m2[2] * bounds.height) / 100; gradient.x1 = bounds.width - gradient.x0; gradient.y1 = bounds.height - gradient.y0; } - + // get colors and stops m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}%)?)+/g); if(m2){ @@ -200,11 +200,11 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { } } break; - + case '-webkit-radial-gradient': case '-moz-radial-gradient': case '-o-radial-gradient': - + gradient = { type: 'circle', x0: 0, @@ -217,14 +217,14 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { ry: 0, colorStops: [] }; - + // center m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/); if(m2){ gradient.cx = (m2[1] * bounds.width) / 100; gradient.cy = (m2[2] * bounds.height) / 100; } - + // size m2 = m1[3].match(/\w+/); m3 = m1[4].match(/[a-z-]*/); @@ -255,9 +255,9 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { gradient.y1 - gradient.cy ); } else { // ellipse - + gradient.type = m2[0]; - + gradient.rx = Math.max( gradient.cx, gradient.x1 - gradient.cx @@ -278,9 +278,9 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { gradient.y1 - gradient.cy ); } else { // ellipse - + gradient.type = m2[0]; - + gradient.rx = Math.min( gradient.cx, gradient.x1 - gradient.cx @@ -291,11 +291,11 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { ); } break; - + // TODO: add support for "30px 40px" sizes (webkit only) } } - + // color stops m2 = m1[5].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g); if(m2){ @@ -322,7 +322,7 @@ _html2canvas.Generate.parseGradient = function(css, bounds) { break; } } - + return gradient; }; @@ -330,19 +330,19 @@ _html2canvas.Generate.Gradient = function(src, bounds) { var canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'), gradient, grad, i, len, img; - + canvas.width = bounds.width; canvas.height = bounds.height; - + // TODO: add support for multi defined background gradients (like radial gradient example in background.html) gradient = _html2canvas.Generate.parseGradient(src, bounds); - + img = new Image(); - + if(gradient){ if(gradient.type === 'linear'){ grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1); - + for (i = 0, len = gradient.colorStops.length; i < len; i+=1) { try { grad.addColorStop(gradient.colorStops[i].stop, gradient.colorStops[i].color); @@ -351,15 +351,15 @@ _html2canvas.Generate.Gradient = function(src, bounds) { h2clog(['failed to add color stop: ', e, '; tried to add: ', gradient.colorStops[i], '; stop: ', i, '; in: ', src]); } } - + ctx.fillStyle = grad; ctx.fillRect(0, 0, bounds.width, bounds.height); - + img.src = canvas.toDataURL(); } else if(gradient.type === 'circle'){ - + grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx); - + for (i = 0, len = gradient.colorStops.length; i < len; i+=1) { try { grad.addColorStop(gradient.colorStops[i].stop, gradient.colorStops[i].color); @@ -368,23 +368,23 @@ _html2canvas.Generate.Gradient = function(src, bounds) { h2clog(['failed to add color stop: ', e, '; tried to add: ', gradient.colorStops[i], '; stop: ', i, '; in: ', src]); } } - + ctx.fillStyle = grad; ctx.fillRect(0, 0, bounds.width, bounds.height); - + img.src = canvas.toDataURL(); } else if(gradient.type === 'ellipse'){ - + // draw circle var canvasRadial = document.createElement('canvas'), ctxRadial = canvasRadial.getContext('2d'), ri = Math.max(gradient.rx, gradient.ry), di = ri * 2, imgRadial; - + canvasRadial.width = canvasRadial.height = di; - + grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri); - + for (i = 0, len = gradient.colorStops.length; i < len; i+=1) { try { grad.addColorStop(gradient.colorStops[i].stop, gradient.colorStops[i].color); @@ -393,40 +393,40 @@ _html2canvas.Generate.Gradient = function(src, bounds) { h2clog(['failed to add color stop: ', e, '; tried to add: ', gradient.colorStops[i], '; stop: ', i, '; in: ', src]); } } - + ctxRadial.fillStyle = grad; ctxRadial.fillRect(0, 0, di, di); - + ctx.fillStyle = gradient.colorStops[i - 1].color; ctx.fillRect(0, 0, canvas.width, canvas.height); - + imgRadial = new Image(); imgRadial.onload = function() { // wait until the image is filled - + // transform circle to ellipse ctx.drawImage(imgRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry); - + img.src = canvas.toDataURL(); - + } imgRadial.src = canvasRadial.toDataURL(); } } - + return img; }; _html2canvas.Generate.ListAlpha = function(number) { var tmp = "", modulus; - + do { - modulus = number % 26; + modulus = number % 26; tmp = String.fromCharCode((modulus) + 64) + tmp; number = number / 26; }while((number*26) > 26); - - return tmp; + + return tmp; }; _html2canvas.Generate.ListRoman = function(number) { @@ -436,19 +436,19 @@ _html2canvas.Generate.ListRoman = function(number) { v, len = romanArray.length; - if (number <= 0 || number >= 4000) { + if (number <= 0 || number >= 4000) { return number; } - + for (v=0; v < len; v+=1) { - while (number >= decimal[v]) { + while (number >= decimal[v]) { number -= decimal[v]; roman += romanArray[v]; } } - + return roman; - + }; })(); \ No newline at end of file diff --git a/src/Parse.js b/src/Parse.js index 9176578..e0a3239 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -12,7 +12,7 @@ _html2canvas.Parse = function ( images, options ) { window.scroll(0,0); - + var support = { rangeBounds: false, svgRendering: options.svgRendering && (function( ){ @@ -23,7 +23,7 @@ _html2canvas.Parse = function ( images, options ) { // browser doesn't support canvas, good luck supporting SVG on canvas return false; } - canvas.width = canvas.height = 10; + canvas.width = canvas.height = 10; img.src = [ "data:image/svg+xml,", "", @@ -41,8 +41,8 @@ _html2canvas.Parse = function ( images, options ) { return false; } h2clog('html2canvas: Parse: SVG powered rendering available'); - return true; - + return true; + })() }, element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default @@ -56,14 +56,14 @@ _html2canvas.Parse = function ( images, options ) { testElement, rangeBounds, rangeHeight, - stack, + stack, ctx, docDim, i, children, childrenLen; - - + + function docSize(){ return { @@ -77,16 +77,16 @@ _html2canvas.Parse = function ( images, options ) { Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight), Math.max(doc.body.clientHeight, doc.documentElement.clientHeight) ) - }; - + }; + } images = images || {}; - + // Test whether we can use ranges to measure bounding boxes // Opera doesn't provide valid bounds.height/bottom even though it supports the method. - + if (doc.createRange) { r = doc.createRange(); //this.support.rangeBounds = new Boolean(r.getBoundingClientRect); @@ -95,7 +95,7 @@ _html2canvas.Parse = function ( images, options ) { testElement.style.height = "123px"; testElement.style.display = "block"; body.appendChild(testElement); - + r.selectNode(testElement); rangeBounds = r.getBoundingClientRect(); rangeHeight = rangeBounds.height; @@ -105,19 +105,19 @@ _html2canvas.Parse = function ( images, options ) { } body.removeChild(testElement); - + } - + } - - + + /* - var rootStack = new this.storageContext($(document).width(),$(document).height()); + var rootStack = new this.storageContext($(document).width(),$(document).height()); rootStack.opacity = this.getCSS(this.element,"opacity"); var stack = this.newElement(this.element,rootStack); - - - this.parseElement(this.element,stack); + + + this.parseElement(this.element,stack); */ @@ -126,7 +126,7 @@ _html2canvas.Parse = function ( images, options ) { var getCSS = _html2canvas.Util.getCSS; function getCSSInt(element, attribute) { var val = parseInt(getCSS(element, attribute), 10); - return (isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html + return (isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html } // Drawing a rectangle @@ -137,49 +137,49 @@ _html2canvas.Parse = function ( images, options ) { numDraws+=1; } } - - + + function textTransform (text, transform) { switch(transform){ case "lowercase": - return text.toLowerCase(); - + return text.toLowerCase(); + case "capitalize": return text.replace( /(^|\s|:|-|\(|\))([a-z])/g , function (m, p1, p2) { if (m.length > 0) { return p1 + p2.toUpperCase(); } - } ); - + } ); + case "uppercase": return text.toUpperCase(); - + default: return text; - + } - + } - + function trimText (text) { return text.replace(/^\s*/g, "").replace(/\s*$/g, ""); } - + function fontMetrics (font, fontSize) { - + if (fontData[font + "-" + fontSize] !== undefined) { return fontData[font + "-" + fontSize]; } - + var container = doc.createElement('div'), img = doc.createElement('img'), span = doc.createElement('span'), baseline, middle, metricsObj; - - + + container.style.visibility = "hidden"; container.style.fontFamily = font; container.style.fontSize = fontSize; @@ -187,14 +187,14 @@ _html2canvas.Parse = function ( images, options ) { container.style.padding = 0; body.appendChild(container); - + // http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif) img.src = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs="; img.width = 1; img.height = 1; - + img.style.margin = 0; img.style.padding = 0; img.style.verticalAlign = "baseline"; @@ -203,46 +203,46 @@ _html2canvas.Parse = function ( images, options ) { span.style.fontSize = fontSize; span.style.margin = 0; span.style.padding = 0; - - - - + + + + span.appendChild(doc.createTextNode('Hidden Text')); container.appendChild(span); container.appendChild(img); baseline = (img.offsetTop - span.offsetTop) + 1; - + container.removeChild(span); container.appendChild(doc.createTextNode('Hidden Text')); - + container.style.lineHeight = "normal"; img.style.verticalAlign = "super"; - + middle = (img.offsetTop-container.offsetTop) + 1; metricsObj = { baseline: baseline, lineWidth: 1, middle: middle }; - - + + fontData[font + "-" + fontSize] = metricsObj; - + body.removeChild(container); return metricsObj; - + } - - - function drawText(currentText, x, y, ctx){ - if (trimText(currentText).length>0) { + + + function drawText(currentText, x, y, ctx){ + if (trimText(currentText).length>0) { ctx.fillText(currentText,x,y); numDraws+=1; - } + } } - - + + function renderText(el, textNode, stack) { var ctx = stack.ctx, family = getCSS(el, "fontFamily"), @@ -271,29 +271,29 @@ _html2canvas.Parse = function ( images, options ) { backupText; // apply text-transform:ation to the text - - - + + + textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform")); text = trimText(textNode.nodeValue); - + if (text.length>0){ - + if (text_decoration !== "none"){ - metrics = fontMetrics(family, size); - } - + metrics = fontMetrics(family, size); + } + text_align = text_align.replace(["-webkit-auto"],["auto"]); if (options.letterRendering === false && /^(left|right|justify|auto)$/.test(text_align) && /^(normal|none)$/.test(letter_spacing)){ - // this.setContextVariable(ctx,"textAlign",text_align); + // this.setContextVariable(ctx,"textAlign",text_align); renderList = textNode.nodeValue.split(/(\b| )/); - + }else{ // this.setContextVariable(ctx,"textAlign","left"); renderList = textNode.nodeValue.split(""); } - + switch(parseInt(bold, 10)){ case 401: bold = "bold"; @@ -303,37 +303,37 @@ _html2canvas.Parse = function ( images, options ) { break; } - ctx.setVariable("fillStyle", color); - + ctx.setVariable("fillStyle", color); + /* need to be defined in the order as defined in http://www.w3.org/TR/CSS21/fonts.html#font-shorthand to properly work in Firefox - */ + */ ctx.setVariable("font", font_style+ " " + font_variant + " " + bold + " " + size + " " + family); - + if (align){ ctx.setVariable("textAlign", "right"); }else{ ctx.setVariable("textAlign", "left"); } - + /* if (stack.clip){ ctx.rect (stack.clip.left, stack.clip.top, stack.clip.width, stack.clip.height); ctx.clip(); } */ - - + + oldTextNode = textNode; - - + + for ( c=0, listLen = renderList.length; c < listLen; c+=1 ) { textValue = null; - - - + + + if (support.rangeBounds){ // getBoundingClientRect is supported for ranges if (text_decoration !== "none" || trimText(renderList[c]).length !== 0) { @@ -347,49 +347,49 @@ _html2canvas.Parse = function ( images, options ) { // TODO add IE support range = body.createTextRange(); } - + if (range.getBoundingClientRect()) { bounds = range.getBoundingClientRect(); }else{ bounds = {}; } - + } }else{ // it isn't supported, so let's wrap it inside an element instead and get the bounds there - + // IE 9 bug if (typeof oldTextNode.nodeValue !== "string" ){ continue; } - + newTextNode = oldTextNode.splitText(renderList[c].length); - + parent = oldTextNode.parentNode; wrapElement = doc.createElement('wrapper'); backupText = oldTextNode.cloneNode(true); wrapElement.appendChild(oldTextNode.cloneNode(true)); parent.replaceChild(wrapElement, oldTextNode); - + bounds = _html2canvas.Util.Bounds(wrapElement); - + textValue = oldTextNode.nodeValue; - + oldTextNode = newTextNode; - parent.replaceChild(backupText, wrapElement); - - + parent.replaceChild(backupText, wrapElement); + + } - + if (textValue !== null){ drawText(textValue, bounds.left, bounds.bottom, ctx); } switch(text_decoration) { - case "underline": + 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 + // 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 renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color); break; case "overline": @@ -399,52 +399,52 @@ _html2canvas.Parse = function ( images, options ) { // TODO try and find exact position for line-through renderRect(ctx, bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, color); break; - - } - - - - - + + } + + + + + textOffset += renderList[c].length; - + } - - - + + + } - + } - + function listPosition (element, val) { var boundElement = doc.createElement( "boundelement" ), type, bounds; - + boundElement.style.display = "inline"; //boundElement.style.width = "1px"; //boundElement.style.height = "1px"; - + type = element.style.listStyleType; element.style.listStyleType = "none"; - + boundElement.appendChild( doc.createTextNode( val ) ); - + element.insertBefore(boundElement, element.firstChild); - + bounds = _html2canvas.Util.Bounds( boundElement ); element.removeChild( boundElement ); element.style.listStyleType = type; return bounds; } - - + + function renderListItem(element, stack, elBounds) { - - + + var position = getCSS(element, "listStylePosition"), x, y, @@ -453,12 +453,12 @@ _html2canvas.Parse = function ( images, options ) { text, listBounds, bold = getCSS(element, "fontWeight"); - + if (/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)) { - + // TODO remove jQuery dependency currentIndex = $(element).index()+1; - + switch(type){ case "decimal": text = currentIndex; @@ -467,8 +467,8 @@ _html2canvas.Parse = function ( images, options ) { if (currentIndex.toString().length === 1){ text = currentIndex = "0" + currentIndex.toString(); }else{ - text = currentIndex.toString(); - } + text = currentIndex.toString(); + } break; case "upper-roman": text = _html2canvas.Generate.ListRoman( currentIndex ); @@ -477,19 +477,19 @@ _html2canvas.Parse = function ( images, options ) { text = _html2canvas.Generate.ListRoman( currentIndex ).toLowerCase(); break; case "lower-alpha": - text = _html2canvas.Generate.ListAlpha( currentIndex ).toLowerCase(); + text = _html2canvas.Generate.ListAlpha( currentIndex ).toLowerCase(); break; case "upper-alpha": - text = _html2canvas.Generate.ListAlpha( currentIndex ); + text = _html2canvas.Generate.ListAlpha( currentIndex ); break; } - + text += ". "; listBounds = listPosition(element, text); - - - + + + switch(bold){ case 401: bold = "bold"; @@ -498,43 +498,43 @@ _html2canvas.Parse = function ( images, options ) { bold = "normal"; break; } - - - - + + + + ctx.setVariable( "fillStyle", getCSS(element, "color") ); ctx.setVariable( "font", getCSS(element, "fontVariant") + " " + bold + " " + getCSS(element, "fontStyle") + " " + getCSS(element, "fontSize") + " " + getCSS(element, "fontFamily") ); - + if ( position === "inside" ) { ctx.setVariable("textAlign", "left"); - // this.setFont(stack.ctx, element, false); + // this.setFont(stack.ctx, element, false); x = elBounds.left; - + }else{ - return; - /* - TODO really need to figure out some more accurate way to try and find the position. - as defined in http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-position, it does not even have a specified "correct" position, so each browser - may display it whatever way it feels like. + return; + /* + TODO really need to figure out some more accurate way to try and find the position. + as defined in http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-position, it does not even have a specified "correct" position, so each browser + may display it whatever way it feels like. "The position of the list-item marker adjacent to floats is undefined in CSS 2.1. CSS 2.1 does not specify the precise location of the marker box or its position in the painting order" - + ctx.setVariable("textAlign", "right"); // this.setFont(stack.ctx, element, true); x = elBounds.left - 10; */ } - + y = listBounds.bottom; - + drawText(text, x, y, ctx); - - + + } - - + + } - + function loadImage (src){ var img = images[src]; if (img && img.succeeded === true) { @@ -543,28 +543,28 @@ _html2canvas.Parse = function ( images, options ) { return false; } } - - - - + + + + function clipBounds(src, dst){ - + var x = Math.max(src.left, dst.left), y = Math.max(src.top, dst.top), x2 = Math.min((src.left + src.width), (dst.left + dst.width)), y2 = Math.min((src.top + src.height), (dst.top + dst.height)); - + return { left:x, top:y, width:x2-x, height:y2-y }; - + } - + function setZ(zIndex, parentZ){ // TODO fix static elements overlapping relative/absolute elements under same stack, if they are defined after them var newContext; @@ -572,25 +572,25 @@ _html2canvas.Parse = function ( images, options ) { newContext = h2czContext(0); return newContext; } - + if (zIndex !== "auto"){ needReorder = true; newContext = h2czContext(zIndex); - parentZ.children.push(newContext); + parentZ.children.push(newContext); return newContext; - + } - + return parentZ; - + } - + function renderBorders(el, ctx, bounds, clip){ - + /* - * TODO add support for different border-style's than solid - */ - + * TODO add support for different border-style's than solid + */ + var x = bounds.left, y = bounds.top, w = bounds.width, @@ -606,28 +606,28 @@ _html2canvas.Parse = function ( images, options ) { var borders = [], sides = ["Top","Right","Bottom","Left"], s; - + for (s = 0; s < 4; s+=1){ borders.push({ width: getCSSInt(el, 'border' + sides[s] + 'Width'), color: getCSS(el, 'border' + sides[s] + 'Color') - }); + }); } - - return borders; - - }(el)); - + + return borders; + + }(el)); + for (borderSide = 0; borderSide < 4; borderSide+=1){ borderData = borders[borderSide]; - + if (borderData.width>0){ bx = x; by = y; bw = w; bh = h - (borders[2].width); - + switch(borderSide){ case 0: // top border @@ -636,7 +636,7 @@ _html2canvas.Parse = function ( images, options ) { case 1: // right border bx = x + w - (borders[1].width); - bw = borders[1].width; + bw = borders[1].width; break; case 2: // bottom border @@ -645,37 +645,37 @@ _html2canvas.Parse = function ( images, options ) { break; case 3: // left border - bw = borders[3].width; + bw = borders[3].width; break; - } - + } + borderBounds = { left:bx, top:by, width: bw, height:bh }; - + if (clip){ borderBounds = clipBounds(borderBounds, clip); } - - - if (borderBounds.width>0 && borderBounds.height>0){ + + + if (borderBounds.width>0 && borderBounds.height>0){ renderRect(ctx, bx, by, borderBounds.width, borderBounds.height, borderData.color); } - - + + } } return borders; - + } - - + + function renderFormValue (el, bounds, stack){ - + var valueWrap = doc.createElement('valuewrap'), cssArr = ['lineHeight','textAlign','fontFamily','color','fontSize','paddingLeft','paddingTop','width','height','border','borderLeftWidth','borderTopWidth'], i, @@ -683,7 +683,7 @@ _html2canvas.Parse = function ( images, options ) { textNode, arrLen, style; - + for (i = 0, arrLen = cssArr.length; i < arrLen; i+=1){ style = cssArr[i]; @@ -694,43 +694,43 @@ _html2canvas.Parse = function ( images, options ) { h2clog("html2canvas: Parse: Exception caught in renderFormValue: " + e.message); } } - - - valueWrap.style.borderColor = "black"; - valueWrap.style.borderStyle = "solid"; + + + valueWrap.style.borderColor = "black"; + valueWrap.style.borderStyle = "solid"; valueWrap.style.display = "block"; valueWrap.style.position = "absolute"; if (/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName === "SELECT"){ valueWrap.style.lineHeight = getCSS(el, "height"); } - - + + valueWrap.style.top = bounds.top + "px"; valueWrap.style.left = bounds.left + "px"; - + if (el.nodeName === "SELECT"){ // TODO increase accuracy of text position textValue = el.options[el.selectedIndex].text; - } else{ - textValue = el.value; + } else{ + textValue = el.value; } textNode = doc.createTextNode(textValue); - + valueWrap.appendChild(textNode); body.appendChild(valueWrap); - - - renderText(el, textNode, stack); - body.removeChild(valueWrap); - - - - } - - - - + + renderText(el, textNode, stack); + body.removeChild(valueWrap); + + + + } + + + + + function renderImage (ctx, image, sx, sy, sw, sh, dx, dy, dw, dh) { ctx.drawImage( image, @@ -741,20 +741,20 @@ _html2canvas.Parse = function ( images, options ) { dx, //dx dy, // dy dw, //dw - dh //dh + dh //dh ); - numDraws+=1; - + numDraws+=1; + } - + function renderBackgroundRepeat (ctx, image, x, y, width, height, elx, ely){ var sourceX = 0, sourceY=0; if (elx-x>0){ sourceX = elx-x; } - + if (ely-y>0){ sourceY = ely-y; } @@ -763,7 +763,7 @@ _html2canvas.Parse = function ( images, options ) { ctx, image, sourceX, // source X - sourceY, // source Y + sourceY, // source Y width-sourceX, // source Width height-sourceY, // source Height x+sourceX, // destination X @@ -772,58 +772,58 @@ _html2canvas.Parse = function ( images, options ) { height-sourceY // destination height ); } - - + + function renderBackgroundRepeatY (ctx, image, bgp, x, y, w, h){ - + var height, - width = Math.min(image.width,w),bgy; - - bgp.top = bgp.top-Math.ceil(bgp.top/image.height)*image.height; - - - for(bgy=(y+bgp.top);bgyh+y){ height = (h+y)-bgy; }else{ height = image.height; } - renderBackgroundRepeat(ctx,image,x+bgp.left,bgy,width,height,x,y); - - bgy = Math.floor(bgy+image.height); - - } + renderBackgroundRepeat(ctx,image,x+bgp.left,bgy,width,height,x,y); + + bgy = Math.floor(bgy+image.height); + + } } - + function renderBackgroundRepeatX(ctx, image, bgp, x, y, w, h){ - + var height = Math.min(image.height,h), - width,bgx; - - - bgp.left = bgp.left-Math.ceil(bgp.left/image.width)*image.width; - - - for (bgx=(x+bgp.left);bgxw+x){ width = (w+x)-bgx; }else{ width = image.width; } - - renderBackgroundRepeat(ctx,image,bgx,(y+bgp.top),width,height,x,y); - - bgx = Math.floor(bgx+image.width); - - } + renderBackgroundRepeat(ctx,image,bgx,(y+bgp.top),width,height,x,y); + + bgx = Math.floor(bgx+image.width); + + + } } - + function renderBackground(el,bounds,ctx){ - + // TODO add support for multi background-images var background_image = getCSS(el, "backgroundImage"), background_repeat = getCSS(el, "backgroundRepeat").split(",")[0], @@ -839,32 +839,32 @@ _html2canvas.Parse = function ( images, options ) { h, height, add; - + // if (typeof background_image !== "undefined" && /^(1|none)$/.test(background_image) === false && /^(-webkit|-moz|linear-gradient|-o-)/.test(background_image)===false){ - - if ( !/data:image\/.*;base64,/i.test(background_image) && !/^(-webkit|-moz|linear-gradient|-o-)/.test(background_image) ) { + + if ( !/data:image\/.*;base64,/i.test(background_image) && !/^(-webkit|-moz|linear-gradient|-o-)/.test(background_image) ) { background_image = background_image.split(",")[0]; } - + if ( typeof background_image !== "undefined" && /^(1|none)$/.test( background_image ) === false ) { background_image = _html2canvas.Util.backgroundImage( background_image ); image = loadImage( background_image ); - + bgp = _html2canvas.Util.BackgroundPosition(el, bounds, image); - + // TODO add support for background-origin if ( image ){ switch ( background_repeat ) { - + case "repeat-x": - renderBackgroundRepeatX( ctx, image, bgp, bounds.left, bounds.top, bounds.width, bounds.height ); + renderBackgroundRepeatX( ctx, image, bgp, bounds.left, bounds.top, bounds.width, bounds.height ); break; - + case "repeat-y": - renderBackgroundRepeatY( ctx, image, bgp, bounds.left, bounds.top, bounds.width, bounds.height ); + renderBackgroundRepeatY( ctx, image, bgp, bounds.left, bounds.top, bounds.width, bounds.height ); break; - + case "no-repeat": /* this.drawBackgroundRepeat( @@ -877,8 +877,8 @@ _html2canvas.Parse = function ( images, options ) { bounds.left, bounds.top );*/ - - + + // console.log($(el).css('background-image')); bgw = bounds.width - bgp.left; bgh = bounds.height - bgp.top; @@ -889,33 +889,33 @@ _html2canvas.Parse = function ( images, options ) { // // bgw = Math.min(bgw,image.width); - // bgh = Math.min(bgh,image.height); - + // bgh = Math.min(bgh,image.height); + if (bgsx<0){ bgsx = Math.abs(bgsx); - bgdx += bgsx; + bgdx += bgsx; bgw = Math.min(bounds.width,image.width-bgsx); }else{ bgw = Math.min(bgw,image.width); bgsx = 0; } - + if (bgsy<0){ bgsy = Math.abs(bgsy); - bgdy += bgsy; + bgdy += bgsy; // bgh = bgh-bgsy; bgh = Math.min(bounds.height,image.height-bgsy); }else{ - bgh = Math.min(bgh,image.height); + bgh = Math.min(bgh,image.height); bgsy = 0; - } - + } - if (bgh>0 && bgw > 0){ + + if (bgh>0 && bgw > 0){ renderImage( ctx, image, - bgsx, // source X : 0 + bgsx, // source X : 0 bgsy, // source Y : 1695 bgw, // source Width : 18 bgh, // source Height : 1677 @@ -924,65 +924,65 @@ _html2canvas.Parse = function ( images, options ) { bgw, // destination width : 18 bgh // destination height : 1677 ); - + } break; default: - - - - bgp.top = bgp.top-Math.ceil(bgp.top/image.height)*image.height; - - - for(bgy=(bounds.top+bgp.top);bgyh+bgy){ height = (h+bgy)-bgy; }else{ height = image.height; } // console.log(height); - + if (bgy0){ bgp.top += add; } - bgy = Math.floor(bgy+image.height)-add; + bgy = Math.floor(bgy+image.height)-add; } break; - - - } + + + } }else{ h2clog("html2canvas: Error loading background:" + background_image); //console.log(images); } - + } } - + function renderElement(el, parentStack){ - - var bounds = _html2canvas.Util.Bounds(el), - x = bounds.left, - y = bounds.top, - w = bounds.width, - h = bounds.height, + + var bounds = _html2canvas.Util.Bounds(el), + x = bounds.left, + y = bounds.top, + w = bounds.width, + h = bounds.height, image, bgcolor = getCSS(el, "backgroundColor"), cssPosition = getCSS(el, "position"), @@ -998,7 +998,7 @@ _html2canvas.Parse = function ( images, options ) { paddingTop, paddingRight, paddingBottom; - + if (!parentStack){ docDim = docSize(); parentStack = { @@ -1007,12 +1007,12 @@ _html2canvas.Parse = function ( images, options ) { }else{ docDim = {}; } - + //var zindex = this.formatZ(this.getCSS(el,"zIndex"),cssPosition,parentStack.zIndex,el.parentNode); - + zindex = setZ( getCSS( el, "zIndex"), parentStack.zIndex ); - + stack = { @@ -1021,55 +1021,55 @@ _html2canvas.Parse = function ( images, options ) { opacity: opacity * parentStack.opacity, cssPosition: cssPosition }; - - - + + + // TODO correct overflow for absolute content residing under a static position - + if (parentStack.clip){ stack.clip = _html2canvas.Util.Extend( {}, parentStack.clip ); //stack.clip = parentStack.clip; // stack.clip.height = stack.clip.height - parentStack.borders[2].width; - } - - + } + + if ( options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(el, "overflow")) === true && /(BODY)/i.test(el.nodeName) === false ){ if (stack.clip){ stack.clip = clipBounds(stack.clip, bounds); }else{ stack.clip = bounds; } - } + } stackLength = zindex.children.push(stack); - - ctx = zindex.children[stackLength-1].ctx; - - ctx.setVariable("globalAlpha", stack.opacity); + + ctx = zindex.children[stackLength-1].ctx; + + ctx.setVariable("globalAlpha", stack.opacity); // draw element borders borders = renderBorders(el, ctx, bounds, false); stack.borders = borders; - + // let's modify clip area for child elements, so borders dont get overwritten - + /* if (stack.clip){ - stack.clip.width = stack.clip.width-(borders[1].width); - stack.clip.height = stack.clip.height-(borders[2].width); + stack.clip.width = stack.clip.width-(borders[1].width); + stack.clip.height = stack.clip.height-(borders[2].width); } */ - if (ignoreElementsRegExp.test(el.nodeName) && options.iframeDefault !== "transparent"){ + if (ignoreElementsRegExp.test(el.nodeName) && options.iframeDefault !== "transparent"){ if (options.iframeDefault === "default"){ bgcolor = "#efefef"; }else{ - bgcolor = options.iframeDefault; + bgcolor = options.iframeDefault; } } - - // draw base element bgcolor + + // draw base element bgcolor bgbounds = { left: x + borders[3].width, @@ -1077,16 +1077,16 @@ _html2canvas.Parse = function ( images, options ) { width: w - (borders[1].width + borders[3].width), height: h - (borders[0].width + borders[2].width) }; - - //if (this.withinBounds(stack.clip,bgbounds)){ - + + //if (this.withinBounds(stack.clip,bgbounds)){ + if (stack.clip){ bgbounds = clipBounds(bgbounds, stack.clip); - - //} - + + //} + } - + if (bgbounds.height > 0 && bgbounds.width > 0){ renderRect( @@ -1097,10 +1097,10 @@ _html2canvas.Parse = function ( images, options ) { bgbounds.height, bgcolor ); - - renderBackground(el, bgbounds, ctx); + + renderBackground(el, bgbounds, ctx); } - + switch(el.nodeName){ case "IMG": imgSrc = el.getAttribute('src'); @@ -1111,8 +1111,8 @@ _html2canvas.Parse = function ( images, options ) { paddingTop = getCSSInt(el, 'paddingTop'); paddingRight = getCSSInt(el, 'paddingRight'); paddingBottom = getCSSInt(el, 'paddingBottom'); - - + + renderImage( ctx, image, @@ -1123,9 +1123,9 @@ _html2canvas.Parse = function ( images, options ) { x + paddingLeft + borders[3].width, //dx y + paddingTop + borders[0].width, // dy bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), //dw - bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh + bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh ); - + }else{ h2clog("html2canvas: Error loading :" + imgSrc); } @@ -1134,13 +1134,13 @@ _html2canvas.Parse = function ( images, options ) { // TODO add all relevant type's, i.e. HTML5 new stuff // todo add support for placeholder attribute for browsers which support it if (/^(text|url|email|submit|button|reset)$/.test(el.type) && el.value.length > 0){ - + renderFormValue(el, bounds, stack); - + /* this just doesn't work well enough - + this.newText(el,{ nodeValue:el.value, splitText: function(){ @@ -1186,18 +1186,18 @@ _html2canvas.Parse = function ( images, options ) { return zindex.children[stackLength - 1]; } - - - + + + function parseElement (el, stack) { - + // skip hidden elements and their children - if (getCSS(el, 'display') !== "none" && getCSS(el, 'visibility') !== "hidden") { - + if (getCSS(el, 'display') !== "none" && getCSS(el, 'visibility') !== "hidden") { + stack = renderElement(el, stack) || stack; - + ctx = stack.ctx; - + if ( !ignoreElementsRegExp.test( el.nodeName ) ) { var elementChildren = _html2canvas.Util.Children( el ), i, @@ -1205,31 +1205,31 @@ _html2canvas.Parse = function ( images, options ) { childrenLen; for (i = 0, childrenLen = elementChildren.length; i < childrenLen; i+=1) { node = elementChildren[i]; - + if ( node.nodeType === 1 ) { - parseElement(node, stack); - }else if ( node.nodeType === 3 ) { + parseElement(node, stack); + }else if ( node.nodeType === 3 ) { renderText(el, node, stack); - } - + } + } - - } + + } } } stack = renderElement(element, null); - + /* SVG powered HTML rendering, non-tainted canvas available from FF 11+ onwards */ - + if ( support.svgRendering ) { (function( body ){ var img = new Image(), size = docSize(), html = ""; - + function parseDOM( el ) { var children = _html2canvas.Util.Children( el ), len = children.length, @@ -1242,14 +1242,14 @@ _html2canvas.Parse = function ( images, options ) { elm = children[ i ]; if ( elm.nodeType === 3 ) { // Text node - + html += elm.nodeValue.replace(/\/g,">"); } else if ( elm.nodeType === 1 ) { // Element - if ( !/^(script|meta|title)$/.test(elm.nodeName.toLowerCase()) ) { - + if ( !/^(script|meta|title)$/.test(elm.nodeName.toLowerCase()) ) { + html += "<" + elm.nodeName.toLowerCase(); - + // add attributes if ( elm.hasAttributes() ) { attr = elm.attributes; @@ -1258,21 +1258,21 @@ _html2canvas.Parse = function ( images, options ) { html += " " + attr[ a ].name + '="' + attr[ a ].value + '"'; } } - - + + html += '>'; - + parseDOM( elm ); - - + + html += ""; } } - + } - + } - + parseDOM( body ); img.src = [ "data:image/svg+xml,", @@ -1284,27 +1284,27 @@ _html2canvas.Parse = function ( images, options ) { "", "" ].join(""); - - - + + + img.onload = function() { stack.svgRender = img; }; - + })( document.documentElement ); - + } - - + + // parse every child element - for (i = 0, children = element.children, childrenLen = children.length; i < childrenLen; i+=1){ - parseElement(children[i], stack); + for (i = 0, children = element.children, childrenLen = children.length; i < childrenLen; i+=1){ + parseElement(children[i], stack); } - - + + stack.backgroundColor = getCSS( document.documentElement, "backgroundColor" ); - + return stack; }; @@ -1313,5 +1313,5 @@ function h2czContext(zindex) { return { zindex: zindex, children: [] - }; + }; } diff --git a/src/Renderer.js b/src/Renderer.js index 834767e..f88be36 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -9,7 +9,7 @@ _html2canvas.Renderer = function(parseQueue, options){ var queue = []; - + function sortZ(zStack){ var subStacks = [], stackValues = [], @@ -20,40 +20,40 @@ _html2canvas.Renderer = function(parseQueue, options){ zValue, zLen, stackChild, - b, + b, subStackLen; - + for (s = 0, zLen = zStackChildren.length; s < zLen; s+=1){ - + stackChild = zStackChildren[s]; - + if (stackChild.children && stackChild.children.length > 0){ subStacks.push(stackChild); stackValues.push(stackChild.zindex); - }else{ + }else{ queue.push(stackChild); - } - + } + } - + stackValues.sort(function(a, b) { return a - b; }); - + for (i = 0, stackLen = stackValues.length; i < stackLen; i+=1){ zValue = stackValues[i]; for (b = 0, subStackLen = subStacks.length; b <= subStackLen; b+=1){ - + if (subStacks[b].zindex === zValue){ stackChild = subStacks.splice(b, 1); sortZ(stackChild[0]); break; - + } - } + } } - + } diff --git a/src/Util.js b/src/Util.js index 93971cd..9dd865a 100644 --- a/src/Util.js +++ b/src/Util.js @@ -8,20 +8,20 @@ html2canvas = function( elements, opts ) { - + var queue, canvas, options = { // general logging: false, elements: elements, - + // preload options proxy: "http://html2canvas.appspot.com/", timeout: 0, // no timeout useCORS: false, // try to load images as CORS (where available), before falling back to proxy allowTaint: false, // whether to allow images to taint the canvas, won't need proxy if set to true - + // parse options svgRendering: false, // use svg powered rendering where available (FF11+) iframeDefault: "default", @@ -35,11 +35,11 @@ html2canvas = function( elements, opts ) { width: null, height: null, taintTest: true, // do a taint test with all images before applying to canvas - renderer: "Canvas" + renderer: "Canvas" }, renderer; - + options = _html2canvas.Util.Extend(opts, options); - + if (typeof options.renderer === "string" && _html2canvas.Renderer[options.renderer] !== undefined) { options._renderer = _html2canvas.Renderer[options.renderer]( options ); } else if (typeof options.renderer === "function") { @@ -50,34 +50,34 @@ html2canvas = function( elements, opts ) { _html2canvas.logging = options.logging; options.complete = function( images ) { - + if (typeof options.onpreloaded === "function") { if ( options.onpreloaded( images ) === false ) { return; } } queue = _html2canvas.Parse( images, options ); - + if (typeof options.onparsed === "function") { if ( options.onparsed( queue ) === false ) { return; } } - + canvas = _html2canvas.Renderer( queue, options ); - + if (typeof options.onrendered === "function") { options.onrendered( canvas ); } - - + + }; - + // for pages without images, we still want this to be async, i.e. return methods before executing window.setTimeout( function(){ _html2canvas.Preload( options ); - }, 0 ); - + }, 0 ); + return { render: function( queue, opts ) { return _html2canvas.Renderer( queue, _html2canvas.Util.Extend(opts, options) ); diff --git a/src/plugins/jquery.plugin.html2canvas.js b/src/plugins/jquery.plugin.html2canvas.js index 43a8c0e..8584fea 100644 --- a/src/plugins/jquery.plugin.html2canvas.js +++ b/src/plugins/jquery.plugin.html2canvas.js @@ -21,8 +21,8 @@ console.profileEnd(); } $canvas.css({ - position: 'absolute', - left: 0, + position: 'absolute', + left: 0, top: 0 }).appendTo(document.body); $canvas.siblings().toggle(); @@ -32,7 +32,7 @@ throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden")); }); throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms
",4000); - + // test if canvas is read-able try { $canvas[0].toDataURL(); @@ -43,7 +43,7 @@ } } }; - + html2obj = html2canvas(this, options); function throwMessage(msg,duration){ diff --git a/src/renderers/Canvas.js b/src/renderers/Canvas.js index 5361bb6..4941fc4 100644 --- a/src/renderers/Canvas.js +++ b/src/renderers/Canvas.js @@ -10,7 +10,7 @@ _html2canvas.Renderer.Canvas = function( options ) { options = options || {}; - + var doc = document, canvas = options.canvas || doc.createElement('canvas'), usingFlashcanvas = false, @@ -19,7 +19,7 @@ _html2canvas.Renderer.Canvas = function( options ) { methods, flashMaxSize = 2880; // flash bitmap limited to 2880x2880px // http://stackoverflow.com/questions/2033792/argumenterror-error-2015-invalid-bitmapdata - + if (canvas.getContext){ h2clog("html2canvas: Renderer: using canvas renderer"); canvasReadyToDraw = true; @@ -28,62 +28,62 @@ _html2canvas.Renderer.Canvas = function( options ) { h2clog("html2canvas: Renderer: canvas not available, using flashcanvas"); var script = doc.createElement("script"); script.src = options.flashcanvas; - + script.onload = (function(script, func){ - var intervalFunc; - + var intervalFunc; + if (script.onload === undefined) { // IE lack of support for script onload - + if( script.onreadystatechange !== undefined ) { - + intervalFunc = function() { if (script.readyState !== "loaded" && script.readyState !== "complete") { window.setTimeout( intervalFunc, 250 ); - + } else { // it is loaded func(); - + } - + }; - + window.setTimeout( intervalFunc, 250 ); } else { h2clog("html2canvas: Renderer: Can't track when flashcanvas is loaded"); } - + } else { return func; } - + })(script, function(){ - + if (typeof window.FlashCanvas !== "undefined") { h2clog("html2canvas: Renderer: Flashcanvas initialized"); window.FlashCanvas.initElement( canvas ); - + canvasReadyToDraw = true; if ( _createCalled !== false ) { methods._create.apply( null, _createCalled ); } } }); - + doc.body.appendChild( script ); - } + } methods = { _create: function( zStack, options, doc, queue, _html2canvas ) { - + if ( !canvasReadyToDraw ) { _createCalled = arguments; return canvas; } - + var ctx = canvas.getContext("2d"), storageContext, i, @@ -98,52 +98,52 @@ _html2canvas.Renderer.Canvas = function( options ) { testctx = ( hasCTX ) ? testCanvas.getContext("2d") : {}, safeImages = [], fstyle; - + canvas.width = canvas.style.width = (!usingFlashcanvas) ? options.width || zStack.ctx.width : Math.min(flashMaxSize, (options.width || zStack.ctx.width) ); canvas.height = canvas.style.height = (!usingFlashcanvas) ? options.height || zStack.ctx.height : Math.min(flashMaxSize, (options.height || zStack.ctx.height) ); - + fstyle = ctx.fillStyle; ctx.fillStyle = zStack.backgroundColor; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = fstyle; if ( options.svgRendering && zStack.svgRender !== undefined ) { - // TODO: enable async rendering to support this + // TODO: enable async rendering to support this ctx.drawImage( zStack.svgRender, 0, 0 ); } else { for ( i = 0, queueLen = queue.length; i < queueLen; i+=1 ) { - + storageContext = queue.splice(0, 1)[0]; - storageContext.canvasPosition = storageContext.canvasPosition || {}; - - //this.canvasRenderContext(storageContext,parentctx); + storageContext.canvasPosition = storageContext.canvasPosition || {}; + + //this.canvasRenderContext(storageContext,parentctx); // set common settings for canvas ctx.textBaseline = "bottom"; - + if (storageContext.clip){ ctx.save(); ctx.beginPath(); // console.log(storageContext); ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height); ctx.clip(); - + } - + if (storageContext.ctx.storage){ - + for (a = 0, storageLen = storageContext.ctx.storage.length; a < storageLen; a+=1){ - + renderItem = storageContext.ctx.storage[a]; - - + + switch(renderItem.type){ case "variable": - ctx[renderItem.name] = renderItem['arguments']; + ctx[renderItem.name] = renderItem['arguments']; break; case "function": if (renderItem.name === "fillRect") { - + if (!usingFlashcanvas || renderItem['arguments'][0] + renderItem['arguments'][2] < flashMaxSize && renderItem['arguments'][1] + renderItem['arguments'][3] < flashMaxSize) { ctx.fillRect.apply( ctx, renderItem['arguments'] ); } @@ -152,45 +152,45 @@ _html2canvas.Renderer.Canvas = function( options ) { ctx.fillText.apply( ctx, renderItem['arguments'] ); } }else if(renderItem.name === "drawImage") { - - if (renderItem['arguments'][8] > 0 && renderItem['arguments'][7]){ + + if (renderItem['arguments'][8] > 0 && renderItem['arguments'][7]){ if ( hasCTX && options.taintTest ) { if ( safeImages.indexOf( renderItem['arguments'][ 0 ].src ) === -1 ) { testctx.drawImage( renderItem['arguments'][ 0 ], 0, 0 ); try { testctx.getImageData( 0, 0, 1, 1 ); - } catch(e) { + } catch(e) { testCanvas = doc.createElement("canvas"); testctx = testCanvas.getContext("2d"); continue; } - + safeImages.push( renderItem['arguments'][ 0 ].src ); - + } } - ctx.drawImage.apply( ctx, renderItem['arguments'] ); - } + ctx.drawImage.apply( ctx, renderItem['arguments'] ); + } } - - + + break; default: - + } - + } - } + } if (storageContext.clip){ ctx.restore(); } - - } + + } } - + h2clog("html2canvas: Renderer: Canvas renderer done - returning canvas obj"); - + queueLen = options.elements.length; if (queueLen === 1) { @@ -201,29 +201,29 @@ _html2canvas.Renderer.Canvas = function( options ) { newCanvas.width = bounds.width; newCanvas.height = bounds.height; ctx = newCanvas.getContext("2d"); - + ctx.drawImage( canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height ); canvas = null; return newCanvas; } } /*else { // TODO clip and resize multiple elements - + for ( i = 0; i < queueLen; i+=1 ) { if (options.elements[ i ] instanceof Element) { - + } - + } } */ - - - + + + return canvas; } }; - + return methods; }; diff --git a/src/renderers/SVG.js b/src/renderers/SVG.js index cc34da3..747e045 100644 --- a/src/renderers/SVG.js +++ b/src/renderers/SVG.js @@ -12,7 +12,7 @@ _html2canvas.Renderer.SVG = function( options ) { options = options || {}; - + var doc = document, svgNS = "http://www.w3.org/2000/svg", svg = doc.createElementNS(svgNS, "svg"), @@ -30,8 +30,8 @@ _html2canvas.Renderer.SVG = function( options ) { fontStyle, clipId = 0, methods; - - + + methods = { _create: function( zStack, options, doc, queue, _html2canvas ) { svg.setAttribute("version", "1.1"); @@ -42,17 +42,17 @@ _html2canvas.Renderer.SVG = function( options ) { svg.setAttribute("height", Math.max(zStack.ctx.height, options.height) + "px"); svg.setAttribute("preserveAspectRatio", "none"); svg.appendChild(defs); - - - - for (i = 0, queueLen = queue.length; i < queueLen; i+=1){ - - storageContext = queue.splice(0, 1)[0]; - storageContext.canvasPosition = storageContext.canvasPosition || {}; - - //this.canvasRenderContext(storageContext,parentctx); - + + + for (i = 0, queueLen = queue.length; i < queueLen; i+=1){ + + storageContext = queue.splice(0, 1)[0]; + storageContext.canvasPosition = storageContext.canvasPosition || {}; + + //this.canvasRenderContext(storageContext,parentctx); + + /* if (storageContext.clip){ ctx.save(); @@ -60,24 +60,24 @@ _html2canvas.Renderer.SVG = function( options ) { // console.log(storageContext); ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height); ctx.clip(); - + }*/ - + if (storageContext.ctx.storage){ - + for (a = 0, storageLen = storageContext.ctx.storage.length; a < storageLen; a+=1){ - + renderItem = storageContext.ctx.storage[a]; - - - + + + switch(renderItem.type){ case "variable": - settings[renderItem.name] = renderItem['arguments']; + settings[renderItem.name] = renderItem['arguments']; break; case "function": if (renderItem.name === "fillRect") { - + el = doc.createElementNS(svgNS, "rect"); el.setAttribute("x", renderItem['arguments'][0]); el.setAttribute("y", renderItem['arguments'][1]); @@ -88,67 +88,67 @@ _html2canvas.Renderer.SVG = function( options ) { } else if(renderItem.name === "fillText") { el = doc.createElementNS(svgNS, "text"); - + fontStyle = settings.font.split(" "); - + el.style.fontVariant = fontStyle.splice(0, 1)[0]; el.style.fontWeight = fontStyle.splice(0, 1)[0]; el.style.fontStyle = fontStyle.splice(0, 1)[0]; el.style.fontSize = fontStyle.splice(0, 1)[0]; - - el.setAttribute("x", renderItem['arguments'][1]); + + el.setAttribute("x", renderItem['arguments'][1]); el.setAttribute("y", renderItem['arguments'][2] - (parseInt(el.style.fontSize, 10) + 3)); - + el.setAttribute("fill", settings.fillStyle); - - - - + + + + // TODO get proper baseline el.style.dominantBaseline = "text-before-edge"; el.style.fontFamily = fontStyle.join(" "); text = doc.createTextNode(renderItem['arguments'][0]); el.appendChild(text); - - + + svg.appendChild(el); - - - + + + } else if(renderItem.name === "drawImage") { if (renderItem['arguments'][8] > 0 && renderItem['arguments'][7]){ - + // TODO check whether even any clipping is necessary for this particular image el = doc.createElementNS(svgNS, "clipPath"); - el.setAttribute("id", "clipId" + clipId); - + el.setAttribute("id", "clipId" + clipId); + text = doc.createElementNS(svgNS, "rect"); - text.setAttribute("x", renderItem['arguments'][5] ); + text.setAttribute("x", renderItem['arguments'][5] ); text.setAttribute("y", renderItem['arguments'][6]); - - text.setAttribute("width", renderItem['arguments'][3]); + + text.setAttribute("width", renderItem['arguments'][3]); text.setAttribute("height", renderItem['arguments'][4]); el.appendChild(text); defs.appendChild(el); - + el = doc.createElementNS(svgNS, "image"); el.setAttributeNS(xlinkNS, "xlink:href", renderItem['arguments'][0].src); - el.setAttribute("width", renderItem['arguments'][7]); - el.setAttribute("height", renderItem['arguments'][8]); - el.setAttribute("x", renderItem['arguments'][5]); + el.setAttribute("width", renderItem['arguments'][7]); + el.setAttribute("height", renderItem['arguments'][8]); + el.setAttribute("x", renderItem['arguments'][5]); el.setAttribute("y", renderItem['arguments'][6]); el.setAttribute("clip-path", "url(#clipId" + clipId + ")"); // el.setAttribute("xlink:href", ); - + el.setAttribute("preserveAspectRatio", "none"); - + svg.appendChild(el); - - - clipId += 1; + + + clipId += 1; /* ctx.drawImage( renderItem['arguments'][0], @@ -162,45 +162,45 @@ _html2canvas.Renderer.SVG = function( options ) { renderItem['arguments'][8] ); */ - } + } } - - - + + + break; default: - + } - + } - } + } /* if (storageContext.clip){ ctx.restore(); } */ - - + + } - - - - - - - - - - + + + + + + + + + + h2clog("html2canvas: Renderer: SVG Renderer done - returning SVG DOM obj"); - + return svg; } - }; - + }; + return methods; - - + + }; diff --git a/tests/test.js b/tests/test.js index 0b479c5..fecd191 100644 --- a/tests/test.js +++ b/tests/test.js @@ -15,13 +15,13 @@ var h2cSelector, h2cOptions; } window.onload = function() { h2cSelector = [document.body]; - + if (window.setUp) { window.setUp(); } - + setTimeout(function() { - + $(h2cSelector).html2canvas($.extend({ flashcanvas: "../external/flashcanvas.min.js", logging: true,