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