PageSourceSearch

https://printatch.com/Scripts/html2canvas.js

js printatch.com collected 2026-09-29 03:18:53 UTC 96,610 bytes, 2,910 lines download raw bytes

1/*
2  html2canvas 0.4.1 <http://html2canvas.hertzen.com>
3  Copyright (c) 2013 Niklas von Hertzen
4
5  Released under MIT License
6*/
7
8(function(window, document, undefined) {
9
10  "use strict";
11
12  var _html2canvas = {},
13    previousElement,
14    computedCSS,
15    html2canvas;
16
17  _html2canvas.Util = {};
18
19  _html2canvas.Util.log = function(a) {
20    if(_html2canvas.logging && window.console && window.console.log) {
21      window.console.log(a);
22    }
23  };
24
25  _html2canvas.Util.trimText = (function(isNative) {
26    return function(input) {
27      return isNative ? isNative.apply(input) : ((input || '') + '').replace(/^\s+|\s+$/g, '');
28    };
29  })(String.prototype.trim);
30
31  _html2canvas.Util.asFloat = function(v) {
32    return parseFloat(v);
33  };
34
35  (function() {
36    // TODO: support all possible length values
37    var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
38    var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
39    _html2canvas.Util.parseTextShadows = function(value) {
40      if(!value || value === 'none') {
41        return [];
42      }
43
44      // find multiple shadow declarations
45      var shadows = value.match(TEXT_SHADOW_PROPERTY),
46        results = [];
47      for(var i = 0; shadows && (i < shadows.length); i++) {
48        var s = shadows[i].match(TEXT_SHADOW_VALUES);
49        results.push({
50          color: s[0],
51          offsetX: s[1] ? s[1].replace('px', '') : 0,
52          offsetY: s[2] ? s[2].replace('px', '') : 0,
53          blur: s[3] ? s[3].replace('px', '') : 0
54        });
55      }
56      return results;
57    };
58  })();
59
60  _html2canvas.Util.parseBackgroundImage = function(value) {
61    var whitespace = ' \r\n\t',
62      method, definition, prefix, prefix_i, block, results = [],
63      c, mode = 0,
64      numParen = 0,
65      quote, args;
66
67    var appendResult = function() {
68      if(method) {
69        if(definition.substr(0, 1) === '"') {
70          definition = definition.substr(1, definition.length - 2);
71        }
72        if(definition) {
73          args.push(definition);
74        }
75        if(method.substr(0, 1) === '-' &&
76          (prefix_i = method.indexOf('-', 1) + 1) > 0) {
77          prefix = method.substr(0, prefix_i);
78          method = method.substr(prefix_i);
79        }
80        results.push({
81          prefix: prefix,
82          method: method.toLowerCase(),
83          value: block,
84          args: args
85        });
86      }
87      args = []; //for some odd reason, setting .length = 0 didn't work in safari
88      method =
89        prefix =
90        definition =
91        block = '';
92    };
93
94    appendResult();
95    for(var i = 0, ii = value.length; i < ii; i++) {
96      c = value[i];
97      if(mode === 0 && whitespace.indexOf(c) > -1) {
98        continue;
99      }
100      switch(c) {
101        case '"':
102          if(!quote) {
103            quote = c;
104          } else if(quote === c) {
105            quote = null;
106          }
107          break;
108
109        case '(':
110          if(quote) {
111            break;
112          } else if(mode === 0) {
113            mode = 1;
114            block += c;
115            continue;
116          } else {
117            numParen++;
118          }
119          break;
120
121        case ')':
122          if(quote) {
123            break;
124          } else if(mode === 1) {
125            if(numParen === 0) {
126              mode = 0;
127              block += c;
128              appendResult();
129              continue;
130            } else {
131              numParen--;
132            }
133          }
134          break;
135
136        case ',':
137          if(quote) {
138            break;
139          } else if(mode === 0) {
140            appendResult();
141            continue;
142          } else if(mode === 1) {
143            if(numParen === 0 && !method.match(/^url$/i)) {
144              args.push(definition);
145              definition = '';
146              block += c;
147              continue;
148            }
149          }
150          break;
151      }
152
153      block += c;
154      if(mode === 0) {
155        method += c;
156      } else {
157        definition += c;
158      }
159    }
160    appendResult();
161
162    return results;
163  };
164
165  _html2canvas.Util.Bounds = function(element) {
166    var clientRect, bounds = {};
167
168    if(element.getBoundingClientRect) {
169      clientRect = element.getBoundingClientRect();
170
171      // TODO add scroll position to bounds, so no scrolling of window necessary
172      bounds.top = clientRect.top;
173      bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
174      bounds.left = clientRect.left;
175
176      bounds.width = element.offsetWidth;
177      bounds.height = element.offsetHeight;
178    }
179
180    return bounds;
181  };
182
183  // TODO ideally, we'd want everything to go through this function instead of Util.Bounds,
184  // but would require further work to calculate the correct positions for elements with offsetParents
185  _html2canvas.Util.OffsetBounds = function(element) {
186    var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {
187      top: 0,
188      left: 0
189    };
190
191    return {
192      top: element.offsetTop + parent.top,
193      bottom: element.offsetTop + element.offsetHeight + parent.top,
194      left: element.offsetLeft + parent.left,
195      width: element.offsetWidth,
196      height: element.offsetHeight
197    };
198  };
199
200  function toPX(element, attribute, value) {
201    var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute],
202      left,
203      style = element.style;
204
205    // Check if we are not dealing with pixels, (Opera has issues with this)
206    // Ported from jQuery css.js
207    // From the awesome hack by Dean Edwards
208    // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
209
210    // If we're not dealing with a regular pixel number
211    // but a number that has a weird ending, we need to convert it to pixels
212
213    if(!/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test(value) && /^-?\d/.test(value)) {
214      // Remember the original values
215      left = style.left;
216
217      // Put in the new values to get a computed value out
218      if(rsLeft) {
219        element.runtimeStyle.left = element.currentStyle.left;
220      }
221      style.left = attribute === "fontSize" ? "1em" : (value || 0
221);
222      value = style.pixelLeft + "px";
223
224      // Revert the changed values
225      style.left = left;
226      if(rsLeft) {
227        element.runtimeStyle.left = rsLeft;
228      }
229    }
230
231    if(!/^(thin|medium|thick)$/i.test(value)) {
232      return Math.round(parseFloat(value)) + "px";
233    }
234
235    return value;
236  }
237
238  function asInt(val) {
239    return parseInt(val, 10);
240  }
241
242  function parseBackgroundSizePosition(value, element, attribute, index) {
243    value = (value || '').split(',');
244    value = value[index || 0] || value[0] || 'auto';
245    value = _html2canvas.Util.trimText(value).split(' ');
246
247    if(attribute === 'backgroundSize' && (!value[0] || value[0].match(/cover|contain|auto/))) {
248      //these values will be handled in the parent function
249    } else {
250      value[0] = (value[0].indexOf("%") === -1) ? toPX(element, attribute + "X", value[0]) : value[0];
251      if(value[1] === undefined) {
252        if(attribute === 'backgroundSize') {
253          value[1] = 'auto';
254          return value;
255        } else {
256          // IE 9 doesn't return double digit always
257          value[1] = value[0];
258        }
259      }
260      value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1];
261    }
262    return value;
263  }
264
265  _html2canvas.Util.getCSS = function(element, attribute, index) {
266    if(previousElement !== element) {
267      computedCSS = document.defaultView.getComputedStyle(element, null);
268    }
269
270    var value = computedCSS[attribute];
271
272    if(/^background(Size|Position)$/.test(attribute)) {
273      return parseBackgroundSizePosition(value, element, attribute, index);
274    } else if(/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) {
275      var arr = value.split(" ");
276      if(arr.length <= 1) {
277        arr[1] = arr[0];
278      }
279      return arr.map(asInt);
280    }
281
282    return value;
283  };
284
285  _html2canvas.Util.resizeBounds = function(current_width, current_height, target_width, target_height, stretch_mode) {
286    var target_ratio = target_width / target_height,
287      current_ratio = current_width / current_height,
288      output_width, output_height;
289
290    if(!stretch_mode || stretch_mode === 'auto') {
291      output_width = target_width;
292      output_height = target_height;
293    } else if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
294      output_height = target_height;
295      output_width = target_height * current_ratio;
296    } else {
297      output_width = target_width;
298      output_height = target_width / current_ratio;
299    }
300
301    return {
302      width: output_width,
303      height: output_height
304    };
305  };
306
307  function backgroundBoundsFactory(prop, el, bounds, image, imageIndex, backgroundSize) {
308    var bgposition = _html2canvas.Util.getCSS(el, prop, imageIndex),
309      topPos,
310      left,
311      percentage,
312      val;
313
314    if(bgposition.length === 1) {
315      val = bgposition[0];
316
317      bgposition = [];
318
319      bgposition[0] = val;
320      bgposition[1] = val;
321    }
322
323    if(bgposition[0].toString().indexOf("%") !== -1) {
324      percentage = (parseFloat(bgposition[0]) / 100);
325      left = bounds.width * percentage;
326      if(prop !== 'backgroundSize') {
327        left -= (backgroundSize || image).width * percentage;
328      }
329    } else {
330      if(prop === 'backgroundSize') {
331        if(bgposition[0] === 'auto') {
332          left = image.width;
333        } else {
334          if(/contain|cover/.test(bgposition[0])) {
335            var resized = _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, bgposition[0]);
336            left = resized.width;
337            topPos = resized.height;
338          } else {
339            left = parseInt(bgposition[0], 10);
340          }
341        }
342      } else {
343        left = parseInt(bgposition[0], 10);
344      }
345    }
346
347    if(bgposition[1] === 'auto') {
348      topPos = left / image.width * image.height;
349    } else if(bgposition[1].toString().indexOf("%") !== -1) {
350      percentage = (parseFloat(bgposition[1]) / 100);
351      topPos = bounds.height * percentage;
352      if(prop !== 'backgroundSize') {
353        topPos -= (backgroundSize || image).height * percentage;
354      }
355
356    } else {
357      topPos = parseInt(bgposition[1], 10);
358    }
359
360    return [left, topPos];
361  }
362
363  _html2canvas.Util.BackgroundPosition = function(el, bounds, image, imageIndex, backgroundSize) {
364    var result = backgroundBoundsFactory('backgroundPosition', el, bounds, image, imageIndex, backgroundSize);
365    return {
366      left: result[0],
367      top: result[1]
368    };
369  };
370
371  _html2canvas.Util.BackgroundSize = function(el, bounds, image, imageIndex) {
372    var result = backgroundBoundsFactory('backgroundSize', el, bounds, image, imageIndex);
373    return {
374      width: result[0],
375      height: result[1]
376    };
377  };
378
379  _html2canvas.Util.Extend = function(options, defaults) {
380    for(var key in options) {
381      if(options.hasOwnProperty(key)) {
382        defaults[key] = options[key];
383      }
384    }
385    return defaults;
386  };
387
388  /*
389   * Derived from jQuery.contents()
390   * Copyright 2010, John Resig
391   * Dual licensed under the MIT or GPL Version 2 licenses.
392   * http://jquery.org/license
393   */
394  _html2canvas.Util.Children = function(elem) {
395    var children;
396    try {
397      children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? elem.contentDocument || elem.contentWindow.document : (function(
398        array) {
399        var ret = [];
400        if(array !== null) {
401          (function(first, second) {
402            var i = first.length,
403              j = 0;
404
405            if(typeof second.length === "number") {
406              for(var l = second.length; j < l; j++) {
407                first[i++] = second[j];
408              }
409            } else {
410              while(second[j] !== undefined) {
411                first[i++] = second[j++];
412              }
413            }
414
415            first.length = i;
416
417            return first;
418          })(ret, array);
419        }
420        return ret;
421      })(elem.childNodes);
422
423    } catch(ex) {
424      _html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message);
425      children = [];
426    }
427    return children;
428  };
429
430  _html2canvas.Util.isTransparent = function(backgroundColor) {
431    return(backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
432  };
433  _html2canvas.Util.Font = (function() {
434
435    var fontData = {};
436
437    return function(font, fontSize, doc) {
438      if(fontData[font + "-" + fontSize] !== undefined) {
439        return fontData[font + "-" + fontSize];
440      }
441
442      var container = doc.createElement('div'),
443        img = doc.createElement('img'),
444        span = doc.createElement('span'),
445        sampleText = 'Hidden Text',
446        baseline,
447        middle,
448        metricsObj;
449
450      container.style.visibility = "hidden";
451      container.style.fontFamily = font;
452      container.style.fontSize = fontSize;
453      container.style.margin = 0;
454      container.style.padding = 0;
455
456      doc.body.appendChild(container);
457
458      // http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif)
459      img.src = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=";
460      img.width = 1;
461      img.height = 1;
462
463      img.style.margin = 0;
464      img.style.padding = 0;
465      img.style.verticalAlign = "baseline";
466
467      span.style.fontFamily = font;
468      span.style.fontSize = fontSize;
469      span.style.margin = 0;
470      span.style.padding = 0;
471
472      span.appendChild(doc.createTextNode(sampleText));
473      container.appendChild(span);
474      container.appendChild(img);
475      baseline = (img.offsetTop - span.offsetTop) + 1;
476
477      container.removeChild(span);
478      container.appendChild(doc.createTextNode(sampleText));
479
480      container.style.lineHeight = "normal";
481      img.style.verticalAlign = "super";
482
483      middle = (img.offsetTop - container.offsetTop) + 1;
484      metricsObj = {
485        baseline: baseline,
486        lineWidth: 1,
487        middle: middle
488      };
489
490      fontData[font + "-" + fontSize] = metricsObj;
491
492      doc.body.removeChild(container);
493
494      return metricsObj;
495    };
496  })();
497
498  (function() {
499    var Util = _html2canvas.Util,
500      Generate = {};
501
502    _html2canvas.Generate = Generate;
503
504    var reGradients = [
505      /^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
506      /^(-o-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
507      /^(-webkit-gradient)\((linear|radial),\s((?:\d{1,3}%?)\s(?:\d{1,3}%?),\s(?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)\-]+)\)$/,
508      /^(-moz-linear-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)]+)\)$/,
509      /^(-webkit-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/,
510      /^(-moz-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s?([a-z\-]*)([\w\d\.\s,%\(\)]+)\)$/,
511      /^(-o-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/
512    ];
513
514    /*
515     * TODO: Add IE10 vendor prefix (-ms) support
516     * TODO: Add W3C gradient (linear-gradient) support
517     * TODO: Add old Webkit -webkit-gradient(radial, ...) support
518     * TODO: Maybe some RegExp optimizations are possible ;o)
519     */
520    Generate.parseGradient = function(css, bounds) {
521      var gradient, i, len = reGradients.length,
522        m1, stop, m2, m2Len, step, m3, tl, tr, br, bl;
523
524      for(i = 0; i < len; i += 1) {
525        m1 = css.match(reGradients[i]);
526        if(m1) {
527          break;
528        }
529      }
530
531      if(m1) {
532        switch(m1[1]) {
533          case '-webkit-linear-gradient':
534          case '-o-linear-gradient':
535
536            gradient = {
537              type: 'linear',
538              x0: null,
539              y0: null,
540              x1: null,
541              y1: null,
542              colorStops: []
543            };
544
545            // get coordinates
546            m2 = m1[2].match(/\w+/g);
547            if(m2) {
548              m2Len = m2.length;
549              for(i = 0; i < m2Len; i += 1) {
550                switch(m2[i]) {
551                  case 'top':
552                    gradient.y0 = 0;
553                    gradient.y1 = bounds.height;
554                    break;
555
556                  case 'right':
557                    gradient.x0 = bounds.width;
558                    gradient.x1 = 0;
559                    break;
560
561                  case 'bottom':
562                    gradient.y0 = bounds.height;
563                    gradient.y1 = 0;
564                    break;
565
566                  case 'left':
567                    gradient.x0 = 0;
568                    gradient.x1 = bounds.width;
569                    break;
570                }
571              }
572            }
573            if(gradient.x0 === null && gradient.x1 === null) { // center
574              gradient.x0 = gradient.x1 = bounds.width / 2;
575            }
576            if(gradient.y0 === null && gradient.y1 === null) { // center
577              gradient.y0 = gradient.y1 = bounds.height / 2;
578            }
579
580            // get colors and stops
581            m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
582            if(m2) {
583              m2Len = m2.length;
584              step = 1 / Math.max(m2Len - 1, 1);
585              for(i = 0; i < m2Len; i += 1) {
586                m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
587                if(m3[2]) {
588                  stop = parseFloat(m3[2]);
589                  if(m3[3] === '%') {
590                    stop /= 100;
591                  } else { // px - stupid opera
592                    stop /= bounds.width;
593                  }
594                } else {
595                  stop = i * step;
596                }
597                gradient.colorStops.push({
598                  color: m3[1],
599                  stop: stop
600                });
601              }
602            }
603            break;
604
605          case '-webkit-gradient':
606
607            gradient = {
608              type: m1[2] === 'radial' ? 'circle' : m1[2], // TODO: Add radial gradient support for older mozilla definitions
609              x0: 0,
610              y0: 0,
611              x1: 0,
612              y1: 0,
613              colorStops: []
614            };
615
616            // get coordinates
617            m2 = m1[3].match(/(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/);
618            if(m2) {
619              gradient.x0 = (m2[1] * bounds.width) / 100;
620              gradient.y0 = (m2[2] * bounds.height) / 100;
621              gradient.x1 = (m2[3] * bounds.width) / 100;
622              gradient.y1 = (m2[4] * bounds.height) / 100;
623            }
624
625            // get colors and stops
626            m2 = m1[4].match(/((?:from|to|color-stop)\((?:[0-9\.]+,\s)?(?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)\))+/g);
627            if(m2) {
628              m2Len = m2.length;
629              for(i = 0; i < m2Len; i += 1) {
630                m3 = m2[i].match(/(from|to|color-stop)\(([0-9\.]+)?(?:,\s)?((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\)/);
631                stop = parseFloat(m3[2]);
632                if(m3[1] === 'from') {
633                  stop = 0.0;
634                }
635                if(m3[1] === 'to') {
636                  stop = 1.0;
637                }
638                gradient.colorStops.push({
639                  color: m3[3],
640                  stop: stop
641                });
642              }
643            }
644            break;
645
646          case '-moz-linear-gradient':
647
648            gradient = {
649              type: 'linear',
650              x0: 0,
651              y0: 0,
652              x1: 0,
653              y1: 0,
654              colorStops: []
655            };
656
657            // get coordinates
658            m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
659
660            // m2[1] == 0%   -> left
661            // m2[1] == 50%  -> center
662            // m2[1] == 100% -> right
663
664            // m2[2] == 0%   -> top
665            // m2[2] == 50%  -> center
666            // m2[2] == 100% -> bottom
667
668            if(m2) {
669              gradient.x0 = (m2[1] * bounds.width) / 100;
670              gradient.y0 = (m2[2] * bounds.height) / 100;
671              gradient.x1 = bounds.width - gradient.x0;
672              gradient.y1 = bounds.height - gradient.y0;
673            }
674
675            // get colors and stops
676            m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}%)?)+/g);
677            if(m2) {
678              m2Len = m2.length;
679              step = 1 / Math.max(m2Len - 1, 1);
680              for(i = 0; i < m2Len; i += 1) {
681                m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%)?/);
682                if(m3[2]) {
683                  stop = parseFloat(m3[2]);
684                  if(m3[3]) { // percentage
685                    stop /= 100;
686                  }
687                } else {
688                  stop = i * step;
689                }
690                gradient.colorStops.push({
691                  color: m3[1],
692                  stop: stop
693                });
694              }
695            }
696            break;
697
698          case '-webkit-radial-gradient':
699          case '-moz-radial-gradient':
700          case '-o-radial-gradient':
701
702            gradient = {
703              type: 'circle',
704              x0: 0,
705              y0: 0,
706              x1: bounds.width,
707              y1: bounds.height,
708              cx: 0,
709              cy: 0,
710              rx: 0,
711              ry: 0,
712              colorStops: []
713            };
714
715            // center
716            m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
717            if(m2) {
718              gradient.cx = (m2[1] * bounds.width) / 100;
719              gradient.cy = (m2[2] * bounds.height) / 100;
720            }
721
722            // size
723            m2 = m1[3].match(/\w+/);
724            m3 = m1[4].match(/[a-z\-]*/);
725            if(m2 && m3) {
726              switch(m3[0]) {
727                case 'farthest-corner':
728                case 'cover': // is equivalent to farthest-corner
729                case '': // mozilla removes "cover" from definition :(
730                  tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
731                  tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
732                  br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
733                  bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
734                  gradient.rx = gradient.ry = Math.max(tl, tr, br, bl);
735                  break;
736                case 'closest-corner':
737                  tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
738                  tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
739                  br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
740                  bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
741                  gradient.rx = gradient.ry = Math.min(tl, tr, br, bl);
742                  break;
743                case 'farthest-side':
744                  if(m2[0] === 'circle') {
745                    gradient.rx = gradient.ry = Math.max(
746                      gradient.cx,
747                      gradient.cy,
748                      gradient.x1 - gradient.cx,
749                      gradient.y1 - gradient.cy
750                    );
751                  } else { // ellipse
752
753                    gradient.type = m2[0];
754
755                    gradient.rx = Math.max(
756                      gradient.cx,
757                      gradient.x1 - gradient.cx
758                    );
759                    gradient.ry = Math.max(
760                      gradient.cy,
761                      gradient.y1 - gradient.cy
762                    );
763                  }
764                  break;
765                case 'closest-side':
766                case 'contain': // is equivalent to closest-side
767                  if(m2[0] === 'circle') {
768                    gradient.rx = gradient.ry = Math.min(
769                      gradient.cx,
770                      gradient.cy,
771                      gradient.x1 - gradient.cx,
772                      gradient.y1 - gradient.cy
773                    );
774                  } else { // ellipse
775
776                    gradient.type = m2[0];
777
778                    gradient.rx = Math.min(
779                      gradient.cx,
780                      gradient.x1 - gradient.cx
781                    );
782                    gradient.ry = Math.min(
783                      gradient.cy,
784                      gradient.y1 - gradient.cy
785                    );
786                  }
787                  break;
788
789                  // TODO: add support for "30px 40px" sizes (webkit only)
790              }
791            }
792
793            // color stops
794            m2 = m1[5].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
795            if(m2) {
796              m2Len = m2.length;
797              step = 1 / Math.max(m2Len - 1, 1);
798              for(i = 0; i < m2Len; i += 1) {
799                m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
800                if(m3[2]) {
801                  stop = parseFloat(m3[2]);
802                  if(m3[3] === '%') {
803                    stop /= 100;
804                  } else { // px - stupid opera
805                    stop /= bounds.width;
806                  }
807                } else {
808                  stop = i * step;
809                }
810                gradient.colorStops.push({
811                  color: m3[1],
812                  stop: stop
813                });
814              }
815            }
816            break;
817        }
818      }
819
820      return gradient;
821    };
822
823    function addScrollStops(grad) {
824      return function(colorStop) {
825        try {
826          grad.addColorStop(colorStop.stop, colorStop.color);
827        } catch(e) {
828          Util.log(['failed to add color stop: ', e, '; tried to add: ', colorStop]);
829        }
830      };
831    }
832
833    Generate.Gradient = function(src, bounds) {
834      if(bounds.width === 0 || bounds.height === 0) {
835        return;
836      }
837
838      var canvas = document.createElement('canvas'),
839        ctx = canvas.getContext('2d'),
840        gradient, grad;
841
842      canvas.width = bounds.width;
843      canvas.height = bounds.height;
844
845      // TODO: add support for multi defined background gradients
846      gradient = _html2canvas.Generate.parseGradient(src, bounds);
847
848      if(gradient) {
849        switch(gradient.type) {
850          case 'linear':
851            grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
852            gradient.colorStops.forEach(addScrollStops(grad));
853            ctx.fillStyle = grad;
854            ctx.fillRect(0, 0, bounds.width, bounds.height);
855            break;
856
857          case 'circle':
858            grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
859            gradient.colorStops.forEach(addScrollStops(grad));
860            ctx.fillStyle = grad;
861            ctx.fillRect(0, 0, bounds.width, bounds.height);
862            break;
863
864          case 'ellipse':
865            var canvasRadial = document.createElement('canvas'),
866              ctxRadial = canvasRadial.getContext('2d'),
867              ri = Math.max(gradient.rx, gradient.ry),
868              di = ri * 2;
869
870            canvasRadial.width = canvasRadial.height = di;
871
872            grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
873            gradient.colorStops.forEach(addScrollStops(grad));
874
875            ctxRadial.fillStyle = grad;
876            ctxRadial.fillRect(0, 0, di, di);
877
878            ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color;
879            ctx.fillRect(0, 0, canvas.width, canvas.height);
880            ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
881            break;
882        }
883      }
884
885      return canvas;
886    };
887
888    Generate.ListAlpha = function(number) {
889      var tmp = "",
890        modulus;
891
892      do {
893        modulus = number % 26;
894        tmp = String.fromCharCode((modulus) + 64) + tmp;
895        number = number / 26;
896      } while ((number * 26) > 26);
897
898      return tmp;
899    };
900
901    Generate.ListRoman = function(number) {
902      var romanArray = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"],
903        decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
904        roman = "",
905        v,
906        len = romanArray.length;
907
908      if(number <= 0 || number >= 4000) {
909        return number;
910      }
911
912      for(v = 0; v < len; v += 1) {
913        while(number >= decimal[v]) {
914          number -= decimal[v];
915          roman += romanArray[v];
916        }
917      }
918
919      return roman;
920    };
921  })();
922
923  function h2cRenderContext(width, height) {
924    var storage = [];
925    return {
926      storage: storage,
927      width: width,
928      height: height,
929      clip: function() {
930        storage.push({
931          type: "function",
932          name: "clip",
933          'arguments': arguments
934        });
935      },
936      translate: function() {
937        storage.push({
938          type: "function",
939          name: "translate",
940          'arguments': arguments
941        });
942      },
943      fill: function() {
944        storage.push({
945          type: "function",
946          name: "fill",
947          'arguments': arguments
948        });
949      },
950      save: function() {
951        storage.push({
952          type: "function",
953          name: "save",
954          'arguments': arguments
955        });
956      },
957      restore: function() {
958        storage.push({
959          type: "function",
960          name: "restore",
961          'arguments': arguments
962        });
963      },
964      fillRect: function() {
965        storage.push({
966          type: "function",
967          name: "fillRect",
968          'arguments': arguments
969        });
970      },
971      createPattern: function() {
972        storage.push({
973          type: "function",
974          name: "createPattern",
975          'arguments': arguments
976        });
977      },
978      drawShape: function() {
979
980        var shape = [];
981
982        storage.push({
983          type: "function",
984          name: "drawShape",
985          'arguments': shape
986        });
987
988        return {
989          moveTo: function() {
990            shape.push({
991              name: "moveTo",
992              'arguments': arguments
993            });
994          },
995          lineTo: function() {
996            shape.push({
997              name: "lineTo",
998              'arguments': arguments
999            });
1000          },
1001          arcTo: function() {
1002            shape.push({
1003              name: "arcTo",
1004              'arguments': arguments
1005            });
1006          },
1007          bezierCurveTo: function() {
1008            shape.push({
1009              name: "bezierCurveTo",
1010              'arguments': arguments
1011            });
1012          },
1013          quadraticCurveTo: function() {
1014            shape.push({
1015              name: "quadraticCurveTo",
1016              'arguments': arguments
1017            });
1018          }
1019        };
1020
1021      },
1022      drawImage: function() {
1023        storage.push({
1024          type: "function",
1025          name: "drawImage",
1026          'arguments': arguments
1027        });
1028      },
1029      fillText: function() {
1030        storage.push({
1031          type: "function",
1032          name: "fillText",
1033          'arguments': arguments
1034        });
1035      },
1036      setVariable: function(variable, value) {
1037        storage.push({
1038          type: "variable",
1039          name: variable,
1040          'arguments': value
1041        });
1042        return value;
1043      }
1044    };
1045  }
1046  _html2canvas.Parse = function(images, options) {
1047    window.scroll(0, 0);
1048
1049    var element = ((options.elements === undefined) ? document.body : options.elements[0]), // select body by default
1050      numDraws = 0,
1051      doc = element.ownerDocument,
1052      Util = _html2canvas.Util,
1053      support = Util.Support(options, doc),
1054      ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"),
1055      body = doc.body,
1056      getCSS = Util.getCSS,
1057      pseudoHide = "___html2canvas___pseudoelement",
1058      hidePseudoElements = doc.createElement('style');
1059
1060    hidePseudoElements.innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' +
1061      '.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }';
1062
1063    body.appendChild(hidePseudoElements);
1064
1065    images = images || {};
1066
1067    function documentWidth() {
1068      return Math.max(
1069        Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
1070        Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
1071        Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
1072      );
1073    }
1074
1075    function documentHeight() {
1076      return Math.max(
1077        Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
1078        Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
1079        Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
1080      );
1081    }
1082
1083    function getCSSInt(element, attribute) {
1084      var val = parseInt(getCSS(element, attribute), 10);
1085      return(isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html
1086    }
1087
1088    function renderRect(ctx, x, y, w, h, bgcolor) {
1089      if(bgcolor !== "transparent") {
1090        ctx.setVariable("fillStyle", bgcolor);
1091        ctx.fillRect(x, y, w, h);
1092        numDraws += 1;
1093      }
1094    }
1095
1096    function capitalize(m, p1, p2) {
1097      if(m.length > 0) {
1098        return p1 + p2.toUpperCase();
1099      }
1100    }
1101
1102    function textTransform(text, transform) {
1103      switch(transform) {
1104        case "lowercase":
1105          return text.toLowerCase();
1106        case "capitalize":
1107          return text.replace(/(^|\s|:|-|\(|\))([a-z])/g, capitalize);
1108        case "uppercase":
1109          return text.toUpperCase();
1110        default:
1111          return text;
1112      }
1113    }
1114
1115    function noLetterSpacing(letter_spacing) {
1116      return(/^(normal|none|0px)$/.test(letter_spacing));
1117    }
1118
1119    function drawText(currentText, x, y, ctx) {
1120      if(currentText !== null && Util.trimText(currentText).length > 0) {
1121        ctx.fillText(currentText, x, y);
1122        numDraws += 1;
1123      }
1124    }
1125
1126    function setTextVariables(ctx, el, text_decoration, color) {
1127      var align = false,
1128        bold = getCSS(el, "fontWeight"),
1129        family = getCSS(el, "fontFamily"),
1130        size = getCSS(el, "fontSize"),
1131        shadows = Util.parseTextShadows(getCSS(el, "textShadow"));
1132
1133      switch(parseInt(bold, 10)) {
1134        case 401:
1135          bold = "bold";
1136          break;
1137        case 400:
1138          bold = "normal";
1139          break;
1140      }
1141
1142      ctx.setVariable("fillStyle", color);
1143      ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" "));
1144      ctx.setVariable("textAlign", (align) ? "right" : "left");
1145
1146      if(shadows.length) {
1147        // TODO: support multiple text shadows
1148        // apply the first text shadow
1149        ctx.setVariable("shadowColor", shadows[0].color);
1150        ctx.setVariable("shadowOffsetX", shadows[0].offsetX);
1151        ctx.setVariable("shadowOffsetY", shadows[0].offsetY);
1152        ctx.setVariable("shadowBlur", shadows[0].blur);
1153      }
1154
1155      if(text_decoration !== "none") {
1156        return Util.Font(family, size, doc);
1157      }
1158    }
1159
1160    function renderTextDecoration(ctx, text_decoration, bounds, metrics, color) {
1161      switch(text_decoration) {
1162        case "underline":
1163          // Draws a line at the baseline of the font
1164          // 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
1165          renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color);
1166          break;
1167        case "overline":
1168          renderRect(ctx, bounds.left, Math.round(bounds.top), bounds.width, 1, color);
1169          break;
1170        case "line-through":
1171          // TODO try and find exact position for line-through
1172          renderRect(ctx, bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, color);
1173          break;
1174      }
1175    }
1176
1177    function getTextBounds(state, text, textDecoration, isLast, transform) {
1178      var bounds;
1179      if(support.rangeBounds && !transform) {
1180        if(textDecoration !== "none" || Util.trimText(text).length !== 0) {
1181          bounds = textRangeBounds(text, state.node, state.textOffset);
1182        }
1183        state.textOffset += text.length;
1184      } else if(state.node && typeof state.node.nodeValue === "string") {
1185        var newTextNode = (isLast) ? state.node.splitText(text.length) : null;
1186        bounds = textWrapperBounds(state.node, transform);
1187        state.node = newTextNode;
1188      }
1189      return bounds;
1190    }
1191
1192    function textRangeBounds(text, textNode, textOffset) {
1193      var range = doc.createRange();
1194      range.setStart(textNode, textOffset);
1195      range.setEnd(textNode, textOffset + text.length);
1196      return range.getBoundingClientRect();
1197    }
1198
1199    function textWrapperBounds(oldTextNode, transform) {
1200      var parent = oldTextNode.parentNode,
1201        wrapElement = doc.createElement('wrapper'),
1202        backupText = oldTextNode.cloneNode(true);
1203
1204      wrapElement.appendChild(oldTextNode.cloneNode(true));
1205      parent.replaceChild(wrapElement, oldTextNode);
1206
1207      var bounds = transform ? Util.OffsetBounds(wrapElement) : Util.Bounds(wrapElement);
1208      parent.replaceChild(backupText, wrapElement);
1209      return bounds;
1210    }
1211
1212    function renderText(el, textNode, stack) {
1213      var ctx = stack.ctx,
1214        color = getCSS(el, "color"),
1215        textDecoration = getCSS(el, "textDecoration"),
1216        textAlign = getCSS(el, "textAlign"),
1217        metrics,
1218        textList,
1219        state = {
1220          node: textNode,
1221          textOffset: 0
1222        };
1223
1224      if(Util.trimText(textNode.nodeValue).length > 0) {
1225        textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform"));
1226        textAlign = textAlign.replace(["-webkit-auto"], ["auto"]);
1227
1228        textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ?
1229          textNode.nodeValue.split(/(\b| )/) : textNode.nodeValue.split("");
1230
1231        metrics = setTextVariables(ctx, el, textDecoration, color);
1232
1233        if(options.chinese) {
1234          textList.forEach(function(word, index) {
1235            if(/.*[\u4E00-\u9FA5].*$/.test(word)) {
1236              word = word.split("");
1237              word.unshift(index, 1);
1238              textList.splice.apply(textList, word);
1239            }
1240          });
1241        }
1242
1243        textList.forEach(function(text, index) {
1244          var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix);
1245          if(bounds) {
1246            drawText(text, bounds.left, bounds.bottom, ctx);
1247            renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
1248          }
1249        });
1250      }
1251    }
1252
1253    function listPosition(element, val) {
1254      var boundElement = doc.createElement("boundelement"),
1255        originalType,
1256        bounds;
1257
1258      boundElement.style.display = "inline";
1259
1260      originalType = element.style.listStyleType;
1261      element.style.listStyleType = "none";
1262
1263      boundElement.appendChild(doc.createTextNode(val));
1264
1265      element.insertBefore(boundElement, element.firstChild);
1266
1267      bounds = Util.Bounds(boundElement);
1268      element.removeChild(boundElement);
1269      element.style.listStyleType = originalType;
1270      return bounds;
1271    }
1272
1273    function elementIndex(el) {
1274      var i = -1,
1275        count = 1,
1276        childs = el.parentNode.childNodes;
1277
1278      if(el.parentNode) {
1279        while(childs[++i] !== el) {
1280          if(childs[i].nodeType === 1) {
1281            count++;
1282          }
1283        }
1284        return count;
1285      } else {
1286        return -1;
1287      }
1288    }
1289
1290    function listItemText(element, type) {
1291      var currentIndex = elementIndex(element),
1292        text;
1293      switch(type) {
1294        case "decimal":
1295          text = currentIndex;
1296          break;
1297        case "decimal-leading-zero":
1298          text = (currentIndex.toString().length === 1) ? currentIndex = "0" + currentIndex.toString() : currentIndex.toString();
1299          break;
1300        case "upper-roman":
1301          text = _html2canvas.Generate.ListRoman(currentIndex);
1302          break;
1303        case "lower-roman":
1304          text = _html2canvas.Generate.ListRoman(currentIndex).toLowerCase();
1305          break;
1306        case "lower-alpha":
1307          text = _html2canvas.Generate.ListAlpha(currentIndex).toLowerCase();
1308          break;
1309        case "upper-alpha":
1310          text = _html2canvas.Generate.ListAlpha(currentIndex);
1311          break;
1312      }
1313
1314      return text + ". ";
1315    }
1316
1317    function renderListItem(element, stack, elBounds) {
1318      var x,
1319        text,
1320        ctx = stack.ctx,
1321        type = getCSS(element, "listStyleType"),
1322        listBounds;
1323
1324      if(/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)) {
1325        text = listItemText(element, type);
1326        listBounds = listPosition(element, text);
1327        setTextVariables(ctx, element, "none", getCSS(element, "color"));
1328
1329        if(getCSS(element, "listStylePosition") === "inside") {
1330          ctx.setVariable("textAlign", "left");
1331          x = elBounds.left;
1332        } else {
1333          return;
1334        }
1335
1336        drawText(text, x, listBounds.bottom, ctx);
1337      }
1338    }
1339
1340    function loadImage(src) {
1341      var img = images[src];
1342      return(img && img.succeeded === true) ? img.img : false;
1343    }
1344
1345    function clipBounds(src, dst) {
1346      var x = Math.max(src.left, dst.left),
1347        y = Math.max(src.top, dst.top),
1348        x2 = Math.min((src.left + src.width), (dst.left + dst.width)),
1349        y2 = Math.min((src.top + src.height), (dst.top + dst.height));
1350
1351      return {
1352        left: x,
1353        top: y,
1354        width: x2 - x,
1355        height: y2 - y
1356      };
1357    }
1358
1359    function setZ(element, stack, parentStack) {
1360      var newContext,
1361        isPositioned = stack.cssPosition !== 'static',
1362        zIndex = isPositioned ? getCSS(element, 'zIndex') : 'auto',
1363        opacity = getCSS(element, 'opacity'),
1364        isFloated = getCSS(element, 'cssFloat') !== 'none';
1365
1366      // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
1367      // When a new stacking context should be created:
1368      // the root element (HTML),
1369      // positioned (absolutely or relatively) with a z-index value other than "auto",
1370      // elements with an opacity value less than 1. (See the specification for opacity),
1371      // on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post)
1372
1373      stack.zIndex = newContext = h2czContext(zIndex);
1374      newContext.isPositioned = isPositioned;
1375      newContext.isFloated = isFloated;
1376      newContext.opacity = opacity;
1377      newContext.ownStacking = (zIndex !== 'auto' || opacity < 1);
1378
1379      if(parentStack) {
1380        parentStack.zIndex.children.push(stack);
1381      }
1382    }
1383
1384    function renderImage(ctx, element, image, bounds, borders) {
1385
1386      var paddingLeft = getCSSInt(element, 'paddingLeft'),
1387        paddingTop = getCSSInt(element, 'paddingTop'),
1388        paddingRight = getCSSInt(element, 'paddingRight'),
1389        paddingBottom = getCSSInt(element, 'paddingBottom');
1390
1391      drawImage(
1392        ctx,
1393        image,
1394        0, //sx
1395        0, //sy
1396        image.width, //sw
1397        image.height, //sh
1398        bounds.left + paddingLeft + borders[3].width, //dx
1399        bounds.top + paddingTop + borders[0].width, // dy
1400        bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), //dw
1401        bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh
1402      );
1403    }
1404
1405    function getBorderData(element) {
1406      return ["Top", "Right", "Bottom", "Left"].map(function(side) {
1407        return {
1408          width: getCSSInt(element, 'border' + side + 'Width'),
1409          color: getCSS(element, 'border' + side + 'Color')
1410        };
1411      });
1412    }
1413
1414    function getBorderRadiusData(element) {
1415      return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
1416        return getCSS(element, 'border' + side + 'Radius');
1417      });
1418    }
1419
1420    var getCurvePoints = (function(kappa) {
1421
1422      return function(x, y, r1, r2) {
1423        var ox = (r1) * kappa, // control point offset horizontal
1424          oy = (r2) * kappa, // control point offset vertical
1425          xm = x + r1, // x-middle
1426          ym = y + r2; // y-middle
1427        return {
1428          topLeft: bezierCurve({
1429            x: x,
1430            y: ym
1431          }, {
1432            x: x,
1433            y: ym - oy
1434          }, {
1435            x: xm - ox,
1436            y: y
1437          }, {
1438            x: xm,
1439            y: y
1440          }),
1441          topRight: bezierCurve({
1442            x: x,
1443            y: y
1444          }, {
1445            x: x + ox,
1446            y: y
1447          }, {
1448            x: xm,
1449            y: ym - oy
1450          }, {
1451            x: xm,
1452            y: ym
1453          }),
1454          bottomRight: bezierCurve({
1455            x: xm,
1456            y: y
1457          }, {
1458            x: xm,
1459            y: y + oy
1460          }, {
1461            x: x + ox,
1462            y: ym
1463          }, {
1464            x: x,
1465            y: ym
1466          }),
1467          bottomLeft: bezierCurve({
1468            x: xm,
1469            y: ym
1470          }, {
1471            x: xm - ox,
1472            y: ym
1473          }, {
1474            x: x,
1475            y: y + oy
1476          }, {
1477            x: x,
1478            y: y
1479          })
1480        };
1481      };
1482    })(4 * ((Math.sqrt(2) - 1) / 3));
1483
1484    function bezierCurve(start, startControl, endControl, end) {
1485
1486      var lerp = function(a, b, t) {
1487        return {
1488          x: a.x + (b.x - a.x) * t,
1489          y: a.y + (b.y - a.y) * t
1490        };
1491      };
1492
1493      return {
1494        start: start,
1495        startControl: startControl,
1496        endControl: endControl,
1497        end: end,
1498        subdivide: function(t) {
1499          var ab = lerp(start, startControl, t),
1500            bc = lerp(startControl, endControl, t),
1501            cd = lerp(endControl, end, t),
1502            abbc = lerp(ab, bc, t),
1503            bccd = lerp(bc, cd, t),
1504            dest = lerp(abbc, bccd, t);
1505          return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
1506        },
1507        curveTo: function(borderArgs) {
1508          borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
1509        },
1510        curveToReversed: function(borderArgs) {
1511          borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
1512        }
1513      };
1514    }
1515
1516    function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
1517      if(radius1[0] > 0 || radius1[1] > 0) {
1518        borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
1519        corner1[0].curveTo(borderArgs);
1520        corner1[1].curveTo(borderArgs);
1521      } else {
1522        borderArgs.push(["line", x, y]);
1523      }
1524
1525      if(radius2[0] > 0 || radius2[1] > 0) {
1526        borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
1527      }
1528    }
1529
1530    function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
1531      var borderArgs = [];
1532
1533      if(radius1[0] > 0 || radius1[1] > 0) {
1534        borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
1535        outer1[1].curveTo(borderArgs);
1536      } else {
1537        borderArgs.push(["line", borderData.c1[0], borderData.c1[1]]);
1538      }
1539
1540      if(radius2[0] > 0 || radius2[1] > 0) {
1541        borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
1542        outer2[0].curveTo(borderArgs);
1543        borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
1544        inner2[0].curveToReversed(borderArgs);
1545      } else {
1546        borderArgs.push(["line", borderData.c2[0], borderData.c2[1]]);
1547        borderArgs.push(["line", borderData.c3[0], borderData.c3[1]]);
1548      }
1549
1550      if(radius1[0] > 0 || radius1[1] > 0) {
1551        borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
1552        inner1[1].curveToReversed(borderArgs);
1553      } else {
1554        borderArgs.push(["line", borderData.c4[0], borderData.c4[1]]);
1555      }
1556
1557      return borderArgs;
1558    }
1559
1560    function calculateCurvePoints(bounds, borderRadius, borders) {
1561
1562      var x = bounds.left,
1563        y = bounds.top,
1564        width = bounds.width,
1565        height = bounds.height,
1566
1567        tlh = borderRadius[0][0],
1568        tlv = borderRadius[0][1],
1569        trh = borderRadius[1][0],
1570        trv = borderRadius[1][1],
1571        brh = borderRadius[2][0],
1572        brv = borderRadius[2][1],
1573        blh = borderRadius[3][0],
1574        blv = borderRadius[3][1],
1575
1576        topWidth = width - trh,
1577        rightHeight = height - brv,
1578        bottomWidth = width - brh,
1579        leftHeight = height - blv;
1580
1581      return {
1582        topLeftOuter: getCurvePoints(
1583          x,
1584          y,
1585          tlh,
1586          tlv
1587        ).topLeft.subdivide(0.5),
1588
1589        topLeftInner: getCurvePoints(
1590          x + borders[3].width,
1591          y + borders[0].width,
1592          Math.max(0, tlh - borders[3].width),
1593          Math.max(0, tlv - borders[0].width)
1594        ).topLeft.subdivide(0.5),
1595
1596        topRightOuter: getCurvePoints(
1597          x + topWidth,
1598          y,
1599          trh,
1600          trv
1601        ).topRight.subdivide(0.5),
1602
1603        topRightInner: getCurvePoints(
1604          x + Math.min(topWidth, width + borders[3].width),
1605          y + borders[0].width, (topWidth > width + borders[3].width) ? 0 : trh - borders[3].width,
1606          trv - borders[0].width
1607        ).topRight.subdivide(0.5),
1608
1609        bottomRightOuter: getCurvePoints(
1610          x + bottomWidth,
1611          y + rightHeight,
1612          brh,
1613          brv
1614        ).bottomRight.subdivide(0.5),
1615
1616        bottomRightInner: getCurvePoints(
1617          x + Math.min(bottomWidth, width + borders[3].width),
1618          y + Math.min(rightHeight, height + borders[0].width),
1619          Math.max(0, brh - borders[1].width),
1620          Math.max(0, brv - borders[2].width)
1621        ).bottomRight.subdivide(0.5),
1622
1623        bottomLeftOuter: getCurvePoints(
1624          x,
1625          y + leftHeight,
1626          blh,
1627          blv
1628        ).bottomLeft.subdivide(0.5),
1629
1630        bottomLeftInner: getCurvePoints(
1631          x + borders[3].width,
1632          y + leftHeight,
1633          Math.max(0, blh - borders[3].width),
1634          Math.max(0, blv - borders[2].width)
1635        ).bottomLeft.subdivide(0.5)
1636      };
1637    }
1638
1639    function getBorderClip(element, borderPoints, borders, radius, bounds) {
1640      var backgroundClip = getCSS(element, 'backgroundClip'),
1641        borderArgs = [];
1642
1643      switch(backgroundClip) {
1644        case "content-box":
1645        case "padding-box":
1646          parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width,
1647            bounds.top + borders[0].width);
1648          parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width -
1649            borders[1].width, bounds.top + borders[0].width);
1650          parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width -
1651            borders[1].width, bounds.top + bounds.height - borders[2].width);
1652          parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width,
1653            bounds.top + bounds.height - borders[2].width);
1654          break;
1655
1656        default:
1657          parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
1658          parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width,
1659            bounds.top);
1660          parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width,
1661            bounds.top + bounds.height);
1662          parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds
1663            .height);
1664          break;
1665      }
1666
1667      return borderArgs;
1668    }
1669
1670    function parseBorders(element, bounds, borders) {
1671      var x = bounds.left,
1672        y = bounds.top,
1673        width = bounds.width,
1674        height = bounds.height,
1675        borderSide,
1676        bx,
1677        by,
1678        bw,
1679        bh,
1680        borderArgs,
1681        // http://www.w3.org/TR/css3-background/#the-border-radius
1682        borderRadius = getBorderRadiusData(element),
1683        borderPoints = calculateCurvePoints(bounds, borderRadius, borders),
1684        borderData = {
1685          clip: getBorderClip(element, borderPoints, borders, borderRadius, bounds),
1686          borders: []
1687        };
1688
1689      for(borderSide = 0; borderSide < 4; borderSide++) {
1690
1691        if(borders[borderSide].width > 0) {
1692          bx = x;
1693          by = y;
1694          bw = width;
1695          bh = height - (borders[2].width);
1696
1697          switch(borderSide) {
1698            case 0:
1699              // top border
1700              bh = borders[0].width;
1701
1702              borderArgs = drawSide({
1703                  c1: [bx, by],
1704                  c2: [bx + bw, by],
1705                  c3: [bx + bw - borders[1].width, by + bh],
1706                  c4: [bx + borders[3].width, by + bh]
1707                }, borderRadius[0], borderRadius[1],
1708                borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
1709              break;
1710            case 1:
1711              // right border
1712              bx = x + width - (borders[1].width);
1713              bw = borders[1].width;
1714
1715              borderArgs = drawSide({
1716                  c1: [bx + bw, by],
1717                  c2: [bx + bw, by + bh + borders[2].width],
1718                  c3: [bx, by + bh],
1719                  c4: [bx, by + borders[0].width]
1720                }, borderRadius[1], borderRadius[2],
1721                borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
1722              break;
1723            case 2:
1724              // bottom border
1725              by = (by + height) - (borders[2].width);
1726              bh = borders[2].width;
1727
1728              borderArgs = drawSide({
1729                  c1: [bx + bw, by + bh],
1730                  c2: [bx, by + bh],
1731                  c3: [bx + borders[3].width, by],
1732                  c4: [bx + bw - borders[3].width, by]
1733                }, borderRadius[2], borderRadius[3],
1734                borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
1735              break;
1736            case 3:
1737              // left border
1738              bw = borders[3].width;
1739
1740              borderArgs = drawSide({
1741                  c1: [bx, by + bh + borders[2].width],
1742                  c2: [bx, by],
1743                  c3: [bx + bw, by + borders[0].width],
1744                  c4: [bx + bw, by + bh]
1745                }, borderRadius[3], borderRadius[0],
1746                borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
1747              break;
1748          }
1749
1750          borderData.borders.push({
1751            args: borderArgs,
1752            color: borders[borderSide].color
1753          });
1754
1755        }
1756      }
1757
1758      return borderData;
1759    }
1760
1761    function createShape(ctx, args) {
1762      var shape = ctx.drawShape();
1763      args.forEach(function(border, index) {
1764        shape[(index === 0) ? "moveTo" : border[0] + "To"].apply(null, border.slice(1));
1765      });
1766      return shape;
1767    }
1768
1769    function renderBorders(ctx, borderArgs, color) {
1770      if(color !== "transparent") {
1771        ctx.setVariable("fillStyle", color);
1772        createShape(ctx, borderArgs);
1773        ctx.fill();
1774        numDraws += 1;
1775      }
1776    }
1777
1778    function renderFormValue(el, bounds, stack) {
1779
1780      var valueWrap = doc.createElement('valuewrap'),
1781        cssPropertyArray = ['lineHeight', 'textAlign', 'fontFamily', 'color', 'fontSize', 'paddingLeft', 'paddingTop', 'width', 'height',
1782          'border', 'borderLeftWidth', 'borderTopWidth'
1783        ],
1784        textValue,
1785        textNode;
1786
1787      cssPropertyArray.forEach(function(property) {
1788        try {
1789          valueWrap.style[property] = getCSS(el, property);
1790        } catch(e) {
1791          // Older IE has issues with "border"
1792          Util.log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
1793        }
1794      });
1795
1796      valueWrap.style.borderColor = "black";
1797      valueWrap.style.borderStyle = "solid";
1798      valueWrap.style.display = "block";
1799      valueWrap.style.position = "absolute";
1800
1801      if(/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName === "SELECT") {
1802        valueWrap.style.lineHeight = getCSS(el, "height");
1803      }
1804
1805      valueWrap.style.top = bounds.top + "px";
1806      valueWrap.style.left = bounds.left + "px";
1807
1808      textValue = (el.nodeName === "SELECT") ? (el.options[el.selectedIndex] || 0).text : el.value;
1809      if(!textValue) {
1810        textValue = el.placeholder;
1811      }
1812
1813      textNode = doc.createTextNode(textValue);
1814
1815      valueWrap.appendChild(textNode);
1816      body.appendChild(valueWrap);
1817
1818      renderText(el, textNode, stack);
1819      body.removeChild(valueWrap);
1820    }
1821
1822    function drawImage(ctx) {
1823      ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 1));
1824      numDraws += 1;
1825    }
1826
1827    function getPseudoElement(el, which) {
1828      var elStyle = window.getComputedStyle(el, which);
1829      if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") {
1830        return;
1831      }
1832      var content = elStyle.content + '',
1833        first = content.substr(0, 1);
1834      //strips quotes
1835      if(first === content.substr(content.length - 1) && first.match(/'|"/)) {
1836        content = content.substr(1, content.length - 2);
1837      }
1838
1839      var isImage = content.substr(0, 3) === 'url',
1840        elps = document.createElement(isImage ? 'img' : 'span');
1841
1842      elps.className = pseudoHide + "-before " + pseudoHide + "-after";
1843
1844      Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
1845        // Prevent assigning of read only CSS Rules, ex. length, parentRule
1846        try {
1847          elps.style[prop] = elStyle[prop];
1848        } catch(e) {
1849          Util.log(['Tried to assign readonly property ', prop, 'Error:', e]);
1850        }
1851      });
1852
1853      if(isImage) {
1854        elps.src = Util.parseBackgroundImage(content)[0].args[0];
1855      } else {
1856        elps.innerHTML = content;
1857      }
1858      return elps;
1859    }
1860
1861    function indexedProperty(property) {
1862      return(isNaN(window.parseInt(property, 10)));
1863    }
1864
1865    function injectPseudoElements(el, stack) {
1866      var before = getPseudoElement(el, ':before'),
1867        after = getPseudoElement(el, ':after');
1868      if(!before && !after) {
1869        return;
1870      }
1871
1872      if(before) {
1873        el.className += " " + pseudoHide + "-before";
1874        el.parentNode.insertBefore(before, el);
1875        parseElement(before, stack, true);
1876        el.parentNode.removeChild(before);
1877        el.className = el.className.replace(pseudoHide + "-before", "").trim();
1878      }
1879
1880      if(after) {
1881        el.className += " " + pseudoHide + "-after";
1882        el.appendChild(after);
1883        parseElement(after, stack, true);
1884        el.removeChild(after);
1885        el.className = el.className.replace(pseudoHide + "-after", "").trim();
1886      }
1887
1888    }
1889
1890    function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
1891      var offsetX = Math.round(bounds.left + backgroundPosition.left),
1892        offsetY = Math.round(bounds.top + backgroundPosition.top);
1893
1894      ctx.createPattern(image);
1895      ctx.translate(offsetX, offsetY);
1896      ctx.fill();
1897      ctx.translate(-offsetX, -offsetY);
1898    }
1899
1900    function backgroundRepeatShape(ctx, image, backgroundPosition, bounds, left, top, width, height) {
1901      var args = [];
1902      args.push(["line", Math.round(left), Math.round(top)]);
1903      args.push(["line", Math.round(left + width), Math.round(top)]);
1904      args.push(["line", Math.round(left + width), Math.round(height + top)]);
1905      args.push(["line", Math.round(left), Math.round(height + top)]);
1906      createShape(ctx, args);
1907      ctx.save();
1908      ctx.clip();
1909      renderBackgroundRepeat(ctx, image, backgroundPosition, bounds);
1910      ctx.restore();
1911    }
1912
1913    function renderBackgroundColor(ctx, backgroundBounds, bgcolor) {
1914      renderRect(
1915        ctx,
1916        backgroundBounds.left,
1917        backgroundBounds.top,
1918        backgroundBounds.width,
1919        backgroundBounds.height,
1920        bgcolor
1921      );
1922    }
1923
1924    function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) {
1925      var backgroundSize = Util.BackgroundSize(el, bounds, image, imageIndex),
1926        backgroundPosition = Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize),
1927        backgroundRepeat = getCSS(el, "backgroundRepeat").split(",").map(Util.trimText);
1928
1929      image = resizeImage(image, backgroundSize);
1930
1931      backgroundRepeat = backgroundRepeat[imageIndex] || backgroundRepeat[0];
1932
1933      switch(backgroundRepeat) {
1934        case "repeat-x":
1935          backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
1936            bounds.left, bounds.top + backgroundPosition.top, 99999, image.height);
1937          break;
1938
1939        case "repeat-y":
1940          backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
1941            bounds.left + backgroundPosition.left, bounds.top, image.width, 99999);
1942          break;
1943
1944        case "no-repeat":
1945          backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
1946            bounds.left + backgroundPosition.left, bounds.top + backgroundPosition.top, image.width, image.height);
1947          break;
1948
1949        default:
1950          renderBackgroundRepeat(ctx, image, backgroundPosition, {
1951            top: bounds.top,
1952            left: bounds.left,
1953            width: image.width,
1954            height: image.height
1955          });
1956          break;
1957      }
1958    }
1959
1960    function renderBackgroundImage(element, bounds, ctx) {
1961      var backgroundImage = getCSS(element, "backgroundImage"),
1962        backgroundImages = Util.parseBackgroundImage(backgroundImage),
1963        image,
1964        imageIndex = backgroundImages.length;
1965
1966      while(imageIndex--) {
1967        backgroundImage = backgroundImages[imageIndex];
1968
1969        if(!backgroundImage.args || backgroundImage.args.length === 0) {
1970          continue;
1971        }
1972
1973        var key = backgroundImage.method === 'url' ?
1974          backgroundImage.args[0] :
1975          backgroundImage.value;
1976
1977        image = loadImage(key);
1978
1979        // TODO add support for background-origin
1980        if(image) {
1981          renderBackgroundRepeating(element, bounds, ctx, image, imageIndex);
1982        } else {
1983          Util.log("html2canvas: Error loading background:", backgroundImage);
1984        }
1985      }
1986    }
1987
1988    function resizeImage(image, bounds) {
1989      if(image.width === bounds.width && image.height === bounds.height) {
1990        return image;
1991      }
1992
1993      var ctx, canvas = doc.createElement('canvas');
1994      canvas.width = bounds.width;
1995      canvas.height = bounds.height;
1996      ctx = canvas.getContext("2d");
1997      drawImage(ctx, image, 0, 0, image.width, image.height, 0, 0, bounds.width, bounds.height);
1998      return canvas;
1999    }
2000
2001    function setOpacity(ctx, element, parentStack) {
2002      return ctx.setVariable("globalAlpha", getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1));
2003    }
2004
2005    function removePx(str) {
2006      return str.replace("px", "");
2007    }
2008
2009    var transformRegExp = /(matrix)\((.+)\)/;
2010
2011    function getTransform(element, parentStack) {
2012      var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element,
2013        "-ms-transform") || getCSS(element, "-o-transform");
2014      var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element,
2015        "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px";
2016
2017      transformOrigin = transformOrigin.split(" ").map(removePx).map(Util.asFloat);
2018
2019      var matrix;
2020      if(transform && transform !== "none") {
2021        var match = transform.match(transformRegExp);
2022        if(match) {
2023          switch(match[1]) {
2024            case "matrix":
2025              matrix = match[2].split(",").map(Util.trimText).map(Util.asFloat);
2026              break;
2027          }
2028        }
2029      }
2030
2031      return {
2032        origin: transformOrigin,
2033        matrix: matrix
2034      };
2035    }
2036
2037    function createStack(element, parentStack, bounds, transform) {
2038      var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width, (!parentStack) ? documentHeight() : bounds.height),
2039        stack = {
2040          ctx: ctx,
2041          opacity: setOpacity(ctx, element, parentStack),
2042          cssPosition: getCSS(element, "position"),
2043          borders: getBorderData(element),
2044          transform: transform,
2045          clip: (parentStack && parentStack.clip) ? Util.Extend({}, parentStack.clip) : null
2046        };
2047
2048      setZ(element, stack, parentStack);
2049
2050      // TODO correct overflow for absolute content residing under a static position
2051      if(options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) ===
2052        false) {
2053        stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds;
2054      }
2055
2056      return stack;
2057    }
2058
2059    function getBackgroundBounds(borders, bounds, clip) {
2060      var backgroundBounds = {
2061        left: bounds.left + borders[3].width,
2062        top: bounds.top + borders[0].width,
2063        width: bounds.width - (borders[1].width + borders[3].width),
2064        height: bounds.height - (borders[0].width + borders[2].width)
2065      };
2066
2067      if(clip) {
2068        backgroundBounds = clipBounds(backgroundBounds, clip);
2069      }
2070
2071      return backgroundBounds;
2072    }
2073
2074    function getBounds(element, transform) {
2075      var bounds = (transform.matrix) ? Util.OffsetBounds(element) : Util.Bounds(element);
2076      transform.origin[0] += bounds.left;
2077      transform.origin[1] += bounds.top;
2078      return bounds;
2079    }
2080
2081    function renderElement(element, parentStack, pseudoElement, ignoreBackgroun
2081d) {
2082      var transform = getTransform(element, parentStack),
2083        bounds = getBounds(element, transform),
2084        image,
2085        stack = createStack(element, parentStack, bounds, transform),
2086        borders = stack.borders,
2087        ctx = stack.ctx,
2088        backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip),
2089        borderData = parseBorders(element, bounds, borders),
2090        backgroundColor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor");
2091
2092      createShape(ctx, borderData.clip);
2093
2094      ctx.save();
2095      ctx.clip();
2096
2097      if(backgroundBounds.height > 0 && backgroundBounds.width > 0 && !ignoreBackground) {
2098        renderBackgroundColor(ctx, bounds, backgroundColor);
2099        renderBackgroundImage(element, backgroundBounds, ctx);
2100      } else if(ignoreBackground) {
2101        stack.backgroundColor = backgroundColor;
2102      }
2103
2104      ctx.restore();
2105
2106      borderData.borders.forEach(function(border) {
2107        renderBorders(ctx, border.args, border.color);
2108      });
2109
2110      if(!pseudoElement) {
2111        injectPseudoElements(element, stack);
2112      }
2113
2114      switch(element.nodeName) {
2115        case "IMG":
2116          if((image = loadImage(element.getAttribute('src')))) {
2117            renderImage(ctx, element, image, bounds, borders);
2118          } else {
2119            Util.log("html2canvas: Error loading <img>:" + element.getAttribute('src'));
2120          }
2121          break;
2122        case "INPUT":
2123          // TODO add all relevant type's, i.e. HTML5 new stuff
2124          // todo add support for placeholder attribute for browsers which support it
2125          if(/^(text|url|email|submit|button|reset)$/.test(element.type) && (element.value || element.placeholder || "").length > 0) {
2126            renderFormValue(element, bounds, stack);
2127          }
2128          break;
2129        case "TEXTAREA":
2130          if((element.value || element.placeholder || "").length > 0) {
2131            renderFormValue(element, bounds, stack);
2132          }
2133          break;
2134        case "SELECT":
2135          if((element.options || element.placeholder || "").length > 0) {
2136            renderFormValue(element, bounds, stack);
2137          }
2138          break;
2139        case "LI":
2140          renderListItem(element, stack, backgroundBounds);
2141          break;
2142        case "CANVAS":
2143          renderImage(ctx, element, element, bounds, borders);
2144          break;
2145      }
2146
2147      return stack;
2148    }
2149
2150    function isElementVisible(element) {
2151      return(getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute(
2152        "data-html2canvas-ignore"));
2153    }
2154
2155    function parseElement(element, stack, pseudoElement) {
2156      if(isElementVisible(element)) {
2157        stack = renderElement(element, stack, pseudoElement, false) || stack;
2158        if(!ignoreElementsRegExp.test(element.nodeName)) {
2159          parseChildren(element, stack, pseudoElement);
2160        }
2161      }
2162    }
2163
2164    function parseChildren(element, stack, pseudoElement) {
2165      Util.Children(element).forEach(function(node) {
2166        if(node.nodeType === node.ELEMENT_NODE) {
2167          parseElement(node, stack, pseudoElement);
2168        } else if(node.nodeType === node.TEXT_NODE) {
2169          renderText(element, node, stack);
2170        }
2171      });
2172    }
2173
2174    function init() {
2175      var background = getCSS(document.documentElement, "backgroundColor"),
2176        transparentBackground = (Util.isTransparent(background) && element === document.body),
2177        stack = renderElement(element, null, false, transparentBackground);
2178      parseChildren(element, stack);
2179
2180      if(transparentBackground) {
2181        background = stack.backgroundColor;
2182      }
2183
2184      body.removeChild(hidePseudoElements);
2185      return {
2186        backgroundColor: background,
2187        stack: stack
2188      };
2189    }
2190
2191    return init();
2192  };
2193
2194  function h2czContext(zindex) {
2195    return {
2196      zindex: zindex,
2197      children: []
2198    };
2199  }
2200
2201  _html2canvas.Preload = function(options) {
2202
2203    var images = {
2204        numLoaded: 0, // also failed are counted here
2205        numFailed: 0,
2206        numTotal: 0,
2207        cleanupDone: false
2208      },
2209      pageOrigin,
2210      Util = _html2canvas.Util,
2211      methods,
2212      i,
2213      count = 0,
2214      element = options.elements[0] || document.body,
2215      doc = element.ownerDocument,
2216      domImages = element.getElementsByTagName('img'), // Fetch images of the present element only
2217      imgLen = domImages.length,
2218      link = doc.createElement("a"),
2219      supportCORS = (function(img) {
2220        return(img.crossOrigin !== undefined);
2221      })(new Image()),
2222      timeoutTimer;
2223
2224    link.href = window.location.href;
2225    pageOrigin = link.protocol + link.host;
2226
2227    function isSameOrigin(url) {
2228      link.href = url;
2229      link.href = link.href; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/
2230      var origin = link.protocol + link.host;
2231      return(origin === pageOrigin);
2232    }
2233
2234    function start() {
2235      Util.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")");
2236      if(!images.firstRun && images.numLoaded >= images.numTotal) {
2237        Util.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")");
2238
2239        if(typeof options.complete === "function") {
2240          options.complete(images);
2241        }
2242
2243      }
2244    }
2245
2246    // TODO modify proxy to serve images with CORS enabled, where available
2247    function proxyGetImage(url, img, imageObj) {
2248      var callback_name,
2249        scriptUrl = options.proxy,
2250        script;
2251
2252      link.href = url;
2253      url = link.href; // work around for pages with base href="" set - WARNING: this may change the url
2254
2255      if(scriptUrl.indexOf("?") > -1) {
2256        scriptUrl += "&";
2257      } else {
2258        scriptUrl += "?";
2259      }
2260      scriptUrl += 'url=' + encodeURIComponent(url);
2261      if(options.proxyReturnsImg) {
2262        setImageLoadHandlers(img, imageObj);
2263        img.src = scriptUrl;
2264      } else {
2265        callback_name = 'html2canvas_' + (count++);
2266        imageObj.callbackname = callback_name;
2267        scriptUrl += '&callback=' + callback_name;
2268        script = doc.createElement("script");
2269
2270        window[callback_name] = function(a) {
2271          if(a.substring(0, 6) === "error:") {
2272            imageObj.succeeded = false;
2273            images.numLoaded++;
2274            images.numFailed++;
2275            start();
2276          } else {
2277            setImageLoadHandlers(img, imageObj);
2278            img.src = a;
2279          }
2280          window[callback_name] = undefined; // to work with IE<9  // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
2281          try {
2282            delete window[callback_name]; // for all browser that support this
2283          } catch(ex) {}
2284          script.parentNode.removeChild(script);
2285          script = null;
2286          delete imageObj.script;
2287          delete imageObj.callbackname;
2288        };
2289
2290        script.setAttribute("type", "text/javascript");
2291        script.setAttribute("src", scriptUrl);
2292        imageObj.script = script;
2293        window.document.body.appendChild(script);
2294      }
2295
2296    }
2297
2298    function loadPseudoElement(element, type) {
2299      var style = window.getComputedStyle(element, type),
2300        content = style.content;
2301      if(content.substr(0, 3) === 'url') {
2302        methods.loadImage(_html2canvas.Util.parseBackgroundImage(content)[0].args[0]);
2303      }
2304      loadBackgroundImages(style.backgroundImage, element);
2305    }
2306
2307    function loadPseudoElementImages(element) {
2308      loadPseudoElement(element, ":before");
2309      loadPseudoElement(element, ":after");
2310    }
2311
2312    function loadGradientImage(backgroundImage, bounds) {
2313      var img = _html2canvas.Generate.Gradient(backgroundImage, bounds);
2314
2315      if(img !== undefined) {
2316        images[backgroundImage] = {
2317          img: img,
2318          succeeded: true
2319        };
2320        images.numTotal++;
2321        images.numLoaded++;
2322        start();
2323      }
2324    }
2325
2326    function invalidBackgrounds(background_image) {
2327      return(background_image && background_image.method && background_image.args && background_image.args.length > 0);
2328    }
2329
2330    function loadBackgroundImages(background_image, el) {
2331      var bounds;
2332
2333      _html2canvas.Util.parseBackgroundImage(background_image).filter(invalidBackgrounds).forEach(function(background_image) {
2334        if(background_image.method === 'url') {
2335          methods.loadImage(background_image.args[0]);
2336        } else if(background_image.method.match(/\-?gradient$/)) {
2337          if(bounds === undefined) {
2338            bounds = _html2canvas.Util.Bounds(el);
2339          }
2340          loadGradientImage(background_image.value, bounds);
2341        }
2342      });
2343    }
2344
2345    function getImages(el) {
2346      var elNodeType = false;
2347
2348      // Firefox fails with permission denied on pages with iframes
2349      try {
2350        Util.Children(el).forEach(getImages);
2351      } catch(e) {}
2352
2353      try {
2354        elNodeType = el.nodeType;
2355      } catch(ex) {
2356        elNodeType = false;
2357        Util.log("html2canvas: failed to access some element's nodeType - Exception: " + ex.message);
2358      }
2359
2360      if(elNodeType === 1 || elNodeType === undefined) {
2361        loadPseudoElementImages(el);
2362        try {
2363          loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el);
2364        } catch(e) {
2365          Util.log("html2canvas: failed to get background-image - Exception: " + e.message);
2366        }
2367        loadBackgroundImages(el);
2368      }
2369    }
2370
2371    function setImageLoadHandlers(img, imageObj) {
2372      img.onload = function() {
2373        if(imageObj.timer !== undefined) {
2374          // CORS succeeded
2375          window.clearTimeout(imageObj.timer);
2376        }
2377
2378        images.numLoaded++;
2379        imageObj.succeeded = true;
2380        img.onerror = img.onload = null;
2381        start();
2382      };
2383      img.onerror = function() {
2384        if(img.crossOrigin === "anonymous") {
2385          // CORS failed
2386          window.clearTimeout(imageObj.timer);
2387
2388          // let's try with proxy instead
2389          if(options.proxy) {
2390            var src = img.src;
2391            img = new Image();
2392            imageObj.img = img;
2393            img.src = src;
2394
2395            proxyGetImage(img.src, img, imageObj);
2396            return;
2397          }
2398        }
2399
2400        images.numLoaded++;
2401        images.numFailed++;
2402        imageObj.succeeded = false;
2403        img.onerror = img.onload = null;
2404        start();
2405      };
2406    }
2407
2408    methods = {
2409      loadImage: function(src) {
2410        var img, imageObj;
2411        if(src && images[src] === undefined) {
2412          img = new Image();
2413          if(src.match(/data:image\/.*;base64,/i)) {
2414            img.src = src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, '');
2415            imageObj = images[src] = {
2416              img: img
2417            };
2418            images.numTotal++;
2419            setImageLoadHandlers(img, imageObj);
2420          } else if(isSameOrigin(src) || options.allowTaint === true) {
2421            imageObj = images[src] = {
2422              img: img
2423            };
2424            images.numTotal++;
2425            setImageLoadHandlers(img, imageObj);
2426            img.src = src;
2427          } else if(supportCORS && !options.allowTaint && options.useCORS) {
2428            // attempt to load with CORS
2429
2430            img.crossOrigin = "anonymous";
2431            imageObj = images[src] = {
2432              img: img
2433            };
2434            images.numTotal++;
2435            setImageLoadHandlers(img, imageObj);
2436            img.src = src;
2437          } else if(options.proxy) {
2438            imageObj = images[src] = {
2439              img: img
2440            };
2441            images.numTotal++;
2442            proxyGetImage(src, img, imageObj);
2443          }
2444        }
2445
2446      },
2447      cleanupDOM: function(cause) {
2448        var img, src;
2449        if(!images.cleanupDone) {
2450          if(cause && typeof cause === "string") {
2451            Util.log("html2canvas: Cleanup because: " + cause);
2452          } else {
2453            Util.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
2454          }
2455
2456          for(src in images) {
2457            if(images.hasOwnProperty(src)) {
2458              img = images[src];
2459              if(typeof img === "object" && img.callbackname && img.succeeded === undefined) {
2460                // cancel proxy image request
2461                window[img.callbackname] = undefined; // to work with IE<9  // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
2462                try {
2463                  delete window[img.callbackname]; // for all browser that support this
2464                } catch(ex) {}
2465                if(img.script && img.script.parentNode) {
2466                  img.script.setAttribute("src", "about:blank"); // try to cancel running request
2467                  img.script.parentNode.removeChild(img.script);
2468                }
2469                images.numLoaded++;
2470                images.numFailed++;
2471                Util.log("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal);
2472              }
2473            }
2474          }
2475
2476          // cancel any pending requests
2477          if(window.stop !== undefined) {
2478            window.stop();
2479          } else if(document.execCommand !== undefined) {
2480            document.execCommand("Stop", false);
2481          }
2482          if(document.close !== undefined) {
2483            document.close();
2484          }
2485          images.cleanupDone = true;
2486          if(!(cause && typeof cause === "string")) {
2487            start();
2488          }
2489        }
2490      },
2491
2492      renderingDone: function() {
2493        if(timeoutTimer) {
2494          window.clearTimeout(timeoutTimer);
2495        }
2496      }
2497    };
2498
2499    if(options.timeout > 0) {
2500      timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout);
2501    }
2502
2503    Util.log('html2canvas: Preload starts: finding background-images');
2504    images.firstRun = true;
2505
2506    getImages(element);
2507
2508    Util.log('html2canvas: Preload: Finding images');
2509    // load <img> images
2510    for(i = 0; i < imgLen; i += 1) {
2511      methods.loadImage(domImages[i].getAttribute("src"));
2512    }
2513
2514    images.firstRun = false;
2515    Util.log('html2canvas: Preload: Done.');
2516    if(images.numTotal === images.numLoaded) {
2517      start();
2518    }
2519
2520    return methods;
2521  };
2522
2523  _html2canvas.Renderer = function(parseQueue, options) {
2524
2525    // http://www.w3.org/TR/CSS21/zindex.html
2526    function createRenderQueue(parseQueue) {
2527      var queue = [],
2528        rootContext;
2529
2530      rootContext = (function buildStackingContext(rootNode) {
2531        var rootContext = {};
2532
2533        function insert(context, node, specialParent) {
2534          var zi = (node.zIndex.zindex === 'auto') ? 0 : Number(node.zIndex.zindex),
2535            contextForChildren = context, // the stacking context for children
2536            isPositioned = node.zIndex.isPositioned,
2537            isFloated = node.zIndex.isFloated,
2538            stub = {
2539              node: node
2540            },
2541            childrenDest = specialParent; // where children without z-index should be pushed into
2542
2543          if(node.zIndex.ownStacking) {
2544            // '!' comes before numbers in sorted array
2545            contextForChildren = stub.context = {
2546              '!': [{
2547                node: node,
2548                children: []
2549              }]
2550            };
2551            childrenDest = undefined;
2552          } else if(isPositioned || isFloated) {
2553            childrenDest = stub.children = [];
2554          }
2555
2556          if(zi === 0 && specialParent) {
2557            specialParent.push(stub);
2558          } else {
2559            if(!context[zi]) {
2560              context[zi] = [];
2561            }
2562            context[zi].push(stub);
2563          }
2564
2565          node.zIndex.children.forEach(function(childNode) {
2566            insert(contextForChildren, childNode, childrenDest);
2567          });
2568        }
2569        insert(rootContext, rootNode);
2570        return rootContext;
2571      })(parseQueue);
2572
2573      function sortZ(context) {
2574        Object.keys(context).sort().forEach(function(zi) {
2575          var nonPositioned = [],
2576            floated = [],
2577            positioned = [],
2578            list = [];
2579
2580          // positioned after static
2581          context[zi].forEach(function(v) {
2582            if(v.node.zIndex.isPositioned || v.node.zIndex.opacity < 1) {
2583              // http://www.w3.org/TR/css3-color/#transparency
2584              // non-positioned element with opactiy < 1 should be stacked as if it were a positioned element with ‘z-index: 0’ and ‘opacity: 1’.
2585              positioned.push(v);
2586            } else if(v.node.zIndex.isFloated) {
2587              floated.push(v);
2588            } else {
2589              nonPositioned.push(v);
2590            }
2591          });
2592
2593          (function walk(arr) {
2594            arr.forEach(function(v) {
2595              list.push(v);
2596              if(v.children) {
2597                walk(v.children);
2598              }
2599            });
2600          })(nonPositioned.concat(floated, positioned));
2601
2602          list.forEach(function(v) {
2603            if(v.context) {
2604              sortZ(v.context);
2605            } else {
2606              queue.push(v.node);
2607            }
2608          });
2609        });
2610      }
2611
2612      sortZ(rootContext);
2613
2614      return queue;
2615    }
2616
2617    function getRenderer(rendererName) {
2618      var renderer;
2619
2620      if(typeof options.renderer === "string" && _html2canvas.Renderer[rendererName] !== undefined) {
2621        renderer = _html2canvas.Renderer[rendererName](options);
2622      } else if(typeof rendererName === "function") {
2623        renderer = rendererName(options);
2624      } else {
2625        throw new Error("Unknown renderer");
2626      }
2627
2628      if(typeof renderer !== "function") {
2629        throw new Error("Invalid renderer defined");
2630      }
2631      return renderer;
2632    }
2633
2634    return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue.stack), _html2canvas);
2635  };
2636
2637  _html2canvas.Util.Support = function(options, doc) {
2638
2639    function supportSVGRendering() {
2640      var img = new Image(),
2641        canvas = doc.createElement("canvas"),
2642        ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d");
2643      if(ctx === false) {
2644        return false;
2645      }
2646      canvas.width = canvas.height = 10;
2647      img.src = [
2648        "data:image/svg+xml,",
2649        "<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'>",
2650        "<foreignObject width='10' height='10'>",
2651        "<div xmlns='http://www.w3.org/1999/xhtml' style='width:10;height:10;'>",
2652        "sup",
2653        "</div>",
2654        "</foreignObject>",
2655        "</svg>"
2656      ].join("");
2657      try {
2658        ctx.drawImage(img, 0, 0);
2659        canvas.toDataURL();
2660      } catch(e) {
2661        return false;
2662      }
2663      _html2canvas.Util.log('html2canvas: Parse: SVG powered rendering available');
2664      return true;
2665    }
2666
2667    // Test whether we can use ranges to measure bounding boxes
2668    // Opera doesn't provide valid bounds.height/bottom even though it supports the method.
2669
2670    function supportRangeBounds() {
2671      var r, testElement, rangeBounds, rangeHeight, support = false;
2672
2673      if(doc.createRange) {
2674        r = doc.createRange();
2675        if(r.getBoundingClientRect) {
2676          testElement = doc.createElement('boundtest');
2677          testElement.style.height = "123px";
2678          testElement.style.display = "block";
2679          doc.body.appendChild(testElement);
2680
2681          r.selectNode(testElement);
2682          rangeBounds = r.getBoundingClientRect();
2683          rangeHeight = rangeBounds.height;
2684
2685          if(rangeHeight === 123) {
2686            support = true;
2687          }
2688          doc.body.removeChild(testElement);
2689        }
2690      }
2691
2692      return support;
2693    }
2694
2695    return {
2696      rangeBounds: supportRangeBounds(),
2697      svgRendering: options.svgRendering && supportSVGRendering()
2698    };
2699  };
2700  window.html2canvas = function(elements, opts) {
2701    elements = (elements.length) ? elements : [elements];
2702    var queue,
2703      canvas,
2704      options = {
2705        // general
2706        logging: false,
2707        elements: elements,
2708        background: "#fff",
2709
2710        // preload options
2711        proxy: null,
2712        timeout: 0, // no timeout
2713        useCORS: false, // try to load images as CORS (where available), before falling back to proxy
2714        allowTaint: false, // whether to allow images to taint the canvas, won't need proxy if set to true
2715
2716        // parse options
2717        svgRendering: false, // use svg powered rendering where available (FF11+)
2718        ignoreElements: "IFRAME|OBJECT|PARAM",
2719        useOverflow: true,
2720        letterRendering: false,
2721        chinese: false,
2722
2723        // render options
2724
2725        width: null,
2726        height: null,
2727        taintTest: true, // do a taint test with all images before applying to canvas
2728        renderer: "Canvas"
2729      };
2730
2731    options = _html2canvas.Util.Extend(opts, options);
2732
2733    _html2canvas.logging = options.logging;
2734    options.complete = function(images) {
2735
2736      if(typeof options.onpreloaded === "function") {
2737        if(options.onpreloaded(images) === false) {
2738          return;
2739        }
2740      }
2741      queue = _html2canvas.Parse(images, options);
2742
2743      if(typeof options.onparsed === "function") {
2744        if(options.onparsed(queue) === false) {
2745          return;
2746        }
2747      }
2748
2749      canvas = _html2canvas.Renderer(queue, options);
2750
2751      if(typeof options.onrendered === "function") {
2752        options.onrendered(canvas);
2753      }
2754
2755    };
2756
2757    // for pages without images, we still want this to be async, i.e. return methods before executing
2758    window.setTimeout(function() {
2759      _html2canvas.Preload(options);
2760    }, 0);
2761
2762    return {
2763      render: function(queue, opts) {
2764        return _html2canvas.Renderer(queue, _html2canvas.Util.Extend(opts, options));
2765      },
2766      parse: function(images, opts) {
2767        return _html2canvas.Parse(images, _html2canvas.Util.Extend(opts, options));
2768      },
2769      preload: function(opts) {
2770        return _html2canvas.Preload(_html2canvas.Util.Extend(opts, options));
2771      },
2772      log: _html2canvas.Util.log
2773    };
2774  };
2775
2776  window.html2canvas.log = _html2canvas.Util.log; // for renderers
2777  window.html2canvas.Renderer = {
2778    Canvas: undefined // We are assuming this will be used
2779  };
2780  _html2canvas.Renderer.Canvas = function(options) {
2781    options = options || {};
2782
2783    var doc = document,
2784      safeImages = [],
2785      testCanvas = document.createElement("canvas"),
2786      testctx = testCanvas.getContext("2d"),
2787      Util = _html2canvas.Util,
2788      canvas = options.canvas || doc.createElement('canvas');
2789
2790    function createShape(ctx, args) {
2791      ctx.beginPath();
2792      args.forEach(function(arg) {
2793        ctx[arg.name].apply(ctx, arg['arguments']);
2794      });
2795      ctx.closePath();
2796    }
2797
2798    function safeImage(item) {
2799      if(safeImages.indexOf(item['arguments'][0].src) === -1) {
2800        testctx.drawImage(item['arguments'][0], 0, 0);
2801        try {
2802          testctx.getImageData(0, 0, 1, 1);
2803        } catch(e) {
2804          testCanvas = doc.createElement("canvas");
2805          testctx = testCanvas.getContext("2d");
2806          return false;
2807        }
2808        safeImages.push(item['arguments'][0].src);
2809      }
2810      return true;
2811    }
2812
2813    function renderItem(ctx, item) {
2814      switch(item.type) {
2815        case "variable":
2816          ctx[item.name] = item['arguments'];
2817          break;
2818        case "function":
2819          switch(item.name) {
2820            case "createPattern":
2821              if(item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
2822                try {
2823                  ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
2824                } catch(e) {
2825                  Util.log("html2canvas: Renderer: Error creating pattern", e.message);
2826                }
2827              }
2828              break;
2829            case "drawShape":
2830              createShape(ctx, item['arguments']);
2831              break;
2832            case "drawImage":
2833              if(item['arguments'][8] > 0 && item['arguments'][7] > 0) {
2834                if(!options.taintTest || (options.taintTest && safeImage(item))) {
2835                  ctx.drawImage.apply(ctx, item['arguments']);
2836                }
2837              }
2838              break;
2839            default:
2840              ctx[item.name].apply(ctx, item['arguments']);
2841          }
2842          break;
2843      }
2844    }
2845
2846    return function(parsedData, options, document, queue, _html2canvas) {
2847      var ctx = canvas.getContext("2d"),
2848        newCanvas,
2849        bounds,
2850        fstyle,
2851        zStack = parsedData.stack;
2852
2853      canvas.width = canvas.style.width = options.width || zStack.ctx.width;
2854      canvas.height = canvas.style.height = options.height || zStack.ctx.height;
2855
2856      fstyle = ctx.fillStyle;
2857      ctx.fillStyle = (Util.isTransparent(zStack.backgroundColor) && options.background !== undefined) ? options.background : parsedData.backgroundColor;
2858      ctx.fillRect(0, 0, canvas.width, canvas.height);
2859      ctx.fillStyle = fstyle;
2860
2861      queue.forEach(function(storageContext) {
2862        // set common settings for canvas
2863        ctx.textBaseline = "bottom";
2864        ctx.save();
2865
2866        if(storageContext.transform.matrix) {
2867          ctx.translate(storageContext.transform.origin[0], storageContext.transform.origin[1]);
2868          ctx.transform.apply(ctx, storageContext.transform.matrix);
2869          ctx.translate(-storageContext.transform.origin[0], -storageContext.transform.origin[1]);
2870        }
2871
2872        if(storageContext.clip) {
2873          ctx.beginPath();
2874          ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
2875          ctx.clip();
2876        }
2877
2878        if(storageContext.ctx.storage) {
2879          storageContext.ctx.storage.forEach(function(item) {
2880            renderItem(ctx, item);
2881          });
2882        }
2883
2884        ctx.restore();
2885      });
2886
2887      Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
2888
2889      if(options.elements.length === 1) {
2890        if(typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") {
2891          // crop image to the bounds of selected (single) element
2892          bounds = _html2canvas.Util.Bounds(options.elements[0]);
2893          newCanvas = document.createElement('canvas');
2894          newCanvas.width = Math.ceil(bounds.width);
2895          newCanvas.height = Math.ceil(bounds.height);
2896          ctx = newCanvas.getContext("2d");
2897
2898          // patch for ie and FF
2899          if($.browser.mozilla || $.browser.msie)
2900            bounds.top = Math.max(bounds.top, 0);
2901          ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
2902          canvas = null;
2903          return newCanvas;
2904        }
2905      }
2906
2907      return canvas;
2908    };
2909  };
2910})(window, document);; /* end of script */ ; /* end of script */ ; /* end of script */ ; /* end of script */ ; /* end of script */

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.