Add support for pseudoelements

This commit is contained in:
Niklas von Hertzen 2014-02-08 16:52:41 +02:00
parent b5891c49b4
commit 5d20493f46
5 changed files with 359 additions and 202 deletions

View File

@ -14,5 +14,5 @@
"jQuery": true "jQuery": true
}, },
"predef": ["NodeParser", "NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise", "predef": ["NodeParser", "NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise",
"ImageContainer", "ProxyImageContainer", "DummyImageContainer", "Font", "FontMetrics", "GradientContainer", "LinearGradientContainer", "WebkitGradientContainer", "log", "smallImage"] "ImageContainer", "ProxyImageContainer", "DummyImageContainer", "Font", "FontMetrics", "GradientContainer", "LinearGradientContainer", "WebkitGradientContainer", "log", "smallImage", "parseBackgrounds"]
} }

View File

@ -363,12 +363,16 @@ NodeContainer.prototype.isElementVisible = function() {
NodeContainer.prototype.css = function(attribute) { NodeContainer.prototype.css = function(attribute) {
if (!this.computedStyles) { if (!this.computedStyles) {
this.computedStyles = this.node.ownerDocument.defaultView.getComputedStyle(this.node, null); this.computedStyles = this.computedStyle(null);
} }
return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]); return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);
}; };
NodeContainer.prototype.computedStyle = function(type) {
return this.node.ownerDocument.defaultView.getComputedStyle(this.node, type);
};
NodeContainer.prototype.cssInt = function(attribute) { NodeContainer.prototype.cssInt = function(attribute) {
var value = parseInt(this.css(attribute), 10); var value = parseInt(this.css(attribute), 10);
return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html
@ -393,103 +397,6 @@ NodeContainer.prototype.fontWeight = function() {
}; };
NodeContainer.prototype.parseBackgroundImages = function() { NodeContainer.prototype.parseBackgroundImages = function() {
function parseBackgrounds(backgroundImage) {
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 = '';
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 results;
}
return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage"))); return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage")));
}; };
@ -588,6 +495,103 @@ function isPercentage(value) {
return value.toString().indexOf("%") !== -1; return value.toString().indexOf("%") !== -1;
} }
function parseBackgrounds(backgroundImage) {
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 = '';
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 results;
}
function NodeParser(element, renderer, support, imageLoader, options) { function NodeParser(element, renderer, support, imageLoader, options) {
log("Starting NodeParser"); log("Starting NodeParser");
this.renderer = renderer; this.renderer = renderer;
@ -597,9 +601,10 @@ function NodeParser(element, renderer, support, imageLoader, options) {
this.stack = new StackingContext(true, 1, element.ownerDocument, null); this.stack = new StackingContext(true, 1, element.ownerDocument, null);
var parent = new NodeContainer(element, null); var parent = new NodeContainer(element, null);
parent.visibile = parent.isElementVisible(); parent.visibile = parent.isElementVisible();
this.nodes = [parent].concat(this.getChildren(parent)).filter(function(container) { this.createPseudoHideStyles(element.ownerDocument);
this.nodes = flatten([parent].concat(this.getChildren(parent)).filter(function(container) {
return container.visible = container.isElementVisible(); return container.visible = container.isElementVisible();
}); }).map(this.getPseudoElements, this));
this.fontMetrics = new FontMetrics(); this.fontMetrics = new FontMetrics();
log("Fetched nodes"); log("Fetched nodes");
this.images = imageLoader.fetch(this.nodes.filter(isElement)); this.images = imageLoader.fetch(this.nodes.filter(isElement));
@ -614,6 +619,68 @@ function NodeParser(element, renderer, support, imageLoader, options) {
}, this)); }, this));
} }
NodeParser.prototype.createPseudoHideStyles = function(document) {
var hidePseudoElements = document.createElement('style');
hidePseudoElements.innerHTML = '.' + this.pseudoHideClass + ':before { content: "" !important; display: none !important; }' +
'.' + this.pseudoHideClass + ':after { content: "" !important; display: none !important; }';
document.body.appendChild(hidePseudoElements);
};
NodeParser.prototype.getPseudoElements = function(container) {
var nodes = [[container]];
if (container instanceof NodeContainer) {
var before = this.getPseudoElement(container, ":before");
var after = this.getPseudoElement(container, ":after");
if (before) {
container.node.insertBefore(before[0].node, container.node.firstChild);
nodes.push(before);
}
if (after) {
container.node.appendChild(after[0].node);
nodes.push(after);
}
if (before || after) {
container.node.className += " " + this.pseudoHideClass;
}
}
return flatten(nodes);
};
NodeParser.prototype.getPseudoElement = function(container, type) {
var style = container.computedStyle(type);
if(!style || !style.content || style.content === "none" || style.content === "-moz-alt-content" || style.display === "none") {
return null;
}
var content = stripQuotes(style.content);
var isImage = content.substr(0, 3) === 'url';
var pseudoNode = document.createElement(isImage ? 'img' : 'html2canvaspseudoelement');
var pseudoContainer = new NodeContainer(pseudoNode, container);
Object.keys(style).filter(indexedProperty).forEach(function(property) {
// Prevent assigning of read only CSS Rules, ex. length, parentRule
try {
pseudoNode.style[property] = style[property];
} catch (e) {
log('Tried to assign readonly property ', property, 'Error:', e);
}
});
pseudoNode.className = this.pseudoHideClass;
if (isImage) {
pseudoNode.src = parseBackgrounds(content)[0].args[0];
return [pseudoContainer];
} else {
var text = document.createTextNode(content);
pseudoNode.appendChild(text);
return [pseudoContainer, new TextContainer(text, pseudoContainer)];
}
};
NodeParser.prototype.getChildren = function(parentContainer) { NodeParser.prototype.getChildren = function(parentContainer) {
return flatten([].filter.call(parentContainer.node.childNodes, renderableNode).map(function(node) { 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); var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);
@ -918,6 +985,8 @@ NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, bor
return borderArgs; return borderArgs;
}; };
NodeParser.prototype.pseudoHideClass = "___html2canvas___pseudoelement";
function getCurvePoints(x, y, r1, r2) { function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3); var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
var ox = (r1) * kappa, // control point offset horizontal var ox = (r1) * kappa, // control point offset horizontal
@ -1137,6 +1206,15 @@ function flatten(arrays) {
return [].concat.apply([], arrays); return [].concat.apply([], arrays);
} }
function stripQuotes(content) {
var first = content.substr(0, 1);
return (first === content.substr(content.length - 1) && first.match(/'|"/)) ? content.substr(1, content.length - 2) : content;
}
function indexedProperty(property) {
return (isNaN(parseInt(property, 10)));
}
/* /*
Copyright (c) 2013 Yehuda Katz, Tom Dale, and contributors Copyright (c) 2013 Yehuda Katz, Tom Dale, and contributors

File diff suppressed because one or more lines are too long

View File

@ -20,12 +20,16 @@ NodeContainer.prototype.isElementVisible = function() {
NodeContainer.prototype.css = function(attribute) { NodeContainer.prototype.css = function(attribute) {
if (!this.computedStyles) { if (!this.computedStyles) {
this.computedStyles = this.node.ownerDocument.defaultView.getComputedStyle(this.node, null); this.computedStyles = this.computedStyle(null);
} }
return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]); return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);
}; };
NodeContainer.prototype.computedStyle = function(type) {
return this.node.ownerDocument.defaultView.getComputedStyle(this.node, type);
};
NodeContainer.prototype.cssInt = function(attribute) { NodeContainer.prototype.cssInt = function(attribute) {
var value = parseInt(this.css(attribute), 10); var value = parseInt(this.css(attribute), 10);
return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html
@ -50,103 +54,6 @@ NodeContainer.prototype.fontWeight = function() {
}; };
NodeContainer.prototype.parseBackgroundImages = function() { NodeContainer.prototype.parseBackgroundImages = function() {
function parseBackgrounds(backgroundImage) {
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 = '';
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 results;
}
return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage"))); return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage")));
}; };
@ -244,3 +151,100 @@ NodeContainer.prototype.TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\
function isPercentage(value) { function isPercentage(value) {
return value.toString().indexOf("%") !== -1; return value.toString().indexOf("%") !== -1;
} }
function parseBackgrounds(backgroundImage) {
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 = '';
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 results;
}

View File

@ -7,9 +7,10 @@ function NodeParser(element, renderer, support, imageLoader, options) {
this.stack = new StackingContext(true, 1, element.ownerDocument, null); this.stack = new StackingContext(true, 1, element.ownerDocument, null);
var parent = new NodeContainer(element, null); var parent = new NodeContainer(element, null);
parent.visibile = parent.isElementVisible(); parent.visibile = parent.isElementVisible();
this.nodes = [parent].concat(this.getChildren(parent)).filter(function(container) { this.createPseudoHideStyles(element.ownerDocument);
this.nodes = flatten([parent].concat(this.getChildren(parent)).filter(function(container) {
return container.visible = container.isElementVisible(); return container.visible = container.isElementVisible();
}); }).map(this.getPseudoElements, this));
this.fontMetrics = new FontMetrics(); this.fontMetrics = new FontMetrics();
log("Fetched nodes"); log("Fetched nodes");
this.images = imageLoader.fetch(this.nodes.filter(isElement)); this.images = imageLoader.fetch(this.nodes.filter(isElement));
@ -24,6 +25,68 @@ function NodeParser(element, renderer, support, imageLoader, options) {
}, this)); }, this));
} }
NodeParser.prototype.createPseudoHideStyles = function(document) {
var hidePseudoElements = document.createElement('style');
hidePseudoElements.innerHTML = '.' + this.pseudoHideClass + ':before { content: "" !important; display: none !important; }' +
'.' + this.pseudoHideClass + ':after { content: "" !important; display: none !important; }';
document.body.appendChild(hidePseudoElements);
};
NodeParser.prototype.getPseudoElements = function(container) {
var nodes = [[container]];
if (container instanceof NodeContainer) {
var before = this.getPseudoElement(container, ":before");
var after = this.getPseudoElement(container, ":after");
if (before) {
container.node.insertBefore(before[0].node, container.node.firstChild);
nodes.push(before);
}
if (after) {
container.node.appendChild(after[0].node);
nodes.push(after);
}
if (before || after) {
container.node.className += " " + this.pseudoHideClass;
}
}
return flatten(nodes);
};
NodeParser.prototype.getPseudoElement = function(container, type) {
var style = container.computedStyle(type);
if(!style || !style.content || style.content === "none" || style.content === "-moz-alt-content" || style.display === "none") {
return null;
}
var content = stripQuotes(style.content);
var isImage = content.substr(0, 3) === 'url';
var pseudoNode = document.createElement(isImage ? 'img' : 'html2canvaspseudoelement');
var pseudoContainer = new NodeContainer(pseudoNode, container);
Object.keys(style).filter(indexedProperty).forEach(function(property) {
// Prevent assigning of read only CSS Rules, ex. length, parentRule
try {
pseudoNode.style[property] = style[property];
} catch (e) {
log('Tried to assign readonly property ', property, 'Error:', e);
}
});
pseudoNode.className = this.pseudoHideClass;
if (isImage) {
pseudoNode.src = parseBackgrounds(content)[0].args[0];
return [pseudoContainer];
} else {
var text = document.createTextNode(content);
pseudoNode.appendChild(text);
return [pseudoContainer, new TextContainer(text, pseudoContainer)];
}
};
NodeParser.prototype.getChildren = function(parentContainer) { NodeParser.prototype.getChildren = function(parentContainer) {
return flatten([].filter.call(parentContainer.node.childNodes, renderableNode).map(function(node) { 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); var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);
@ -328,6 +391,8 @@ NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, bor
return borderArgs; return borderArgs;
}; };
NodeParser.prototype.pseudoHideClass = "___html2canvas___pseudoelement";
function getCurvePoints(x, y, r1, r2) { function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3); var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
var ox = (r1) * kappa, // control point offset horizontal var ox = (r1) * kappa, // control point offset horizontal
@ -546,3 +611,12 @@ function nonIgnoredElement(nodeContainer) {
function flatten(arrays) { function flatten(arrays) {
return [].concat.apply([], arrays); return [].concat.apply([], arrays);
} }
function stripQuotes(content) {
var first = content.substr(0, 1);
return (first === content.substr(content.length - 1) && first.match(/'|"/)) ? content.substr(1, content.length - 2) : content;
}
function indexedProperty(property) {
return (isNaN(parseInt(property, 10)));
}