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.stack = null;
this.bounds = null;
this.borders = null;
this.clip = [];
this.backgroundClip = [];
this.offsetBounds = null;
this.visible = null;
this.computedStyles = null;
@ -1127,12 +1130,12 @@ NodeContainer.prototype.cssFloat = function(attribute) {
NodeContainer.prototype.fontWeight = function() {
var weight = this.css("fontWeight");
switch(parseInt(weight, 10)){
case 401:
weight = "bold";
break;
case 400:
weight = "normal";
break;
case 401:
weight = "bold";
break;
case 400:
weight = "normal";
break;
}
return weight;
};
@ -1328,55 +1331,54 @@ function parseBackgrounds(backgroundImage) {
return;
}
switch(c) {
case '"':
if(!quote) {
quote = c;
}
else if(quote === c) {
quote = null;
}
case '"':
if(!quote) {
quote = c;
} else if(quote === c) {
quote = null;
}
break;
case '(':
if(quote) {
break;
case '(':
if(quote) {
break;
} else if(mode === 0) {
mode = 1;
} 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;
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;
}
} 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;
@ -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));
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");
log("Calculate overflow clips");
this.calculateOverflowClips();
log("Creating stacking contexts");
this.createStackingContexts();
log("Sorting stacking contexts");
this.sortStackingContexts(this.stack);
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);
switch(container.node.nodeName) {
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);
}
@ -1703,7 +1731,8 @@ NodeParser.prototype.paintNode = function(container) {
case "TEXTAREA":
this.paintFormValue(container);
break;
}
}
}, this);
};
NodeParser.prototype.paintFormValue = function(container) {
@ -1754,33 +1783,35 @@ NodeParser.prototype.paintText = function(container) {
this.renderer.clearShadow();
}
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.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) {
switch(container.css("textDecoration").split(" ")[0]) {
case "underline":
// 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
this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.css("color"));
break;
case "overline":
this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.css("color"));
break;
case "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"));
break;
case "underline":
// 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
this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.css("color"));
break;
case "overline":
this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.css("color"));
break;
case "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"));
break;
}
};
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 {
@ -1801,53 +1832,53 @@ NodeParser.prototype.parseBorders = function(container) {
var bh = nodeBounds.height - (borders[2].width);
switch(borderSide) {
case 0:
// top border
bh = borders[0].width;
border.args = drawSide({
c1: [bx, by],
c2: [bx + bw, by],
c3: [bx + bw - borders[1].width, by + bh],
c4: [bx + borders[3].width, by + bh]
}, radius[0], radius[1],
borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
break;
case 1:
// right border
bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
bw = borders[1].width;
case 0:
// top border
bh = borders[0].width;
border.args = drawSide({
c1: [bx, by],
c2: [bx + bw, by],
c3: [bx + bw - borders[1].width, by + bh],
c4: [bx + borders[3].width, by + bh]
}, radius[0], radius[1],
borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
break;
case 1:
// right border
bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
bw = borders[1].width;
border.args = drawSide({
c1: [bx + bw, by],
c2: [bx + bw, by + bh + borders[2].width],
c3: [bx, by + bh],
c4: [bx, by + borders[0].width]
}, radius[1], radius[2],
borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
break;
case 2:
// bottom border
by = (by + nodeBounds.height) - (borders[2].width);
bh = borders[2].width;
border.args = drawSide({
c1: [bx + bw, by + bh],
c2: [bx, by + bh],
c3: [bx + borders[3].width, by],
c4: [bx + bw - borders[3].width, by]
}, radius[2], radius[3],
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
break;
case 3:
// left border
bw = borders[3].width;
border.args = drawSide({
c1: [bx, by + bh + borders[2].width],
c2: [bx, by],
c3: [bx + bw, by + borders[0].width],
c4: [bx + bw, by + bh]
}, radius[3], radius[0],
borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
break;
border.args = drawSide({
c1: [bx + bw, by],
c2: [bx + bw, by + bh + borders[2].width],
c3: [bx, by + bh],
c4: [bx, by + borders[0].width]
}, radius[1], radius[2],
borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
break;
case 2:
// bottom border
by = (by + nodeBounds.height) - (borders[2].width);
bh = borders[2].width;
border.args = drawSide({
c1: [bx + bw, by + bh],
c2: [bx, by + bh],
c3: [bx + borders[3].width, by],
c4: [bx + bw - borders[3].width, by]
}, radius[2], radius[3],
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
break;
case 3:
// left border
bw = borders[3].width;
border.args = drawSide({
c1: [bx, by + bh + borders[2].width],
c2: [bx, by],
c3: [bx + bw, by + borders[0].width],
c4: [bx + bw, by + bh]
}, radius[3], radius[0],
borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
break;
}
}
return border;
@ -1860,20 +1891,20 @@ NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, bor
borderArgs = [];
switch(backgroundClip) {
case "content-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[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[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
break;
case "content-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[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[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
break;
default:
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[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);
break;
default:
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[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);
break;
}
return borderArgs;
@ -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();
this.shape(shape).clip();
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;
@ -63,12 +66,12 @@ NodeContainer.prototype.cssFloat = function(attribute) {
NodeContainer.prototype.fontWeight = function() {
var weight = this.css("fontWeight");
switch(parseInt(weight, 10)){
case 401:
weight = "bold";
break;
case 400:
weight = "normal";
break;
case 401:
weight = "bold";
break;
case 400:
weight = "normal";
break;
}
return weight;
};
@ -264,55 +267,54 @@ function parseBackgrounds(backgroundImage) {
return;
}
switch(c) {
case '"':
if(!quote) {
quote = c;
}
else if(quote === c) {
quote = null;
}
case '"':
if(!quote) {
quote = c;
} else if(quote === c) {
quote = null;
}
break;
case '(':
if(quote) {
break;
case '(':
if(quote) {
break;
} else if(mode === 0) {
mode = 1;
} 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;
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;
}
} 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;

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));
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");
log("Calculate overflow clips");
this.calculateOverflowClips();
log("Creating stacking contexts");
this.createStackingContexts();
log("Sorting stacking contexts");
this.sortStackingContexts(this.stack);
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);
switch(container.node.nodeName) {
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);
}
@ -273,7 +299,8 @@ NodeParser.prototype.paintNode = function(container) {
case "TEXTAREA":
this.paintFormValue(container);
break;
}
}
}, this);
};
NodeParser.prototype.paintFormValue = function(container) {
@ -324,33 +351,35 @@ NodeParser.prototype.paintText = function(container) {
this.renderer.clearShadow();
}
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.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) {
switch(container.css("textDecoration").split(" ")[0]) {
case "underline":
// 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
this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.css("color"));
break;
case "overline":
this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.css("color"));
break;
case "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"));
break;
case "underline":
// 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
this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.css("color"));
break;
case "overline":
this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.css("color"));
break;
case "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"));
break;
}
};
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 {
@ -371,53 +400,53 @@ NodeParser.prototype.parseBorders = function(container) {
var bh = nodeBounds.height - (borders[2].width);
switch(borderSide) {
case 0:
// top border
bh = borders[0].width;
border.args = drawSide({
c1: [bx, by],
c2: [bx + bw, by],
c3: [bx + bw - borders[1].width, by + bh],
c4: [bx + borders[3].width, by + bh]
}, radius[0], radius[1],
borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
break;
case 1:
// right border
bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
bw = borders[1].width;
case 0:
// top border
bh = borders[0].width;
border.args = drawSide({
c1: [bx, by],
c2: [bx + bw, by],
c3: [bx + bw - borders[1].width, by + bh],
c4: [bx + borders[3].width, by + bh]
}, radius[0], radius[1],
borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
break;
case 1:
// right border
bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
bw = borders[1].width;
border.args = drawSide({
c1: [bx + bw, by],
c2: [bx + bw, by + bh + borders[2].width],
c3: [bx, by + bh],
c4: [bx, by + borders[0].width]
}, radius[1], radius[2],
borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
break;
case 2:
// bottom border
by = (by + nodeBounds.height) - (borders[2].width);
bh = borders[2].width;
border.args = drawSide({
c1: [bx + bw, by + bh],
c2: [bx, by + bh],
c3: [bx + borders[3].width, by],
c4: [bx + bw - borders[3].width, by]
}, radius[2], radius[3],
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
break;
case 3:
// left border
bw = borders[3].width;
border.args = drawSide({
c1: [bx, by + bh + borders[2].width],
c2: [bx, by],
c3: [bx + bw, by + borders[0].width],
c4: [bx + bw, by + bh]
}, radius[3], radius[0],
borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
break;
border.args = drawSide({
c1: [bx + bw, by],
c2: [bx + bw, by + bh + borders[2].width],
c3: [bx, by + bh],
c4: [bx, by + borders[0].width]
}, radius[1], radius[2],
borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
break;
case 2:
// bottom border
by = (by + nodeBounds.height) - (borders[2].width);
bh = borders[2].width;
border.args = drawSide({
c1: [bx + bw, by + bh],
c2: [bx, by + bh],
c3: [bx + borders[3].width, by],
c4: [bx + bw - borders[3].width, by]
}, radius[2], radius[3],
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
break;
case 3:
// left border
bw = borders[3].width;
border.args = drawSide({
c1: [bx, by + bh + borders[2].width],
c2: [bx, by],
c3: [bx + bw, by + borders[0].width],
c4: [bx + bw, by + bh]
}, radius[3], radius[0],
borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
break;
}
}
return border;
@ -430,20 +459,20 @@ NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, bor
borderArgs = [];
switch(backgroundClip) {
case "content-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[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[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
break;
case "content-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[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[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
break;
default:
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[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);
break;
default:
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[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);
break;
}
return borderArgs;
@ -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();
this.shape(shape).clip();
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 />