PageSourceSearch

https://coastalreign.com/assets/public/libs/html2canvas.js

js coastalreign.com collected 2026-09-26 01:04:03 UTC 123,759 bytes, 3,375 lines download raw bytes

1/*
2  html2canvas 0.5.0-alpha1 <http://html2canvas.hertzen.com>
3  Copyright (c) 2015 Niklas von Hertzen
4
5  Released under MIT License
6*/
7
8(function(window, document, exports, global, define, undefined){
9
10/*!
11 * @overview es6-promise - a tiny implementation of Promises/A+.
12 * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
13 * @license   Licensed under MIT license
14 *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
15 * @version   2.0.1
16 */
17
18(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a<l;a+=2)(0,n[a])(n[a+1]),n[a]=void 0,n[a+1]=void 0;
19l=0}function p(){}function J(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function K(a,b,c){r(function(a){var e=!1,f=J(c,b,function(c){e||(e=!0,b!==c?q(a,c):m(a,c))},function(b){e||(e=!0,g(a,b))});!e&&f&&(e=!0,g(a,f))},a)}function L(a,b){1===b.a?m(a,b.b):2===a.a?g(a,b.b):u(b,void 0,function(b){q(a,b)},function(b){g(a,b)})}function q(a,b){if(a===b)g(a,new TypeError("You cannot resolve a promise with itself"));else if("function"===typeof b||"object"===typeof b&&null!==b)if(b.constructor===a.constructor)L(a,
20b);else{var c;try{c=b.then}catch(d){v.error=d,c=v}c===v?g(a,v.error):void 0===c?m(a,b):s(c)?K(a,b,c):m(a,b)}else m(a,b)}function M(a){a.f&&a.f(a.b);x(a)}function m(a,b){void 0===a.a&&(a.b=b,a.a=1,0!==a.e.length&&r(x,a))}function g(a,b){void 0===a.a&&(a.a=2,a.b=b,r(M,a))}function u(a,b,c,d){var e=a.e,f=e.length;a.f=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.a&&r(x,a)}function x(a){var b=a.e,c=a.a;if(0!==b.length){for(var d,e,f=a.b,g=0;g<b.length;g+=3)d=b[g],e=b[g+c],d?C(c,d,e,f):e(f);a.e.length=0}}function D(){this.error=
21null}function C(a,b,c,d){var e=s(c),f,k,h,l;if(e){try{f=c(d)}catch(n){y.error=n,f=y}f===y?(l=!0,k=f.error,f=null):h=!0;if(b===f){g(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,h=!0;void 0===b.a&&(e&&h?q(b,f):l?g(b,k):1===a?m(b,f):2===a&&g(b,f))}function N(a,b){try{b(function(b){q(a,b)},function(b){g(a,b)})}catch(c){g(a,c)}}function k(a,b,c,d){this.n=a;this.c=new a(p,d);this.i=c;this.o(b)?(this.m=b,this.d=this.length=b.length,this.l(),0===this.length?m(this.c,
22this.b):(this.length=this.length||0,this.k(),0===this.d&&m(this.c,this.b))):g(this.c,this.p())}function h(a){O++;this.b=this.a=void 0;this.e=[];if(p!==a){if(!s(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof h))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object c
22onstructor cannot be called as a function.");N(this,a)}}var E=Array.isArray?Array.isArray:function(a){return"[object Array]"===
23Object.prototype.toString.call(a)},l=0,w="undefined"!==typeof window?window:{},B=w.MutationObserver||w.WebKitMutationObserver,w="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,n=Array(1E3),A;A="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?F():B?G():w?H():I();var v=new D,y=new D;k.prototype.o=function(a){return E(a)};k.prototype.p=function(){return Error("Array Methods must be provided an Array")};k.prototype.l=
24function(){this.b=Array(this.length)};k.prototype.k=function(){for(var a=this.length,b=this.c,c=this.m,d=0;void 0===b.a&&d<a;d++)this.j(c[d],d)};k.prototype.j=function(a,b){var c=this.n;"object"===typeof a&&null!==a?a.constructor===c&&void 0!==a.a?(a.f=null,this.g(a.a,b,a.b)):this.q(c.resolve(a),b):(this.d--,this.b[b]=this.h(a))};k.prototype.g=function(a,b,c){var d=this.c;void 0===d.a&&(this.d--,this.i&&2===a?g(d,c):this.b[b]=this.h(c));0===this.d&&m(d,this.b)};k.prototype.h=function(a){return a};
25k.prototype.q=function(a,b){var c=this;u(a,void 0,function(a){c.g(1,b,a)},function(a){c.g(2,b,a)})};var O=0;h.all=function(a,b){return(new k(this,a,!0,b)).c};h.race=function(a,b){function c(a){q(e,a)}function d(a){g(e,a)}var e=new this(p,b);if(!E(a))return (g(e,new TypeError("You must pass an array to race.")), e);for(var f=a.length,h=0;void 0===e.a&&h<f;h++)u(this.resolve(a[h]),void 0,c,d);return e};h.resolve=function(a,b){if(a&&"object"===typeof a&&a.constructor===this)return a;var c=new this(p,b);
26q(c,a);return c};h.reject=function(a,b){var c=new this(p,b);g(c,a);return c};h.prototype={constructor:h,then:function(a,b){var c=this.a;if(1===c&&!a||2===c&&!b)return this;var d=new this.constructor(p),e=this.b;if(c){var f=arguments[c-1];r(function(){C(c,d,f,e)})}else u(this,d,a,b);return d},"catch":function(a){return this.then(null,a)}};var z={Promise:h,polyfill:function(){var a;a="undefined"!==typeof global?global:"undefined"!==typeof window&&window.document?window:self;"Promise"in a&&"resolve"in
27a.Promise&&"reject"in a.Promise&&"all"in a.Promise&&"race"in a.Promise&&function(){var b;new a.Promise(function(a){b=a});return s(b)}()||(a.Promise=h)}};"function"===typeof define&&define.amd?define(function(){return z}):"undefined"!==typeof module&&module.exports?module.exports=z:"undefined"!==typeof this&&(this.ES6Promise=z);}).call(window);
28if (window) {
29    window.ES6Promise.polyfill();
30}
31
32
33if (typeof(document) === "undefined" || typeof(Object.create) !== "function" || typeof(document.createElement("canvas").getContext) !== "function") {
34    (window || module.exports).html2canvas = function() {
35        return Promise.reject("No canvas support");
36    };
37    return;
38}
39
40/*! https://mths.be/punycode v1.3.1 by @mathias */
41;(function(root) {
42
43	/** Detect free variables */
44	var freeExports = typeof exports == 'object' && exports &&
45		!exports.nodeType && exports;
46	var freeModule = typeof module == 'object' && module &&
47		!module.nodeType && module;
48	var freeGlobal = typeof global == 'object' && global;
49	if (
50		freeGlobal.global === freeGlobal ||
51		freeGlobal.window === freeGlobal ||
52		freeGlobal.self === freeGlobal
53	) {
54		root = freeGlobal;
55	}
56
57	/**
58	 * The `punycode` object.
59	 * @name punycode
60	 * @type Object
61	 */
62	var punycode,
63
64	/** Highest positive signed 32-bit float value */
65	maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
66
67	/** Bootstring parameters */
68	base = 36,
69	tMin = 1,
70	tMax = 26,
71	skew = 38,
72	damp = 700,
73	initialBias = 72,
74	initialN = 128, // 0x80
75	delimiter = '-', // '\x2D'
76
77	/** Regular expressions */
78	regexPunycode = /^xn--/,
79	regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
80	regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators
81
82	/** Error messages */
83	errors = {
84		'overflow': 'Overflow: input needs wider integers to process',
85		'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
86		'invalid-input': 'Invalid input'
87	},
88
89	/** Convenience shortcuts */
90	baseMinusTMin = base - tMin,
91	floor = Math.floor,
92	stringFromCharCode = String.fromCharCode,
93
94	/** Temporary variable */
95	key;
96
97	/*--------------------------------------------------------------------------*/
98
99	/**
100	 * A generic error utility function.
101	 * @private
102	 * @param {String} type The error type.
103	 * @returns {Error} Throws a `RangeError` with the applicable error message.
104	 */
105	function error(type) {
106		throw RangeError(errors[type]);
107	}
108
109	/**
110	 * A generic `Array#map` utility function.
111	 * @private
112	 * @param {Array} array The array to iterate over.
113	 * @param {Function} callback The function that gets called for every array
114	 * item.
115	 * @returns {Array} A new array of values returned by the callback function.
116	 */
117	function map(array, fn) {
118		var length = array.length;
119		var result = [];
120		while (length--) {
121			result[length] = fn(array[length]);
122		}
123		return result;
124	}
125
126	/**
127	 * A simple `Array#map`-like wrapper to work with domain name strings or email
128	 * addresses.
129	 * @private
130	 * @param {String} domain The domain name or email address.
131	 * @param {Function} callback The function that gets called for every
132	 * character.
133	 * @returns {Array} A new string of characters returned by the callback
134	 * function.
135	 */
136	function mapDomain(string, fn) {
137		var parts = string.split('@');
138		var result = '';
139		if (parts.length > 1) {
140			// In email addresses, only the domain name should be punycoded. Leave
141			// the local part (i.e. everything up to `@`) intact.
142			result = parts[0] + '@';
143			string = parts[1];
144		}
145		var labels = string.split(regexSeparators
vendor: 7,463 bytes, lines 145-415
145);
146		var encoded = map(labels, fn).join('.');
147		return result + encoded;
148	}
149
150	/**
151	 * Creates an array containing the numeric code points of each Unicode
152	 * character in the string. While JavaScript uses UCS-2 internally,
153	 * this function will convert a pair of surrogate halves (each of which
154	 * UCS-2 exposes as separate characters) into a single code point,
155	 * matching UTF-16.
156	 * @see `punycode.ucs2.encode`
157	 * @see <https://mathiasbynens.be/notes/javascript-encoding>
158	 * @memberOf punycode.ucs2
159	 * @name decode
160	 * @param {String} string The Unicode input string (UCS-2).
161	 * @returns {Array} The new array of code points.
162	 */
163	function ucs2decode(string) {
164		var output = [],
165		    counter = 0,
166		    length = string.length,
167		    value,
168		    extra;
169		while (counter < length) {
170			value = string.charCodeAt(counter++);
171			if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
172				// high surrogate, and there is a next character
173				extra = string.charCodeAt(counter++);
174				if ((extra & 0xFC00) == 0xDC00) { // low surrogate
175					output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
176				} else {
177					// unmatched surrogate; only append this code unit, in case the next
178					// code unit is the high surrogate of a surrogate pair
179					output.push(value);
180					counter--;
181				}
182			} else {
183				output.push(value);
184			}
185		}
186		return output;
187	}
188
189	/**
190	 * Creates a string based on an array of numeric code points.
191	 * @see `punycode.ucs2.decode`
192	 * @memberOf punycode.ucs2
193	 * @name encode
194	 * @param {Array} codePoints The array of numeric code points.
195	 * @returns {String} The new Unicode string (UCS-2).
196	 */
197	function ucs2encode(array) {
198		return map(array, function(value) {
199			var output = '';
200			if (value > 0xFFFF) {
201				value -= 0x10000;
202				output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
203				value = 0xDC00 | value & 0x3FF;
204			}
205			output += stringFromCharCode(value);
206			return output;
207		}).join('');
208	}
209
210	/**
211	 * Converts a basic code point into a digit/integer.
212	 * @see `digitToBasic()`
213	 * @private
214	 * @param {Number} codePoint The basic numeric code point value.
215	 * @returns {Number} The numeric value of a basic code point (for use in
216	 * representing integers) in the range `0` to `base - 1`, or `base` if
217	 * the code point does not represent a value.
218	 */
219	function basicToDigit(codePoint) {
220		if (codePoint - 48 < 10) {
221			return codePoint - 22;
222		}
223		if (codePoint - 65 < 26) {
224			return codePoint - 65;
225		}
226		if (codePoint - 97 < 26) {
227			return codePoint - 97;
228		}
229		return base;
230	}
231
232	/**
233	 * Converts a digit/integer into a basic code point.
234	 * @see `basicToDigit()`
235	 * @private
236	 * @param {Number} digit The numeric value of a basic code point.
237	 * @returns {Number} The basic code point whose value (when used for
238	 * representing integers) is `digit`, which needs to be in the range
239	 * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
240	 * used; else, the lowercase form is used. The behavior is undefined
241	 * if `flag` is non-zero and `digit` has no uppercase form.
242	 */
243	function digitToBasic(digit, flag) {
244		//  0..25 map to ASCII a..z or A..Z
245		// 26..35 map to ASCII 0..9
246		return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
247	}
248
249	/**
250	 * Bias adaptation function as per section 3.4 of RFC 3492.
251	 * http://tools.ietf.org/html/rfc3492#section-3.4
252	 * @private
253	 */
254	function adapt(delta, numPoints, firstTime) {
255		var k = 0;
256		delta = firstTime ? floor(delta / damp) : delta >> 1;
257		delta += floor(delta / numPoints);
258		for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
259			delta = floor(delta / baseMinusTMin);
260		}
261		return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
262	}
263
264	/**
265	 * Converts a Punycode string of ASCII-only symbols to a string of Unicode
266	 * symbols.
267	 * @memberOf punycode
268	 * @param {String} input The Punycode string of ASCII-only symbols.
269	 * @returns {String} The resulting string of Unicode symbols.
270	 */
271	function decode(input) {
272		// Don't use UCS-2
273		var output = [],
274		    inputLength = input.length,
275		    out,
276		    i = 0,
277		    n = initialN,
278		    bias = initialBias,
279		    basic,
280		    j,
281		    index,
282		    oldi,
283		    w,
284		    k,
285		    digit,
286		    t,
287		    /** Cached calculation results */
288		    baseMinusT;
289
290		// Handle the basic code points: let `basic` be the number of input code
291		// points before the last delimiter, or `0` if there is none, then copy
292		// the first basic code points to the output.
293
294		basic = input.lastIndexOf(delimiter);
295		if (basic < 0) {
296			basic = 0;
297		}
298
299		for (j = 0; j < basic; ++j) {
300			// if it's not a basic code point
301			if (input.charCodeAt(j) >= 0x80) {
302				error('not-basic');
303			}
304			output.push(input.charCodeAt(j));
305		}
306
307		// Main decoding loop: start just after the last delimiter if any basic code
308		// points were copied; start at the beginning otherwise.
309
310		for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
311
312			// `index` is the index of the next character to be consumed.
313			// Decode a generalized variable-length integer into `delta`,
314			// which gets added to `i`. The overflow checking is easier
315			// if we increase `i` as we go, then subtract off its starting
316			// value at the end to obtain `delta`.
317			for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
318
319				if (index >= inputLength) {
320					error('invalid-input');
321				}
322
323				digit = basicToDigit(input.charCodeAt(index++));
324
325				if (digit >= base || digit > floor((maxInt - i) / w)) {
326					error('overflow');
327				}
328
329				i += digit * w;
330				t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
331
332				if (digit < t) {
333					break;
334				}
335
336				baseMinusT = base - t;
337				if (w > floor(maxInt / baseMinusT)) {
338					error('overflow');
339				}
340
341				w *= baseMinusT;
342
343			}
344
345			out = output.length + 1;
346			bias = adapt(i - oldi, out, oldi == 0);
347
348			// `i` was supposed to wrap around from `out` to `0`,
349			// incrementing `n` each time, so we'll fix that now:
350			if (floor(i / out) > maxInt - n) {
351				error('overflow');
352			}
353
354			n += floor(i / out);
355			i %= out;
356
357			// Insert `n` at position `i` of the output
358			output.splice(i++, 0, n);
359
360		}
361
362		return ucs2encode(output);
363	}
364
365	/**
366	 * Converts a string of Unicode symbols (e.g. a domain name label) to a
367	 * Punycode string of ASCII-only symbols.
368	 * @memberOf punycode
369	 * @param {String} input The string of Unicode symbols.
370	 * @returns {String} The resulting Punycode string of ASCII-only symbols.
371	 */
372	function encode(input) {
373		var n,
374		    delta,
375		    handledCPCount,
376		    basicLength,
377		    bias,
378		    j,
379		    m,
380		    q,
381		    k,
382		    t,
383		    currentValue,
384		    output = [],
385		    /** `inputLength` will hold the number of code points in `input`. */
386		    inputLength,
387		    /** Cached calculation results */
388		    handledCPCountPlusOne,
389		    baseMinusT,
390		    qMinusT;
391
392		// Convert the input in UCS-2 to Unicode
393		input = ucs2decode(input);
394
395		// Cache the length
396		inputLength = input.length;
397
398		// Initialize the state
399		n = initialN;
400		delta = 0;
401		bias = initialBias;
402
403		// Handle the basic code points
404		for (j = 0; j < inputLength; ++j) {
405			currentValue = input[j];
406			if (currentValue < 0x80) {
407				output.push(stringFromCharCode(currentValue));
408			}
409		}
410
411		handledCPCount = basicLength = output.length;
412
413		// `handledCPCount` is the number of code points that have been handled;
414		// `basicLength` is the number of basic code points.
415
vendor: 3,147 bytes, lines 416-526
416		// Finish the basic string - if it is not empty - with a delimiter
417		if (basicLength) {
418			output.push(delimiter);
419		}
420
421		// Main encoding loop:
422		while (handledCPCount < inputLength) {
423
424			// All non-basic code points < n have been handled already. Find the next
425			// larger one:
426			for (m = maxInt, j = 0; j < inputLength; ++j) {
427				currentValue = input[j];
428				if (currentValue >= n && currentValue < m) {
429					m = currentValue;
430				}
431			}
432
433			// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
434			// but guard against overflow
435			handledCPCountPlusOne = handledCPCount + 1;
436			if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
437				error('overflow');
438			}
439
440			delta += (m - n) * handledCPCountPlusOne;
441			n = m;
442
443			for (j = 0; j < inputLength; ++j) {
444				currentValue = input[j];
445
446				if (currentValue < n && ++delta > maxInt) {
447					error('overflow');
448				}
449
450				if (currentValue == n) {
451					// Represent delta as a generalized variable-length integer
452					for (q = delta, k = base; /* no condition */; k += base) {
453						t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
454						if (q < t) {
455							break;
456						}
457						qMinusT = q - t;
458						baseMinusT = base - t;
459						output.push(
460							stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
461						);
462						q = floor(qMinusT / baseMinusT);
463					}
464
465					output.push(stringFromCharCode(digitToBasic(q, 0)));
466					bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
467					delta = 0;
468					++handledCPCount;
469				}
470			}
471
472			++delta;
473			++n;
474
475		}
476		return output.join('');
477	}
478
479	/**
480	 * Converts a Punycode string representing a domain name or an email address
481	 * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
482	 * it doesn't matter if you call it on a string that has already been
483	 * converted to Unicode.
484	 * @memberOf punycode
485	 * @param {String} input The Punycoded domain name or email address to
486	 * convert to Unicode.
487	 * @returns {String} The Unicode representation of the given Punycode
488	 * string.
489	 */
490	function toUnicode(input) {
491		return mapDomain(input, function(string) {
492			return regexPunycode.test(string)
493				? decode(string.slice(4).toLowerCase())
494				: string;
495		});
496	}
497
498	/**
499	 * Converts a Unicode string representing a domain name or an email address to
500	 * Punycode. Only the non-ASCII parts of the domain name will be converted,
501	 * i.e. it doesn't matter if you call it with a domain that's already in
502	 * ASCII.
503	 * @memberOf punycode
504	 * @param {String} input The domain name or email address to convert, as a
505	 * Unicode string.
506	 * @returns {String} The Punycode representation of the given domain name or
507	 * email address.
508	 */
509	function toASCII(input) {
510		return mapDomain(input, function(string) {
511			return regexNonASCII.test(string)
512				? 'xn--' + encode(string)
513				: string;
514		});
515	}
516
517	/*--------------------------------------------------------------------------*/
518
519	/** Define the public API */
520	punycode = {
521		/**
522		 * A string representing the current Punycode.js version number.
523		 * @memberOf punycode
524		 * @type String
525		 */
526		'version': '1.
5263.1',
527		/**
528		 * An object of methods to convert from JavaScript's internal character
529		 * representation (UCS-2) to Unicode code points, and back.
530		 * @see <https://mathiasbynens.be/notes/javascript-encoding>
531		 * @memberOf punycode
532		 * @type Object
533		 */
534		'ucs2': {
535			'decode': ucs2decode,
536			'encode': ucs2encode
537		},
538		'decode': decode,
539		'encode': encode,
540		'toASCII': toASCII,
541		'toUnicode': toUnicode
542	};
543
544	/** Expose `punycode` */
545	// Some AMD build optimizers, like r.js, check for specific condition patterns
546	// like the following:
547	if (
548		typeof define == 'function' &&
549		typeof define.amd == 'object' &&
550		define.amd
551	) {
552		define('punycode', function() {
553			return punycode;
554		});
555	} else if (freeExports && freeModule) {
556		if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+
557			freeModule.exports = punycode;
558		} else { // in Narwhal or RingoJS v0.7.0-
559			for (key in punycode) {
560				punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
561			}
562		}
563	} else { // in Rhino or a web browser
564		root.punycode = punycode;
565	}
566
567}(this));
568
569var html2canvasNodeAttribute = "data-html2canvas-node";
570var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone";
571var html2canvasCanvasCloneIndex = 0;
572var html2canvasCloneIndex = 0;
573
574window.html2canvas = function(nodeList, options) {
575    var index = html2canvasCloneIndex++;
576    options = options || {};
577    if (options.logging) {
578        window.html2canvas.logging = true;
579        window.html2canvas.start = Date.now();
580    }
581
582    options.async = typeof(options.async) === "undefined" ? true : options.async;
583    options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
584    options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
585    options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
586    options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
587    options.renderer = typeof(options.renderer) === "function" ? options.renderer : CanvasRenderer;
588    options.strict = !!options.strict;
589
590    if (typeof(nodeList) === "string") {
591        if (typeof(options.proxy) !== "string") {
592            return Promise.reject("Proxy must be used when rendering url");
593        }
594        var width = options.width != null ? options.width : window.innerWidth;
595        var height = options.height != null ? options.height : window.innerHeight;
596        return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, width, height, options).then(function(container) {
597            return renderWindow(container.contentWindow.document.documentElement, container, options, width, height);
598        });
599    }
600
601    var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];
602    node.setAttribute(html2canvasNodeAttribute + index, index);
603    return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {
604        if (typeof(options.onrendered) === "function") {
605            log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
606            options.onrendered(canvas);
607        }
608        return canvas;
609    });
610};
611
612window.html2canvas.punycode = this.punycode;
613window.html2canvas.proxy = {};
614
615function renderDocument(document, options, windowWidth, windowHeight, html2canvasIndex) {
616    return createWindowClone(document, document, windowWidth, windowHeight, options, document.defaultView.pageXOffset, document.defaultView.pageYOffset).then(function(container) {
617        log("Document cloned");
618        var attributeName = html2canvasNodeAttribute + html2canvasIndex;
619        var selector = "[" + attributeName + "='" + html2canvasIndex + "']";
620        document.querySelector(selector).removeAttribute(attributeName);
621        var clonedWindow = container.contentWindow;
622        var node = clonedWindow.document.querySelector(selector);
623        var oncloneHandler = (typeof(options.onclone) === "function") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true);
624        return oncloneHandler.then(function() {
625            return renderWindow(node, container, options, windowWidth, windowHeight);
626        });
627    });
628}
629
630function renderWindow(node, container, options, windowWidth, windowHeight) {
631    var clonedWindow = container.contentWindow;
632    var support = new Support(clonedWindow.document);
633    var imageLoader = new ImageLoader(options, support);
634    var bounds = getBounds(node);
635    var width = options.type === "view" ? windowWidth : documentWidth(clonedWindow.document);
636    var height = options.type === "view" ? windowHeight : documentHeight(clonedWindow.document);
637    var renderer = new options.renderer(width, height, imageLoader, options, document);
638    var parser = new NodeParser(node, renderer, support, imageLoader, options);
639    return parser.ready.then(function() {
640        log("Finished rendering");
641        var canvas;
642
643        if (options.type === "view") {
644            canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
645        } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {
646            canvas = renderer.canvas;
647        } else {
648            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: clonedWindow.pageXOffset, y: clonedWindow.pageYOffset});
649        }
650
651        cleanupContainer(container, options);
652        return canvas;
653    });
654}
655
656function cleanupContainer(container, options) {
657    if (options.removeContainer) {
658        container.parentNode.removeChild(container);
659        log("Cleaned up container");
660    }
661}
662
663function crop(canvas, bounds) {
664    var croppedCanvas = document.createElement("canvas");
665    var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left));
666    var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width));
667    var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top));
668    var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height));
669    croppedCanvas.width = bounds.width;
670    croppedCanvas.height =  bounds.height;
671    log("Cropping canvas at:", "left:", bounds.left, "top:", bounds.top, "width:", (x2-x1), "height:", (y2-y1));
672    log("Resulting crop with width", bounds.width, "and height", bounds.height, " with x", x1, "and y", y1);
673    croppedCanvas.getContext("2d").drawImage(canvas, x1, y1, x2-x1, y2-y1, bounds.x, bounds.y, x2-x1, y2-y1);
674    return croppedCanvas;
675}
676
677function documentWidth (doc) {
678    return Math.max(
679        Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
680        Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
681        Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
682    );
683}
684
685function documentHeight (doc) {
686    return Math.max(
687        Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
688        Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
689        Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
690    );
691}
692
693function smallImage() {
694    return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
695}
696
697function isIE9() {
698    return document.documentMode && document.documentMode <= 9;
699}
700
701// https://github.com/niklasvh/html2canvas/issues/503
702function cloneNodeIE9(node, javascriptEnabled) {
703    var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
704
705    var child = node.firstChild;
706    while(child) {
707        if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {
708            clone.appendChild(cloneNodeIE9(child, javascriptEnabled));
709        }
710        child = child.nextSibling;
711    }
712
713    return clone;
714}
715
716function createWindowClone(ownerDocument, containerDocument, width, height, options, x ,y) {
717    labelCanvasElements(ownerDocument);
718    var documentElement = isIE9() ? cloneNodeIE9(ownerDocument.documentElement, options.javascriptEnabled) : ownerDocument.documentElement.cloneNode(true);
719    var container = containerDocument.createElement("iframe");
720
721    container.className = "html2canvas-container";
722    container.style.visibility = "hidden";
723    container.style.position = "fixed";
724    container.style.left = "-10000px";
725    container.style.top = "0px";
726    container.style.border = "0";
727    container.width = width;
728    container.height = height;
729    container.scrolling = "no"; // ios won't scroll without it
730    containerDocument.body.appendChild(container);
731
732    return new Promise(function(resolve) {
733        var documentClone = container.contentWindow.document;
734
735        cloneNodeValues(ownerDocument.documentElement, documentElement, "textarea");
736        cloneNodeValues(ownerDocument.documentElement, documentElement, "select");
737
738        /* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
739         if window url is about:blank, we can assign the url to current by writing onto the document
740         */
741        container.contentWindow.onload = container.onload = function() {
742            var interval = setInterval(function() {
743                if (documentClone.body.childNodes.length > 0) {
744                    cloneCanvasContents(ownerDocument, documentClone);
745                    clearInterval(interval);
746                    if (options.type === "view") {
747                        container.contentWindow.scrollTo(x, y);
748                    }
749                    resolve(container);
750                }
751            }, 50);
752        };
753
754        documentClone.open();
755        documentClone.write("<!DOCTYPE html><html></html>");
756        // Chrome scrolls the parent document for some reason after the write to the cloned window???
757        restoreOwnerScroll(ownerDocument, x, y);
758        documentClone.replaceChild(options.javascriptEnabled === true ? documentClone.adoptNode(documentElement) : removeScriptNodes(documentClone.adoptNode(documentElement)), documentClone.documentElement);
759        documentClone.close();
760    });
761}
762
763function cloneNodeValues(document, clone, nodeName) {
764    var originalNodes = document.getElementsByTagName(nodeName);
765    var clonedNodes = clone.getElementsByTagName(nodeName);
766    var count = originalNodes.length;
767    for (var i = 0; i < count; i++) {
768        clonedNodes[i].value = originalNodes[i].value;
769    }
770}
771
772function restoreOwnerScroll(ownerDocument, x, y) {
773    if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) {
774        ownerDocument.defaultView.scrollTo(x, y);
775    }
776}
777
778function loadUrlDocument(src, proxy, document, width, height, options) {
779    return new Proxy(src, proxy, window.document).then(documentFromHTML(src)).then(function(doc) {
780        return createWindowClone(doc, document, width, height, options, 0, 0);
781    });
782}
783
784function documentFromHTML(src) {
785    return function(html) {
786        var parser = new DOMParser(), doc;
787        try {
788            doc = parser.parseFromString(html, "text/html");
789        } catch(e) {
790            log("DOMParser not supported, falling back to createHTMLDocument");
791            doc = document.implementation.createHTMLDocument("");
792            try {
793                doc.open();
794                doc.write(html);
795                doc.close();
796            } catch(ee) {
797                log("createHTMLDocument write not supported, falling back to document.body.innerHTML");
798                doc.body.innerHTML = html; // ie9 doesnt support writing to documentElement
799            }
800        }
801
802        var b = doc.querySelector("base");
803        if (!b || !b.href.host) {
804            var base = doc.createElement("base");
805            base.href = src;
806            doc.head.insertBefore(base, doc.head.firstChild);
807        }
808
809        return doc;
810    };
811}
812
813
814function labelCanvasElements(ownerDocument) {
815    [].slice.call(ownerDocument.querySelectorAll("canvas"), 0).forEach(function(canvas) {
816        canvas.setAttribute(html2canvasCanvasCloneAttribute, "canvas-" + html2canvasCanvasCloneIndex++);
817    });
818}
819
820function cloneCanvasContents(ownerDocument, documentClone) {
821    [].slice.call(ownerDocument.querySelectorAll("[" + html2canvasCanvasCloneAttribute + "]"), 0).forEach(function(canvas) {
822        try {
823            var clonedCanvas = documentClone.querySelector('[' + html2canvasCanvasCloneAttribute + '="' + canvas.getAttribute(html2canvasCanvasCloneAttribute) + '"]');
824            if (clonedCanvas) {
825                clonedCanvas.width = canvas.width;
826                clonedCanvas.height = canvas.height;
827                clonedCanvas.getContext("2d").putImageData(canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height), 0, 0);
828            }
829        } catch(e) {
830            log("Unable to copy canvas content from", canvas, e);
831        }
832        canvas.removeAttribute(html2canvasCanvasCloneAttribute);
833    });
834}
835
836function removeScriptNodes(parent) {
837    [].slice.call(parent.childNodes, 0).filter(isElementNode).forEach(function(node) {
838        if (node.tagName === "SCRIPT") {
839            parent.removeChild(node);
840        } else {
841            removeScriptNodes(node);
842        }
843    });
844    return parent;
845}
846
847function isElementNode(node) {
848    return node.nodeType === Node.ELEMENT_NODE;
849}
850
851function absoluteUrl(url) {
852    var link = document.createElement("a");
853    link.href = url;
854    link.href = link.href;
855    return link;
856}
857
858// http://dev.w3.org/csswg/css-color/
859
860function Color(value) {
861    this.r = 0;
862    this.g = 0;
863    this.b = 0;
864    this.a = null;
865    var result = this.fromArray(value) ||
866        this.namedColor(value) ||
867        this.rgb(value) ||
868        this.rgba(value) ||
869        this.hex6(value) ||
870        this.hex3(value);
871}
872
873Color.prototype.darken = function(amount) {
874    var a = 1 - amount;
875    return  new Color([
876        Math.round(this.r * a),
877        Math.round(this.g * a),
878        Math.round(this.b * a),
879        this.a
880    ]);
881};
882
883Color.prototype.isTransparent = function() {
884    return this.a === 0;
885};
886
887Color.prototype.isBlack = function() {
888    return this.r === 0 && this.g === 0 && this.b === 0;
889};
890
891Color.prototype.fromArray = function(array) {
892    if (Array.isArray(array)) {
893        this.r = Math.min(array[0], 255);
894        this.g = Math.min(array[1], 255);
895        this.b = Math.min(array[2], 255);
896        if (array.length > 3) {
897            this.a = array[3];
898        }
899    }
900
901    return (Array.isArray(array));
902};
903
904var _hex3 = /^#([a-f0-9]{3})$/i;
905
906Color.prototype.hex3 = function(value) {
907    var match = null;
908    if ((match = value.match(_hex3)) !== null) {
909        this.r = parseInt(match[1][0] + match[1][0], 16);
910        this.g = parseInt(match[1][1] + match[1][1], 16);
911        this.b = parseInt(match[1][2] + match[1][2], 16);
912    }
913    return match !== null;
914};
915
916var _hex6 = /^#([a-f0-9]{6})$/i;
917
918Color.prototype.hex6 = function(value) {
919    var match = null;
920    if ((match = value.match(_hex6)) !== null) {
921        this.r = parseInt(match[1].substring(0, 2), 16);
922        this.g = parseInt(match[1].substring(2, 4), 16);
923        this.b = parseInt(match[1].substring(4, 6), 16);
924    }
925    return match !== null;
926};
927
928
929var _rgb = /^rgb\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3})\)$/;
930
931Color.prototype.rgb = function(value) {
932    var match = null;
933    if ((match = value.match(_rgb)) !== null) {
934        this.r = Number(match[1]);
935        this.g = Number(match[2]);
936        this.b = Number(match[3]);
937    }
938    return match !== null;
939};
940
941var _rgba = /^rgba\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *, *(\d+\.?\d*)\)$/;
942
943Color.prototype.rgba = function(value) {
944    var match = null;
945    if ((match = value.match(_rgba)) !== null) {
946        this.r = Number(match[1]);
947        this.g = Number(match[2]);
948        this.b = Number(match[3]);
949        this.a = Number(match[4]);
950    }
951    return match !== null;
952};
953
954Color.prototype.toString = function() {
955    return this.a !== null && this.a !== 1 ?
956    "rgba(" + [this.r, this.g, this.b, this.a].join(",") + ")" :
957    "rgb(" + [this.r, this.g, this.b].join(",") + ")";
958};
959
960Color.prototype.namedColor = function(value) {
961    var color = colors[value.toLowerCase()];
962    if (color) {
963        this.r = color[0];
964        this.g = color[1];
965        this.b = color[2];
966    } else if (value.toLowerCase() === "transparent") {
967        this.r = this.g = this.b = this.a = 0;
968        return true;
969    }
970
971    return !!color;
972};
973
974Color.prototype.isColor = true;
975
976// 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}, {}))
977var colors = {
978    "aliceblue": [240, 248, 255],
979    "antiquewhite": [250, 235, 215],
980    "aqua": [0, 255, 255],
981    "aquamarine": [127, 255, 212],
982    "azure": [240, 255, 255],
983    "beige": [245, 245, 220],
984    "bisque": [255, 228, 196],
985    "black": [0, 0, 0],
986    "blanchedalmond": [255, 235, 205],
987    "blue": [0, 0, 255],
988    "blueviolet": [138, 43, 226],
989    "brown": [165, 42, 42],
990    "burlywood": [222, 184, 135],
991    "cadetblue": [95, 158, 160],
992    "chartreuse": [127, 255, 0],
993    "chocolate": [210, 105, 30],
994    "coral": [255, 127, 80],
995    "cornflowerblue": [100, 149, 237],
996    "cornsilk": [255, 248, 220],
997    "crimson": [220, 20, 60],
998    "cyan": [0, 255, 255],
999    "darkblue": [0, 0, 139],
1000    "darkcyan": [0, 139, 139],
1001    "darkgoldenrod": [184, 134, 11],
1002    "darkgray": [169, 169, 169],
1003    "darkgreen": [0, 100, 0],
1004    "darkgrey": [169, 169, 169],
1005    "darkkhaki": [189, 183, 107],
1006    "darkmagenta": [139, 0, 139],
1007    "darkolivegreen": [85, 107, 47],
1008    "darkorange": [255, 140, 0],
1009    "darkorchid": [153, 50, 204],
1010    "darkred": [139, 0, 0],
1011    "darksalmon": [233, 150, 122],
1012    "darkseagreen": [143, 188, 143],
1013    "darkslateblue": [72, 61, 139],
1014    "darkslategray": [47, 79, 79],
1015    "darkslategrey": [47, 79, 79],
1016    "darkturquoise": [0, 206, 209],
1017    "darkviolet": [148, 0, 211],
1018    "deeppink": [255, 20, 147],
1019    "deepskyblue": [0, 191, 255],
1020    "dimgray": [105, 105, 105],
1021    "dimgrey": [105, 105, 105],
1022    "dodgerblue": [30, 144, 255],
1023    "firebrick": [178, 34, 34],
1024    "floralwhite": [255, 250, 240],
1025    "forestgreen": [34, 139, 34],
1026    "fuchsia": [255, 0, 255],
1027    "gainsboro": [220, 220, 220],
1028    "ghostwhite": [248, 248, 255],
1029    "gold": [255, 215, 0],
1030    "goldenrod": [218, 165, 32],
1031    "gray": [128, 128, 128],
1032    "green": [0, 128, 0],
1033    "greenyellow": [173, 255, 47],
1034    "grey": [128, 128, 128],
1035    "honeydew": [240, 255, 240],
1036    "hotpink": [255, 105, 180],
1037    "indianred": [205, 92, 92],
1038    "indigo": [75, 0, 130],
1039    "ivory": [255, 255, 240],
1040    "khaki": [240, 230, 140],
1041    "lavender": [230, 230, 250],
1042    "lavenderblush": [255, 240, 245],
1043    "lawngreen": [124, 252, 0],
1044    "lemonchiffon": [255, 250, 205],
1045    "lightblue": [173, 216, 230],
1046    "lightcoral": [240, 128, 128],
1047    "lightcyan": [224, 255, 255],
1048    "lightgoldenrodyellow": [250, 250, 210],
1049    "lightgray": [211, 211, 211],
1050    "lightgreen": [144, 238, 144],
1051    "lightgrey": [211, 211, 211],
1052    "lightpink": [255, 182, 193],
1053    "lightsalmon": [255, 160, 122],
1054    "lightseagreen": [32, 178, 170],
1055    "lightskyblue": [135, 206, 250],
1056    "lightslategray": [119, 136, 153],
1057    "lightslategrey": [119, 136, 153],
1058    "lightsteelblue": [176, 196, 222],
1059    "lightyellow": [255, 255, 224],
1060    "lime": [0, 255, 0],
1061    "limegreen": [50, 205, 50],
1062    "linen": [250, 240, 230],
1063    "magenta": [255, 0, 255],
1064    "maroon": [128, 0, 0],
1065    "mediumaquamarine": [102, 205, 170],
1066    "mediumblue": [0, 0, 205],
1067    "mediumorchid": [186, 85, 211],
1068    "mediumpurple": [147, 112, 219],
1069    "mediumseagreen": [60, 179, 113],
1070    "mediumslateblue": [123, 104, 238],
1071    "mediumspringgreen": [0, 250, 154],
1072    "mediumturquoise": [72, 209, 204],
1073    "mediumvioletred": [199, 21, 133],
1074    "midnightblue": [25, 25, 112],
1075    "mintcream": [245, 255, 250],
1076    "mistyrose": [255, 228, 225],
1077    "moccasin": [255, 228, 181],
1078    "navajowhite": [255, 222, 173],
1079    "navy": [0, 0, 128],
1080    "oldlace": [253, 245, 230],
1081    "olive": [128, 128, 0],
1082    "olivedrab": [107, 142, 35],
1083    "orange": [255, 165, 0],
1084    "orangered": [255, 69, 0],
1085    "orchid": [218, 112, 214],
1086    "palegoldenrod": [238, 232, 170],
1087    "palegreen": [152, 251, 152],
1088    "paleturquoise": [175, 238, 238],
1089    "palevioletred": [219, 112, 147],
1090    "papayawhip": [255, 239, 213],
1091    "peachpuff": [255, 218, 185],
1092    "peru": [205, 133, 63],
1093    "pink": [255, 192, 203],
1094    "plum": [221, 160, 221],
1095    "powderblue": [176, 224, 230],
1096    "purple": [128, 0, 128],
1097    "rebeccapurple": [102, 51, 153],
1098    "red": [255, 0, 0],
1099    "rosybrown": [188, 143, 143],
1100    "royalblue": [65, 105, 225],
1101    "saddlebrown": [139, 69, 19],
1102    "salmon": [250, 128, 114],
1103    "sandybrown": [244, 164, 96],
1104    "seagreen": [46, 139, 87],
1105    "seashell": [255, 245, 238],
1106    "sienna": [160, 82, 45],
1107    "silver": [192, 192, 192],
1108    "skyblue": [135, 206, 235],
1109    "slateblue": [106, 90, 205],
1110    "slategray": [112, 128, 144],
1111    "slategrey": [112, 128, 144],
1112    "snow": [255, 250, 250],
1113    "springgreen": [0, 255, 127],
1114    "steelblue": [70, 130, 180],
1115    "tan": [210, 180, 140],
1116    "teal": [0, 128, 128],
1117    "thistle": [216, 191, 216],
1118    "tomato": [255, 99, 71],
1119    "turquoise": [64, 224, 208],
1120    "violet": [238, 130, 238],
1121    "wheat": [245, 222, 179],
1122    "white": [255, 255, 255],
1123    "whitesmoke": [245, 245, 245],
1124    "yellow": [255, 255, 0],
1125    "yellowgreen": [154, 205, 50]
1126};
1127
1128
1129function DummyImageContainer(src) {
1130    this.src = src;
1131    log("DummyImageContainer for", src);
1132    if (!this.promise || !this.image) {
1133        log("Initiating DummyImageContainer");
1134        DummyImageContainer.prototype.image = new Image();
1135        var image = this.image;
1136        DummyImageContainer.prototype.promise = new Promise(function(resolve, reject) {
1137            image.onload = resolve;
1138            image.onerror = reject;
1139            image.src = smallImage();
1140            if (image.complete === true) {
1141                resolve(image);
1142            }
1143        });
1144    }
1145}
1146
1147function Font(family, size) {
1148    var container = document.createElement('div'),
1149        img = document.createElement('img'),
1150        span = document.createElement('span'),
1151        sampleText = 'Hidden Text',
1152        baseline,
1153        middle;
1154
1155    container.style.visibility = "hidden";
1156    container.style.fontFamily = family;
1157    container.style.fontSize = size;
1158    container.style.margin = 0;
1159    container.style.padding = 0;
1160
1161    document.body.appendChild(container);
1162
1163    img.src = smallImage();
1164    img.width = 1;
1165    img.height = 1;
1166
1167    img.style.margin = 0;
1168    img.style.padding = 0;
1169    img.style.verticalAlign = "baseline";
1170
1171    span.style.fontFamily = family;
1172    span.style.fontSize = size;
1173    span.style.margin = 0;
1174    span.style.padding = 0;
1175
1176    span.appendChild(document.createTextNode(sampleText));
1177    container.appendChild(span);
1178    container.appendChild(img);
1179    baseline = (img.offsetTop - span.offsetTop) + 1;
1180
1181    container.removeChild(span);
1182    container.appendChild(document.createTextNode(sampleText));
1183
1184    container.style.lineHeight = "normal";
1185    img.style.verticalAlign = "super";
1186
1187    middle = (img.offsetTop-container.offsetTop) + 1;
1188
1189    document.body.removeChild(container);
1190
1191    this.baseline = baseline;
1192    this.lineWidth = 1;
1193    this.middle = middle;
1194}
1195
1196function FontMetrics() {
1197    this.data = {};
1198}
1199
1200FontMetrics.prototype.getMetrics = function(family, size) {
1201    if (this.data[family + "-" + size] === undefined) {
1202        this.data[family + "-" + size] = new Font(family, size);
1203    }
1204    return this.data[family + "-" + size];
1205};
1206
1207function FrameContainer(container, sameOrigin, options) {
1208    this.image = null;
1209    this.src = container;
1210    var self = this;
1211    var bounds = getBounds(container);
1212    this.promise = (!sameOrigin ? this.proxyLoad(options.proxy, bounds, options) : new Promise(function(resolve) {
1213        if (container.contentWindow.document.URL === "about:blank" || container.contentWindow.document.documentElement == null) {
1214            container.contentWindow.onload = container.onload = function() {
1215                resolve(container);
1216            };
1217        } else {
1218            resolve(container);
1219        }
1220    })).then(function(container) {
1221        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});
1222    }).then(function(canvas) {
1223        return self.image = canvas;
1224    });
1225}
1226
1227FrameContainer.prototype.proxyLoad = function(proxy, bounds, options) {
1228    var container = this.src;
1229    return loadUrlDocument(container.src, proxy, container.ownerDocument, bounds.width, bounds.height, options);
1230};
1231
1232function GradientContainer(imageData) {
1233    this.src = imageData.value;
1234    this.colorStops = [];
1235    this.type = null;
1236    this.x0 = 0.5;
1237    this.y0 = 0.5;
1238    this.x1 = 0.5;
1239    this.y1 = 0.5;
1240    this.promise = Promise.resolve(true);
1241}
1242
1243GradientContainer.prototype.TYPES = {
1244    LINEAR: 1,
1245    RADIAL: 2
1246};
1247
1248function ImageContainer(src, cors) {
1249    this.src = src;
1250    this.image = new Image();
1251    var self = this;
1252    this.tainted = null;
1253    this.promise = new Promise(function(resolve, reject) {
1254        self.image.onload = resolve;
1255        self.image.onerror = reject;
1256        if (cors) {
1257            self.image.crossOrigin = "anonymous";
1258        }
1259        self.image.src = src;
1260        if (self.image.complete === true) {
1261            resolve(self.image);
1262        }
1263    });
1264}
1265
1266function ImageLoader(options, support) {
1267    this.link = null;
1268    this.options = options;
1269    this.support = support;
1270    this.origin = this.getOrigin(window.location.href);
1271}
1272
1273ImageLoader.prototype.findImages = function(nodes) {
1274    var images = [];
1275    nodes.reduce(function(imageNodes, container) {
1276        switch(container.node.nodeName) {
1277        case "IMG":
1278            return imageNodes.concat([{
1279                args: [container.node.src],
1280                method: "url"
1281            }]);
1282        case "svg":
1283        case "IFRAME":
1284            return imageNodes.concat([{
1285                args: [container.node],
1286                method: container.node.nodeName
1287            }]);
1288        }
1289        return imageNodes;
1290    }, []).forEach(this.addImage(images, this.loadImage), this);
1291    return images;
1292};
1293
1294ImageLoader.prototype.findBackgroundImage = function(images, container) {
1295    container.parseBackgroundImages().filter(this.hasImageBackground).forEach(this.addImage(images, this.loadImage), this);
1296    return images;
1297};
1298
1299ImageLoader.prototype.addImage = function(images, callback) {
1300    return function(newImage) {
1301        newImage.args.forEach(function(image) {
1302            if (!this.imageExists(images, image)) {
1303                images.splice(0, 0, callback.call(this, newImage));
1304                log('Added image #' + (images.length), typeof(image) === "string" ? image.substring(0, 100) : image);
1305            }
1306        }, this);
1307    };
1308};
1309
1310ImageLoader.prototype.hasImageBackground = function(imageData) {
1311    return imageData.method !== "none";
1312};
1313
1314ImageLoader.prototype.loadImage = function(imageData) {
1315    if (imageData.method === "url") {
1316        var src = imageData.args[0];
1317        if (this.isSVG(src) && !this.support.svg && !this.options.allowTaint) {
1318            return new SVGContainer(src);
1319        } else if (src.match(/data:image\/.*;base64,/i)) {
1320            return new ImageContainer(src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''), false);
1321        } else if (this.isSameOrigin(src) || this.options.allowTaint === true || this.isSVG(src)) {
1322            return new ImageContainer(src, false);
1323        } else if (this.support.cors && !this.options.allowTaint && this.options.useCORS) {
1324            return new ImageContainer(src, true);
1325        } else if (this.options.proxy) {
1326            return new ProxyImageContainer(src, this.options.proxy);
1327        } else {
1328            return new DummyImageContainer(src);
1329        }
1330    } else if (imageData.method === "linear-gradient") {
1331        return new LinearGradientContainer(imageData);
1332    } else if (imageData.method === "gradient") {
1333        return new WebkitGradientContainer(imageData);
1334    } else if (imageData.method === "svg") {
1335        return new SVGNodeContainer(imageData.args[0], this.support.svg);
1336    } else if (imageData.method === "IFRAME") {
1337        return new FrameContainer(imageData.args[0], this.isSameOrigin(imageData.args[0].src), this.options);
1338    } else {
1339        return new DummyImageContainer(imageData);
1340    }
1341};
1342
1343ImageLoader.prototype.isSVG = function(src) {
1344    return src.substring(src.length - 3).toLowerCase() === "svg" || SVGContainer.prototype.isInline(src);
1345};
1346
1347ImageLoader.prototype.imageExists = function(images, src) {
1348    return images.some(function(image) {
1349        return image.src === src;
1350    });
1351};
1352
1353ImageLoader.prototype.isSameOrigin = function(url) {
1354    return (this.getOrigin(url) === this.origin);
1355};
1356
1357ImageLoader.prototype.getOrigin = function(url) {
1358    var link = this.link || (this.link = document.createElement("a"));
1359    link.href = url;
1360    link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
1361    return link.protocol + link.hostname + link.port;
1362};
1363
1364ImageLoader.prototype.getPromise = function(container) {
1365    return this.timeout(container, this.options.imageTimeout)['catch'](function() {
1366        var dummy = new DummyImageContainer(container.src);
1367        return dummy.promise.then(function(image) {
1368            container.image = image;
1369        });
1370    });
1371};
1372
1373ImageLoader.prototype.get = function(src) {
1374    var found = null;
1375    return this.images.some(function(img) {
1376        return (found = img).src === src;
1377    }) ? found : null;
1378};
1379
1380ImageLoader.prototype.fetch = function(nodes) {
1381    this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes));
1382    this.images.forEach(function(image, index) {
1383        image.promise.then(function() {
1384            log("Succesfully loaded image #"+ (index+1), image);
1385        }, function(e) {
1386            log("Failed loading image #"+ (index+1), image, e);
1387        });
1388    });
1389    this.ready = Promise.all(this.images.map(this.getPromise, this));
1390    log("Finished searching images");
1391    return this;
1392};
1393
1394ImageLoader.prototype.timeout = function(container, timeout) {
1395    var timer;
1396    var promise = Promise.race([container.promise, new Promise(function(res, reject) {
1397        timer = setTimeout(function() {
1398            log("Timed out loading image", container);
1399            reject(container);
1400        }, timeout);
1401    })]).then(function(container) {
1402        clearTimeout(timer);
1403        return container;
1404    });
1405    promise['catch'](function() {
1406        clearTimeout(timer);
1407    });
1408    return promise;
1409};
1410
1411function LinearGradientContainer(imageData) {
1412    GradientContainer.apply(this, arguments);
1413    this.type = this.TYPES.LINEAR;
1414
1415    var hasDirection = imageData.args[0].match(this.stepRegExp) === null;
1416
1417    if (hasDirection) {
1418        imageData.args[0].split(" ").reverse().forEach(function(position) {
1419            switch(position) {
1420            case "left":
1421                this.x0 = 0;
1422                this.x1 = 1;
1423                break;
1424            case "top":
1425                this.y0 = 0;
1426                this.y1 = 1;
1427                break;
1428            case "right":
1429                this.x0 = 1;
1430                this.x1 = 0;
1431                break;
1432            case "bottom":
1433                this.y0 = 1;
1434                this.y1 = 0;
1435                break;
1436            case "to":
1437                var y0 = this.y0;
1438                var x0 = this.x0;
1439                this.y0 = this.y1;
1440                this.x0 = this.x1;
1441                this.x1 = x0;
1442                this.y1 = y0;
1443                break;
1444            }
1445        }, this);
1446    } else {
1447        this.y0 = 0;
1448        this.y1 = 1;
1449    }
1450
1451    this.colorStops = imageData.args.slice(hasDirection ? 1 : 0).map(function(colorStop) {
1452        var colorStopMatch = colorStop.match(this.stepRegExp);
1453        return {
1454            color: new Color(colorStopMatch[1]),
1455            stop: colorStopMatch[3] === "%" ? colorStopMatch[2] / 100 : null
1456        };
1457    }, this);
1458
1459    if (this.colorStops[0].stop === null) {
1460        this.colorStops[0].stop = 0;
1461    }
1462
1463    if (this.colorStops[this.colorStops.length - 1].stop === null) {
1464        this.colorStops[this.colorStops.length - 1].stop = 1;
1465    }
1466
1467    this.colorStops.forEach(function(colorStop, index) {
1468        if (colorStop.stop === null) {
1469            this.colorStops.slice(index).some(function(find, count) {
1470                if (find.stop !== null) {
1471                    colorStop.stop = ((find.stop - this.colorStops[index - 1].stop) / (count + 1)) + this.colorStops[index - 1].stop;
1472                    return true;
1473                } else {
1474                    return false;
1475                }
1476            }, this);
1477        }
1478    }, this);
1479}
1480
1481LinearGradientContainer.prototype = Object.create(GradientContainer.prototype);
1482
1483LinearGradientContainer.prototype.stepRegExp = /((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/;
1484
1485function log() {
1486    if (window.html2canvas.logging && window.console && window.console.log) {
1487        Function.prototype.bind.call(window.console.log, (window.console)).apply(window.console, [(Date.now() - window.html2canvas.start) + "ms", "html2canvas:"].concat([].slice.call(arguments, 0)));
1488    }
1489}
1490
1491function NodeContainer(node, parent) {
1492    this.node = node;
1493    this.parent = parent;
1494    this.stack = null;
1495    this.bounds = null;
1496    this.borders = null;
1497    this.clip = [];
1498    this.backgroundClip = [];
1499    this.offsetBounds = null;
1500    this.visible = null;
1501    this.computedStyles = null;
1502    this.colors = {};
1503    this.styles = {};
1504    this.backgroundImages = null;
1505    this.transformData = null;
1506    this.transformMatrix = null;
1507    this.isPseudoElement = false;
1508    this.opacity = null;
1509}
1510
1511NodeContainer.prototype.cloneTo = function(stack) {
1512    stack.visible = this.visible;
1513    stack.borders = this.borders;
1514    stack.bounds = this.bounds;
1515    stack.clip = this.clip;
1516    stack.backgroundClip = this.backgroundClip;
1517    stack.computedStyles = this.computedStyles;
1518    stack.styles = this.styles;
1519    stack.backgroundImages = this.backgroundImages;
1520    stack.opacity = this.opacity;
1521};
1522
1523NodeContainer.prototype.getOpacity = function() {
1524    return this.opacity === null ? (this.opacity = this.cssFloat('opacity')) : this.opacity;
1525};
1526
1527NodeContainer.prototype.assignStack = function(stack) {
1528    this.stack = stack;
1529    stack.children.push(this);
1530};
1531
1532NodeContainer.prototype.isElementVisible = function() {
1533    return this.node.nodeType === Node.TEXT_NODE ? this.parent.visible : (
1534        this.css('display') !== "none" &&
1535        this.css('visibility') !== "hidden" &&
1536        !this.node.hasAttribute("data-html2canvas-ignore") &&
1537        (this.node.nodeName !== "INPUT" || this.node.getAttribute("type") !== "hidden")
1538    );
1539};
1540
1541NodeContainer.prototype.css = function(attribute) {
1542    if (!this.computedStyles) {
1543        this.computedStyles = this.isPseudoElement ? this.parent.computedStyle(this.before ? ":before" : ":after") : this.computedStyle(null);
1544    }
1545
1546    return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);
1547};
1548
1549NodeContainer.prototype.prefixedCss = function(attribute) {
1550    var prefixes = ["webkit", "moz", "ms", "o"];
1551    var value = this.css(attribute);
1552    if (value === undefined) {
1553        prefixes.some(function(prefix) {
1554            value = this.css(prefix + attribute.substr(0, 1).toUpperCase() + attribute.substr(1));
1555            return value !== undefined;
1556        }, this);
1557    }
1558    return value === undefined ? null : value;
1559};
1560
1561NodeContainer.prototype.computedStyle = function(type) {
1562    return this.node.ownerDocument.defaultView.getComputedStyle(this.node, type);
1563};
1564
1565NodeContainer.prototype.cssInt = function(attribute) {
1566    var value = parseInt(this.css(attribute), 10);
1567    return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html
1568};
1569
1570NodeContainer.prototype.color = function(attribute) {
1571    return this.colors[attribute] || (this.colors[attribute] = new Color(this.css(attribute)));
1572};
1573
1574NodeContainer.prototype.cssFloat = function(attribute) {
1575    var value = parseFloat(this.css(attribute));
1576    return (isNaN(value)) ? 0 : value;
1577};
1578
1579NodeContainer.prototype.fontWeight = function() {
1580    var weight = this.css("fontWeight");
1581    switch(parseInt(weight, 10)){
1582    case 401:
1583        weight = "bold";
1584        break;
1585    case 400:
1586        weight = "normal";
1587        break;
1588    }
1589    return weight;
1590};
1591
1592NodeContainer.prototype.parseClip = function() {
1593    var matches = this.css('clip').match(this.CLIP);
1594    if (matches) {
1595        return {
1596            top: parseInt(matches[1], 10),
1597            right: parseInt(matches[2], 10),
1598            bottom: parseInt(matches[3], 10),
1599            left: parseInt(matches[4], 10)
1600        };
1601    }
1602    return null;
1603};
1604
1605NodeContainer.prototype.parseBackgroundImages = function() {
1606    return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage")));
1607};
1608
1609NodeContainer.prototype.cssList = function(property, index) {
1610    var value = (this.css(property) || '').split(',');
1611    value = value[index || 0] || value[0] || 'auto';
1612    value = value.trim().split(' ');
1613    if (value.length === 1) {
1614        value = [value[0], value[0]];
1615    }
1616    return value;
1617};
1618
1619NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) {
1620    var size = this.cssList("backgroundSize", index);
1621    var width, height;
1622
1623    if (isPercentage(size[0])) {
1624        width = bounds.width * parseFloat(size[0]) / 100;
1625    } else if (/contain|cover/.test(size[0])) {
1626        var targetRatio = bounds.width / bounds.height, currentRatio = image.width / image.height;
1627        return (targetRatio < currentRatio ^ size[0] === 'contain') ?  {width: bounds.height * currentRatio, height: bounds.height} : {width: bounds.width, height: bounds.width / currentRatio};
1628    } else {
1629        width = parseInt(size[0], 10);
1630    }
1631
1632    if (size[0] === 'auto' && size[1] === 'auto') {
1633        height = image.height;
1634    } else if (size[1] === 'auto') {
1635        height = width / image.width * image.height;
1636    } else if (isPercentage(size[1])) {
1637        height =  bounds.height * parseFloat(size[1]) / 100;
1638    } else {
1639        height = parseInt(size[1], 10);
1640    }
1641
1642    if (size[0] === 'auto') {
1643        width = height / image.height * image.width;
1644    }
1645
1646    return {width: width, height: height};
1647};
1648
1649NodeContainer.prototype.parseBackgroundPosition = function(bounds, image, index, backgroundSize) {
1650    var position = this.cssList('backgroundPosition', index);
1651    var left, top;
1652
1653    if (isPercentage(position[0])){
1654        left = (bounds.width - (backgroundSize || image).width) * (parseFloat(position[0]) / 100);
1655    } else {
1656        left = parseInt(position[0], 10);
1657    }
1658
1659    if (position[1] === 'auto') {
1660        top = left / image.width * image.height;
1661    } else if (isPercentage(position[1])){
1662        top =  (bounds.height - (backgroundSize || image).height) * parseFloat(position[1]) / 100;
1663    } else {
1664        top = parseInt(position[1], 10);
1665    }
1666
1667    if (position[0] === 'auto') {
1668        left = top / image.height * image.width;
1669    }
1670
1671    return {left: left, top: top};
1672};
1673
1674NodeContainer.prototype.parseBackgroundRepeat = function(index) {
1675    return this.cssList("backgroundRepeat", index)[0];
1676};
1677
1678NodeContainer.prototype.parseTextShadows = function() {
1679    var textShadow = this.css("textShadow");
1680    var results = [];
1681
1682    if (textShadow && textShadow !== 'none') {
1683        var shadows = textShadow.match(this.TEXT_SHADOW_PROPERTY);
1684        for (var i = 0; shadows && (i < shadows.length); i++) {
1685            var s = shadows[i].match(this.TEXT_SHADOW_VALUES);
1686            results.push({
1687                color: new Color(s[0]),
1688                offsetX: s[1] ? parseFloat(s[1].replace('px', '')) : 0,
1689                offsetY: s[2] ? parseFloat(s[2].replace('px', '')) : 0,
1690                blur: s[3] ? s[3].replace('px', '') : 0
1691            });
1692        }
1693    }
1694    return results;
1695};
1696
1697NodeContainer.prototype.parseTransform = function() {
1698    if (!this.transformData) {
1699        if (this.hasTransform()) {
1700            var offset = this.parseBounds();
1701            var origin = this.prefixedCss("transformOrigin").split(" ").map(removePx).map(asFloat);
1702            origin[0] += offset.left;
1703            origin[1] += offset.top;
1704            this.transformData = {
1705                origin: origin,
1706                matrix: this.parseTransformMatrix()
1707            };
1708        } else {
1709            this.transformData = {
1710                origin: [0, 0],
1711                matrix: [1, 0, 0, 1, 0, 0]
1712            };
1713        }
1714    }
1715    return this.transformData;
1716};
1717
1718NodeContainer.prototype.parseTransformMatrix = function() {
1719    if (!this.transformMatrix) {
1720        var transform = this.prefixedCss("transform");
1721        var matrix = transform ? parseMatrix(transform.match(this.MATRIX_PROPERTY)) : null;
1722        this.transformMatrix = matrix ? matrix : [1, 0, 0, 1, 0, 0];
1723    }
1724    return this.transformMatrix;
1725};
1726
1727NodeContainer.prototype.parseBounds = function() {
1728    return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node));
1729};
1730
1731NodeContainer.prototype.hasTransform = function() {
1732    return this.parseTransformMatrix().join(",") !== "1,0,0,1,0,0" || (this.parent && this.parent.hasTransform());
1733};
1734
1735NodeContainer.prototype.getValue = function() {
1736    var value = this.node.value || "";
1737    if (this.node.tagName === "SELECT") {
1738        value = selectionValue(this.node);
1739    } else if (this.node.type === "password") {
1740        value = Array(value.length + 1).join('\u2022'); // jshint ignore:line
1741    }
1742    return value.length === 0 ? (this.node.placeholder || "") : value;
1743};
1744
1745NodeContainer.prototype.MATRIX_PROPERTY = /(matrix)\((.+)\)/;
1746NodeContainer.prototype.TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
1747NodeContainer.prototype.TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
1748NodeContainer.prototype.CLIP = /^rect\((\d+)px,? (\d+)px,? (\d+)px,? (\d+)px\)$/;
1749
1750function selectionValue(node) {
1751    var option = node.options[node.selectedIndex || 0];
1752    return option ? (option.text || "") : "";
1753}
1754
1755function parseMatrix(match) {
1756    if (match && match[1] === "matrix") {
1757        return match[2].split(",").map(function(s) {
1758            return parseFloat(s.trim());
1759        });
1760    }
1761}
1762
1763function isPercentage(value) {
1764    return value.toString().indexOf("%") !== -1;
1765}
1766
1767function parseBackgrounds(backgroundImage) {
1768    var whitespace = ' \r\n\t',
1769        method, definition, prefix, prefix_i, block, results = [],
1770        mode = 0, numParen = 0, quote, args;
1771    var appendResult = function() {
1772        if(method) {
1773            if (definition.substr(0, 1) === '"') {
1774                definition = definition.substr(1, definition.length - 2);
1775            }
1776            if (definition) {
1777                args.push(definition);
1778            }
1779            if (method.substr(0, 1) === '-' && (prefix_i = method.indexOf('-', 1 ) + 1) > 0) {
1780                prefix = method.substr(0, prefix_i);
1781                method = method.substr(prefix_i);
1782            }
1783            results.push({
1784                prefix: prefix,
1785                method: method.toLowerCase(),
1786                value: block,
1787                args: args,
1788                image: null
1789            });
1790        }
1791        args = [];
1792        method = prefix = definition = block = '';
1793    };
1794    args = [];
1795    method = prefix = definition = block = '';
1796    backgroundImage.split("").forEach(function(c) {
1797        if (mode === 0 && whitespace.indexOf(c) > -1) {
1798            return;
1799        }
1800        switch(c) {
1801        case '"':
1802            if(!quote) {
1803                quote = c;
1804            } else if(quote === c) {
1805                quote = null;
1806            }
1807            break;
1808        case '(':
1809            if(quote) {
1810                break;
1811            } else if(mode === 0) {
1812                mode = 1;
1813                block += c;
1814                return;
1815            } else {
1816                numParen++;
1817            }
1818            break;
1819        case ')':
1820            if (quote) {
1821                break;
1822            } else if(mode === 1) {
1823                if(numParen === 0) {
1824                    mode = 0;
1825                    block += c;
1826                    appendResult();
1827                    return;
1828                } else {
1829                    numParen--;
1830                }
1831            }
1832            break;
1833
1834        case ',':
1835            if (quote) {
1836                break;
1837            } else if(mode === 0) {
1838                appendResult();
1839                return;
1840            } else if (mode === 1) {
1841                if (numParen === 0 && !method.match(/^url$/i)) {
1842                    args.push(definition);
1843                    definition = '';
1844                    block += c;
1845                    return;
1846                }
1847            }
1848            break;
1849        }
1850
1851        block += c;
1852        if (mode === 0) {
1853            method += c;
1854        } else {
1855            definition += c;
1856        }
1857    });
1858
1859    appendResult();
1860    return results;
1861}
1862
1863function removePx(str) {
1864    return str.replace("px", "");
1865}
1866
1867function asFloat(str) {
1868    return parseFloat(str);
1869}
1870
1871function getBounds(node) {
1872    if (node.getBoundingClientRect) {
1873        var clientRect = node.getBoundingClientRect();
1874        var width = node.offsetWidth == null ? clientRect.width : node.offsetWidth;
1875        return {
1876            top: clientRect.top,
1877            bottom: clientRect.bottom || (clientRect.top + clientRect.height),
1878            right: clientRect.left + width,
1879            left: clientRect.left,
1880            width:  width,
1881            height: node.offsetHeight == null ? clientRect.height : node.offsetHeight
1882        };
1883    }
1884    return {};
1885}
1886
1887function offsetBounds(node) {
1888    var parent = node.offsetParent ? offsetBounds(node.offsetParent) : {top: 0, left: 0};
1889
1890    return {
1891        top: node.offsetTop + parent.top,
1892        bottom: node.offsetTop + node.offsetHeight + parent.top,
1893        right: node.offsetLeft + parent.left + node.offsetWidth,
1894        left: node.offsetLeft + parent.left,
1895        width: node.offsetWidth,
1896        height: node.offsetHeight
1897    };
1898}
1899
1900function NodeParser(element, renderer, support, imageLoader, options) {
1901    log("Starting NodeParser");
1902    this.renderer = renderer;
1903    this.options = options;
1904    this.range = null;
1905    this.support = support;
1906    this.renderQueue = [];
1907    this.stack = new StackingContext(true, 1, element.ownerDocument, null);
1908    var parent = new NodeContainer(element, null);
1909    if (options.background) {
1910        renderer.rectangle(0, 0, renderer.width, renderer.height, new Color(options.background));
1911    }
1912    if (element === element.ownerDocument.documentElement) {
1913        // http://www.w3.org/TR/css3-background/#special-backgrounds
1914        var canvasBackground = new NodeContainer(parent.color('backgroundColor').isTransparent() ? element.ownerDocument.body : element.ownerDocument.documentElement, null);
1915        renderer.rectangle(0, 0, renderer.width, renderer.height, canvasBackground.color('backgroundColor'));
1916    }
1917    parent.visibile = parent.isElementVisible();
1918    this.createPseudoHideStyles(element.ownerDocument);
1919    this.disableAnimations(element.ownerDocument);
1920    this.nodes = flatten([parent].concat(this.getChildren(parent)).filter(function(container) {
1921        return container.visible = container.isElementVisible();
1922    }).map(this.getPseudoElements, this));
1923    this.fontMetrics = new FontMetrics();
1924    log("Fetched nodes, total:", this.nodes.length);
1925    log("Calculate overflow clips");
1926    this.calculateOverflowClips();
1927    log("Start fetching images");
1928    this.images = imageLoader.fetch(this.nodes.filter(isElement));
1929    this.ready = this.images.ready.then(bind(function() {
1930        log("Images loaded, starting parsing");
1931        log("Creating stacking contexts");
1932        this.createStackingContexts();
1933        log("Sorting stacking contexts");
1934        this.sortStackingContexts(this.stack);
1935        this.parse(this.stack);
1936        log("Render queue created with " + this.renderQueue.length + " items");
1937        return new Promise(bind(function(resolve) {
1938            if (!options.async) {
1939                this.renderQueue.forEach(this.paint, this);
1940                resolve();
1941            } else if (typeof(options.async) === "function") {
1942                options.async.call(this, this.renderQueue, resolve);
1943            } else if (this.renderQueue.length > 0){
1944                this.renderIndex = 0;
1945                this.asyncRenderer(this.renderQueue, resolve);
1946            } else {
1947                resolve();
1948            }
1949        }, this));
1950    }, this));
1951}
1952
1953NodeParser.prototype.calculateOverflowClips = function() {
1954    this.nodes.forEach(function(container) {
1955        if (isElement(container)) {
1956            if (isPseudoElement(container)) {
1957                container.appendToDOM();
1958            }
1959            container.borders = this.parseBorders(container);
1960            var clip = (container.css('overflow') === "hidden") ? [container.borders.clip] : [];
1961            var cssClip = container.parseClip();
1962            if (cssClip && ["absolute", "fixed"].indexOf(container.css('position')) !== -1) {
1963                clip.push([["rect",
1964                        container.bounds.left + cssClip.left,
1965                        container.bounds.top + cssClip.top,
1966                        cssClip.right - cssClip.left,
1967                        cssClip.bottom - cssClip.top
1968                ]]);
1969            }
1970            container.clip = hasParentClip(container) ? container.parent.clip.concat(clip) : clip;
1971            container.backgroundClip = (container.css('overflow') !== "hidden") ? container.clip.concat([container.borders.clip]) : container.clip;
1972            if (isPseudoElement(container)) {
1973                container.cleanDOM();
1974            }
1975        } else if (isTextNode(container)) {
1976            container.clip = hasParentClip(container) ? container.parent.clip : [];
1977        }
1978        if (!isPseudoElement(container)) {
1979            container.bounds = null;
1980        }
1981    }, this);
1982};
1983
1984function hasParentClip(container) {
1985    return container.parent && container.parent.clip.length;
1986}
1987
1988NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
1989    asyncTimer = asyncTimer || Date.now();
1990    this.paint(queue[this.renderIndex++]);
1991    if (queue.length === this.renderIndex) {
1992        resolve();
1993    } else if (asyncTimer + 20 > Date.now()) {
1994        this.asyncRenderer(queue, resolve, asyncTimer);
1995    } else {
1996        setTimeout(bind(function() {
1997            this.asyncRenderer(queue, resolve);
1998        }, this), 0);
1999    }
2000};
2001
2002NodeParser.prototype.createPseudoHideStyles = function(document) {
2003    this.createStyles(document, '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + ':before { content: "" !important; display: none !important; }' +
2004        '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER + ':after { content: "" !important; display: none !important; }');
2005};
2006
2007NodeParser.prototype.disableAnimations = function(document) {
2008    this.createStyles(document, '* { -webkit-animation: none !important; -moz-animation: none !important; -o-animation: none !important; animation: none !important; ' +
2009        '-webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important;}');
2010};
2011
2012NodeParser.prototype.createStyles = function(document, styles) {
2013    var hidePseudoElements = document.createElement('style');
2014    hidePseudoElements.innerHTML = styles;
2015    document.body.appendChild(hidePseudoElements);
2016};
2017
2018NodeParser.prototype.getPseudoElements = function(container) {
2019    var nodes = [[container]];
2020    if (container.node.nodeType === Node.ELEMENT_NODE) {
2021        var before = this.getPseudoElement(container, ":before");
2022        var after = this.getPseudoElement(container, ":after");
2023
2024        if (before) {
2025            nodes.push(before);
2026        }
2027
2028        if (after) {
2029            nodes.push(after);
2030        }
2031    }
2032    return flatten(nodes);
2033};
2034
2035function toCamelCase(str) {
2036    return str.replace(/(\-[a-z])/g, function(match){
2037        return match.toUpperCase().replace('-','');
2038    });
2039}
2040
2041NodeParser.prototype.getPseudoElement = function(container, type) {
2042    var style = container.computedStyle(type);
2043    if(!style || !style.content || style.content === "none" || style.content === "-moz-alt-content" || style.display === "none") {
2044        return null;
2045    }
2046
2047    var content = stripQuotes(style.content);
2048    var isImage = content.substr(0, 3) === 'url';
2049    var pseudoNode = document.createElement(isImage ? 'img' : 'html2canvaspseudoelement');
2050    var pseudoContainer = new PseudoElementContainer(pseudoNode, container, type);
2051
2052    for (var i = style.length-1; i >= 0; i--) {
2053        var property = toCamelCase(style.item(i));
2054        pseudoNode.style[property] = style[property];
2055    }
2056
2057    pseudoNode.className = PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + " " + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER;
2058
2059    if (isImage) {
2060        pseudoNode.src = parseBackgrounds(content)[0].args[0];
2061        return [pseudoContainer];
2062    } else {
2063        var text = document.createTextNode(content);
2064        pseudoNode.appendChild(text);
2065        return [pseudoContainer, new TextContainer(text, pseudoContainer)];
2066    }
2067};
2068
2069
2070NodeParser.prototype.getChildren = function(parentContainer) {
2071    return flatten([].filter.call(parentContainer.node.childNodes, renderableNode).map(function(node) {
2072        var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);
2073        return node.nodeType === Node.ELEMENT_NODE && container.length && node.tagName !== "TEXTAREA" ? (container[0].isElementVisible() ? container.concat(this.getChildren(container[0])) : []) : container;
2074    }, this));
2075};
2076
2077NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
2078    var stack = new StackingContext(hasOwnStacking, container.getOpacity(), container.node, container.parent);
2079    container.cloneTo(stack);
2080    var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
2081    parentStack.contexts.push(stack);
2082    container.stack = stack;
2083};
2084
2085NodeParser.prototype.createStackingContexts = function() {
2086    this.nodes.forEach(function(container) {
2087        if (isElement(container) && (this.isRootElement(container) || hasOpacity(container) || isPositionedForStacking(container) || this.isBodyWithTransparentRoot(container) || container.hasTransform())) {
2088            this.newStackingContext(container, true);
2089        } else if (isElement(container) && ((isPositioned(container) && zIndex0(container)) || isInlineBlock(container) || isFloating(container))) {
2090            this.newStackingContext(container, false);
2091        } else {
2092            container.assignStack(container.parent.stack);
2093        }
2094    }, this);
2095};
2096
2097NodeParser.prototype.isBodyWithTransparentRoot = function(container) {
2098    return container.node.nodeName === "BODY" && container.parent.color('backgroundColor').isTransparent();
2099};
2100
2101NodeParser.prototype.isRootElement = function(container) {
2102    return container.parent === null;
2103};
2104
2105NodeParser.prototype.sortStackingContexts = function(stack) {
2106    stack.contexts.sort(zIndexSort(stack.contexts.slice(0)));
2107    stack.contexts.forEach(this.sortStackingContexts, this);
2108};
2109
2110NodeParser.prototype.parseTextBounds = function(container) {
2111    return function(text, index, textList) {
2112        if (container.parent.css("textDecoration").substr(0, 4) !== "none" || text.trim().length !== 0) {
2113            if (this.support.rangeBounds && !container.parent.hasTransform()) {
2114                var offset = textList.slice(0, index).join("").length;
2115                return this.getRangeBounds(container.node, offset, text.length);
2116            } else if (container.node && typeof(container.node.data) === "string") {
2117                var replacementNode = container.node.splitText(text.length);
2118                var bounds = this.getWrapperBounds(container.node, container.parent.hasTransform());
2119                container.node = replacementNode;
2120                return bounds;
2121            }
2122        } else if(!this.support.rangeBounds || container.parent.hasTransform()){
2123            container.node = container.node.splitText(text.length);
2124        }
2125        return {};
2126    };
2127};
2128
2129NodeParser.prototype.getWrapperBounds = function(node, transform) {
2130    var wrapper = node.ownerDocument.createElement('html2canvaswrapper');
2131    var parent = node.parentNode,
2132        backupText = node.cloneNode(true);
2133
2134    wrapper.appendChild(node.cloneNode(true));
2135    parent.replaceChild(wrapper, node);
2136    var bounds = transform ? offsetBounds(wrapper) : getBounds(wrapper);
2137    parent.replaceChild(backupText, wrapper);
2138    return bounds;
2139};
2140
2141NodeParser.prototype.getRangeBounds = function(node, offset, length) {
2142    var range = this.range || (this.range = node.ownerDocument.createRange());
2143    range.setStart(node, offset);
2144    range.setEnd(node, offset + length);
2145    return range.getBoundingClientRect();
2146};
2147
2148function ClearTransform() {}
2149
2150NodeParser.prototype.parse = function(stack) {
2151    // http://www.w3.org/TR/CSS21/visuren.html#z-index
2152    var negativeZindex = stack.contexts.filter(negativeZIndex); // 2. the child stacking contexts with negative stack levels (most negative first).
2153    var descendantElements = stack.children.filter(isElement);
2154    var descendantNonFloats = descendantElements.filter(not(isFloating));
2155    var nonInlineNonPositionedDescendants = descendantNonFloats.filter(not(isPositioned)).filter(not(inlineLevel)); // 3 the in-flow, non-inline-level, non-positioned descendants.
2156    var nonPositionedFloats = descendantElements.filter(not(isPositioned)).filter(isFloating); // 4. the non-positioned floats.
2157    var inFlow = descendantNonFloats.filter(not(isPositioned)).filter(inlineLevel); // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
2158    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.
2159    var text = stack.children.filter(isTextNode).filter(hasText);
2160    var positiveZindex = stack.contexts.filter(positiveZIndex); // 7. the child stacking contexts with positive stack levels (least positive first).
2161    negativeZindex.concat(nonInlineNonPositionedDescendants).concat(nonPositionedFloats)
2162        .concat(inFlow).concat(stackLevel0).concat(text).concat(positiveZindex).forEach(function(container) {
2163            this.renderQueue.push(container);
2164            if (isStackingContext(container)) {
2165                this.parse(container);
2166                this.renderQueue.push(new ClearTransform());
2167            }
2168        }, this);
2169};
2170
2171NodeParser.prototype.paint = function(container) {
2172    try {
2173        if (container instanceof ClearTransform) {
2174            this.renderer.ctx.restore();
2175        } else if (isTextNode(container)) {
2176            if (isPseudoElement(container.parent)) {
2177                container.parent.appendToDOM();
2178            }
2179            this.paintText(container);
2180            if (isPseudoElement(container.parent)) {
2181                container.parent.cleanDOM();
2182            }
2183        } else {
2184            this.paintNode(container);
2185        }
2186    } catch(e) {
2187        log(e);
2188        if (this.options.strict) {
2189            throw e;
2190        }
2191    }
2192};
2193
2194NodeParser.prototype.paintNode = function(container) {
2195    if (isStackingContext(container)) {
2196        this.renderer.setOpacity(container.opacity);
2197        this.renderer.ctx.save();
2198        if (container.hasTransform()) {
2199            this.renderer.setTransform(container.parseTransform());
2200        }
2201    }
2202
2203    if (container.node.nodeName === "INPUT" && container.node.type === "checkbox") {
2204        this.paintCheckbox(container);
2205    } else if (container.node.nodeName === "INPUT" && container.node.type === "radio") {
2206        this.paintRadio(container);
2207    } else {
2208        this.paintElement(container);
2209    }
2210};
2211
2212NodeParser.prototype.paintElement = function(container) {
2213    var bounds = container.parseBounds();
2214    this.renderer.clip(container.backgroundClip, function() {
2215        this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
2216    }, this);
2217
2218    this.renderer.clip(container.clip, function() {
2219        this.renderer.renderBorders(container.borders.borders);
2220    }, this);
2221
2222    this.renderer.clip(container.backgroundClip, function() {
2223        switch (container.node.nodeName) {
2224        case "svg":
2225        case "IFRAME":
2226            var imgContainer = this.images.get(container.node);
2227            if (imgContainer) {
2228                this.renderer.renderImage(container, bounds, container.borders, imgContainer);
2229            } else {
2230                log("Error loading <" + container.node.nodeName + ">", container.node);
2231            }
2232            break;
2233        case "IMG":
2234            var imageContainer = this.images.get(container.node.src);
2235            if (imageContainer) {
2236                this.renderer.renderImage(container, bounds, container.borders, imageContainer);
2237            } else {
2238                log("Error loading <img>", container.node.src);
2239            }
2240            break;
2241        case "CANVAS":
2242            this.renderer.renderImage(container, bounds, container.borders, {image: container.node});
2243            break;
2244        case "SELECT":
2245        case "INPUT":
2246        case "TEXTAREA":
2247            this.paintFormValue(container);
2248            break;
2249        }
2250    }, this);
2251};
2252
2253NodeParser.prototype.paintCheckbox = function(container) {
2254    var b = container.parseBounds();
2255
2256    var size = Math.min(b.width, b.height);
2257    var bounds = {width: size - 1, height: size - 1, top: b.top, left: b.left};
2258    var r = [3, 3];
2259    var radius = [r, r, r, r];
2260    var borders = [1,1,1,1].map(function(w) {
2261        return {color: new Color('#A5A5A5'), width: w};
2262    });
2263
2264    var borderPoints = calculateCurvePoints(bounds, radius, borders);
2265
2266    this.renderer.clip(container.backgroundClip, function() {
2267        this.renderer.rectangle(bounds.left + 1, bounds.top + 1, bounds.width - 2, bounds.height - 2, new Color("#DEDEDE"));
2268        this.renderer.renderBorders(calculateBorders(borders, bounds, borderPoints, radius));
2269        if (container.node.checked) {
2270            this.renderer.font(new Color('#424242'), 'normal', 'normal', 'bold', (size - 3) + "px", 'arial');
2271            this.renderer.text("\u2714", bounds.left + size / 6, bounds.top + size - 1);
2272        }
2273    }, this);
2274};
2275
2276NodeParser.prototype.paintRadio = function(container) {
2277    var bounds = container.parseBounds();
2278
2279    var size = Math.min(bounds.width, bounds.height) - 2;
2280
2281    this.renderer.clip(container.backgroundClip, function() {
2282        this.renderer.circleStroke(bounds.left + 1, bounds.top + 1, size, new Color('#DEDEDE'), 1, new Color('#A5A5A5'));
2283        if (container.node.checked) {
2284            this.renderer.circle(Math.ceil(bounds.left + size / 4) + 1, Math.ceil(bounds.top + size / 4) + 1, Math.floor(size / 2), new Color('#424242'));
2285        }
2286    }, this);
2287};
2288
2289NodeParser.prototype.paintFormValue = function(container) {
2290    var value = container.getValue();
2291    if (value.length > 0) {
2292        var document = container.node.ownerDocument;
2293        var wrapper = document.createElement('html2canvaswrapper');
2294        var properties = ['lineHeight', 'textAlign', 'fontFamily', 'fontWeight', 'fontSize', 'color',
2295            'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom',
2296            'width', 'height', 'borderLeftStyle', 'borderTopStyle', 'borderLeftWidth', 'borderTopWidth',
2297            'boxSizing', 'whiteSpace', 'wordWrap'];
2298
2299        properties.forEach(function(property) {
2300            try {
2301                wrapper.style[property] = container.css(property);
2302            } catch(e) {
2303                // Older IE has issues with "border"
2304                log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
2305            }
2306        });
2307        var bounds = container.parseBounds();
2308        wrapper.style.position = "fixed";
2309        wrapper.style.left = bounds.left + "px";
2310        wrapper.style.top = bounds.top + "px";
2311        wrapper.textContent = value;
2312        document.body.appendChild(wrapper);
2313        this.paintText(new TextContainer(wrapper.firstChild, container));
2314        document.body.removeChild(wrapper);
2315    }
2316};
2317
2318NodeParser.prototype.paintText = function(container) {
2319    container.applyTextTransform();
2320    var characters = window.html2canvas.punycode.ucs2.decode(container.node.data);
2321    var textList = (!this.options.letterRendering || noLetterSpacing(container)) && !hasUnicode(container.node.data) ? getWords(characters) : characters.map(function(ch
2321aracter) {
2322        return window.html2canvas.punycode.ucs2.encode([character]);
2323    });
2324
2325    var weight = container.parent.fontWeight();
2326    var size = container.parent.css('fontSize');
2327    var family = container.parent.css('fontFamily');
2328    var shadows = container.parent.parseTextShadows();
2329
2330    this.renderer.font(container.parent.color('color'), container.parent.css('fontStyle'), container.parent.css('fontVariant'), weight, size, family);
2331    if (shadows.length) {
2332        // TODO: support multiple text shadows
2333        this.renderer.fontShadow(shadows[0].color, shadows[0].offsetX, shadows[0].offsetY, shadows[0].blur);
2334    } else {
2335        this.renderer.clearShadow();
2336    }
2337
2338    this.renderer.clip(container.parent.clip, function() {
2339        textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {
2340            if (bounds) {
2341                this.renderer.text(textList[index], bounds.left, bounds.bottom);
2342                this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));
2343            }
2344        }, this);
2345    }, this);
2346};
2347
2348NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {
2349    switch(container.css("textDecoration").split(" ")[0]) {
2350    case "underline":
2351        // Draws a line at the baseline of the font
2352        // 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
2353        this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.color("color"));
2354        break;
2355    case "overline":
2356        this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.color("color"));
2357        break;
2358    case "line-through":
2359        // TODO try and find exact position for line-through
2360        this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.color("color"));
2361        break;
2362    }
2363};
2364
2365var borderColorTransforms = {
2366    inset: [
2367        ["darken", 0.60],
2368        ["darken", 0.10],
2369        ["darken", 0.10],
2370        ["darken", 0.60]
2371    ]
2372};
2373
2374NodeParser.prototype.parseBorders = function(container) {
2375    var nodeBounds = container.parseBounds();
2376    var radius = getBorderRadiusData(container);
2377    var borders = ["Top", "Right", "Bottom", "Left"].map(function(side, index) {
2378        var style = container.css('border' + side + 'Style');
2379        var color = container.color('border' + side + 'Color');
2380        if (style === "inset" && color.isBlack()) {
2381            color = new Color([255, 255, 255, color.a]); // this is wrong, but
2382        }
2383        var colorTransform = borderColorTransforms[style] ? borderColorTransforms[style][index] : null;
2384        return {
2385            width: container.cssInt('border' + side + 'Width'),
2386            color: colorTransform ? color[colorTransform[0]](colorTransform[1]) : color,
2387            args: null
2388        };
2389    });
2390    var borderPoints = calculateCurvePoints(nodeBounds, radius, borders);
2391
2392    return {
2393        clip: this.parseBackgroundClip(container, borderPoints, borders, radius, nodeBounds),
2394        borders: calculateBorders(borders, nodeBounds, borderPoints, radius)
2395    };
2396};
2397
2398function calculateBorders(borders, nodeBounds, borderPoints, radius) {
2399    return borders.map(function(border, borderSide) {
2400        if (border.width > 0) {
2401            var bx = nodeBounds.left;
2402            var by = nodeBounds.top;
2403            var bw = nodeBounds.width;
2404            var bh = nodeBounds.height - (borders[2].width);
2405
2406            switch(borderSide) {
2407            case 0:
2408                // top border
2409                bh = borders[0].width;
2410                border.args = drawSide({
2411                        c1: [bx, by],
2412                        c2: [bx + bw, by],
2413                        c3: [bx + bw - borders[1].width, by + bh],
2414                        c4: [bx + borders[3].width, by + bh]
2415                    }, radius[0], radius[1],
2416                    borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
2417                break;
2418            case 1:
2419                // right border
2420                bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
2421                bw = borders[1].width;
2422
2423                border.args = drawSide({
2424                        c1: [bx + bw, by],
2425                        c2: [bx + bw, by + bh + borders[2].width],
2426                        c3: [bx, by + bh],
2427                        c4: [bx, by + borders[0].width]
2428                    }, radius[1], radius[2],
2429                    borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
2430                break;
2431            case 2:
2432                // bottom border
2433                by = (by + nodeBounds.height) - (borders[2].width);
2434                bh = borders[2].width;
2435                border.args = drawSide({
2436                        c1: [bx + bw, by + bh],
2437                        c2: [bx, by + bh],
2438                        c3: [bx + borders[3].width, by],
2439                        c4: [bx + bw - borders[3].width, by]
2440                    }, radius[2], radius[3],
2441                    borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
2442                break;
2443            case 3:
2444                // left border
2445                bw = borders[3].width;
2446                border.args = drawSide({
2447                        c1: [bx, by + bh + borders[2].width],
2448                        c2: [bx, by],
2449                        c3: [bx + bw, by + borders[0].width],
2450                        c4: [bx + bw, by + bh]
2451                    }, radius[3], radius[0],
2452                    borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
2453                break;
2454            }
2455        }
2456        return border;
2457    });
2458}
2459
2460NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, borders, radius, bounds) {
2461    var backgroundClip = container.css('backgroundClip'),
2462        borderArgs = [];
2463
2464    switch(backgroundClip) {
2465    case "content-box":
2466    case "padding-box":
2467        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
2468        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
2469        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
2470        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
2471        break;
2472
2473    default:
2474        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
2475        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
2476        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
2477        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
2478        break;
2479    }
2480
2481    return borderArgs;
2482};
2483
2484function getCurvePoints(x, y, r1, r2) {
2485    var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
2486    var ox = (r1) * kappa, // control point offset horizontal
2487        oy = (r2) * kappa, // control point offset vertical
2488        xm = x + r1, // x-middle
2489        ym = y + r2; // y-middle
2490    return {
2491        topLeft: bezierCurve({x: x, y: ym}, {x: x, y: ym - oy}, {x: xm - ox, y: y}, {x: xm, y: y}),
2492        topRight: bezierCurve({x: x, y: y}, {x: x + ox,y: y}, {x: xm, y: ym - oy}, {x: xm, y: ym}),
2493        bottomRight: bezierCurve({x: xm, y: y}, {x: xm, y: y + oy}, {x: x + ox, y: ym}, {x: x, y: ym}),
2494        bottomLeft: bezierCurve({x: xm, y: ym}, {x: xm - ox, y: ym}, {x: x, y: y + oy}, {x: x, y:y})
2495    };
2496}
2497
2498function calculateCurvePoints(bounds, borderRadius, borders) {
2499    var x = bounds.left,
2500        y = bounds.top,
2501        width = bounds.width,
2502        height = bounds.height,
2503
2504        tlh = borderRadius[0][0],
2505        tlv = borderRadius[0][1],
2506        trh = borderRadius[1][0],
2507        trv = borderRadius[1][1],
2508        brh = borderRadius[2][0],
2509        brv = borderRadius[2][1],
2510        blh = borderRadius[3][0],
2511        blv = borderRadius[3][1];
2512
2513    var topWidth = width - trh,
2514        rightHeight = height - brv,
2515        bottomWidth = width - brh,
2516        leftHeight = height - blv;
2517
2518    return {
2519        topLeftOuter: getCurvePoints(x, y, tlh, tlv).topLeft.subdivide(0.5),
2520        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),
2521        topRightOuter: getCurvePoints(x + topWidth, y, trh, trv).topRight.subdivide(0.5),
2522        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),
2523        bottomRightOuter: getCurvePoints(x + bottomWidth, y + rightHeight, brh, brv).bottomRight.subdivide(0.5),
2524        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),
2525        bottomLeftOuter: getCurvePoints(x, y + leftHeight, blh, blv).bottomLeft.subdivide(0.5),
2526        bottomLeftInner: getCurvePoints(x + borders[3].width, y + leftHeight, Math.max(0, blh - borders[3].width), blv - borders[2].width).bottomLeft.subdivide(0.5)
2527    };
2528}
2529
2530function bezierCurve(start, startControl, endControl, end) {
2531    var lerp = function (a, b, t) {
2532        return {
2533            x: a.x + (b.x - a.x) * t,
2534            y: a.y + (b.y - a.y) * t
2535        };
2536    };
2537
2538    return {
2539        start: start,
2540        startControl: startControl,
2541        endControl: endControl,
2542        end: end,
2543        subdivide: function(t) {
2544            var ab = lerp(start, startControl, t),
2545                bc = lerp(startControl, endControl, t),
2546                cd = lerp(endControl, end, t),
2547                abbc = lerp(ab, bc, t),
2548                bccd = lerp(bc, cd, t),
2549                dest = lerp(abbc, bccd, t);
2550            return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
2551        },
2552        curveTo: function(borderArgs) {
2553            borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
2554        },
2555        curveToReversed: function(borderArgs) {
2556            borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
2557        }
2558    };
2559}
2560
2561function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
2562    var borderArgs = [];
2563
2564    if (radius1[0] > 0 || radius1[1] > 0) {
2565        borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
2566        outer1[1].curveTo(borderArgs);
2567    } else {
2568        borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]);
2569    }
2570
2571    if (radius2[0] > 0 || radius2[1] > 0) {
2572        borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
2573        outer2[0].curveTo(borderArgs);
2574        borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
2575        inner2[0].curveToReversed(borderArgs);
2576    } else {
2577        borderArgs.push(["line", borderData.c2[0], borderData.c2[1]]);
2578        borderArgs.push(["line", borderData.c3[0], borderData.c3[1]]);
2579    }
2580
2581    if (radius1[0] > 0 || radius1[1] > 0) {
2582        borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
2583        inner1[1].curveToReversed(borderArgs);
2584    } else {
2585        borderArgs.push(["line", borderData.c4[0], borderData.c4[1]]);
2586    }
2587
2588    return borderArgs;
2589}
2590
2591function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
2592    if (radius1[0] > 0 || radius1[1] > 0) {
2593        borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
2594        corner1[0].curveTo(borderArgs);
2595        corner1[1].curveTo(borderArgs);
2596    } else {
2597        borderArgs.push(["line", x, y]);
2598    }
2599
2600    if (radius2[0] > 0 || radius2[1] > 0) {
2601        borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
2602    }
2603}
2604
2605function negativeZIndex(container) {
2606    return container.cssInt("zIndex") < 0;
2607}
2608
2609function positiveZIndex(container) {
2610    return container.cssInt("zIndex") > 0;
2611}
2612
2613function zIndex0(container) {
2614    return container.cssInt("zIndex") === 0;
2615}
2616
2617function inlineLevel(container) {
2618    return ["inline", "inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
2619}
2620
2621function isStackingContext(container) {
2622    return (container instanceof StackingContext);
2623}
2624
2625function hasText(container) {
2626    return container.node.data.trim().length > 0;
2627}
2628
2629function noLetterSpacing(container) {
2630    return (/^(normal|none|0px)$/.test(container.parent.css("letterSpacing")));
2631}
2632
2633function getBorderRadiusData(container) {
2634    return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
2635        var value = container.css('border' + side + 'Radius');
2636        var arr = value.split(" ");
2637        if (arr.length <= 1) {
2638            arr[1] = arr[0];
2639        }
2640        return arr.map(asInt);
2641    });
2642}
2643
2644function renderableNode(node) {
2645    return (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE);
2646}
2647
2648function isPositionedForStacking(container) {
2649    var position = container.css("position");
2650    var zIndex = (["absolute", "relative", "fixed"].indexOf(position) !== -1) ? container.css("zIndex") : "auto";
2651    return zIndex !== "auto";
2652}
2653
2654function isPositioned(container) {
2655    return container.css("position") !== "static";
2656}
2657
2658function isFloating(container) {
2659    return container.css("float") !== "none";
2660}
2661
2662function isInlineBlock(container) {
2663    return ["inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
2664}
2665
2666function not(callback) {
2667    var context = this;
2668    return function() {
2669        return !callback.apply(context, arguments);
2670    };
2671}
2672
2673function isElement(container) {
2674    return container.node.nodeType === Node.ELEMENT_NODE;
2675}
2676
2677function isPseudoElement(container) {
2678    return container.isPseudoElement === true;
2679}
2680
2681function isTextNode(container) {
2682    return container.node.nodeType === Node.TEXT_NODE;
2683}
2684
2685function zIndexSort(contexts) {
2686    return function(a, b) {
2687        return (a.cssInt("zIndex") + (contexts.indexOf(a) / contexts.length)) - (b.cssInt("zIndex") + (contexts.indexOf(b) / contexts.length));
2688    };
2689}
2690
2691function hasOpacity(container) {
2692    return container.getOpacity() < 1;
2693}
2694
2695function bind(callback, context) {
2696    return function() {
2697        return callback.apply(context, arguments);
2698    };
2699}
2700
2701function asInt(value) {
2702    return parseInt(value, 10);
2703}
2704
2705function getWidth(border) {
2706    return border.width;
2707}
2708
2709function nonIgnoredElement(nodeContainer) {
2710    return (nodeContainer.node.nodeType !== Node.ELEMENT_NODE || ["SCRIPT", "HEAD", "TITLE", "OBJECT", "BR", "OPTION"].indexOf(nodeContainer.node.nodeName) === -1);
2711}
2712
2713function flatten(arrays) {
2714    return [].concat.apply([], arrays);
2715}
2716
2717function stripQuotes(content) {
2718    var first = content.substr(0, 1);
2719    return (first === content.substr(content.length - 1) && first.match(/'|"/)) ? content.substr(1, content.length - 2) : content;
2720}
2721
2722function getWords(characters) {
2723    var words = [], i = 0, onWordBoundary = false, word;
2724    while(characters.length) {
2725        if (isWordBoundary(characters[i]) === onWordBoundary) {
2726            word = characters.splice(0, i);
2727            if (word.length) {
2728                words.push(window.html2canvas.punycode.ucs2.encode(word));
2729            }
2730            onWordBoundary =! onWordBoundary;
2731            i = 0;
2732        } else {
2733            i++;
2734        }
2735
2736        if (i >= characters.length) {
2737            word = characters.splice(0, i);
2738            if (word.length) {
2739                words.push(window.html2canvas.punycode.ucs2.encode(word));
2740            }
2741        }
2742    }
2743    return words;
2744}
2745
2746function isWordBoundary(characterCode) {
2747    return [
2748        32, // <space>
2749        13, // \r
2750        10, // \n
2751        9, // \t
2752        45 // -
2753    ].indexOf(characterCode) !== -1;
2754}
2755
2756function hasUnicode(string) {
2757    return (/[^\u0000-\u00ff]/).test(string);
2758}
2759
2760function Proxy(src, proxyUrl, document) {
2761    if (!proxyUrl) {
2762        return Promise.reject("No proxy configured");
2763    }
2764    var callback = createCallback(supportsCORS);
2765    var url = createProxyUrl(proxyUrl, src, callback);
2766
2767    return supportsCORS ? XHR(url) : (jsonp(document, url, callback).then(function(response) {
2768        return decode64(response.content);
2769    }));
2770}
2771var proxyCount = 0;
2772
2773var supportsCORS = ('withCredentials' in new XMLHttpRequest());
2774var supportsCORSImage = ('crossOrigin' in new Image());
2775
2776function ProxyURL(src, proxyUrl, document) {
2777    var callback = createCallback(supportsCORSImage);
2778    var url = createProxyUrl(proxyUrl, src, callback);
2779    return (supportsCORSImage ? Promise.resolve(url) : jsonp(document, url, callback).then(function(response) {
2780        return "data:" + response.type + ";base64," + response.content;
2781    }));
2782}
2783
2784function jsonp(document, url, callback) {
2785    return new Promise(function(resolve, reject) {
2786        var s = document.createElement("script");
2787        var cleanup = function() {
2788            delete window.html2canvas.proxy[callback];
2789            document.body.removeChild(s);
2790        };
2791        window.html2canvas.proxy[callback] = function(response) {
2792            cleanup();
2793            resolve(response);
2794        };
2795        s.src = url;
2796        s.onerror = function(e) {
2797            cleanup();
2798            reject(e);
2799        };
2800        document.body.appendChild(s);
2801    });
2802}
2803
2804function createCallback(useCORS) {
2805    return !useCORS ? "html2canvas_" + Date.now() + "_" + (++proxyCount) + "_" + Math.round(Math.random() * 100000) : "";
2806}
2807
2808function createProxyUrl(proxyUrl, src, callback) {
2809    return proxyUrl + "?url=" + encodeURIComponent(src) + (callback.length ? "&callback=html2canvas.proxy." + callback : "");
2810}
2811
2812function ProxyImageContainer(src, proxy) {
2813    var script = document.createElement("script");
2814    var link = document.createElement("a");
2815    link.href = src;
2816    src = link.href;
2817    this.src = src;
2818    this.image = new Image();
2819    var self = this;
2820    this.promise = new Promise(function(resolve, reject) {
2821        self.image.crossOrigin = "Anonymous";
2822        self.image.onload = resolve;
2823        self.image.onerror = reject;
2824
2825        new ProxyURL(src, proxy, document).then(function(url) {
2826            self.image.src = url;
2827        })['catch'](reject);
2828    });
2829}
2830
2831function PseudoElementContainer(node, parent, type) {
2832    NodeContainer.call(this, node, parent);
2833    this.isPseudoElement = true;
2834    this.before = type === ":before";
2835}
2836
2837PseudoElementContainer.prototype.cloneTo = function(stack) {
2838    PseudoElementContainer.prototype.cloneTo.call(this, stack);
2839    stack.isPseudoElement = true;
2840    stack.before = this.before;
2841};
2842
2843PseudoElementContainer.prototype = Object.create(NodeContainer.prototype);
2844
2845PseudoElementContainer.prototype.appendToDOM = function() {
2846    if (this.before) {
2847        this.parent.node.insertBefore(this.node, this.parent.node.firstChild);
2848    } else {
2849        this.parent.node.appendChild(this.node);
2850    }
2851    this.parent.node.className += " " + this.getHideClass();
2852};
2853
2854PseudoElementContainer.prototype.cleanDOM = function() {
2855    this.node.parentNode.removeChild(this.node);
2856    this.parent.node.className = this.parent.node.className.replace(this.getHideClass(), "");
2857};
2858
2859PseudoElementContainer.prototype.getHideClass = function() {
2860    return this["PSEUDO_HIDE_ELEMENT_CLASS_" + (this.before ? "BEFORE" : "AFTER")];
2861};
2862
2863PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE = "___html2canvas___pseudoelement_before";
2864PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER = "___html2canvas___pseudoelement_after";
2865
2866function Renderer(width, height, images, options, document) {
2867    this.width = width;
2868    this.height = height;
2869    this.images = images;
2870    this.options = options;
2871    this.document = document;
2872}
2873
2874Renderer.prototype.renderImage = function(container, bounds, borderData, imageContainer) {
2875    var paddingLeft = container.cssInt('paddingLeft'),
2876        paddingTop = container.cssInt('paddingTop'),
2877        paddingRight = container.cssInt('paddingRight'),
2878        paddingBottom = container.cssInt('paddingBottom'),
2879        borders = borderData.borders;
2880
2881    var width = bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight);
2882    var height = bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom);
2883    this.drawImage(
2884        imageContainer,
2885        0,
2886        0,
2887        imageContainer.image.width || width,
2888        imageContainer.image.height || height,
2889        bounds.left + paddingLeft + borders[3].width,
2890        bounds.top + paddingTop + borders[0].width,
2891        width,
2892        height
2893    );
2894};
2895
2896Renderer.prototype.renderBackground = function(container, bounds, borderData) {
2897    if (bounds.height > 0 && bounds.width > 0) {
2898        this.renderBackgroundColor(container, bounds);
2899        this.renderBackgroundImage(container, bounds, borderData);
2900    }
2901};
2902
2903Renderer.prototype.renderBackgroundColor = function(container, bounds) {
2904    var color = container.color("backgroundColor");
2905    if (!color.isTransparent()) {
2906        this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, color);
2907    }
2908};
2909
2910Renderer.prototype.renderBorders = function(borders) {
2911    borders.forEach(this.renderBorder, this);
2912};
2913
2914Renderer.prototype.renderBorder = function(data) {
2915    if (!data.color.isTransparent() && data.args !== null) {
2916        this.drawShape(data.args, data.color);
2917    }
2918};
2919
2920Renderer.prototype.renderBackgroundImage = function(container, bounds, borderData) {
2921    var backgroundImages = container.parseBackgroundImages();
2922    backgroundImages.reverse().forEach(function(backgroundImage, index, arr) {
2923        switch(backgroundImage.method) {
2924        case "url":
2925            var image = this.images.get(backgroundImage.args[0]);
2926            if (image) {
2927                this.renderBackgroundRepeating(container, bounds, image, arr.length - (index+1), borderData);
2928            } else {
2929                log("Error loading background-image", backgroundImage.args[0]);
2930            }
2931            break;
2932        case "linear-gradient":
2933        case "gradient":
2934            var gradientImage = this.images.get(backgroundImage.value);
2935            if (gradientImage) {
2936                this.renderBackgroundGradient(gradientImage, bounds, borderData);
2937            } else {
2938                log("Error loading background-image", backgroundImage.args[0]);
2939            }
2940            break;
2941        case "none":
2942            break;
2943        default:
2944            log("Unknown background-image type", backgroundImage.args[0]);
2945        }
2946    }, this);
2947};
2948
2949Renderer.prototype.renderBackgroundRepeating = function(container, bounds, imageContainer, index, borderData) {
2950    var size = container.parseBackgroundSize(bounds, imageContainer.image, index);
2951    var position = container.parseBackgroundPosition(bounds, imageContainer.image, index, size);
2952    var repeat = container.parseBackgroundRepeat(index);
2953    switch (repeat) {
2954    case "repeat-x":
2955    case "repeat no-repeat":
2956        this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + borderData[3], bounds.top + position.top + borderData[0], 99999, size.height, borderData);
2957        break;
2958    case "repeat-y":
2959    case "no-repeat repeat":
2960        this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + borderData[0], size.width, 99999, borderData);
2961        break;
2962    case "no-repeat":
2963        this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + position.top + borderData[0], size.width, size.height, borderData);
2964        break;
2965    default:
2966        this.renderBackgroundRepeat(imageContainer, position, size, {top: bounds.top, left: bounds.left}, borderData[3], borderData[0]);
2967        break;
2968    }
2969};
2970
2971function StackingContext(hasOwnStacking, opacity, element, parent) {
2972    NodeContainer.call(this, element, parent);
2973    this.ownStacking = hasOwnStacking;
2974    this.contexts = [];
2975    this.children = [];
2976    this.opacity = (this.parent ? this.parent.stack.opacity : 1) * opacity;
2977}
2978
2979StackingContext.prototype = Object.create(NodeContainer.prototype);
2980
2981StackingContext.prototype.getParentStack = function(context) {
2982    var parentStack = (this.parent) ? this.parent.stack : null;
2983    return parentStack ? (parentStack.ownStacking ? parentStack : parentStack.getParentStack(context)) : context.stack;
2984};
2985
2986function Support(document) {
2987    this.rangeBounds = this.testRangeBounds(document);
2988    this.cors = this.testCORS();
2989    this.svg = this.testSVG();
2990}
2991
2992Support.prototype.testRangeBounds = function(document) {
2993    var range, testElement, rangeBounds, rangeHeight, support = false;
2994
2995    if (document.createRange) {
2996        range = document.createRange();
2997        if (range.getBoundingClientRect) {
2998            testElement = document.createElement('boundtest');
2999            testElement.style.height = "123px";
3000            testElement.style.display = "block";
3001            document.body.appendChild(testElement);
3002
3003            range.selectNode(testElement);
3004            rangeBounds = range.getBoundingClientRect();
3005            rangeHeight = rangeBounds.height;
3006
3007            if (rangeHeight === 123) {
3008                support = true;
3009            }
3010            document.body.removeChild(testElement);
3011        }
3012    }
3013
3014    return support;
3015};
3016
3017Support.prototype.testCORS = function() {
3018    return typeof((new Image()).crossOrigin) !== "undefined";
3019};
3020
3021Support.prototype.testSVG = function() {
3022    var img = new Image();
3023    var canvas = document.createElement("canvas");
3024    var ctx =  canvas.getContext("2d");
3025    img.src = "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>";
3026
3027    try {
3028        ctx.drawImage(img, 0, 0);
3029        canvas.toDataURL();
3030    } catch(e) {
3031        return false;
3032    }
3033    return true;
3034};
3035
3036function SVGContainer(src) {
3037    this.src = src;
3038    this.image = null;
3039    var self = this;
3040
3041    this.promise = this.hasFabric().then(function() {
3042        return (self.isInline(src) ? Promise.resolve(self.inlineFormatting(src)) : XHR(src));
3043    }).then(function(svg) {
3044        return new Promise(function(resolve) {
3045            html2canvas.fabric.loadSVGFromString(svg, self.createCanvas.call(self, resolve));
3046        });
3047    });
3048}
3049
3050SVGContainer.prototype.hasFabric = function() {
3051    return !html2canvas.fabric ? Promise.reject(new Error("html2canvas.svg.js is not loaded, cannot render svg")) : Promise.resolve();
3052};
3053
3054SVGContainer.prototype.inlineFormatting = function(src) {
3055    return (/^data:image\/svg\+xml;base64,/.test(src)) ? this.decode64(this.removeContentType(src)) : this.removeContentType(src);
3056};
3057
3058SVGContainer.prototype.removeContentType = function(src) {
3059    return src.replace(/^data:image\/svg\+xml(;base64)?,/,'');
3060};
3061
3062SVGContainer.prototype.isInline = function(src) {
3063    return (/^data:image\/svg\+xml/i.test(src));
3064};
3065
3066SVGContainer.prototype.createCanvas = function(resolve) {
3067    var self = this;
3068    return function (objects, options) {
3069        var canvas = new html2canvas.fabric.StaticCanvas('c');
3070        self.image = canvas.lowerCanvasEl;
3071        canvas
3072            .setWidth(options.width)
3073            .setHeight(options.height)
3074            .add(html2canvas.fabric.util.groupSVGElements(objects, options))
3075            .renderAll();
3076        resolve(canvas.lowerCanvasEl);
3077    };
3078};
3079
3080SVGContainer.prototype.decode64 = function(str) {
3081    return (typeof(window.atob) === "function") ? window.atob(str) : decode64(str);
3082};
3083
3084/*
3085 * base64-arraybuffer
3086 * https://github.com/niklasvh/base64-arraybuffer
3087 *
3088 * Copyright (c) 2012 Niklas von Hertzen
3089 * Licensed under the MIT license.
3090 */
3091
3092function decode64(base64) {
3093    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
3093+/";
3094    var len = base64.length, i, encoded1, encoded2, encoded3, encoded4, byte1, byte2, byte3;
3095
3096    var output = "";
3097
3098    for (i = 0; i < len; i+=4) {
3099        encoded1 = chars.indexOf(base64[i]);
3100        encoded2 = chars.indexOf(base64[i+1]);
3101        encoded3 = chars.indexOf(base64[i+2]);
3102        encoded4 = chars.indexOf(base64[i+3]);
3103
3104        byte1 = (encoded1 << 2) | (encoded2 >> 4);
3105        byte2 = ((encoded2 & 15) << 4) | (encoded3 >> 2);
3106        byte3 = ((encoded3 & 3) << 6) | encoded4;
3107        if (encoded3 === 64) {
3108            output += String.fromCharCode(byte1);
3109        } else if (encoded4 === 64 || encoded4 === -1) {
3110            output += String.fromCharCode(byte1, byte2);
3111        } else{
3112            output += String.fromCharCode(byte1, byte2, byte3);
3113        }
3114    }
3115
3116    return output;
3117}
3118
3119function SVGNodeContainer(node, native) {
3120    this.src = node;
3121    this.image = null;
3122    var self = this;
3123
3124    this.promise = native ? new Promise(function(resolve, reject) {
3125        self.image = new Image();
3126        self.image.onload = resolve;
3127        self.image.onerror = reject;
3128        self.image.src = "data:image/svg+xml," + (new XMLSerializer()).serializeToString(node);
3129        if (self.image.complete === true) {
3130            resolve(self.image);
3131        }
3132    }) : this.hasFabric().then(function() {
3133        return new Promise(function(resolve) {
3134            html2canvas.fabric.parseSVGDocument(node, self.createCanvas.call(self, resolve));
3135        });
3136    });
3137}
3138
3139SVGNodeContainer.prototype = Object.create(SVGContainer.prototype);
3140
3141function TextContainer(node, parent) {
3142    NodeContainer.call(this, node, parent);
3143}
3144
3145TextContainer.prototype = Object.create(NodeContainer.prototype);
3146
3147TextContainer.prototype.applyTextTransform = function() {
3148    this.node.data = this.transform(this.parent.css("textTransform"));
3149};
3150
3151TextContainer.prototype.transform = function(transform) {
3152    var text = this.node.data;
3153    switch(transform){
3154        case "lowercase":
3155            return text.toLowerCase();
3156        case "capitalize":
3157            return text.replace(/(^|\s|:|-|\(|\))([a-z])/g, capitalize);
3158        case "uppercase":
3159            return text.toUpperCase();
3160        default:
3161            return text;
3162    }
3163};
3164
3165function capitalize(m, p1, p2) {
3166    if (m.length > 0) {
3167        return p1 + p2.toUpperCase();
3168    }
3169}
3170
3171function WebkitGradientContainer(imageData) {
3172    GradientContainer.apply(this, arguments);
3173    this.type = (imageData.args[0] === "linear") ? this.TYPES.LINEAR : this.TYPES.RADIAL;
3174}
3175
3176WebkitGradientContainer.prototype = Object.create(GradientContainer.prototype);
3177
3178function XHR(url) {
3179    return new Promise(function(resolve, reject) {
3180        var xhr = new XMLHttpRequest();
3181        xhr.open('GET', url);
3182
3183        xhr.onload = function() {
3184            if (xhr.status === 200) {
3185                resolve(xhr.responseText);
3186            } else {
3187                reject(new Error(xhr.statusText));
3188            }
3189        };
3190
3191        xhr.onerror = function() {
3192            reject(new Error("Network Error"));
3193        };
3194
3195        xhr.send();
3196    });
3197}
3198
3199function CanvasRenderer(width, height) {
3200    Renderer.apply(this, arguments);
3201    this.canvas = this.options.canvas || this.document.createElement("canvas");
3202    if (!this.options.canvas) {
3203        this.canvas.width = width;
3204        this.canvas.height = height;
3205    }
3206    this.ctx = this.canvas.getContext("2d");
3207    this.taintCtx = this.document.createElement("canvas").getContext("2d");
3208    this.ctx.textBaseline = "bottom";
3209    this.variables = {};
3210    log("Initialized CanvasRenderer with size", width, "x", height);
3211}
3212
3213CanvasRenderer.prototype = Object.create(Renderer.prototype);
3214
3215CanvasRenderer.prototype.setFillStyle = function(fillStyle) {
3216    this.ctx.fillStyle = typeof(fillStyle) === "object" && !!fillStyle.isColor ? fillStyle.toString() : fillStyle;
3217    return this.ctx;
3218};
3219
3220CanvasRenderer.prototype.rectangle = function(left, top, width, height, color) {
3221    this.setFillStyle(color).fillRect(left, top, width, height);
3222};
3223
3224CanvasRenderer.prototype.circle = function(left, top, size, color) {
3225    this.setFillStyle(color);
3226    this.ctx.beginPath();
3227    this.ctx.arc(left + size / 2, top + size / 2, size / 2, 0, Math.PI*2, true);
3228    this.ctx.closePath();
3229    this.ctx.fill();
3230};
3231
3232CanvasRenderer.prototype.circleStroke = function(left, top, size, color, stroke, strokeColor) {
3233    this.circle(left, top, size, color);
3234    this.ctx.strokeStyle = strokeColor.toString();
3235    this.ctx.stroke();
3236};
3237
3238CanvasRenderer.prototype.drawShape = function(shape, color) {
3239    this.shape(shape);
3240    this.setFillStyle(color).fill();
3241};
3242
3243CanvasRenderer.prototype.taints = function(imageContainer) {
3244    if (imageContainer.tainted === null) {
3245        this.taintCtx.drawImage(imageContainer.image, 0, 0);
3246        try {
3247            this.taintCtx.getImageData(0, 0, 1, 1);
3248            imageContainer.tainted = false;
3249        } catch(e) {
3250            this.taintCtx = document.createElement("canvas").getContext("2d");
3251            imageContainer.tainted = true;
3252        }
3253    }
3254
3255    return imageContainer.tainted;
3256};
3257
3258CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx, dy, dw, dh) {
3259    if (!this.taints(imageContainer) || this.options.allowTaint) {
3260        this.ctx.drawImage(imageContainer.image, sx, sy, sw, sh, dx, dy, dw, dh);
3261    }
3262};
3263
3264CanvasRenderer.prototype.clip = function(shapes, callback, context) {
3265    this.ctx.save();
3266    shapes.filter(hasEntries).forEach(function(shape) {
3267        this.shape(shape).clip();
3268    }, this);
3269    callback.call(context);
3270    this.ctx.restore();
3271};
3272
3273CanvasRenderer.prototype.shape = function(shape) {
3274    this.ctx.beginPath();
3275    shape.forEach(function(point, index) {
3276        if (point[0] === "rect") {
3277            this.ctx.rect.apply(this.ctx, point.slice(1));
3278        } else {
3279            this.ctx[(index === 0) ? "moveTo" : point[0] + "To" ].apply(this.ctx, point.slice(1));
3280        }
3281    }, this);
3282    this.ctx.closePath();
3283    return this.ctx;
3284};
3285
3286CanvasRenderer.prototype.font = function(color, style, variant, weight, size, family) {
3287    this.setFillStyle(color).font = [style, variant, weight, size, family].join(" ").split(",")[0];
3288};
3289
3290CanvasRenderer.prototype.fontShadow = function(color, offsetX, offsetY, blur) {
3291    this.setVariable("shadowColor", color.toString())
3292        .setVariable("shadowOffsetY", offsetX)
3293        .setVariable("shadowOffsetX", offsetY)
3294        .setVariable("shadowBlur", blur);
3295};
3296
3297CanvasRenderer.prototype.clearShadow = function() {
3298    this.setVariable("shadowColor", "rgba(0,0,0,0)");
3299};
3300
3301CanvasRenderer.prototype.setOpacity = function(opacity) {
3302    this.ctx.globalAlpha = opacity;
3303};
3304
3305CanvasRenderer.prototype.setTransform = function(transform) {
3306    this.ctx.translate(transform.origin[0], transform.origin[1]);
3307    this.ctx.transform.apply(this.ctx, transform.matrix);
3308    this.ctx.translate(-transform.origin[0], -transform.origin[1]);
3309};
3310
3311CanvasRenderer.prototype.setVariable = function(property, value) {
3312    if (this.variables[property] !== value) {
3313        this.variables[property] = this.ctx[property] = value;
3314    }
3315
3316    return this;
3317};
3318
3319CanvasRenderer.prototype.text = function(text, left, bottom) {
3320    this.ctx.fillText(text, left, bottom);
3321};
3322
3323CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, size, bounds, left, top, width, height, borderData) {
3324    var shape = [
3325        ["line", Math.round(left), Math.round(top)],
3326        ["line", Math.round(left + width), Math.round(top)],
3327        ["line", Math.round(left + width), Math.round(height + top)],
3328        ["line", Math.round(left), Math.round(height + top)]
3329    ];
3330    this.clip([shape], function() {
3331        this.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]);
3332    }, this);
3333};
3334
3335CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, size, bounds, borderLeft, borderTop) {
3336    var offsetX = Math.round(bounds.left + backgroundPosition.left + borderLeft), offsetY = Math.round(bounds.top + backgroundPosition.top + borderTop);
3337    this.setFillStyle(this.ctx.createPattern(this.resizeImage(imageContainer, size), "repeat"));
3338    this.ctx.translate(offsetX, offsetY);
3339    this.ctx.fill();
3340    this.ctx.translate(-offsetX, -offsetY);
3341};
3342
3343CanvasRenderer.prototype.renderBackgroundGradient = function(gradientImage, bounds) {
3344    if (gradientImage instanceof LinearGradientContainer) {
3345        var gradient = this.ctx.createLinearGradient(
3346            bounds.left + bounds.width * gradientImage.x0,
3347            bounds.top + bounds.height * gradientImage.y0,
3348            bounds.left +  bounds.width * gradientImage.x1,
3349            bounds.top +  bounds.height * gradientImage.y1);
3350        gradientImage.colorStops.forEach(function(colorStop) {
3351            gradient.addColorStop(colorStop.stop, colorStop.color.toString());
3352        });
3353        this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, gradient);
3354    }
3355};
3356
3357CanvasRenderer.prototype.resizeImage = function(imageContainer, size) {
3358    var image = imageContainer.image;
3359    if(image.width === size.width && image.height === size.height) {
3360        return image;
3361    }
3362
3363    var ctx, canvas = document.createElement('canvas');
3364    canvas.width = size.width;
3365    canvas.height = size.height;
3366    ctx = canvas.getContext("2d");
3367    ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height );
3368    return canvas;
3369};
3370
3371function hasEntries(array) {
3372    return array.length > 0;
3373}
3374
3375}
3375).call({}, typeof(window) !== "undefined" ? window : undefined, typeof(document) !== "undefined" ? document : undefined);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.