/*
html2canvas 0.5.0-rc1
Copyright (c) 2014 Niklas von Hertzen
Released under MIT License
*/
(function(window, document, undefined){
window.html2canvas = function(nodeList, options) {
var container = createWindowClone(document, window.innerWidth, window.innerHeight);
var clonedWindow = container.contentWindow;
var element = (nodeList === undefined) ? document.body : nodeList[0];
var node = clonedWindow.document.documentElement;
var support = new Support();
var imageLoader = new ImageLoader(options, support);
options = options || {};
if (options.logging) {
window.html2canvas.logging = true;
window.html2canvas.start = Date.now();
}
var renderer = new CanvasRenderer(documentWidth(), documentHeight(), imageLoader);
var parser = new NodeParser(node, renderer, support, imageLoader, options);
window.console.log(parser);
};
function documentWidth () {
return Math.max(
Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
Math.max(document.body.offsetWidth, document.documentElement.offsetWidth),
Math.max(document.body.clientWidth, document.documentElement.clientWidth)
);
}
function documentHeight () {
return Math.max(
Math.max(document.body.scrollHeight, document.documentElement.scrollHeight),
Math.max(document.body.offsetHeight, document.documentElement.offsetHeight),
Math.max(document.body.clientHeight, document.documentElement.clientHeight)
);
}
function createWindowClone(ownerDocument, width, height) {
var documentElement = ownerDocument.documentElement.cloneNode(true),
container = ownerDocument.createElement("iframe");
container.style.display = "hidden";
container.style.position = "absolute";
container.style.width = width + "px";
container.style.height = height + "px";
ownerDocument.body.appendChild(container);
var documentClone = container.contentWindow.document;
documentClone.replaceChild(documentClone.adoptNode(documentElement), documentClone.documentElement);
return container;
}
function NodeParser(element, renderer, support, imageLoader, options) {
this.renderer = renderer;
this.options = options;
this.range = null;
this.support = support;
this.stack = new StackingContext(true, 1, element.ownerDocument, null);
var parent = new NodeContainer(element, null);
parent.visibile = parent.isElementVisible();
this.nodes = [parent].concat(this.getChildren(parent)).filter(function(container) {
return container.visible = container.isElementVisible();
});
this.images = imageLoader.fetch(this.nodes.filter(isElement));
this.createStackingContexts();
this.sortStackingContexts(this.stack);
this.images.ready.then(bind(function() {
log("Images loaded, starting parsing");
this.parse(this.stack);
options.onrendered(renderer.canvas);
}, this));
}
NodeParser.prototype.getChildren = function(parentContainer) {
return flatten([].filter.call(parentContainer.node.childNodes, renderableNode).map(function(node) {
var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);
return node.nodeType === Node.ELEMENT_NODE && container.length ? (container[0].isElementVisible() ? container.concat(this.getChildren(container[0])) : []) : container;
}, this));
};
NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
var stack = new StackingContext(hasOwnStacking, container.cssFloat('opacity'), container.node, container.parent);
stack.visible = container.visible;
var parentStack = stack.getParentStack(this);
parentStack.contexts.push(stack);
container.stack = stack;
};
NodeParser.prototype.createStackingContexts = function() {
this.nodes.forEach(function(container) {
if (isElement(container) && (this.isRootElement(container) || hasOpacity(container) || isPositionedForStacking(container) || this.isBodyWithTransparentRoot(container))) {
this.newStackingContext(container, true);
} else if (isElement(container) && (isPositioned(container))) {
this.newStackingContext(container, false);
} else {
container.assignStack(container.parent.stack);
}
}, this);
};
NodeParser.prototype.isBodyWithTransparentRoot = function(container) {
return container.node.nodeName === "BODY" && this.renderer.isTransparent(container.parent.css('backgroundColor'));
};
NodeParser.prototype.isRootElement = function(container) {
return container.node.nodeName === "HTML";
};
NodeParser.prototype.sortStackingContexts = function(stack) {
stack.contexts.sort(zIndexSort);
stack.contexts.forEach(this.sortStackingContexts, this);
};
NodeParser.prototype.parseBounds = function(nodeContainer) {
return nodeContainer.bounds = this.getBounds(nodeContainer.node);
};
NodeParser.prototype.getBounds = function(node) {
if (node.getBoundingClientRect) {
var clientRect = node.getBoundingClientRect();
return {
top: clientRect.top,
bottom: clientRect.bottom || (clientRect.top + clientRect.height),
left: clientRect.left,
width: node.offsetWidth,
height: node.offsetHeight
};
}
return {};
};
NodeParser.prototype.parseTextBounds = function(container) {
return function(text, index, textList) {
if (container.parent.css("textDecoration") !== "none" || text.trim().length !== 0) {
var offset = textList.slice(0, index).join("").length;
if (this.support.rangeBounds) {
return this.getRangeBounds(container.node, offset, text.length);
} else if (container.node && typeof(container.node.data) === "string") {
var replacementNode = container.node.splitText(text.length);
var bounds = this.getWrapperBounds(container.node);
container.node = replacementNode;
return bounds;
}
}
};
};
NodeParser.prototype.getWrapperBounds = function(node) {
var wrapper = node.ownerDocument.createElement('wrapper');
var parent = node.parentNode,
backupText = node.cloneNode(true);
wrapper.appendChild(node.cloneNode(true));
parent.replaceChild(wrapper, node);
var bounds = this.getBounds(wrapper);
parent.replaceChild(backupText, wrapper);
return bounds;
};
NodeParser.prototype.getRangeBounds = function(node, offset, length) {
var range = this.range || (this.range = node.ownerDocument.createRange());
range.setStart(node, offset);
range.setEnd(node, offset + length);
return range.getBoundingClientRect();
};
function negativeZIndex(container) {
return container.cssInt("zIndex") < 0;
}
function positiveZIndex(container) {
return container.cssInt("zIndex") > 0;
}
function zIndex0(container) {
return container.cssInt("zIndex") === 0;
}
function inlineLevel(container) {
return ["inline", "inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
}
function isStackingContext(container) {
return (container instanceof StackingContext);
}
function hasText(container) {
return container.node.data.trim().length > 0;
}
function noLetterSpacing(container) {
return (/^(normal|none|0px)$/.test(container.parent.css("letterSpacing")));
}
NodeParser.prototype.parse = function(stack) {
// http://www.w3.org/TR/CSS21/visuren.html#z-index
var negativeZindex = stack.contexts.filter(negativeZIndex); // 2. the child stacking contexts with negative stack levels (most negative first).
var descendantElements = stack.children.filter(isElement);
var descendantNonFloats = descendantElements.filter(not(isFloating));
var nonInlineNonPositionedDescendants = descendantNonFloats.filter(not(isPositioned)).filter(not(inlineLevel)); // 3 the in-flow, non-inline-level, non-positioned descendants.
var nonPositionedFloats = descendantElements.filter(not(isPositioned)).filter(isFloating); // 4. the non-positioned floats.
var inFlow = descendantNonFloats.filter(not(isPositioned)).filter(inlineLevel); // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
var stackLevel0 = stack.contexts.concat(descendantNonFloats.filter(isPositioned)).filter(zIndex0); // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
var text = stack.children.filter(isTextNode).filter(hasText);
var positiveZindex = stack.contexts.filter(positiveZIndex); // 7. the child stacking contexts with positive stack levels (least positive first).
var rendered = [];
negativeZindex.concat(nonInlineNonPositionedDescendants).concat(nonPositionedFloats)
.concat(inFlow).concat(stackLevel0).concat(text).concat(positiveZindex).forEach(function(container) {
this.paint(container);
if (rendered.indexOf(container.node) !== -1) {
window.console.log(container, container.node);
throw new Error("rendering twice");
}
rendered.push(container.node);
if (isStackingContext(container)) {
this.parse(container);
}
}, this);
};
NodeParser.prototype.paint = function(container) {
if (isTextNode(container)) {
this.paintText(container);
} else {
this.paintNode(container);
}
};
NodeParser.prototype.paintNode = function(container) {
if (isStackingContext(container)) {
this.renderer.setOpacity(container.opacity);
}
var bounds = this.parseBounds(container);
var borderData = this.parseBorders(container);
this.renderer.clip(borderData.clip, function() {
this.renderer.renderBackground(container, bounds);
}, this);
this.renderer.renderBorders(borderData.borders);
};
NodeParser.prototype.paintText = function(container) {
container.applyTextTransform();
var textList = container.node.data.split(!this.options.letterRendering || noLetterSpacing(container) ? /(\b| )/ : "");
var weight = container.parent.fontWeight();
var size = container.parent.css('fontSize');
var family = container.parent.css('fontFamily');
this.renderer.font(container.parent.css('color'), container.parent.css('fontStyle'), container.parent.css('fontVariant'), weight, size, family);
textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {
if (bounds) {
this.renderer.text(textList[index], bounds.left, bounds.bottom);
// renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
}
/* var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix);
if (bounds) {
drawText(text, bounds.left, bounds.bottom, ctx);
renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
} */
}, this);
};
NodeParser.prototype.parseBorders = function(container) {
var nodeBounds = container.bounds;
var radius = getBorderRadiusData(container);
var borders = ["Top", "Right", "Bottom", "Left"].map(function(side) {
return {
width: container.cssInt('border' + side + 'Width'),
color: container.css('border' + side + 'Color'),
args: null
};
});
var borderPoints = calculateCurvePoints(nodeBounds, radius, borders);
return {
clip: this.parseBackgroundClip(container, borderPoints, borders, radius, nodeBounds),
borders: borders.map(function(border, borderSide) {
if (border.width > 0) {
var bx = nodeBounds.left;
var by = nodeBounds.top;
var bw = nodeBounds.width;
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;
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;
})
};
};
NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, borders, radius, bounds) {
var backgroundClip = container.css('backgroundClip'),
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;
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;
};
function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
if (radius1[0] > 0 || radius1[1] > 0) {
borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
corner1[0].curveTo(borderArgs);
corner1[1].curveTo(borderArgs);
} else {
borderArgs.push(["line", x, y]);
}
if (radius2[0] > 0 || radius2[1] > 0) {
borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
}
}
function getBorderRadiusData(container) {
return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
var value = container.css('border' + side + 'Radius');
var arr = value.split(" ");
if (arr.length <= 1) {
arr[1] = arr[0];
}
return arr.map(asInt);
});
}
function asInt(value) {
return parseInt(value, 10);
}
function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
var ox = (r1) * kappa, // control point offset horizontal
oy = (r2) * kappa, // control point offset vertical
xm = x + r1, // x-middle
ym = y + r2; // y-middle
return {
topLeft: bezierCurve({x: x, y: ym}, {x: x, y: ym - oy}, {x: xm - ox, y: y}, {x: xm, y: y}),
topRight: bezierCurve({x: x, y: y}, {x: x + ox,y: y}, {x: xm, y: ym - oy}, {x: xm, y: ym}),
bottomRight: bezierCurve({x: xm, y: y}, {x: xm, y: y + oy}, {x: x + ox, y: ym}, {x: x, y: ym}),
bottomLeft: bezierCurve({x: xm, y: ym}, {x: xm - ox, y: ym}, {x: x, y: y + oy}, {x: x, y:y})
};
}
function calculateCurvePoints(bounds, borderRadius, borders) {
var x = bounds.left,
y = bounds.top,
width = bounds.width,
height = bounds.height,
tlh = borderRadius[0][0],
tlv = borderRadius[0][1],
trh = borderRadius[1][0],
trv = borderRadius[1][1],
brh = borderRadius[2][0],
brv = borderRadius[2][1],
blh = borderRadius[3][0],
blv = borderRadius[3][1];
var topWidth = width - trh,
rightHeight = height - brv,
bottomWidth = width - brh,
leftHeight = height - blv;
return {
topLeftOuter: getCurvePoints(x, y, tlh, tlv).topLeft.subdivide(0.5),
topLeftInner: getCurvePoints(x + borders[3].width, y + borders[0].width, Math.max(0, tlh - borders[3].width), Math.max(0, tlv - borders[0].width)).topLeft.subdivide(0.5),
topRightOuter: getCurvePoints(x + topWidth, y, trh, trv).topRight.subdivide(0.5),
topRightInner: getCurvePoints(x + Math.min(topWidth, width + borders[3].width), y + borders[0].width, (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width, trv - borders[0].width).topRight.subdivide(0.5),
bottomRightOuter: getCurvePoints(x + bottomWidth, y + rightHeight, brh, brv).bottomRight.subdivide(0.5),
bottomRightInner: getCurvePoints(x + Math.min(bottomWidth, width + borders[3].width), y + Math.min(rightHeight, height + borders[0].width), Math.max(0, brh - borders[1].width), Math.max(0, brv - borders[2].width)).bottomRight.subdivide(0.5),
bottomLeftOuter: getCurvePoints(x, y + leftHeight, blh, blv).bottomLeft.subdivide(0.5),
bottomLeftInner: getCurvePoints(x + borders[3].width, y + leftHeight, Math.max(0, blh - borders[3].width), Math.max(0, blv - borders[2].width)).bottomLeft.subdivide(0.5)
};
}
function bezierCurve(start, startControl, endControl, end) {
var lerp = function (a, b, t) {
return {
x: a.x + (b.x - a.x) * t,
y: a.y + (b.y - a.y) * t
};
};
return {
start: start,
startControl: startControl,
endControl: endControl,
end: end,
subdivide: function(t) {
var ab = lerp(start, startControl, t),
bc = lerp(startControl, endControl, t),
cd = lerp(endControl, end, t),
abbc = lerp(ab, bc, t),
bccd = lerp(bc, cd, t),
dest = lerp(abbc, bccd, t);
return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
},
curveTo: function(borderArgs) {
borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
},
curveToReversed: function(borderArgs) {
borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
}
};
}
function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
var borderArgs = [];
if (radius1[0] > 0 || radius1[1] > 0) {
borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
outer1[1].curveTo(borderArgs);
} else {
borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]);
}
if (radius2[0] > 0 || radius2[1] > 0) {
borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
outer2[0].curveTo(borderArgs);
borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
inner2[0].curveToReversed(borderArgs);
} else {
borderArgs.push(["line", borderData.c2[0], borderData.c2[1]]);
borderArgs.push(["line", borderData.c3[0], borderData.c3[1]]);
}
if (radius1[0] > 0 || radius1[1] > 0) {
borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
inner1[1].curveToReversed(borderArgs);
} else {
borderArgs.push(["line", borderData.c4[0], borderData.c4[1]]);
}
return borderArgs;
}
function nonIgnoredElement(nodeContainer) {
return (nodeContainer.node.nodeType !== Node.ELEMENT_NODE || ["SCRIPT", "HEAD", "TITLE", "OBJECT", "BR"].indexOf(nodeContainer.node.nodeName) === -1);
}
function flatten(arrays) {
return [].concat.apply([], arrays);
}
function renderableNode(node) {
return (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE);
}
function isPositionedForStacking(container) {
var position = container.css("position");
var zIndex = (position === "absolute" || position === "relative") ? container.css("zIndex") : "auto";
return zIndex !== "auto";
}
function isPositioned(container) {
return container.css("position") !== "static";
}
function isFloating(container) {
return container.css("float") !== "none";
}
function not(callback) {
var context = this;
return function() {
return !callback.apply(context, arguments);
};
}
function isElement(container) {
return container.node.nodeType === Node.ELEMENT_NODE;
}
function isTextNode(container) {
return container.node.nodeType === Node.TEXT_NODE;
}
function zIndexSort(a, b) {
return a.cssInt("zIndex") - b.cssInt("zIndex");
}
function hasOpacity(container) {
return container.css("opacity") < 1;
}
function bind(callback, context) {
return function() {
return callback.apply(context, arguments);
};
}
function ImageContainer(src, cors) {
this.src = src;
this.image = new Image();
var image = this.image;
this.promise = new Promise(function(resolve, reject) {
image.onload = resolve;
image.onerror = reject;
if (cors) {
image.crossOrigin = "anonymous";
}
image.src = src;
});
}
function ImageLoader(options, support) {
this.link = null;
this.options = options;
this.support = support;
this.origin = window.location.protocol + window.location.host;
}
ImageLoader.prototype.findImages = function(images, container) {
var backgrounds = container.parseBackgroundImages();
var backgroundImages = backgrounds.filter(this.isImageBackground).map(this.getBackgroundUrl).filter(this.imageExists(images)).map(this.loadImage, this);
return images.concat(backgroundImages);
};
ImageLoader.prototype.getBackgroundUrl = function(imageData) {
return imageData.args[0];
};
ImageLoader.prototype.isImageBackground = function(imageData) {
return imageData.method === "url";
};
ImageLoader.prototype.loadImage = function(src) {
if (src.match(/data:image\/.*;base64,/i)) {
return new ImageContainer(src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''), false);
} else if (this.isSameOrigin(src) || this.options.allowTaint === true) {
return new ImageContainer(src, false);
} else if (this.support.cors && !this.options.allowTaint && this.options.useCORS) {
return new ImageContainer(src, true);
} else if (this.options.proxy) {
return new ProxyImageContainer(src);
} else {
return new DummyImageContainer(src);
}
};
ImageLoader.prototype.imageExists = function(images) {
return function(newImage) {
return !images.some(function(image) {
return image.src !== newImage.src;
});
};
};
ImageLoader.prototype.isSameOrigin = function(url) {
var link = this.link || (this.link = document.createElement("a"));
link.href = url;
link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
var origin = link.protocol + link.host;
return (origin === this.origin);
};
ImageLoader.prototype.getPromise = function(container) {
return container.promise;
};
ImageLoader.prototype.get = function(src) {
var found = null;
return this.images.some(function(img) {
return (found = img).src === src;
}) ? found : null;
};
ImageLoader.prototype.fetch = function(nodes) {
this.images = nodes.reduce(bind(this.findImages, this), []);
this.ready = Promise.all(this.images.map(this.getPromise));
return this;
};
function log() {
if (window.html2canvas.logging && window.console && window.console.log) {
window.console.log.apply(window.console, [(Date.now() - window.html2canvas.start) + "ms", "html2canvas:"].concat([].slice.call(arguments, 0)));
}
}
function NodeContainer(node, parent) {
this.node = node;
this.parent = parent;
this.stack = null;
this.bounds = null;
this.visible = null;
this.computedStyles = null;
this.styles = {};
this.backgroundImages = null;
}
NodeContainer.prototype.assignStack = function(stack) {
this.stack = stack;
stack.children.push(this);
};
NodeContainer.prototype.isElementVisible = function() {
return this.node.nodeType === Node.TEXT_NODE ? this.parent.visible : (this.css('display') !== "none" && this.css('visibility') !== "hidden" && !this.node.hasAttribute("data-html2canvas-ignore"));
};
NodeContainer.prototype.css = function(attribute) {
if (!this.computedStyles) {
this.computedStyles = this.node.ownerDocument.defaultView.getComputedStyle(this.node, null);
}
return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);
};
NodeContainer.prototype.cssInt = function(attribute) {
var value = parseInt(this.css(attribute), 10);
return (Number.isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html
};
NodeContainer.prototype.cssFloat = function(attribute) {
var value = parseFloat(this.css(attribute));
return (Number.isNaN(value)) ? 0 : value;
};
NodeContainer.prototype.fontWeight = function() {
var weight = this.css("fontWeight");
switch(parseInt(weight, 10)){
case 401:
weight = "bold";
break;
case 400:
weight = "normal";
break;
}
return weight;
};
NodeContainer.prototype.parseBackgroundImages = function() {
var whitespace = ' \r\n\t',
method, definition, prefix, prefix_i, block, results = [],
mode = 0, numParen = 0, quote, args;
var appendResult = function() {
if(method) {
if (definition.substr(0, 1) === '"') {
definition = definition.substr(1, definition.length - 2);
}
if (definition) {
args.push(definition);
}
if (method.substr(0, 1) === '-' && (prefix_i = method.indexOf('-', 1 ) + 1) > 0) {
prefix = method.substr(0, prefix_i);
method = method.substr(prefix_i);
}
results.push({
prefix: prefix,
method: method.toLowerCase(),
value: block,
args: args,
image: null
});
}
args = [];
method = prefix = definition = block = '';
};
args = [];
method = prefix = definition = block = '';
this.css("backgroundImage").split("").forEach(function(c) {
if (mode === 0 && whitespace.indexOf(c) > -1) {
return;
}
switch(c) {
case '"':
if(!quote) {
quote = c;
}
else if(quote === c) {
quote = null;
}
break;
case '(':
if(quote) {
break;
} else if(mode === 0) {
mode = 1;
block += c;
return;
} else {
numParen++;
}
break;
case ')':
if (quote) {
break;
} else if(mode === 1) {
if(numParen === 0) {
mode = 0;
block += c;
appendResult();
return;
} else {
numParen--;
}
}
break;
case ',':
if (quote) {
break;
} else if(mode === 0) {
appendResult();
return;
} else if (mode === 1) {
if (numParen === 0 && !method.match(/^url$/i)) {
args.push(definition);
definition = '';
block += c;
return;
}
}
break;
}
block += c;
if (mode === 0) {
method += c;
} else {
definition += c;
}
});
appendResult();
return this.backgroundImages || (this.backgroundImages = results);
};
NodeContainer.prototype.cssList = function(property, index) {
var value = (this.css(property) || '').split(',');
value = value[index || 0] || value[0] || 'auto';
value = value.trim().split(' ');
if (value.length === 1) {
value = [value[0], value[0]];
}
return value;
};
NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) {
var size = this.cssList("backgroundSize", index);
var width, height;
if (isPercentage(size[0])) {
width = bounds.width * parseFloat(size[0]) / 100;
} else if (/contain|cover/.test(size[0])) {
var targetRatio = bounds.width / bounds.height, currentRatio = image.width / image.height;
return (targetRatio < currentRatio ^ size[0] === 'contain') ? {width: bounds.height * currentRatio, height: bounds.height} : {width: bounds.width, height: bounds.width / currentRatio};
} else {
width = parseInt(size[0], 10);
}
if (size[0] === 'auto' && size[1] === 'auto') {
height = image.height;
} else if (size[1] === 'auto') {
height = width / image.width * image.height;
} else if (isPercentage(size[1])) {
height = bounds.height * parseFloat(size[1]) / 100;
} else {
height = parseInt(size[1], 10);
}
if (size[0] === 'auto') {
width = height / image.height * image.width;
}
return {width: width, height: height};
};
NodeContainer.prototype.parseBackgroundPosition = function(bounds, image, index, backgroundSize) {
var position = this.cssList('backgroundPosition', index);
var left, top;
if (isPercentage(position[0])){
left = (bounds.width - (backgroundSize || image).width) * (parseFloat(position[0]) / 100);
} else {
left = parseInt(position[0], 10);
}
if (position[1] === 'auto') {
top = left / image.width * image.height;
} else if (isPercentage(position[1])){
top = (bounds.height - (backgroundSize || image).height) * parseFloat(position[1]) / 100;
} else {
top = parseInt(position[1], 10);
}
if (position[0] === 'auto') {
left = top / image.height * image.width;
}
return {left: left, top: top};
};
NodeContainer.prototype.parseBackgroundRepeat = function(index) {
return this.cssList("backgroundRepeat", index)[0];
};
function isPercentage(value) {
return value.toString().indexOf("%") !== -1;
}
function Renderer(width, height, images) {
this.width = width;
this.height = height;
this.images = images;
}
Renderer.prototype.renderBackground = function(container, bounds) {
if (bounds.height > 0 && bounds.width > 0) {
this.renderBackgroundColor(container, bounds);
this.renderBackgroundImage(container, bounds);
}
};
Renderer.prototype.renderBackgroundColor = function(container, bounds) {
var color = container.css("backgroundColor");
if (!this.isTransparent(color)) {
this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, container.css("backgroundColor"));
}
};
Renderer.prototype.renderBorders = function(borders) {
borders.forEach(this.renderBorder, this);
};
Renderer.prototype.renderBorder = function(data) {
if (!this.isTransparent(data.color) && data.args !== null) {
this.drawShape(data.args, data.color);
}
};
Renderer.prototype.renderBackgroundImage = function(container, bounds) {
var backgroundImages = container.parseBackgroundImages();
backgroundImages.reverse().forEach(function(backgroundImage, index) {
if (backgroundImage.method === "url") {
var image = this.images.get(backgroundImage.args[0]);
if (image) {
this.renderBackgroundRepeating(container, bounds, image, index);
} else {
log("Error loading background-image", backgroundImage.args[0]);
}
}
}, this);
};
Renderer.prototype.renderBackgroundRepeating = function(container, bounds, imageContainer, index) {
var size = container.parseBackgroundSize(bounds, imageContainer.image, index);
var position = container.parseBackgroundPosition(bounds, imageContainer.image, index, size);
var repeat = container.parseBackgroundRepeat(index);
// image = resizeImage(image, backgroundSize);
switch (repeat) {
case "repeat-x":
case "repeat no-repeat":
this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left, bounds.top + position.top, 99999, imageContainer.image.height);
break;
case "repeat-y":
case "no-repeat repeat":
this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left + position.left, bounds.top, imageContainer.image.width, 99999);
break;
case "no-repeat":
this.backgroundRepeatShape(imageContainer, position, bounds, bounds.left + position.left, bounds.top + position.top, imageContainer.image.width, imageContainer.image.height);
break;
default:
this.renderBackgroundRepeat(imageContainer, position, {top: bounds.top, left: bounds.left});
break;
}
};
Renderer.prototype.isTransparent = function(color) {
return (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)");
};
function CanvasRenderer(width, height) {
Renderer.apply(this, arguments);
this.canvas = document.createElement("canvas");
this.canvas.width = width;
this.canvas.height = height;
this.ctx = this.canvas.getContext("2d");
this.ctx.textBaseline = "bottom";
}
CanvasRenderer.prototype = Object.create(Renderer.prototype);
CanvasRenderer.prototype.setFillStyle = function(color) {
this.ctx.fillStyle = color;
return this.ctx;
};
CanvasRenderer.prototype.rectangle = function(left, top, width, height, color) {
this.setFillStyle(color).fillRect(left, top, width, height);
};
CanvasRenderer.prototype.drawShape = function(shape, color) {
this.shape(shape);
this.setFillStyle(color).fill();
};
CanvasRenderer.prototype.clip = function(shape, callback, context) {
this.ctx.save();
this.shape(shape).clip();
callback.call(context);
this.ctx.restore();
};
CanvasRenderer.prototype.shape = function(shape) {
this.ctx.beginPath();
shape.forEach(function(point, index) {
this.ctx[(index === 0) ? "moveTo" : point[0] + "To" ].apply(this.ctx, point.slice(1));
}, this);
this.ctx.closePath();
return this.ctx;
};
CanvasRenderer.prototype.font = function(color, style, variant, weight, size, family) {
this.setFillStyle(color).font = [style, variant, weight, size, family].join(" ");
};
CanvasRenderer.prototype.setOpacity = function(opacity) {
this.ctx.globalAlpha = opacity;
};
CanvasRenderer.prototype.text = function(text, left, bottom) {
this.ctx.fillText(text, left, bottom);
};
CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, bounds, left, top, width, height) {
var shape = [
["line", Math.round(left), Math.round(top)],
["line", Math.round(left + width), Math.round(top)],
["line", Math.round(left + width), Math.round(height + top)],
["line", Math.round(left), Math.round(height + top)]
];
console.log(shape);
this.clip(shape, function() {
this.renderBackgroundRepeat(imageContainer, backgroundPosition, bounds);
}, this);
};
CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, bounds) {
var offsetX = Math.round(bounds.left + backgroundPosition.left), offsetY = Math.round(bounds.top + backgroundPosition.top);
this.setFillStyle(this.ctx.createPattern(imageContainer.image, "repeat"));
this.ctx.translate(offsetX, offsetY);
this.ctx.fill();
this.ctx.translate(-offsetX, -offsetY);
};
function StackingContext(hasOwnStacking, opacity, element, parent) {
NodeContainer.call(this, element, parent);
this.ownStacking = hasOwnStacking;
this.contexts = [];
this.children = [];
this.opacity = (this.parent ? this.parent.stack.opacity : 1) * opacity;
}
StackingContext.prototype = Object.create(NodeContainer.prototype);
StackingContext.prototype.getParentStack = function(context) {
var parentStack = (this.parent) ? this.parent.stack : null;
return parentStack ? (parentStack.ownStacking ? parentStack : parentStack.getParentStack(context)) : context.stack;
};
function Support() {
this.rangeBounds = this.testRangeBounds();
this.cors = this.testCORS();
}
Support.prototype.testRangeBounds = function() {
var range, testElement, rangeBounds, rangeHeight, support = false;
if (document.createRange) {
range = document.createRange();
if (range.getBoundingClientRect) {
testElement = document.createElement('boundtest');
testElement.style.height = "123px";
testElement.style.display = "block";
document.body.appendChild(testElement);
range.selectNode(testElement);
rangeBounds = range.getBoundingClientRect();
rangeHeight = rangeBounds.height;
if (rangeHeight === 123) {
support = true;
}
document.body.removeChild(testElement);
}
}
return support;
};
Support.prototype.testCORS = function() {
return typeof((new Image()).crossOrigin) !== "undefined";
};
function TextContainer(node, parent) {
NodeContainer.call(this, node, parent);
}
TextContainer.prototype = Object.create(NodeContainer.prototype);
TextContainer.prototype.applyTextTransform = function() {
this.node.data = this.transform(this.parent.css("textTransform"));
};
TextContainer.prototype.transform = function(transform) {
var text = this.node.data;
switch(transform){
case "lowercase":
return text.toLowerCase();
case "capitalize":
return text.replace(/(^|\s|:|-|\(|\))([a-z])/g, capitalize);
case "uppercase":
return text.toUpperCase();
default:
return text;
}
};
function capitalize(m, p1, p2) {
if (m.length > 0) {
return p1 + p2.toUpperCase();
}
}
})(window,document);