Merge pull request #270 from ssafejava/parseSpeed

Speed parsing by as much as 50% & add async parsing option.
This commit is contained in:
Niklas von Hertzen 2013-09-22 11:32:25 -07:00
commit 4c75d819a4
4 changed files with 563 additions and 306 deletions

View File

@ -1029,7 +1029,7 @@ function h2cRenderContext(width, height) {
}
};
}
_html2canvas.Parse = function (images, options) {
_html2canvas.Parse = function (images, options, cb) {
window.scroll(0,0);
var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default
@ -1041,15 +1041,163 @@ _html2canvas.Parse = function (images, options) {
body = doc.body,
getCSS = Util.getCSS,
pseudoHide = "___html2canvas___pseudoelement",
hidePseudoElements = doc.createElement('style');
hidePseudoElementsStyles = doc.createElement('style');
hidePseudoElements.innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' +
'.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }';
hidePseudoElementsStyles.innerHTML = '.' + pseudoHide +
'-parent:before { content: "" !important; display: none !important; }' +
'.' + pseudoHide + '-parent:after { content: "" !important; display: none !important; }';
body.appendChild(hidePseudoElements);
body.appendChild(hidePseudoElementsStyles);
images = images || {};
init();
function init() {
var background = getCSS(document.documentElement, "backgroundColor"),
transparentBackground = (Util.isTransparent(background) && element === document.body),
stack = renderElement(element, null, false, transparentBackground);
// create pseudo elements in a single pass to prevent synchronous layouts
addPseudoElements(element);
parseChildren(element, stack, function() {
if (transparentBackground) {
background = stack.backgroundColor;
}
removePseudoElements();
Util.log('Done parsing, moving to Render.');
cb({
backgroundColor: background,
stack: stack
});
});
}
// Given a root element, find all pseudo elements below, create elements mocking pseudo element styles
// so we can process them as normal elements, and hide the original pseudo elements so they don't interfere
// with layout.
function addPseudoElements(el) {
// These are done in discrete steps to prevent a relayout loop caused by addClass() invalidating
// layouts & getPseudoElement calling getComputedStyle.
var jobs = [], classes = [];
getPseudoElementClasses();
findPseudoElements(el);
runJobs();
function getPseudoElementClasses(){
var findPsuedoEls = /:before|:after/;
var sheets = document.styleSheets;
for (var i = 0, j = sheets.length; i < j; i++) {
try {
var rules = sheets[i].cssRules;
for (var k = 0, l = rules.length; k < l; k++) {
if(findPsuedoEls.test(rules[k].selectorText)) {
classes.push(rules[k].selectorText);
}
}
}
catch(e) { // will throw security exception for style sheets loaded from external domains
}
}
// Trim off the :after and :before (or ::after and ::before)
for (i = 0, j = classes.length; i < j; i++) {
classes[i] = classes[i].match(/(^[^:]*)/)[1];
}
}
// Using the list of elements we know how pseudo el styles, create fake pseudo elements.
function findPseudoElements(el) {
var els = document.querySelectorAll(classes.join(','));
for(var i = 0, j = els.length; i < j; i++) {
createPseudoElements(els[i]);
}
}
// Create pseudo elements & add them to a job queue.
function createPseudoElements(el) {
var before = getPseudoElement(el, ':before'),
after = getPseudoElement(el, ':after');
if(before) {
jobs.push({type: 'before', pseudo: before, el: el});
}
if (after) {
jobs.push({type: 'after', pseudo: after, el: el});
}
}
// Adds a class to the pseudo's parent to prevent the original before/after from messing
// with layouts.
// Execute the inserts & addClass() calls in a batch to prevent relayouts.
function runJobs() {
// Add Class
jobs.forEach(function(job){
addClass(job.el, pseudoHide + "-parent");
});
// Insert el
jobs.forEach(function(job){
if(job.type === 'before'){
job.el.insertBefore(job.pseudo, job.el.firstChild);
} else {
job.el.appendChild(job.pseudo);
}
});
}
}
// Delete our fake pseudo elements from the DOM. This will remove those actual elements
// and the classes on their parents that hide the actual pseudo elements.
// Note that NodeLists are 'live' collections so you can't use a for loop here. They are
// actually deleted from the NodeList after each iteration.
function removePseudoElements(){
// delete pseudo elements
body.removeChild(hidePseudoElementsStyles);
var pseudos = document.getElementsByClassName(pseudoHide + "-element");
while (pseudos.length) {
pseudos[0].parentNode.removeChild(pseudos[0]);
}
// Remove pseudo hiding classes
var parents = document.getElementsByClassName(pseudoHide + "-parent");
while(parents.length) {
removeClass(parents[0], pseudoHide + "-parent");
}
}
function addClass (el, className) {
if (el.classList) {
el.classList.add(className);
} else {
el.className = el.className + " " + className;
}
}
function removeClass (el, className) {
if (el.classList) {
el.classList.remove(className);
} else {
el.className = el.className.replace(className, "").trim();
}
}
function hasClass (el, className) {
return el.className.indexOf(className) > -1;
}
// Note that this doesn't work in < IE8, but we don't support that anyhow
function nodeListToArray (nodeList) {
return Array.prototype.slice.call(nodeList);
}
function documentWidth () {
return Math.max(
Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
@ -1367,6 +1515,13 @@ _html2canvas.Parse = function (images, options) {
}
}
function h2czContext(zindex) {
return {
zindex: zindex,
children: []
};
}
function renderImage(ctx, element, image, bounds, borders) {
var paddingLeft = getCSSInt(element, 'paddingLeft'),
@ -1403,69 +1558,67 @@ _html2canvas.Parse = function (images, options) {
});
}
var getCurvePoints = (function(kappa) {
return function(x, y, r1, r2) {
var ox = (r1) * kappa, // control point offset horizontal
oy = (r2) * kappa, // control point offset vertical
xm = x + r1, // x-middle
ym = y + r2; // y-middle
return {
topLeft: bezierCurve({
x:x,
y:ym
}, {
x:x,
y:ym - oy
}, {
x:xm - ox,
y:y
}, {
x:xm,
y:y
}),
topRight: bezierCurve({
x:x,
y:y
}, {
x:x + ox,
y:y
}, {
x:xm,
y:ym - oy
}, {
x:xm,
y:ym
}),
bottomRight: bezierCurve({
x:xm,
y:y
}, {
x:xm,
y:y + oy
}, {
x:x + ox,
y:ym
}, {
x:x,
y:ym
}),
bottomLeft: bezierCurve({
x:xm,
y:ym
}, {
x:xm - ox,
y:ym
}, {
x:x,
y:y + oy
}, {
x:x,
y:y
})
};
function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
var ox = (r1) * kappa, // control point offset horizontal
oy = (r2) * kappa, // control point offset vertical
xm = x + r1, // x-middle
ym = y + r2; // y-middle
return {
topLeft: bezierCurve({
x:x,
y:ym
}, {
x:x,
y:ym - oy
}, {
x:xm - ox,
y:y
}, {
x:xm,
y:y
}),
topRight: bezierCurve({
x:x,
y:y
}, {
x:x + ox,
y:y
}, {
x:xm,
y:ym - oy
}, {
x:xm,
y:ym
}),
bottomRight: bezierCurve({
x:xm,
y:y
}, {
x:xm,
y:y + oy
}, {
x:x + ox,
y:ym
}, {
x:x,
y:ym
}),
bottomLeft: bezierCurve({
x:xm,
y:ym
}, {
x:xm - ox,
y:ym
}, {
x:x,
y:y + oy
}, {
x:x,
y:y
})
};
})(4 * ((Math.sqrt(2) - 1) / 3));
}
function bezierCurve(start, startControl, endControl, end) {
@ -1804,20 +1957,25 @@ _html2canvas.Parse = function (images, options) {
function getPseudoElement(el, which) {
var elStyle = window.getComputedStyle(el, which);
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") {
var parentStyle = window.getComputedStyle(el);
// If no content attribute is present, the pseudo element is hidden,
// or the parent has a content property equal to the content on the pseudo element,
// move along.
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" ||
elStyle.display === "none" || parentStyle.content === elStyle.content) {
return;
}
var content = elStyle.content + '',
first = content.substr( 0, 1 );
//strips quotes
if(first === content.substr( content.length - 1 ) && first.match(/'|"/)) {
content = content.substr( 1, content.length - 2 );
var content = elStyle.content + '';
// Strip inner quotes
if(content[0] === "'" || content[0] === "\"") {
content = content.replace(/(^['"])|(['"]$)/g, '');
}
var isImage = content.substr( 0, 3 ) === 'url',
elps = document.createElement( isImage ? 'img' : 'span' );
elps.className = pseudoHide + "-before " + pseudoHide + "-after";
elps.className = pseudoHide + "-element ";
Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
// Prevent assigning of read only CSS Rules, ex. length, parentRule
@ -1840,31 +1998,6 @@ _html2canvas.Parse = function (images, options) {
return (isNaN(window.parseInt(property, 10)));
}
function injectPseudoElements(el, stack) {
var before = getPseudoElement(el, ':before'),
after = getPseudoElement(el, ':after');
if(!before && !after) {
return;
}
if(before) {
el.className += " " + pseudoHide + "-before";
el.parentNode.insertBefore(before, el);
parseElement(before, stack, true);
el.parentNode.removeChild(before);
el.className = el.className.replace(pseudoHide + "-before", "").trim();
}
if (after) {
el.className += " " + pseudoHide + "-after";
el.appendChild(after);
parseElement(after, stack, true);
el.removeChild(after);
el.className = el.className.replace(pseudoHide + "-after", "").trim();
}
}
function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
var offsetX = Math.round(bounds.left + backgroundPosition.left),
offsetY = Math.round(bounds.top + backgroundPosition.top);
@ -1984,9 +2117,8 @@ _html2canvas.Parse = function (images, options) {
return str.replace("px", "");
}
var transformRegExp = /(matrix)\((.+)\)/;
function getTransform(element, parentStack) {
var transformRegExp = /(matrix)\((.+)\)/;
var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform");
var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px";
@ -2053,7 +2185,7 @@ _html2canvas.Parse = function (images, options) {
return bounds;
}
function renderElement(element, parentStack, pseudoElement, ignoreBackground) {
function renderElement(element, parentStack, ignoreBackground) {
var transform = getTransform(element, parentStack),
bounds = getBounds(element, transform),
image,
@ -2083,10 +2215,6 @@ _html2canvas.Parse = function (images, options) {
renderBorders(ctx, border.args, border.color);
});
if (!pseudoElement) {
injectPseudoElements(element, stack);
}
switch(element.nodeName){
case "IMG":
if ((image = loadImage(element.getAttribute('src')))) {
@ -2127,52 +2255,54 @@ _html2canvas.Parse = function (images, options) {
return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
}
function parseElement (element, stack, pseudoElement) {
function parseElement (element, stack, cb) {
if (!cb) {
cb = function(){};
}
if (isElementVisible(element)) {
stack = renderElement(element, stack, pseudoElement, false) || stack;
stack = renderElement(element, stack, false) || stack;
if (!ignoreElementsRegExp.test(element.nodeName)) {
parseChildren(element, stack, pseudoElement);
return parseChildren(element, stack, cb);
}
}
cb();
}
function parseChildren(element, stack, pseudoElement) {
Util.Children(element).forEach(function(node) {
function parseChildren(element, stack, cb) {
var children = Util.Children(element);
// After all nodes have processed, finished() will call the cb.
// We add one and kick it off so this will still work when children.length === 0.
// Note that unless async is true, this will happen synchronously, just will callbacks.
var jobs = children.length + 1;
finished();
if (options.async) {
children.forEach(function(node) {
// Don't block the page from rendering
setTimeout(function(){ parseNode(node); }, 0);
});
} else {
children.forEach(parseNode);
}
function parseNode(node) {
if (node.nodeType === node.ELEMENT_NODE) {
parseElement(node, stack, pseudoElement);
parseElement(node, stack, finished);
} else if (node.nodeType === node.TEXT_NODE) {
renderText(element, node, stack);
finished();
} else {
finished();
}
}
function finished(el) {
if (--jobs <= 0){
Util.log("finished rendering " + children.length + " children.");
cb();
}
});
}
function init() {
var background = getCSS(document.documentElement, "backgroundColor"),
transparentBackground = (Util.isTransparent(background) && element === document.body),
stack = renderElement(element, null, false, transparentBackground);
parseChildren(element, stack);
if (transparentBackground) {
background = stack.backgroundColor;
}
body.removeChild(hidePseudoElements);
return {
backgroundColor: background,
stack: stack
};
}
return init();
};
function h2czContext(zindex) {
return {
zindex: zindex,
children: []
};
}
_html2canvas.Preload = function( options ) {
var images = {
@ -2678,9 +2808,9 @@ window.html2canvas = function(elements, opts) {
useOverflow: true,
letterRendering: false,
chinese: false,
async: false, // If true, parsing will not block, but if the user scrolls during parse the image can get weird
// render options
width: null,
height: null,
taintTest: true, // do a taint test with all images before applying to canvas
@ -2697,21 +2827,19 @@ window.html2canvas = function(elements, opts) {
return;
}
}
queue = _html2canvas.Parse( images, options );
if (typeof options.onparsed === "function") {
if ( options.onparsed( queue ) === false ) {
return;
_html2canvas.Parse( images, options, function(queue) {
if (typeof options.onparsed === "function") {
if ( options.onparsed( queue ) === false ) {
return;
}
}
}
canvas = _html2canvas.Renderer( queue, options );
if (typeof options.onrendered === "function") {
options.onrendered( canvas );
}
canvas = _html2canvas.Renderer( queue, options );
if (typeof options.onrendered === "function") {
options.onrendered( canvas );
}
});
};
// for pages without images, we still want this to be async, i.e. return methods before executing

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
_html2canvas.Parse = function (images, options) {
_html2canvas.Parse = function (images, options, cb) {
window.scroll(0,0);
var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default
@ -10,15 +10,163 @@ _html2canvas.Parse = function (images, options) {
body = doc.body,
getCSS = Util.getCSS,
pseudoHide = "___html2canvas___pseudoelement",
hidePseudoElements = doc.createElement('style');
hidePseudoElementsStyles = doc.createElement('style');
hidePseudoElements.innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' +
'.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }';
hidePseudoElementsStyles.innerHTML = '.' + pseudoHide +
'-parent:before { content: "" !important; display: none !important; }' +
'.' + pseudoHide + '-parent:after { content: "" !important; display: none !important; }';
body.appendChild(hidePseudoElements);
body.appendChild(hidePseudoElementsStyles);
images = images || {};
init();
function init() {
var background = getCSS(document.documentElement, "backgroundColor"),
transparentBackground = (Util.isTransparent(background) && element === document.body),
stack = renderElement(element, null, false, transparentBackground);
// create pseudo elements in a single pass to prevent synchronous layouts
addPseudoElements(element);
parseChildren(element, stack, function() {
if (transparentBackground) {
background = stack.backgroundColor;
}
removePseudoElements();
Util.log('Done parsing, moving to Render.');
cb({
backgroundColor: background,
stack: stack
});
});
}
// Given a root element, find all pseudo elements below, create elements mocking pseudo element styles
// so we can process them as normal elements, and hide the original pseudo elements so they don't interfere
// with layout.
function addPseudoElements(el) {
// These are done in discrete steps to prevent a relayout loop caused by addClass() invalidating
// layouts & getPseudoElement calling getComputedStyle.
var jobs = [], classes = [];
getPseudoElementClasses();
findPseudoElements(el);
runJobs();
function getPseudoElementClasses(){
var findPsuedoEls = /:before|:after/;
var sheets = document.styleSheets;
for (var i = 0, j = sheets.length; i < j; i++) {
try {
var rules = sheets[i].cssRules;
for (var k = 0, l = rules.length; k < l; k++) {
if(findPsuedoEls.test(rules[k].selectorText)) {
classes.push(rules[k].selectorText);
}
}
}
catch(e) { // will throw security exception for style sheets loaded from external domains
}
}
// Trim off the :after and :before (or ::after and ::before)
for (i = 0, j = classes.length; i < j; i++) {
classes[i] = classes[i].match(/(^[^:]*)/)[1];
}
}
// Using the list of elements we know how pseudo el styles, create fake pseudo elements.
function findPseudoElements(el) {
var els = document.querySelectorAll(classes.join(','));
for(var i = 0, j = els.length; i < j; i++) {
createPseudoElements(els[i]);
}
}
// Create pseudo elements & add them to a job queue.
function createPseudoElements(el) {
var before = getPseudoElement(el, ':before'),
after = getPseudoElement(el, ':after');
if(before) {
jobs.push({type: 'before', pseudo: before, el: el});
}
if (after) {
jobs.push({type: 'after', pseudo: after, el: el});
}
}
// Adds a class to the pseudo's parent to prevent the original before/after from messing
// with layouts.
// Execute the inserts & addClass() calls in a batch to prevent relayouts.
function runJobs() {
// Add Class
jobs.forEach(function(job){
addClass(job.el, pseudoHide + "-parent");
});
// Insert el
jobs.forEach(function(job){
if(job.type === 'before'){
job.el.insertBefore(job.pseudo, job.el.firstChild);
} else {
job.el.appendChild(job.pseudo);
}
});
}
}
// Delete our fake pseudo elements from the DOM. This will remove those actual elements
// and the classes on their parents that hide the actual pseudo elements.
// Note that NodeLists are 'live' collections so you can't use a for loop here. They are
// actually deleted from the NodeList after each iteration.
function removePseudoElements(){
// delete pseudo elements
body.removeChild(hidePseudoElementsStyles);
var pseudos = document.getElementsByClassName(pseudoHide + "-element");
while (pseudos.length) {
pseudos[0].parentNode.removeChild(pseudos[0]);
}
// Remove pseudo hiding classes
var parents = document.getElementsByClassName(pseudoHide + "-parent");
while(parents.length) {
removeClass(parents[0], pseudoHide + "-parent");
}
}
function addClass (el, className) {
if (el.classList) {
el.classList.add(className);
} else {
el.className = el.className + " " + className;
}
}
function removeClass (el, className) {
if (el.classList) {
el.classList.remove(className);
} else {
el.className = el.className.replace(className, "").trim();
}
}
function hasClass (el, className) {
return el.className.indexOf(className) > -1;
}
// Note that this doesn't work in < IE8, but we don't support that anyhow
function nodeListToArray (nodeList) {
return Array.prototype.slice.call(nodeList);
}
function documentWidth () {
return Math.max(
Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
@ -336,6 +484,13 @@ _html2canvas.Parse = function (images, options) {
}
}
function h2czContext(zindex) {
return {
zindex: zindex,
children: []
};
}
function renderImage(ctx, element, image, bounds, borders) {
var paddingLeft = getCSSInt(element, 'paddingLeft'),
@ -372,69 +527,67 @@ _html2canvas.Parse = function (images, options) {
});
}
var getCurvePoints = (function(kappa) {
return function(x, y, r1, r2) {
var ox = (r1) * kappa, // control point offset horizontal
oy = (r2) * kappa, // control point offset vertical
xm = x + r1, // x-middle
ym = y + r2; // y-middle
return {
topLeft: bezierCurve({
x:x,
y:ym
}, {
x:x,
y:ym - oy
}, {
x:xm - ox,
y:y
}, {
x:xm,
y:y
}),
topRight: bezierCurve({
x:x,
y:y
}, {
x:x + ox,
y:y
}, {
x:xm,
y:ym - oy
}, {
x:xm,
y:ym
}),
bottomRight: bezierCurve({
x:xm,
y:y
}, {
x:xm,
y:y + oy
}, {
x:x + ox,
y:ym
}, {
x:x,
y:ym
}),
bottomLeft: bezierCurve({
x:xm,
y:ym
}, {
x:xm - ox,
y:ym
}, {
x:x,
y:y + oy
}, {
x:x,
y:y
})
};
function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
var ox = (r1) * kappa, // control point offset horizontal
oy = (r2) * kappa, // control point offset vertical
xm = x + r1, // x-middle
ym = y + r2; // y-middle
return {
topLeft: bezierCurve({
x:x,
y:ym
}, {
x:x,
y:ym - oy
}, {
x:xm - ox,
y:y
}, {
x:xm,
y:y
}),
topRight: bezierCurve({
x:x,
y:y
}, {
x:x + ox,
y:y
}, {
x:xm,
y:ym - oy
}, {
x:xm,
y:ym
}),
bottomRight: bezierCurve({
x:xm,
y:y
}, {
x:xm,
y:y + oy
}, {
x:x + ox,
y:ym
}, {
x:x,
y:ym
}),
bottomLeft: bezierCurve({
x:xm,
y:ym
}, {
x:xm - ox,
y:ym
}, {
x:x,
y:y + oy
}, {
x:x,
y:y
})
};
})(4 * ((Math.sqrt(2) - 1) / 3));
}
function bezierCurve(start, startControl, endControl, end) {
@ -773,20 +926,25 @@ _html2canvas.Parse = function (images, options) {
function getPseudoElement(el, which) {
var elStyle = window.getComputedStyle(el, which);
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") {
var parentStyle = window.getComputedStyle(el);
// If no content attribute is present, the pseudo element is hidden,
// or the parent has a content property equal to the content on the pseudo element,
// move along.
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" ||
elStyle.display === "none" || parentStyle.content === elStyle.content) {
return;
}
var content = elStyle.content + '',
first = content.substr( 0, 1 );
//strips quotes
if(first === content.substr( content.length - 1 ) && first.match(/'|"/)) {
content = content.substr( 1, content.length - 2 );
var content = elStyle.content + '';
// Strip inner quotes
if(content[0] === "'" || content[0] === "\"") {
content = content.replace(/(^['"])|(['"]$)/g, '');
}
var isImage = content.substr( 0, 3 ) === 'url',
elps = document.createElement( isImage ? 'img' : 'span' );
elps.className = pseudoHide + "-before " + pseudoHide + "-after";
elps.className = pseudoHide + "-element ";
Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
// Prevent assigning of read only CSS Rules, ex. length, parentRule
@ -809,31 +967,6 @@ _html2canvas.Parse = function (images, options) {
return (isNaN(window.parseInt(property, 10)));
}
function injectPseudoElements(el, stack) {
var before = getPseudoElement(el, ':before'),
after = getPseudoElement(el, ':after');
if(!before && !after) {
return;
}
if(before) {
el.className += " " + pseudoHide + "-before";
el.parentNode.insertBefore(before, el);
parseElement(before, stack, true);
el.parentNode.removeChild(before);
el.className = el.className.replace(pseudoHide + "-before", "").trim();
}
if (after) {
el.className += " " + pseudoHide + "-after";
el.appendChild(after);
parseElement(after, stack, true);
el.removeChild(after);
el.className = el.className.replace(pseudoHide + "-after", "").trim();
}
}
function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
var offsetX = Math.round(bounds.left + backgroundPosition.left),
offsetY = Math.round(bounds.top + backgroundPosition.top);
@ -953,9 +1086,8 @@ _html2canvas.Parse = function (images, options) {
return str.replace("px", "");
}
var transformRegExp = /(matrix)\((.+)\)/;
function getTransform(element, parentStack) {
var transformRegExp = /(matrix)\((.+)\)/;
var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform");
var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px";
@ -1022,7 +1154,7 @@ _html2canvas.Parse = function (images, options) {
return bounds;
}
function renderElement(element, parentStack, pseudoElement, ignoreBackground) {
function renderElement(element, parentStack, ignoreBackground) {
var transform = getTransform(element, parentStack),
bounds = getBounds(element, transform),
image,
@ -1052,10 +1184,6 @@ _html2canvas.Parse = function (images, options) {
renderBorders(ctx, border.args, border.color);
});
if (!pseudoElement) {
injectPseudoElements(element, stack);
}
switch(element.nodeName){
case "IMG":
if ((image = loadImage(element.getAttribute('src')))) {
@ -1096,48 +1224,51 @@ _html2canvas.Parse = function (images, options) {
return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
}
function parseElement (element, stack, pseudoElement) {
function parseElement (element, stack, cb) {
if (!cb) {
cb = function(){};
}
if (isElementVisible(element)) {
stack = renderElement(element, stack, pseudoElement, false) || stack;
stack = renderElement(element, stack, false) || stack;
if (!ignoreElementsRegExp.test(element.nodeName)) {
parseChildren(element, stack, pseudoElement);
return parseChildren(element, stack, cb);
}
}
cb();
}
function parseChildren(element, stack, pseudoElement) {
Util.Children(element).forEach(function(node) {
function parseChildren(element, stack, cb) {
var children = Util.Children(element);
// After all nodes have processed, finished() will call the cb.
// We add one and kick it off so this will still work when children.length === 0.
// Note that unless async is true, this will happen synchronously, just will callbacks.
var jobs = children.length + 1;
finished();
if (options.async) {
children.forEach(function(node) {
// Don't block the page from rendering
setTimeout(function(){ parseNode(node); }, 0);
});
} else {
children.forEach(parseNode);
}
function parseNode(node) {
if (node.nodeType === node.ELEMENT_NODE) {
parseElement(node, stack, pseudoElement);
parseElement(node, stack, finished);
} else if (node.nodeType === node.TEXT_NODE) {
renderText(element, node, stack);
finished();
} else {
finished();
}
}
function finished(el) {
if (--jobs <= 0){
Util.log("finished rendering " + children.length + " children.");
cb();
}
});
}
function init() {
var background = getCSS(document.documentElement, "backgroundColor"),
transparentBackground = (Util.isTransparent(background) && element === document.body),
stack = renderElement(element, null, false, transparentBackground);
parseChildren(element, stack);
if (transparentBackground) {
background = stack.backgroundColor;
}
body.removeChild(hidePseudoElements);
return {
backgroundColor: background,
stack: stack
};
}
return init();
};
function h2czContext(zindex) {
return {
zindex: zindex,
children: []
};
}
};

View File

@ -20,9 +20,9 @@ window.html2canvas = function(elements, opts) {
useOverflow: true,
letterRendering: false,
chinese: false,
async: false, // If true, parsing will not block, but if the user scrolls during parse the image can get weird
// render options
width: null,
height: null,
taintTest: true, // do a taint test with all images before applying to canvas
@ -39,21 +39,19 @@ window.html2canvas = function(elements, opts) {
return;
}
}
queue = _html2canvas.Parse( images, options );
if (typeof options.onparsed === "function") {
if ( options.onparsed( queue ) === false ) {
return;
_html2canvas.Parse( images, options, function(queue) {
if (typeof options.onparsed === "function") {
if ( options.onparsed( queue ) === false ) {
return;
}
}
}
canvas = _html2canvas.Renderer( queue, options );
if (typeof options.onrendered === "function") {
options.onrendered( canvas );
}
canvas = _html2canvas.Renderer( queue, options );
if (typeof options.onrendered === "function") {
options.onrendered( canvas );
}
});
};
// for pages without images, we still want this to be async, i.e. return methods before executing