_html2canvas.Parse = function (images, options) { window.scroll(0,0); var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default numDraws = 0, doc = element.ownerDocument, support = _html2canvas.Util.Support(options, doc), ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"), body = doc.body, getCSS = _html2canvas.Util.getCSS, pseudoHide = "___html2canvas___pseudoelement", hidePseudoElements = doc.createElement('style'); hidePseudoElements.innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' + '.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }'; body.appendChild(hidePseudoElements); images = images || {}; function documentWidth () { return Math.max( Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth), Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth), Math.max(doc.body.clientWidth, doc.documentElement.clientWidth) ); } function documentHeight () { return Math.max( Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight), Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight), Math.max(doc.body.clientHeight, doc.documentElement.clientHeight) ); } function getCSSInt(element, attribute) { var val = parseInt(getCSS(element, attribute), 10); return (isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html } function renderRect (ctx, x, y, w, h, bgcolor) { if (bgcolor !== "transparent"){ ctx.setVariable("fillStyle", bgcolor); ctx.fillRect(x, y, w, h); numDraws+=1; } } function textTransform (text, transform) { switch(transform){ case "lowercase": return text.toLowerCase(); case "capitalize": return text.replace( /(^|\s|:|-|\(|\))([a-z])/g , function (m, p1, p2) { if (m.length > 0) { return p1 + p2.toUpperCase(); } } ); case "uppercase": return text.toUpperCase(); default: return text; } } function noLetterSpacing(letter_spacing) { return (/^(normal|none|0px)$/.test(letter_spacing)); } function drawText(currentText, x, y, ctx){ if (currentText !== null && _html2canvas.Util.trimText(currentText).length > 0) { ctx.fillText(currentText, x, y); numDraws+=1; } } function setTextVariables(ctx, el, text_decoration, color) { var align = false, bold = getCSS(el, "fontWeight"), family = getCSS(el, "fontFamily"), size = getCSS(el, "fontSize"), shadow = getCSS(el, "textShadow"); switch(parseInt(bold, 10)){ case 401: bold = "bold"; break; case 400: bold = "normal"; break; } ctx.setVariable("fillStyle", color); ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" ")); ctx.setVariable("textAlign", (align) ? "right" : "left"); if (shadow !== "none") { var shadows = _html2canvas.Util.parseTextShadows(shadow); // TODO: support multiple text shadows // apply the first text shadow ctx.setVariable("shadowColor", shadows[0].color); ctx.setVariable("shadowOffsetX", shadows[0].offsetX); ctx.setVariable("shadowOffsetY", shadows[0].offsetY); ctx.setVariable("shadowBlur", shadows[0].blur); } if (text_decoration !== "none"){ return _html2canvas.Util.Font(family, size, doc); } } function renderTextDecoration(ctx, text_decoration, bounds, metrics, color) { switch(text_decoration) { 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 renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color); break; case "overline": renderRect(ctx, bounds.left, Math.round(bounds.top), bounds.width, 1, color); break; case "line-through": // TODO try and find exact position for line-through renderRect(ctx, bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, color); break; } } function getTextBounds(state, text, textDecoration, isLast) { var bounds; if (support.rangeBounds) { if (textDecoration !== "none" || _html2canvas.Util.trimText(text).length !== 0) { bounds = textRangeBounds(text, state.node, state.textOffset); } state.textOffset += text.length; } else if (state.node && typeof state.node.nodeValue === "string" ){ var newTextNode = (isLast) ? state.node.splitText(text.length) : null; bounds = textWrapperBounds(state.node); state.node = newTextNode; } return bounds; } function textRangeBounds(text, textNode, textOffset) { var range = doc.createRange(); range.setStart(textNode, textOffset); range.setEnd(textNode, textOffset + text.length); return range.getBoundingClientRect(); } function textWrapperBounds(oldTextNode) { var parent = oldTextNode.parentNode, wrapElement = doc.createElement('wrapper'), backupText = oldTextNode.cloneNode(true); wrapElement.appendChild(oldTextNode.cloneNode(true)); parent.replaceChild(wrapElement, oldTextNode); var bounds = _html2canvas.Util.Bounds(wrapElement); parent.replaceChild(backupText, wrapElement); return bounds; } function renderText(el, textNode, stack) { var ctx = stack.ctx, color = getCSS(el, "color"), textDecoration = getCSS(el, "textDecoration"), textAlign = getCSS(el, "textAlign"), metrics, textList, state = { node: textNode, textOffset: 0 }; if (_html2canvas.Util.trimText(textNode.nodeValue).length > 0) { textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform")); textAlign = textAlign.replace(["-webkit-auto"],["auto"]); textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ? textNode.nodeValue.split(/(\b| )/) : textNode.nodeValue.split(""); metrics = setTextVariables(ctx, el, textDecoration, color); if (options.chinese) { textList.forEach(function(word, index) { if (/.*[\u4E00-\u9FA5].*$/.test(word)) { word = word.split(""); word.unshift(index, 1); textList.splice.apply(textList, word); } }); } textList.forEach(function(text, index) { var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1)); if (bounds) { drawText(text, bounds.left, bounds.bottom, ctx); renderTextDecoration(ctx, textDecoration, bounds, metrics, color); } }); } } function listPosition (element, val) { var boundElement = doc.createElement( "boundelement" ), originalType, bounds; boundElement.style.display = "inline"; originalType = element.style.listStyleType; element.style.listStyleType = "none"; boundElement.appendChild(doc.createTextNode(val)); element.insertBefore(boundElement, element.firstChild); bounds = _html2canvas.Util.Bounds(boundElement); element.removeChild(boundElement); element.style.listStyleType = originalType; return bounds; } function elementIndex( el ) { var i = -1, count = 1, childs = el.parentNode.childNodes; if (el.parentNode) { while( childs[ ++i ] !== el ) { if ( childs[ i ].nodeType === 1 ) { count++; } } return count; } else { return -1; } } function listItemText(element, type) { var currentIndex = elementIndex(element), text; switch(type){ case "decimal": text = currentIndex; break; case "decimal-leading-zero": text = (currentIndex.toString().length === 1) ? currentIndex = "0" + currentIndex.toString() : currentIndex.toString(); break; case "upper-roman": text = _html2canvas.Generate.ListRoman( currentIndex ); break; case "lower-roman": text = _html2canvas.Generate.ListRoman( currentIndex ).toLowerCase(); break; case "lower-alpha": text = _html2canvas.Generate.ListAlpha( currentIndex ).toLowerCase(); break; case "upper-alpha": text = _html2canvas.Generate.ListAlpha( currentIndex ); break; } text += ". "; return text; } function renderListItem(element, stack, elBounds) { var x, text, ctx = stack.ctx, type = getCSS(element, "listStyleType"), listBounds; if (/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)) { text = listItemText(element, type); listBounds = listPosition(element, text); setTextVariables(ctx, element, "none", getCSS(element, "color")); if (getCSS(element, "listStylePosition") === "inside") { ctx.setVariable("textAlign", "left"); x = elBounds.left; } else { return; } drawText(text, x, listBounds.bottom, ctx); } } function loadImage (src){ var img = images[src]; if (img && img.succeeded === true) { return img.img; } else { return false; } } function clipBounds(src, dst){ var x = Math.max(src.left, dst.left), y = Math.max(src.top, dst.top), x2 = Math.min((src.left + src.width), (dst.left + dst.width)), y2 = Math.min((src.top + src.height), (dst.top + dst.height)); return { left:x, top:y, width:x2-x, height:y2-y }; } function setZ(zIndex, parentZ){ // TODO fix static elements overlapping relative/absolute elements under same stack, if they are defined after them var newContext; if (!parentZ){ newContext = h2czContext(0); return newContext; } if (zIndex !== "auto"){ newContext = h2czContext(zIndex); parentZ.children.push(newContext); return newContext; } return parentZ; } function renderImage(ctx, element, image, bounds, borders) { var paddingLeft = getCSSInt(element, 'paddingLeft'), paddingTop = getCSSInt(element, 'paddingTop'), paddingRight = getCSSInt(element, 'paddingRight'), paddingBottom = getCSSInt(element, 'paddingBottom'); drawImage( ctx, image, 0, //sx 0, //sy image.width, //sw image.height, //sh bounds.left + paddingLeft + borders[3].width, //dx bounds.top + paddingTop + borders[0].width, // dy bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), //dw bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh ); } function getBorderData(element) { return ["Top", "Right", "Bottom", "Left"].map(function(side) { return { width: getCSSInt(element, 'border' + side + 'Width'), color: getCSS(element, 'border' + side + 'Color') }; }); } function getBorderRadiusData(element) { return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) { return getCSS(element, 'border' + side + 'Radius'); }); } var getCurvePoints = (function(kappa) { return function(x, y, r1, r2) { 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 }) }; }; })(4 * ((Math.sqrt(2) - 1) / 3)); 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 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 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 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], 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 getBorderClip(element, borderPoints, borders, radius, bounds) { var backgroundClip = getCSS(element, '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 parseBorders(element, bounds, borders){ var x = bounds.left, y = bounds.top, width = bounds.width, height = bounds.height, borderSide, bx, by, bw, bh, borderArgs, // http://www.w3.org/TR/css3-background/#the-border-radius borderRadius = getBorderRadiusData(element), borderPoints = calculateCurvePoints(bounds, borderRadius, borders), borderData = { clip: getBorderClip(element, borderPoints, borders, borderRadius, bounds), borders: [] }; for (borderSide = 0; borderSide < 4; borderSide++) { if (borders[borderSide].width > 0) { bx = x; by = y; bw = width; bh = height - (borders[2].width); switch(borderSide) { case 0: // top border bh = borders[0].width; borderArgs = drawSide({ c1: [bx, by], c2: [bx + bw, by], c3: [bx + bw - borders[1].width, by + bh], c4: [bx + borders[3].width, by + bh] }, borderRadius[0], borderRadius[1], borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner); break; case 1: // right border bx = x + width - (borders[1].width); bw = borders[1].width; borderArgs = drawSide({ c1: [bx + bw, by], c2: [bx + bw, by + bh + borders[2].width], c3: [bx, by + bh], c4: [bx, by + borders[0].width] }, borderRadius[1], borderRadius[2], borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner); break; case 2: // bottom border by = (by + height) - (borders[2].width); bh = borders[2].width; borderArgs = drawSide({ c1: [bx + bw, by + bh], c2: [bx, by + bh], c3: [bx + borders[3].width, by], c4: [bx + bw - borders[2].width, by] }, borderRadius[2], borderRadius[3], borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner); break; case 3: // left border bw = borders[3].width; borderArgs = drawSide({ c1: [bx, by + bh + borders[2].width], c2: [bx, by], c3: [bx + bw, by + borders[0].width], c4: [bx + bw, by + bh] }, borderRadius[3], borderRadius[0], borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner); break; } borderData.borders.push({ args: borderArgs, color: borders[borderSide].color }); } } return borderData; } function createShape(ctx, args) { var shape = ctx.drawShape(); args.forEach(function(border, index) { shape[(index === 0) ? "moveTo" : border[0] + "To" ].apply(null, border.slice(1)); }); return shape; } function renderBorders(ctx, borderArgs, color) { if (color !== "transparent") { ctx.setVariable( "fillStyle", color); createShape(ctx, borderArgs); ctx.fill(); numDraws+=1; } } function renderFormValue (el, bounds, stack){ var valueWrap = doc.createElement('valuewrap'), cssPropertyArray = ['lineHeight','textAlign','fontFamily','color','fontSize','paddingLeft','paddingTop','width','height','border','borderLeftWidth','borderTopWidth'], textValue, textNode; cssPropertyArray.forEach(function(property) { try { valueWrap.style[property] = getCSS(el, property); } catch(e) { // Older IE has issues with "border" h2clog("html2canvas: Parse: Exception caught in renderFormValue: " + e.message); } }); valueWrap.style.borderColor = "black"; valueWrap.style.borderStyle = "solid"; valueWrap.style.display = "block"; valueWrap.style.position = "absolute"; if (/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName === "SELECT"){ valueWrap.style.lineHeight = getCSS(el, "height"); } valueWrap.style.top = bounds.top + "px"; valueWrap.style.left = bounds.left + "px"; textValue = (el.nodeName === "SELECT") ? (el.options[el.selectedIndex] || 0).text : el.value; if(!textValue) { textValue = el.placeholder; } textNode = doc.createTextNode(textValue); valueWrap.appendChild(textNode); body.appendChild(valueWrap); renderText(el, textNode, stack); body.removeChild(valueWrap); } function drawImage (ctx) { ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 1)); numDraws+=1; } function getPseudoElement(el, which) { var elStyle = window.getComputedStyle(el, which); if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") { return; } var content = elStyle.content + '', first = content.substr( 0, 1 ); //strips quotes if(first === content.substr( content.length - 1 ) && first.match(/'|"/)) { content = content.substr( 1, content.length - 2 ); } var isImage = content.substr( 0, 3 ) === 'url', elps = document.createElement( isImage ? 'img' : 'span' ); elps.className = pseudoHide + "-before " + pseudoHide + "-after"; Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) { // Prevent assigning of read only CSS Rules, ex. length, parentRule try { elps.style[prop] = elStyle[prop]; } catch (e) { h2clog(['Tried to assign readonly property ', prop, 'Error:', e]); } }); if(isImage) { elps.src = _html2canvas.Util.parseBackgroundImage(content)[0].args[0]; } else { elps.innerHTML = content; } return elps; } function indexedProperty(property) { return (isNaN(window.parseInt(property, 10))); } function injectPseudoElements(el, stack) { var before = getPseudoElement(el, ':before'), after = getPseudoElement(el, ':after'); if(!before && !after) { return; } if(before) { el.className += " " + pseudoHide + "-before"; el.parentNode.insertBefore(before, el); parseElement(before, stack, true); el.parentNode.removeChild(before); el.className = el.className.replace(pseudoHide + "-before", "").trim(); } if (after) { el.className += " " + pseudoHide + "-after"; el.appendChild(after); parseElement(after, stack, true); el.removeChild(after); el.className = el.className.replace(pseudoHide + "-after", "").trim(); } } function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) { var offsetX = Math.round(bounds.left + backgroundPosition.left), offsetY = Math.round(bounds.top + backgroundPosition.top); ctx.createPattern(image); ctx.translate(offsetX, offsetY); ctx.fill(); ctx.translate(-offsetX, -offsetY); } function backgroundRepeatShape(ctx, image, backgroundPosition, bounds, left, top, width, height) { var args = []; args.push(["line", Math.round(left), Math.round(top)]); args.push(["line", Math.round(left + width), Math.round(top)]); args.push(["line", Math.round(left + width), Math.round(height + top)]); args.push(["line", Math.round(left), Math.round(height + top)]); createShape(ctx, args); ctx.save(); ctx.clip(); renderBackgroundRepeat(ctx, image, backgroundPosition, bounds); ctx.restore(); } function renderBackgroundColor(ctx, backgroundBounds, bgcolor) { renderRect( ctx, backgroundBounds.left, backgroundBounds.top, backgroundBounds.width, backgroundBounds.height, bgcolor ); } function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) { var backgroundSize = _html2canvas.Util.BackgroundSize(el, bounds, image, imageIndex), backgroundPosition = _html2canvas.Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize), backgroundRepeat = getCSS(el, "backgroundRepeat").split(",").map(function(value) { return value.trim(); }); image = resizeImage(image, backgroundSize); backgroundRepeat = backgroundRepeat[imageIndex] || backgroundRepeat[0]; switch (backgroundRepeat) { case "repeat-x": backgroundRepeatShape(ctx, image, backgroundPosition, bounds, bounds.left, bounds.top + backgroundPosition.top, 99999, image.height); break; case "repeat-y": backgroundRepeatShape(ctx, image, backgroundPosition, bounds, bounds.left + backgroundPosition.left, bounds.top, image.width, 99999); break; case "no-repeat": backgroundRepeatShape(ctx, image, backgroundPosition, bounds, bounds.left + backgroundPosition.left, bounds.top + backgroundPosition.top, image.width, image.height); break; default: renderBackgroundRepeat(ctx, image, backgroundPosition, { top: bounds.top, left: bounds.left, width: image.width, height: image.height }); break; } } function renderBackgroundImage(element, bounds, ctx) { var backgroundImage = getCSS(element, "backgroundImage"), backgroundImages = _html2canvas.Util.parseBackgroundImage(backgroundImage), image, imageIndex = backgroundImages.length; while(imageIndex--) { backgroundImage = backgroundImages[imageIndex]; if (!backgroundImage.args || backgroundImage.args.length === 0) { continue; } var key = backgroundImage.method === 'url' ? backgroundImage.args[0] : backgroundImage.value; image = loadImage(key); // TODO add support for background-origin if (image) { renderBackgroundRepeating(element, bounds, ctx, image, imageIndex); } else { h2clog("html2canvas: Error loading background:", backgroundImage); } } } function resizeImage(image, bounds) { if(image.width === bounds.width && image.height === bounds.height) { return image; } var ctx, canvas = doc.createElement('canvas'); canvas.width = bounds.width; canvas.height = bounds.height; ctx = canvas.getContext("2d"); drawImage(ctx, image, 0, 0, image.width, image.height, 0, 0, bounds.width, bounds.height ); return canvas; } function setOpacity(ctx, element, parentStack) { var opacity = getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1); ctx.setVariable("globalAlpha", opacity); return opacity; } function createStack(element, parentStack, bounds) { var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height), stack = { ctx: ctx, zIndex: setZ(getCSS(element, "zIndex"), (parentStack) ? parentStack.zIndex : null), opacity: setOpacity(ctx, element, parentStack), cssPosition: getCSS(element, "position"), borders: getBorderData(element), clip: (parentStack && parentStack.clip) ? _html2canvas.Util.Extend( {}, parentStack.clip ) : null }; // TODO correct overflow for absolute content residing under a static position if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){ stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds; } stack.zIndex.children.push(stack); return stack; } function getBackgroundBounds(borders, bounds, clip) { var backgroundBounds = { left: bounds.left + borders[3].width, top: bounds.top + borders[0].width, width: bounds.width - (borders[1].width + borders[3].width), height: bounds.height - (borders[0].width + borders[2].width) }; if (clip) { backgroundBounds = clipBounds(backgroundBounds, clip); } return backgroundBounds; } function renderElement(element, parentStack, pseudoElement){ var bounds = _html2canvas.Util.Bounds(element), image, bgcolor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor"), stack = createStack(element, parentStack, bounds), borders = stack.borders, ctx = stack.ctx, backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip), borderData = parseBorders(element, bounds, borders); createShape(ctx, borderData.clip); ctx.save(); ctx.clip(); if (backgroundBounds.height > 0 && backgroundBounds.width > 0){ renderBackgroundColor(ctx, bounds, bgcolor); renderBackgroundImage(element, backgroundBounds, ctx); } ctx.restore(); borderData.borders.forEach(function(border) { renderBorders(ctx, border.args, border.color); }); if (!pseudoElement) { injectPseudoElements(element, stack); } switch(element.nodeName){ case "IMG": if ((image = loadImage(element.getAttribute('src')))) { renderImage(ctx, element, image, bounds, borders); } else { h2clog("html2canvas: Error loading :" + element.getAttribute('src')); } break; case "INPUT": // TODO add all relevant type's, i.e. HTML5 new stuff // todo add support for placeholder attribute for browsers which support it if (/^(text|url|email|submit|button|reset)$/.test(element.type) && (element.value || element.placeholder || "").length > 0){ renderFormValue(element, bounds, stack); } break; case "TEXTAREA": if ((element.value || element.placeholder || "").length > 0){ renderFormValue(element, bounds, stack); } break; case "SELECT": if ((element.options||element.placeholder || "").length > 0){ renderFormValue(element, bounds, stack); } break; case "LI": renderListItem(element, stack, backgroundBounds); break; case "CANVAS": renderImage(ctx, element, element, bounds, borders); break; } return stack; } function isElementVisible(element) { return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore")); } function parseElement (el, stack, pseudoElement) { if (isElementVisible(el)) { stack = renderElement(el, stack, pseudoElement) || stack; if (!ignoreElementsRegExp.test(el.nodeName)) { _html2canvas.Util.Children(el).forEach(function(node) { if (node.nodeType === 1) { parseElement(node, stack, pseudoElement); } else if (node.nodeType === 3) { renderText(el, node, stack); } }); } } } function svgDOMRender(body, stack) { var img = new Image(), docWidth = documentWidth(), docHeight = documentHeight(), html = ""; function parseDOM(el) { var children = _html2canvas.Util.Children( el ), len = children.length, attr, a, alen, elm, i; for ( i = 0; i < len; i+=1 ) { elm = children[ i ]; if ( elm.nodeType === 3 ) { // Text node html += elm.nodeValue.replace(//g,">"); } else if ( elm.nodeType === 1 ) { // Element if ( !/^(script|meta|title)$/.test(elm.nodeName.toLowerCase()) ) { html += "<" + elm.nodeName.toLowerCase(); // add attributes if ( elm.hasAttributes() ) { attr = elm.attributes; alen = attr.length; for ( a = 0; a < alen; a+=1 ) { html += " " + attr[ a ].name + '="' + attr[ a ].value + '"'; } } html += '>'; parseDOM( elm ); html += ""; } } } } parseDOM(body); img.src = [ "data:image/svg+xml,", "", "", "", html.replace(/\#/g,"%23"), "", "", "" ].join(""); img.onload = function() { stack.svgRender = img; }; } function init() { var stack = renderElement(element, null); if (support.svgRendering) { svgDOMRender(document.documentElement, stack); } Array.prototype.slice.call(element.children, 0).forEach(function(childElement) { parseElement(childElement, stack); }); stack.backgroundColor = getCSS(document.documentElement, "backgroundColor"); body.removeChild(hidePseudoElements); return stack; } return init(); }; function h2czContext(zindex) { return { zindex: zindex, children: [] }; }