├── README.md
├── css
└── style.css
├── image
├── banner.jpg
└── logo.jpg
├── index.html
├── js
├── html2canvas.js
├── html2canvas.min.js
├── html2canvas.svg.js
├── html2canvas.svg.min.js
├── jspdf.debug.js
└── jspdf.min.js
└── main.js
/README.md:
--------------------------------------------------------------------------------
1 | # how-transform-html-into-pdf
2 | An example about how to transform HTML into PDF
3 |
4 | [Demo预览](https://pwcong.github.io/how-transform-html-into-pdf/)
5 |
6 | # Require
7 | * [html2canvas](https://github.com/niklasvh/html2canvas)
8 | * [jspdf](https://github.com/MrRio/jsPDF)
9 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | the width and height of content decided by the format of pdf
4 |
5 | 'a0': [2383.94, 3370.39], 'a1': [1683.78, 2383.94],
6 | 'a2': [1190.55, 1683.78], 'a3': [841.89, 1190.55],
7 | 'a4': [595.28, 841.89], 'a5': [419.53, 595.28],
8 | 'a6': [297.64, 419.53], 'a7': [209.76, 297.64],
9 | 'a8': [147.40, 209.76], 'a9': [104.88, 147.40],
10 | 'a10': [73.70, 104.88], 'b0': [2834.65, 4008.19],
11 | 'b1': [2004.09, 2834.65], 'b2': [1417.32, 2004.09],
12 | 'b3': [1000.63, 1417.32], 'b4': [708.66, 1000.63],
13 | 'b5': [498.90, 708.66], 'b6': [354.33, 498.90],
14 | 'b7': [249.45, 354.33], 'b8': [175.75, 249.45],
15 | 'b9': [124.72, 175.75], 'b10': [87.87, 124.72],
16 | 'c0': [2599.37, 3676.54], 'c1': [1836.85, 2599.37],
17 | 'c2': [1298.27, 1836.85], 'c3': [918.43, 1298.27],
18 | 'c4': [649.13, 918.43], 'c5': [459.21, 649.13],
19 | 'c6': [323.15, 459.21], 'c7': [229.61, 323.15],
20 | 'c8': [161.57, 229.61], 'c9': [113.39, 161.57],
21 | 'c10': [79.37, 113.39], 'dl': [311.81, 623.62],
22 | 'letter': [612, 792],
23 | 'government-letter': [576, 756],
24 | 'legal': [612, 1008],
25 | 'junior-legal': [576, 360],
26 | 'ledger': [1224, 792],
27 | 'tabloid': [792, 1224],
28 | 'credit-card': [153, 243]
29 | */
30 |
31 | body{
32 | margin: 0px;
33 | font-family: "Microsoft YaHei", sans-serif;
34 | }
35 |
36 | .container{
37 |
38 | display: flex;
39 | justify-content: center;
40 | flex-flow: row wrap;
41 |
42 | }
43 |
44 | .tools{
45 | position: fixed;
46 | right: 16px;
47 | bottom: 16px;
48 | text-align: center;
49 | }
50 |
51 | .tools button{
52 | padding: 8px;
53 | border: 1px #aaa solid;
54 | border-radius: 4px;
55 | outline: none;
56 | font-size: 16px;
57 | transition: background-color 0.3s;
58 | }
59 |
60 | .tools button:hover{
61 | background-color: orange;
62 | }
63 |
64 | .tools button:active{
65 | background-color: #FF8800;
66 | }
67 |
68 | #content{
69 | display: flex;
70 | flex-flow: row nowrap;
71 | flex-direction: column;
72 | width: 595px;
73 | height: 840px;
74 | }
75 |
76 | .content-banner{
77 | color: white;
78 | display: flex;
79 | align-items: center;
80 | width: 100%;
81 | min-height: 200px;
82 | background: url(../image/banner.jpg);
83 | background-repeat: no-repeat;
84 | background-size: cover;
85 | }
86 |
87 | .content-banner-logo{
88 | margin: 18px;
89 | width: 120px;
90 | height: 120px;
91 | border-radius: 60px;
92 | }
93 |
94 | .content-main{
95 | flex: 1;
96 | background-color: #f5f5f5;
97 | text-align: center;
98 | }
--------------------------------------------------------------------------------
/image/banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pwcong/how-transform-html-into-pdf/1cd85ab34c2464b57595e21e278a219a1897a06c/image/banner.jpg
--------------------------------------------------------------------------------
/image/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pwcong/how-transform-html-into-pdf/1cd85ab34c2464b57595e21e278a219a1897a06c/image/logo.jpg
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | How transform HTML into PDF
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |

19 |
How Transform HTML Into PDF
20 |
21 |
22 |
23 |
Hello World!
24 | Hello World!
25 | Hello World!
26 | Hello World!
27 | Hello World!
28 | Hello World!
29 | Hello World!
30 | Hello World!
31 | Hello World!
32 | Hello World!
33 | Hello World!
34 | Hello World!
35 | Hello World!
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/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