├── 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