Skip to content

Commit 1070cec

Browse files
committed
Implement background rendering
1 parent ba9a33b commit 1070cec

6 files changed

Lines changed: 115 additions & 40 deletions

File tree

build/html2canvas.js

Lines changed: 90 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,25 @@
88
(function(window, document, undefined){
99

1010
window.html2canvas = function(nodeList, options) {
11-
var container = createWindowClone(document, window.innerWidth, window.innerHeight);
12-
var clonedWindow = container.contentWindow;
13-
var element = (nodeList === undefined) ? document.body : nodeList[0];
14-
var node = clonedWindow.document.documentElement;
15-
var support = new Support();
16-
var imageLoader = new ImageLoader(options, support);
1711
options = options || {};
1812
if (options.logging) {
1913
window.html2canvas.logging = true;
2014
window.html2canvas.start = Date.now();
2115
}
16+
createWindowClone(document, window.innerWidth, window.innerHeight).then(function(container) {
17+
log("Document cloned");
18+
var clonedWindow = container.contentWindow;
19+
var element = (nodeList === undefined) ? document.body : nodeList[0];
20+
var node = clonedWindow.document.documentElement;
21+
var support = new Support();
22+
var imageLoader = new ImageLoader(options, support);
2223

23-
var renderer = new CanvasRenderer(documentWidth(), documentHeight(), imageLoader);
24-
var parser = new NodeParser(node, renderer, support, imageLoader, options);
2524

26-
window.console.log(parser);
25+
var renderer = new CanvasRenderer(documentWidth(), documentHeight(), imageLoader);
26+
var parser = new NodeParser(node, renderer, support, imageLoader, options);
27+
28+
window.console.log(parser);
29+
});
2730
};
2831

2932
function documentWidth () {
@@ -50,13 +53,38 @@ function createWindowClone(ownerDocument, width, height) {
5053
container.style.position = "absolute";
5154
container.style.width = width + "px";
5255
container.style.height = height + "px";
53-
5456
ownerDocument.body.appendChild(container);
5557

56-
var documentClone = container.contentWindow.document;
57-
documentClone.replaceChild(documentClone.adoptNode(documentElement), documentClone.documentElement);
58-
59-
return container;
58+
return new Promise(function(resolve) {
59+
var loadedTimer = function() {
60+
/* Chrome doesn't detect relative background-images assigned in style sheets when fetched through getComputedStyle,
61+
before a certain time has passed
62+
*/
63+
if (container.contentWindow.getComputedStyle(div, null)['backgroundImage'] !== "none") {
64+
documentClone.body.removeChild(div);
65+
documentClone.body.removeChild(style);
66+
resolve(container);
67+
} else {
68+
window.setTimeout(loadedTimer, 10);
69+
}
70+
};
71+
var documentClone = container.contentWindow.document;
72+
/* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
73+
if window url is about:blank, we can assign the url to current by writing onto the document
74+
*/
75+
documentClone.open();
76+
documentClone.write("");
77+
documentClone.close();
78+
79+
documentClone.replaceChild(documentClone.adoptNode(documentElement), documentClone.documentElement);
80+
var div = documentClone.createElement("div");
81+
div.className = "html2canvas-ready-test";
82+
documentClone.body.appendChild(div);
83+
var style = documentClone.createElement("style");
84+
style.innerHTML = "body div.html2canvas-ready-test { background-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); }";
85+
documentClone.body.appendChild(style);
86+
loadedTimer();
87+
});
6088
}
6189

6290
function NodeParser(element, renderer, support, imageLoader, options) {
@@ -219,7 +247,7 @@ NodeParser.prototype.parse = function(stack) {
219247
.concat(inFlow).concat(stackLevel0).concat(text).concat(positiveZindex).forEach(function(container) {
220248
this.paint(container);
221249
if (rendered.indexOf(container.node) !== -1) {
222-
window.console.log(container, container.node);
250+
log(container, container.node);
223251
throw new Error("rendering twice");
224252
}
225253
rendered.push(container.node);
@@ -578,7 +606,6 @@ function ImageContainer(src, cors) {
578606
});
579607
}
580608

581-
582609
function ImageLoader(options, support) {
583610
this.link = null;
584611
this.options = options;
@@ -875,6 +902,29 @@ function isPercentage(value) {
875902
return value.toString().indexOf("%") !== -1;
876903
}
877904

905+
/*
906+
Copyright (c) 2013 Yehuda Katz, Tom Dale, and contributors
907+
908+
Permission is hereby granted, free of charge, to any person obtaining a copy of
909+
this software and associated documentation files (the "Software"), to deal in
910+
the Software without restriction, including without limitation the rights to
911+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
912+
of the Software, and to permit persons to whom the Software is furnished to do
913+
so, subject to the following conditions:
914+
915+
The above copyright notice and this permission notice shall be included in all
916+
copies or substantial portions of the Software.
917+
918+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
919+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
920+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
921+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
922+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
923+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
924+
SOFTWARE.
925+
*/
926+
!function(){var a,b,c,d;!function(){var e={},f={};a=function(a,b,c){e[a]={deps:b,callback:c}},d=c=b=function(a){function c(b){if("."!==b.charAt(0))return b;for(var c=b.split("/"),d=a.split("/").slice(0,-1),e=0,f=c.length;f>e;e++){var g=c[e];if(".."===g)d.pop();else{if("."===g)continue;d.push(g)}}return d.join("/")}if(d._eak_seen=e,f[a])return f[a];if(f[a]={},!e[a])throw new Error("Could not find module "+a);for(var g,h=e[a],i=h.deps,j=h.callback,k=[],l=0,m=i.length;m>l;l++)"exports"===i[l]?k.push(g={}):k.push(b(c(i[l])));var n=j.apply(this,k);return f[a]=g||n}}(),a("promise/all",["./utils","exports"],function(a,b){"use strict";function c(a){var b=this;if(!d(a))throw new TypeError("You must pass an array to all.");return new b(function(b,c){function d(a){return function(b){f(a,b)}}function f(a,c){h[a]=c,0===--i&&b(h)}var g,h=[],i=a.length;0===i&&b([]);for(var j=0;j<a.length;j++)g=a[j],g&&e(g.then)?g.then(d(j),c):f(j,g)})}var d=a.isArray,e=a.isFunction;b.all=c}),a("promise/asap",["exports"],function(a){"use strict";function b(){return function(){process.nextTick(e)}}function c(){var a=0,b=new i(e),c=document.createTextNode("");return b.observe(c,{characterData:!0}),function(){c.data=a=++a%2}}function d(){return function(){j.setTimeout(e,1)}}function e(){for(var a=0;a<k.length;a++){var b=k[a],c=b[0],d=b[1];c(d)}k=[]}function f(a,b){var c=k.push([a,b]);1===c&&g()}var g,h="undefined"!=typeof window?window:{},i=h.MutationObserver||h.WebKitMutationObserver,j="undefined"!=typeof global?global:this,k=[];g="undefined"!=typeof process&&"[object process]"==={}.toString.call(process)?b():i?c():d(),a.asap=f}),a("promise/cast",["exports"],function(a){"use strict";function b(a){if(a&&"object"==typeof a&&a.constructor===this)return a;var b=this;return new b(function(b){b(a)})}a.cast=b}),a("promise/config",["exports"],function(a){"use strict";function b(a,b){return 2!==arguments.length?c[a]:(c[a]=b,void 0)}var c={instrument:!1};a.config=c,a.configure=b}),a("promise/polyfill",["./promise","./utils","exports"],function(a,b,c){"use strict";function d(){var a="Promise"in window&&"cast"in window.Promise&&"resolve"in window.Promise&&"reject"in window.Promise&&"all"in window.Promise&&"race"in window.Promise&&function(){var a;return new window.Promise(function(b){a=b}),f(a)}();a||(window.Promise=e)}var e=a.Promise,f=b.isFunction;c.polyfill=d}),a("promise/promise",["./config","./utils","./cast","./all","./race","./resolve","./reject","./asap","exports"],function(a,b,c,d,e,f,g,h,i){"use strict";function j(a){if(!w(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof j))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");this._subscribers=[],k(a,this)}function k(a,b){function c(a){p(b,a)}function d(a){r(b,a)}try{a(c,d)}catch(e){d(e)}}function l(a,b,c,d){var e,f,g,h,i=w(c);if(i)try{e=c(d),g=!0}catch(j){h=!0,f=j}else e=d,g=!0;o(b,e)||(i&&g?p(b,e):h?r(b,f):a===F?p(b,e):a===G&&r(b,e))}function m(a,b,c,d){var e=a._subscribers,f=e.length;e[f]=b,e[f+F]=c,e[f+G]=d}function n(a,b){for(var c,d,e=a._subscribers,f=a._detail,g=0;g<e.length;g+=3)c=e[g],d=e[g+b],l(b,c,d,f);a._subscribers=null}function o(a,b){var c,d=null;try{if(a===b)throw new TypeError("A promises callback cannot return that same promise.");if(v(b)&&(d=b.then,w(d)))return d.call(b,function(d){return c?!0:(c=!0,b!==d?p(a,d):q(a,d),void 0)},function(b){return c?!0:(c=!0,r(a,b),void 0)}),!0}catch(e){return c?!0:(r(a,e),!0)}return!1}function p(a,b){a===b?q(a,b):o(a,b)||q(a,b)}function q(a,b){a._state===D&&(a._state=E,a._detail=b,u.async(s,a))}function r(a,b){a._state===D&&(a._state=E,a._detail=b,u.async(t,a))}function s(a){n(a,a._state=F)}function t(a){n(a,a._state=G)}var u=a.config,v=(a.configure,b.objectOrFunction),w=b.isFunction,x=(b.now,c.cast),y=d.all,z=e.race,A=f.resolve,B=g.reject,C=h.asap;u.async=C;var D=void 0,E=0,F=1,G=2;j.prototype={constructor:j,_state:void 0,_detail:void 0,_subscribers:void 0,then:function(a,b){var c=this,d=new this.constructor(function(){});if(this._state){var e=arguments;u.async(function(){l(c._state,d,e[c._state-1],c._detail)})}else m(this,d,a,b);return d},"catch":function(a){return this.then(null,a)}},j.all=y,j.cast=x,j.race=z,j.resolve=A,j.reject=B,i.Promise=j}),a("promise/race",["./utils","exports"],function(a,b){"use strict";function c(a){var b=this;if(!d(a))throw new TypeError("You must pass an array to race.");return new b(function(b,c){for(var d,e=0;e<a.length;e++)d=a[e],d&&"function"==typeof d.then?d.then(b,c):b(d)})}var d=a.isArray;b.race=c}),a("promise/reject",["exports"],function(a){"use strict";function b(a){var b=this;return new b(function(b,c){c(a)})}a.reject=b}),a("promise/resolve",["exports"],function(a){"use strict";function b(a){var b=this;return new b(function(b){b(a)})}a.resolve=b}),a("promise/utils",["exports"],function(a){"use strict";function b(a){return c(a)||"object"==typeof a&&null!==a}function c(a){return"function"==typeof a}function d(a){return"[object Array]"===Object.prototype.toString.call(a)}var e=Date.now||function(){return(new Date).getTime()};a.objectOrFunction=b,a.isFunction=c,a.isArray=d,a.now=e}),b("promise/polyfill").polyfill()}();
927+
878928
function Renderer(width, height, images) {
879929
this.width = width;
880930
this.height = height;
@@ -907,15 +957,15 @@ Renderer.prototype.renderBorder = function(data) {
907957

908958
Renderer.prototype.renderBackgroundImage = function(container, bounds) {
909959
var backgroundImages = container.parseBackgroundImages();
910-
backgroundImages.reverse().forEach(function(backgroundImage, index) {
960+
backgroundImages.reverse().forEach(function(backgroundImage, index, arr) {
911961
if (backgroundImage.method === "url") {
912962
var image = this.images.get(backgroundImage.args[0]);
913963
if (image) {
914-
this.renderBackgroundRepeating(container, bounds, image, index);
964+
this.renderBackgroundRepeating(container, bounds, image, arr.length - (index+1));
915965
} else {
916966
log("Error loading background-image", backgroundImage.args[0]);
917967
}
918-
}
968+
}
919969
}, this);
920970
};
921971

@@ -927,17 +977,17 @@ Renderer.prototype.renderBackgroundRepeating = function(container, bounds, image
927977
switch (repeat) {
928978
case "repeat-x":
929979
case "repeat no-repeat":
930-
this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left, bounds.top + position.top, 99999, imageContainer.image.height);
980+
this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left, bounds.top + position.top, 99999, imageContainer.image.height);
931981
break;
932982
case "repeat-y":
933983
case "no-repeat repeat":
934-
this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left + position.left, bounds.top, imageContainer.image.width, 99999);
984+
this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left, bounds.top, imageContainer.image.width, 99999);
935985
break;
936986
case "no-repeat":
937-
this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left + position.left, bounds.top + position.top, imageContainer.image.width, imageContainer.image.height);
987+
this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left, bounds.top + position.top, imageContainer.image.width, imageContainer.image.height);
938988
break;
939989
default:
940-
this.renderBackgroundRepeat(imageContainer, position, {top: bounds.top, left: bounds.left});
990+
this.renderBackgroundRepeat(imageContainer, position, size, {top: bounds.top, left: bounds.left});
941991
break;
942992
}
943993
};
@@ -999,27 +1049,40 @@ CanvasRenderer.prototype.text = function(text, left, bottom) {
9991049
this.ctx.fillText(text, left, bottom);
10001050
};
10011051

1002-
CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, bounds, left, top, width, height) {
1052+
CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, size, bounds, left, top, width, height) {
10031053
var shape = [
10041054
["line", Math.round(left), Math.round(top)],
10051055
["line", Math.round(left + width), Math.round(top)],
10061056
["line", Math.round(left + width), Math.round(height + top)],
10071057
["line", Math.round(left), Math.round(height + top)]
10081058
];
1009-
console.log(shape);
10101059
this.clip(shape, function() {
10111060
this.renderBackgroundRepeat(imageContainer, backgroundPosition, bounds);
10121061
}, this);
10131062
};
10141063

1015-
CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, bounds) {
1064+
CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, size, bounds) {
10161065
var offsetX = Math.round(bounds.left + backgroundPosition.left), offsetY = Math.round(bounds.top + backgroundPosition.top);
1017-
this.setFillStyle(this.ctx.createPattern(imageContainer.image, "repeat"));
1066+
this.setFillStyle(this.ctx.createPattern(this.resizeImage(imageContainer, size), "repeat"));
10181067
this.ctx.translate(offsetX, offsetY);
10191068
this.ctx.fill();
10201069
this.ctx.translate(-offsetX, -offsetY);
10211070
};
10221071

1072+
CanvasRenderer.prototype.resizeImage = function(imageContainer, size) {
1073+
var image = imageContainer.image;
1074+
if(image.width === size.width && image.height === size.height) {
1075+
return image;
1076+
}
1077+
1078+
var ctx, canvas = document.createElement('canvas');
1079+
canvas.width = size.width;
1080+
canvas.height = size.height;
1081+
ctx = canvas.getContext("2d");
1082+
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height );
1083+
return canvas;
1084+
};
1085+
10231086
function StackingContext(hasOwnStacking, opacity, element, parent) {
10241087
NodeContainer.call(this, element, parent);
10251088
this.ownStacking = hasOwnStacking;

0 commit comments

Comments
 (0)