diff --git a/.gitignore b/.gitignore index 2ad031c..227e65e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /tests/cache/ /tests/flashcanvas.html /lib/ +/bin/ /build/ image.jpg /.project @@ -12,4 +13,5 @@ node_modules/ .envrc server.js *.sublime-workspace -chromedriver.log \ No newline at end of file +chromedriver.log +*.baseline \ No newline at end of file diff --git a/LICENSE b/LICENSE index d60aa56..a73ffc9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,22 @@ -/* - The MIT License +Copyright (c) 2012 Niklas von Hertzen - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/grunt.js b/grunt.js index 81cfa9e..552c691 100644 --- a/grunt.js +++ b/grunt.js @@ -13,7 +13,7 @@ module.exports = function(grunt) { post: '})(window,document);' }, lint: { - files: ['grunt.js', 'build/<%= pkg.name %>.js'] + files: ['build/<%= pkg.name %>.js'] }, qunit: { files: ['tests/qunit/index.html'] @@ -55,7 +55,19 @@ module.exports = function(grunt) { uglify: {} }); + var selenium = require("./tests/selenium.js"); + grunt.registerTask('webdriver', 'Browser render tests', function(arg1) { + + var done = this.async(); + + if (arguments.length === 0) { + selenium.tests(); + } else { + selenium[arg1].apply(null, arguments); + } + }); + // Default task. - grunt.registerTask('default', 'concat lint qunit min'); + grunt.registerTask('default', 'concat lint qunit webdriver min'); }; diff --git a/package.json b/package.json index 3f3ba81..91a07a2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,14 @@ "author": { "name":"Niklas von Hertzen (@niklasvh)" }, + "dependencies": { + "base64-arraybuffer": ">= 0.1.0", + "png-js": ">= 0.1.1", + "webdriver.js": ">= 0.1.0" + }, "homepage": "http://html2canvas.hertzen.com", - "licenses": [{"type": "MIT"}] + "licenses": [{ + "type": "MIT" + }] } \ No newline at end of file diff --git a/readme.md b/readme.md index 17a0317..e5f50d9 100644 --- a/readme.md +++ b/readme.md @@ -7,13 +7,12 @@ html2canvas ###How does it work?### -The script renders the current page as a canvas image, by reading the DOM and the different styles applied to the elements. However, as many elements are displayed differently on different browsers and operating systems (such as form elements such as radio buttons or checkboxes) as well as +The script renders the current page as a canvas image, by reading the DOM and the different styles applied to the elements. -It does not require any rendering from the server, as the whole image is created on the clients browser. However, for browsers without canvas support alternatives such as flashcanvas or ExplorerCanvas are necessary to create the image. +It does not require any rendering from the server, as the whole image is created on the clients browser. However, as it is heavily dependent on the browser, this library is *not suitable* to be used on for example on node.js. +It doesn't magically circumvent and browser content policy restrictions either, so rendering cross origin content will require a proxy to get the content to the same origin. -Additionally, to render iframe content or images situated outside of the same origin policy a proxy will be necessary to load the content to the users browser. - -The script is still in a very experimental state, so I don't recommend using it in a production environment nor start building applications with it yet, as there will be still major changes made. However, please do test it out and report your findings, especially if something should be working, but is displaying it incorrectly. +The script is still in a **very experimental state**, so I don't recommend using it in a production environment nor start building applications with it yet, as there will be still major changes made. ###Browser compatibility### @@ -21,8 +20,8 @@ The script should work fine on the following browsers: * Firefox 3.5+ * Google Chrome -* Newer versions of Opera (exactly how new is yet to be determined) -* >=IE9 (Older versions compatible with the use of flashcanvas) +* Opera 12+ +* IE9+ Note that the compatibility will most likely be increased in future builds, as many of the current restrictions have at least partial work arounds, which can be used with older browser versions. @@ -46,6 +45,41 @@ To access the created canvas, provide the `onrendered` event in the options whic } }); +### Building ### + +The library uses grunt for building. Alternatively, you can download ready builds from the downloads page. + +Run the full build process (including lint, qunit and webdriver tests): + + $ grunt + +Skip lint and tests and simply build from source: + + $ grunt concat + $ grunt min + +### Running tests ### + +The library has two sets of tests. The first set is a number of qunit tests that check that different values parsed by browsers are correctly converted in html2canvas. To run these tests with grunt you'll need phantomjs. + +The other set of tests run Firefox, Chrome and Internet Explorer with webdriver. The selenium standalone server (runs on Java) is required for these tests and can be downloaded from here. They capture an actual screenshot from the test pages and compare the image to the screenshot created by html2canvas and calculate the percentage differences. These tests generally aren't expected to provide 100% matches, but while commiting changes, these should generally not go decrease from the baseline values. + +If you didn't download `html2canvas` from `npm`, start by downloading the dependencies: + + $ npm update + +Run qunit tests: + + $ grunt test + +Run webdriver tests: + + $ java -jar /path/to/selenium-server-standalone-2.xx.x.jar + $ grunt webdriver + +Commiting improvements in baseline values: + + $ grunt webdriver:baseline ### Examples ### @@ -53,6 +87,11 @@ For more information and examples, please visit the webdriver + v0.34 - 26.6.2012 * Removed (last?) jQuery dependencies (niklasvh) diff --git a/src/Core.js b/src/Core.js index cc2294f..ef4bf09 100644 --- a/src/Core.js +++ b/src/Core.js @@ -7,28 +7,28 @@ html2canvas; function h2clog(a) { - if (_html2canvas.logging && window.console && window.console.log) { - window.console.log(a); - } + if (_html2canvas.logging && window.console && window.console.log) { + window.console.log(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 ); - } else { - src = src.substr( 4 ); - src = src.substr( 0, src.length - 1 ); - } - + 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 ); + } else { + src = src.substr( 4 ); + src = src.substr( 0, src.length - 1 ); + } + + return src; }; _html2canvas.Util.parseBackgroundImage = function (value) { @@ -131,196 +131,178 @@ _html2canvas.Util.parseBackgroundImage = function (value) { }; _html2canvas.Util.Bounds = function getBounds (el) { - var clientRect, - bounds = {}; + var clientRect, + bounds = {}; - if (el.getBoundingClientRect){ - clientRect = 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; + // 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); + // 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; + return bounds; - } + } }; _html2canvas.Util.getCSS = function (el, attribute) { - // return $(el).css(attribute); + // return $(el).css(attribute); var val, isBackgroundSizePosition = !!attribute.match( /^background(Size|Position)$/ ); - function toPX( attribute, val ) { - var rsLeft = el.runtimeStyle && el.runtimeStyle[ attribute ], - left, - style = el.style; + 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 + // 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 + // 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 - if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( val ) && /^-?\d/.test( val ) ) { + if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( val ) && /^-?\d/.test( val ) ) { - // Remember the original values - left = style.left; + // Remember the original values + left = style.left; - // Put in the new values to get a computed value out - if ( rsLeft ) { - el.runtimeStyle.left = el.currentStyle.left; - } - style.left = attribute === "fontSize" ? "1em" : (val || 0); - val = style.pixelLeft + "px"; + // Put in the new values to get a computed value out + if ( rsLeft ) { + el.runtimeStyle.left = el.currentStyle.left; + } + style.left = attribute === "fontSize" ? "1em" : (val || 0); + val = style.pixelLeft + "px"; - // Revert the changed values - style.left = left; - if ( rsLeft ) { - el.runtimeStyle.left = rsLeft; - } - - } - - if (!/^(thin|medium|thick)$/i.test( val )) { - return Math.round(parseFloat( val )) + "px"; - } - - return val; + // Revert the changed values + style.left = left; + if ( rsLeft ) { + el.runtimeStyle.left = rsLeft; + } } - - - if ( window.getComputedStyle ) { - if ( previousElement !== el ) { - computedCSS = document.defaultView.getComputedStyle(el, null); - } - val = computedCSS[ attribute ]; - - if ( isBackgroundSizePosition ) { - - 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 ( /border(Top|Bottom)(Left|Right)Radius/.test( attribute) ) { - var arr = val.split(" "); - if ( arr.length <= 1 ) { - arr[ 1 ] = arr[ 0 ]; - } - arr[ 0 ] = parseInt( arr[ 0 ], 10 ); - arr[ 1 ] = parseInt( arr[ 1 ], 10 ); - val = arr; - } - - } else if ( el.currentStyle ) { - // IE 9> - if ( isBackgroundSizePosition ) { - // 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": - val = "1px"; - break; - case "medium": - val = "0px"; // this is wrong, it should be 3px but IE uses medium for no border as well.. TODO find a work around - break; - case "thick": - val = "5px"; - break; - } - } - } - - - + if (!/^(thin|medium|thick)$/i.test( val )) { + return Math.round(parseFloat( val )) + "px"; } - - - return val; + } -//return $(el).css(attribute); + if ( window.getComputedStyle ) { + if ( previousElement !== el ) { + computedCSS = document.defaultView.getComputedStyle(el, null); + } + val = computedCSS[ attribute ]; + if ( isBackgroundSizePosition ) { + + 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 ( /border(Top|Bottom)(Left|Right)Radius/.test( attribute) ) { + var arr = val.split(" "); + if ( arr.length <= 1 ) { + arr[ 1 ] = arr[ 0 ]; + } + arr[ 0 ] = parseInt( arr[ 0 ], 10 ); + arr[ 1 ] = parseInt( arr[ 1 ], 10 ); + val = arr; + } + + } else if ( el.currentStyle ) { + // IE 9> + if ( isBackgroundSizePosition ) { + // 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": + val = "1px"; + break; + case "medium": + val = "0px"; // this is wrong, it should be 3px but IE uses medium for no border as well.. TODO find a work around + break; + case "thick": + val = "5px"; + break; + } + } + } + } + + return val; }; function backgroundBoundsFactory( prop, el, bounds, image ) { - // TODO add support for multi image backgrounds + // TODO add support for multi image backgrounds - var bgposition = _html2canvas.Util.getCSS( el, prop ) , - topPos, - left, - percentage, - val; + var bgposition = _html2canvas.Util.getCSS( el, prop ) , + topPos, + left, + percentage, + val; - if (bgposition.length === 1){ - val = bgposition; + if (bgposition.length === 1){ + val = bgposition; - bgposition = []; + bgposition = []; - bgposition[0] = val; - bgposition[1] = val; - } + bgposition[0] = val; + bgposition[1] = val; + } + 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[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){ - - percentage = (parseFloat(bgposition[1])/100); - topPos = ((bounds.height * percentage)-(image.height*percentage)); - }else{ - topPos = parseInt(bgposition[1],10); - } + if (bgposition[1].toString().indexOf("%") !== -1){ + percentage = (parseFloat(bgposition[1])/100); + topPos = ((bounds.height * percentage)-(image.height*percentage)); + } else { + topPos = parseInt(bgposition[1],10); + } return [left, topPos]; } - _html2canvas.Util.BackgroundPosition = function( el, bounds, image ) { var result = backgroundBoundsFactory( 'backgroundPosition', el, bounds, image ); return { left: result[0], top: result[1] }; }; - _html2canvas.Util.BackgroundSize = function( el, bounds, image ) { var result = backgroundBoundsFactory( 'backgroundSize', el, bounds, image ); return { width: result[1], height: result[0] }; }; _html2canvas.Util.Extend = function (options, defaults) { - for (var key in options) { - if (options.hasOwnProperty(key)) { - defaults[key] = options[key]; - } + for (var key in options) { + if (options.hasOwnProperty(key)) { + defaults[key] = options[key]; } - return defaults; + } + return defaults; }; @@ -333,43 +315,43 @@ _html2canvas.Util.Extend = function (options, defaults) { _html2canvas.Util.Children = function( elem ) { - var children; - try { + var children; + try { - children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? - elem.contentDocument || elem.contentWindow.document : (function( array ){ - var ret = []; + children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? + elem.contentDocument || elem.contentWindow.document : (function( array ){ + var ret = []; - if ( array !== null ) { + if ( array !== null ) { - (function( first, second ) { - var i = first.length, - j = 0; - - if ( typeof second.length === "number" ) { - for ( var l = second.length; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - })( ret, array ); + (function( first, second ) { + var i = first.length, + j = 0; + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; } - return ret; - })( elem.childNodes ); + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } - } catch (ex) { - h2clog("html2canvas.Util.Children failed with exception: " + ex.message); - children = []; - } - return children; + first.length = i; + + return first; + })( ret, array ); + + } + + return ret; + })( elem.childNodes ); + + } catch (ex) { + h2clog("html2canvas.Util.Children failed with exception: " + ex.message); + children = []; + } + return children; }; diff --git a/src/Core.js.orig b/src/Core.js.orig new file mode 100644 index 0000000..798e97b --- /dev/null +++ b/src/Core.js.orig @@ -0,0 +1,451 @@ +"use strict"; + +var _html2canvas = {}, +previousElement, +computedCSS, +html2canvas; + + +function h2clog(a) { + if (_html2canvas.logging && window.console && window.console.log) { + window.console.log(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 ); + } else { + src = src.substr( 4 ); + src = src.substr( 0, src.length - 1 ); + } + + return src; +}; + +_html2canvas.Util.parseBackgroundImage = function (value) { + var whitespace = ' \r\n\t', + method, definition, prefix, prefix_i, block, results = [], + c, mode = 0, numParen = 0, quote, args; + + var appendResult = function(){ + if(method) { + if(definition.substr( 0, 1 ) === '"') { + definition = definition.substr( 1, definition.length - 2 ); + } + if(definition) { + args.push(definition); + } + if(method.substr( 0, 1 ) === '-' && + (prefix_i = method.indexOf( '-', 1 ) + 1) > 0) { + prefix = method.substr( 0, prefix_i); + method = method.substr( prefix_i ); + } + results.push({ + prefix: prefix, + method: method.toLowerCase(), + value: block, + args: args + }); + } + args = []; //for some odd reason, setting .length = 0 didn't work in safari + method = + prefix = + definition = + block = ''; + }; + + appendResult(); + for(var i = 0, ii = value.length; i -1){ + continue; + } + switch(c) { + case '"': + if(!quote) { + quote = c; + } + else if(quote === c) { + quote = null; + } + break; + + case '(': + if(quote) { break; } + else if(mode === 0) { + mode = 1; + block += c; + continue; + } else { + numParen++; + } + break; + + case ')': + if(quote) { break; } + else if(mode === 1) { + if(numParen === 0) { + mode = 0; + block += c; + appendResult(); + continue; + } else { + numParen--; + } + } + break; + + case ',': + if(quote) { break; } + else if(mode === 0) { + appendResult(); + continue; + } + else if (mode === 1) { + if(numParen === 0 && !method.match(/^url$/i)) { + args.push(definition); + definition = ''; + block += c; + continue; + } + } + break; + } + + block += c; + if(mode === 0) { method += c; } + else { definition += c; } + } + appendResult(); + + return results; +}; + +_html2canvas.Util.Bounds = function getBounds (el) { + var clientRect, + bounds = {}; + + 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) { +<<<<<<< HEAD + // return $(el).css(attribute); + + var val, + isBackgroundSizePosition = !!attribute.match( /^background(Size|Position)$/ ); +======= + // return $(el).css(attribute); +>>>>>>> niklasvh/develop + + 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 + + if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( val ) && /^-?\d/.test( val ) ) { + + // Remember the original values + left = style.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + el.runtimeStyle.left = el.currentStyle.left; + } + style.left = attribute === "fontSize" ? "1em" : (val || 0); + val = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + el.runtimeStyle.left = rsLeft; + } + + } + +<<<<<<< HEAD + + + if ( window.getComputedStyle ) { + if ( previousElement !== el ) { + computedCSS = document.defaultView.getComputedStyle(el, null); + } + val = computedCSS[ attribute ]; + + if ( isBackgroundSizePosition ) { + + 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 ( /border(Top|Bottom)(Left|Right)Radius/.test( attribute) ) { + var arr = val.split(" "); + if ( arr.length <= 1 ) { + arr[ 1 ] = arr[ 0 ]; + } + arr[ 0 ] = parseInt( arr[ 0 ], 10 ); + arr[ 1 ] = parseInt( arr[ 1 ], 10 ); + val = arr; + } + + } else if ( el.currentStyle ) { + // IE 9> + if ( isBackgroundSizePosition ) { + // 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": + val = "1px"; + break; + case "medium": + val = "0px"; // this is wrong, it should be 3px but IE uses medium for no border as well.. TODO find a work around + break; + case "thick": + val = "5px"; + break; + } + } + } + + + +======= + if (!/^(thin|medium|thick)$/i.test( val )) { + return Math.round(parseFloat( val )) + "px"; +>>>>>>> niklasvh/develop + } + + return val; + + } + + +<<<<<<< HEAD +//return $(el).css(attribute); + + +}; + + +function backgroundBoundsFactory( prop, el, bounds, image ) { + // TODO add support for multi image backgrounds + + var bgposition = _html2canvas.Util.getCSS( el, prop ) , + topPos, + left, + percentage, + val; + + if (bgposition.length === 1){ + val = bgposition; + + bgposition = []; + + bgposition[0] = val; + bgposition[1] = val; +======= + if ( window.getComputedStyle ) { + if ( previousElement !== el ) { + computedCSS = document.defaultView.getComputedStyle(el, null); +>>>>>>> niklasvh/develop + } + val = computedCSS[ 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 ( /border(Top|Bottom)(Left|Right)Radius/.test( attribute) ) { + var arr = val.split(" "); + if ( arr.length <= 1 ) { + arr[ 1 ] = arr[ 0 ]; + } + arr[ 0 ] = parseInt( arr[ 0 ], 10 ); + arr[ 1 ] = parseInt( arr[ 1 ], 10 ); + val = arr; + } + + } else if ( el.currentStyle ) { + // 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" ] ) ]; + } else { + + val = toPX( attribute, el.currentStyle[ attribute ] ); + + if (/^(border)/i.test( attribute ) && /^(medium|thin|thick)$/i.test( val )) { + switch (val) { + case "thin": + val = "1px"; + break; + case "medium": + val = "0px"; // this is wrong, it should be 3px but IE uses medium for no border as well.. TODO find a work around + break; + case "thick": + val = "5px"; + break; + } + } + } + } + +<<<<<<< HEAD + return [left, topPos]; +} +======= + return val; +}; +>>>>>>> niklasvh/develop + +_html2canvas.Util.BackgroundPosition = function( el, bounds, image ) { + var result = backgroundBoundsFactory( 'backgroundPosition', el, bounds, image ); + return { left: result[0], top: result[1] }; +}; + +<<<<<<< HEAD +_html2canvas.Util.BackgroundSize = function( el, bounds, image ) { + var result = backgroundBoundsFactory( 'backgroundSize', el, bounds, image ); + return { width: result[1], height: result[0] }; +======= +_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); + left = ((bounds.width * percentage)-(image.width*percentage)); + } else { + left = parseInt(bgposition[0],10); + } + + if (bgposition[1].toString().indexOf("%") !== -1){ + percentage = (parseFloat(bgposition[1])/100); + topPos = ((bounds.height * percentage)-(image.height*percentage)); + } else { + topPos = parseInt(bgposition[1],10); + } + + return { + top: topPos, + left: left + }; +>>>>>>> niklasvh/develop +}; + +_html2canvas.Util.Extend = function (options, defaults) { + for (var key in options) { + if (options.hasOwnProperty(key)) { + defaults[key] = options[key]; + } + } + return defaults; +}; + + +/* + * Derived from jQuery.contents() + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + */ +_html2canvas.Util.Children = function( elem ) { + + + var children; + try { + + children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? + elem.contentDocument || elem.contentWindow.document : (function( array ){ + var ret = []; + + if ( array !== null ) { + + (function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + })( ret, array ); + + } + + return ret; + })( elem.childNodes ); + + } catch (ex) { + h2clog("html2canvas.Util.Children failed with exception: " + ex.message); + children = []; + } + return children; +}; diff --git a/src/Font.js b/src/Font.js new file mode 100644 index 0000000..2bc264b --- /dev/null +++ b/src/Font.js @@ -0,0 +1,64 @@ +_html2canvas.Util.Font = (function () { + + var fontData = {}; + + return function(font, fontSize, doc) { + if (fontData[font + "-" + fontSize] !== undefined) { + return fontData[font + "-" + fontSize]; + } + + var container = doc.createElement('div'), + img = doc.createElement('img'), + span = doc.createElement('span'), + sampleText = 'Hidden Text', + baseline, + middle, + metricsObj; + + container.style.visibility = "hidden"; + container.style.fontFamily = font; + container.style.fontSize = fontSize; + container.style.margin = 0; + container.style.padding = 0; + + doc.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"; + + span.style.fontFamily = font; + span.style.fontSize = fontSize; + span.style.margin = 0; + span.style.padding = 0; + + span.appendChild(doc.createTextNode(sampleText)); + container.appendChild(span); + container.appendChild(img); + baseline = (img.offsetTop - span.offsetTop) + 1; + + container.removeChild(span); + container.appendChild(doc.createTextNode(sampleText)); + + 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; + + doc.body.removeChild(container); + + return metricsObj; + }; +})(); diff --git a/src/Parse.js b/src/Parse.js index 1a35142..eb5ba85 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -1,101 +1,31 @@ _html2canvas.Parse = function (images, options) { window.scroll(0,0); - var support = { - rangeBounds: false, - svgRendering: options.svgRendering && (function( ){ - var img = new Image(), - canvas = document.createElement("canvas"), - ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d"); - if (ctx === false) { - // browser doesn't support canvas, good luck supporting SVG on canvas - return false; - } - canvas.width = canvas.height = 10; - img.src = [ - "data:image/svg+xml,", - "", - "", - "
", - "sup", - "
", - "
", - "
" - ].join(""); - try { - ctx.drawImage(img, 0, 0); - canvas.toDataURL(); - } catch(e) { - return false; - } - h2clog('html2canvas: Parse: SVG powered rendering available'); - return true; - - })() - }, - element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default + var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default numDraws = 0, - fontData = {}, doc = element.ownerDocument, + support = _html2canvas.Util.Support(options, doc), ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"), body = doc.body, - r, - testElement, - rangeBounds, - rangeHeight, - stack, - ctx, - docDim, - i, - children, - childrenLen; - - - function docSize(){ - - return { - width: Math.max( - Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth), - Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth), - Math.max(doc.body.clientWidth, doc.documentElement.clientWidth) - ), - height: Math.max( - Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight), - Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight), - Math.max(doc.body.clientHeight, doc.documentElement.clientHeight) - ) - }; - - } + getCSS = _html2canvas.Util.getCSS; 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); - if (r.getBoundingClientRect){ - testElement = doc.createElement('boundtest'); - testElement.style.height = "123px"; - testElement.style.display = "block"; - body.appendChild(testElement); - - r.selectNode(testElement); - rangeBounds = r.getBoundingClientRect(); - rangeHeight = rangeBounds.height; - - if (rangeHeight === 123) { - support.rangeBounds = true; - } - body.removeChild(testElement); - } - + function documentWidth () { + return Math.max( + Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth), + Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth), + Math.max(doc.body.clientWidth, doc.documentElement.clientWidth) + ); } - var getCSS = _html2canvas.Util.getCSS; + function documentHeight () { + return Math.max( + Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight), + Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight), + Math.max(doc.body.clientHeight, doc.documentElement.clientHeight) + ); + } function getCSSInt(element, attribute) { var val = parseInt(getCSS(element, attribute), 10); @@ -135,67 +65,6 @@ _html2canvas.Parse = function (images, options) { 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'), - sampleText = 'Hidden Text', - baseline, - middle, - metricsObj; - - container.style.visibility = "hidden"; - container.style.fontFamily = font; - container.style.fontSize = fontSize; - container.style.margin = 0; - 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"; - - span.style.fontFamily = font; - span.style.fontSize = fontSize; - span.style.margin = 0; - span.style.padding = 0; - - span.appendChild(doc.createTextNode(sampleText)); - container.appendChild(span); - container.appendChild(img); - baseline = (img.offsetTop - span.offsetTop) + 1; - - container.removeChild(span); - container.appendChild(doc.createTextNode(sampleText)); - - 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 (currentText !== null && trimText(currentText).length > 0) { ctx.fillText(currentText, x, y); @@ -205,11 +74,9 @@ _html2canvas.Parse = function (images, options) { function setTextVariables(ctx, el, text_decoration, color) { var align = false, - font_style = getCSS(el, "fontStyle"), bold = getCSS(el, "fontWeight"), family = getCSS(el, "fontFamily"), - size = getCSS(el, "fontSize"), - font_variant = getCSS(el, "fontVariant"); + size = getCSS(el, "fontSize"); switch(parseInt(bold, 10)){ case 401: @@ -221,15 +88,15 @@ _html2canvas.Parse = function (images, options) { } ctx.setVariable("fillStyle", color); - ctx.setVariable("font", [font_style, font_variant, bold, size, family].join(" ")); + ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" ")); ctx.setVariable("textAlign", (align) ? "right" : "left"); if (text_decoration !== "none"){ - return fontMetrics(family, size); + return _html2canvas.Util.Font(family, size, doc); } } - function renderTextDecoration(text_decoration, bounds, metrics, color) { + function renderTextDecoration(ctx, text_decoration, bounds, metrics, color) { switch(text_decoration) { case "underline": // Draws a line at the baseline of the font @@ -237,7 +104,7 @@ _html2canvas.Parse = function (images, options) { renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color); break; case "overline": - renderRect(ctx, bounds.left, bounds.top, bounds.width, 1, color); + renderRect(ctx, bounds.left, Math.round(bounds.top), bounds.width, 1, color); break; case "line-through": // TODO try and find exact position for line-through @@ -246,101 +113,71 @@ _html2canvas.Parse = function (images, options) { } } + function getTextBounds(state, text, textDecoration, isLast) { + var bounds; + if (support.rangeBounds) { + if (textDecoration !== "none" || trimText(text).length !== 0) { + bounds = textRangeBounds(text, state.node, state.textOffset); + } + state.textOffset += text.length; + } else if (state.node && typeof state.node.nodeValue === "string" ){ + var newTextNode = (isLast) ? state.node.splitText(text.length) : null; + bounds = textWrapperBounds(state.node); + state.node = newTextNode; + } + return bounds; + } + + function textRangeBounds(text, textNode, textOffset) { + var range = doc.createRange(); + range.setStart(textNode, textOffset); + range.setEnd(textNode, textOffset + text.length); + return range.getBoundingClientRect(); + } + + function textWrapperBounds(oldTextNode) { + var parent = oldTextNode.parentNode, + wrapElement = doc.createElement('wrapper'), + backupText = oldTextNode.cloneNode(true); + + wrapElement.appendChild(oldTextNode.cloneNode(true)); + parent.replaceChild(wrapElement, oldTextNode); + + var bounds = _html2canvas.Util.Bounds(wrapElement); + parent.replaceChild(backupText, wrapElement); + return bounds; + } + function renderText(el, textNode, stack) { var ctx = stack.ctx, color = getCSS(el, "color"), - text_decoration = getCSS(el, "textDecoration"), - text_align = getCSS(el, "textAlign"), - letter_spacing = getCSS(el, "letterSpacing"), - bounds, - text, + textDecoration = getCSS(el, "textDecoration"), + textAlign = getCSS(el, "textAlign"), metrics, - renderList, - listLen, - newTextNode, - textValue, - textOffset = 0, - oldTextNode, - c, - range, - parent, - wrapElement, - backupText; + textList, + state = { + node: textNode, + textOffset: 0 + }; - textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform")); - text = trimText(textNode.nodeValue); + if (trimText(textNode.nodeValue).length > 0) { + textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform")); + textAlign = textAlign.replace(["-webkit-auto"],["auto"]); - if (text.length > 0){ + textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ? + textNode.nodeValue.split(/(\b| )/) + : textNode.nodeValue.split(""); - text_align = text_align.replace(["-webkit-auto"],["auto"]); + metrics = setTextVariables(ctx, el, textDecoration, color); - renderList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(text_align) && noLetterSpacing(letter_spacing)) ? - textNode.nodeValue.split(/(\b| )/) : - textNode.nodeValue.split(""); - - metrics = setTextVariables(ctx, el, text_decoration, color); - 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) { - textValue = renderList[c]; - if (doc.createRange){ - range = doc.createRange(); - - range.setStart(textNode, textOffset); - range.setEnd(textNode, textOffset + textValue.length); - } else { - // 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 (!oldTextNode || typeof oldTextNode.nodeValue !== "string" ){ - continue; - } - - newTextNode = (i < listLen-1) ? oldTextNode.splitText(renderList[c].length) : null; - - 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); + textList.forEach(function(text, index) { + var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1)); + if (bounds) { + drawText(text, bounds.left, bounds.bottom, ctx); + renderTextDecoration(ctx, textDecoration, bounds, metrics, color); } - - if (textValue !== null) { - drawText(textValue, bounds.left, bounds.bottom, ctx); - } - renderTextDecoration(text_decoration, bounds, metrics, color); - - textOffset += renderList[c].length; - - } - + }); } - } function listPosition (element, val) { @@ -368,7 +205,7 @@ _html2canvas.Parse = function (images, options) { count = 1, childs = el.parentNode.childNodes; - if ( el.parentNode ) { + if (el.parentNode) { while( childs[ ++i ] !== el ) { if ( childs[ i ].nodeType === 1 ) { count++; @@ -378,7 +215,6 @@ _html2canvas.Parse = function (images, options) { } else { return -1; } - } function listItemText(element, type) { @@ -412,6 +248,7 @@ _html2canvas.Parse = function (images, options) { function renderListItem(element, stack, elBounds) { var x, text, + ctx = stack.ctx, type = getCSS(element, "listStyleType"), listBounds; @@ -472,6 +309,27 @@ _html2canvas.Parse = function (images, options) { return parentZ; } + function renderImage(ctx, element, image, bounds, borders) { + + var paddingLeft = getCSSInt(element, 'paddingLeft'), + paddingTop = getCSSInt(element, 'paddingTop'), + paddingRight = getCSSInt(element, 'paddingRight'), + paddingBottom = getCSSInt(element, 'paddingBottom'); + + drawImage( + ctx, + image, + 0, //sx + 0, //sy + image.width, //sw + image.height, //sh + bounds.left + paddingLeft + borders[3].width, //dx + bounds.top + 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 + ); + } + function renderBorders(el, ctx, bounds, clip){ var x = bounds.left, y = bounds.top, @@ -608,35 +466,30 @@ _html2canvas.Parse = function (images, options) { } 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, + cssPropertyArray = ['lineHeight','textAlign','fontFamily','color','fontSize','paddingLeft','paddingTop','width','height','border','borderLeftWidth','borderTopWidth'], textValue, - textNode, - arrLen, - style; - - for (i = 0, arrLen = cssArr.length; i < arrLen; i+=1){ - style = cssArr[i]; + textNode; + cssPropertyArray.forEach(function(property) { try { - valueWrap.style[style] = getCSS(el, style); - } catch( e ) { + valueWrap.style[property] = getCSS(el, property); + } catch(e) { // Older IE has issues with "border" h2clog("html2canvas: Parse: Exception caught in renderFormValue: " + e.message); } - } + }); 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"); } @@ -654,461 +507,376 @@ _html2canvas.Parse = function (images, options) { body.removeChild(valueWrap); } - function renderImage (ctx) { - ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments,1)); + function drawImage (ctx) { + ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 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; - } + function renderBackgroundSlice (ctx, image, x, y, width, height, elx, ely){ + var sourceX = (elx - x > 0) ? elx-x :0, + sourceY= (ely - y > 0) ? ely-y : 0; - if (ely-y>0){ - sourceY = ely-y; - } - - renderImage( + drawImage( ctx, image, - sourceX, // source X - sourceY, // source Y - width-sourceX, // source Width - height-sourceY, // source Height - x+sourceX, // destination X - y+sourceY, // destination Y - width-sourceX, // destination width - height-sourceY // destination height + Math.floor(sourceX), // source X + Math.floor(sourceY), // source Y + Math.ceil(width-sourceX), // source Width + Math.ceil(height-sourceY), // source Height + Math.ceil(x+sourceX), // destination X + Math.ceil(y+sourceY), // destination Y + Math.ceil(width-sourceX), // destination width + Math.ceil(height-sourceY) // destination height ); } + function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) { + var bgy, + height, + add, + h; - function renderBackgroundRepeatY (ctx, image, bgp, x, y, w, h){ + backgroundPosition.top -= Math.ceil(backgroundPosition.top / image.height) * image.height; + + for(bgy = (bounds.top + backgroundPosition.top); bgy < (bounds.height + bounds.top); bgy = Math.floor(bgy+image.height) - add) { + h = Math.min(image.height,(bounds.height + bounds.top) - bgy); + + height = (Math.floor(bgy + image.height) > h + bgy) ? (h + bgy) - bgy : image.height; + + if (bgy < bounds.top){ + add = bounds.top - bgy; + bgy = bounds.top; + } else { + add = 0; + } + + renderBackgroundRepeatX(ctx, image, backgroundPosition, bounds.left, bgy, bounds.width, height); + + if (add > 0){ + backgroundPosition.top += add; + } + } + } + + function renderBackgroundNoRepeat(ctx, image, backgroundPosition, x, y, w, h) { + var bgdw = w - backgroundPosition.left, + bgdh = h - backgroundPosition.top, + bgsx = backgroundPosition.left, + bgsy = backgroundPosition.top, + bgdx = backgroundPosition.left + x, + bgdy = backgroundPosition.top + y; + + if (bgsx<0){ + bgsx = Math.abs(bgsx); + bgdx += bgsx; + bgdw = Math.min(w,image.width-bgsx); + } else { + bgdw = Math.min(bgdw,image.width); + bgsx = 0; + } + + if (bgsy < 0){ + bgsy = Math.abs(bgsy); + bgdy += bgsy; + bgdh = Math.min(h, image.height - bgsy); + } else { + bgdh = Math.min(bgdh, image.height); + bgsy = 0; + } + + if (bgdh > 0 && bgdw > 0){ + drawImage( + ctx, + image, + bgsx, + bgsy, + bgdw, + bgdh, + bgdx, + bgdy, + bgdw, + bgdh + ); + } + } + + function renderBackgroundRepeatY (ctx, image, backgroundPosition, x, y, w, h){ var height, width = Math.min(image.width, w), bgy; - bgp.top -= Math.ceil(bgp.top / image.height) * image.height; + backgroundPosition.top -= Math.ceil(backgroundPosition.top / image.height) * image.height; - for(bgy=(y + bgp.top);bgy < h + y;){ + for (bgy = y + backgroundPosition.top; bgy < h + y; bgy = Math.round(bgy + image.height)){ height = (Math.floor(bgy + image.height) > h + y) ? (h+y) - bgy : image.height; - renderBackgroundRepeat(ctx, image, x+bgp.left, bgy,width, height, x, y); - bgy = Math.floor(bgy + image.height); + renderBackgroundSlice(ctx, image, x + backgroundPosition.left, bgy,width, height, x, y); } } - function renderBackgroundRepeatX(ctx, image, bgp, x, y, w, h){ + function renderBackgroundRepeatX(ctx, image, backgroundPosition, x, y, w, h){ var height = Math.min(image.height, h), width, bgx; - bgp.left -= Math.ceil(bgp.left / image.width) * image.width; + backgroundPosition.left -= Math.ceil(backgroundPosition.left / image.width) * image.width; - for (bgx=(x + bgp.left); bgx < w + x;) { + for (bgx = x + backgroundPosition.left; bgx < w + x; bgx = Math.round(bgx + image.width)) { width = (Math.floor(bgx + image.width) > w + x) ? (w + x) - bgx : image.width; - renderBackgroundRepeat(ctx, image, bgx,(y + bgp.top), width, height, x, y); - bgx = Math.floor(bgx + image.width); + renderBackgroundSlice(ctx, image, bgx,(y + backgroundPosition.top), width, height, x, y); } } - function renderBackground(el,bounds,ctx){ + function renderBackgroundColor(ctx, backgroundBounds, bgcolor) { + renderRect( + ctx, + backgroundBounds.left, + backgroundBounds.top, + backgroundBounds.width, + backgroundBounds.height, + bgcolor + ); + } + function renderBackgroundRepeating(el, bounds, ctx, image) { + var backgroundPosition = _html2canvas.Util.BackgroundPosition(el, bounds, image), + backgroundRepeat = getCSS(el, "backgroundRepeat").split(",")[0]; + switch (backgroundRepeat) { + case "repeat-x": + renderBackgroundRepeatX(ctx, image, backgroundPosition, bounds.left, bounds.top, bounds.width, bounds.height); + break; + + case "repeat-y": + renderBackgroundRepeatY(ctx, image, backgroundPosition, bounds.left, bounds.top, bounds.width, bounds.height); + break; + + case "no-repeat": + renderBackgroundNoRepeat(ctx, image, backgroundPosition, bounds.left, bounds.top, bounds.width, bounds.height); + break; + + default: + renderBackgroundRepeat(ctx, image, backgroundPosition, bounds); + break; + } + } + + function renderBackgroundImage(element, bounds, ctx) { // TODO add support for multi background-images - var background_image = getCSS(el, "backgroundImage"), - background_repeat = getCSS(el, "backgroundRepeat").split(",")[0], - image, - bgp, - bgy, - bgw, - bgsx, - bgsy, - bgdx, - bgdy, - bgh, - h, - height, - add; + var backgroundImage = getCSS(element, "backgroundImage"), + image; - if ( !/data:image\/.*;base64,/i.test(background_image) && !/^(-webkit|-moz|linear-gradient|-o-)/.test(background_image) ) { - background_image = background_image.split(",")[0]; + if (!/data:image\/.*;base64,/i.test(backgroundImage) && !/^(-webkit|-moz|linear-gradient|-o-)/.test(backgroundImage)) { + backgroundImage = backgroundImage.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); + if (typeof backgroundImage !== "undefined" && /^(1|none)$/.test(backgroundImage) === false) { + image = loadImage(_html2canvas.Util.backgroundImage(backgroundImage)); // 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); - break; - - case "repeat-y": - renderBackgroundRepeatY(ctx, image, bgp, bounds.left, bounds.top, bounds.width, bounds.height); - break; - - case "no-repeat": - bgw = bounds.width - bgp.left; - bgh = bounds.height - bgp.top; - bgsx = bgp.left; - bgsy = bgp.top; - bgdx = bgp.left+bounds.left; - bgdy = bgp.top+bounds.top; - - if (bgsx<0){ - bgsx = Math.abs(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; - // bgh = bgh-bgsy; - bgh = Math.min(bounds.height,image.height-bgsy); - }else{ - bgh = Math.min(bgh,image.height); - bgsy = 0; - } - - if (bgh>0 && bgw > 0){ - renderImage( - ctx, - image, - bgsx, // source X : 0 - bgsy, // source Y : 1695 - bgw, // source Width : 18 - bgh, // source Height : 1677 - bgdx, // destination X :906 - bgdy, // destination Y : 1020 - 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);bgy h + bgy) ? (h+bgy)-bgy : image.height; - if (bgy0){ - bgp.top += add; - } - bgy = Math.floor(bgy+image.height)-add; - } - break; - - } - }else{ - h2clog("html2canvas: Error loading background:" + background_image); + if (image) { + renderBackgroundRepeating(element, bounds, ctx, image); + } else { + h2clog("html2canvas: Error loading background:" + backgroundImage); } } } + function setOpacity(ctx, element, parentStack) { + var opacity = getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1); + ctx.setVariable("globalAlpha", opacity); + return opacity; + } + function createStack(element, parentStack, bounds) { - function renderElement(el, parentStack){ - - 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"), - zindex, - opacity = getCSS(el, "opacity"), - stack, - stackLength, - borders, - ctx, - bgbounds, - imgSrc, - paddingLeft, - paddingTop, - paddingRight, - paddingBottom; - - if (!parentStack){ - docDim = docSize(); - parentStack = { - opacity: 1 - }; - }else{ - docDim = {}; - } - - zindex = setZ( getCSS( el, "zIndex"), parentStack.zIndex ); - - - + var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height), stack = { - ctx: h2cRenderContext( docDim.width || w , docDim.height || h ), - zIndex: zindex, - opacity: opacity * parentStack.opacity, - cssPosition: cssPosition + ctx: ctx, + zIndex: setZ(getCSS(element, "zIndex"), (parentStack) ? parentStack.zIndex : null), + opacity: setOpacity(ctx, element, parentStack), + cssPosition: getCSS(element, "position"), + borders: renderBorders(element, ctx, bounds, false), + clip: (parentStack && parentStack.clip) ? _html2canvas.Util.Extend( {}, parentStack.clip ) : null }; - - // TODO correct overflow for absolute content residing under a static position - - if (parentStack.clip){ - stack.clip = _html2canvas.Util.Extend( {}, parentStack.clip ); - } - - if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(el, "overflow")) === true && /(BODY)/i.test(el.nodeName) === false){ + if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){ stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds; } - stackLength = zindex.children.push(stack); + stack.zIndex.children.push(stack); - ctx = zindex.children[stackLength-1].ctx; + return stack; + } - ctx.setVariable("globalAlpha", stack.opacity); - - - borders = renderBorders(el, ctx, bounds, false); - stack.borders = borders; - - - - if (ignoreElementsRegExp.test(el.nodeName) && options.iframeDefault !== "transparent"){ - bgcolor = (options.iframeDefault === "default") ? "#efefef" : options.iframeDefault; - } - - - bgbounds = { - left: x + borders[3].width, - top: y + borders[0].width, - width: w - (borders[1].width + borders[3].width), - height: h - (borders[0].width + borders[2].width) + function getBackgroundBounds(borders, bounds, clip) { + var backgroundBounds = { + left: bounds.left + borders[3].width, + top: bounds.top + borders[0].width, + width: bounds.width - (borders[1].width + borders[3].width), + height: bounds.height - (borders[0].width + borders[2].width) }; - - if (stack.clip){ - bgbounds = clipBounds(bgbounds, stack.clip); + if (clip) { + backgroundBounds = clipBounds(backgroundBounds, clip); } + return backgroundBounds; + } - if (bgbounds.height > 0 && bgbounds.width > 0){ - renderRect( - ctx, - bgbounds.left, - bgbounds.top, - bgbounds.width, - bgbounds.height, - bgcolor - ); + function renderElement(element, parentStack){ + var bounds = _html2canvas.Util.Bounds(element), + image, + bgcolor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor"), + stack = createStack(element, parentStack, bounds), + borders = stack.borders, + ctx = stack.ctx, + backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip); - renderBackground(el, bgbounds, ctx); + if (backgroundBounds.height > 0 && backgroundBounds.width > 0){ + renderBackgroundColor(ctx, backgroundBounds, bgcolor); + renderBackgroundImage(element, backgroundBounds, ctx); } - switch(el.nodeName){ + switch(element.nodeName){ case "IMG": - imgSrc = el.getAttribute('src'); - image = loadImage(imgSrc); - if (image){ - - paddingLeft = getCSSInt(el, 'paddingLeft'); - paddingTop = getCSSInt(el, 'paddingTop'); - paddingRight = getCSSInt(el, 'paddingRight'); - paddingBottom = getCSSInt(el, 'paddingBottom'); - - - renderImage( - ctx, - image, - 0, //sx - 0, //sy - image.width, //sw - image.height, //sh - 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 - ); - - }else{ - h2clog("html2canvas: Error loading :" + imgSrc); + if ((image = loadImage(element.getAttribute('src')))) { + renderImage(ctx, element, image, bounds, borders); + } else { + h2clog("html2canvas: Error loading :" + element.getAttribute('src')); } break; case "INPUT": // 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); + if (/^(text|url|email|submit|button|reset)$/.test(element.type) && element.value.length > 0){ + renderFormValue(element, bounds, stack); } break; case "TEXTAREA": - if (el.value.length > 0){ - renderFormValue(el, bounds, stack); + if (element.value.length > 0){ + renderFormValue(element, bounds, stack); } break; case "SELECT": - if (el.options.length > 0){ - renderFormValue(el, bounds, stack); + if (element.options.length > 0){ + renderFormValue(element, bounds, stack); } break; case "LI": - renderListItem(el, stack, bgbounds); + renderListItem(element, stack, backgroundBounds); break; case "CANVAS": - paddingLeft = getCSSInt(el, 'paddingLeft'); - paddingTop = getCSSInt(el, 'paddingTop'); - paddingRight = getCSSInt(el, 'paddingRight'); - paddingBottom = getCSSInt(el, 'paddingBottom'); - renderImage( - ctx, - el, - 0, - 0, - el.width, - el.height, - x + paddingLeft + borders[3].width, - y + paddingTop + borders[0].width, - bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), - bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) - ); + renderImage(ctx, element, element, bounds, borders); break; } - return zindex.children[stackLength - 1]; + return stack; } - + function isElementVisible(element) { + return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore")); + } function parseElement (el, stack) { - // skip hidden elements and their children - if (getCSS(el, 'display') !== "none" && getCSS(el, 'visibility') !== "hidden" && !el.hasAttribute("data-html2canvas-ignore")) { + if (isElementVisible(el)) { stack = renderElement(el, stack) || stack; - ctx = stack.ctx; - if (!ignoreElementsRegExp.test(el.nodeName)) { - var elementChildren = _html2canvas.Util.Children(el), - i, - node, - childrenLen; - - for (i = 0, childrenLen = elementChildren.length; i < childrenLen; i+=1) { - node = elementChildren[i]; + _html2canvas.Util.Children(el).forEach(function(node) { if (node.nodeType === 1) { parseElement(node, stack); } else if (node.nodeType === 3) { renderText(el, node, stack); } - } - + }); } } } - stack = renderElement(element, null); + function svgDOMRender(body, stack) { + var img = new Image(), + docWidth = documentWidth(), + docHeight = documentHeight(), + html = ""; - /* - SVG powered HTML rendering, non-tainted canvas available from FF 11+ onwards - */ + function parseDOM(el) { + var children = _html2canvas.Util.Children( el ), + len = children.length, + attr, + a, + alen, + elm, + i; + for ( i = 0; i < len; i+=1 ) { + 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 ( support.svgRendering ) { - (function( body ){ - var img = new Image(), - size = docSize(), - html = ""; + html += "<" + elm.nodeName.toLowerCase(); - function parseDOM( el ) { - var children = _html2canvas.Util.Children( el ), - len = children.length, - attr, - a, - alen, - elm, - i; - for ( i = 0; i < len; i+=1 ) { - 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()) ) { - - html += "<" + elm.nodeName.toLowerCase(); - - // add attributes - if ( elm.hasAttributes() ) { - attr = elm.attributes; - alen = attr.length; - for ( a = 0; a < alen; a+=1 ) { - html += " " + attr[ a ].name + '="' + attr[ a ].value + '"'; - } + // add attributes + if ( elm.hasAttributes() ) { + attr = elm.attributes; + alen = attr.length; + for ( a = 0; a < alen; a+=1 ) { + html += " " + attr[ a ].name + '="' + attr[ a ].value + '"'; } - - - html += '>'; - - parseDOM( elm ); - - - html += ""; } - } + + html += '>'; + + parseDOM( elm ); + + + html += ""; + } } } - parseDOM(body); - img.src = [ - "data:image/svg+xml,", - "", - "", - "", - html.replace(/\#/g,"%23"), - "", - "", - "" - ].join(""); + } - img.onload = function() { - stack.svgRender = img; - }; + parseDOM(body); + img.src = [ + "data:image/svg+xml,", + "", + "", + "", + html.replace(/\#/g,"%23"), + "", + "", + "" + ].join(""); - })(document.documentElement); + img.onload = function() { + stack.svgRender = img; + }; } + function init() { + var stack = renderElement(element, null); - // parse every child element - for (i = 0, children = element.children, childrenLen = children.length; i < childrenLen; i+=1){ - parseElement(children[i], stack); + if (support.svgRendering) { + svgDOMRender(document.documentElement, stack); + } + + Array.prototype.slice.call(element.children, 0).forEach(function(childElement) { + parseElement(childElement, stack); + }); + + stack.backgroundColor = getCSS(document.documentElement, "backgroundColor"); + + return stack; } - stack.backgroundColor = getCSS(document.documentElement, "backgroundColor"); - - return stack; - + return init(); }; function h2czContext(zindex) { diff --git a/src/Parse.js.orig b/src/Parse.js.orig new file mode 100644 index 0000000..442c777 --- /dev/null +++ b/src/Parse.js.orig @@ -0,0 +1,947 @@ +_html2canvas.Parse = function (images, options) { + window.scroll(0,0); + + var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default + numDraws = 0, + doc = element.ownerDocument, + support = _html2canvas.Util.Support(options, doc), + ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"), + body = doc.body, + getCSS = _html2canvas.Util.getCSS; + + images = images || {}; + + function documentWidth () { + return Math.max( + Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth), + Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth), + Math.max(doc.body.clientWidth, doc.documentElement.clientWidth) + ); + } + + function documentHeight () { + return Math.max( + Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight), + Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight), + Math.max(doc.body.clientHeight, doc.documentElement.clientHeight) + ); + } + + 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 + } + + function renderRect (ctx, x, y, w, h, bgcolor) { + if (bgcolor !== "transparent"){ + ctx.setVariable("fillStyle", bgcolor); + ctx.fillRect(x, y, w, h); + numDraws+=1; + } + } + + function textTransform (text, transform) { + switch(transform){ + case "lowercase": + 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 noLetterSpacing(letter_spacing) { + return (/^(normal|none|0px)$/.test(letter_spacing)); + } + + function trimText (text) { + return text.replace(/^\s*/g, "").replace(/\s*$/g, ""); + } + + function drawText(currentText, x, y, ctx){ + if (currentText !== null && trimText(currentText).length > 0) { + ctx.fillText(currentText, x, y); + numDraws+=1; + } + } + + function setTextVariables(ctx, el, text_decoration, color) { + var align = false, + bold = getCSS(el, "fontWeight"), + family = getCSS(el, "fontFamily"), + size = getCSS(el, "fontSize"); + + switch(parseInt(bold, 10)){ + case 401: + bold = "bold"; + break; + case 400: + bold = "normal"; + break; + } + + ctx.setVariable("fillStyle", color); + ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" ")); + ctx.setVariable("textAlign", (align) ? "right" : "left"); + + if (text_decoration !== "none"){ + return _html2canvas.Util.Font(family, size, doc); + } + } + + function renderTextDecoration(ctx, text_decoration, bounds, metrics, color) { + switch(text_decoration) { + 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 + renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color); + break; + case "overline": + renderRect(ctx, bounds.left, Math.round(bounds.top), bounds.width, 1, color); + break; + case "line-through": + // 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; + } + } + +<<<<<<< HEAD + function renderText(el, textNode, stack) { + var ctx = stack.ctx, + color = getCSS(el, "color"), + text_decoration = getCSS(el, "textDecoration"), + text_align = getCSS(el, "textAlign"), + letter_spacing = getCSS(el, "letterSpacing"), + bounds, + text, + metrics, + renderList, + listLen, + newTextNode, + textValue, + textOffset = 0, + oldTextNode, + c, + range, + parent, + wrapElement, + backupText; + + textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform")); + text = trimText(textNode.nodeValue); + + if (text.length > 0){ + + text_align = text_align.replace(["-webkit-auto"],["auto"]); + + renderList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(text_align) && noLetterSpacing(letter_spacing)) ? + textNode.nodeValue.split(/(\b| )/) : + textNode.nodeValue.split(""); + + metrics = setTextVariables(ctx, el, text_decoration, color); + 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) { + textValue = renderList[c]; + if (doc.createRange){ + range = doc.createRange(); + + range.setStart(textNode, textOffset); + range.setEnd(textNode, textOffset + textValue.length); + } else { + // TODO add IE support + range = body.createTextRange(); + } + + if (range.getBoundingClientRect()) { + bounds = range.getBoundingClientRect(); + } else { + bounds = {}; + } +======= + function getTextBounds(state, text, textDecoration, isLast) { + var bounds; + if (support.rangeBounds) { + if (textDecoration !== "none" || trimText(text).length !== 0) { + bounds = textRangeBounds(text, state.node, state.textOffset); + } + state.textOffset += text.length; + } else if (state.node && typeof state.node.nodeValue === "string" ){ + var newTextNode = (isLast) ? state.node.splitText(text.length) : null; + bounds = textWrapperBounds(state.node); + state.node = newTextNode; + } + return bounds; + } +>>>>>>> niklasvh/develop + + function textRangeBounds(text, textNode, textOffset) { + var range = doc.createRange(); + range.setStart(textNode, textOffset); + range.setEnd(textNode, textOffset + text.length); + return range.getBoundingClientRect(); + } + + function textWrapperBounds(oldTextNode) { + var parent = oldTextNode.parentNode, + wrapElement = doc.createElement('wrapper'), + backupText = oldTextNode.cloneNode(true); + + wrapElement.appendChild(oldTextNode.cloneNode(true)); + parent.replaceChild(wrapElement, oldTextNode); + + var bounds = _html2canvas.Util.Bounds(wrapElement); + parent.replaceChild(backupText, wrapElement); + return bounds; + } + + function renderText(el, textNode, stack) { + var ctx = stack.ctx, + color = getCSS(el, "color"), + textDecoration = getCSS(el, "textDecoration"), + textAlign = getCSS(el, "textAlign"), + metrics, + textList, + state = { + node: textNode, + textOffset: 0 + }; + + if (trimText(textNode.nodeValue).length > 0) { + textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform")); + textAlign = textAlign.replace(["-webkit-auto"],["auto"]); + + textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ? + textNode.nodeValue.split(/(\b| )/) + : textNode.nodeValue.split(""); + + metrics = setTextVariables(ctx, el, textDecoration, color); + + textList.forEach(function(text, index) { + var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1)); + if (bounds) { + drawText(text, bounds.left, bounds.bottom, ctx); + renderTextDecoration(ctx, textDecoration, bounds, metrics, color); + } + }); + } + } + + function listPosition (element, val) { + var boundElement = doc.createElement( "boundelement" ), + originalType, + bounds; + + boundElement.style.display = "inline"; + + originalType = 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 = originalType; + return bounds; + } + + function elementIndex( el ) { + var i = -1, + count = 1, + childs = el.parentNode.childNodes; + + if (el.parentNode) { + while( childs[ ++i ] !== el ) { + if ( childs[ i ].nodeType === 1 ) { + count++; + } + } + return count; + } else { + return -1; + } + } + + function listItemText(element, type) { + var currentIndex = elementIndex(element), + text; + switch(type){ + case "decimal": + text = currentIndex; + break; + case "decimal-leading-zero": + text = (currentIndex.toString().length === 1) ? currentIndex = "0" + currentIndex.toString() : currentIndex.toString(); + break; + case "upper-roman": + text = _html2canvas.Generate.ListRoman( currentIndex ); + break; + case "lower-roman": + text = _html2canvas.Generate.ListRoman( currentIndex ).toLowerCase(); + break; + case "lower-alpha": + text = _html2canvas.Generate.ListAlpha( currentIndex ).toLowerCase(); + break; + case "upper-alpha": + text = _html2canvas.Generate.ListAlpha( currentIndex ); + break; + } + + text += ". "; + return text; + } + + function renderListItem(element, stack, elBounds) { + var x, + text, + ctx = stack.ctx, + type = getCSS(element, "listStyleType"), + listBounds; + + if (/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)) { + text = listItemText(element, type); + listBounds = listPosition(element, text); + setTextVariables(ctx, element, "none", getCSS(element, "color")); + + if (getCSS(element, "listStylePosition") === "inside") { + ctx.setVariable("textAlign", "left"); + x = elBounds.left; + } else { + return; + } + + drawText(text, x, listBounds.bottom, ctx); + } + } + + function loadImage (src){ + var img = images[src]; + if (img && img.succeeded === true) { + return img.img; + } else { + 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; + if (!parentZ){ + newContext = h2czContext(0); + return newContext; + } + + if (zIndex !== "auto"){ + newContext = h2czContext(zIndex); + parentZ.children.push(newContext); + return newContext; + + } + + return parentZ; + } + + function renderImage(ctx, element, image, bounds, borders) { + + var paddingLeft = getCSSInt(element, 'paddingLeft'), + paddingTop = getCSSInt(element, 'paddingTop'), + paddingRight = getCSSInt(element, 'paddingRight'), + paddingBottom = getCSSInt(element, 'paddingBottom'); + + drawImage( + ctx, + image, + 0, //sx + 0, //sy + image.width, //sw + image.height, //sh + bounds.left + paddingLeft + borders[3].width, //dx + bounds.top + 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 + ); + } + + function renderBorders(el, ctx, bounds, clip){ + var x = bounds.left, + y = bounds.top, + w = bounds.width, + h = bounds.height, + borderSide, + borderData, + bx, + by, + bw, + bh, + i, + borderArgs, + borderBounds, + borders = (function(el){ + 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)), + // http://www.w3.org/TR/css3-background/#the-border-radius + borderRadius = (function( el ) { + var borders = [], + sides = ["TopLeft","TopRight","BottomRight","BottomLeft"], + s; + + for (s = 0; s < 4; s+=1){ + borders.push( getCSS(el, 'border' + sides[s] + 'Radius') ); + } + + return borders; + })( el ); + + + + for ( borderSide = 0; borderSide < 4; borderSide+=1 ) { + borderData = borders[ borderSide ]; + borderArgs = []; + if (borderData.width>0){ + bx = x; + by = y; + bw = w; + bh = h - (borders[2].width); + + switch(borderSide){ + case 0: + // top border + bh = borders[0].width; + + i = 0; + borderArgs[ i++ ] = [ "line", bx, by ]; // top left + borderArgs[ i++ ] = [ "line", bx + bw, by ]; // top right + borderArgs[ i++ ] = [ "line", bx + bw - borders[ 1 ].width, by + bh ]; // bottom right + borderArgs[ i++ ] = [ "line", bx + borders[ 3 ].width, by + bh ]; // bottom left + + break; + case 1: + // right border + bx = x + w - (borders[1].width); + bw = borders[1].width; + + i = 0; + borderArgs[ i++ ] = [ "line", bx, by + borders[ 0 ].width]; // top left + borderArgs[ i++ ] = [ "line", bx + bw, by ]; // top right + borderArgs[ i++ ] = [ "line", bx + bw, by + bh + borders[ 2 ].width ]; // bottom right + borderArgs[ i++ ] = [ "line", bx, by + bh ]; // bottom left + + break; + case 2: + // bottom border + by = (by + h) - (borders[2].width); + bh = borders[2].width; + + i = 0; + borderArgs[ i++ ] = [ "line", bx + borders[ 3 ].width, by ]; // top left + borderArgs[ i++ ] = [ "line", bx + bw - borders[ 2 ].width, by ]; // top right + borderArgs[ i++ ] = [ "line", bx + bw, by + bh ]; // bottom right + borderArgs[ i++ ] = [ "line", bx, by + bh ]; // bottom left + + break; + case 3: + // left border + bw = borders[3].width; + + i = 0; + borderArgs[ i++ ] = [ "line", bx, by ]; // top left + borderArgs[ i++ ] = [ "line", bx + bw, by + borders[ 0 ].width ]; // top right + borderArgs[ i++ ] = [ "line", bx + bw, by + bh ]; // bottom right + borderArgs[ i++ ] = [ "line", bx, by + bh + borders[ 2 ].width ]; // bottom left + + break; + } + + borderBounds = { + left:bx, + top:by, + width: bw, + height:bh + }; + + if (clip){ + borderBounds = clipBounds(borderBounds, clip); + } + + + if ( borderBounds.width > 0 && borderBounds.height > 0 ) { + + if ( borderData.color !== "transparent" ){ + ctx.setVariable( "fillStyle", borderData.color ); + + var shape = ctx.drawShape(), + numBorderArgs = borderArgs.length; + + for ( i = 0; i < numBorderArgs; i++ ) { + shape[( i === 0) ? "moveTo" : borderArgs[ i ][ 0 ] + "To" ].apply( null, borderArgs[ i ].slice(1) ); + } + + numDraws+=1; + } + + } + + + } + } + + return borders; + } + + + function renderFormValue (el, bounds, stack){ + + var valueWrap = doc.createElement('valuewrap'), + cssPropertyArray = ['lineHeight','textAlign','fontFamily','color','fontSize','paddingLeft','paddingTop','width','height','border','borderLeftWidth','borderTopWidth'], + textValue, + textNode; + + cssPropertyArray.forEach(function(property) { + try { + valueWrap.style[property] = getCSS(el, property); + } catch(e) { + // Older IE has issues with "border" + h2clog("html2canvas: Parse: Exception caught in renderFormValue: " + e.message); + } + }); + + 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"; + + textValue = (el.nodeName === "SELECT") ? el.options[el.selectedIndex].text : el.value; + textNode = doc.createTextNode(textValue); + + valueWrap.appendChild(textNode); + body.appendChild(valueWrap); + + renderText(el, textNode, stack); + body.removeChild(valueWrap); + } + + function drawImage (ctx) { + ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 1)); + numDraws+=1; + } + + function renderBackgroundSlice (ctx, image, x, y, width, height, elx, ely){ + var sourceX = (elx - x > 0) ? elx-x :0, + sourceY= (ely - y > 0) ? ely-y : 0; + + drawImage( + ctx, + image, + Math.floor(sourceX), // source X + Math.floor(sourceY), // source Y + Math.ceil(width-sourceX), // source Width + Math.ceil(height-sourceY), // source Height + Math.ceil(x+sourceX), // destination X + Math.ceil(y+sourceY), // destination Y + Math.ceil(width-sourceX), // destination width + Math.ceil(height-sourceY) // destination height + ); + } + + function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) { + var bgy, + height, + add, + h; + + backgroundPosition.top -= Math.ceil(backgroundPosition.top / image.height) * image.height; + + for(bgy = (bounds.top + backgroundPosition.top); bgy < (bounds.height + bounds.top); bgy = Math.floor(bgy+image.height) - add) { + h = Math.min(image.height,(bounds.height + bounds.top) - bgy); + + height = (Math.floor(bgy + image.height) > h + bgy) ? (h + bgy) - bgy : image.height; + + if (bgy < bounds.top){ + add = bounds.top - bgy; + bgy = bounds.top; + } else { + add = 0; + } + + renderBackgroundRepeatX(ctx, image, backgroundPosition, bounds.left, bgy, bounds.width, height); + + if (add > 0){ + backgroundPosition.top += add; + } + } + } + + function renderBackgroundNoRepeat(ctx, image, backgroundPosition, x, y, w, h) { + var bgdw = w - backgroundPosition.left, + bgdh = h - backgroundPosition.top, + bgsx = backgroundPosition.left, + bgsy = backgroundPosition.top, + bgdx = backgroundPosition.left + x, + bgdy = backgroundPosition.top + y; + + if (bgsx<0){ + bgsx = Math.abs(bgsx); + bgdx += bgsx; + bgdw = Math.min(w,image.width-bgsx); + } else { + bgdw = Math.min(bgdw,image.width); + bgsx = 0; + } + + if (bgsy < 0){ + bgsy = Math.abs(bgsy); + bgdy += bgsy; + bgdh = Math.min(h, image.height - bgsy); + } else { + bgdh = Math.min(bgdh, image.height); + bgsy = 0; + } + + if (bgdh > 0 && bgdw > 0){ + drawImage( + ctx, + image, + bgsx, + bgsy, + bgdw, + bgdh, + bgdx, + bgdy, + bgdw, + bgdh + ); + } + } + + function renderBackgroundRepeatY (ctx, image, backgroundPosition, x, y, w, h){ + var height, + width = Math.min(image.width, w), + bgy; + + backgroundPosition.top -= Math.ceil(backgroundPosition.top / image.height) * image.height; + + for (bgy = y + backgroundPosition.top; bgy < h + y; bgy = Math.round(bgy + image.height)){ + height = (Math.floor(bgy + image.height) > h + y) ? (h+y) - bgy : image.height; + renderBackgroundSlice(ctx, image, x + backgroundPosition.left, bgy,width, height, x, y); + } + } + + function renderBackgroundRepeatX(ctx, image, backgroundPosition, x, y, w, h){ + var height = Math.min(image.height, h), + width, + bgx; + + backgroundPosition.left -= Math.ceil(backgroundPosition.left / image.width) * image.width; + + for (bgx = x + backgroundPosition.left; bgx < w + x; bgx = Math.round(bgx + image.width)) { + width = (Math.floor(bgx + image.width) > w + x) ? (w + x) - bgx : image.width; + renderBackgroundSlice(ctx, image, bgx,(y + backgroundPosition.top), width, height, x, y); + } + } + + function renderBackgroundColor(ctx, backgroundBounds, bgcolor) { + renderRect( + ctx, + backgroundBounds.left, + backgroundBounds.top, + backgroundBounds.width, + backgroundBounds.height, + bgcolor + ); + } + + function renderBackgroundRepeating(el, bounds, ctx, image) { + var backgroundPosition = _html2canvas.Util.BackgroundPosition(el, bounds, image), + backgroundRepeat = getCSS(el, "backgroundRepeat").split(",")[0]; + switch (backgroundRepeat) { + case "repeat-x": + renderBackgroundRepeatX(ctx, image, backgroundPosition, bounds.left, bounds.top, bounds.width, bounds.height); + break; + + case "repeat-y": + renderBackgroundRepeatY(ctx, image, backgroundPosition, bounds.left, bounds.top, bounds.width, bounds.height); + break; + + case "no-repeat": + renderBackgroundNoRepeat(ctx, image, backgroundPosition, bounds.left, bounds.top, bounds.width, bounds.height); + break; + + default: + renderBackgroundRepeat(ctx, image, backgroundPosition, bounds); + break; + } + } + + function renderBackgroundImage(element, bounds, ctx) { + // TODO add support for multi background-images + var backgroundImage = getCSS(element, "backgroundImage"), + image; + + if (!/data:image\/.*;base64,/i.test(backgroundImage) && !/^(-webkit|-moz|linear-gradient|-o-)/.test(backgroundImage)) { + backgroundImage = backgroundImage.split(",")[0]; + } + + if (typeof backgroundImage !== "undefined" && /^(1|none)$/.test(backgroundImage) === false) { + image = loadImage(_html2canvas.Util.backgroundImage(backgroundImage)); + + // TODO add support for background-origin + if (image) { + renderBackgroundRepeating(element, bounds, ctx, image); + } else { + h2clog("html2canvas: Error loading background:" + backgroundImage); + } + + } + } + + function setOpacity(ctx, element, parentStack) { + var opacity = getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1); + ctx.setVariable("globalAlpha", opacity); + return opacity; + } + + function createStack(element, parentStack, bounds) { + + var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height), + stack = { + ctx: ctx, + zIndex: setZ(getCSS(element, "zIndex"), (parentStack) ? parentStack.zIndex : null), + opacity: setOpacity(ctx, element, parentStack), + cssPosition: getCSS(element, "position"), + borders: renderBorders(element, ctx, bounds, false), + clip: (parentStack && parentStack.clip) ? _html2canvas.Util.Extend( {}, parentStack.clip ) : null + }; + + // TODO correct overflow for absolute content residing under a static position + if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){ + stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds; + } + + stack.zIndex.children.push(stack); + + return stack; + } + + function getBackgroundBounds(borders, bounds, clip) { + var backgroundBounds = { + left: bounds.left + borders[3].width, + top: bounds.top + borders[0].width, + width: bounds.width - (borders[1].width + borders[3].width), + height: bounds.height - (borders[0].width + borders[2].width) + }; + + if (clip) { + backgroundBounds = clipBounds(backgroundBounds, clip); + } + + return backgroundBounds; + } + + function renderElement(element, parentStack){ + var bounds = _html2canvas.Util.Bounds(element), + image, + bgcolor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor"), + stack = createStack(element, parentStack, bounds), + borders = stack.borders, + ctx = stack.ctx, + backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip); + + if (backgroundBounds.height > 0 && backgroundBounds.width > 0){ + renderBackgroundColor(ctx, backgroundBounds, bgcolor); + renderBackgroundImage(element, backgroundBounds, ctx); + } + + switch(element.nodeName){ + case "IMG": + if ((image = loadImage(element.getAttribute('src')))) { + renderImage(ctx, element, image, bounds, borders); + } else { + h2clog("html2canvas: Error loading :" + element.getAttribute('src')); + } + break; + case "INPUT": + // 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(element.type) && element.value.length > 0){ + renderFormValue(element, bounds, stack); + } + break; + case "TEXTAREA": + if (element.value.length > 0){ + renderFormValue(element, bounds, stack); + } + break; + case "SELECT": + if (element.options.length > 0){ + renderFormValue(element, bounds, stack); + } + break; + case "LI": + renderListItem(element, stack, backgroundBounds); + break; + case "CANVAS": + renderImage(ctx, element, element, bounds, borders); + break; + } + + return stack; + } + + function isElementVisible(element) { + return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore")); + } + + function parseElement (el, stack) { + + if (isElementVisible(el)) { + stack = renderElement(el, stack) || stack; + if (!ignoreElementsRegExp.test(el.nodeName)) { + _html2canvas.Util.Children(el).forEach(function(node) { + if (node.nodeType === 1) { + parseElement(node, stack); + } else if (node.nodeType === 3) { + renderText(el, node, stack); + } + }); + } + } + } + + function svgDOMRender(body, stack) { + var img = new Image(), + docWidth = documentWidth(), + docHeight = documentHeight(), + html = ""; + + function parseDOM(el) { + var children = _html2canvas.Util.Children( el ), + len = children.length, + attr, + a, + alen, + elm, + i; + for ( i = 0; i < len; i+=1 ) { + 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()) ) { + + html += "<" + elm.nodeName.toLowerCase(); + + // add attributes + if ( elm.hasAttributes() ) { + attr = elm.attributes; + alen = attr.length; + for ( a = 0; a < alen; a+=1 ) { + html += " " + attr[ a ].name + '="' + attr[ a ].value + '"'; + } + } + + + html += '>'; + + parseDOM( elm ); + + + html += ""; + } + } + + } + + } + + parseDOM(body); + img.src = [ + "data:image/svg+xml,", + "", + "", + "", + html.replace(/\#/g,"%23"), + "", + "", + "" + ].join(""); + + img.onload = function() { + stack.svgRender = img; + }; + + } + + function init() { + var stack = renderElement(element, null); + + if (support.svgRendering) { + svgDOMRender(document.documentElement, stack); + } + + Array.prototype.slice.call(element.children, 0).forEach(function(childElement) { + parseElement(childElement, stack); + }); + + stack.backgroundColor = getCSS(document.documentElement, "backgroundColor"); + + return stack; + } + + return init(); +}; + +function h2czContext(zindex) { + return { + zindex: zindex, + children: [] + }; +} \ No newline at end of file diff --git a/src/Renderer.js b/src/Renderer.js index 1703cc8..edf4d90 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -1,57 +1,58 @@ _html2canvas.Renderer = function(parseQueue, options){ - var queue = []; - function sortZ(zStack){ - var subStacks = [], - stackValues = [], - zStackChildren = zStack.children, - s, - i, - stackLen, - zValue, - zLen, - stackChild, - b, - subStackLen; + function createRenderQueue(parseQueue) { + var queue = []; + var sortZ = function(zStack){ + var subStacks = [], + stackValues = []; - 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{ - 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; - + zStack.children.forEach(function(stackChild) { + if (stackChild.children && stackChild.children.length > 0){ + subStacks.push(stackChild); + stackValues.push(stackChild.zindex); + } else { + queue.push(stackChild); } - } + }); + + stackValues.sort(function(a, b) { + return a - b; + }); + + stackValues.forEach(function(zValue) { + var index; + + subStacks.some(function(stack, i){ + index = i; + return (stack.zindex === zValue); + }); + sortZ(subStacks.splice(index, 1)[0]); + + }); + }; + + sortZ(parseQueue.zIndex); + + return queue; + } + + function getRenderer(rendererName) { + var renderer; + + if (typeof options.renderer === "string" && _html2canvas.Renderer[rendererName] !== undefined) { + renderer = _html2canvas.Renderer[rendererName](options); + } else if (typeof rendererName === "function") { + renderer = rendererName(options); + } else { + throw new Error("Unknown renderer"); } + if ( typeof renderer._create !== "function" ) { + throw new Error("Invalid renderer defined"); + } + return renderer; } - - sortZ(parseQueue.zIndex); - if ( typeof options._renderer._create !== "function" ) { - throw new Error("Invalid renderer defined"); - } - return options._renderer._create( parseQueue, options, document, queue, _html2canvas ); - + return getRenderer(options.renderer)._create(parseQueue, options, document, createRenderQueue(parseQueue), _html2canvas); }; diff --git a/src/Support.js b/src/Support.js new file mode 100644 index 0000000..c2db49d --- /dev/null +++ b/src/Support.js @@ -0,0 +1,63 @@ +_html2canvas.Util.Support = function (options, doc) { + + function supportSVGRendering() { + var img = new Image(), + canvas = doc.createElement("canvas"), + ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d"); + if (ctx === false) { + return false; + } + canvas.width = canvas.height = 10; + img.src = [ + "data:image/svg+xml,", + "", + "", + "
", + "sup", + "
", + "
", + "
" + ].join(""); + try { + ctx.drawImage(img, 0, 0); + canvas.toDataURL(); + } catch(e) { + return false; + } + h2clog('html2canvas: Parse: SVG powered rendering available'); + return true; + } + + // Test whether we can use ranges to measure bounding boxes + // Opera doesn't provide valid bounds.height/bottom even though it supports the method. + + function supportRangeBounds() { + var r, testElement, rangeBounds, rangeHeight, support = false; + + if (doc.createRange) { + r = doc.createRange(); + if (r.getBoundingClientRect) { + testElement = doc.createElement('boundtest'); + testElement.style.height = "123px"; + testElement.style.display = "block"; + doc.body.appendChild(testElement); + + r.selectNode(testElement); + rangeBounds = r.getBoundingClientRect(); + rangeHeight = rangeBounds.height; + + if (rangeHeight === 123) { + support = true; + } + doc.body.removeChild(testElement); + } + } + + return support; + } + + return { + rangeBounds: supportRangeBounds(), + svgRendering: options.svgRendering && supportSVGRendering() + }; +}; \ No newline at end of file diff --git a/src/Util.js b/src/Util.js index 49f9fbb..f8d59ec 100644 --- a/src/Util.js +++ b/src/Util.js @@ -1,5 +1,4 @@ -html2canvas = window.html2canvas = function( elements, opts ) { - +window.html2canvas = function(elements, opts) { var queue, canvas, options = { @@ -8,14 +7,13 @@ html2canvas = window.html2canvas = function( elements, opts ) { elements: elements, // preload options - proxy: "http://html2canvas.appspot.com/", + proxy: "", 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", ignoreElements: "IFRAME|OBJECT|PARAM", useOverflow: true, letterRendering: false, @@ -27,18 +25,10 @@ html2canvas = window.html2canvas = function( elements, opts ) { height: null, taintTest: true, // do a taint test with all images before applying to 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") { - options._renderer = options.renderer( options ); - } else { - throw("Unknown renderer"); - } - _html2canvas.logging = options.logging; options.complete = function( images ) { @@ -83,7 +73,7 @@ html2canvas = window.html2canvas = function( elements, opts ) { }; }; -html2canvas.log = h2clog; // for renderers -html2canvas.Renderer = { +window.html2canvas.log = h2clog; // for renderers +window.html2canvas.Renderer = { Canvas: undefined // We are assuming this will be used }; \ No newline at end of file diff --git a/src/Util.js.orig b/src/Util.js.orig new file mode 100644 index 0000000..b0d9fa1 --- /dev/null +++ b/src/Util.js.orig @@ -0,0 +1,84 @@ +<<<<<<< HEAD +html2canvas = window.html2canvas = function( elements, opts ) { + +======= +window.html2canvas = function(elements, opts) { +>>>>>>> niklasvh/develop + var queue, + canvas, + options = { + // general + logging: false, + elements: elements, + + // preload options + proxy: "", + 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+) + ignoreElements: "IFRAME|OBJECT|PARAM", + useOverflow: true, + letterRendering: false, + + // render options + + flashcanvas: undefined, // path to flashcanvas + width: null, + height: null, + taintTest: true, // do a taint test with all images before applying to canvas + renderer: "Canvas" + }; + + options = _html2canvas.Util.Extend(opts, options); + + _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 ); + + return { + render: function( queue, opts ) { + return _html2canvas.Renderer( queue, _html2canvas.Util.Extend(opts, options) ); + }, + parse: function( images, opts ) { + return _html2canvas.Parse( images, _html2canvas.Util.Extend(opts, options) ); + }, + preload: function( opts ) { + return _html2canvas.Preload( _html2canvas.Util.Extend(opts, options) ); + }, + log: h2clog + }; +}; + +window.html2canvas.log = h2clog; // for renderers +window.html2canvas.Renderer = { + Canvas: undefined // We are assuming this will be used +}; \ No newline at end of file diff --git a/tests/assets/jquery.plugin.html2canvas.js b/tests/assets/jquery.plugin.html2canvas.js index fd446ba..eb764e8 100644 --- a/tests/assets/jquery.plugin.html2canvas.js +++ b/tests/assets/jquery.plugin.html2canvas.js @@ -3,7 +3,7 @@ */ (function( $ ){ $.fn.html2canvas = function(options) { - if (options && options.profile && window.console && window.console.profile) { + if (options && options.profile && window.console && window.console.profile && window.location.search !== "?selenium2") { console.profile(); } var date = new Date(), diff --git a/tests/borders.html b/tests/borders.html deleted file mode 100644 index 3d62423..0000000 --- a/tests/borders.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - border tests - - - - - - - -
- -
-
DIV #1 -
position: relative; -
-
DIV #2 -
position: absolute; -
z-index: 2; -
-
- -
- -
-
DIV #3 -
position: relative; -
z-index: 1; -
-
DIV #4 -
position: absolute; -
z-index: 10; -
-
- -

DIV #5
position:relative;
- -

DIV #6
position:static;
- - - diff --git a/tests/cases/background/position.html b/tests/cases/background/position.html index f48e3bb..2d53bb7 100644 --- a/tests/cases/background/position.html +++ b/tests/cases/background/position.html @@ -41,8 +41,8 @@
-
-
+
+
diff --git a/tests/cases/border/dashed.html b/tests/cases/border/dashed.html new file mode 100644 index 0000000..74e95de --- /dev/null +++ b/tests/cases/border/dashed.html @@ -0,0 +1,47 @@ + + + + Borders tests + + + + + +
 
+
 
+
 
+
 
+ + diff --git a/tests/cases/border/dotted.html b/tests/cases/border/dotted.html new file mode 100644 index 0000000..77f1c35 --- /dev/null +++ b/tests/cases/border/dotted.html @@ -0,0 +1,47 @@ + + + + Borders tests + + + + + +
 
+
 
+
 
+
 
+ + diff --git a/tests/cases/border/double.html b/tests/cases/border/double.html new file mode 100644 index 0000000..daa3852 --- /dev/null +++ b/tests/cases/border/double.html @@ -0,0 +1,47 @@ + + + + Borders tests + + + + + +
 
+
 
+
 
+
 
+ + diff --git a/tests/cases/border/solid.html b/tests/cases/border/solid.html new file mode 100644 index 0000000..faab8a7 --- /dev/null +++ b/tests/cases/border/solid.html @@ -0,0 +1,47 @@ + + + + Borders tests + + + + + +
 
+
 
+
 
+
 
+ + diff --git a/tests/cases/text/text.html b/tests/cases/text/text.html index 4fcc083..2cacb95 100644 --- a/tests/cases/text/text.html +++ b/tests/cases/text/text.html @@ -97,7 +97,7 @@

<h2> text-transform

  • text-transform:none;
  • -
  • text-transform:capitalize; (including foreign characters such as Öaäå)
  • +
  • text-transform: capitalize; (including foreign characters such as Öaäå)
  • text-transform:uppercase;
  • text-transform:lowercase;
diff --git a/tests/qunit/index.html b/tests/qunit/index.html index 5aa5b38..4545f06 100644 --- a/tests/qunit/index.html +++ b/tests/qunit/index.html @@ -9,7 +9,7 @@ - + @@ -53,9 +53,9 @@
-
-
-
+
+
+
@@ -64,8 +64,8 @@
-
-
+
+
@@ -78,16 +78,16 @@
-
-
-
-
+
+
+
+
-
-
-
+
+
+