~openerp-india/openerp-india/7.0

« back to all changes in this revision

Viewing changes to web_keyboard_shortcuts/static/src/js/html2canvas.js

  • Committer: Parth Gajjar (Open ERP)
  • Date: 2013-06-14 13:47:59 UTC
  • mto: (1.10.29 trunk-nco)
  • mto: This revision was merged to the branch mainline in revision 8.
  • Revision ID: pga@tinyerp.com-20130614134759-r272xs9j4d85zoyy
[ADD]added 5 modules. attachment_size_limit, web_gui, web_keyboard_shortcuts, web_mail_img, web_pdf_viewer

Show diffs side-by-side

added added

removed removed

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