httpng 0.1.0

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