html2img 0.0.2

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