diff --git a/.jshintrc b/.jshintrc
index b6120bd..60584a5 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -14,5 +14,5 @@
"jQuery": true
},
"predef": ["NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise",
- "ImageContainer", "ProxyImageContainer", "DummyImageContainer"]
+ "ImageContainer", "ProxyImageContainer", "DummyImageContainer", "log"]
}
diff --git a/build/html2canvas.js b/build/html2canvas.js
index fcca2c8..16b0117 100644
--- a/build/html2canvas.js
+++ b/build/html2canvas.js
@@ -11,14 +11,37 @@ window.html2canvas = function(nodeList, options) {
var container = createWindowClone(document, window.innerWidth, window.innerHeight);
var clonedWindow = container.contentWindow;
var element = (nodeList === undefined) ? document.body : nodeList[0];
+ var node = clonedWindow.document.documentElement;
+ var support = new Support();
+ var imageLoader = new ImageLoader(options, support);
+ options = options || {};
+ if (options.logging) {
+ window.html2canvas.logging = true;
+ window.html2canvas.start = Date.now();
+ }
- var canvas = new CanvasRenderer();
- var parser = new NodeParser(clonedWindow.document.documentElement, canvas, options || {});
+ var renderer = new CanvasRenderer(documentWidth(), documentHeight(), imageLoader);
+ var parser = new NodeParser(node, renderer, support, imageLoader, options);
window.console.log(parser);
- options.onrendered(canvas.canvas);
};
+function documentWidth () {
+ return Math.max(
+ Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
+ Math.max(document.body.offsetWidth, document.documentElement.offsetWidth),
+ Math.max(document.body.clientWidth, document.documentElement.clientWidth)
+ );
+}
+
+function documentHeight () {
+ return Math.max(
+ Math.max(document.body.scrollHeight, document.documentElement.scrollHeight),
+ Math.max(document.body.offsetHeight, document.documentElement.offsetHeight),
+ Math.max(document.body.clientHeight, document.documentElement.clientHeight)
+ );
+}
+
function createWindowClone(ownerDocument, width, height) {
var documentElement = ownerDocument.documentElement.cloneNode(true),
container = ownerDocument.createElement("iframe");
@@ -36,31 +59,37 @@ function createWindowClone(ownerDocument, width, height) {
return container;
}
-function NodeParser(element, renderer, options) {
+function NodeParser(element, renderer, support, imageLoader, options) {
this.renderer = renderer;
this.options = options;
- this.support = new Support();
this.range = null;
+ this.support = support;
this.stack = new StackingContext(true, 1, element.ownerDocument, null);
var parent = new NodeContainer(element, null);
- parent.blockFormattingContext = parent;
+ parent.visibile = parent.isElementVisible();
this.nodes = [parent].concat(this.getChildren(parent)).filter(function(container) {
return container.visible = container.isElementVisible();
});
+ this.images = imageLoader.fetch(this.nodes.filter(isElement));
this.createStackingContexts();
this.sortStackingContexts(this.stack);
- this.parse(this.stack);
+ this.images.ready.then(bind(function() {
+ log("Images loaded, starting parsing");
+ this.parse(this.stack);
+ options.onrendered(renderer.canvas);
+ }, this));
}
NodeParser.prototype.getChildren = function(parentContainer) {
return flatten([].filter.call(parentContainer.node.childNodes, renderableNode).map(function(node) {
- var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);
- return node.nodeType === Node.ELEMENT_NODE && container.length ? container.concat(this.getChildren(container[0])) : container;
+ var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);
+ return node.nodeType === Node.ELEMENT_NODE && container.length ? (container[0].isElementVisible() ? container.concat(this.getChildren(container[0])) : []) : container;
}, this));
};
NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
var stack = new StackingContext(hasOwnStacking, container.cssFloat('opacity'), container.node, container.parent);
+ stack.visible = container.visible;
var parentStack = stack.getParentStack(this);
parentStack.contexts.push(stack);
container.stack = stack;
@@ -177,11 +206,12 @@ function noLetterSpacing(container) {
NodeParser.prototype.parse = function(stack) {
// http://www.w3.org/TR/CSS21/visuren.html#z-index
var negativeZindex = stack.contexts.filter(negativeZIndex); // 2. the child stacking contexts with negative stack levels (most negative first).
- var descendantElements = stack.children.filter(isElement).filter(not(isFloating));
- var nonInlineNonPositionedDescendants = descendantElements.filter(not(isPositioned)).filter(not(inlineLevel)); // 3 the in-flow, non-inline-level, non-positioned descendants.
+ var descendantElements = stack.children.filter(isElement);
+ var descendantNonFloats = descendantElements.filter(not(isFloating));
+ var nonInlineNonPositionedDescendants = descendantNonFloats.filter(not(isPositioned)).filter(not(inlineLevel)); // 3 the in-flow, non-inline-level, non-positioned descendants.
var nonPositionedFloats = descendantElements.filter(not(isPositioned)).filter(isFloating); // 4. the non-positioned floats.
- var inFlow = descendantElements.filter(not(isPositioned)).filter(inlineLevel); // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
- var stackLevel0 = stack.contexts.concat(descendantElements.filter(isPositioned)).filter(zIndex0); // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
+ var inFlow = descendantNonFloats.filter(not(isPositioned)).filter(inlineLevel); // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
+ var stackLevel0 = stack.contexts.concat(descendantNonFloats.filter(isPositioned)).filter(zIndex0); // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
var text = stack.children.filter(isTextNode).filter(hasText);
var positiveZindex = stack.contexts.filter(positiveZIndex); // 7. the child stacking contexts with positive stack levels (least positive first).
var rendered = [];
@@ -528,13 +558,329 @@ function hasOpacity(container) {
return container.css("opacity") < 1;
}
-function Renderer() {}
-function NYI() {
+function bind(callback, context) {
return function() {
- throw new Error("Render function not implemented");
+ return callback.apply(context, arguments);
};
}
+function ImageContainer(src, cors) {
+ this.src = src;
+ this.image = new Image();
+ var image = this.image;
+ this.promise = new Promise(function(resolve, reject) {
+ image.onload = resolve;
+ image.onerror = reject;
+ if (cors) {
+ image.crossOrigin = "anonymous";
+ }
+ image.src = src;
+ });
+}
+
+
+function ImageLoader(options, support) {
+ this.link = null;
+ this.options = options;
+ this.support = support;
+ this.origin = window.location.protocol + window.location.host;
+}
+
+ImageLoader.prototype.findImages = function(images, container) {
+ var backgrounds = container.parseBackgroundImages();
+ var backgroundImages = backgrounds.filter(this.isImageBackground).map(this.getBackgroundUrl).filter(this.imageExists(images)).map(this.loadImage, this);
+ return images.concat(backgroundImages);
+};
+
+ImageLoader.prototype.getBackgroundUrl = function(imageData) {
+ return imageData.args[0];
+};
+
+ImageLoader.prototype.isImageBackground = function(imageData) {
+ return imageData.method === "url";
+};
+
+ImageLoader.prototype.loadImage = function(src) {
+ if (src.match(/data:image\/.*;base64,/i)) {
+ return new ImageContainer(src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''), false);
+ } else if (this.isSameOrigin(src) || this.options.allowTaint === true) {
+ return new ImageContainer(src, false);
+ } else if (this.support.cors && !this.options.allowTaint && this.options.useCORS) {
+ return new ImageContainer(src, true);
+ } else if (this.options.proxy) {
+ return new ProxyImageContainer(src);
+ } else {
+ return new DummyImageContainer(src);
+ }
+};
+
+ImageLoader.prototype.imageExists = function(images) {
+ return function(newImage) {
+ return !images.some(function(image) {
+ return image.src !== newImage.src;
+ });
+ };
+};
+
+ImageLoader.prototype.isSameOrigin = function(url) {
+ var link = this.link || (this.link = document.createElement("a"));
+ link.href = url;
+ link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
+ var origin = link.protocol + link.host;
+ return (origin === this.origin);
+};
+
+ImageLoader.prototype.getPromise = function(container) {
+ return container.promise;
+};
+
+ImageLoader.prototype.get = function(src) {
+ var found = null;
+ return this.images.some(function(img) {
+ return (found = img).src === src;
+ }) ? found : null;
+};
+
+ImageLoader.prototype.fetch = function(nodes) {
+ this.images = nodes.reduce(bind(this.findImages, this), []);
+ this.ready = Promise.all(this.images.map(this.getPromise));
+ return this;
+};
+
+function log() {
+ if (window.html2canvas.logging && window.console && window.console.log) {
+ window.console.log.apply(window.console, [(Date.now() - window.html2canvas.start) + "ms", "html2canvas:"].concat([].slice.call(arguments, 0)));
+ }
+}
+
+function NodeContainer(node, parent) {
+ this.node = node;
+ this.parent = parent;
+ this.stack = null;
+ this.bounds = null;
+ this.visible = null;
+ this.computedStyles = null;
+ this.styles = {};
+ this.backgroundImages = null;
+}
+
+NodeContainer.prototype.assignStack = function(stack) {
+ this.stack = stack;
+ stack.children.push(this);
+};
+
+NodeContainer.prototype.isElementVisible = function() {
+ return this.node.nodeType === Node.TEXT_NODE ? this.parent.visible : (this.css('display') !== "none" && this.css('visibility') !== "hidden" && !this.node.hasAttribute("data-html2canvas-ignore"));
+};
+
+NodeContainer.prototype.css = function(attribute) {
+ if (!this.computedStyles) {
+ this.computedStyles = this.node.ownerDocument.defaultView.getComputedStyle(this.node, null);
+ }
+
+ return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);
+};
+
+NodeContainer.prototype.cssInt = function(attribute) {
+ var value = parseInt(this.css(attribute), 10);
+ return (Number.isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html
+};
+
+NodeContainer.prototype.cssFloat = function(attribute) {
+ var value = parseFloat(this.css(attribute));
+ return (Number.isNaN(value)) ? 0 : value;
+};
+
+NodeContainer.prototype.fontWeight = function() {
+ var weight = this.css("fontWeight");
+ switch(parseInt(weight, 10)){
+ case 401:
+ weight = "bold";
+ break;
+ case 400:
+ weight = "normal";
+ break;
+ }
+ return weight;
+};
+
+NodeContainer.prototype.parseBackgroundImages = function() {
+ var whitespace = ' \r\n\t',
+ method, definition, prefix, prefix_i, block, results = [],
+ 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,
+ image: null
+ });
+ }
+ args = [];
+ method = prefix = definition = block = '';
+ };
+ args = [];
+ method = prefix = definition = block = '';
+ this.css("backgroundImage").split("").forEach(function(c) {
+ if (mode === 0 && whitespace.indexOf(c) > -1) {
+ return;
+ }
+ 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;
+ return;
+ } else {
+ numParen++;
+ }
+ break;
+ case ')':
+ if (quote) {
+ break;
+ } else if(mode === 1) {
+ if(numParen === 0) {
+ mode = 0;
+ block += c;
+ appendResult();
+ return;
+ } else {
+ numParen--;
+ }
+ }
+ break;
+
+ case ',':
+ if (quote) {
+ break;
+ } else if(mode === 0) {
+ appendResult();
+ return;
+ } else if (mode === 1) {
+ if (numParen === 0 && !method.match(/^url$/i)) {
+ args.push(definition);
+ definition = '';
+ block += c;
+ return;
+ }
+ }
+ break;
+ }
+
+ block += c;
+ if (mode === 0) {
+ method += c;
+ } else {
+ definition += c;
+ }
+ });
+
+ appendResult();
+
+ return this.backgroundImages || (this.backgroundImages = results);
+};
+
+NodeContainer.prototype.cssList = function(property, index) {
+ var value = (this.css(property) || '').split(',');
+ value = value[index || 0] || value[0] || 'auto';
+ value = value.trim().split(' ');
+ if (value.length === 1) {
+ value = [value[0], value[0]];
+ }
+ return value;
+};
+
+NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) {
+ var size = this.cssList("backgroundSize", index);
+ var width, height;
+
+ if (isPercentage(size[0])) {
+ width = bounds.width * parseFloat(size[0]) / 100;
+ } else if (/contain|cover/.test(size[0])) {
+ var targetRatio = bounds.width / bounds.height, currentRatio = image.width / image.height;
+ return (targetRatio < currentRatio ^ size[0] === 'contain') ? {width: bounds.height * currentRatio, height: bounds.height} : {width: bounds.width, height: bounds.width / currentRatio};
+ } else {
+ width = parseInt(size[0], 10);
+ }
+
+ if (size[0] === 'auto' && size[1] === 'auto') {
+ height = image.height;
+ } else if (size[1] === 'auto') {
+ height = width / image.width * image.height;
+ } else if (isPercentage(size[1])) {
+ height = bounds.height * parseFloat(size[1]) / 100;
+ } else {
+ height = parseInt(size[1], 10);
+ }
+
+ if (size[0] === 'auto') {
+ width = height / image.height * image.width;
+ }
+
+ return {width: width, height: height};
+};
+
+NodeContainer.prototype.parseBackgroundPosition = function(bounds, image, index, backgroundSize) {
+ var position = this.cssList('backgroundPosition', index);
+ var left, top;
+
+ if (isPercentage(position[0])){
+ left = (bounds.width - (backgroundSize || image).width) * (parseFloat(position[0]) / 100);
+ } else {
+ left = parseInt(position[0], 10);
+ }
+
+ if (position[1] === 'auto') {
+ top = left / image.width * image.height;
+ } else if (isPercentage(position[1])){
+ top = (bounds.height - (backgroundSize || image).height) * parseFloat(position[1]) / 100;
+ } else {
+ top = parseInt(position[1], 10);
+ }
+
+ if (position[0] === 'auto') {
+ left = top / image.height * image.width;
+ }
+
+ return {left: left, top: top};
+};
+
+NodeContainer.prototype.parseBackgroundRepeat = function(index) {
+ return this.cssList("backgroundRepeat", index)[0];
+};
+
+function isPercentage(value) {
+ return value.toString().indexOf("%") !== -1;
+}
+
+function Renderer(width, height, images) {
+ this.width = width;
+ this.height = height;
+ this.images = images;
+}
+
Renderer.prototype.renderBackground = function(container, bounds) {
if (bounds.height > 0 && bounds.width > 0) {
this.renderBackgroundColor(container, bounds);
@@ -545,13 +891,7 @@ Renderer.prototype.renderBackground = function(container, bounds) {
Renderer.prototype.renderBackgroundColor = function(container, bounds) {
var color = container.css("backgroundColor");
if (!this.isTransparent(color)) {
- this.rectangle(
- bounds.left,
- bounds.top,
- bounds.width,
- bounds.height,
- container.css("backgroundColor")
- );
+ this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, container.css("backgroundColor"));
}
};
@@ -566,54 +906,53 @@ Renderer.prototype.renderBorder = function(data) {
};
Renderer.prototype.renderBackgroundImage = function(container, bounds) {
+ var backgroundImages = container.parseBackgroundImages();
+ backgroundImages.reverse().forEach(function(backgroundImage, index) {
+ if (backgroundImage.method === "url") {
+ var image = this.images.get(backgroundImage.args[0]);
+ if (image) {
+ this.renderBackgroundRepeating(container, bounds, image, index);
+ } else {
+ log("Error loading background-image", backgroundImage.args[0]);
+ }
+ }
+ }, this);
+};
+Renderer.prototype.renderBackgroundRepeating = function(container, bounds, imageContainer, index) {
+ var size = container.parseBackgroundSize(bounds, imageContainer.image, index);
+ var position = container.parseBackgroundPosition(bounds, imageContainer.image, index, size);
+ var repeat = container.parseBackgroundRepeat(index);
+// image = resizeImage(image, backgroundSize);
+ switch (repeat) {
+ case "repeat-x":
+ case "repeat no-repeat":
+ this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left, bounds.top + position.top, 99999, imageContainer.image.height);
+ break;
+ case "repeat-y":
+ case "no-repeat repeat":
+ this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left + position.left, bounds.top, imageContainer.image.width, 99999);
+ break;
+ case "no-repeat":
+ this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left + position.left, bounds.top + position.top, imageContainer.image.width, imageContainer.image.height);
+ break;
+ default:
+ this.renderBackgroundRepeat(imageContainer, position, {top: bounds.top, left: bounds.left});
+ break;
+ }
};
Renderer.prototype.isTransparent = function(color) {
return (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)");
};
-Renderer.prototype.clip = NYI();
-Renderer.prototype.rectangle = NYI();
-Renderer.prototype.shape = NYI();
-
-function Support() {
- this.rangeBounds = this.testRangeBounds();
-}
-
-Support.prototype.testRangeBounds = function() {
- var range, testElement, rangeBounds, rangeHeight, support = false;
-
- if (document.createRange) {
- range = document.createRange();
- if (range.getBoundingClientRect) {
- testElement = document.createElement('boundtest');
- testElement.style.height = "123px";
- testElement.style.display = "block";
- document.body.appendChild(testElement);
-
- range.selectNode(testElement);
- rangeBounds = range.getBoundingClientRect();
- rangeHeight = rangeBounds.height;
-
- if (rangeHeight === 123) {
- support = true;
- }
- document.body.removeChild(testElement);
- }
- }
-
- return support;
-};
-
-function CanvasRenderer() {
- Renderer.call(this);
+function CanvasRenderer(width, height) {
+ Renderer.apply(this, arguments);
this.canvas = document.createElement("canvas");
- this.canvas.width = window.innerWidth;
- this.canvas.height = window.innerHeight;
+ this.canvas.width = width;
+ this.canvas.height = height;
this.ctx = this.canvas.getContext("2d");
this.ctx.textBaseline = "bottom";
- document.body.appendChild(this.canvas);
}
CanvasRenderer.prototype = Object.create(Renderer.prototype);
@@ -660,4 +999,104 @@ CanvasRenderer.prototype.text = function(text, left, bottom) {
this.ctx.fillText(text, left, bottom);
};
+CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, bounds, left, top, width, height) {
+ var shape = [
+ ["line", Math.round(left), Math.round(top)],
+ ["line", Math.round(left + width), Math.round(top)],
+ ["line", Math.round(left + width), Math.round(height + top)],
+ ["line", Math.round(left), Math.round(height + top)]
+ ];
+ console.log(shape);
+ this.clip(shape, function() {
+ this.renderBackgroundRepeat(imageContainer, backgroundPosition, bounds);
+ }, this);
+};
+
+CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, bounds) {
+ var offsetX = Math.round(bounds.left + backgroundPosition.left), offsetY = Math.round(bounds.top + backgroundPosition.top);
+ this.setFillStyle(this.ctx.createPattern(imageContainer.image, "repeat"));
+ this.ctx.translate(offsetX, offsetY);
+ this.ctx.fill();
+ this.ctx.translate(-offsetX, -offsetY);
+};
+
+function StackingContext(hasOwnStacking, opacity, element, parent) {
+ NodeContainer.call(this, element, parent);
+ this.ownStacking = hasOwnStacking;
+ this.contexts = [];
+ this.children = [];
+ this.opacity = (this.parent ? this.parent.stack.opacity : 1) * opacity;
+}
+
+StackingContext.prototype = Object.create(NodeContainer.prototype);
+
+StackingContext.prototype.getParentStack = function(context) {
+ var parentStack = (this.parent) ? this.parent.stack : null;
+ return parentStack ? (parentStack.ownStacking ? parentStack : parentStack.getParentStack(context)) : context.stack;
+};
+
+function Support() {
+ this.rangeBounds = this.testRangeBounds();
+ this.cors = this.testCORS();
+}
+
+Support.prototype.testRangeBounds = function() {
+ var range, testElement, rangeBounds, rangeHeight, support = false;
+
+ if (document.createRange) {
+ range = document.createRange();
+ if (range.getBoundingClientRect) {
+ testElement = document.createElement('boundtest');
+ testElement.style.height = "123px";
+ testElement.style.display = "block";
+ document.body.appendChild(testElement);
+
+ range.selectNode(testElement);
+ rangeBounds = range.getBoundingClientRect();
+ rangeHeight = rangeBounds.height;
+
+ if (rangeHeight === 123) {
+ support = true;
+ }
+ document.body.removeChild(testElement);
+ }
+ }
+
+ return support;
+};
+
+Support.prototype.testCORS = function() {
+ return typeof((new Image()).crossOrigin) !== "undefined";
+};
+
+function TextContainer(node, parent) {
+ NodeContainer.call(this, node, parent);
+}
+
+TextContainer.prototype = Object.create(NodeContainer.prototype);
+
+TextContainer.prototype.applyTextTransform = function() {
+ this.node.data = this.transform(this.parent.css("textTransform"));
+};
+
+TextContainer.prototype.transform = function(transform) {
+ var text = this.node.data;
+ switch(transform){
+ case "lowercase":
+ return text.toLowerCase();
+ case "capitalize":
+ return text.replace(/(^|\s|:|-|\(|\))([a-z])/g, capitalize);
+ case "uppercase":
+ return text.toUpperCase();
+ default:
+ return text;
+ }
+};
+
+function capitalize(m, p1, p2) {
+ if (m.length > 0) {
+ return p1 + p2.toUpperCase();
+ }
+}
+
})(window,document);
\ No newline at end of file
diff --git a/build/html2canvas.min.js b/build/html2canvas.min.js
index 76e8a8f..0454c47 100644
--- a/build/html2canvas.min.js
+++ b/build/html2canvas.min.js
@@ -4,4 +4,4 @@
Released under MIT License
*/
-(function(t,e,n){function o(t,e,n){var o=t.documentElement.cloneNode(!0),r=t.createElement("iframe");r.style.display="hidden",r.style.position="absolute",r.style.width=e+"px",r.style.height=n+"px",t.body.appendChild(r);var i=r.contentWindow.document;return i.replaceChild(i.adoptNode(o),i.documentElement),r}function r(t,e,n){this.renderer=e,this.options=n,this.support=new S,this.range=null,this.stack=new StackingContext(!0,1,t.ownerDocument,null);var o=new NodeContainer(t,null);o.blockFormattingContext=o,this.nodes=[o].concat(this.getChildren(o)).filter(function(t){return t.visible=t.isElementVisible()}),this.createStackingContexts(),this.sortStackingContexts(this.stack),this.parse(this.stack)}function i(t){return 0>t.cssInt("zIndex")}function s(t){return t.cssInt("zIndex")>0}function c(t){return 0===t.cssInt("zIndex")}function a(t){return-1!==["inline","inline-block","inline-table"].indexOf(t.css("display"))}function h(t){return t instanceof StackingContext}function d(t){return t.node.data.trim().length>0}function p(t){return/^(normal|none|0px)$/.test(t.parent.css("letterSpacing"))}function u(t,e,n,o,r,i,s){e[0]>0||e[1]>0?(t.push(["line",o[0].start.x,o[0].start.y]),o[0].curveTo(t),o[1].curveTo(t)):t.push(["line",i,s]),(n[0]>0||n[1]>0)&&t.push(["line",r[0].start.x,r[0].start.y])}function l(t){return["TopLeft","TopRight","BottomRight","BottomLeft"].map(function(e){var n=t.css("border"+e+"Radius"),o=n.split(" ");return 1>=o.length&&(o[1]=o[0]),o.map(f)})}function f(t){return parseInt(t,10)}function g(t,e,n,o){var r=4*((Math.sqrt(2)-1)/3),i=n*r,s=o*r,c=t+n,a=e+o;return{topLeft:x({x:t,y:a},{x:t,y:a-s},{x:c-i,y:e},{x:c,y:e}),topRight:x({x:t,y:e},{x:t+i,y:e},{x:c,y:a-s},{x:c,y:a}),bottomRight:x({x:c,y:e},{x:c,y:e+s},{x:t+i,y:a},{x:t,y:a}),bottomLeft:x({x:c,y:a},{x:c-i,y:a},{x:t,y:e+s},{x:t,y:e})}}function y(t,e,n){var o=t.left,r=t.top,i=t.width,s=t.height,c=e[0][0],a=e[0][1],h=e[1][0],d=e[1][1],p=e[2][0],u=e[2][1],l=e[3][0],f=e[3][1],y=i-h,x=s-u,m=i-p,w=s-f;return{topLeftOuter:g(o,r,c,a).topLeft.subdivide(.5),topLeftInner:g(o+n[3].width,r+n[0].width,Math.max(0,c-n[3].width),Math.max(0,a-n[0].width)).topLeft.subdivide(.5),topRightOuter:g(o+y,r,h,d).topRight.subdivide(.5),topRightInner:g(o+Math.min(y,i+n[3].width),r+n[0].width,y>i+n[3].width?0:h-n[3].width,d-n[0].width).topRight.subdivide(.5),bottomRightOuter:g(o+m,r+x,p,u).bottomRight.subdivide(.5),bottomRightInner:g(o+Math.min(m,i+n[3].width),r+Math.min(x,s+n[0].width),Math.max(0,p-n[1].width),Math.max(0,u-n[2].width)).bottomRight.subdivide(.5),bottomLeftOuter:g(o,r+w,l,f).bottomLeft.subdivide(.5),bottomLeftInner:g(o+n[3].width,r+w,Math.max(0,l-n[3].width),Math.max(0,f-n[2].width)).bottomLeft.subdivide(.5)}}function x(t,e,n,o){var r=function(t,e,n){return{x:t.x+(e.x-t.x)*n,y:t.y+(e.y-t.y)*n}};return{start:t,startControl:e,endControl:n,end:o,subdivide:function(i){var s=r(t,e,i),c=r(e,n,i),a=r(n,o,i),h=r(s,c,i),d=r(c,a,i),p=r(h,d,i);return[x(t,s,h,p),x(p,d,a,o)]},curveTo:function(t){t.push(["bezierCurve",e.x,e.y,n.x,n.y,o.x,o.y])},curveToReversed:function(o){o.push(["bezierCurve",n.x,n.y,e.x,e.y,t.x,t.y])}}}function m(t,e,n,o,r,i,s){var c=[];return e[0]>0||e[1]>0?(c.push(["line",o[1].start.x,o[1].start.y]),o[1].curveTo(c)):c.push(["line",t.c1[0],t.c1[1]]),n[0]>0||n[1]>0?(c.push(["line",i[0].start.x,i[0].start.y]),i[0].curveTo(c),c.push(["line",s[0].end.x,s[0].end.y]),s[0].curveToReversed(c)):(c.push(["line",t.c2[0],t.c2[1]]),c.push(["line",t.c3[0],t.c3[1]])),e[0]>0||e[1]>0?(c.push(["line",r[1].end.x,r[1].end.y]),r[1].curveToReversed(c)):c.push(["line",t.c4[0],t.c4[1]]),c}function w(t){return t.node.nodeType!==Node.ELEMENT_NODE||-1===["SCRIPT","HEAD","TITLE","OBJECT","BR"].indexOf(t.node.nodeName)}function b(t){return[].concat.apply([],t)}function v(t){return t.nodeType===Node.TEXT_NODE||t.nodeType===Node.ELEMENT_NODE}function R(t){var e=t.css("position"),n="absolute"===e||"relative"===e?t.css("zIndex"):"auto";return"auto"!==n}function T(t){return"static"!==t.css("position")}function C(t){return"none"!==t.css("float")}function E(t){var e=this;return function(){return!t.apply(e,arguments)}}function B(t){return t.node.nodeType===Node.ELEMENT_NODE}function k(t){return t.node.nodeType===Node.TEXT_NODE}function I(t,e){return t.cssInt("zIndex")-e.cssInt("zIndex")}function L(t){return 1>t.css("opacity")}function O(){}function N(){return function(){throw Error("Render function not implemented")}}function S(){this.rangeBounds=this.testRangeBounds()}function M(){O.call(this),this.canvas=e.createElement("canvas"),this.canvas.width=t.innerWidth,this.canvas.height=t.innerHeight,this.ctx=this.canvas.getContext("2d"),this.ctx.textBaseline="bottom",e.body.appendChild(this.canvas)}t.html2canvas=function(i,s){var c=o(e,t.innerWidth,t.innerHeight),a=c.contentWindow;i===n?e.body:i[0];var h=new M,d=new r(a.document.documentElement,h,s||{});t.console.log(d),s.onrendered(h.canvas)},r.prototype.getChildren=function(t){return b([].filter.call(t.node.childNodes,v).map(function(e){var n=[e.nodeType===Node.TEXT_NODE?new TextContainer(e,t):new NodeContainer(e,t)].filter(w);return e.nodeType===Node.ELEMENT_NODE&&n.length?n.concat(this.getChildren(n[0])):n},this))},r.prototype.newStackingContext=function(t,e){var n=new StackingContext(e,t.cssFloat("opacity"),t.node,t.parent),o=n.getParentStack(this);o.contexts.push(n),t.stack=n},r.prototype.createStackingContexts=function(){this.nodes.forEach(function(t){B(t)&&(this.isRootElement(t)||L(t)||R(t)||this.isBodyWithTransparentRoot(t))?this.newStackingContext(t,!0):B(t)&&T(t)?this.newStackingContext(t,!1):t.assignStack(t.parent.stack)},this)},r.prototype.isBodyWithTransparentRoot=function(t){return"BODY"===t.node.nodeName&&this.renderer.isTransparent(t.parent.css("backgroundColor"))},r.prototype.isRootElement=function(t){return"HTML"===t.node.nodeName},r.prototype.sortStackingContexts=function(t){t.contexts.sort(I),t.contexts.forEach(this.sortStackingContexts,this)},r.prototype.parseBounds=function(t){return t.bounds=this.getBounds(t.node)},r.prototype.getBounds=function(t){if(t.getBoundingClientRect){var e=t.getBoundingClientRect();return{top:e.top,bottom:e.bottom||e.top+e.height,left:e.left,width:t.offsetWidth,height:t.offsetHeight}}return{}},r.prototype.parseTextBounds=function(t){return function(e,n,o){if("none"!==t.parent.css("textDecoration")||0!==e.trim().length){var r=o.slice(0,n).join("").length;if(this.support.rangeBounds)return this.getRangeBounds(t.node,r,e.length);if(t.node&&"string"==typeof t.node.data){var i=t.node.splitText(e.length),s=this.getWrapperBounds(t.node);return t.node=i,s}}}},r.prototype.getWrapperBounds=function(t){var e=t.ownerDocument.createElement("wrapper"),n=t.parentNode,o=t.cloneNode(!0);e.appendChild(t.cloneNode(!0)),n.replaceChild(e,t);var r=this.getBounds(e);return n.replaceChild(o,e),r},r.prototype.getRangeBounds=function(t,e,n){var o=this.range||(this.range=t.ownerDocument.createRange());return o.setStart(t,e),o.setEnd(t,e+n),o.getBoundingClientRect()},r.prototype.parse=function(e){var n=e.contexts.filter(i),o=e.children.filter(B).filter(E(C)),r=o.filter(E(T)).filter(E(a)),p=o.filter(E(T)).filter(C),u=o.filter(E(T)).filter(a),l=e.contexts.concat(o.filter(T)).filter(c),f=e.children.filter(k).filter(d),g=e.contexts.filter(s),y=[];n.concat(r).concat(p).concat(u).concat(l).concat(f).concat(g).forEach(function(e){if(this.paint(e),-1!==y.indexOf(e.node))throw t.console.log(e,e.node),Error("rendering twice");y.push(e.node),h(e)&&this.parse(e)},this)},r.prototype.paint=function(t){k(t)?this.paintText(t):this.paintNode(t)},r.prototype.paintNode=function(t){h(t)&&this.renderer.setOpacity(t.opacity);var e=this.parseBounds(t),n=this.parseBorders(t);this.renderer.clip(n.clip,function(){this.renderer.renderBackground(t,e)},this),this.renderer.renderBorders(n.borders)},r.prototype.paintText=function(t){t.applyTextTransform();var e=t.node.data.split(!this.options.letterRendering||p(t)?/(\b| )/:""),n=t.parent.fontWeight(),o=t.parent.css("fontSize"),r=t.parent.css("fontFamily");this.renderer.font(t.parent.css("color"),t.parent.css("fontStyle"),t.parent.css("fontVariant"),n,o,r),e.map(this.parseTextBounds(t),this).forEach(function(t,n){t&&this.renderer.text(e[n],t.left,t.bottom)},this)},r.prototype.parseBorders=function(t){var e=t.bounds,n=l(t),o=["Top","Right","Bottom","Left"].map(function(e){return{width:t.cssInt("border"+e+"Width"),color:t.css("border"+e+"Color"),args:null}}),r=y(e,n,o);return{clip:this.parseBackgroundClip(t,r,o,n,e),borders:o.map(function(t,i){if(t.width>0){var s=e.left,c=e.top,a=e.width,h=e.height-o[2].width;switch(i){case 0:h=o[0].width,t.args=m({c1:[s,c],c2:[s+a,c],c3:[s+a-o[1].width,c+h],c4:[s+o[3].width,c+h]},n[0],n[1],r.topLeftOuter,r.topLeftInner,r.topRightOuter,r.topRightInner);break;case 1:s=e.left+e.width-o[1].width,a=o[1].width,t.args=m({c1:[s+a,c],c2:[s+a,c+h+o[2].width],c3:[s,c+h],c4:[s,c+o[0].width]},n[1],n[2],r.topRightOuter,r.topRightInner,r.bottomRightOuter,r.bottomRightInner);break;case 2:c=c+e.height-o[2].width,h=o[2].width,t.args=m({c1:[s+a,c+h],c2:[s,c+h],c3:[s+o[3].width,c],c4:[s+a-o[3].width,c]},n[2],n[3],r.bottomRightOuter,r.bottomRightInner,r.bottomLeftOuter,r.bottomLeftInner);break;case 3:a=o[3].width,t.args=m({c1:[s,c+h+o[2].width],c2:[s,c],c3:[s+a,c+o[0].width],c4:[s+a,c+h]},n[3],n[0],r.bottomLeftOuter,r.bottomLeftInner,r.topLeftOuter,r.topLeftInner)}}return t})}},r.prototype.parseBackgroundClip=function(t,e,n,o,r){var i=t.css("backgroundClip"),s=[];switch(i){case"content-box":case"padding-box":u(s,o[0],o[1],e.topLeftInner,e.topRightInner,r.left+n[3].width,r.top+n[0].width),u(s,o[1],o[2],e.topRightInner,e.bottomRightInner,r.left+r.width-n[1].width,r.top+n[0].width),u(s,o[2],o[3],e.bottomRightInner,e.bottomLeftInner,r.left+r.width-n[1].width,r.top+r.height-n[2].width),u(s,o[3],o[0],e.bottomLeftInner,e.topLeftInner,r.left+n[3].width,r.top+r.height-n[2].width);break;default:u(s,o[0],o[1],e.topLeftOuter,e.topRightOuter,r.left,r.top),u(s,o[1],o[2],e.topRightOuter,e.bottomRightOuter,r.left+r.width,r.top),u(s,o[2],o[3],e.bottomRightOuter,e.bottomLeftOuter,r.left+r.width,r.top+r.height),u(s,o[3],o[0],e.bottomLeftOuter,e.topLeftOuter,r.left,r.top+r.height)}return s},O.prototype.renderBackground=function(t,e){e.height>0&&e.width>0&&(this.renderBackgroundColor(t,e),this.renderBackgroundImage(t,e))},O.prototype.renderBackgroundColor=function(t,e){var n=t.css("backgroundColor");this.isTransparent(n)||this.rectangle(e.left,e.top,e.width,e.height,t.css("backgroundColor"))},O.prototype.renderBorders=function(t){t.forEach(this.renderBorder,this)},O.prototype.renderBorder=function(t){this.isTransparent(t.color)||null===t.args||this.drawShape(t.args,t.color)},O.prototype.renderBackgroundImage=function(){},O.prototype.isTransparent=function(t){return!t||"transparent"===t||"rgba(0, 0, 0, 0)"===t},O.prototype.clip=N(),O.prototype.rectangle=N(),O.prototype.shape=N(),S.prototype.testRangeBounds=function(){var t,n,o,r,i=!1;return e.createRange&&(t=e.createRange(),t.getBoundingClientRect&&(n=e.createElement("boundtest"),n.style.height="123px",n.style.display="block",e.body.appendChild(n),t.selectNode(n),o=t.getBoundingClientRect(),r=o.height,123===r&&(i=!0),e.body.removeChild(n))),i},M.prototype=Object.create(O.prototype),M.prototype.setFillStyle=function(t){return this.ctx.fillStyle=t,this.ctx},M.prototype.rectangle=function(t,e,n,o,r){this.setFillStyle(r).fillRect(t,e,n,o)},M.prototype.drawShape=function(t,e){this.shape(t),this.setFillStyle(e).fill()},M.prototype.clip=function(t,e,n){this.ctx.save(),this.shape(t).clip(),e.call(n),this.ctx.restore()},M.prototype.shape=function(t){return this.ctx.beginPath(),t.forEach(function(t,e){this.ctx[0===e?"moveTo":t[0]+"To"].apply(this.ctx,t.slice(1))},this),this.ctx.closePath(),this.ctx},M.prototype.font=function(t,e,n,o,r,i){this.setFillStyle(t).font=[e,n,o,r,i].join(" ")},M.prototype.setOpacity=function(t){this.ctx.globalAlpha=t},M.prototype.text=function(t,e,n){this.ctx.fillText(t,e,n)}})(window,document);
\ No newline at end of file
+(function(t,e,n){function o(){return Math.max(Math.max(e.body.scrollWidth,e.documentElement.scrollWidth),Math.max(e.body.offsetWidth,e.documentElement.offsetWidth),Math.max(e.body.clientWidth,e.documentElement.clientWidth))}function i(){return Math.max(Math.max(e.body.scrollHeight,e.documentElement.scrollHeight),Math.max(e.body.offsetHeight,e.documentElement.offsetHeight),Math.max(e.body.clientHeight,e.documentElement.clientHeight))}function r(t,e,n){var o=t.documentElement.cloneNode(!0),i=t.createElement("iframe");i.style.display="hidden",i.style.position="absolute",i.style.width=e+"px",i.style.height=n+"px",t.body.appendChild(i);var r=i.contentWindow.document;return r.replaceChild(r.adoptNode(o),r.documentElement),i}function s(t,e,n,o,i){this.renderer=e,this.options=i,this.range=null,this.support=n,this.stack=new _(!0,1,t.ownerDocument,null);var r=new P(t,null);r.visibile=r.isElementVisible(),this.nodes=[r].concat(this.getChildren(r)).filter(function(t){return t.visible=t.isElementVisible()}),this.images=o.fetch(this.nodes.filter(C)),this.createStackingContexts(),this.sortStackingContexts(this.stack),this.images.ready.then(N(function(){W("Images loaded, starting parsing"),this.parse(this.stack),i.onrendered(e.canvas)},this))}function a(t){return 0>t.cssInt("zIndex")}function c(t){return t.cssInt("zIndex")>0}function h(t){return 0===t.cssInt("zIndex")}function u(t){return-1!==["inline","inline-block","inline-table"].indexOf(t.css("display"))}function p(t){return t instanceof _}function d(t){return t.node.data.trim().length>0}function l(t){return/^(normal|none|0px)$/.test(t.parent.css("letterSpacing"))}function f(t,e,n,o,i,r,s){e[0]>0||e[1]>0?(t.push(["line",o[0].start.x,o[0].start.y]),o[0].curveTo(t),o[1].curveTo(t)):t.push(["line",r,s]),(n[0]>0||n[1]>0)&&t.push(["line",i[0].start.x,i[0].start.y])}function g(t){return["TopLeft","TopRight","BottomRight","BottomLeft"].map(function(e){var n=t.css("border"+e+"Radius"),o=n.split(" ");return 1>=o.length&&(o[1]=o[0]),o.map(m)})}function m(t){return parseInt(t,10)}function y(t,e,n,o){var i=4*((Math.sqrt(2)-1)/3),r=n*i,s=o*i,a=t+n,c=e+o;return{topLeft:w({x:t,y:c},{x:t,y:c-s},{x:a-r,y:e},{x:a,y:e}),topRight:w({x:t,y:e},{x:t+r,y:e},{x:a,y:c-s},{x:a,y:c}),bottomRight:w({x:a,y:e},{x:a,y:e+s},{x:t+r,y:c},{x:t,y:c}),bottomLeft:w({x:a,y:c},{x:a-r,y:c},{x:t,y:e+s},{x:t,y:e})}}function b(t,e,n){var o=t.left,i=t.top,r=t.width,s=t.height,a=e[0][0],c=e[0][1],h=e[1][0],u=e[1][1],p=e[2][0],d=e[2][1],l=e[3][0],f=e[3][1],g=r-h,m=s-d,b=r-p,w=s-f;return{topLeftOuter:y(o,i,a,c).topLeft.subdivide(.5),topLeftInner:y(o+n[3].width,i+n[0].width,Math.max(0,a-n[3].width),Math.max(0,c-n[0].width)).topLeft.subdivide(.5),topRightOuter:y(o+g,i,h,u).topRight.subdivide(.5),topRightInner:y(o+Math.min(g,r+n[3].width),i+n[0].width,g>r+n[3].width?0:h-n[3].width,u-n[0].width).topRight.subdivide(.5),bottomRightOuter:y(o+b,i+m,p,d).bottomRight.subdivide(.5),bottomRightInner:y(o+Math.min(b,r+n[3].width),i+Math.min(m,s+n[0].width),Math.max(0,p-n[1].width),Math.max(0,d-n[2].width)).bottomRight.subdivide(.5),bottomLeftOuter:y(o,i+w,l,f).bottomLeft.subdivide(.5),bottomLeftInner:y(o+n[3].width,i+w,Math.max(0,l-n[3].width),Math.max(0,f-n[2].width)).bottomLeft.subdivide(.5)}}function w(t,e,n,o){var i=function(t,e,n){return{x:t.x+(e.x-t.x)*n,y:t.y+(e.y-t.y)*n}};return{start:t,startControl:e,endControl:n,end:o,subdivide:function(r){var s=i(t,e,r),a=i(e,n,r),c=i(n,o,r),h=i(s,a,r),u=i(a,c,r),p=i(h,u,r);return[w(t,s,h,p),w(p,u,c,o)]},curveTo:function(t){t.push(["bezierCurve",e.x,e.y,n.x,n.y,o.x,o.y])},curveToReversed:function(o){o.push(["bezierCurve",n.x,n.y,e.x,e.y,t.x,t.y])}}}function x(t,e,n,o,i,r,s){var a=[];return e[0]>0||e[1]>0?(a.push(["line",o[1].start.x,o[1].start.y]),o[1].curveTo(a)):a.push(["line",t.c1[0],t.c1[1]]),n[0]>0||n[1]>0?(a.push(["line",r[0].start.x,r[0].start.y]),r[0].curveTo(a),a.push(["line",s[0].end.x,s[0].end.y]),s[0].curveToReversed(a)):(a.push(["line",t.c2[0],t.c2[1]]),a.push(["line",t.c3[0],t.c3[1]])),e[0]>0||e[1]>0?(a.push(["line",i[1].end.x,i[1].end.y]),i[1].curveToReversed(a)):a.push(["line",t.c4[0],t.c4[1]]),a}function v(t){return t.node.nodeType!==Node.ELEMENT_NODE||-1===["SCRIPT","HEAD","TITLE","OBJECT","BR"].indexOf(t.node.nodeName)}function k(t){return[].concat.apply([],t)}function R(t){return t.nodeType===Node.TEXT_NODE||t.nodeType===Node.ELEMENT_NODE}function I(t){var e=t.css("position"),n="absolute"===e||"relative"===e?t.css("zIndex"):"auto";return"auto"!==n}function B(t){return"static"!==t.css("position")}function E(t){return"none"!==t.css("float")}function T(t){var e=this;return function(){return!t.apply(e,arguments)}}function C(t){return t.node.nodeType===Node.ELEMENT_NODE}function O(t){return t.node.nodeType===Node.TEXT_NODE}function S(t,e){return t.cssInt("zIndex")-e.cssInt("zIndex")}function L(t){return 1>t.css("opacity")}function N(t,e){return function(){return t.apply(e,arguments)}}function M(t,e){this.src=t,this.image=new Image;var n=this.image;this.promise=new Promise(function(o,i){n.onload=o,n.onerror=i,e&&(n.crossOrigin="anonymous"),n.src=t})}function D(e,n){this.link=null,this.options=e,this.support=n,this.origin=t.location.protocol+t.location.host}function W(){t.html2canvas.logging&&t.console&&t.console.log&&t.console.log.apply(t.console,[Date.now()-t.html2canvas.start+"ms","html2canvas:"].concat([].slice.call(arguments,0)))}function P(t,e){this.node=t,this.parent=e,this.stack=null,this.bounds=null,this.visible=null,this.computedStyles=null,this.styles={},this.backgroundImages=null}function z(t){return-1!==(""+t).indexOf("%")}function F(t,e,n){this.width=t,this.height=e,this.images=n}function H(t,n){F.apply(this,arguments),this.canvas=e.createElement("canvas"),this.canvas.width=t,this.canvas.height=n,this.ctx=this.canvas.getContext("2d"),this.ctx.textBaseline="bottom"}function _(t,e,n,o){P.call(this,n,o),this.ownStacking=t,this.contexts=[],this.children=[],this.opacity=(this.parent?this.parent.stack.opacity:1)*e}function V(){this.rangeBounds=this.testRangeBounds(),this.cors=this.testCORS()}function j(t,e){P.call(this,t,e)}function U(t,e,o){return t.length>0?e+o.toUpperCase():n}t.html2canvas=function(a,c){var h=r(e,t.innerWidth,t.innerHeight),u=h.contentWindow;a===n?e.body:a[0];var p=u.document.documentElement,d=new V,l=new D(c,d);c=c||{},c.logging&&(t.html2canvas.logging=!0,t.html2canvas.start=Date.now());var f=new H(o(),i(),l),g=new s(p,f,d,l,c);t.console.log(g)},s.prototype.getChildren=function(t){return k([].filter.call(t.node.childNodes,R).map(function(e){var n=[e.nodeType===Node.TEXT_NODE?new j(e,t):new P(e,t)].filter(v);return e.nodeType===Node.ELEMENT_NODE&&n.length?n[0].isElementVisible()?n.concat(this.getChildren(n[0])):[]:n},this))},s.prototype.newStackingContext=function(t,e){var n=new _(e,t.cssFloat("opacity"),t.node,t.parent);n.visible=t.visible;var o=n.getParentStack(this);o.contexts.push(n),t.stack=n},s.prototype.createStackingContexts=function(){this.nodes.forEach(function(t){C(t)&&(this.isRootElement(t)||L(t)||I(t)||this.isBodyWithTransparentRoot(t))?this.newStackingContext(t,!0):C(t)&&B(t)?this.newStackingContext(t,!1):t.assignStack(t.parent.stack)},this)},s.prototype.isBodyWithTransparentRoot=function(t){return"BODY"===t.node.nodeName&&this.renderer.isTransparent(t.parent.css("backgroundColor"))},s.prototype.isRootElement=function(t){return"HTML"===t.node.nodeName},s.prototype.sortStackingContexts=function(t){t.contexts.sort(S),t.contexts.forEach(this.sortStackingContexts,this)},s.prototype.parseBounds=function(t){return t.bounds=this.getBounds(t.node)},s.prototype.getBounds=function(t){if(t.getBoundingClientRect){var e=t.getBoundingClientRect();return{top:e.top,bottom:e.bottom||e.top+e.height,left:e.left,width:t.offsetWidth,height:t.offsetHeight}}return{}},s.prototype.parseTextBounds=function(t){return function(e,n,o){if("none"!==t.parent.css("textDecoration")||0!==e.trim().length){var i=o.slice(0,n).join("").length;if(this.support.rangeBounds)return this.getRangeBounds(t.node,i,e.length);if(t.node&&"string"==typeof t.node.data){var r=t.node.splitText(e.length),s=this.getWrapperBounds(t.node);return t.node=r,s}}}},s.prototype.getWrapperBounds=function(t){var e=t.ownerDocument.createElement("wrapper"),n=t.parentNode,o=t.cloneNode(!0);e.appendChild(t.cloneNode(!0)),n.replaceChild(e,t);var i=this.getBounds(e);return n.replaceChild(o,e),i},s.prototype.getRangeBounds=function(t,e,n){var o=this.range||(this.range=t.ownerDocument.createRange());return o.setStart(t,e),o.setEnd(t,e+n),o.getBoundingClientRect()},s.prototype.parse=function(e){var n=e.contexts.filter(a),o=e.children.filter(C),i=o.filter(T(E)),r=i.filter(T(B)).filter(T(u)),s=o.filter(T(B)).filter(E),l=i.filter(T(B)).filter(u),f=e.contexts.concat(i.filter(B)).filter(h),g=e.children.filter(O).filter(d),m=e.contexts.filter(c),y=[];n.concat(r).concat(s).concat(l).concat(f).concat(g).concat(m).forEach(function(e){if(this.paint(e),-1!==y.indexOf(e.node))throw t.console.log(e,e.node),Error("rendering twice");y.push(e.node),p(e)&&this.parse(e)},this)},s.prototype.paint=function(t){O(t)?this.paintText(t):this.paintNode(t)},s.prototype.paintNode=function(t){p(t)&&this.renderer.setOpacity(t.opacity);var e=this.parseBounds(t),n=this.parseBorders(t);this.renderer.clip(n.clip,function(){this.renderer.renderBackground(t,e)},this),this.renderer.renderBorders(n.borders)},s.prototype.paintText=function(t){t.applyTextTransform();var e=t.node.data.split(!this.options.letterRendering||l(t)?/(\b| )/:""),n=t.parent.fontWeight(),o=t.parent.css("fontSize"),i=t.parent.css("fontFamily");this.renderer.font(t.parent.css("color"),t.parent.css("fontStyle"),t.parent.css("fontVariant"),n,o,i),e.map(this.parseTextBounds(t),this).forEach(function(t,n){t&&this.renderer.text(e[n],t.left,t.bottom)},this)},s.prototype.parseBorders=function(t){var e=t.bounds,n=g(t),o=["Top","Right","Bottom","Left"].map(function(e){return{width:t.cssInt("border"+e+"Width"),color:t.css("border"+e+"Color"),args:null}}),i=b(e,n,o);return{clip:this.parseBackgroundClip(t,i,o,n,e),borders:o.map(function(t,r){if(t.width>0){var s=e.left,a=e.top,c=e.width,h=e.height-o[2].width;switch(r){case 0:h=o[0].width,t.args=x({c1:[s,a],c2:[s+c,a],c3:[s+c-o[1].width,a+h],c4:[s+o[3].width,a+h]},n[0],n[1],i.topLeftOuter,i.topLeftInner,i.topRightOuter,i.topRightInner);break;case 1:s=e.left+e.width-o[1].width,c=o[1].width,t.args=x({c1:[s+c,a],c2:[s+c,a+h+o[2].width],c3:[s,a+h],c4:[s,a+o[0].width]},n[1],n[2],i.topRightOuter,i.topRightInner,i.bottomRightOuter,i.bottomRightInner);break;case 2:a=a+e.height-o[2].width,h=o[2].width,t.args=x({c1:[s+c,a+h],c2:[s,a+h],c3:[s+o[3].width,a],c4:[s+c-o[3].width,a]},n[2],n[3],i.bottomRightOuter,i.bottomRightInner,i.bottomLeftOuter,i.bottomLeftInner);break;case 3:c=o[3].width,t.args=x({c1:[s,a+h+o[2].width],c2:[s,a],c3:[s+c,a+o[0].width],c4:[s+c,a+h]},n[3],n[0],i.bottomLeftOuter,i.bottomLeftInner,i.topLeftOuter,i.topLeftInner)}}return t})}},s.prototype.parseBackgroundClip=function(t,e,n,o,i){var r=t.css("backgroundClip"),s=[];switch(r){case"content-box":case"padding-box":f(s,o[0],o[1],e.topLeftInner,e.topRightInner,i.left+n[3].width,i.top+n[0].width),f(s,o[1],o[2],e.topRightInner,e.bottomRightInner,i.left+i.width-n[1].width,i.top+n[0].width),f(s,o[2],o[3],e.bottomRightInner,e.bottomLeftInner,i.left+i.width-n[1].width,i.top+i.height-n[2].width),f(s,o[3],o[0],e.bottomLeftInner,e.topLeftInner,i.left+n[3].width,i.top+i.height-n[2].width);break;default:f(s,o[0],o[1],e.topLeftOuter,e.topRightOuter,i.left,i.top),f(s,o[1],o[2],e.topRightOuter,e.bottomRightOuter,i.left+i.width,i.top),f(s,o[2],o[3],e.bottomRightOuter,e.bottomLeftOuter,i.left+i.width,i.top+i.height),f(s,o[3],o[0],e.bottomLeftOuter,e.topLeftOuter,i.left,i.top+i.height)}return s},D.prototype.findImages=function(t,e){var n=e.parseBackgroundImages(),o=n.filter(this.isImageBackground).map(this.getBackgroundUrl).filter(this.imageExists(t)).map(this.loadImage,this);return t.concat(o)},D.prototype.getBackgroundUrl=function(t){return t.args[0]},D.prototype.isImageBackground=function(t){return"url"===t.method},D.prototype.loadImage=function(t){return t.match(/data:image\/.*;base64,/i)?new M(t.replace(/url\(['"]{0,}|['"]{0,}\)$/gi,""),!1):this.isSameOrigin(t)||this.options.allowTaint===!0?new M(t,!1):this.support.cors&&!this.options.allowTaint&&this.options.useCORS?new M(t,!0):this.options.proxy?new ProxyImageContainer(t):new DummyImageContainer(t)},D.prototype.imageExists=function(t){return function(e){return!t.some(function(t){return t.src!==e.src})}},D.prototype.isSameOrigin=function(t){var n=this.link||(this.link=e.createElement("a"));n.href=t,n.href=n.href;var o=n.protocol+n.host;return o===this.origin},D.prototype.getPromise=function(t){return t.promise},D.prototype.get=function(t){var e=null;return this.images.some(function(n){return(e=n).src===t})?e:null},D.prototype.fetch=function(t){return this.images=t.reduce(N(this.findImages,this),[]),this.ready=Promise.all(this.images.map(this.getPromise)),this},P.prototype.assignStack=function(t){this.stack=t,t.children.push(this)},P.prototype.isElementVisible=function(){return this.node.nodeType===Node.TEXT_NODE?this.parent.visible:"none"!==this.css("display")&&"hidden"!==this.css("visibility")&&!this.node.hasAttribute("data-html2canvas-ignore")},P.prototype.css=function(t){return this.computedStyles||(this.computedStyles=this.node.ownerDocument.defaultView.getComputedStyle(this.node,null)),this.styles[t]||(this.styles[t]=this.computedStyles[t])},P.prototype.cssInt=function(t){var e=parseInt(this.css(t),10);return Number.isNaN(e)?0:e},P.prototype.cssFloat=function(t){var e=parseFloat(this.css(t));return Number.isNaN(e)?0:e},P.prototype.fontWeight=function(){var t=this.css("fontWeight");switch(parseInt(t,10)){case 401:t="bold";break;case 400:t="normal"}return t},P.prototype.parseBackgroundImages=function(){var t,e,o,i,r,s,a,c=" \r\n ",h=[],u=0,p=0,d=function(){t&&('"'===e.substr(0,1)&&(e=e.substr(1,e.length-2)),e&&a.push(e),"-"===t.substr(0,1)&&(i=t.indexOf("-",1)+1)>0&&(o=t.substr(0,i),t=t.substr(i)),h.push({prefix:o,method:t.toLowerCase(),value:r,args:a,image:null})),a=[],t=o=e=r=""};return a=[],t=o=e=r="",this.css("backgroundImage").split("").forEach(function(o){if(!(0===u&&c.indexOf(o)>-1)){switch(o){case'"':s?s===o&&(s=null):s=o;break;case"(":if(s)break;if(0===u)return u=1,r+=o,n;p++;break;case")":if(s)break;if(1===u){if(0===p)return u=0,r+=o,d(),n;p--}break;case",":if(s)break;if(0===u)return d(),n;if(1===u&&0===p&&!t.match(/^url$/i))return a.push(e),e="",r+=o,n}r+=o,0===u?t+=o:e+=o}}),d(),this.backgroundImages||(this.backgroundImages=h)},P.prototype.cssList=function(t,e){var n=(this.css(t)||"").split(",");return n=n[e||0]||n[0]||"auto",n=n.trim().split(" "),1===n.length&&(n=[n[0],n[0]]),n},P.prototype.parseBackgroundSize=function(t,e,n){var o,i,r=this.cssList("backgroundSize",n);if(z(r[0]))o=t.width*parseFloat(r[0])/100;else{if(/contain|cover/.test(r[0])){var s=t.width/t.height,a=e.width/e.height;return a>s^"contain"===r[0]?{width:t.height*a,height:t.height}:{width:t.width,height:t.width/a}}o=parseInt(r[0],10)}return i="auto"===r[0]&&"auto"===r[1]?e.height:"auto"===r[1]?o/e.width*e.height:z(r[1])?t.height*parseFloat(r[1])/100:parseInt(r[1],10),"auto"===r[0]&&(o=i/e.height*e.width),{width:o,height:i}},P.prototype.parseBackgroundPosition=function(t,e,n,o){var i,r,s=this.cssList("backgroundPosition",n);return i=z(s[0])?(t.width-(o||e).width)*(parseFloat(s[0])/100):parseInt(s[0],10),r="auto"===s[1]?i/e.width*e.height:z(s[1])?(t.height-(o||e).height)*parseFloat(s[1])/100:parseInt(s[1],10),"auto"===s[0]&&(i=r/e.height*e.width),{left:i,top:r}},P.prototype.parseBackgroundRepeat=function(t){return this.cssList("backgroundRepeat",t)[0]},F.prototype.renderBackground=function(t,e){e.height>0&&e.width>0&&(this.renderBackgroundColor(t,e),this.renderBackgroundImage(t,e))},F.prototype.renderBackgroundColor=function(t,e){var n=t.css("backgroundColor");this.isTransparent(n)||this.rectangle(e.left,e.top,e.width,e.height,t.css("backgroundColor"))},F.prototype.renderBorders=function(t){t.forEach(this.renderBorder,this)},F.prototype.renderBorder=function(t){this.isTransparent(t.color)||null===t.args||this.drawShape(t.args,t.color)},F.prototype.renderBackgroundImage=function(t,e){var n=t.parseBackgroundImages();n.reverse().forEach(function(n,o){if("url"===n.method){var i=this.images.get(n.args[0]);i?this.renderBackgroundRepeating(t,e,i,o):W("Error loading background-image",n.args[0])}},this)},F.prototype.renderBackgroundRepeating=function(t,e,n,o){var i=t.parseBackgroundSize(e,n.image,o),r=t.parseBackgroundPosition(e,n.image,o,i),s=t.parseBackgroundRepeat(o);switch(s){case"repeat-x":case"repeat no-repeat":this.backgroundRepeatShape(n,r,e,e.left,e.top+r.top,99999,n.image.height);break;case"repeat-y":case"no-repeat repeat":this.backgroundRepeatShape(n,r,e,e.left+r.left,e.top,n.image.width,99999);break;case"no-repeat":this.backgroundRepeatShape(n,r,e,e.left+r.left,e.top+r.top,n.image.width,n.image.height);break;default:this.renderBackgroundRepeat(n,r,{top:e.top,left:e.left})}},F.prototype.isTransparent=function(t){return!t||"transparent"===t||"rgba(0, 0, 0, 0)"===t},H.prototype=Object.create(F.prototype),H.prototype.setFillStyle=function(t){return this.ctx.fillStyle=t,this.ctx},H.prototype.rectangle=function(t,e,n,o,i){this.setFillStyle(i).fillRect(t,e,n,o)},H.prototype.drawShape=function(t,e){this.shape(t),this.setFillStyle(e).fill()},H.prototype.clip=function(t,e,n){this.ctx.save(),this.shape(t).clip(),e.call(n),this.ctx.restore()},H.prototype.shape=function(t){return this.ctx.beginPath(),t.forEach(function(t,e){this.ctx[0===e?"moveTo":t[0]+"To"].apply(this.ctx,t.slice(1))},this),this.ctx.closePath(),this.ctx},H.prototype.font=function(t,e,n,o,i,r){this.setFillStyle(t).font=[e,n,o,i,r].join(" ")},H.prototype.setOpacity=function(t){this.ctx.globalAlpha=t},H.prototype.text=function(t,e,n){this.ctx.fillText(t,e,n)},H.prototype.backgroundRepeatShape=function(t,e,n,o,i,r,s){var a=[["line",Math.round(o),Math.round(i)],["line",Math.round(o+r),Math.round(i)],["line",Math.round(o+r),Math.round(s+i)],["line",Math.round(o),Math.round(s+i)]];console.log(a),this.clip(a,function(){this.renderBackgroundRepeat(t,e,n)},this)},H.prototype.renderBackgroundRepeat=function(t,e,n){var o=Math.round(n.left+e.left),i=Math.round(n.top+e.top);this.setFillStyle(this.ctx.createPattern(t.image,"repeat")),this.ctx.translate(o,i),this.ctx.fill(),this.ctx.translate(-o,-i)},_.prototype=Object.create(P.prototype),_.prototype.getParentStack=function(t){var e=this.parent?this.parent.stack:null;return e?e.ownStacking?e:e.getParentStack(t):t.stack},V.prototype.testRangeBounds=function(){var t,n,o,i,r=!1;return e.createRange&&(t=e.createRange(),t.getBoundingClientRect&&(n=e.createElement("boundtest"),n.style.height="123px",n.style.display="block",e.body.appendChild(n),t.selectNode(n),o=t.getBoundingClientRect(),i=o.height,123===i&&(r=!0),e.body.removeChild(n))),r},V.prototype.testCORS=function(){return(new Image).crossOrigin!==n},j.prototype=Object.create(P.prototype),j.prototype.applyTextTransform=function(){this.node.data=this.transform(this.parent.css("textTransform"))},j.prototype.transform=function(t){var e=this.node.data;switch(t){case"lowercase":return e.toLowerCase();case"capitalize":return e.replace(/(^|\s|:|-|\(|\))([a-z])/g,U);case"uppercase":return e.toUpperCase();default:return e}}})(window,document);
\ No newline at end of file
diff --git a/src/core.js b/src/core.js
index 6defe60..42d211b 100644
--- a/src/core.js
+++ b/src/core.js
@@ -3,9 +3,16 @@ window.html2canvas = function(nodeList, options) {
var clonedWindow = container.contentWindow;
var element = (nodeList === undefined) ? document.body : nodeList[0];
var node = clonedWindow.document.documentElement;
+ var support = new Support();
+ var imageLoader = new ImageLoader(options, support);
+ options = options || {};
+ if (options.logging) {
+ window.html2canvas.logging = true;
+ window.html2canvas.start = Date.now();
+ }
- var renderer = new CanvasRenderer(documentWidth(), documentHeight());
- var parser = new NodeParser(node, renderer, options || {});
+ var renderer = new CanvasRenderer(documentWidth(), documentHeight(), imageLoader);
+ var parser = new NodeParser(node, renderer, support, imageLoader, options);
window.console.log(parser);
};
@@ -43,21 +50,22 @@ function createWindowClone(ownerDocument, width, height) {
return container;
}
-function NodeParser(element, renderer, options) {
+function NodeParser(element, renderer, support, imageLoader, options) {
this.renderer = renderer;
this.options = options;
- this.support = new Support();
this.range = null;
+ this.support = support;
this.stack = new StackingContext(true, 1, element.ownerDocument, null);
var parent = new NodeContainer(element, null);
parent.visibile = parent.isElementVisible();
this.nodes = [parent].concat(this.getChildren(parent)).filter(function(container) {
return container.visible = container.isElementVisible();
});
- this.images = new ImageLoader(this.nodes.filter(isElement), options, this.support);
+ this.images = imageLoader.fetch(this.nodes.filter(isElement));
this.createStackingContexts();
this.sortStackingContexts(this.stack);
this.images.ready.then(bind(function() {
+ log("Images loaded, starting parsing");
this.parse(this.stack);
options.onrendered(renderer.canvas);
}, this));
diff --git a/src/imageloader.js b/src/imageloader.js
index b0a2ad4..0d6e4fe 100644
--- a/src/imageloader.js
+++ b/src/imageloader.js
@@ -1,10 +1,8 @@
-function ImageLoader(nodes, options, support) {
+function ImageLoader(options, support) {
this.link = null;
this.options = options;
this.support = support;
this.origin = window.location.protocol + window.location.host;
- this.images = nodes.reduce(bind(this.findImages, this), []);
- this.ready = Promise.all(this.images.map(this.getPromise));
}
ImageLoader.prototype.findImages = function(images, container) {
@@ -54,3 +52,16 @@ ImageLoader.prototype.isSameOrigin = function(url) {
ImageLoader.prototype.getPromise = function(container) {
return container.promise;
};
+
+ImageLoader.prototype.get = function(src) {
+ var found = null;
+ return this.images.some(function(img) {
+ return (found = img).src === src;
+ }) ? found : null;
+};
+
+ImageLoader.prototype.fetch = function(nodes) {
+ this.images = nodes.reduce(bind(this.findImages, this), []);
+ this.ready = Promise.all(this.images.map(this.getPromise));
+ return this;
+};
diff --git a/src/log.js b/src/log.js
new file mode 100644
index 0000000..0fcf485
--- /dev/null
+++ b/src/log.js
@@ -0,0 +1,5 @@
+function log() {
+ if (window.html2canvas.logging && window.console && window.console.log) {
+ window.console.log.apply(window.console, [(Date.now() - window.html2canvas.start) + "ms", "html2canvas:"].concat([].slice.call(arguments, 0)));
+ }
+}
diff --git a/src/nodecontainer.js b/src/nodecontainer.js
index 8ec9241..a4fed48 100644
--- a/src/nodecontainer.js
+++ b/src/nodecontainer.js
@@ -146,3 +146,76 @@ NodeContainer.prototype.parseBackgroundImages = function() {
return this.backgroundImages || (this.backgroundImages = results);
};
+
+NodeContainer.prototype.cssList = function(property, index) {
+ var value = (this.css(property) || '').split(',');
+ value = value[index || 0] || value[0] || 'auto';
+ value = value.trim().split(' ');
+ if (value.length === 1) {
+ value = [value[0], value[0]];
+ }
+ return value;
+};
+
+NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) {
+ var size = this.cssList("backgroundSize", index);
+ var width, height;
+
+ if (isPercentage(size[0])) {
+ width = bounds.width * parseFloat(size[0]) / 100;
+ } else if (/contain|cover/.test(size[0])) {
+ var targetRatio = bounds.width / bounds.height, currentRatio = image.width / image.height;
+ return (targetRatio < currentRatio ^ size[0] === 'contain') ? {width: bounds.height * currentRatio, height: bounds.height} : {width: bounds.width, height: bounds.width / currentRatio};
+ } else {
+ width = parseInt(size[0], 10);
+ }
+
+ if (size[0] === 'auto' && size[1] === 'auto') {
+ height = image.height;
+ } else if (size[1] === 'auto') {
+ height = width / image.width * image.height;
+ } else if (isPercentage(size[1])) {
+ height = bounds.height * parseFloat(size[1]) / 100;
+ } else {
+ height = parseInt(size[1], 10);
+ }
+
+ if (size[0] === 'auto') {
+ width = height / image.height * image.width;
+ }
+
+ return {width: width, height: height};
+};
+
+NodeContainer.prototype.parseBackgroundPosition = function(bounds, image, index, backgroundSize) {
+ var position = this.cssList('backgroundPosition', index);
+ var left, top;
+
+ if (isPercentage(position[0])){
+ left = (bounds.width - (backgroundSize || image).width) * (parseFloat(position[0]) / 100);
+ } else {
+ left = parseInt(position[0], 10);
+ }
+
+ if (position[1] === 'auto') {
+ top = left / image.width * image.height;
+ } else if (isPercentage(position[1])){
+ top = (bounds.height - (backgroundSize || image).height) * parseFloat(position[1]) / 100;
+ } else {
+ top = parseInt(position[1], 10);
+ }
+
+ if (position[0] === 'auto') {
+ left = top / image.height * image.width;
+ }
+
+ return {left: left, top: top};
+};
+
+NodeContainer.prototype.parseBackgroundRepeat = function(index) {
+ return this.cssList("backgroundRepeat", index)[0];
+};
+
+function isPercentage(value) {
+ return value.toString().indexOf("%") !== -1;
+}
diff --git a/src/renderer.js b/src/renderer.js
index d188c99..4dbab4e 100644
--- a/src/renderer.js
+++ b/src/renderer.js
@@ -1,8 +1,7 @@
-function Renderer() {}
-function NYI() {
- return function() {
- throw new Error("Render function not implemented");
- };
+function Renderer(width, height, images) {
+ this.width = width;
+ this.height = height;
+ this.images = images;
}
Renderer.prototype.renderBackground = function(container, bounds) {
@@ -15,13 +14,7 @@ Renderer.prototype.renderBackground = function(container, bounds) {
Renderer.prototype.renderBackgroundColor = function(container, bounds) {
var color = container.css("backgroundColor");
if (!this.isTransparent(color)) {
- this.rectangle(
- bounds.left,
- bounds.top,
- bounds.width,
- bounds.height,
- container.css("backgroundColor")
- );
+ this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, container.css("backgroundColor"));
}
};
@@ -36,13 +29,42 @@ Renderer.prototype.renderBorder = function(data) {
};
Renderer.prototype.renderBackgroundImage = function(container, bounds) {
+ var backgroundImages = container.parseBackgroundImages();
+ backgroundImages.reverse().forEach(function(backgroundImage, index) {
+ if (backgroundImage.method === "url") {
+ var image = this.images.get(backgroundImage.args[0]);
+ if (image) {
+ this.renderBackgroundRepeating(container, bounds, image, index);
+ } else {
+ log("Error loading background-image", backgroundImage.args[0]);
+ }
+ }
+ }, this);
+};
+Renderer.prototype.renderBackgroundRepeating = function(container, bounds, imageContainer, index) {
+ var size = container.parseBackgroundSize(bounds, imageContainer.image, index);
+ var position = container.parseBackgroundPosition(bounds, imageContainer.image, index, size);
+ var repeat = container.parseBackgroundRepeat(index);
+// image = resizeImage(image, backgroundSize);
+ switch (repeat) {
+ case "repeat-x":
+ case "repeat no-repeat":
+ this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left, bounds.top + position.top, 99999, imageContainer.image.height);
+ break;
+ case "repeat-y":
+ case "no-repeat repeat":
+ this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left + position.left, bounds.top, imageContainer.image.width, 99999);
+ break;
+ case "no-repeat":
+ this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left + position.left, bounds.top + position.top, imageContainer.image.width, imageContainer.image.height);
+ break;
+ default:
+ this.renderBackgroundRepeat(imageContainer, position, {top: bounds.top, left: bounds.left});
+ break;
+ }
};
Renderer.prototype.isTransparent = function(color) {
return (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)");
};
-
-Renderer.prototype.clip = NYI();
-Renderer.prototype.rectangle = NYI();
-Renderer.prototype.shape = NYI();
diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js
index 8872e79..b71c793 100644
--- a/src/renderers/canvas.js
+++ b/src/renderers/canvas.js
@@ -50,3 +50,24 @@ CanvasRenderer.prototype.setOpacity = function(opacity) {
CanvasRenderer.prototype.text = function(text, left, bottom) {
this.ctx.fillText(text, left, bottom);
};
+
+CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, bounds, left, top, width, height) {
+ var shape = [
+ ["line", Math.round(left), Math.round(top)],
+ ["line", Math.round(left + width), Math.round(top)],
+ ["line", Math.round(left + width), Math.round(height + top)],
+ ["line", Math.round(left), Math.round(height + top)]
+ ];
+ console.log(shape);
+ this.clip(shape, function() {
+ this.renderBackgroundRepeat(imageContainer, backgroundPosition, bounds);
+ }, this);
+};
+
+CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, bounds) {
+ var offsetX = Math.round(bounds.left + backgroundPosition.left), offsetY = Math.round(bounds.top + backgroundPosition.top);
+ this.setFillStyle(this.ctx.createPattern(imageContainer.image, "repeat"));
+ this.ctx.translate(offsetX, offsetY);
+ this.ctx.fill();
+ this.ctx.translate(-offsetX, -offsetY);
+};
diff --git a/tests/assets/jquery.plugin.html2canvas.js b/tests/assets/jquery.plugin.html2canvas.js
index eb764e8..09b1cdc 100644
--- a/tests/assets/jquery.plugin.html2canvas.js
+++ b/tests/assets/jquery.plugin.html2canvas.js
@@ -2,83 +2,83 @@
* jQuery helper plugin for examples and tests
*/
(function( $ ){
- $.fn.html2canvas = function(options) {
- if (options && options.profile && window.console && window.console.profile && window.location.search !== "?selenium2") {
- console.profile();
- }
- var date = new Date(),
- html2obj,
- $message = null,
- timeoutTimer = false,
- timer = date.getTime();
- options = options || {};
-
- options.onrendered = options.onrendered || function( canvas ) {
- var $canvas = $(canvas),
- finishTime = new Date();
-
- if (options && options.profile && window.console && window.console.profileEnd) {
- console.profileEnd();
- }
- $canvas.addClass("html2canvas")
- .css({
- position: 'absolute',
- left: 0,
- top: 0
- }).appendTo(document.body);
-
- if (window.location.search !== "?selenium") {
- $canvas.siblings().toggle();
- $(window).click(function(){
- $canvas.toggle().siblings().toggle();
- throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden"));
- });
- throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms
",4000);
- } else {
- $canvas.css('display', 'none');
- }
- // test if canvas is read-able
- try {
- $canvas[0].toDataURL();
- } catch(e) {
- if ($canvas[0].nodeName.toLowerCase() === "canvas") {
- // TODO, maybe add a bit less offensive way to present this, but still something that can easily be noticed
- alert("Canvas is tainted, unable to read data");
+ $.fn.html2canvas = function(options) {
+ if (options && options.profile && window.console && window.console.profile && window.location.search !== "?selenium2") {
+ console.profile();
+ }
+ var date = new Date(),
+ html2obj,
+ $message = null,
+ timeoutTimer = false,
+ timer = date.getTime();
+ options = options || {};
+
+ options.onrendered = options.onrendered || function( canvas ) {
+ var $canvas = $(canvas),
+ finishTime = new Date();
+
+ if (options && options.profile && window.console && window.console.profileEnd) {
+ console.profileEnd();
+ }
+ $canvas.addClass("html2canvas")
+ .css({
+ position: 'absolute',
+ left: 0,
+ top: 0
+ }).appendTo(document.body);
+
+ if (window.location.search !== "?selenium") {
+ $canvas.siblings().toggle();
+ $(window).click(function(){
+ $canvas.toggle().siblings().toggle();
+ throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden"));
+ });
+ throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms
",4000);
+ } else {
+ $canvas.css('display', 'none');
+ }
+ // test if canvas is read-able
+ try {
+ $canvas[0].toDataURL();
+ } catch(e) {
+ if ($canvas[0].nodeName.toLowerCase() === "canvas") {
+ // TODO, maybe add a bit less offensive way to present this, but still something that can easily be noticed
+ alert("Canvas is tainted, unable to read data");
+ }
+ }
+ };
+
+ html2obj = html2canvas(this, options);
+
+ function throwMessage(msg,duration){
+ window.clearTimeout(timeoutTimer);
+ timeoutTimer = window.setTimeout(function(){
+ $message.fadeOut(function(){
+ $message.remove();
+ $message = null;
+ });
+ },duration || 2000);
+ if ($message)
+ $message.remove();
+ $message = $('