diff --git a/html2canvas.js b/html2canvas.js index df3ee7ec..43fc7a59 100644 --- a/html2canvas.js +++ b/html2canvas.js @@ -1,451 +1,1102 @@ /* - html2canvas 0.4.1 - Copyright (c) 2013 Niklas von Hertzen + html2canvas 0.5.0-beta3 + Copyright (c) 2016 Niklas von Hertzen - Released under MIT License + Released under License */ -(function(window, document, undefined){ - -//"use strict"; - -var _html2canvas = {}, -previousElement, -computedCSS, -html2canvas; - -_html2canvas.Util = {}; - -_html2canvas.Util.log = function(a) { - if (_html2canvas.logging && window.console && window.console.log) { - window.console.log(a); - } -}; - -_html2canvas.Util.trimText = (function(isNative){ - return function(input) { - return isNative ? isNative.apply(input) : ((input || '') + '').replace( /^\s+|\s+$/g , '' ); - }; -})(String.prototype.trim); - -_html2canvas.Util.asFloat = function(v) { - return parseFloat(v); -}; - -(function() { - // TODO: support all possible length values - var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g; - var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g; - _html2canvas.Util.parseTextShadows = function (value) { - if (!value || value === 'none') { - return []; +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.html2canvas=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + while (length--) { + array[length] = fn(array[length]); + } + return array; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings. + * @private + * @param {String} domain The domain name. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + return map(string.split(regexSeparators), fn).join('.'); + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * http://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols to a Punycode string of ASCII-only + * symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name to Unicode. Only the + * Punycoded parts of the domain name will be converted, i.e. it doesn't + * matter if you call it on a string that has already been converted to + * Unicode. + * @memberOf punycode + * @param {String} domain The Punycode domain name to convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(domain) { + return mapDomain(domain, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name to Punycode. Only the + * non-ASCII parts of the domain name will be converted, i.e. it doesn't + * matter if you call it with a domain that's already in ASCII. + * @memberOf punycode + * @param {String} domain The domain name to convert, as a Unicode string. + * @returns {String} The Punycode representation of the given domain name. + */ + function toASCII(domain) { + return mapDomain(domain, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.2.4', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { // in Rhino or a web browser + root.punycode = punycode; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],2:[function(_dereq_,module,exports){ +var log = _dereq_('./log'); + +function restoreOwnerScroll(ownerDocument, x, y) { + if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) { + ownerDocument.defaultView.scrollTo(x, y); } +} - // find multiple shadow declarations - var shadows = value.match(TEXT_SHADOW_PROPERTY), - results = []; - for (var i = 0; shadows && (i < shadows.length); i++) { - var s = shadows[i].match(TEXT_SHADOW_VALUES); - results.push({ - color: s[0], - offsetX: s[1] ? s[1].replace('px', '') : 0, - offsetY: s[2] ? s[2].replace('px', '') : 0, - blur: s[3] ? s[3].replace('px', '') : 0 - }); +function cloneCanvasContents(canvas, clonedCanvas) { + try { + if (clonedCanvas) { + clonedCanvas.width = canvas.width; + clonedCanvas.height = canvas.height; + clonedCanvas.getContext("2d").putImageData(canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height), 0, 0); + } + } catch(e) { + log("Unable to copy canvas content from", canvas, e); } - return results; - }; -})(); +} -_html2canvas.Util.parseBackgroundImage = function (value) { - var whitespace = ' \r\n\t', - method, definition, prefix, prefix_i, block, results = [], - c, mode = 0, numParen = 0, quote, args; +function cloneNode(node, javascriptEnabled) { + var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false); - 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 - }); + var child = node.firstChild; + while(child) { + if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') { + clone.appendChild(cloneNode(child, javascriptEnabled)); } - args = []; //for some odd reason, setting .length = 0 didn't work in safari - method = - prefix = - definition = - block = ''; - }; + child = child.nextSibling; + } - appendResult(); - for(var i = 0, ii = value.length; i -1){ - continue; + if (node.nodeType === 1) { + clone._scrollTop = node.scrollTop; + clone._scrollLeft = node.scrollLeft; + if (node.nodeName === "CANVAS") { + cloneCanvasContents(node, clone); + } else if (node.nodeName === "TEXTAREA" || node.nodeName === "SELECT") { + clone.value = node.value; } - 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; - continue; - } else { - numParen++; - } - break; + return clone; +} - case ')': - if(quote) { break; } - else if(mode === 1) { - if(numParen === 0) { - mode = 0; - block += c; - appendResult(); - continue; - } else { - numParen--; - } - } - break; +function initNode(node) { + if (node.nodeType === 1) { + node.scrollTop = node._scrollTop; + node.scrollLeft = node._scrollLeft; - case ',': - if(quote) { break; } - else if(mode === 0) { - appendResult(); - continue; - } - else if (mode === 1) { - if(numParen === 0 && !method.match(/^url$/i)) { - args.push(definition); - definition = ''; - block += c; - continue; - } - } - break; + var child = node.firstChild; + while(child) { + initNode(child); + child = child.nextSibling; } - - block += c; - if(mode === 0) { method += c; } - else { definition += c; } } - appendResult(); - - return results; -}; - -_html2canvas.Util.Bounds = function (element) { - var clientRect, bounds = {}; - - if (element.getBoundingClientRect){ - clientRect = element.getBoundingClientRect(); - - // TODO add scroll position to bounds, so no scrolling of window necessary - bounds.top = clientRect.top; - bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height); - bounds.left = clientRect.left; +} - bounds.width = element.offsetWidth; - bounds.height = element.offsetHeight; - } +module.exports = function(ownerDocument, containerDocument, width, height, options, x ,y) { + var documentElement = cloneNode(ownerDocument.documentElement, options.javascriptEnabled); + var container = containerDocument.createElement("iframe"); - return bounds; + container.className = "html2canvas-container"; + container.style.visibility = "hidden"; + container.style.position = "fixed"; + container.style.left = "-10000px"; + container.style.top = "0px"; + container.style.border = "0"; + container.width = width; + container.height = height; + container.scrolling = "no"; // ios won't scroll without it + containerDocument.body.appendChild(container); + + return new Promise(function(resolve) { + var documentClone = container.contentWindow.document; + + /* Chrome doesn't detect relative background-images assigned in inline sheets when fetched through getComputedStyle + if window url is about:blank, we can assign the url to current by writing onto the document + */ + container.contentWindow.onload = container.onload = function() { + var interval = setInterval(function() { + if (documentClone.body.childNodes.length > 0) { + initNode(documentClone.documentElement); + clearInterval(interval); + if (options.type === "view") { + container.contentWindow.scrollTo(x, y); + if ((/(iPad|iPhone|iPod)/g).test(navigator.userAgent) && (container.contentWindow.scrollY !== y || container.contentWindow.scrollX !== x)) { + documentClone.documentElement.style.top = (-y) + "px"; + documentClone.documentElement.style.left = (-x) + "px"; + documentClone.documentElement.style.position = 'absolute'; + } + } + resolve(container); + } + }, 50); + }; + + documentClone.open(); + documentClone.write(""); + // Chrome scrolls the parent document for some reason after the write to the cloned window??? + restoreOwnerScroll(ownerDocument, x, y); + documentClone.replaceChild(documentClone.adoptNode(documentElement), documentClone.documentElement); + documentClone.close(); + }); }; -// TODO ideally, we'd want everything to go through this function instead of Util.Bounds, -// but would require further work to calculate the correct positions for elements with offsetParents -_html2canvas.Util.OffsetBounds = function (element) { - var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {top: 0, left: 0}; +},{"./log":13}],3:[function(_dereq_,module,exports){ +// http://dev.w3.org/csswg/css-color/ + +function Color(value) { + this.r = 0; + this.g = 0; + this.b = 0; + this.a = null; + var result = this.fromArray(value) || + this.namedColor(value) || + this.rgb(value) || + this.rgba(value) || + this.hex6(value) || + this.hex3(value); +} - return { - top: element.offsetTop + parent.top, - bottom: element.offsetTop + element.offsetHeight + parent.top, - left: element.offsetLeft + parent.left, - width: element.offsetWidth, - height: element.offsetHeight - }; +Color.prototype.darken = function(amount) { + var a = 1 - amount; + return new Color([ + Math.round(this.r * a), + Math.round(this.g * a), + Math.round(this.b * a), + this.a + ]); }; -function toPX(element, attribute, value ) { - var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute], - left, - style = element.style; - - // Check if we are not dealing with pixels, (Opera has issues with this) - // Ported from jQuery css.js - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - - if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( value ) && /^-?\d/.test(value) ) { - // Remember the original values - left = style.left; +Color.prototype.isTransparent = function() { + return this.a === 0; +}; - // Put in the new values to get a computed value out - if (rsLeft) { - element.runtimeStyle.left = element.currentStyle.left; - } - style.left = attribute === "fontSize" ? "1em" : (value || 0); - value = style.pixelLeft + "px"; +Color.prototype.isBlack = function() { + return this.r === 0 && this.g === 0 && this.b === 0; +}; - // Revert the changed values - style.left = left; - if (rsLeft) { - element.runtimeStyle.left = rsLeft; +Color.prototype.fromArray = function(array) { + if (Array.isArray(array)) { + this.r = Math.min(array[0], 255); + this.g = Math.min(array[1], 255); + this.b = Math.min(array[2], 255); + if (array.length > 3) { + this.a = array[3]; } } - if (!/^(thin|medium|thick)$/i.test(value)) { - return Math.round(parseFloat(value)) + "px"; - } + return (Array.isArray(array)); +}; - return value; -} +var _hex3 = /^#([a-f0-9]{3})$/i; -function asInt(val) { - return parseInt(val, 10); -} +Color.prototype.hex3 = function(value) { + var match = null; + if ((match = value.match(_hex3)) !== null) { + this.r = parseInt(match[1][0] + match[1][0], 16); + this.g = parseInt(match[1][1] + match[1][1], 16); + this.b = parseInt(match[1][2] + match[1][2], 16); + } + return match !== null; +}; -function isPercentage(value) { - return value.toString().indexOf("%") !== -1; -} +var _hex6 = /^#([a-f0-9]{6})$/i; -function parseBackgroundSizePosition(value, element, attribute, index) { - value = (value || '').split(','); - value = value[index || 0] || value[0] || 'auto'; - value = _html2canvas.Util.trimText(value).split(' '); - if(attribute === 'backgroundSize' && (value[0] && value[0].match(/^(cover|contain|auto)$/))) { - return value; - } else { - value[0] = (value[0].indexOf( "%" ) === -1) ? toPX(element, attribute + "X", value[0]) : value[0]; - if(value[1] === undefined) { - if(attribute === 'backgroundSize') { - value[1] = 'auto'; - return value; - } else { - // IE 9 doesn't return double digit always - value[1] = value[0]; - } - } - value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1]; +Color.prototype.hex6 = function(value) { + var match = null; + if ((match = value.match(_hex6)) !== null) { + this.r = parseInt(match[1].substring(0, 2), 16); + this.g = parseInt(match[1].substring(2, 4), 16); + this.b = parseInt(match[1].substring(4, 6), 16); } - return value; -} + return match !== null; +}; -_html2canvas.Util.getCSS = function (element, attribute, index) { - if (previousElement !== element) { - computedCSS = document.defaultView.getComputedStyle(element, null); - } - var value = computedCSS[attribute]; +var _rgb = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/; - if (/^background(Size|Position)$/.test(attribute)) { - return parseBackgroundSizePosition(value, element, attribute, index); - } else if (/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) { - var arr = value.split(" "); - if (arr.length <= 1) { - arr[1] = arr[0]; - } - return arr.map(asInt); +Color.prototype.rgb = function(value) { + var match = null; + if ((match = value.match(_rgb)) !== null) { + this.r = Number(match[1]); + this.g = Number(match[2]); + this.b = Number(match[3]); } - - return value; + return match !== null; }; -_html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){ - var target_ratio = target_width / target_height, - current_ratio = current_width / current_height, - output_width, output_height; +var _rgba = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d?\.?\d+)\s*\)$/; - if(!stretch_mode || stretch_mode === 'auto') { - output_width = target_width; - output_height = target_height; - } else if(target_ratio < current_ratio ^ stretch_mode === 'contain') { - output_height = target_height; - output_width = target_height * current_ratio; - } else { - output_width = target_width; - output_height = target_width / current_ratio; - } +Color.prototype.rgba = function(value) { + var match = null; + if ((match = value.match(_rgba)) !== null) { + this.r = Number(match[1]); + this.g = Number(match[2]); + this.b = Number(match[3]); + this.a = Number(match[4]); + } + return match !== null; +}; - return { - width: output_width, - height: output_height - }; +Color.prototype.toString = function() { + return this.a !== null && this.a !== 1 ? + "rgba(" + [this.r, this.g, this.b, this.a].join(",") + ")" : + "rgb(" + [this.r, this.g, this.b].join(",") + ")"; }; -_html2canvas.Util.BackgroundPosition = function(element, bounds, image, imageIndex, backgroundSize ) { - var backgroundPosition = _html2canvas.Util.getCSS(element, 'backgroundPosition', imageIndex), - leftPosition, - topPosition; - if (backgroundPosition.length === 1){ - backgroundPosition = [backgroundPosition[0], backgroundPosition[0]]; +Color.prototype.namedColor = function(value) { + value = value.toLowerCase(); + var color = colors[value]; + if (color) { + this.r = color[0]; + this.g = color[1]; + this.b = color[2]; + } else if (value === "transparent") { + this.r = this.g = this.b = this.a = 0; + return true; } - if (isPercentage(backgroundPosition[0])){ - leftPosition = (bounds.width - (backgroundSize || image).width) * (parseFloat(backgroundPosition[0]) / 100); - } else { - leftPosition = parseInt(backgroundPosition[0], 10); - } + return !!color; +}; - if (backgroundPosition[1] === 'auto') { - topPosition = leftPosition / image.width * image.height; - } else if (isPercentage(backgroundPosition[1])){ - topPosition = (bounds.height - (backgroundSize || image).height) * parseFloat(backgroundPosition[1]) / 100; - } else { - topPosition = parseInt(backgroundPosition[1], 10); - } +Color.prototype.isColor = true; + +// JSON.stringify([].slice.call($$('.named-color-table tr'), 1).map(function(row) { return [row.childNodes[3].textContent, row.childNodes[5].textContent.trim().split(",").map(Number)] }).reduce(function(data, row) {data[row[0]] = row[1]; return data}, {})) +var colors = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; - if (backgroundPosition[0] === 'auto') { - leftPosition = topPosition / image.height * image.width; +module.exports = Color; + +},{}],4:[function(_dereq_,module,exports){ +var Support = _dereq_('./support'); +var CanvasRenderer = _dereq_('./renderers/canvas'); +var ImageLoader = _dereq_('./imageloader'); +var NodeParser = _dereq_('./nodeparser'); +var NodeContainer = _dereq_('./nodecontainer'); +var log = _dereq_('./log'); +var utils = _dereq_('./utils'); +var createWindowClone = _dereq_('./clone'); +var loadUrlDocument = _dereq_('./proxy').loadUrlDocument; +var getBounds = utils.getBounds; + +var html2canvasNodeAttribute = "data-html2canvas-node"; +var html2canvasCloneIndex = 0; + +function html2canvas(nodeList, options) { + var index = html2canvasCloneIndex++; + options = options || {}; + if (options.logging) { + log.options.logging = true; + log.options.start = Date.now(); + } + + options.async = typeof(options.async) === "undefined" ? true : options.async; + options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint; + options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer; + options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled; + options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout; + options.renderer = typeof(options.renderer) === "function" ? options.renderer : CanvasRenderer; + options.strict = !!options.strict; + + if (typeof(nodeList) === "string") { + if (typeof(options.proxy) !== "string") { + return Promise.reject("Proxy must be used when rendering url"); + } + var width = options.width != null ? options.width : window.innerWidth; + var height = options.height != null ? options.height : window.innerHeight; + return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, width, height, options).then(function(container) { + return renderWindow(container.contentWindow.document.documentElement, container, options, width, height); + }); } - return {left: leftPosition, top: topPosition}; -}; + var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0]; + node.setAttribute(html2canvasNodeAttribute + index, index); + return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) { + if (typeof(options.onrendered) === "function") { + log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas"); + options.onrendered(canvas); + } + return canvas; + }); +} -_html2canvas.Util.BackgroundSize = function(element, bounds, image, imageIndex) { - var backgroundSize = _html2canvas.Util.getCSS(element, 'backgroundSize', imageIndex), width, height; +html2canvas.CanvasRenderer = CanvasRenderer; +html2canvas.NodeContainer = NodeContainer; +html2canvas.log = log; +html2canvas.utils = utils; - if (backgroundSize.length === 1) { - backgroundSize = [backgroundSize[0], backgroundSize[0]]; - } +var html2canvasExport = (typeof(document) === "undefined" || typeof(Object.create) !== "function" || typeof(document.createElement("canvas").getContext) !== "function") ? function() { + return Promise.reject("No canvas support"); +} : html2canvas; - if (isPercentage(backgroundSize[0])) { - width = bounds.width * parseFloat(backgroundSize[0]) / 100; - } else if (/contain|cover/.test(backgroundSize[0])) { - return _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, backgroundSize[0]); - } else { - width = parseInt(backgroundSize[0], 10); - } +module.exports = html2canvasExport; - if (backgroundSize[0] === 'auto' && backgroundSize[1] === 'auto') { - height = image.height; - } else if (backgroundSize[1] === 'auto') { - height = width / image.width * image.height; - } else if (isPercentage(backgroundSize[1])) { - height = bounds.height * parseFloat(backgroundSize[1]) / 100; - } else { - height = parseInt(backgroundSize[1], 10); - } +if (typeof(define) === 'function' && define.amd) { + define('html2canvas', [], function() { + return html2canvasExport; + }); +} - if (backgroundSize[0] === 'auto') { - width = height / image.height * image.width; - } +function renderDocument(document, options, windowWidth, windowHeight, html2canvasIndex) { + return createWindowClone(document, document, windowWidth, windowHeight, options, document.defaultView.pageXOffset, document.defaultView.pageYOffset).then(function(container) { + log("Document cloned"); + var attributeName = html2canvasNodeAttribute + html2canvasIndex; + var selector = "[" + attributeName + "='" + html2canvasIndex + "']"; + document.querySelector(selector).removeAttribute(attributeName); + var clonedWindow = container.contentWindow; + var node = clonedWindow.document.querySelector(selector); + var oncloneHandler = (typeof(options.onclone) === "function") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true); + return oncloneHandler.then(function() { + return renderWindow(node, container, options, windowWidth, windowHeight); + }); + }); +} - return {width: width, height: height}; -}; +function renderWindow(node, container, options, windowWidth, windowHeight) { + var clonedWindow = container.contentWindow; + var support = new Support(clonedWindow.document); + var imageLoader = new ImageLoader(options, support); + var bounds = getBounds(node); + var width = options.type === "view" ? windowWidth : documentWidth(clonedWindow.document); + var height = options.type === "view" ? windowHeight : documentHeight(clonedWindow.document); + var renderer = new options.renderer(width, height, imageLoader, options, document); + var parser = new NodeParser(node, renderer, support, imageLoader, options); + return parser.ready.then(function() { + log("Finished rendering"); + var canvas; + + if (options.type === "view") { + canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0}); + } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) { + canvas = renderer.canvas; + } else { + canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0}); + } -_html2canvas.Util.BackgroundRepeat = function(element, imageIndex) { - var backgroundRepeat = _html2canvas.Util.getCSS(element, "backgroundRepeat").split(",").map(_html2canvas.Util.trimText); - return backgroundRepeat[imageIndex] || backgroundRepeat[0]; -}; + cleanupContainer(container, options); + return canvas; + }); +} -_html2canvas.Util.Extend = function (options, defaults) { - for (var key in options) { - if (options.hasOwnProperty(key)) { - defaults[key] = options[key]; +function cleanupContainer(container, options) { + if (options.removeContainer) { + container.parentNode.removeChild(container); + log("Cleaned up container"); } - } - return defaults; -}; - - -/* - * Derived from jQuery.contents() - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - */ -_html2canvas.Util.Children = function( elem ) { - var children; - try { - children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? elem.contentDocument || elem.contentWindow.document : (function(array) { - var ret = []; - if (array !== null) { - (function(first, second ) { - var i = first.length, - j = 0; - - if (typeof second.length === "number") { - for (var l = second.length; j < l; j++) { - first[i++] = second[j]; - } - } else { - while (second[j] !== undefined) { - first[i++] = second[j++]; - } - } +} - first.length = i; +function crop(canvas, bounds) { + var croppedCanvas = document.createElement("canvas"); + var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left)); + var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width)); + var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top)); + var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height)); + croppedCanvas.width = bounds.width; + croppedCanvas.height = bounds.height; + var width = x2-x1; + var height = y2-y1; + log("Cropping canvas at:", "left:", bounds.left, "top:", bounds.top, "width:", width, "height:", height); + log("Resulting crop with width", bounds.width, "and height", bounds.height, "with x", x1, "and y", y1); + croppedCanvas.getContext("2d").drawImage(canvas, x1, y1, width, height, bounds.x, bounds.y, width, height); + return croppedCanvas; +} - return first; - })(ret, array); - } - return ret; - })(elem.childNodes); +function documentWidth (doc) { + 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) + ); +} - } catch (ex) { - _html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message); - children = []; - } - return children; -}; +function documentHeight (doc) { + 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) + ); +} -_html2canvas.Util.isTransparent = function(backgroundColor) { - return (!backgroundColor || backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)"); -}; +function absoluteUrl(url) { + var link = document.createElement("a"); + link.href = url; + link.href = link.href; + return link; +} -_html2canvas.Util.Font = (function () { +},{"./clone":2,"./imageloader":11,"./log":13,"./nodecontainer":14,"./nodeparser":15,"./proxy":16,"./renderers/canvas":20,"./support":22,"./utils":26}],5:[function(_dereq_,module,exports){ +var log = _dereq_('./log'); +var smallImage = _dereq_('./utils').smallImage; + +function DummyImageContainer(src) { + this.src = src; + log("DummyImageContainer for", src); + if (!this.promise || !this.image) { + log("Initiating DummyImageContainer"); + DummyImageContainer.prototype.image = new Image(); + var image = this.image; + DummyImageContainer.prototype.promise = new Promise(function(resolve, reject) { + image.onload = resolve; + image.onerror = reject; + image.src = smallImage(); + if (image.complete === true) { + resolve(image); + } + }); + } +} - var fontData = {}; +module.exports = DummyImageContainer; - return function(font, fontSize, doc) { - if (fontData[font + "-" + fontSize] !== undefined) { - return fontData[font + "-" + fontSize]; - } +},{"./log":13,"./utils":26}],6:[function(_dereq_,module,exports){ +var smallImage = _dereq_('./utils').smallImage; - var container = doc.createElement('div'), - img = doc.createElement('img'), - span = doc.createElement('span'), - sampleText = 'Hidden Text', - baseline, - middle, - metricsObj; +function Font(family, size) { + var container = document.createElement('div'), + img = document.createElement('img'), + span = document.createElement('span'), + sampleText = 'Hidden Text', + baseline, + middle; container.style.visibility = "hidden"; - container.style.fontFamily = font; - container.style.fontSize = fontSize; + container.style.fontFamily = family; + container.style.fontSize = size; container.style.margin = 0; container.style.padding = 0; - doc.body.appendChild(container); + document.body.appendChild(container); - // http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif) - img.src = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs="; + img.src = smallImage(); img.width = 1; img.height = 1; @@ -453,2558 +1104,2416 @@ _html2canvas.Util.Font = (function () { img.style.padding = 0; img.style.verticalAlign = "baseline"; - span.style.fontFamily = font; - span.style.fontSize = fontSize; + span.style.fontFamily = family; + span.style.fontSize = size; span.style.margin = 0; span.style.padding = 0; - span.appendChild(doc.createTextNode(sampleText)); + span.appendChild(document.createTextNode(sampleText)); container.appendChild(span); container.appendChild(img); baseline = (img.offsetTop - span.offsetTop) + 1; container.removeChild(span); - container.appendChild(doc.createTextNode(sampleText)); + container.appendChild(document.createTextNode(sampleText)); container.style.lineHeight = "normal"; img.style.verticalAlign = "super"; middle = (img.offsetTop-container.offsetTop) + 1; - metricsObj = { - baseline: baseline, - lineWidth: 1, - middle: middle - }; - fontData[font + "-" + fontSize] = metricsObj; + document.body.removeChild(container); - doc.body.removeChild(container); + this.baseline = baseline; + this.lineWidth = 1; + this.middle = middle; +} - return metricsObj; - }; -})(); +module.exports = Font; -(function(){ - var Util = _html2canvas.Util, - Generate = {}; +},{"./utils":26}],7:[function(_dereq_,module,exports){ +var Font = _dereq_('./font'); - _html2canvas.Generate = Generate; +function FontMetrics() { + this.data = {}; +} - var reGradients = [ - /^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/, - /^(-o-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/, - /^(-webkit-gradient)\((linear|radial),\s((?:\d{1,3}%?)\s(?:\d{1,3}%?),\s(?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)\-]+)\)$/, - /^(-moz-linear-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)]+)\)$/, - /^(-webkit-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/, - /^(-moz-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s?([a-z\-]*)([\w\d\.\s,%\(\)]+)\)$/, - /^(-o-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/ - ]; +FontMetrics.prototype.getMetrics = function(family, size) { + if (this.data[family + "-" + size] === undefined) { + this.data[family + "-" + size] = new Font(family, size); + } + return this.data[family + "-" + size]; +}; - /* - * TODO: Add IE10 vendor prefix (-ms) support - * TODO: Add W3C gradient (linear-gradient) support - * TODO: Add old Webkit -webkit-gradient(radial, ...) support - * TODO: Maybe some RegExp optimizations are possible ;o) - */ - Generate.parseGradient = function(css, bounds) { - var gradient, i, len = reGradients.length, m1, stop, m2, m2Len, step, m3, tl,tr,br,bl; +module.exports = FontMetrics; + +},{"./font":6}],8:[function(_dereq_,module,exports){ +var utils = _dereq_('./utils'); +var getBounds = utils.getBounds; +var loadUrlDocument = _dereq_('./proxy').loadUrlDocument; + +function FrameContainer(container, sameOrigin, options) { + this.image = null; + this.src = container; + var self = this; + var bounds = getBounds(container); + this.promise = (!sameOrigin ? this.proxyLoad(options.proxy, bounds, options) : new Promise(function(resolve) { + if (container.contentWindow.document.URL === "about:blank" || container.contentWindow.document.documentElement == null) { + container.contentWindow.onload = container.onload = function() { + resolve(container); + }; + } else { + resolve(container); + } + })).then(function(container) { + var html2canvas = _dereq_('./core'); + return html2canvas(container.contentWindow.document.documentElement, {type: 'view', width: container.width, height: container.height, proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer, allowTaint: options.allowTaint, imageTimeout: options.imageTimeout / 2}); + }).then(function(canvas) { + return self.image = canvas; + }); +} - for(i = 0; i < len; i+=1){ - m1 = css.match(reGradients[i]); - if(m1) { - break; - } - } - - if(m1) { - switch(m1[1]) { - case '-webkit-linear-gradient': - case '-o-linear-gradient': - - gradient = { - type: 'linear', - x0: null, - y0: null, - x1: null, - y1: null, - colorStops: [] - }; - - // get coordinates - m2 = m1[2].match(/\w+/g); - if(m2){ - m2Len = m2.length; - for(i = 0; i < m2Len; i+=1){ - switch(m2[i]) { - case 'top': - gradient.y0 = 0; - gradient.y1 = bounds.height; - break; - - case 'right': - gradient.x0 = bounds.width; - gradient.x1 = 0; - break; - - case 'bottom': - gradient.y0 = bounds.height; - gradient.y1 = 0; - break; - - case 'left': - gradient.x0 = 0; - gradient.x1 = bounds.width; - break; - } - } - } - if(gradient.x0 === null && gradient.x1 === null){ // center - gradient.x0 = gradient.x1 = bounds.width / 2; - } - if(gradient.y0 === null && gradient.y1 === null){ // center - gradient.y0 = gradient.y1 = bounds.height / 2; - } - - // get colors and stops - m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g); - if(m2){ - m2Len = m2.length; - step = 1 / Math.max(m2Len - 1, 1); - for(i = 0; i < m2Len; i+=1){ - m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/); - if(m3[2]){ - stop = parseFloat(m3[2]); - if(m3[3] === '%'){ - stop /= 100; - } else { // px - stupid opera - stop /= bounds.width; - } - } else { - stop = i * step; - } - gradient.colorStops.push({ - color: m3[1], - stop: stop - }); - } - } - break; - - case '-webkit-gradient': - - gradient = { - type: m1[2] === 'radial' ? 'circle' : m1[2], // TODO: Add radial gradient support for older mozilla definitions - x0: 0, - y0: 0, - x1: 0, - y1: 0, - colorStops: [] - }; - - // get coordinates - m2 = m1[3].match(/(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/); - if(m2){ - gradient.x0 = (m2[1] * bounds.width) / 100; - gradient.y0 = (m2[2] * bounds.height) / 100; - gradient.x1 = (m2[3] * bounds.width) / 100; - gradient.y1 = (m2[4] * bounds.height) / 100; - } - - // get colors and stops - m2 = m1[4].match(/((?:from|to|color-stop)\((?:[0-9\.]+,\s)?(?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)\))+/g); - if(m2){ - m2Len = m2.length; - for(i = 0; i < m2Len; i+=1){ - m3 = m2[i].match(/(from|to|color-stop)\(([0-9\.]+)?(?:,\s)?((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\)/); - stop = parseFloat(m3[2]); - if(m3[1] === 'from') { - stop = 0.0; - } - if(m3[1] === 'to') { - stop = 1.0; - } - gradient.colorStops.push({ - color: m3[3], - stop: stop - }); - } - } - break; - - case '-moz-linear-gradient': - - gradient = { - type: 'linear', - x0: 0, - y0: 0, - x1: 0, - y1: 0, - colorStops: [] - }; - - // get coordinates - m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/); - - // m2[1] == 0% -> left - // m2[1] == 50% -> center - // m2[1] == 100% -> right - - // m2[2] == 0% -> top - // m2[2] == 50% -> center - // m2[2] == 100% -> bottom - - if(m2){ - gradient.x0 = (m2[1] * bounds.width) / 100; - gradient.y0 = (m2[2] * bounds.height) / 100; - gradient.x1 = bounds.width - gradient.x0; - gradient.y1 = bounds.height - gradient.y0; - } - - // get colors and stops - m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}%)?)+/g); - if(m2){ - m2Len = m2.length; - step = 1 / Math.max(m2Len - 1, 1); - for(i = 0; i < m2Len; i+=1){ - m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%)?/); - if(m3[2]){ - stop = parseFloat(m3[2]); - if(m3[3]){ // percentage - stop /= 100; - } - } else { - stop = i * step; - } - gradient.colorStops.push({ - color: m3[1], - stop: stop - }); - } - } - break; - - case '-webkit-radial-gradient': - case '-moz-radial-gradient': - case '-o-radial-gradient': - - gradient = { - type: 'circle', - x0: 0, - y0: 0, - x1: bounds.width, - y1: bounds.height, - cx: 0, - cy: 0, - rx: 0, - ry: 0, - colorStops: [] - }; - - // center - m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/); - if(m2){ - gradient.cx = (m2[1] * bounds.width) / 100; - gradient.cy = (m2[2] * bounds.height) / 100; - } - - // size - m2 = m1[3].match(/\w+/); - m3 = m1[4].match(/[a-z\-]*/); - if(m2 && m3){ - switch(m3[0]){ - case 'farthest-corner': - case 'cover': // is equivalent to farthest-corner - case '': // mozilla removes "cover" from definition :( - tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2)); - tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2)); - br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2)); - bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2)); - gradient.rx = gradient.ry = Math.max(tl, tr, br, bl); - break; - case 'closest-corner': - tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2)); - tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2)); - br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2)); - bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2)); - gradient.rx = gradient.ry = Math.min(tl, tr, br, bl); - break; - case 'farthest-side': - if(m2[0] === 'circle'){ - gradient.rx = gradient.ry = Math.max( - gradient.cx, - gradient.cy, - gradient.x1 - gradient.cx, - gradient.y1 - gradient.cy - ); - } else { // ellipse - - gradient.type = m2[0]; - - gradient.rx = Math.max( - gradient.cx, - gradient.x1 - gradient.cx - ); - gradient.ry = Math.max( - gradient.cy, - gradient.y1 - gradient.cy - ); - } - break; - case 'closest-side': - case 'contain': // is equivalent to closest-side - if(m2[0] === 'circle'){ - gradient.rx = gradient.ry = Math.min( - gradient.cx, - gradient.cy, - gradient.x1 - gradient.cx, - gradient.y1 - gradient.cy - ); - } else { // ellipse - - gradient.type = m2[0]; - - gradient.rx = Math.min( - gradient.cx, - gradient.x1 - gradient.cx - ); - gradient.ry = Math.min( - gradient.cy, - gradient.y1 - gradient.cy - ); - } - break; +FrameContainer.prototype.proxyLoad = function(proxy, bounds, options) { + var container = this.src; + return loadUrlDocument(container.src, proxy, container.ownerDocument, bounds.width, bounds.height, options); +}; - // TODO: add support for "30px 40px" sizes (webkit only) - } - } - - // color stops - m2 = m1[5].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g); - if(m2){ - m2Len = m2.length; - step = 1 / Math.max(m2Len - 1, 1); - for(i = 0; i < m2Len; i+=1){ - m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/); - if(m3[2]){ - stop = parseFloat(m3[2]); - if(m3[3] === '%'){ - stop /= 100; - } else { // px - stupid opera - stop /= bounds.width; - } - } else { - stop = i * step; - } - gradient.colorStops.push({ - color: m3[1], - stop: stop - }); +module.exports = FrameContainer; + +},{"./core":4,"./proxy":16,"./utils":26}],9:[function(_dereq_,module,exports){ +function GradientContainer(imageData) { + this.src = imageData.value; + this.colorStops = []; + this.type = null; + this.x0 = 0.5; + this.y0 = 0.5; + this.x1 = 0.5; + this.y1 = 0.5; + this.promise = Promise.resolve(true); +} + +GradientContainer.TYPES = { + LINEAR: 1, + RADIAL: 2 +}; + +// TODO: support hsl[a], negative %/length values +// TODO: support (e.g. -?\d{1,3}(?:\.\d+)deg, etc. : https://developer.mozilla.org/docs/Web/CSS/angle ) +GradientContainer.REGEXP_COLORSTOP = /^\s*(rgba?\(\s*\d{1,3},\s*\d{1,3},\s*\d{1,3}(?:,\s*[0-9\.]+)?\s*\)|[a-z]{3,20}|#[a-f0-9]{3,6})(?:\s+(\d{1,3}(?:\.\d+)?)(%|px)?)?(?:\s|$)/i; + +module.exports = GradientContainer; + +},{}],10:[function(_dereq_,module,exports){ +function ImageContainer(src, cors) { + this.src = src; + this.image = new Image(); + var self = this; + this.tainted = null; + this.promise = new Promise(function(resolve, reject) { + self.image.onload = resolve; + self.image.onerror = reject; + if (cors) { + self.image.crossOrigin = "anonymous"; + } + self.image.src = src; + if (self.image.complete === true) { + resolve(self.image); + } + }); +} + +module.exports = ImageContainer; + +},{}],11:[function(_dereq_,module,exports){ +var log = _dereq_('./log'); +var ImageContainer = _dereq_('./imagecontainer'); +var DummyImageContainer = _dereq_('./dummyimagecontainer'); +var ProxyImageContainer = _dereq_('./proxyimagecontainer'); +var FrameContainer = _dereq_('./framecontainer'); +var SVGContainer = _dereq_('./svgcontainer'); +var SVGNodeContainer = _dereq_('./svgnodecontainer'); +var LinearGradientContainer = _dereq_('./lineargradientcontainer'); +var WebkitGradientContainer = _dereq_('./webkitgradientcontainer'); +var bind = _dereq_('./utils').bind; + +function ImageLoader(options, support) { + this.link = null; + this.options = options; + this.support = support; + this.origin = this.getOrigin(window.location.href); +} + +ImageLoader.prototype.findImages = function(nodes) { + var images = []; + nodes.reduce(function(imageNodes, container) { + switch(container.node.nodeName) { + case "IMG": + return imageNodes.concat([{ + args: [container.node.src], + method: "url" + }]); + case "svg": + case "IFRAME": + return imageNodes.concat([{ + args: [container.node], + method: container.node.nodeName + }]); + } + return imageNodes; + }, []).forEach(this.addImage(images, this.loadImage), this); + return images; +}; + +ImageLoader.prototype.findBackgroundImage = function(images, container) { + container.parseBackgroundImages().filter(this.hasImageBackground).forEach(this.addImage(images, this.loadImage), this); + return images; +}; + +ImageLoader.prototype.addImage = function(images, callback) { + return function(newImage) { + newImage.args.forEach(function(image) { + if (!this.imageExists(images, image)) { + images.splice(0, 0, callback.call(this, newImage)); + log('Added image #' + (images.length), typeof(image) === "string" ? image.substring(0, 100) : image); } - } - break; - } - } - - return gradient; - }; - - function addScrollStops(grad) { - return function(colorStop) { - try { - grad.addColorStop(colorStop.stop, colorStop.color); - } - catch(e) { - Util.log(['failed to add color stop: ', e, '; tried to add: ', colorStop]); - } + }, this); }; - } +}; + +ImageLoader.prototype.hasImageBackground = function(imageData) { + return imageData.method !== "none"; +}; - Generate.Gradient = function(src, bounds) { - if(bounds.width === 0 || bounds.height === 0) { - return; +ImageLoader.prototype.loadImage = function(imageData) { + if (imageData.method === "url") { + var src = imageData.args[0]; + if (this.isSVG(src) && !this.support.svg && !this.options.allowTaint) { + return new SVGContainer(src); + } else 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 || this.isSVG(src)) { + 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, this.options.proxy); + } else { + return new DummyImageContainer(src); + } + } else if (imageData.method === "linear-gradient") { + return new LinearGradientContainer(imageData); + } else if (imageData.method === "gradient") { + return new WebkitGradientContainer(imageData); + } else if (imageData.method === "svg") { + return new SVGNodeContainer(imageData.args[0], this.support.svg); + } else if (imageData.method === "IFRAME") { + return new FrameContainer(imageData.args[0], this.isSameOrigin(imageData.args[0].src), this.options); + } else { + return new DummyImageContainer(imageData); } +}; - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - gradient, grad; +ImageLoader.prototype.isSVG = function(src) { + return src.substring(src.length - 3).toLowerCase() === "svg" || SVGContainer.prototype.isInline(src); +}; + +ImageLoader.prototype.imageExists = function(images, src) { + return images.some(function(image) { + return image.src === src; + }); +}; - canvas.width = bounds.width; - canvas.height = bounds.height; +ImageLoader.prototype.isSameOrigin = function(url) { + return (this.getOrigin(url) === this.origin); +}; + +ImageLoader.prototype.getOrigin = 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/ + return link.protocol + link.hostname + link.port; +}; - // TODO: add support for multi defined background gradients - gradient = _html2canvas.Generate.parseGradient(src, bounds); +ImageLoader.prototype.getPromise = function(container) { + return this.timeout(container, this.options.imageTimeout)['catch'](function() { + var dummy = new DummyImageContainer(container.src); + return dummy.promise.then(function(image) { + container.image = image; + }); + }); +}; - if(gradient) { - switch(gradient.type) { - case 'linear': - grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1); - gradient.colorStops.forEach(addScrollStops(grad)); - ctx.fillStyle = grad; - ctx.fillRect(0, 0, bounds.width, bounds.height); - break; +ImageLoader.prototype.get = function(src) { + var found = null; + return this.images.some(function(img) { + return (found = img).src === src; + }) ? found : null; +}; - case 'circle': - grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx); - gradient.colorStops.forEach(addScrollStops(grad)); - ctx.fillStyle = grad; - ctx.fillRect(0, 0, bounds.width, bounds.height); - break; +ImageLoader.prototype.fetch = function(nodes) { + this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes)); + this.images.forEach(function(image, index) { + image.promise.then(function() { + log("Succesfully loaded image #"+ (index+1), image); + }, function(e) { + log("Failed loading image #"+ (index+1), image, e); + }); + }); + this.ready = Promise.all(this.images.map(this.getPromise, this)); + log("Finished searching images"); + return this; +}; - case 'ellipse': - var canvasRadial = document.createElement('canvas'), - ctxRadial = canvasRadial.getContext('2d'), - ri = Math.max(gradient.rx, gradient.ry), - di = ri * 2; +ImageLoader.prototype.timeout = function(container, timeout) { + var timer; + var promise = Promise.race([container.promise, new Promise(function(res, reject) { + timer = setTimeout(function() { + log("Timed out loading image", container); + reject(container); + }, timeout); + })]).then(function(container) { + clearTimeout(timer); + return container; + }); + promise['catch'](function() { + clearTimeout(timer); + }); + return promise; +}; - canvasRadial.width = canvasRadial.height = di; +module.exports = ImageLoader; - grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri); - gradient.colorStops.forEach(addScrollStops(grad)); +},{"./dummyimagecontainer":5,"./framecontainer":8,"./imagecontainer":10,"./lineargradientcontainer":12,"./log":13,"./proxyimagecontainer":17,"./svgcontainer":23,"./svgnodecontainer":24,"./utils":26,"./webkitgradientcontainer":27}],12:[function(_dereq_,module,exports){ +var GradientContainer = _dereq_('./gradientcontainer'); +var Color = _dereq_('./color'); - ctxRadial.fillStyle = grad; - ctxRadial.fillRect(0, 0, di, di); +function LinearGradientContainer(imageData) { + GradientContainer.apply(this, arguments); + this.type = GradientContainer.TYPES.LINEAR; - ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry); - break; - } - } + var hasDirection = LinearGradientContainer.REGEXP_DIRECTION.test( imageData.args[0] ) || + !GradientContainer.REGEXP_COLORSTOP.test( imageData.args[0] ); - return canvas; - }; - - Generate.ListAlpha = function(number) { - var tmp = "", - modulus; - - do { - modulus = number % 26; - tmp = String.fromCharCode((modulus) + 64) + tmp; - number = number / 26; - }while((number*26) > 26); - - return tmp; - }; - - Generate.ListRoman = function(number) { - var romanArray = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"], - decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1], - roman = "", - v, - len = romanArray.length; - - if (number <= 0 || number >= 4000) { - return number; - } - - for (v=0; v < len; v+=1) { - while (number >= decimal[v]) { - number -= decimal[v]; - roman += romanArray[v]; - } - } - - return roman; - }; -})(); -function h2cRenderContext(width, height) { - var storage = []; - return { - storage: storage, - width: width, - height: height, - clip: function() { - storage.push({ - type: "function", - name: "clip", - 'arguments': arguments - }); - }, - translate: function() { - storage.push({ - type: "function", - name: "translate", - 'arguments': arguments - }); - }, - fill: function() { - storage.push({ - type: "function", - name: "fill", - 'arguments': arguments - }); - }, - save: function() { - storage.push({ - type: "function", - name: "save", - 'arguments': arguments - }); - }, - restore: function() { - storage.push({ - type: "function", - name: "restore", - 'arguments': arguments - }); - }, - fillRect: function () { - storage.push({ - type: "function", - name: "fillRect", - 'arguments': arguments - }); - }, - createPattern: function() { - storage.push({ - type: "function", - name: "createPattern", - 'arguments': arguments - }); - }, - drawShape: function() { - - var shape = []; - - storage.push({ - type: "function", - name: "drawShape", - 'arguments': shape - }); - - return { - moveTo: function() { - shape.push({ - name: "moveTo", - 'arguments': arguments - }); - }, - lineTo: function() { - shape.push({ - name: "lineTo", - 'arguments': arguments - }); - }, - arcTo: function() { - shape.push({ - name: "arcTo", - 'arguments': arguments - }); - }, - bezierCurveTo: function() { - shape.push({ - name: "bezierCurveTo", - 'arguments': arguments - }); - }, - quadraticCurveTo: function() { - shape.push({ - name: "quadraticCurveTo", - 'arguments': arguments - }); - } - }; - - }, - drawImage: function () { - storage.push({ - type: "function", - name: "drawImage", - 'arguments': arguments - }); - }, - fillText: function () { - storage.push({ - type: "function", - name: "fillText", - 'arguments': arguments - }); - }, - setVariable: function (variable, value) { - storage.push({ - type: "variable", - name: variable, - 'arguments': value - }); - return value; - } - }; -} -_html2canvas.Parse = function (images, options, cb) { - window.scroll(0,0); - - var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default - numDraws = 0, - doc = element.ownerDocument, - Util = _html2canvas.Util, - support = Util.Support(options, doc), - ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"), - body = doc.body, - getCSS = Util.getCSS, - pseudoHide = "___html2canvas___pseudoelement", - hidePseudoElementsStyles = doc.createElement('style'); - - hidePseudoElementsStyles.innerHTML = '.' + pseudoHide + - '-parent:before { content: "" !important; display: none !important; }' + - '.' + pseudoHide + '-parent:after { content: "" !important; display: none !important; }'; - - body.appendChild(hidePseudoElementsStyles); - - images = images || {}; - - init(); - - function init() { - var background = getCSS(document.documentElement, "backgroundColor"), - transparentBackground = (Util.isTransparent(background) && element === document.body), - stack = renderElement(element, null, false, transparentBackground); - - // create pseudo elements in a single pass to prevent synchronous layouts - addPseudoElements(element); - - parseChildren(element, stack, function() { - if (transparentBackground) { - background = stack.backgroundColor; - } - - removePseudoElements(); - - Util.log('Done parsing, moving to Render.'); - - cb({ - backgroundColor: background, - stack: stack - }); - }); - } - - // Given a root element, find all pseudo elements below, create elements mocking pseudo element styles - // so we can process them as normal elements, and hide the original pseudo elements so they don't interfere - // with layout. - function addPseudoElements(el) { - // These are done in discrete steps to prevent a relayout loop caused by addClass() invalidating - // layouts & getPseudoElement calling getComputedStyle. - var jobs = [], classes = []; - getPseudoElementClasses(); - findPseudoElements(el); - runJobs(); - - function getPseudoElementClasses(){ - var findPsuedoEls = /:before|:after/; - var sheets = document.styleSheets; - for (var i = 0, j = sheets.length; i < j; i++) { - try { - var rules = sheets[i].cssRules; - for (var k = 0, l = rules.length; k < l; k++) { - if(findPsuedoEls.test(rules[k].selectorText)) { - classes.push(rules[k].selectorText); + if (hasDirection) { + imageData.args[0].split(/\s+/).reverse().forEach(function(position, index) { + switch(position) { + case "left": + this.x0 = 0; + this.x1 = 1; + break; + case "top": + this.y0 = 0; + this.y1 = 1; + break; + case "right": + this.x0 = 1; + this.x1 = 0; + break; + case "bottom": + this.y0 = 1; + this.y1 = 0; + break; + case "to": + var y0 = this.y0; + var x0 = this.x0; + this.y0 = this.y1; + this.x0 = this.x1; + this.x1 = x0; + this.y1 = y0; + break; + case "center": + break; // centered by default + // Firefox internally converts position keywords to percentages: + // http://www.w3.org/TR/2010/WD-CSS2-20101207/colors.html#propdef-background-position + default: // percentage or absolute length + // TODO: support absolute start point positions (e.g., use bounds to convert px to a ratio) + var ratio = parseFloat(position, 10) * 1e-2; + if (isNaN(ratio)) { // invalid or unhandled value + break; + } + if (index === 0) { + this.y0 = ratio; + this.y1 = 1 - this.y0; + } else { + this.x0 = ratio; + this.x1 = 1 - this.x0; + } + break; } - } - } - catch(e) { // will throw security exception for style sheets loaded from external domains - } - } + }, this); + } else { + this.y0 = 0; + this.y1 = 1; + } + + this.colorStops = imageData.args.slice(hasDirection ? 1 : 0).map(function(colorStop) { + var colorStopMatch = colorStop.match(GradientContainer.REGEXP_COLORSTOP); + var value = +colorStopMatch[2]; + var unit = value === 0 ? "%" : colorStopMatch[3]; // treat "0" as "0%" + return { + color: new Color(colorStopMatch[1]), + // TODO: support absolute stop positions (e.g., compute gradient line length & convert px to ratio) + stop: unit === "%" ? value / 100 : null + }; + }); - // Trim off the :after and :before (or ::after and ::before) - for (i = 0, j = classes.length; i < j; i++) { - classes[i] = classes[i].match(/(^[^:]*)/)[1]; - } + if (this.colorStops[0].stop === null) { + this.colorStops[0].stop = 0; } - // Using the list of elements we know how pseudo el styles, create fake pseudo elements. - function findPseudoElements(el) { - var els = document.querySelectorAll(classes.join(',')); - for(var i = 0, j = els.length; i < j; i++) { - createPseudoElements(els[i]); - } + if (this.colorStops[this.colorStops.length - 1].stop === null) { + this.colorStops[this.colorStops.length - 1].stop = 1; } - // Create pseudo elements & add them to a job queue. - function createPseudoElements(el) { - var before = getPseudoElement(el, ':before'), - after = getPseudoElement(el, ':after'); + // calculates and fills-in explicit stop positions when omitted from rule + this.colorStops.forEach(function(colorStop, index) { + if (colorStop.stop === null) { + this.colorStops.slice(index).some(function(find, count) { + if (find.stop !== null) { + colorStop.stop = ((find.stop - this.colorStops[index - 1].stop) / (count + 1)) + this.colorStops[index - 1].stop; + return true; + } else { + return false; + } + }, this); + } + }, this); +} - if(before) { - jobs.push({type: 'before', pseudo: before, el: el}); - } +LinearGradientContainer.prototype = Object.create(GradientContainer.prototype); - if (after) { - jobs.push({type: 'after', pseudo: after, el: el}); - } - } +// TODO: support (e.g. -?\d{1,3}(?:\.\d+)deg, etc. : https://developer.mozilla.org/docs/Web/CSS/angle ) +LinearGradientContainer.REGEXP_DIRECTION = /^\s*(?:to|left|right|top|bottom|center|\d{1,3}(?:\.\d+)?%?)(?:\s|$)/i; - // Adds a class to the pseudo's parent to prevent the original before/after from messing - // with layouts. - // Execute the inserts & addClass() calls in a batch to prevent relayouts. - function runJobs() { - // Add Class - jobs.forEach(function(job){ - addClass(job.el, pseudoHide + "-parent"); - }); +module.exports = LinearGradientContainer; - // Insert el - jobs.forEach(function(job){ - if(job.type === 'before'){ - job.el.insertBefore(job.pseudo, job.el.firstChild); - } else { - job.el.appendChild(job.pseudo); - } - }); +},{"./color":3,"./gradientcontainer":9}],13:[function(_dereq_,module,exports){ +var logger = function() { + if (logger.options.logging && window.console && window.console.log) { + Function.prototype.bind.call(window.console.log, (window.console)).apply(window.console, [(Date.now() - logger.options.start) + "ms", "html2canvas:"].concat([].slice.call(arguments, 0))); } - } +}; +logger.options = {logging: false}; +module.exports = logger; + +},{}],14:[function(_dereq_,module,exports){ +var Color = _dereq_('./color'); +var utils = _dereq_('./utils'); +var getBounds = utils.getBounds; +var parseBackgrounds = utils.parseBackgrounds; +var offsetBounds = utils.offsetBounds; + +function NodeContainer(node, parent) { + this.node = node; + 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; + this.colors = {}; + this.styles = {}; + this.backgroundImages = null; + this.transformData = null; + this.transformMatrix = null; + this.isPseudoElement = false; + this.opacity = null; +} +NodeContainer.prototype.cloneTo = function(stack) { + stack.visible = this.visible; + stack.borders = this.borders; + stack.bounds = this.bounds; + stack.clip = this.clip; + stack.backgroundClip = this.backgroundClip; + stack.computedStyles = this.computedStyles; + stack.styles = this.styles; + stack.backgroundImages = this.backgroundImages; + stack.opacity = this.opacity; +}; - // Delete our fake pseudo elements from the DOM. This will remove those actual elements - // and the classes on their parents that hide the actual pseudo elements. - // Note that NodeLists are 'live' collections so you can't use a for loop here. They are - // actually deleted from the NodeList after each iteration. - function removePseudoElements(){ - // delete pseudo elements - body.removeChild(hidePseudoElementsStyles); - var pseudos = document.getElementsByClassName(pseudoHide + "-element"); - while (pseudos.length) { - pseudos[0].parentNode.removeChild(pseudos[0]); - } +NodeContainer.prototype.getOpacity = function() { + return this.opacity === null ? (this.opacity = this.cssFloat('opacity')) : this.opacity; +}; - // Remove pseudo hiding classes - var parents = document.getElementsByClassName(pseudoHide + "-parent"); - while(parents.length) { - removeClass(parents[0], pseudoHide + "-parent"); - } - } +NodeContainer.prototype.assignStack = function(stack) { + this.stack = stack; + stack.children.push(this); +}; - function addClass (el, className) { - if (el.classList) { - el.classList.add(className); - } else { - el.className = el.className + " " + className; +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") && + (this.node.nodeName !== "INPUT" || this.node.getAttribute("type") !== "hidden") + ); +}; + +NodeContainer.prototype.css = function(attribute) { + if (!this.computedStyles) { + this.computedStyles = this.isPseudoElement ? this.parent.computedStyle(this.before ? ":before" : ":after") : this.computedStyle(null); } - } - function removeClass (el, className) { - if (el.classList) { - el.classList.remove(className); - } else { - el.className = el.className.replace(className, "").trim(); + return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]); +}; + +NodeContainer.prototype.prefixedCss = function(attribute) { + var prefixes = ["webkit", "moz", "ms", "o"]; + var value = this.css(attribute); + if (value === undefined) { + prefixes.some(function(prefix) { + value = this.css(prefix + attribute.substr(0, 1).toUpperCase() + attribute.substr(1)); + return value !== undefined; + }, this); } - } + return value === undefined ? null : value; +}; - function hasClass (el, className) { - return el.className.indexOf(className) > -1; - } +NodeContainer.prototype.computedStyle = function(type) { + return this.node.ownerDocument.defaultView.getComputedStyle(this.node, type); +}; - // Note that this doesn't work in < IE8, but we don't support that anyhow - function nodeListToArray (nodeList) { - return Array.prototype.slice.call(nodeList); - } +NodeContainer.prototype.cssInt = function(attribute) { + var value = parseInt(this.css(attribute), 10); + return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html +}; - 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) - ); - } +NodeContainer.prototype.color = function(attribute) { + return this.colors[attribute] || (this.colors[attribute] = new Color(this.css(attribute))); +}; - 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 capitalize(m, p1, p2) { - if (m.length > 0) { - return p1 + p2.toUpperCase(); - } - } +NodeContainer.prototype.cssFloat = function(attribute) { + var value = parseFloat(this.css(attribute)); + return (isNaN(value)) ? 0 : value; +}; - function textTransform (text, transform) { - 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 noLetterSpacing(letter_spacing) { - return (/^(normal|none|0px)$/.test(letter_spacing)); - } - - function drawText(currentText, x, y, ctx){ - if (currentText !== null && 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"), - shadows = Util.parseTextShadows(getCSS(el, "textShadow")); - - switch(parseInt(bold, 10)){ - case 401: - bold = "bold"; +NodeContainer.prototype.fontWeight = function() { + var weight = this.css("fontWeight"); + switch(parseInt(weight, 10)){ + case 401: + weight = "bold"; break; - case 400: - bold = "normal"; + case 400: + weight = "normal"; break; } + return weight; +}; - ctx.setVariable("fillStyle", color); - ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" ")); - ctx.setVariable("textAlign", (align) ? "right" : "left"); +NodeContainer.prototype.parseClip = function() { + var matches = this.css('clip').match(this.CLIP); + if (matches) { + return { + top: parseInt(matches[1], 10), + right: parseInt(matches[2], 10), + bottom: parseInt(matches[3], 10), + left: parseInt(matches[4], 10) + }; + } + return null; +}; - if (shadows.length) { - // 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); +NodeContainer.prototype.parseBackgroundImages = function() { + return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage"))); +}; + +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], isPercentage(value[0]) ? 'auto' : value[0]]; } + return value; +}; + +NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) { + var size = this.cssList("backgroundSize", index); + var width, height; - if (text_decoration !== "none"){ - return Util.Font(family, size, doc); + 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); } - } - 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; + 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); } - } - function getTextBounds(state, text, textDecoration, isLast, transform) { - var bounds; - if (support.rangeBounds && !transform) { - if (textDecoration !== "none" || 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, transform); - state.node = newTextNode; + if (size[0] === 'auto') { + width = height / image.height * image.width; } - return bounds; - } - function textRangeBounds(text, textNode, textOffset) { - var range = doc.createRange(); - range.setStart(textNode, textOffset); - range.setEnd(textNode, textOffset + text.length); - return range.getBoundingClientRect(); - } + 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); + } - function textWrapperBounds(oldTextNode, transform) { - var parent = oldTextNode.parentNode, - wrapElement = doc.createElement('wrapper'), - backupText = oldTextNode.cloneNode(true); + if (position[0] === 'auto') { + left = top / image.height * image.width; + } - wrapElement.appendChild(oldTextNode.cloneNode(true)); - parent.replaceChild(wrapElement, oldTextNode); + return {left: left, top: top}; +}; - var bounds = transform ? Util.OffsetBounds(wrapElement) : 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 - }; +NodeContainer.prototype.parseBackgroundRepeat = function(index) { + return this.cssList("backgroundRepeat", index)[0]; +}; + +NodeContainer.prototype.parseTextShadows = function() { + var textShadow = this.css("textShadow"); + var results = []; + + if (textShadow && textShadow !== 'none') { + var shadows = textShadow.match(this.TEXT_SHADOW_PROPERTY); + for (var i = 0; shadows && (i < shadows.length); i++) { + var s = shadows[i].match(this.TEXT_SHADOW_VALUES); + results.push({ + color: new Color(s[0]), + offsetX: s[1] ? parseFloat(s[1].replace('px', '')) : 0, + offsetY: s[2] ? parseFloat(s[2].replace('px', '')) : 0, + blur: s[3] ? s[3].replace('px', '') : 0 + }); + } + } + return results; +}; + +NodeContainer.prototype.parseTransform = function() { + if (!this.transformData) { + if (this.hasTransform()) { + var offset = this.parseBounds(); + var origin = this.prefixedCss("transformOrigin").split(" ").map(removePx).map(asFloat); + origin[0] += offset.left; + origin[1] += offset.top; + this.transformData = { + origin: origin, + matrix: this.parseTransformMatrix() + }; + } else { + this.transformData = { + origin: [0, 0], + matrix: [1, 0, 0, 1, 0, 0] + }; + } + } + return this.transformData; +}; + +NodeContainer.prototype.parseTransformMatrix = function() { + if (!this.transformMatrix) { + var transform = this.prefixedCss("transform"); + var matrix = transform ? parseMatrix(transform.match(this.MATRIX_PROPERTY)) : null; + this.transformMatrix = matrix ? matrix : [1, 0, 0, 1, 0, 0]; + } + return this.transformMatrix; +}; + +NodeContainer.prototype.parseBounds = function() { + return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node)); +}; + +NodeContainer.prototype.hasTransform = function() { + return this.parseTransformMatrix().join(",") !== "1,0,0,1,0,0" || (this.parent && this.parent.hasTransform()); +}; - if (Util.trimText(textNode.nodeValue).length > 0) { - textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform")); - textAlign = textAlign.replace(["-webkit-auto"],["auto"]); +NodeContainer.prototype.getValue = function() { + var value = this.node.value || ""; + if (this.node.tagName === "SELECT") { + value = selectionValue(this.node); + } else if (this.node.type === "password") { + value = Array(value.length + 1).join('\u2022'); // jshint ignore:line + } + return value.length === 0 ? (this.node.placeholder || "") : value; +}; - textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ? - textNode.nodeValue.split(/(\b| )/) - : textNode.nodeValue.split(""); +NodeContainer.prototype.MATRIX_PROPERTY = /(matrix|matrix3d)\((.+)\)/; +NodeContainer.prototype.TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g; +NodeContainer.prototype.TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g; +NodeContainer.prototype.CLIP = /^rect\((\d+)px,? (\d+)px,? (\d+)px,? (\d+)px\)$/; - metrics = setTextVariables(ctx, el, textDecoration, color); +function selectionValue(node) { + var option = node.options[node.selectedIndex || 0]; + return option ? (option.text || "") : ""; +} - 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); - } +function parseMatrix(match) { + if (match && match[1] === "matrix") { + return match[2].split(",").map(function(s) { + return parseFloat(s.trim()); }); - } + } else if (match && match[1] === "matrix3d") { + var matrix3d = match[2].split(",").map(function(s) { + return parseFloat(s.trim()); + }); + return [matrix3d[0], matrix3d[1], matrix3d[4], matrix3d[5], matrix3d[12], matrix3d[13]]; + } +} + +function isPercentage(value) { + return value.toString().indexOf("%") !== -1; +} + +function removePx(str) { + return str.replace("px", ""); +} + +function asFloat(str) { + return parseFloat(str); +} + +module.exports = NodeContainer; + +},{"./color":3,"./utils":26}],15:[function(_dereq_,module,exports){ +var log = _dereq_('./log'); +var punycode = _dereq_('punycode'); +var NodeContainer = _dereq_('./nodecontainer'); +var TextContainer = _dereq_('./textcontainer'); +var PseudoElementContainer = _dereq_('./pseudoelementcontainer'); +var FontMetrics = _dereq_('./fontmetrics'); +var Color = _dereq_('./color'); +var StackingContext = _dereq_('./stackingcontext'); +var utils = _dereq_('./utils'); +var bind = utils.bind; +var getBounds = utils.getBounds; +var parseBackgrounds = utils.parseBackgrounds; +var offsetBounds = utils.offsetBounds; + +function NodeParser(element, renderer, support, imageLoader, options) { + log("Starting NodeParser"); + this.renderer = renderer; + this.options = options; + this.range = null; + this.support = support; + this.renderQueue = []; + this.stack = new StackingContext(true, 1, element.ownerDocument, null); + var parent = new NodeContainer(element, null); + if (options.background) { + renderer.rectangle(0, 0, renderer.width, renderer.height, new Color(options.background)); + } + if (element === element.ownerDocument.documentElement) { + // http://www.w3.org/TR/css3-background/#special-backgrounds + var canvasBackground = new NodeContainer(parent.color('backgroundColor').isTransparent() ? element.ownerDocument.body : element.ownerDocument.documentElement, null); + renderer.rectangle(0, 0, renderer.width, renderer.height, canvasBackground.color('backgroundColor')); + } + parent.visibile = parent.isElementVisible(); + this.createPseudoHideStyles(element.ownerDocument); + this.disableAnimations(element.ownerDocument); + this.nodes = flatten([parent].concat(this.getChildren(parent)).filter(function(container) { + return container.visible = container.isElementVisible(); + }).map(this.getPseudoElements, this)); + this.fontMetrics = new FontMetrics(); + log("Fetched nodes, total:", this.nodes.length); + log("Calculate overflow clips"); + this.calculateOverflowClips(); + log("Start fetching images"); + this.images = imageLoader.fetch(this.nodes.filter(isElement)); + this.ready = this.images.ready.then(bind(function() { + log("Images loaded, starting parsing"); + 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) { + if (!options.async) { + this.renderQueue.forEach(this.paint, this); + resolve(); + } else if (typeof(options.async) === "function") { + options.async.call(this, this.renderQueue, resolve); + } else if (this.renderQueue.length > 0){ + this.renderIndex = 0; + this.asyncRenderer(this.renderQueue, resolve); + } else { + resolve(); + } + }, this)); + }, this)); +} - textList.forEach(function(text, index) { - 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); +NodeParser.prototype.calculateOverflowClips = function() { + this.nodes.forEach(function(container) { + if (isElement(container)) { + if (isPseudoElement(container)) { + container.appendToDOM(); + } + container.borders = this.parseBorders(container); + var clip = (container.css('overflow') === "hidden") ? [container.borders.clip] : []; + var cssClip = container.parseClip(); + if (cssClip && ["absolute", "fixed"].indexOf(container.css('position')) !== -1) { + clip.push([["rect", + container.bounds.left + cssClip.left, + container.bounds.top + cssClip.top, + cssClip.right - cssClip.left, + cssClip.bottom - cssClip.top + ]]); + } + container.clip = hasParentClip(container) ? container.parent.clip.concat(clip) : clip; + container.backgroundClip = (container.css('overflow') !== "hidden") ? container.clip.concat([container.borders.clip]) : container.clip; + if (isPseudoElement(container)) { + container.cleanDOM(); + } + } else if (isTextNode(container)) { + container.clip = hasParentClip(container) ? container.parent.clip : []; + } + if (!isPseudoElement(container)) { + 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++]); + if (queue.length === this.renderIndex) { + resolve(); + } else if (asyncTimer + 20 > Date.now()) { + this.asyncRenderer(queue, resolve, asyncTimer); + } else { + setTimeout(bind(function() { + this.asyncRenderer(queue, resolve); + }, this), 0); } - } +}; - function listPosition (element, val) { - var boundElement = doc.createElement( "boundelement" ), - originalType, - bounds; +NodeParser.prototype.createPseudoHideStyles = function(document) { + this.createStyles(document, '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + ':before { content: "" !important; display: none !important; }' + + '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER + ':after { content: "" !important; display: none !important; }'); +}; - boundElement.style.display = "inline"; +NodeParser.prototype.disableAnimations = function(document) { + this.createStyles(document, '* { -webkit-animation: none !important; -moz-animation: none !important; -o-animation: none !important; animation: none !important; ' + + '-webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important;}'); +}; - originalType = element.style.listStyleType; - element.style.listStyleType = "none"; +NodeParser.prototype.createStyles = function(document, styles) { + var hidePseudoElements = document.createElement('style'); + hidePseudoElements.innerHTML = styles; + document.body.appendChild(hidePseudoElements); +}; + +NodeParser.prototype.getPseudoElements = function(container) { + var nodes = [[container]]; + if (container.node.nodeType === Node.ELEMENT_NODE) { + var before = this.getPseudoElement(container, ":before"); + var after = this.getPseudoElement(container, ":after"); + + if (before) { + nodes.push(before); + } + + if (after) { + nodes.push(after); + } + } + return flatten(nodes); +}; + +function toCamelCase(str) { + return str.replace(/(\-[a-z])/g, function(match){ + return match.toUpperCase().replace('-',''); + }); +} + +NodeParser.prototype.getPseudoElement = function(container, type) { + var style = container.computedStyle(type); + if(!style || !style.content || style.content === "none" || style.content === "-moz-alt-content" || style.display === "none") { + return null; + } + + var content = stripQuotes(style.content); + var isImage = content.substr(0, 3) === 'url'; + var pseudoNode = document.createElement(isImage ? 'img' : 'html2canvaspseudoelement'); + var pseudoContainer = new PseudoElementContainer(pseudoNode, container, type); + + for (var i = style.length-1; i >= 0; i--) { + var property = toCamelCase(style.item(i)); + pseudoNode.style[property] = style[property]; + } + + pseudoNode.className = PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + " " + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER; + + if (isImage) { + pseudoNode.src = parseBackgrounds(content)[0].args[0]; + return [pseudoContainer]; + } else { + var text = document.createTextNode(content); + pseudoNode.appendChild(text); + return [pseudoContainer, new TextContainer(text, pseudoContainer)]; + } +}; + + +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 && node.tagName !== "TEXTAREA" ? (container[0].isElementVisible() ? container.concat(this.getChildren(container[0])) : []) : container; + }, this)); +}; + +NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) { + var stack = new StackingContext(hasOwnStacking, container.getOpacity(), container.node, container.parent); + container.cloneTo(stack); + var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack; + 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) || container.hasTransform())) { + this.newStackingContext(container, true); + } else if (isElement(container) && ((isPositioned(container) && zIndex0(container)) || isInlineBlock(container) || isFloating(container))) { + this.newStackingContext(container, false); + } else { + container.assignStack(container.parent.stack); + } + }, this); +}; - boundElement.appendChild(doc.createTextNode(val)); +NodeParser.prototype.isBodyWithTransparentRoot = function(container) { + return container.node.nodeName === "BODY" && container.parent.color('backgroundColor').isTransparent(); +}; + +NodeParser.prototype.isRootElement = function(container) { + return container.parent === null; +}; + +NodeParser.prototype.sortStackingContexts = function(stack) { + stack.contexts.sort(zIndexSort(stack.contexts.slice(0))); + stack.contexts.forEach(this.sortStackingContexts, this); +}; + +NodeParser.prototype.parseTextBounds = function(container) { + return function(text, index, textList) { + if (container.parent.css("textDecoration").substr(0, 4) !== "none" || text.trim().length !== 0) { + if (this.support.rangeBounds && !container.parent.hasTransform()) { + var offset = textList.slice(0, index).join("").length; + 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.parent.hasTransform()); + container.node = replacementNode; + return bounds; + } + } else if(!this.support.rangeBounds || container.parent.hasTransform()){ + container.node = container.node.splitText(text.length); + } + return {}; + }; +}; - element.insertBefore(boundElement, element.firstChild); +NodeParser.prototype.getWrapperBounds = function(node, transform) { + var wrapper = node.ownerDocument.createElement('html2canvaswrapper'); + var parent = node.parentNode, + backupText = node.cloneNode(true); - bounds = Util.Bounds(boundElement); - element.removeChild(boundElement); - element.style.listStyleType = originalType; + wrapper.appendChild(node.cloneNode(true)); + parent.replaceChild(wrapper, node); + var bounds = transform ? offsetBounds(wrapper) : 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 ClearTransform() {} + +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). + negativeZindex.concat(nonInlineNonPositionedDescendants).concat(nonPositionedFloats) + .concat(inFlow).concat(stackLevel0).concat(text).concat(positiveZindex).forEach(function(container) { + this.renderQueue.push(container); + if (isStackingContext(container)) { + this.parse(container); + this.renderQueue.push(new ClearTransform()); + } + }, this); +}; - function elementIndex(el) { - var i = -1, - count = 1, - childs = el.parentNode.childNodes; +NodeParser.prototype.paint = function(container) { + try { + if (container instanceof ClearTransform) { + this.renderer.ctx.restore(); + } else if (isTextNode(container)) { + if (isPseudoElement(container.parent)) { + container.parent.appendToDOM(); + } + this.paintText(container); + if (isPseudoElement(container.parent)) { + container.parent.cleanDOM(); + } + } else { + this.paintNode(container); + } + } catch(e) { + log(e); + if (this.options.strict) { + throw e; + } + } +}; - if (el.parentNode) { - while(childs[++i] !== el) { - if (childs[i].nodeType === 1) { - count++; +NodeParser.prototype.paintNode = function(container) { + if (isStackingContext(container)) { + this.renderer.setOpacity(container.opacity); + this.renderer.ctx.save(); + if (container.hasTransform()) { + this.renderer.setTransform(container.parseTransform()); } - } - return count; + } + + if (container.node.nodeName === "INPUT" && container.node.type === "checkbox") { + this.paintCheckbox(container); + } else if (container.node.nodeName === "INPUT" && container.node.type === "radio") { + this.paintRadio(container); } else { - return -1; + this.paintElement(container); } - } +}; - 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; +NodeParser.prototype.paintElement = function(container) { + var bounds = container.parseBounds(); + this.renderer.clip(container.backgroundClip, function() { + this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth)); + }, this); + + this.renderer.clip(container.clip, function() { + this.renderer.renderBorders(container.borders.borders); + }, this); + + this.renderer.clip(container.backgroundClip, function() { + switch (container.node.nodeName) { + case "svg": + case "IFRAME": + var imgContainer = this.images.get(container.node); + if (imgContainer) { + this.renderer.renderImage(container, bounds, container.borders, imgContainer); + } else { + log("Error loading <" + container.node.nodeName + ">", container.node); + } + break; + case "IMG": + var imageContainer = this.images.get(container.node.src); + if (imageContainer) { + this.renderer.renderImage(container, bounds, container.borders, imageContainer); + } else { + log("Error loading ", container.node.src); + } + break; + case "CANVAS": + this.renderer.renderImage(container, bounds, container.borders, {image: container.node}); + break; + case "SELECT": + case "INPUT": + case "TEXTAREA": + this.paintFormValue(container); + break; + } + }, this); +}; + +NodeParser.prototype.paintCheckbox = function(container) { + var b = container.parseBounds(); + + var size = Math.min(b.width, b.height); + var bounds = {width: size - 1, height: size - 1, top: b.top, left: b.left}; + var r = [3, 3]; + var radius = [r, r, r, r]; + var borders = [1,1,1,1].map(function(w) { + return {color: new Color('#A5A5A5'), width: w}; + }); + + var borderPoints = calculateCurvePoints(bounds, radius, borders); + + this.renderer.clip(container.backgroundClip, function() { + this.renderer.rectangle(bounds.left + 1, bounds.top + 1, bounds.width - 2, bounds.height - 2, new Color("#DEDEDE")); + this.renderer.renderBorders(calculateBorders(borders, bounds, borderPoints, radius)); + if (container.node.checked) { + this.renderer.font(new Color('#424242'), 'normal', 'normal', 'bold', (size - 3) + "px", 'arial'); + this.renderer.text("\u2714", bounds.left + size / 6, bounds.top + size - 1); + } + }, this); +}; + +NodeParser.prototype.paintRadio = function(container) { + var bounds = container.parseBounds(); + + var size = Math.min(bounds.width, bounds.height) - 2; + + this.renderer.clip(container.backgroundClip, function() { + this.renderer.circleStroke(bounds.left + 1, bounds.top + 1, size, new Color('#DEDEDE'), 1, new Color('#A5A5A5')); + if (container.node.checked) { + this.renderer.circle(Math.ceil(bounds.left + size / 4) + 1, Math.ceil(bounds.top + size / 4) + 1, Math.floor(size / 2), new Color('#424242')); + } + }, this); +}; + +NodeParser.prototype.paintFormValue = function(container) { + var value = container.getValue(); + if (value.length > 0) { + var document = container.node.ownerDocument; + var wrapper = document.createElement('html2canvaswrapper'); + var properties = ['lineHeight', 'textAlign', 'fontFamily', 'fontWeight', 'fontSize', 'color', + 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', + 'width', 'height', 'borderLeftStyle', 'borderTopStyle', 'borderLeftWidth', 'borderTopWidth', + 'boxSizing', 'whiteSpace', 'wordWrap']; + + properties.forEach(function(property) { + try { + wrapper.style[property] = container.css(property); + } catch(e) { + // Older IE has issues with "border" + log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message); + } + }); + var bounds = container.parseBounds(); + wrapper.style.position = "fixed"; + wrapper.style.left = bounds.left + "px"; + wrapper.style.top = bounds.top + "px"; + wrapper.textContent = value; + document.body.appendChild(wrapper); + this.paintText(new TextContainer(wrapper.firstChild, container)); + document.body.removeChild(wrapper); } +}; - return text + ". "; - } +NodeParser.prototype.paintText = function(container) { + container.applyTextTransform(); + var characters = punycode.ucs2.decode(container.node.data); + var textList = (!this.options.letterRendering || noLetterSpacing(container)) && !hasUnicode(container.node.data) ? getWords(characters) : characters.map(function(character) { + return punycode.ucs2.encode([character]); + }); - function renderListItem(element, stack, elBounds) { - var x, - text, - ctx = stack.ctx, - type = getCSS(element, "listStyleType"), - listBounds; + var weight = container.parent.fontWeight(); + var size = container.parent.css('fontSize'); + var family = container.parent.css('fontFamily'); + var shadows = container.parent.parseTextShadows(); - 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")); + this.renderer.font(container.parent.color('color'), container.parent.css('fontStyle'), container.parent.css('fontVariant'), weight, size, family); + if (shadows.length) { + // TODO: support multiple text shadows + this.renderer.fontShadow(shadows[0].color, shadows[0].offsetX, shadows[0].offsetY, shadows[0].blur); + } else { + this.renderer.clearShadow(); + } - if (getCSS(element, "listStylePosition") === "inside") { - ctx.setVariable("textAlign", "left"); - x = elBounds.left; - } else { - return; - } + 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); +}; - drawText(text, x, listBounds.bottom, ctx); +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.color("color")); + break; + case "overline": + this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.color("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.color("color")); + break; } - } +}; - function loadImage (src){ - var img = images[src]; - return (img && img.succeeded === true) ? img.img : false; - } +var borderColorTransforms = { + inset: [ + ["darken", 0.60], + ["darken", 0.10], + ["darken", 0.10], + ["darken", 0.60] + ] +}; - 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)); +NodeParser.prototype.parseBorders = function(container) { + var nodeBounds = container.parseBounds(); + var radius = getBorderRadiusData(container); + var borders = ["Top", "Right", "Bottom", "Left"].map(function(side, index) { + var style = container.css('border' + side + 'Style'); + var color = container.color('border' + side + 'Color'); + if (style === "inset" && color.isBlack()) { + color = new Color([255, 255, 255, color.a]); // this is wrong, but + } + var colorTransform = borderColorTransforms[style] ? borderColorTransforms[style][index] : null; + return { + width: container.cssInt('border' + side + 'Width'), + color: colorTransform ? color[colorTransform[0]](colorTransform[1]) : color, + args: null + }; + }); + var borderPoints = calculateCurvePoints(nodeBounds, radius, borders); return { - left:x, - top:y, - width:x2-x, - height:y2-y - }; - } - - function setZ(element, stack, parentStack){ - var newContext, - isPositioned = stack.cssPosition !== 'static', - zIndex = isPositioned ? getCSS(element, 'zIndex') : 'auto', - opacity = getCSS(element, 'opacity'), - isFloated = getCSS(element, 'cssFloat') !== 'none'; - - // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context - // When a new stacking context should be created: - // the root element (HTML), - // positioned (absolutely or relatively) with a z-index value other than "auto", - // elements with an opacity value less than 1. (See the specification for opacity), - // on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post) - - stack.zIndex = newContext = h2czContext(zIndex); - newContext.isPositioned = isPositioned; - newContext.isFloated = isFloated; - newContext.opacity = opacity; - newContext.ownStacking = (zIndex !== 'auto' || opacity < 1); - newContext.depth = parentStack ? (parentStack.zIndex.depth + 1) : 0; - - if (parentStack) { - parentStack.zIndex.children.push(stack); - } - } - - function h2czContext(zindex) { - return { - depth: 0, - zindex: zindex, - children: [] + clip: this.parseBackgroundClip(container, borderPoints, borders, radius, nodeBounds), + borders: calculateBorders(borders, nodeBounds, borderPoints, radius) }; - } - - 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'); +function calculateBorders(borders, nodeBounds, borderPoints, radius) { + return 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; }); - } +} - function getCurvePoints(x, y, r1, r2) { +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 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 + 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 - }) + 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] < width / 2 ? borderRadius[0][0] : width / 2, + tlv = borderRadius[0][1] < height / 2 ? borderRadius[0][1] : height / 2, + trh = borderRadius[1][0] < width / 2 ? borderRadius[1][0] : width / 2, + trv = borderRadius[1][1] < height / 2 ? borderRadius[1][1] : height / 2, + brh = borderRadius[2][0] < width / 2 ? borderRadius[2][0] : width / 2, + brv = borderRadius[2][1] < height / 2 ? borderRadius[2][1] : height / 2, + blh = borderRadius[3][0] < width / 2 ? borderRadius[3][0] : width / 2, + blv = borderRadius[3][1] < height / 2 ? borderRadius[3][1] : height / 2; + + var topWidth = width - trh, + rightHeight = height - brv, + bottomWidth = width - brh, + leftHeight = height - blv; - function bezierCurve(start, startControl, endControl, end) { + 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), 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), 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 { + 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]); - } + 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 = []; - 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); + borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]); + outer1[1].curveTo(borderArgs); } else { - borderArgs.push(["line", x, y]); + borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]); } if (radius2[0] > 0 || radius2[1] > 0) { - borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]); + 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]]); } - } - - 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); + borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]); + inner1[1].curveToReversed(borderArgs); } else { - borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]); + borderArgs.push(["line", borderData.c4[0], borderData.c4[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]]); - } + return borderArgs; +} +function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) { if (radius1[0] > 0 || radius1[1] > 0) { - borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]); - inner1[1].curveToReversed(borderArgs); + borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]); + corner1[0].curveTo(borderArgs); + corner1[1].curveTo(borderArgs); } else { - borderArgs.push([ "line", borderData.c4[0], borderData.c4[1]]); + borderArgs.push(["line", x, y]); } - return borderArgs; - } + if (radius2[0] > 0 || radius2[1] > 0) { + borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]); + } +} - function calculateCurvePoints(bounds, borderRadius, borders) { +function negativeZIndex(container) { + return container.cssInt("zIndex") < 0; +} - 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; +function positiveZIndex(container) { + return container.cssInt("zIndex") > 0; +} - 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 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"))); +} + +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 renderableNode(node) { + return (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE); +} + +function isPositionedForStacking(container) { + var position = container.css("position"); + var zIndex = (["absolute", "relative", "fixed"].indexOf(position) !== -1) ? container.css("zIndex") : "auto"; + return zIndex !== "auto"; +} + +function isPositioned(container) { + return container.css("position") !== "static"; +} + +function isFloating(container) { + return container.css("float") !== "none"; +} + +function isInlineBlock(container) { + return ["inline-block", "inline-table"].indexOf(container.css("display")) !== -1; +} + +function not(callback) { + var context = this; + return function() { + return !callback.apply(context, arguments); }; - } +} - function getBorderClip(element, borderPoints, borders, radius, bounds) { - var backgroundClip = getCSS(element, 'backgroundClip'), - borderArgs = []; +function isElement(container) { + return container.node.nodeType === Node.ELEMENT_NODE; +} - 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; +function isPseudoElement(container) { + return container.isPseudoElement === true; +} - 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; +function isTextNode(container) { + return container.node.nodeType === Node.TEXT_NODE; +} + +function zIndexSort(contexts) { + return function(a, b) { + return (a.cssInt("zIndex") + (contexts.indexOf(a) / contexts.length)) - (b.cssInt("zIndex") + (contexts.indexOf(b) / contexts.length)); + }; +} + +function hasOpacity(container) { + return container.getOpacity() < 1; +} + +function asInt(value) { + return parseInt(value, 10); +} + +function getWidth(border) { + return border.width; +} + +function nonIgnoredElement(nodeContainer) { + return (nodeContainer.node.nodeType !== Node.ELEMENT_NODE || ["SCRIPT", "HEAD", "TITLE", "OBJECT", "BR", "OPTION"].indexOf(nodeContainer.node.nodeName) === -1); +} + +function flatten(arrays) { + return [].concat.apply([], arrays); +} + +function stripQuotes(content) { + var first = content.substr(0, 1); + return (first === content.substr(content.length - 1) && first.match(/'|"/)) ? content.substr(1, content.length - 2) : content; +} + +function getWords(characters) { + var words = [], i = 0, onWordBoundary = false, word; + while(characters.length) { + if (isWordBoundary(characters[i]) === onWordBoundary) { + word = characters.splice(0, i); + if (word.length) { + words.push(punycode.ucs2.encode(word)); + } + onWordBoundary =! onWordBoundary; + i = 0; + } else { + i++; + } + + if (i >= characters.length) { + word = characters.splice(0, i); + if (word.length) { + words.push(punycode.ucs2.encode(word)); + } + } } + return words; +} + +function isWordBoundary(characterCode) { + return [ + 32, // + 13, // \r + 10, // \n + 9, // \t + 45 // - + ].indexOf(characterCode) !== -1; +} + +function hasUnicode(string) { + return (/[^\u0000-\u00ff]/).test(string); +} + +module.exports = NodeParser; + +},{"./color":3,"./fontmetrics":7,"./log":13,"./nodecontainer":14,"./pseudoelementcontainer":18,"./stackingcontext":21,"./textcontainer":25,"./utils":26,"punycode":1}],16:[function(_dereq_,module,exports){ +var XHR = _dereq_('./xhr'); +var utils = _dereq_('./utils'); +var log = _dereq_('./log'); +var createWindowClone = _dereq_('./clone'); +var decode64 = utils.decode64; + +function Proxy(src, proxyUrl, document) { + var supportsCORS = ('withCredentials' in new XMLHttpRequest()); + if (!proxyUrl) { + return Promise.reject("No proxy configured"); + } + var callback = createCallback(supportsCORS); + var url = createProxyUrl(proxyUrl, src, callback); + + return supportsCORS ? XHR(url) : (jsonp(document, url, callback).then(function(response) { + return decode64(response.content); + })); +} +var proxyCount = 0; + +function ProxyURL(src, proxyUrl, document) { + var supportsCORSImage = ('crossOrigin' in new Image()); + var callback = createCallback(supportsCORSImage); + var url = createProxyUrl(proxyUrl, src, callback); + return (supportsCORSImage ? Promise.resolve(url) : jsonp(document, url, callback).then(function(response) { + return "data:" + response.type + ";base64," + response.content; + })); +} - return borderArgs; - } +function jsonp(document, url, callback) { + return new Promise(function(resolve, reject) { + var s = document.createElement("script"); + var cleanup = function() { + delete window.html2canvas.proxy[callback]; + document.body.removeChild(s); + }; + window.html2canvas.proxy[callback] = function(response) { + cleanup(); + resolve(response); + }; + s.src = url; + s.onerror = function(e) { + cleanup(); + reject(e); + }; + document.body.appendChild(s); + }); +} - 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: [] - }; +function createCallback(useCORS) { + return !useCORS ? "html2canvas_" + Date.now() + "_" + (++proxyCount) + "_" + Math.round(Math.random() * 100000) : ""; +} - 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[3].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; - } +function createProxyUrl(proxyUrl, src, callback) { + return proxyUrl + "?url=" + encodeURIComponent(src) + (callback.length ? "&callback=html2canvas.proxy." + callback : ""); +} - borderData.borders.push({ - args: borderArgs, - color: borders[borderSide].color - }); +function documentFromHTML(src) { + return function(html) { + var parser = new DOMParser(), doc; + try { + doc = parser.parseFromString(html, "text/html"); + } catch(e) { + log("DOMParser not supported, falling back to createHTMLDocument"); + doc = document.implementation.createHTMLDocument(""); + try { + doc.open(); + doc.write(html); + doc.close(); + } catch(ee) { + log("createHTMLDocument write not supported, falling back to document.body.innerHTML"); + doc.body.innerHTML = html; // ie9 doesnt support writing to documentElement + } + } - } - } + var b = doc.querySelector("base"); + if (!b || !b.href.host) { + var base = doc.createElement("base"); + base.href = src; + doc.head.insertBefore(base, doc.head.firstChild); + } - return borderData; - } + return doc; + }; +} - 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)); +function loadUrlDocument(src, proxy, document, width, height, options) { + return new Proxy(src, proxy, window.document).then(documentFromHTML(src)).then(function(doc) { + return createWindowClone(doc, document, width, height, options, 0, 0); }); - 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" - Util.log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message); - } +} + +exports.Proxy = Proxy; +exports.ProxyURL = ProxyURL; +exports.loadUrlDocument = loadUrlDocument; + +},{"./clone":2,"./log":13,"./utils":26,"./xhr":28}],17:[function(_dereq_,module,exports){ +var ProxyURL = _dereq_('./proxy').ProxyURL; + +function ProxyImageContainer(src, proxy) { + var link = document.createElement("a"); + link.href = src; + src = link.href; + this.src = src; + this.image = new Image(); + var self = this; + this.promise = new Promise(function(resolve, reject) { + self.image.crossOrigin = "Anonymous"; + self.image.onload = resolve; + self.image.onerror = reject; + + new ProxyURL(src, proxy, document).then(function(url) { + self.image.src = url; + })['catch'](reject); }); +} - valueWrap.style.borderColor = "black"; - valueWrap.style.borderStyle = "solid"; - valueWrap.style.display = "block"; - valueWrap.style.position = "absolute"; +module.exports = ProxyImageContainer; - if (/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName === "SELECT"){ - valueWrap.style.lineHeight = getCSS(el, "height"); - } +},{"./proxy":16}],18:[function(_dereq_,module,exports){ +var NodeContainer = _dereq_('./nodecontainer'); + +function PseudoElementContainer(node, parent, type) { + NodeContainer.call(this, node, parent); + this.isPseudoElement = true; + this.before = type === ":before"; +} + +PseudoElementContainer.prototype.cloneTo = function(stack) { + PseudoElementContainer.prototype.cloneTo.call(this, stack); + stack.isPseudoElement = true; + stack.before = this.before; +}; - valueWrap.style.top = bounds.top + "px"; - valueWrap.style.left = bounds.left + "px"; +PseudoElementContainer.prototype = Object.create(NodeContainer.prototype); - textValue = (el.nodeName === "SELECT") ? (el.options[el.selectedIndex] || 0).text : el.value; - if(!textValue) { - textValue = el.placeholder; +PseudoElementContainer.prototype.appendToDOM = function() { + if (this.before) { + this.parent.node.insertBefore(this.node, this.parent.node.firstChild); + } else { + this.parent.node.appendChild(this.node); } + this.parent.node.className += " " + this.getHideClass(); +}; + +PseudoElementContainer.prototype.cleanDOM = function() { + this.node.parentNode.removeChild(this.node); + this.parent.node.className = this.parent.node.className.replace(this.getHideClass(), ""); +}; + +PseudoElementContainer.prototype.getHideClass = function() { + return this["PSEUDO_HIDE_ELEMENT_CLASS_" + (this.before ? "BEFORE" : "AFTER")]; +}; + +PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE = "___html2canvas___pseudoelement_before"; +PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER = "___html2canvas___pseudoelement_after"; - textNode = doc.createTextNode(textValue); +module.exports = PseudoElementContainer; - valueWrap.appendChild(textNode); - body.appendChild(valueWrap); +},{"./nodecontainer":14}],19:[function(_dereq_,module,exports){ +var log = _dereq_('./log'); - renderText(el, textNode, stack); - body.removeChild(valueWrap); - } +function Renderer(width, height, images, options, document) { + this.width = width; + this.height = height; + this.images = images; + this.options = options; + this.document = document; +} - function drawImage (ctx) { - ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 1)); - numDraws+=1; - } +Renderer.prototype.renderImage = function(container, bounds, borderData, imageContainer) { + var paddingLeft = container.cssInt('paddingLeft'), + paddingTop = container.cssInt('paddingTop'), + paddingRight = container.cssInt('paddingRight'), + paddingBottom = container.cssInt('paddingBottom'), + borders = borderData.borders; + + var width = bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight); + var height = bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom); + this.drawImage( + imageContainer, + 0, + 0, + imageContainer.image.width || width, + imageContainer.image.height || height, + bounds.left + paddingLeft + borders[3].width, + bounds.top + paddingTop + borders[0].width, + width, + height + ); +}; - function getPseudoElement(el, which) { - var elStyle = window.getComputedStyle(el, which); - var parentStyle = window.getComputedStyle(el); - // If no content attribute is present, the pseudo element is hidden, - // or the parent has a content property equal to the content on the pseudo element, - // move along. - if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || - elStyle.display === "none" || parentStyle.content === elStyle.content) { - return; +Renderer.prototype.renderBackground = function(container, bounds, borderData) { + if (bounds.height > 0 && bounds.width > 0) { + this.renderBackgroundColor(container, bounds); + this.renderBackgroundImage(container, bounds, borderData); } - var content = elStyle.content + ''; +}; - // Strip inner quotes - if(content[0] === "'" || content[0] === "\"") { - content = content.replace(/(^['"])|(['"]$)/g, ''); +Renderer.prototype.renderBackgroundColor = function(container, bounds) { + var color = container.color("backgroundColor"); + if (!color.isTransparent()) { + this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, color); } +}; - var isImage = content.substr( 0, 3 ) === 'url', - elps = document.createElement( isImage ? 'img' : 'span' ); +Renderer.prototype.renderBorders = function(borders) { + borders.forEach(this.renderBorder, this); +}; - elps.className = pseudoHide + "-element "; +Renderer.prototype.renderBorder = function(data) { + if (!data.color.isTransparent() && data.args !== null) { + this.drawShape(data.args, data.color); + } +}; - 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) { - Util.log(['Tried to assign readonly property ', prop, 'Error:', e]); - } - }); +Renderer.prototype.renderBackgroundImage = function(container, bounds, borderData) { + var backgroundImages = container.parseBackgroundImages(); + backgroundImages.reverse().forEach(function(backgroundImage, index, arr) { + switch(backgroundImage.method) { + case "url": + var image = this.images.get(backgroundImage.args[0]); + if (image) { + this.renderBackgroundRepeating(container, bounds, image, arr.length - (index+1), borderData); + } else { + log("Error loading background-image", backgroundImage.args[0]); + } + break; + case "linear-gradient": + case "gradient": + var gradientImage = this.images.get(backgroundImage.value); + if (gradientImage) { + this.renderBackgroundGradient(gradientImage, bounds, borderData); + } else { + log("Error loading background-image", backgroundImage.args[0]); + } + break; + case "none": + break; + default: + log("Unknown background-image type", backgroundImage.args[0]); + } + }, this); +}; - if(isImage) { - elps.src = Util.parseBackgroundImage(content)[0].args[0]; - } else { - elps.innerHTML = content; - } - return elps; - } - - function indexedProperty(property) { - return (isNaN(window.parseInt(property, 10))); - } - - 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 = Util.BackgroundSize(el, bounds, image, imageIndex), - backgroundPosition = Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize), - backgroundRepeat = Util.BackgroundRepeat(el, imageIndex); - - image = resizeImage(image, backgroundSize); - - switch (backgroundRepeat) { - case "repeat-x": - case "repeat no-repeat": - backgroundRepeatShape(ctx, image, backgroundPosition, bounds, - bounds.left, bounds.top + backgroundPosition.top, 99999, image.height); +Renderer.prototype.renderBackgroundRepeating = function(container, bounds, imageContainer, index, borderData) { + var size = container.parseBackgroundSize(bounds, imageContainer.image, index); + var position = container.parseBackgroundPosition(bounds, imageContainer.image, index, size); + var repeat = container.parseBackgroundRepeat(index); + switch (repeat) { + case "repeat-x": + case "repeat no-repeat": + this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + borderData[3], bounds.top + position.top + borderData[0], 99999, size.height, borderData); break; - case "repeat-y": - case "no-repeat repeat": - backgroundRepeatShape(ctx, image, backgroundPosition, bounds, - bounds.left + backgroundPosition.left, bounds.top, image.width, 99999); + case "repeat-y": + case "no-repeat repeat": + this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + borderData[0], size.width, 99999, borderData); break; - case "no-repeat": - backgroundRepeatShape(ctx, image, backgroundPosition, bounds, - bounds.left + backgroundPosition.left, bounds.top + backgroundPosition.top, image.width, image.height); + case "no-repeat": + this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + position.top + borderData[0], size.width, size.height, borderData); break; - default: - renderBackgroundRepeat(ctx, image, backgroundPosition, { - top: bounds.top, - left: bounds.left, - width: image.width, - height: image.height - }); + default: + this.renderBackgroundRepeat(imageContainer, position, size, {top: bounds.top, left: bounds.left}, borderData[3], borderData[0]); break; } - } - - function renderBackgroundImage(element, bounds, ctx) { - var backgroundImage = getCSS(element, "backgroundImage"), - backgroundImages = Util.parseBackgroundImage(backgroundImage), - image, - imageIndex = backgroundImages.length; - - while(imageIndex--) { - backgroundImage = backgroundImages[imageIndex]; +}; - if (!backgroundImage.args || backgroundImage.args.length === 0) { - continue; - } +module.exports = Renderer; + +},{"./log":13}],20:[function(_dereq_,module,exports){ +var Renderer = _dereq_('../renderer'); +var LinearGradientContainer = _dereq_('../lineargradientcontainer'); +var log = _dereq_('../log'); + +function CanvasRenderer(width, height) { + Renderer.apply(this, arguments); + this.canvas = this.options.canvas || this.document.createElement("canvas"); + if (!this.options.canvas) { + this.canvas.width = width; + this.canvas.height = height; + } + this.ctx = this.canvas.getContext("2d"); + this.taintCtx = this.document.createElement("canvas").getContext("2d"); + this.ctx.textBaseline = "bottom"; + this.variables = {}; + log("Initialized CanvasRenderer with size", width, "x", height); +} - var key = backgroundImage.method === 'url' ? - backgroundImage.args[0] : - backgroundImage.value; +CanvasRenderer.prototype = Object.create(Renderer.prototype); - image = loadImage(key); +CanvasRenderer.prototype.setFillStyle = function(fillStyle) { + this.ctx.fillStyle = typeof(fillStyle) === "object" && !!fillStyle.isColor ? fillStyle.toString() : fillStyle; + return this.ctx; +}; - // TODO add support for background-origin - if (image) { - renderBackgroundRepeating(element, bounds, ctx, image, imageIndex); - } else { - Util.log("html2canvas: Error loading background:", backgroundImage); - } - } - } +CanvasRenderer.prototype.rectangle = function(left, top, width, height, color) { + this.setFillStyle(color).fillRect(left, top, width, height); +}; - function resizeImage(image, bounds) { - if(image.width === bounds.width && image.height === bounds.height) { - return image; - } +CanvasRenderer.prototype.circle = function(left, top, size, color) { + this.setFillStyle(color); + this.ctx.beginPath(); + this.ctx.arc(left + size / 2, top + size / 2, size / 2, 0, Math.PI*2, true); + this.ctx.closePath(); + this.ctx.fill(); +}; - 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; - } +CanvasRenderer.prototype.circleStroke = function(left, top, size, color, stroke, strokeColor) { + this.circle(left, top, size, color); + this.ctx.strokeStyle = strokeColor.toString(); + this.ctx.stroke(); +}; - function setOpacity(ctx, element, parentStack) { - return ctx.setVariable("globalAlpha", getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1)); - } +CanvasRenderer.prototype.drawShape = function(shape, color) { + this.shape(shape); + this.setFillStyle(color).fill(); +}; - function removePx(str) { - return str.replace("px", ""); - } - - function getTransform(element, parentStack) { - var transformRegExp = /(matrix)\((.+)\)/; - var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform"); - var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px"; - - transformOrigin = transformOrigin.split(" ").map(removePx).map(Util.asFloat); - - var matrix; - if (transform && transform !== "none") { - var match = transform.match(transformRegExp); - if (match) { - switch(match[1]) { - case "matrix": - matrix = match[2].split(",").map(Util.trimText).map(Util.asFloat); - break; +CanvasRenderer.prototype.taints = function(imageContainer) { + if (imageContainer.tainted === null) { + this.taintCtx.drawImage(imageContainer.image, 0, 0); + try { + this.taintCtx.getImageData(0, 0, 1, 1); + imageContainer.tainted = false; + } catch(e) { + this.taintCtx = document.createElement("canvas").getContext("2d"); + imageContainer.tainted = true; } - } } - return { - origin: transformOrigin, - matrix: matrix - }; - } - - function createStack(element, parentStack, bounds, transform) { - var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height), - stack = { - ctx: ctx, - opacity: setOpacity(ctx, element, parentStack), - cssPosition: getCSS(element, "position"), - borders: getBorderData(element), - transform: transform, - clip: (parentStack && parentStack.clip) ? Util.Extend( {}, parentStack.clip ) : null - }; - - setZ(element, stack, parentStack); + return imageContainer.tainted; +}; - // 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; +CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx, dy, dw, dh) { + if (!this.taints(imageContainer) || this.options.allowTaint) { + this.ctx.drawImage(imageContainer.image, sx, sy, sw, sh, dx, dy, dw, dh); } +}; - return stack; - } +CanvasRenderer.prototype.clip = function(shapes, callback, context) { + this.ctx.save(); + shapes.filter(hasEntries).forEach(function(shape) { + this.shape(shape).clip(); + }, this); + callback.call(context); + this.ctx.restore(); +}; - 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) - }; +CanvasRenderer.prototype.shape = function(shape) { + this.ctx.beginPath(); + shape.forEach(function(point, index) { + if (point[0] === "rect") { + this.ctx.rect.apply(this.ctx, point.slice(1)); + } else { + this.ctx[(index === 0) ? "moveTo" : point[0] + "To" ].apply(this.ctx, point.slice(1)); + } + }, this); + this.ctx.closePath(); + return this.ctx; +}; - if (clip) { - backgroundBounds = clipBounds(backgroundBounds, clip); - } +CanvasRenderer.prototype.font = function(color, style, variant, weight, size, family) { + this.setFillStyle(color).font = [style, variant, weight, size, family].join(" ").split(",")[0]; +}; - return backgroundBounds; - } +CanvasRenderer.prototype.fontShadow = function(color, offsetX, offsetY, blur) { + this.setVariable("shadowColor", color.toString()) + .setVariable("shadowOffsetY", offsetX) + .setVariable("shadowOffsetX", offsetY) + .setVariable("shadowBlur", blur); +}; - function getBounds(element, transform) { - var bounds = (transform.matrix) ? Util.OffsetBounds(element) : Util.Bounds(element); - transform.origin[0] += bounds.left; - transform.origin[1] += bounds.top; - return bounds; - } +CanvasRenderer.prototype.clearShadow = function() { + this.setVariable("shadowColor", "rgba(0,0,0,0)"); +}; - function renderElement(element, parentStack, ignoreBackground) { - var transform = getTransform(element, parentStack), - bounds = getBounds(element, transform), - image, - stack = createStack(element, parentStack, bounds, transform), - borders = stack.borders, - ctx = stack.ctx, - backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip), - borderData = parseBorders(element, bounds, borders), - backgroundColor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor"); +CanvasRenderer.prototype.setOpacity = function(opacity) { + this.ctx.globalAlpha = opacity; +}; +CanvasRenderer.prototype.setTransform = function(transform) { + this.ctx.translate(transform.origin[0], transform.origin[1]); + this.ctx.transform.apply(this.ctx, transform.matrix); + this.ctx.translate(-transform.origin[0], -transform.origin[1]); +}; - createShape(ctx, borderData.clip); +CanvasRenderer.prototype.setVariable = function(property, value) { + if (this.variables[property] !== value) { + this.variables[property] = this.ctx[property] = value; + } - ctx.save(); - ctx.clip(); + return this; +}; - if (backgroundBounds.height > 0 && backgroundBounds.width > 0 && !ignoreBackground) { - renderBackgroundColor(ctx, bounds, backgroundColor); - renderBackgroundImage(element, backgroundBounds, ctx); - } else if (ignoreBackground) { - stack.backgroundColor = backgroundColor; - } +CanvasRenderer.prototype.text = function(text, left, bottom) { + this.ctx.fillText(text, left, bottom); +}; - ctx.restore(); +CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, size, bounds, left, top, width, height, borderData) { + 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)] + ]; + this.clip([shape], function() { + this.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]); + }, this); +}; - borderData.borders.forEach(function(border) { - renderBorders(ctx, border.args, border.color); - }); +CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, size, bounds, borderLeft, borderTop) { + var offsetX = Math.round(bounds.left + backgroundPosition.left + borderLeft), offsetY = Math.round(bounds.top + backgroundPosition.top + borderTop); + this.setFillStyle(this.ctx.createPattern(this.resizeImage(imageContainer, size), "repeat")); + this.ctx.translate(offsetX, offsetY); + this.ctx.fill(); + this.ctx.translate(-offsetX, -offsetY); +}; - switch(element.nodeName){ - case "IMG": - if ((image = loadImage(element.getAttribute('src')))) { - renderImage(ctx, element, image, bounds, borders); - } else { - Util.log("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; +CanvasRenderer.prototype.renderBackgroundGradient = function(gradientImage, bounds) { + if (gradientImage instanceof LinearGradientContainer) { + var gradient = this.ctx.createLinearGradient( + bounds.left + bounds.width * gradientImage.x0, + bounds.top + bounds.height * gradientImage.y0, + bounds.left + bounds.width * gradientImage.x1, + bounds.top + bounds.height * gradientImage.y1); + gradientImage.colorStops.forEach(function(colorStop) { + gradient.addColorStop(colorStop.stop, colorStop.color.toString()); + }); + this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, gradient); + } +}; + +CanvasRenderer.prototype.resizeImage = function(imageContainer, size) { + var image = imageContainer.image; + if(image.width === size.width && image.height === size.height) { + return image; } - return stack; - } + var ctx, canvas = document.createElement('canvas'); + canvas.width = size.width; + canvas.height = size.height; + ctx = canvas.getContext("2d"); + ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height ); + return canvas; +}; - function isElementVisible(element) { - return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore")); - } +function hasEntries(array) { + return array.length > 0; +} - function parseElement (element, stack, cb) { - if (!cb) { - cb = function(){}; - } - if (isElementVisible(element)) { - stack = renderElement(element, stack, false) || stack; - if (!ignoreElementsRegExp.test(element.nodeName)) { - return parseChildren(element, stack, cb); - } - } - cb(); - } +module.exports = CanvasRenderer; - function parseChildren(element, stack, cb) { - var children = Util.Children(element); - // After all nodes have processed, finished() will call the cb. - // We add one and kick it off so this will still work when children.length === 0. - // Note that unless async is true, this will happen synchronously, just will callbacks. - var jobs = children.length + 1; - finished(); +},{"../lineargradientcontainer":12,"../log":13,"../renderer":19}],21:[function(_dereq_,module,exports){ +var NodeContainer = _dereq_('./nodecontainer'); - if (options.async) { - children.forEach(function(node) { - // Don't block the page from rendering - setTimeout(function(){ parseNode(node); }, 0); - }); - } else { - children.forEach(parseNode); - } - - function parseNode(node) { - if (node.nodeType === node.ELEMENT_NODE) { - parseElement(node, stack, finished); - } else if (node.nodeType === node.TEXT_NODE) { - renderText(element, node, stack); - finished(); - } else { - finished(); - } - } - function finished(el) { - if (--jobs <= 0){ - Util.log("finished rendering " + children.length + " children."); - cb(); - } - } - } -}; -_html2canvas.Preload = function( options ) { - - var images = { - numLoaded: 0, // also failed are counted here - numFailed: 0, - numTotal: 0, - cleanupDone: false - }, - pageOrigin, - Util = _html2canvas.Util, - methods, - i, - count = 0, - element = options.elements[0] || document.body, - doc = element.ownerDocument, - domImages = element.getElementsByTagName('img'), // Fetch images of the present element only - imgLen = domImages.length, - link = doc.createElement("a"), - supportCORS = (function( img ){ - return (img.crossOrigin !== undefined); - })(new Image()), - timeoutTimer; - - link.href = window.location.href; - pageOrigin = link.protocol + link.host; - - function isSameOrigin(url){ - link.href = url; - link.href = link.href; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/ - var origin = link.protocol + link.host; - return (origin === pageOrigin); - } +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; +} - function start(){ - Util.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")"); - if (!images.firstRun && images.numLoaded >= images.numTotal){ - Util.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")"); +StackingContext.prototype = Object.create(NodeContainer.prototype); - if (typeof options.complete === "function"){ - options.complete(images); - } +StackingContext.prototype.getParentStack = function(context) { + var parentStack = (this.parent) ? this.parent.stack : null; + return parentStack ? (parentStack.ownStacking ? parentStack : parentStack.getParentStack(context)) : context.stack; +}; - } - } +module.exports = StackingContext; - // TODO modify proxy to serve images with CORS enabled, where available - function proxyGetImage(url, img, imageObj){ - var callback_name, - scriptUrl = options.proxy, - script; +},{"./nodecontainer":14}],22:[function(_dereq_,module,exports){ +function Support(document) { + this.rangeBounds = this.testRangeBounds(document); + this.cors = this.testCORS(); + this.svg = this.testSVG(); +} - link.href = url; - url = link.href; // work around for pages with base href="" set - WARNING: this may change the url +Support.prototype.testRangeBounds = function(document) { + var range, testElement, rangeBounds, rangeHeight, support = false; - callback_name = 'html2canvas_' + (count++); - imageObj.callbackname = callback_name; + 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); - if (scriptUrl.indexOf("?") > -1) { - scriptUrl += "&"; - } else { - scriptUrl += "?"; - } - scriptUrl += 'url=' + encodeURIComponent(url) + '&callback=' + callback_name; - script = doc.createElement("script"); - - window[callback_name] = function(a){ - if (a.substring(0,6) === "error:"){ - imageObj.succeeded = false; - images.numLoaded++; - images.numFailed++; - start(); - } else { - setImageLoadHandlers(img, imageObj); - img.src = a; - } - window[callback_name] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9) - try { - delete window[callback_name]; // for all browser that support this - } catch(ex) {} - script.parentNode.removeChild(script); - script = null; - delete imageObj.script; - delete imageObj.callbackname; - }; + range.selectNode(testElement); + rangeBounds = range.getBoundingClientRect(); + rangeHeight = rangeBounds.height; - script.setAttribute("type", "text/javascript"); - script.setAttribute("src", scriptUrl); - imageObj.script = script; - window.document.body.appendChild(script); - - } - - function loadPseudoElement(element, type) { - var style = window.getComputedStyle(element, type), - content = style.content; - if (content.substr(0, 3) === 'url') { - methods.loadImage(_html2canvas.Util.parseBackgroundImage(content)[0].args[0]); - } - loadBackgroundImages(style.backgroundImage, element); - } - - function loadPseudoElementImages(element) { - loadPseudoElement(element, ":before"); - loadPseudoElement(element, ":after"); - } - - function loadGradientImage(backgroundImage, bounds) { - var img = _html2canvas.Generate.Gradient(backgroundImage, bounds); - - if (img !== undefined){ - images[backgroundImage] = { - img: img, - succeeded: true - }; - images.numTotal++; - images.numLoaded++; - start(); - } - } - - function invalidBackgrounds(background_image) { - return (background_image && background_image.method && background_image.args && background_image.args.length > 0 ); - } - - function loadBackgroundImages(background_image, el) { - var bounds; - - _html2canvas.Util.parseBackgroundImage(background_image).filter(invalidBackgrounds).forEach(function(background_image) { - if (background_image.method === 'url') { - methods.loadImage(background_image.args[0]); - } else if(background_image.method.match(/\-?gradient$/)) { - if(bounds === undefined) { - bounds = _html2canvas.Util.Bounds(el); + if (rangeHeight === 123) { + support = true; + } + document.body.removeChild(testElement); } - loadGradientImage(background_image.value, bounds); - } - }); - } + } - function getImages (el) { - var elNodeType = false; + return support; +}; - // Firefox fails with permission denied on pages with iframes - try { - Util.Children(el).forEach(getImages); - } - catch( e ) {} +Support.prototype.testCORS = function() { + return typeof((new Image()).crossOrigin) !== "undefined"; +}; + +Support.prototype.testSVG = function() { + var img = new Image(); + var canvas = document.createElement("canvas"); + var ctx = canvas.getContext("2d"); + img.src = "data:image/svg+xml,"; try { - elNodeType = el.nodeType; - } catch (ex) { - elNodeType = false; - Util.log("html2canvas: failed to access some element's nodeType - Exception: " + ex.message); - } - - if (elNodeType === 1 || elNodeType === undefined) { - loadPseudoElementImages(el); - try { - loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el); - } catch(e) { - Util.log("html2canvas: failed to get background-image - Exception: " + e.message); - } - loadBackgroundImages(el); - } - } - - function setImageLoadHandlers(img, imageObj) { - img.onload = function() { - if ( imageObj.timer !== undefined ) { - // CORS succeeded - window.clearTimeout( imageObj.timer ); - } - - images.numLoaded++; - imageObj.succeeded = true; - img.onerror = img.onload = null; - start(); - }; - img.onerror = function() { - if (img.crossOrigin === "anonymous") { - // CORS failed - window.clearTimeout( imageObj.timer ); - - // let's try with proxy instead - if ( options.proxy ) { - var src = img.src; - img = new Image(); - imageObj.img = img; - img.src = src; - - proxyGetImage( img.src, img, imageObj ); - return; - } - } + ctx.drawImage(img, 0, 0); + canvas.toDataURL(); + } catch(e) { + return false; + } + return true; +}; - images.numLoaded++; - images.numFailed++; - imageObj.succeeded = false; - img.onerror = img.onload = null; - start(); - }; - } - - methods = { - loadImage: function( src ) { - var img, imageObj; - if ( src && images[src] === undefined ) { - img = new Image(); - if ( src.match(/data:image\/.*;base64,/i) ) { - img.src = src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''); - imageObj = images[src] = { - img: img - }; - images.numTotal++; - setImageLoadHandlers(img, imageObj); - } else if ( isSameOrigin( src ) || options.allowTaint === true ) { - imageObj = images[src] = { - img: img - }; - images.numTotal++; - setImageLoadHandlers(img, imageObj); - img.src = src; - } else if ( supportCORS && !options.allowTaint && options.useCORS ) { - // attempt to load with CORS - - img.crossOrigin = "anonymous"; - imageObj = images[src] = { - img: img - }; - images.numTotal++; - setImageLoadHandlers(img, imageObj); - img.src = src; - } else if ( options.proxy ) { - imageObj = images[src] = { - img: img - }; - images.numTotal++; - proxyGetImage( src, img, imageObj ); - } - } - - }, - cleanupDOM: function(cause) { - var img, src; - if (!images.cleanupDone) { - if (cause && typeof cause === "string") { - Util.log("html2canvas: Cleanup because: " + cause); - } else { - Util.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms."); - } +module.exports = Support; - for (src in images) { - if (images.hasOwnProperty(src)) { - img = images[src]; - if (typeof img === "object" && img.callbackname && img.succeeded === undefined) { - // cancel proxy image request - window[img.callbackname] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9) - try { - delete window[img.callbackname]; // for all browser that support this - } catch(ex) {} - if (img.script && img.script.parentNode) { - img.script.setAttribute("src", "about:blank"); // try to cancel running request - img.script.parentNode.removeChild(img.script); - } - images.numLoaded++; - images.numFailed++; - Util.log("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal); - } - } - } +},{}],23:[function(_dereq_,module,exports){ +var XHR = _dereq_('./xhr'); +var decode64 = _dereq_('./utils').decode64; - // cancel any pending requests - if(window.stop !== undefined) { - window.stop(); - } else if(document.execCommand !== undefined) { - document.execCommand("Stop", false); - } - if (document.close !== undefined) { - document.close(); - } - images.cleanupDone = true; - if (!(cause && typeof cause === "string")) { - start(); - } - } - }, +function SVGContainer(src) { + this.src = src; + this.image = null; + var self = this; - renderingDone: function() { - if (timeoutTimer) { - window.clearTimeout(timeoutTimer); - } - } - }; + this.promise = this.hasFabric().then(function() { + return (self.isInline(src) ? Promise.resolve(self.inlineFormatting(src)) : XHR(src)); + }).then(function(svg) { + return new Promise(function(resolve) { + window.html2canvas.svg.fabric.loadSVGFromString(svg, self.createCanvas.call(self, resolve)); + }); + }); +} - if (options.timeout > 0) { - timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout); - } +SVGContainer.prototype.hasFabric = function() { + return !window.html2canvas.svg || !window.html2canvas.svg.fabric ? Promise.reject(new Error("html2canvas.svg.js is not loaded, cannot render svg")) : Promise.resolve(); +}; - Util.log('html2canvas: Preload starts: finding background-images'); - images.firstRun = true; +SVGContainer.prototype.inlineFormatting = function(src) { + return (/^data:image\/svg\+xml;base64,/.test(src)) ? this.decode64(this.removeContentType(src)) : this.removeContentType(src); +}; - getImages(element); +SVGContainer.prototype.removeContentType = function(src) { + return src.replace(/^data:image\/svg\+xml(;base64)?,/,''); +}; - Util.log('html2canvas: Preload: Finding images'); - // load images - for (i = 0; i < imgLen; i+=1){ - methods.loadImage( domImages[i].getAttribute( "src" ) ); - } +SVGContainer.prototype.isInline = function(src) { + return (/^data:image\/svg\+xml/i.test(src)); +}; - images.firstRun = false; - Util.log('html2canvas: Preload: Done.'); - if (images.numTotal === images.numLoaded) { - start(); - } +SVGContainer.prototype.createCanvas = function(resolve) { + var self = this; + return function (objects, options) { + var canvas = new window.html2canvas.svg.fabric.StaticCanvas('c'); + self.image = canvas.lowerCanvasEl; + canvas + .setWidth(options.width) + .setHeight(options.height) + .add(window.html2canvas.svg.fabric.util.groupSVGElements(objects, options)) + .renderAll(); + resolve(canvas.lowerCanvasEl); + }; +}; - return methods; +SVGContainer.prototype.decode64 = function(str) { + return (typeof(window.atob) === "function") ? window.atob(str) : decode64(str); }; -_html2canvas.Renderer = function(parseQueue, options){ - function sortZindex(a, b) { - if (a === 'children') { - return -1; - } else if (b === 'children') { - return 1; - } else { - return a - b; - } - } - - // http://www.w3.org/TR/CSS21/zindex.html - function createRenderQueue(parseQueue) { - var queue = [], - rootContext; - - rootContext = (function buildStackingContext(rootNode) { - var rootContext = {}; - function insert(context, node, specialParent) { - var zi = (node.zIndex.zindex === 'auto') ? 0 : Number(node.zIndex.zindex), - contextForChildren = context, // the stacking context for children - isPositioned = node.zIndex.isPositioned, - isFloated = node.zIndex.isFloated, - stub = {node: node}, - childrenDest = specialParent; // where children without z-index should be pushed into - - if (node.zIndex.ownStacking) { - contextForChildren = stub.context = { - children: [{node:node, children: []}] - }; - childrenDest = undefined; - } else if (isPositioned || isFloated) { - childrenDest = stub.children = []; - } +module.exports = SVGContainer; - if (zi === 0 && specialParent) { - specialParent.push(stub); - } else { - if (!context[zi]) { context[zi] = []; } - context[zi].push(stub); - } +},{"./utils":26,"./xhr":28}],24:[function(_dereq_,module,exports){ +var SVGContainer = _dereq_('./svgcontainer'); - node.zIndex.children.forEach(function(childNode) { - insert(contextForChildren, childNode, childrenDest); - }); - } - insert(rootContext, rootNode); - return rootContext; - })(parseQueue); - - function sortZ(context) { - Object.keys(context).sort(sortZindex).forEach(function(zi) { - var nonPositioned = [], - floated = [], - positioned = [], - list = []; - - // positioned after static - context[zi].forEach(function(v) { - if (v.node.zIndex.isPositioned || v.node.zIndex.opacity < 1) { - // http://www.w3.org/TR/css3-color/#transparency - // non-positioned element with opactiy < 1 should be stacked as if it were a positioned element with ‘z-index: 0’ and ‘opacity: 1’. - positioned.push(v); - } else if (v.node.zIndex.isFloated) { - floated.push(v); - } else { - nonPositioned.push(v); - } - }); +function SVGNodeContainer(node, _native) { + this.src = node; + this.image = null; + var self = this; - (function walk(arr) { - arr.forEach(function(v) { - list.push(v); - if (v.children) { walk(v.children); } - }); - })(nonPositioned.concat(floated, positioned)); - - list.forEach(function(v) { - if (v.context) { - sortZ(v.context); - } else { - queue.push(v.node); - } + this.promise = _native ? new Promise(function(resolve, reject) { + self.image = new Image(); + self.image.onload = resolve; + self.image.onerror = reject; + self.image.src = "data:image/svg+xml," + (new XMLSerializer()).serializeToString(node); + if (self.image.complete === true) { + resolve(self.image); + } + }) : this.hasFabric().then(function() { + return new Promise(function(resolve) { + window.html2canvas.svg.fabric.parseSVGDocument(node, self.createCanvas.call(self, resolve)); }); - }); - } + }); +} - sortZ(rootContext); +SVGNodeContainer.prototype = Object.create(SVGContainer.prototype); - return queue; - } +module.exports = SVGNodeContainer; - function getRenderer(rendererName) { - var renderer; +},{"./svgcontainer":23}],25:[function(_dereq_,module,exports){ +var NodeContainer = _dereq_('./nodecontainer'); - if (typeof options.renderer === "string" && _html2canvas.Renderer[rendererName] !== undefined) { - renderer = _html2canvas.Renderer[rendererName](options); - } else if (typeof rendererName === "function") { - renderer = rendererName(options); - } else { - throw new Error("Unknown renderer"); - } +function TextContainer(node, parent) { + NodeContainer.call(this, node, parent); +} - if ( typeof renderer !== "function" ) { - throw new Error("Invalid renderer defined"); - } - return renderer; - } +TextContainer.prototype = Object.create(NodeContainer.prototype); - return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue.stack), _html2canvas); +TextContainer.prototype.applyTextTransform = function() { + this.node.data = this.transform(this.parent.css("textTransform")); }; -_html2canvas.Util.Support = function (options, doc) { - - function supportSVGRendering() { - var img = new Image(), - canvas = doc.createElement("canvas"), - ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d"); - if (ctx === false) { - return false; +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; } - canvas.width = canvas.height = 10; - img.src = [ - "data:image/svg+xml,", - "", - "", - "
", - "sup", - "
", - "
", - "
" - ].join(""); - try { - ctx.drawImage(img, 0, 0); - canvas.toDataURL(); - } catch(e) { - return false; +}; + +function capitalize(m, p1, p2) { + if (m.length > 0) { + return p1 + p2.toUpperCase(); } - _html2canvas.Util.log('html2canvas: Parse: SVG powered rendering available'); - return true; - } +} - // Test whether we can use ranges to measure bounding boxes - // Opera doesn't provide valid bounds.height/bottom even though it supports the method. +module.exports = TextContainer; - function supportRangeBounds() { - var r, testElement, rangeBounds, rangeHeight, support = false; +},{"./nodecontainer":14}],26:[function(_dereq_,module,exports){ +exports.smallImage = function smallImage() { + return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; +}; - if (doc.createRange) { - r = doc.createRange(); - if (r.getBoundingClientRect) { - testElement = doc.createElement('boundtest'); - testElement.style.height = "123px"; - testElement.style.display = "block"; - doc.body.appendChild(testElement); +exports.bind = function(callback, context) { + return function() { + return callback.apply(context, arguments); + }; +}; - r.selectNode(testElement); - rangeBounds = r.getBoundingClientRect(); - rangeHeight = rangeBounds.height; +/* + * base64-arraybuffer + * https://github.com/niklasvh/base64-arraybuffer + * + * Copyright (c) 2012 Niklas von Hertzen + * Licensed under the MIT license. + */ - if (rangeHeight === 123) { - support = true; +exports.decode64 = function(base64) { + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var len = base64.length, i, encoded1, encoded2, encoded3, encoded4, byte1, byte2, byte3; + + var output = ""; + + for (i = 0; i < len; i+=4) { + encoded1 = chars.indexOf(base64[i]); + encoded2 = chars.indexOf(base64[i+1]); + encoded3 = chars.indexOf(base64[i+2]); + encoded4 = chars.indexOf(base64[i+3]); + + byte1 = (encoded1 << 2) | (encoded2 >> 4); + byte2 = ((encoded2 & 15) << 4) | (encoded3 >> 2); + byte3 = ((encoded3 & 3) << 6) | encoded4; + if (encoded3 === 64) { + output += String.fromCharCode(byte1); + } else if (encoded4 === 64 || encoded4 === -1) { + output += String.fromCharCode(byte1, byte2); + } else{ + output += String.fromCharCode(byte1, byte2, byte3); } - doc.body.removeChild(testElement); - } } - return support; - } - - return { - rangeBounds: supportRangeBounds(), - svgRendering: options.svgRendering && supportSVGRendering() - }; -}; -window.html2canvas = function(elements, opts) { - elements = (elements.length) ? elements : [elements]; - var queue, - canvas, - options = { - // general - logging: false, - elements: elements, - background: "#fff", - - // preload options - proxy: null, - timeout: 0, // no timeout - useCORS: false, // try to load images as CORS (where available), before falling back to proxy - allowTaint: false, // whether to allow images to taint the canvas, won't need proxy if set to true - - // parse options - svgRendering: false, // use svg powered rendering where available (FF11+) - ignoreElements: "IFRAME|OBJECT|PARAM", - useOverflow: true, - letterRendering: false, - chinese: false, - async: false, // If true, parsing will not block, but if the user scrolls during parse the image can get weird - - // render options - width: null, - height: null, - taintTest: true, // do a taint test with all images before applying to canvas - renderer: "Canvas" - }; - - options = _html2canvas.Util.Extend(opts, options); - - _html2canvas.logging = options.logging; - options.complete = function( images ) { - - if (typeof options.onpreloaded === "function") { - if ( options.onpreloaded( images ) === false ) { - return; - } - } - _html2canvas.Parse( images, options, function(queue) { - if (typeof options.onparsed === "function") { - if ( options.onparsed( queue ) === false ) { - return; - } - } + return output; +}; - canvas = _html2canvas.Renderer( queue, options ); +exports.getBounds = function(node) { + if (node.getBoundingClientRect) { + var clientRect = node.getBoundingClientRect(); + var width = node.offsetWidth == null ? clientRect.width : node.offsetWidth; + return { + top: clientRect.top, + bottom: clientRect.bottom || (clientRect.top + clientRect.height), + right: clientRect.left + width, + left: clientRect.left, + width: width, + height: node.offsetHeight == null ? clientRect.height : node.offsetHeight + }; + } + return {}; +}; - if (typeof options.onrendered === "function") { - options.onrendered( canvas ); - } - }); - }; - - // for pages without images, we still want this to be async, i.e. return methods before executing - window.setTimeout( function(){ - _html2canvas.Preload( options ); - }, 0 ); - - return { - render: function( queue, opts ) { - return _html2canvas.Renderer( queue, _html2canvas.Util.Extend(opts, options) ); - }, - parse: function( images, opts ) { - return _html2canvas.Parse( images, _html2canvas.Util.Extend(opts, options) ); - }, - preload: function( opts ) { - return _html2canvas.Preload( _html2canvas.Util.Extend(opts, options) ); - }, - log: _html2canvas.Util.log - }; -}; - -window.html2canvas.log = _html2canvas.Util.log; // for renderers -window.html2canvas.Renderer = { - Canvas: undefined // We are assuming this will be used -}; -_html2canvas.Renderer.Canvas = function(options) { - options = options || {}; - - var doc = document, - safeImages = [], - testCanvas = document.createElement("canvas"), - testctx = testCanvas.getContext("2d"), - Util = _html2canvas.Util, - canvas = options.canvas || doc.createElement('canvas'); - - function createShape(ctx, args) { - ctx.beginPath(); - args.forEach(function(arg) { - ctx[arg.name].apply(ctx, arg['arguments']); - }); - ctx.closePath(); - } - - function safeImage(item) { - if (safeImages.indexOf(item['arguments'][0].src) === -1) { - testctx.drawImage(item['arguments'][0], 0, 0); - try { - testctx.getImageData(0, 0, 1, 1); - } catch(e) { - testCanvas = doc.createElement("canvas"); - testctx = testCanvas.getContext("2d"); - return false; - } - safeImages.push(item['arguments'][0].src); - } - return true; - } +exports.offsetBounds = function(node) { + var parent = node.offsetParent ? exports.offsetBounds(node.offsetParent) : {top: 0, left: 0}; - function renderItem(ctx, item) { - switch(item.type){ - case "variable": - ctx[item.name] = item['arguments']; - break; - case "function": - switch(item.name) { - case "createPattern": - if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) { - try { - ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat"); - } catch(e) { - Util.log("html2canvas: Renderer: Error creating pattern", e.message); - } + return { + top: node.offsetTop + parent.top, + bottom: node.offsetTop + node.offsetHeight + parent.top, + right: node.offsetLeft + parent.left + node.offsetWidth, + left: node.offsetLeft + parent.left, + width: node.offsetWidth, + height: node.offsetHeight + }; +}; + +exports.parseBackgrounds = function(backgroundImage) { + 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 = ''; + 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 "drawShape": - createShape(ctx, item['arguments']); + case ')': + if (quote) { + break; + } else if(mode === 1) { + if(numParen === 0) { + mode = 0; + block += c; + appendResult(); + return; + } else { + numParen--; + } + } break; - case "drawImage": - if (item['arguments'][8] > 0 && item['arguments'][7] > 0) { - if (!options.taintTest || (options.taintTest && safeImage(item))) { - ctx.drawImage.apply( ctx, item['arguments'] ); - } + + 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; - default: - ctx[item.name].apply(ctx, item['arguments']); } - break; - } - } - - return function(parsedData, options, document, queue, _html2canvas) { - var ctx = canvas.getContext("2d"), - newCanvas, - bounds, - fstyle, - zStack = parsedData.stack; - - canvas.width = canvas.style.width = options.width || zStack.ctx.width; - canvas.height = canvas.style.height = options.height || zStack.ctx.height; - - fstyle = ctx.fillStyle; - ctx.fillStyle = (Util.isTransparent(parsedData.backgroundColor) && options.background !== undefined) ? options.background : parsedData.backgroundColor; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = fstyle; - queue.forEach(function(storageContext) { - // set common settings for canvas - ctx.textBaseline = "bottom"; - ctx.save(); - - if (storageContext.transform.matrix) { - ctx.translate(storageContext.transform.origin[0], storageContext.transform.origin[1]); - ctx.transform.apply(ctx, storageContext.transform.matrix); - ctx.translate(-storageContext.transform.origin[0], -storageContext.transform.origin[1]); - } - - if (storageContext.clip){ - ctx.beginPath(); - ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height); - ctx.clip(); - } - - if (storageContext.ctx.storage) { - storageContext.ctx.storage.forEach(function(item) { - renderItem(ctx, item); - }); - } - ctx.restore(); + block += c; + if (mode === 0) { + method += c; + } else { + definition += c; + } }); - Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj"); + appendResult(); + return results; +}; + +},{}],27:[function(_dereq_,module,exports){ +var GradientContainer = _dereq_('./gradientcontainer'); + +function WebkitGradientContainer(imageData) { + GradientContainer.apply(this, arguments); + this.type = imageData.args[0] === "linear" ? GradientContainer.TYPES.LINEAR : GradientContainer.TYPES.RADIAL; +} - if (options.elements.length === 1) { - if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") { - // crop image to the bounds of selected (single) element - bounds = _html2canvas.Util.Bounds(options.elements[0]); - newCanvas = document.createElement('canvas'); - +WebkitGradientContainer.prototype = Object.create(GradientContainer.prototype); - newCanvas.width = Math.ceil(bounds.width); - newCanvas.height = Math.ceil(bounds.height); - - ctx = newCanvas.getContext("2d"); - ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height); +module.exports = WebkitGradientContainer; - - - canvas = null; - return newCanvas; - } - } +},{"./gradientcontainer":9}],28:[function(_dereq_,module,exports){ +function XHR(url) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url); - return canvas; - }; -}; -})(window,document); + xhr.onload = function() { + if (xhr.status === 200) { + resolve(xhr.responseText); + } else { + reject(new Error(xhr.statusText)); + } + }; + + xhr.onerror = function() { + reject(new Error("Network Error")); + }; + + xhr.send(); + }); +} + +module.exports = XHR; + +},{}]},{},[4])(4) +}); \ No newline at end of file diff --git a/tableExport.js b/tableExport.js index 1bfaa0fc..9ee22f0c 100644 --- a/tableExport.js +++ b/tableExport.js @@ -282,7 +282,12 @@ THE SOFTWARE.*/ html2canvas($(el), { onrendered: function(canvas) { var img = canvas.toDataURL("image/png"); - window.open(img); + //window.open(img); + var link = document.createElement('a'); + link.href = img; + link.download = 'resultados.png'; + document.body.appendChild(link); + link.click(); }