├── README.md ├── css └── style.css ├── img ├── 1.jpg ├── 2.jpg ├── 3.jpg └── pdf.png ├── index.html ├── js ├── html2canvas.js ├── html2canvas.min.js ├── html2canvas.svg.js ├── html2canvas.svg.min.js ├── jspdf.debug.js └── jspdf.min.js └── renderPDF.js /README.md: -------------------------------------------------------------------------------- 1 | # How-Transform-HTML-INTO-Multipage-PDF 2 | A example about how to transform HTML into Multipage PDF. 3 | 4 | [Preview Demo](https://pwcong.github.io/how-transform-html-into-multipage-pdf/) 5 | 6 | # Support Browser 7 | * IE10+ 8 | * Chrome 9 | * Firefox 10 | * 360 11 | 12 | # Usage 13 | ## First Step. 14 | import these js file like this: 15 | ``` 16 | 17 | 18 | 19 | 20 | ``` 21 | 22 | ## Last Step. 23 | execute the method `renderPDF` that require one parameter (3 optional parameters) like this: 24 | ``` 25 | renderPDF(document.getElementById("content")); 26 | 27 | // or 28 | 29 | renderPDF(document.getElementById("content"), "pdfName", "a4", function(){ 30 | console.log("success"); 31 | }) 32 | 33 | ``` 34 | 35 | # API 36 | 37 | * renderPDF(content: Element, pdfName: string, format: string, onSuccess: function ) 38 | * content[Required]: the html element will be transfromed 39 | * pdfName[Optional]: the filename of PDF, default is "content" 40 | * format[Optional]: decide the page format of final PDF, default is "a4" 41 | * onSuccess[Optional]: execute the function when generate PDF successfully 42 | 43 | # Others 44 | ## About PDF Format 45 | 46 | The width and height of content decided by the Format of PDF 47 | 48 | ``` 49 | 'a0': [2383.94, 3370.39], 'a1': [1683.78, 2383.94], 50 | 'a2': [1190.55, 1683.78], 'a3': [841.89, 1190.55], 51 | 'a4': [595.28, 841.89], 'a5': [419.53, 595.28], 52 | 'a6': [297.64, 419.53], 'a7': [209.76, 297.64], 53 | 'a8': [147.40, 209.76], 'a9': [104.88, 147.40], 54 | 'a10': [73.70, 104.88], 'b0': [2834.65, 4008.19], 55 | 'b1': [2004.09, 2834.65], 'b2': [1417.32, 2004.09], 56 | 'b3': [1000.63, 1417.32], 'b4': [708.66, 1000.63], 57 | 'b5': [498.90, 708.66], 'b6': [354.33, 498.90], 58 | 'b7': [249.45, 354.33], 'b8': [175.75, 249.45], 59 | 'b9': [124.72, 175.75], 'b10': [87.87, 124.72], 60 | 'c0': [2599.37, 3676.54], 'c1': [1836.85, 2599.37], 61 | 'c2': [1298.27, 1836.85], 'c3': [918.43, 1298.27], 62 | 'c4': [649.13, 918.43], 'c5': [459.21, 649.13], 63 | 'c6': [323.15, 459.21], 'c7': [229.61, 323.15], 64 | 'c8': [161.57, 229.61], 'c9': [113.39, 161.57], 65 | 'c10': [79.37, 113.39], 'dl': [311.81, 623.62], 66 | 'letter': [612, 792], 67 | 'government-letter': [576, 756], 68 | 'legal': [612, 1008], 69 | 'junior-legal': [576, 360], 70 | 'ledger': [1224, 792], 71 | 'tabloid': [792, 1224] 72 | ``` 73 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: "Microsoft YaHei", sans-serif; 3 | margin: 0px; 4 | } 5 | 6 | .container{ 7 | 8 | width: 100%; 9 | display: flex; 10 | justify-content: center; 11 | 12 | } 13 | 14 | #content{ 15 | display: flex; 16 | flex-flow: row wrap; 17 | width: 500px; 18 | border: 1px grey solid; 19 | } 20 | 21 | #content img{ 22 | min-width: 100%; 23 | height: auto; 24 | } 25 | 26 | .tips{ 27 | position: fixed; 28 | left: 0px; 29 | top: -48px; 30 | width: 100%; 31 | font-size: 20px; 32 | color: white; 33 | height: 48px; 34 | display: flex; 35 | align-items: center; 36 | justify-content: center; 37 | background-color: #00796B; 38 | 39 | transition: top 0.4s; 40 | } 41 | 42 | #active{ 43 | 44 | top: 0px; 45 | } 46 | 47 | 48 | #btn{ 49 | right: 8px; 50 | bottom: 8px; 51 | font-size: 20px; 52 | color: white; 53 | display: flex; 54 | align-items: center; 55 | padding: 4px 8px 4px 8px; 56 | position: fixed; 57 | background-color: #00796B; 58 | cursor: pointer; 59 | transition: background-color 0.4s; 60 | } 61 | 62 | #btn:hover{ 63 | background-color: #009688; 64 | } 65 | 66 | #btn:active{ 67 | background-color: #004D40; 68 | } 69 | 70 | #btn img{ 71 | margin-right: 4px; 72 | width: 28px; 73 | height: 28px; 74 | } -------------------------------------------------------------------------------- /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwcong/how-transform-html-into-multipage-pdf/d9597f05a51883b7cc2ee1a16e3c7f2220dc480d/img/1.jpg -------------------------------------------------------------------------------- /img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwcong/how-transform-html-into-multipage-pdf/d9597f05a51883b7cc2ee1a16e3c7f2220dc480d/img/2.jpg -------------------------------------------------------------------------------- /img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwcong/how-transform-html-into-multipage-pdf/d9597f05a51883b7cc2ee1a16e3c7f2220dc480d/img/3.jpg -------------------------------------------------------------------------------- /img/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwcong/how-transform-html-into-multipage-pdf/d9597f05a51883b7cc2ee1a16e3c7f2220dc480d/img/pdf.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | How transform HTML into Multipage PDF 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 正在生成PDF中。。。 13 |
14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | 27 |
导出PDF
28 | 29 | 30 | 31 | 32 | 33 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /js/html2canvas.js: -------------------------------------------------------------------------------- 1 | /* 2 | html2canvas 0.5.0-alpha1 3 | Copyright (c) 2015 Niklas von Hertzen 4 | 5 | Released under MIT License 6 | */ 7 | 8 | (function(window, document, exports, global, define, undefined){ 9 | 10 | /*! 11 | * @overview es6-promise - a tiny implementation of Promises/A+. 12 | * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) 13 | * @license Licensed under MIT license 14 | * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE 15 | * @version 2.0.1 16 | */ 17 | 18 | (function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a= 0x80 (not a basic code point)', 86 | 'invalid-input': 'Invalid input' 87 | }, 88 | 89 | /** Convenience shortcuts */ 90 | baseMinusTMin = base - tMin, 91 | floor = Math.floor, 92 | stringFromCharCode = String.fromCharCode, 93 | 94 | /** Temporary variable */ 95 | key; 96 | 97 | /*--------------------------------------------------------------------------*/ 98 | 99 | /** 100 | * A generic error utility function. 101 | * @private 102 | * @param {String} type The error type. 103 | * @returns {Error} Throws a `RangeError` with the applicable error message. 104 | */ 105 | function error(type) { 106 | throw RangeError(errors[type]); 107 | } 108 | 109 | /** 110 | * A generic `Array#map` utility function. 111 | * @private 112 | * @param {Array} array The array to iterate over. 113 | * @param {Function} callback The function that gets called for every array 114 | * item. 115 | * @returns {Array} A new array of values returned by the callback function. 116 | */ 117 | function map(array, fn) { 118 | var length = array.length; 119 | var result = []; 120 | while (length--) { 121 | result[length] = fn(array[length]); 122 | } 123 | return result; 124 | } 125 | 126 | /** 127 | * A simple `Array#map`-like wrapper to work with domain name strings or email 128 | * addresses. 129 | * @private 130 | * @param {String} domain The domain name or email address. 131 | * @param {Function} callback The function that gets called for every 132 | * character. 133 | * @returns {Array} A new string of characters returned by the callback 134 | * function. 135 | */ 136 | function mapDomain(string, fn) { 137 | var parts = string.split('@'); 138 | var result = ''; 139 | if (parts.length > 1) { 140 | // In email addresses, only the domain name should be punycoded. Leave 141 | // the local part (i.e. everything up to `@`) intact. 142 | result = parts[0] + '@'; 143 | string = parts[1]; 144 | } 145 | var labels = string.split(regexSeparators); 146 | var encoded = map(labels, fn).join('.'); 147 | return result + encoded; 148 | } 149 | 150 | /** 151 | * Creates an array containing the numeric code points of each Unicode 152 | * character in the string. While JavaScript uses UCS-2 internally, 153 | * this function will convert a pair of surrogate halves (each of which 154 | * UCS-2 exposes as separate characters) into a single code point, 155 | * matching UTF-16. 156 | * @see `punycode.ucs2.encode` 157 | * @see 158 | * @memberOf punycode.ucs2 159 | * @name decode 160 | * @param {String} string The Unicode input string (UCS-2). 161 | * @returns {Array} The new array of code points. 162 | */ 163 | function ucs2decode(string) { 164 | var output = [], 165 | counter = 0, 166 | length = string.length, 167 | value, 168 | extra; 169 | while (counter < length) { 170 | value = string.charCodeAt(counter++); 171 | if (value >= 0xD800 && value <= 0xDBFF && counter < length) { 172 | // high surrogate, and there is a next character 173 | extra = string.charCodeAt(counter++); 174 | if ((extra & 0xFC00) == 0xDC00) { // low surrogate 175 | output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); 176 | } else { 177 | // unmatched surrogate; only append this code unit, in case the next 178 | // code unit is the high surrogate of a surrogate pair 179 | output.push(value); 180 | counter--; 181 | } 182 | } else { 183 | output.push(value); 184 | } 185 | } 186 | return output; 187 | } 188 | 189 | /** 190 | * Creates a string based on an array of numeric code points. 191 | * @see `punycode.ucs2.decode` 192 | * @memberOf punycode.ucs2 193 | * @name encode 194 | * @param {Array} codePoints The array of numeric code points. 195 | * @returns {String} The new Unicode string (UCS-2). 196 | */ 197 | function ucs2encode(array) { 198 | return map(array, function(value) { 199 | var output = ''; 200 | if (value > 0xFFFF) { 201 | value -= 0x10000; 202 | output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); 203 | value = 0xDC00 | value & 0x3FF; 204 | } 205 | output += stringFromCharCode(value); 206 | return output; 207 | }).join(''); 208 | } 209 | 210 | /** 211 | * Converts a basic code point into a digit/integer. 212 | * @see `digitToBasic()` 213 | * @private 214 | * @param {Number} codePoint The basic numeric code point value. 215 | * @returns {Number} The numeric value of a basic code point (for use in 216 | * representing integers) in the range `0` to `base - 1`, or `base` if 217 | * the code point does not represent a value. 218 | */ 219 | function basicToDigit(codePoint) { 220 | if (codePoint - 48 < 10) { 221 | return codePoint - 22; 222 | } 223 | if (codePoint - 65 < 26) { 224 | return codePoint - 65; 225 | } 226 | if (codePoint - 97 < 26) { 227 | return codePoint - 97; 228 | } 229 | return base; 230 | } 231 | 232 | /** 233 | * Converts a digit/integer into a basic code point. 234 | * @see `basicToDigit()` 235 | * @private 236 | * @param {Number} digit The numeric value of a basic code point. 237 | * @returns {Number} The basic code point whose value (when used for 238 | * representing integers) is `digit`, which needs to be in the range 239 | * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is 240 | * used; else, the lowercase form is used. The behavior is undefined 241 | * if `flag` is non-zero and `digit` has no uppercase form. 242 | */ 243 | function digitToBasic(digit, flag) { 244 | // 0..25 map to ASCII a..z or A..Z 245 | // 26..35 map to ASCII 0..9 246 | return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); 247 | } 248 | 249 | /** 250 | * Bias adaptation function as per section 3.4 of RFC 3492. 251 | * http://tools.ietf.org/html/rfc3492#section-3.4 252 | * @private 253 | */ 254 | function adapt(delta, numPoints, firstTime) { 255 | var k = 0; 256 | delta = firstTime ? floor(delta / damp) : delta >> 1; 257 | delta += floor(delta / numPoints); 258 | for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { 259 | delta = floor(delta / baseMinusTMin); 260 | } 261 | return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); 262 | } 263 | 264 | /** 265 | * Converts a Punycode string of ASCII-only symbols to a string of Unicode 266 | * symbols. 267 | * @memberOf punycode 268 | * @param {String} input The Punycode string of ASCII-only symbols. 269 | * @returns {String} The resulting string of Unicode symbols. 270 | */ 271 | function decode(input) { 272 | // Don't use UCS-2 273 | var output = [], 274 | inputLength = input.length, 275 | out, 276 | i = 0, 277 | n = initialN, 278 | bias = initialBias, 279 | basic, 280 | j, 281 | index, 282 | oldi, 283 | w, 284 | k, 285 | digit, 286 | t, 287 | /** Cached calculation results */ 288 | baseMinusT; 289 | 290 | // Handle the basic code points: let `basic` be the number of input code 291 | // points before the last delimiter, or `0` if there is none, then copy 292 | // the first basic code points to the output. 293 | 294 | basic = input.lastIndexOf(delimiter); 295 | if (basic < 0) { 296 | basic = 0; 297 | } 298 | 299 | for (j = 0; j < basic; ++j) { 300 | // if it's not a basic code point 301 | if (input.charCodeAt(j) >= 0x80) { 302 | error('not-basic'); 303 | } 304 | output.push(input.charCodeAt(j)); 305 | } 306 | 307 | // Main decoding loop: start just after the last delimiter if any basic code 308 | // points were copied; start at the beginning otherwise. 309 | 310 | for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { 311 | 312 | // `index` is the index of the next character to be consumed. 313 | // Decode a generalized variable-length integer into `delta`, 314 | // which gets added to `i`. The overflow checking is easier 315 | // if we increase `i` as we go, then subtract off its starting 316 | // value at the end to obtain `delta`. 317 | for (oldi = i, w = 1, k = base; /* no condition */; k += base) { 318 | 319 | if (index >= inputLength) { 320 | error('invalid-input'); 321 | } 322 | 323 | digit = basicToDigit(input.charCodeAt(index++)); 324 | 325 | if (digit >= base || digit > floor((maxInt - i) / w)) { 326 | error('overflow'); 327 | } 328 | 329 | i += digit * w; 330 | t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 331 | 332 | if (digit < t) { 333 | break; 334 | } 335 | 336 | baseMinusT = base - t; 337 | if (w > floor(maxInt / baseMinusT)) { 338 | error('overflow'); 339 | } 340 | 341 | w *= baseMinusT; 342 | 343 | } 344 | 345 | out = output.length + 1; 346 | bias = adapt(i - oldi, out, oldi == 0); 347 | 348 | // `i` was supposed to wrap around from `out` to `0`, 349 | // incrementing `n` each time, so we'll fix that now: 350 | if (floor(i / out) > maxInt - n) { 351 | error('overflow'); 352 | } 353 | 354 | n += floor(i / out); 355 | i %= out; 356 | 357 | // Insert `n` at position `i` of the output 358 | output.splice(i++, 0, n); 359 | 360 | } 361 | 362 | return ucs2encode(output); 363 | } 364 | 365 | /** 366 | * Converts a string of Unicode symbols (e.g. a domain name label) to a 367 | * Punycode string of ASCII-only symbols. 368 | * @memberOf punycode 369 | * @param {String} input The string of Unicode symbols. 370 | * @returns {String} The resulting Punycode string of ASCII-only symbols. 371 | */ 372 | function encode(input) { 373 | var n, 374 | delta, 375 | handledCPCount, 376 | basicLength, 377 | bias, 378 | j, 379 | m, 380 | q, 381 | k, 382 | t, 383 | currentValue, 384 | output = [], 385 | /** `inputLength` will hold the number of code points in `input`. */ 386 | inputLength, 387 | /** Cached calculation results */ 388 | handledCPCountPlusOne, 389 | baseMinusT, 390 | qMinusT; 391 | 392 | // Convert the input in UCS-2 to Unicode 393 | input = ucs2decode(input); 394 | 395 | // Cache the length 396 | inputLength = input.length; 397 | 398 | // Initialize the state 399 | n = initialN; 400 | delta = 0; 401 | bias = initialBias; 402 | 403 | // Handle the basic code points 404 | for (j = 0; j < inputLength; ++j) { 405 | currentValue = input[j]; 406 | if (currentValue < 0x80) { 407 | output.push(stringFromCharCode(currentValue)); 408 | } 409 | } 410 | 411 | handledCPCount = basicLength = output.length; 412 | 413 | // `handledCPCount` is the number of code points that have been handled; 414 | // `basicLength` is the number of basic code points. 415 | 416 | // Finish the basic string - if it is not empty - with a delimiter 417 | if (basicLength) { 418 | output.push(delimiter); 419 | } 420 | 421 | // Main encoding loop: 422 | while (handledCPCount < inputLength) { 423 | 424 | // All non-basic code points < n have been handled already. Find the next 425 | // larger one: 426 | for (m = maxInt, j = 0; j < inputLength; ++j) { 427 | currentValue = input[j]; 428 | if (currentValue >= n && currentValue < m) { 429 | m = currentValue; 430 | } 431 | } 432 | 433 | // Increase `delta` enough to advance the decoder's state to , 434 | // but guard against overflow 435 | handledCPCountPlusOne = handledCPCount + 1; 436 | if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { 437 | error('overflow'); 438 | } 439 | 440 | delta += (m - n) * handledCPCountPlusOne; 441 | n = m; 442 | 443 | for (j = 0; j < inputLength; ++j) { 444 | currentValue = input[j]; 445 | 446 | if (currentValue < n && ++delta > maxInt) { 447 | error('overflow'); 448 | } 449 | 450 | if (currentValue == n) { 451 | // Represent delta as a generalized variable-length integer 452 | for (q = delta, k = base; /* no condition */; k += base) { 453 | t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 454 | if (q < t) { 455 | break; 456 | } 457 | qMinusT = q - t; 458 | baseMinusT = base - t; 459 | output.push( 460 | stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) 461 | ); 462 | q = floor(qMinusT / baseMinusT); 463 | } 464 | 465 | output.push(stringFromCharCode(digitToBasic(q, 0))); 466 | bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); 467 | delta = 0; 468 | ++handledCPCount; 469 | } 470 | } 471 | 472 | ++delta; 473 | ++n; 474 | 475 | } 476 | return output.join(''); 477 | } 478 | 479 | /** 480 | * Converts a Punycode string representing a domain name or an email address 481 | * to Unicode. Only the Punycoded parts of the input will be converted, i.e. 482 | * it doesn't matter if you call it on a string that has already been 483 | * converted to Unicode. 484 | * @memberOf punycode 485 | * @param {String} input The Punycoded domain name or email address to 486 | * convert to Unicode. 487 | * @returns {String} The Unicode representation of the given Punycode 488 | * string. 489 | */ 490 | function toUnicode(input) { 491 | return mapDomain(input, function(string) { 492 | return regexPunycode.test(string) 493 | ? decode(string.slice(4).toLowerCase()) 494 | : string; 495 | }); 496 | } 497 | 498 | /** 499 | * Converts a Unicode string representing a domain name or an email address to 500 | * Punycode. Only the non-ASCII parts of the domain name will be converted, 501 | * i.e. it doesn't matter if you call it with a domain that's already in 502 | * ASCII. 503 | * @memberOf punycode 504 | * @param {String} input The domain name or email address to convert, as a 505 | * Unicode string. 506 | * @returns {String} The Punycode representation of the given domain name or 507 | * email address. 508 | */ 509 | function toASCII(input) { 510 | return mapDomain(input, function(string) { 511 | return regexNonASCII.test(string) 512 | ? 'xn--' + encode(string) 513 | : string; 514 | }); 515 | } 516 | 517 | /*--------------------------------------------------------------------------*/ 518 | 519 | /** Define the public API */ 520 | punycode = { 521 | /** 522 | * A string representing the current Punycode.js version number. 523 | * @memberOf punycode 524 | * @type String 525 | */ 526 | 'version': '1.3.1', 527 | /** 528 | * An object of methods to convert from JavaScript's internal character 529 | * representation (UCS-2) to Unicode code points, and back. 530 | * @see 531 | * @memberOf punycode 532 | * @type Object 533 | */ 534 | 'ucs2': { 535 | 'decode': ucs2decode, 536 | 'encode': ucs2encode 537 | }, 538 | 'decode': decode, 539 | 'encode': encode, 540 | 'toASCII': toASCII, 541 | 'toUnicode': toUnicode 542 | }; 543 | 544 | /** Expose `punycode` */ 545 | // Some AMD build optimizers, like r.js, check for specific condition patterns 546 | // like the following: 547 | if ( 548 | typeof define == 'function' && 549 | typeof define.amd == 'object' && 550 | define.amd 551 | ) { 552 | define('punycode', function() { 553 | return punycode; 554 | }); 555 | } else if (freeExports && freeModule) { 556 | if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+ 557 | freeModule.exports = punycode; 558 | } else { // in Narwhal or RingoJS v0.7.0- 559 | for (key in punycode) { 560 | punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); 561 | } 562 | } 563 | } else { // in Rhino or a web browser 564 | root.punycode = punycode; 565 | } 566 | 567 | }(this)); 568 | 569 | var html2canvasNodeAttribute = "data-html2canvas-node"; 570 | var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone"; 571 | var html2canvasCanvasCloneIndex = 0; 572 | var html2canvasCloneIndex = 0; 573 | 574 | window.html2canvas = function(nodeList, options) { 575 | var index = html2canvasCloneIndex++; 576 | options = options || {}; 577 | if (options.logging) { 578 | window.html2canvas.logging = true; 579 | window.html2canvas.start = Date.now(); 580 | } 581 | 582 | options.async = typeof(options.async) === "undefined" ? true : options.async; 583 | options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint; 584 | options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer; 585 | options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled; 586 | options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout; 587 | options.renderer = typeof(options.renderer) === "function" ? options.renderer : CanvasRenderer; 588 | options.strict = !!options.strict; 589 | 590 | if (typeof(nodeList) === "string") { 591 | if (typeof(options.proxy) !== "string") { 592 | return Promise.reject("Proxy must be used when rendering url"); 593 | } 594 | var width = options.width != null ? options.width : window.innerWidth; 595 | var height = options.height != null ? options.height : window.innerHeight; 596 | return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, width, height, options).then(function(container) { 597 | return renderWindow(container.contentWindow.document.documentElement, container, options, width, height); 598 | }); 599 | } 600 | 601 | var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0]; 602 | node.setAttribute(html2canvasNodeAttribute + index, index); 603 | return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) { 604 | if (typeof(options.onrendered) === "function") { 605 | log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas"); 606 | options.onrendered(canvas); 607 | } 608 | return canvas; 609 | }); 610 | }; 611 | 612 | window.html2canvas.punycode = this.punycode; 613 | window.html2canvas.proxy = {}; 614 | 615 | function renderDocument(document, options, windowWidth, windowHeight, html2canvasIndex) { 616 | return createWindowClone(document, document, windowWidth, windowHeight, options, document.defaultView.pageXOffset, document.defaultView.pageYOffset).then(function(container) { 617 | log("Document cloned"); 618 | var attributeName = html2canvasNodeAttribute + html2canvasIndex; 619 | var selector = "[" + attributeName + "='" + html2canvasIndex + "']"; 620 | document.querySelector(selector).removeAttribute(attributeName); 621 | var clonedWindow = container.contentWindow; 622 | var node = clonedWindow.document.querySelector(selector); 623 | var oncloneHandler = (typeof(options.onclone) === "function") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true); 624 | return oncloneHandler.then(function() { 625 | return renderWindow(node, container, options, windowWidth, windowHeight); 626 | }); 627 | }); 628 | } 629 | 630 | function renderWindow(node, container, options, windowWidth, windowHeight) { 631 | var clonedWindow = container.contentWindow; 632 | var support = new Support(clonedWindow.document); 633 | var imageLoader = new ImageLoader(options, support); 634 | var bounds = getBounds(node); 635 | var width = options.type === "view" ? windowWidth : documentWidth(clonedWindow.document); 636 | var height = options.type === "view" ? windowHeight : documentHeight(clonedWindow.document); 637 | var renderer = new options.renderer(width, height, imageLoader, options, document); 638 | var parser = new NodeParser(node, renderer, support, imageLoader, options); 639 | return parser.ready.then(function() { 640 | log("Finished rendering"); 641 | var canvas; 642 | 643 | if (options.type === "view") { 644 | canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0}); 645 | } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) { 646 | canvas = renderer.canvas; 647 | } else { 648 | canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: clonedWindow.pageXOffset, y: clonedWindow.pageYOffset}); 649 | } 650 | 651 | cleanupContainer(container, options); 652 | return canvas; 653 | }); 654 | } 655 | 656 | function cleanupContainer(container, options) { 657 | if (options.removeContainer) { 658 | container.parentNode.removeChild(container); 659 | log("Cleaned up container"); 660 | } 661 | } 662 | 663 | function crop(canvas, bounds) { 664 | var croppedCanvas = document.createElement("canvas"); 665 | var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left)); 666 | var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width)); 667 | var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top)); 668 | var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height)); 669 | croppedCanvas.width = bounds.width; 670 | croppedCanvas.height = bounds.height; 671 | log("Cropping canvas at:", "left:", bounds.left, "top:", bounds.top, "width:", (x2-x1), "height:", (y2-y1)); 672 | log("Resulting crop with width", bounds.width, "and height", bounds.height, " with x", x1, "and y", y1); 673 | croppedCanvas.getContext("2d").drawImage(canvas, x1, y1, x2-x1, y2-y1, bounds.x, bounds.y, x2-x1, y2-y1); 674 | return croppedCanvas; 675 | } 676 | 677 | function documentWidth (doc) { 678 | return Math.max( 679 | Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth), 680 | Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth), 681 | Math.max(doc.body.clientWidth, doc.documentElement.clientWidth) 682 | ); 683 | } 684 | 685 | function documentHeight (doc) { 686 | return Math.max( 687 | Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight), 688 | Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight), 689 | Math.max(doc.body.clientHeight, doc.documentElement.clientHeight) 690 | ); 691 | } 692 | 693 | function smallImage() { 694 | return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; 695 | } 696 | 697 | function isIE9() { 698 | return document.documentMode && document.documentMode <= 9; 699 | } 700 | 701 | // https://github.com/niklasvh/html2canvas/issues/503 702 | function cloneNodeIE9(node, javascriptEnabled) { 703 | var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false); 704 | 705 | var child = node.firstChild; 706 | while(child) { 707 | if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') { 708 | clone.appendChild(cloneNodeIE9(child, javascriptEnabled)); 709 | } 710 | child = child.nextSibling; 711 | } 712 | 713 | return clone; 714 | } 715 | 716 | function createWindowClone(ownerDocument, containerDocument, width, height, options, x ,y) { 717 | labelCanvasElements(ownerDocument); 718 | var documentElement = isIE9() ? cloneNodeIE9(ownerDocument.documentElement, options.javascriptEnabled) : ownerDocument.documentElement.cloneNode(true); 719 | var container = containerDocument.createElement("iframe"); 720 | 721 | container.className = "html2canvas-container"; 722 | container.style.visibility = "hidden"; 723 | container.style.position = "fixed"; 724 | container.style.left = "-10000px"; 725 | container.style.top = "0px"; 726 | container.style.border = "0"; 727 | container.width = width; 728 | container.height = height; 729 | container.scrolling = "no"; // ios won't scroll without it 730 | containerDocument.body.appendChild(container); 731 | 732 | return new Promise(function(resolve) { 733 | var documentClone = container.contentWindow.document; 734 | 735 | cloneNodeValues(ownerDocument.documentElement, documentElement, "textarea"); 736 | cloneNodeValues(ownerDocument.documentElement, documentElement, "select"); 737 | 738 | /* Chrome doesn't detect relative background-images assigned in inline