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

321
dist/html2canvas.js vendored
View File

@ -1067,6 +1067,9 @@ function NodeContainer(node, parent) {
this.parent = parent; this.parent = parent;
this.stack = null; this.stack = null;
this.bounds = null; this.bounds = null;
this.borders = null;
this.clip = [];
this.backgroundClip = [];
this.offsetBounds = null; this.offsetBounds = null;
this.visible = null; this.visible = null;
this.computedStyles = null; this.computedStyles = null;
@ -1127,12 +1130,12 @@ NodeContainer.prototype.cssFloat = function(attribute) {
NodeContainer.prototype.fontWeight = function() { NodeContainer.prototype.fontWeight = function() {
var weight = this.css("fontWeight"); var weight = this.css("fontWeight");
switch(parseInt(weight, 10)){ switch(parseInt(weight, 10)){
case 401: case 401:
weight = "bold"; weight = "bold";
break; break;
case 400: case 400:
weight = "normal"; weight = "normal";
break; break;
} }
return weight; return weight;
}; };
@ -1328,55 +1331,54 @@ function parseBackgrounds(backgroundImage) {
return; return;
} }
switch(c) { switch(c) {
case '"': case '"':
if(!quote) { if(!quote) {
quote = c; quote = c;
} } else if(quote === c) {
else if(quote === c) { quote = null;
quote = null; }
} break;
case '(':
if(quote) {
break; break;
case '(': } else if(mode === 0) {
if(quote) { mode = 1;
break; block += c;
} else if(mode === 0) { return;
mode = 1; } else {
numParen++;
}
break;
case ')':
if (quote) {
break;
} else if(mode === 1) {
if(numParen === 0) {
mode = 0;
block += c; 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(); appendResult();
return; return;
} else if (mode === 1) { } else {
if (numParen === 0 && !method.match(/^url$/i)) { numParen--;
args.push(definition);
definition = '';
block += c;
return;
}
} }
}
break;
case ',':
if (quote) {
break; 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; block += c;
@ -1450,12 +1452,14 @@ function NodeParser(element, renderer, support, imageLoader, options) {
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));
log("Creating stacking contexts");
this.createStackingContexts();
log("Sorting stacking contexts");
this.sortStackingContexts(this.stack);
this.ready = this.images.ready.then(bind(function() { this.ready = this.images.ready.then(bind(function() {
log("Images loaded, starting parsing"); 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.parse(this.stack); this.parse(this.stack);
log("Render queue created with " + this.renderQueue.length + " items"); log("Render queue created with " + this.renderQueue.length + " items");
return new Promise(bind(function(resolve) { return new Promise(bind(function(resolve) {
@ -1472,6 +1476,24 @@ function NodeParser(element, renderer, support, imageLoader, options) {
}, this)); }, 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) { NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
asyncTimer = asyncTimer || Date.now(); asyncTimer = asyncTimer || Date.now();
this.paint(queue[this.renderIndex++]); this.paint(queue[this.renderIndex++]);
@ -1561,6 +1583,10 @@ NodeParser.prototype.getChildren = function(parentContainer) {
NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) { NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
var stack = new StackingContext(hasOwnStacking, container.cssFloat('opacity'), container.node, container.parent); var stack = new StackingContext(hasOwnStacking, container.cssFloat('opacity'), container.node, container.parent);
stack.visible = container.visible; 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; var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
parentStack.contexts.push(stack); parentStack.contexts.push(stack);
container.stack = stack; container.stack = stack;
@ -1675,17 +1701,19 @@ NodeParser.prototype.paintNode = function(container) {
} }
} }
var bounds = container.parseBounds(); var bounds = container.parseBounds();
var borderData = this.parseBorders(container); this.renderer.clip(container.backgroundClip, function() {
this.renderer.clip(borderData.clip, function() { this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
this.renderer.renderBackground(container, bounds, borderData.borders.map(getWidth));
}, this); }, this);
this.renderer.renderBorders(borderData.borders);
switch(container.node.nodeName) { this.renderer.clip(container.clip, function() {
this.renderer.renderBorders(container.borders.borders);
switch (container.node.nodeName) {
case "svg": case "svg":
case "IFRAME": case "IFRAME":
var imgContainer = this.images.get(container.node); var imgContainer = this.images.get(container.node);
if (imgContainer) { if (imgContainer) {
this.renderer.renderImage(container, bounds, borderData, imgContainer); this.renderer.renderImage(container, bounds, container.borders, imgContainer);
} else { } else {
log("Error loading <" + container.node.nodeName + ">", container.node); log("Error loading <" + container.node.nodeName + ">", container.node);
} }
@ -1693,7 +1721,7 @@ NodeParser.prototype.paintNode = function(container) {
case "IMG": case "IMG":
var imageContainer = this.images.get(container.node.src); var imageContainer = this.images.get(container.node.src);
if (imageContainer) { if (imageContainer) {
this.renderer.renderImage(container, bounds, borderData, imageContainer); this.renderer.renderImage(container, bounds, container.borders, imageContainer);
} else { } else {
log("Error loading <img>", container.node.src); log("Error loading <img>", container.node.src);
} }
@ -1703,7 +1731,8 @@ NodeParser.prototype.paintNode = function(container) {
case "TEXTAREA": case "TEXTAREA":
this.paintFormValue(container); this.paintFormValue(container);
break; break;
} }
}, this);
}; };
NodeParser.prototype.paintFormValue = function(container) { NodeParser.prototype.paintFormValue = function(container) {
@ -1754,33 +1783,35 @@ NodeParser.prototype.paintText = function(container) {
this.renderer.clearShadow(); this.renderer.clearShadow();
} }
textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) { this.renderer.clip(container.parent.clip, function() {
if (bounds) { textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {
this.renderer.text(textList[index], bounds.left, bounds.bottom); if (bounds) {
this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size)); this.renderer.text(textList[index], bounds.left, bounds.bottom);
} this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));
}
}, this);
}, this); }, this);
}; };
NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) { NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {
switch(container.css("textDecoration").split(" ")[0]) { switch(container.css("textDecoration").split(" ")[0]) {
case "underline": case "underline":
// Draws a line at the baseline of the font // Draws a line at the baseline of the font
// TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size // TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size
this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.css("color")); this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.css("color"));
break; break;
case "overline": case "overline":
this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.css("color")); this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.css("color"));
break; break;
case "line-through": case "line-through":
// TODO try and find exact position for line-through // TODO try and find exact position for line-through
this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.css("color")); this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.css("color"));
break; break;
} }
}; };
NodeParser.prototype.parseBorders = function(container) { NodeParser.prototype.parseBorders = function(container) {
var nodeBounds = container.bounds; var nodeBounds = container.parseBounds();
var radius = getBorderRadiusData(container); var radius = getBorderRadiusData(container);
var borders = ["Top", "Right", "Bottom", "Left"].map(function(side) { var borders = ["Top", "Right", "Bottom", "Left"].map(function(side) {
return { return {
@ -1801,53 +1832,53 @@ NodeParser.prototype.parseBorders = function(container) {
var bh = nodeBounds.height - (borders[2].width); var bh = nodeBounds.height - (borders[2].width);
switch(borderSide) { switch(borderSide) {
case 0: case 0:
// top border // top border
bh = borders[0].width; bh = borders[0].width;
border.args = drawSide({ border.args = drawSide({
c1: [bx, by], c1: [bx, by],
c2: [bx + bw, by], c2: [bx + bw, by],
c3: [bx + bw - borders[1].width, by + bh], c3: [bx + bw - borders[1].width, by + bh],
c4: [bx + borders[3].width, by + bh] c4: [bx + borders[3].width, by + bh]
}, radius[0], radius[1], }, radius[0], radius[1],
borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner); borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
break; break;
case 1: case 1:
// right border // right border
bx = nodeBounds.left + nodeBounds.width - (borders[1].width); bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
bw = borders[1].width; bw = borders[1].width;
border.args = drawSide({ border.args = drawSide({
c1: [bx + bw, by], c1: [bx + bw, by],
c2: [bx + bw, by + bh + borders[2].width], c2: [bx + bw, by + bh + borders[2].width],
c3: [bx, by + bh], c3: [bx, by + bh],
c4: [bx, by + borders[0].width] c4: [bx, by + borders[0].width]
}, radius[1], radius[2], }, radius[1], radius[2],
borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner); borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
break; break;
case 2: case 2:
// bottom border // bottom border
by = (by + nodeBounds.height) - (borders[2].width); by = (by + nodeBounds.height) - (borders[2].width);
bh = borders[2].width; bh = borders[2].width;
border.args = drawSide({ border.args = drawSide({
c1: [bx + bw, by + bh], c1: [bx + bw, by + bh],
c2: [bx, by + bh], c2: [bx, by + bh],
c3: [bx + borders[3].width, by], c3: [bx + borders[3].width, by],
c4: [bx + bw - borders[3].width, by] c4: [bx + bw - borders[3].width, by]
}, radius[2], radius[3], }, radius[2], radius[3],
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner); borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
break; break;
case 3: case 3:
// left border // left border
bw = borders[3].width; bw = borders[3].width;
border.args = drawSide({ border.args = drawSide({
c1: [bx, by + bh + borders[2].width], c1: [bx, by + bh + borders[2].width],
c2: [bx, by], c2: [bx, by],
c3: [bx + bw, by + borders[0].width], c3: [bx + bw, by + borders[0].width],
c4: [bx + bw, by + bh] c4: [bx + bw, by + bh]
}, radius[3], radius[0], }, radius[3], radius[0],
borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner); borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
break; break;
} }
} }
return border; return border;
@ -1860,20 +1891,20 @@ NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, bor
borderArgs = []; borderArgs = [];
switch(backgroundClip) { switch(backgroundClip) {
case "content-box": case "content-box":
case "padding-box": case "padding-box":
parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width); parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width); parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width); parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width); parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
break; break;
default: default:
parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top); parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top); parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height); parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height); parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
break; break;
} }
return borderArgs; return borderArgs;
@ -2148,7 +2179,7 @@ function isWordBoundary(characterCode) {
} }
function hasUnicode(string) { function hasUnicode(string) {
return /[^\u0000-\u00ff]/.test(string); return (/[^\u0000-\u00ff]/).test(string);
} }
function Proxy(src, proxyUrl, document) { 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(); this.ctx.save();
this.shape(shape).clip(); shapes.filter(hasEntries).forEach(function(shape) {
this.shape(shape).clip();
}, this);
callback.call(context); callback.call(context);
this.ctx.restore(); 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 + width), Math.round(height + top)],
["line", Math.round(left), 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.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]);
}, this); }, this);
}; };
@ -2706,4 +2739,8 @@ CanvasRenderer.prototype.resizeImage = function(imageContainer, size) {
return canvas; return canvas;
}; };
function hasEntries(array) {
return array.length > 0;
}
})(window, document); })(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.parent = parent;
this.stack = null; this.stack = null;
this.bounds = null; this.bounds = null;
this.borders = null;
this.clip = [];
this.backgroundClip = [];
this.offsetBounds = null; this.offsetBounds = null;
this.visible = null; this.visible = null;
this.computedStyles = null; this.computedStyles = null;
@ -63,12 +66,12 @@ NodeContainer.prototype.cssFloat = function(attribute) {
NodeContainer.prototype.fontWeight = function() { NodeContainer.prototype.fontWeight = function() {
var weight = this.css("fontWeight"); var weight = this.css("fontWeight");
switch(parseInt(weight, 10)){ switch(parseInt(weight, 10)){
case 401: case 401:
weight = "bold"; weight = "bold";
break; break;
case 400: case 400:
weight = "normal"; weight = "normal";
break; break;
} }
return weight; return weight;
}; };
@ -264,55 +267,54 @@ function parseBackgrounds(backgroundImage) {
return; return;
} }
switch(c) { switch(c) {
case '"': case '"':
if(!quote) { if(!quote) {
quote = c; quote = c;
} } else if(quote === c) {
else if(quote === c) { quote = null;
quote = null; }
} break;
case '(':
if(quote) {
break; break;
case '(': } else if(mode === 0) {
if(quote) { mode = 1;
break; block += c;
} else if(mode === 0) { return;
mode = 1; } else {
numParen++;
}
break;
case ')':
if (quote) {
break;
} else if(mode === 1) {
if(numParen === 0) {
mode = 0;
block += c; 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(); appendResult();
return; return;
} else if (mode === 1) { } else {
if (numParen === 0 && !method.match(/^url$/i)) { numParen--;
args.push(definition);
definition = '';
block += c;
return;
}
} }
}
break;
case ',':
if (quote) {
break; 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; block += c;

View File

@ -20,12 +20,14 @@ function NodeParser(element, renderer, support, imageLoader, options) {
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));
log("Creating stacking contexts");
this.createStackingContexts();
log("Sorting stacking contexts");
this.sortStackingContexts(this.stack);
this.ready = this.images.ready.then(bind(function() { this.ready = this.images.ready.then(bind(function() {
log("Images loaded, starting parsing"); 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.parse(this.stack); this.parse(this.stack);
log("Render queue created with " + this.renderQueue.length + " items"); log("Render queue created with " + this.renderQueue.length + " items");
return new Promise(bind(function(resolve) { return new Promise(bind(function(resolve) {
@ -42,6 +44,24 @@ function NodeParser(element, renderer, support, imageLoader, options) {
}, this)); }, 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) { NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
asyncTimer = asyncTimer || Date.now(); asyncTimer = asyncTimer || Date.now();
this.paint(queue[this.renderIndex++]); this.paint(queue[this.renderIndex++]);
@ -131,6 +151,10 @@ NodeParser.prototype.getChildren = function(parentContainer) {
NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) { NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
var stack = new StackingContext(hasOwnStacking, container.cssFloat('opacity'), container.node, container.parent); var stack = new StackingContext(hasOwnStacking, container.cssFloat('opacity'), container.node, container.parent);
stack.visible = container.visible; 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; var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
parentStack.contexts.push(stack); parentStack.contexts.push(stack);
container.stack = stack; container.stack = stack;
@ -245,17 +269,19 @@ NodeParser.prototype.paintNode = function(container) {
} }
} }
var bounds = container.parseBounds(); var bounds = container.parseBounds();
var borderData = this.parseBorders(container); this.renderer.clip(container.backgroundClip, function() {
this.renderer.clip(borderData.clip, function() { this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
this.renderer.renderBackground(container, bounds, borderData.borders.map(getWidth));
}, this); }, this);
this.renderer.renderBorders(borderData.borders);
switch(container.node.nodeName) { this.renderer.clip(container.clip, function() {
this.renderer.renderBorders(container.borders.borders);
switch (container.node.nodeName) {
case "svg": case "svg":
case "IFRAME": case "IFRAME":
var imgContainer = this.images.get(container.node); var imgContainer = this.images.get(container.node);
if (imgContainer) { if (imgContainer) {
this.renderer.renderImage(container, bounds, borderData, imgContainer); this.renderer.renderImage(container, bounds, container.borders, imgContainer);
} else { } else {
log("Error loading <" + container.node.nodeName + ">", container.node); log("Error loading <" + container.node.nodeName + ">", container.node);
} }
@ -263,7 +289,7 @@ NodeParser.prototype.paintNode = function(container) {
case "IMG": case "IMG":
var imageContainer = this.images.get(container.node.src); var imageContainer = this.images.get(container.node.src);
if (imageContainer) { if (imageContainer) {
this.renderer.renderImage(container, bounds, borderData, imageContainer); this.renderer.renderImage(container, bounds, container.borders, imageContainer);
} else { } else {
log("Error loading <img>", container.node.src); log("Error loading <img>", container.node.src);
} }
@ -273,7 +299,8 @@ NodeParser.prototype.paintNode = function(container) {
case "TEXTAREA": case "TEXTAREA":
this.paintFormValue(container); this.paintFormValue(container);
break; break;
} }
}, this);
}; };
NodeParser.prototype.paintFormValue = function(container) { NodeParser.prototype.paintFormValue = function(container) {
@ -324,33 +351,35 @@ NodeParser.prototype.paintText = function(container) {
this.renderer.clearShadow(); this.renderer.clearShadow();
} }
textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) { this.renderer.clip(container.parent.clip, function() {
if (bounds) { textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {
this.renderer.text(textList[index], bounds.left, bounds.bottom); if (bounds) {
this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size)); this.renderer.text(textList[index], bounds.left, bounds.bottom);
} this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));
}
}, this);
}, this); }, this);
}; };
NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) { NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {
switch(container.css("textDecoration").split(" ")[0]) { switch(container.css("textDecoration").split(" ")[0]) {
case "underline": case "underline":
// Draws a line at the baseline of the font // Draws a line at the baseline of the font
// TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size // TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size
this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.css("color")); this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.css("color"));
break; break;
case "overline": case "overline":
this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.css("color")); this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.css("color"));
break; break;
case "line-through": case "line-through":
// TODO try and find exact position for line-through // TODO try and find exact position for line-through
this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.css("color")); this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.css("color"));
break; break;
} }
}; };
NodeParser.prototype.parseBorders = function(container) { NodeParser.prototype.parseBorders = function(container) {
var nodeBounds = container.bounds; var nodeBounds = container.parseBounds();
var radius = getBorderRadiusData(container); var radius = getBorderRadiusData(container);
var borders = ["Top", "Right", "Bottom", "Left"].map(function(side) { var borders = ["Top", "Right", "Bottom", "Left"].map(function(side) {
return { return {
@ -371,53 +400,53 @@ NodeParser.prototype.parseBorders = function(container) {
var bh = nodeBounds.height - (borders[2].width); var bh = nodeBounds.height - (borders[2].width);
switch(borderSide) { switch(borderSide) {
case 0: case 0:
// top border // top border
bh = borders[0].width; bh = borders[0].width;
border.args = drawSide({ border.args = drawSide({
c1: [bx, by], c1: [bx, by],
c2: [bx + bw, by], c2: [bx + bw, by],
c3: [bx + bw - borders[1].width, by + bh], c3: [bx + bw - borders[1].width, by + bh],
c4: [bx + borders[3].width, by + bh] c4: [bx + borders[3].width, by + bh]
}, radius[0], radius[1], }, radius[0], radius[1],
borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner); borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
break; break;
case 1: case 1:
// right border // right border
bx = nodeBounds.left + nodeBounds.width - (borders[1].width); bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
bw = borders[1].width; bw = borders[1].width;
border.args = drawSide({ border.args = drawSide({
c1: [bx + bw, by], c1: [bx + bw, by],
c2: [bx + bw, by + bh + borders[2].width], c2: [bx + bw, by + bh + borders[2].width],
c3: [bx, by + bh], c3: [bx, by + bh],
c4: [bx, by + borders[0].width] c4: [bx, by + borders[0].width]
}, radius[1], radius[2], }, radius[1], radius[2],
borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner); borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
break; break;
case 2: case 2:
// bottom border // bottom border
by = (by + nodeBounds.height) - (borders[2].width); by = (by + nodeBounds.height) - (borders[2].width);
bh = borders[2].width; bh = borders[2].width;
border.args = drawSide({ border.args = drawSide({
c1: [bx + bw, by + bh], c1: [bx + bw, by + bh],
c2: [bx, by + bh], c2: [bx, by + bh],
c3: [bx + borders[3].width, by], c3: [bx + borders[3].width, by],
c4: [bx + bw - borders[3].width, by] c4: [bx + bw - borders[3].width, by]
}, radius[2], radius[3], }, radius[2], radius[3],
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner); borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
break; break;
case 3: case 3:
// left border // left border
bw = borders[3].width; bw = borders[3].width;
border.args = drawSide({ border.args = drawSide({
c1: [bx, by + bh + borders[2].width], c1: [bx, by + bh + borders[2].width],
c2: [bx, by], c2: [bx, by],
c3: [bx + bw, by + borders[0].width], c3: [bx + bw, by + borders[0].width],
c4: [bx + bw, by + bh] c4: [bx + bw, by + bh]
}, radius[3], radius[0], }, radius[3], radius[0],
borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner); borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
break; break;
} }
} }
return border; return border;
@ -430,20 +459,20 @@ NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, bor
borderArgs = []; borderArgs = [];
switch(backgroundClip) { switch(backgroundClip) {
case "content-box": case "content-box":
case "padding-box": case "padding-box":
parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width); parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width); parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width); parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width); parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
break; break;
default: default:
parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top); parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top); parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height); parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height); parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
break; break;
} }
return borderArgs; return borderArgs;
@ -718,5 +747,5 @@ function isWordBoundary(characterCode) {
} }
function hasUnicode(string) { 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(); this.ctx.save();
this.shape(shape).clip(); shapes.filter(hasEntries).forEach(function(shape) {
this.shape(shape).clip();
}, this);
callback.call(context); callback.call(context);
this.ctx.restore(); 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 + width), Math.round(height + top)],
["line", Math.round(left), 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.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]);
}, this); }, 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 ); ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height );
return canvas; return canvas;
}; };
function hasEntries(array) {
return array.length > 0;
}

View File

@ -37,7 +37,7 @@
<div style="overflow:hidden;"> <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 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 /> <div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />