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