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