Fix pseudoelement rendering (with nth-child selectors etc.)

This commit is contained in:
Niklas von Hertzen
2014-10-06 22:46:43 +03:00
parent d5430070a2
commit e103ad8219
8 changed files with 175 additions and 53 deletions

View File

@ -13,8 +13,26 @@ function NodeContainer(node, parent) {
this.backgroundImages = null;
this.transformData = null;
this.transformMatrix = null;
this.isPseudoElement = false;
this.opacity = null;
}
NodeContainer.prototype.cloneTo = function(stack) {
stack.visible = this.visible;
stack.borders = this.borders;
stack.bounds = this.bounds;
stack.clip = this.clip;
stack.backgroundClip = this.backgroundClip;
stack.computedStyles = this.computedStyles;
stack.styles = this.styles;
stack.backgroundImages = this.backgroundImages;
stack.opacity = this.opacity;
};
NodeContainer.prototype.getOpacity = function() {
return this.opacity === null ? (this.opacity = this.cssFloat('opacity')) : this.opacity;
};
NodeContainer.prototype.assignStack = function(stack) {
this.stack = stack;
stack.children.push(this);
@ -31,7 +49,7 @@ NodeContainer.prototype.isElementVisible = function() {
NodeContainer.prototype.css = function(attribute) {
if (!this.computedStyles) {
this.computedStyles = this.computedStyle(null);
this.computedStyles = this.isPseudoElement ? this.parent.computedStyle(this.before ? ":before" : ":after") : this.computedStyle(null);
}
return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);

View File

@ -19,11 +19,12 @@ function NodeParser(element, renderer, support, imageLoader, options) {
}).map(this.getPseudoElements, this));
this.fontMetrics = new FontMetrics();
log("Fetched nodes, total:", this.nodes.length);
log("Calculate overflow clips");
this.calculateOverflowClips();
log("Start fetching images");
this.images = imageLoader.fetch(this.nodes.filter(isElement));
this.ready = this.images.ready.then(bind(function() {
log("Images loaded, starting parsing");
log("Calculate overflow clips");
this.calculateOverflowClips();
log("Creating stacking contexts");
this.createStackingContexts();
log("Sorting stacking contexts");
@ -47,14 +48,22 @@ function NodeParser(element, renderer, support, imageLoader, options) {
NodeParser.prototype.calculateOverflowClips = function() {
this.nodes.forEach(function(container) {
if (isElement(container)) {
if (isPseudoElement(container)) {
container.appendToDOM();
}
container.borders = this.parseBorders(container);
var clip = (container.css('overflow') === "hidden") ? [container.borders.clip] : [];
container.clip = hasParentClip(container) ? container.parent.clip.concat(clip) : clip;
container.backgroundClip = (container.css('overflow') !== "hidden") ? container.clip.concat([container.borders.clip]) : container.clip;
if (isPseudoElement(container)) {
container.cleanDOM();
}
} else if (isTextNode(container)) {
container.clip = hasParentClip(container) ? container.parent.clip : [];
}
container.bounds = null;
if (!isPseudoElement(container)) {
container.bounds = null;
}
}, this);
};
@ -78,8 +87,8 @@ NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
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; }';
hidePseudoElements.innerHTML = '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + ':before { content: "" !important; display: none !important; }' +
'.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER + ':after { content: "" !important; display: none !important; }';
document.body.appendChild(hidePseudoElements);
};
@ -90,18 +99,12 @@ NodeParser.prototype.getPseudoElements = function(container) {
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);
};
@ -121,14 +124,14 @@ NodeParser.prototype.getPseudoElement = function(container, type) {
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);
var pseudoContainer = new PseudoElementContainer(pseudoNode, container, type);
for (var i = style.length-1; i >= 0; i--) {
var property = toCamelCase(style.item(i));
pseudoNode.style[property] = style[property];
}
pseudoNode.className = this.pseudoHideClass;
pseudoNode.className = PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + " " + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER;
if (isImage) {
pseudoNode.src = parseBackgrounds(content)[0].args[0];
@ -149,12 +152,8 @@ NodeParser.prototype.getChildren = function(parentContainer) {
};
NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
var stack = new StackingContext(hasOwnStacking, container.cssFloat('opacity'), container.node, container.parent);
stack.visible = container.visible;
stack.borders = container.borders;
stack.bounds = container.bounds;
stack.clip = container.clip;
stack.backgroundClip = container.backgroundClip;
var stack = new StackingContext(hasOwnStacking, container.getOpacity(), container.node, container.parent);
container.cloneTo(stack);
var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
parentStack.contexts.push(stack);
container.stack = stack;
@ -251,7 +250,13 @@ NodeParser.prototype.paint = function(container) {
if (container instanceof ClearTransform) {
this.renderer.ctx.restore();
} else if (isTextNode(container)) {
if (isPseudoElement(container.parent)) {
container.parent.appendToDOM();
}
this.paintText(container);
if (isPseudoElement(container.parent)) {
container.parent.cleanDOM();
}
} else {
this.paintNode(container);
}
@ -268,6 +273,7 @@ NodeParser.prototype.paintNode = function(container) {
this.renderer.setTransform(container.parseTransform());
}
}
var bounds = container.parseBounds();
this.renderer.clip(container.backgroundClip, function() {
this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
@ -481,8 +487,6 @@ NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, bor
return borderArgs;
};
NodeParser.prototype.pseudoHideClass = "___html2canvas___pseudoelement";
function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
var ox = (r1) * kappa, // control point offset horizontal
@ -676,6 +680,10 @@ function isElement(container) {
return container.node.nodeType === Node.ELEMENT_NODE;
}
function isPseudoElement(container) {
return container.isPseudoElement === true;
}
function isTextNode(container) {
return container.node.nodeType === Node.TEXT_NODE;
}
@ -685,7 +693,7 @@ function zIndexSort(a, b) {
}
function hasOpacity(container) {
return container.css("opacity") < 1;
return container.getOpacity() < 1;
}
function bind(callback, context) {

View File

@ -0,0 +1,34 @@
function PseudoElementContainer(node, parent, type) {
NodeContainer.call(this, node, parent);
this.isPseudoElement = true;
this.before = type === ":before";
}
PseudoElementContainer.prototype.cloneTo = function(stack) {
PseudoElementContainer.prototype.cloneTo.call(this, stack);
stack.isPseudoElement = true;
stack.before = this.before;
};
PseudoElementContainer.prototype = Object.create(NodeContainer.prototype);
PseudoElementContainer.prototype.appendToDOM = function() {
if (this.before) {
this.parent.node.insertBefore(this.node, this.parent.node.firstChild);
} else {
this.parent.node.appendChild(this.node);
}
this.parent.node.className += " " + this.getHideClass();
};
PseudoElementContainer.prototype.cleanDOM = function() {
this.node.parentNode.removeChild(this.node);
this.parent.node.className = this.parent.node.className.replace(this.getHideClass(), "");
};
PseudoElementContainer.prototype.getHideClass = function() {
return this["PSEUDO_HIDE_ELEMENT_CLASS_" + (this.before ? "BEFORE" : "AFTER")];
};
PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE = "___html2canvas___pseudoelement_before";
PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER = "___html2canvas___pseudoelement_after";