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