Correctly handle overflow content

This commit is contained in:
Niklas von Hertzen 2014-09-27 16:54:53 +03:00
parent b1f948bb60
commit 1d8a316f13
6 changed files with 361 additions and 287 deletions

65
dist/html2canvas.js vendored
View File

@ -1067,6 +1067,9 @@ function NodeContainer(node, parent) {
this.parent = parent;
this.stack = null;
this.bounds = null;
this.borders = null;
this.clip = [];
this.backgroundClip = [];
this.offsetBounds = null;
this.visible = null;
this.computedStyles = null;
@ -1331,8 +1334,7 @@ function parseBackgrounds(backgroundImage) {
case '"':
if(!quote) {
quote = c;
}
else if(quote === c) {
} else if(quote === c) {
quote = null;
}
break;
@ -1450,12 +1452,14 @@ function NodeParser(element, renderer, support, imageLoader, options) {
this.fontMetrics = new FontMetrics();
log("Fetched nodes");
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");
this.sortStackingContexts(this.stack);
this.ready = this.images.ready.then(bind(function() {
log("Images loaded, starting parsing");
this.parse(this.stack);
log("Render queue created with " + this.renderQueue.length + " items");
return new Promise(bind(function(resolve) {
@ -1472,6 +1476,24 @@ function NodeParser(element, renderer, support, imageLoader, options) {
}, this));
}
NodeParser.prototype.calculateOverflowClips = function() {
this.nodes.forEach(function(container) {
if (isElement(container)) {
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;
} else if (isTextNode(container)) {
container.clip = hasParentClip(container) ? container.parent.clip : [];
}
container.bounds = null;
}, this);
};
function hasParentClip(container) {
return container.parent && container.parent.clip.length;
}
NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
asyncTimer = asyncTimer || Date.now();
this.paint(queue[this.renderIndex++]);
@ -1561,6 +1583,10 @@ 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 parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
parentStack.contexts.push(stack);
container.stack = stack;
@ -1675,17 +1701,19 @@ NodeParser.prototype.paintNode = function(container) {
}
}
var bounds = container.parseBounds();
var borderData = this.parseBorders(container);
this.renderer.clip(borderData.clip, function() {
this.renderer.renderBackground(container, bounds, borderData.borders.map(getWidth));
this.renderer.clip(container.backgroundClip, function() {
this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
}, this);
this.renderer.renderBorders(borderData.borders);
this.renderer.clip(container.clip, function() {
this.renderer.renderBorders(container.borders.borders);
switch (container.node.nodeName) {
case "svg":
case "IFRAME":
var imgContainer = this.images.get(container.node);
if (imgContainer) {
this.renderer.renderImage(container, bounds, borderData, imgContainer);
this.renderer.renderImage(container, bounds, container.borders, imgContainer);
} else {
log("Error loading <" + container.node.nodeName + ">", container.node);
}
@ -1693,7 +1721,7 @@ NodeParser.prototype.paintNode = function(container) {
case "IMG":
var imageContainer = this.images.get(container.node.src);
if (imageContainer) {
this.renderer.renderImage(container, bounds, borderData, imageContainer);
this.renderer.renderImage(container, bounds, container.borders, imageContainer);
} else {
log("Error loading <img>", container.node.src);
}
@ -1704,6 +1732,7 @@ NodeParser.prototype.paintNode = function(container) {
this.paintFormValue(container);
break;
}
}, this);
};
NodeParser.prototype.paintFormValue = function(container) {
@ -1754,12 +1783,14 @@ NodeParser.prototype.paintText = function(container) {
this.renderer.clearShadow();
}
this.renderer.clip(container.parent.clip, function() {
textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {
if (bounds) {
this.renderer.text(textList[index], bounds.left, bounds.bottom);
this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));
}
}, this);
}, this);
};
NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {
@ -1780,7 +1811,7 @@ NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics)
};
NodeParser.prototype.parseBorders = function(container) {
var nodeBounds = container.bounds;
var nodeBounds = container.parseBounds();
var radius = getBorderRadiusData(container);
var borders = ["Top", "Right", "Bottom", "Left"].map(function(side) {
return {
@ -2148,7 +2179,7 @@ function isWordBoundary(characterCode) {
}
function hasUnicode(string) {
return /[^\u0000-\u00ff]/.test(string);
return (/[^\u0000-\u00ff]/).test(string);
}
function Proxy(src, proxyUrl, document) {
@ -2605,9 +2636,11 @@ CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx
}
};
CanvasRenderer.prototype.clip = function(shape, callback, context) {
CanvasRenderer.prototype.clip = function(shapes, callback, context) {
this.ctx.save();
shapes.filter(hasEntries).forEach(function(shape) {
this.shape(shape).clip();
}, this);
callback.call(context);
this.ctx.restore();
};
@ -2665,7 +2698,7 @@ CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgr
["line", Math.round(left + width), Math.round(height + top)],
["line", Math.round(left), Math.round(height + top)]
];
this.clip(shape, function() {
this.clip([shape], function() {
this.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]);
}, this);
};
@ -2706,4 +2739,8 @@ CanvasRenderer.prototype.resizeImage = function(imageContainer, size) {
return canvas;
};
function hasEntries(array) {
return array.length > 0;
}
})(window, document);

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,9 @@ function NodeContainer(node, parent) {
this.parent = parent;
this.stack = null;
this.bounds = null;
this.borders = null;
this.clip = [];
this.backgroundClip = [];
this.offsetBounds = null;
this.visible = null;
this.computedStyles = null;
@ -267,8 +270,7 @@ function parseBackgrounds(backgroundImage) {
case '"':
if(!quote) {
quote = c;
}
else if(quote === c) {
} else if(quote === c) {
quote = null;
}
break;

View File

@ -20,12 +20,14 @@ function NodeParser(element, renderer, support, imageLoader, options) {
this.fontMetrics = new FontMetrics();
log("Fetched nodes");
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");
this.sortStackingContexts(this.stack);
this.ready = this.images.ready.then(bind(function() {
log("Images loaded, starting parsing");
this.parse(this.stack);
log("Render queue created with " + this.renderQueue.length + " items");
return new Promise(bind(function(resolve) {
@ -42,6 +44,24 @@ function NodeParser(element, renderer, support, imageLoader, options) {
}, this));
}
NodeParser.prototype.calculateOverflowClips = function() {
this.nodes.forEach(function(container) {
if (isElement(container)) {
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;
} else if (isTextNode(container)) {
container.clip = hasParentClip(container) ? container.parent.clip : [];
}
container.bounds = null;
}, this);
};
function hasParentClip(container) {
return container.parent && container.parent.clip.length;
}
NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
asyncTimer = asyncTimer || Date.now();
this.paint(queue[this.renderIndex++]);
@ -131,6 +151,10 @@ 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 parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
parentStack.contexts.push(stack);
container.stack = stack;
@ -245,17 +269,19 @@ NodeParser.prototype.paintNode = function(container) {
}
}
var bounds = container.parseBounds();
var borderData = this.parseBorders(container);
this.renderer.clip(borderData.clip, function() {
this.renderer.renderBackground(container, bounds, borderData.borders.map(getWidth));
this.renderer.clip(container.backgroundClip, function() {
this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
}, this);
this.renderer.renderBorders(borderData.borders);
this.renderer.clip(container.clip, function() {
this.renderer.renderBorders(container.borders.borders);
switch (container.node.nodeName) {
case "svg":
case "IFRAME":
var imgContainer = this.images.get(container.node);
if (imgContainer) {
this.renderer.renderImage(container, bounds, borderData, imgContainer);
this.renderer.renderImage(container, bounds, container.borders, imgContainer);
} else {
log("Error loading <" + container.node.nodeName + ">", container.node);
}
@ -263,7 +289,7 @@ NodeParser.prototype.paintNode = function(container) {
case "IMG":
var imageContainer = this.images.get(container.node.src);
if (imageContainer) {
this.renderer.renderImage(container, bounds, borderData, imageContainer);
this.renderer.renderImage(container, bounds, container.borders, imageContainer);
} else {
log("Error loading <img>", container.node.src);
}
@ -274,6 +300,7 @@ NodeParser.prototype.paintNode = function(container) {
this.paintFormValue(container);
break;
}
}, this);
};
NodeParser.prototype.paintFormValue = function(container) {
@ -324,12 +351,14 @@ NodeParser.prototype.paintText = function(container) {
this.renderer.clearShadow();
}
this.renderer.clip(container.parent.clip, function() {
textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {
if (bounds) {
this.renderer.text(textList[index], bounds.left, bounds.bottom);
this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));
}
}, this);
}, this);
};
NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {
@ -350,7 +379,7 @@ NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics)
};
NodeParser.prototype.parseBorders = function(container) {
var nodeBounds = container.bounds;
var nodeBounds = container.parseBounds();
var radius = getBorderRadiusData(container);
var borders = ["Top", "Right", "Bottom", "Left"].map(function(side) {
return {
@ -718,5 +747,5 @@ function isWordBoundary(characterCode) {
}
function hasUnicode(string) {
return /[^\u0000-\u00ff]/.test(string);
return (/[^\u0000-\u00ff]/).test(string);
}

View File

@ -47,9 +47,11 @@ CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx
}
};
CanvasRenderer.prototype.clip = function(shape, callback, context) {
CanvasRenderer.prototype.clip = function(shapes, callback, context) {
this.ctx.save();
shapes.filter(hasEntries).forEach(function(shape) {
this.shape(shape).clip();
}, this);
callback.call(context);
this.ctx.restore();
};
@ -107,7 +109,7 @@ CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgr
["line", Math.round(left + width), Math.round(height + top)],
["line", Math.round(left), Math.round(height + top)]
];
this.clip(shape, function() {
this.clip([shape], function() {
this.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]);
}, this);
};
@ -147,3 +149,7 @@ CanvasRenderer.prototype.resizeImage = function(imageContainer, size) {
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height );
return canvas;
};
function hasEntries(array) {
return array.length > 0;
}

View File

@ -37,7 +37,7 @@
<div style="overflow:hidden;">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />