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