mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Compare commits
997 Commits
Author | SHA1 | Date | |
---|---|---|---|
6020386bbe | |||
f7c2289db1 | |||
7c3269bdbe | |||
e587a82dca | |||
67c5e8dec4 | |||
181d1b1103 | |||
46db86755f | |||
9fda3e1ede | |||
6521a487d7 | |||
0476d06515 | |||
74696faf47 | |||
1cc853a318 | |||
04787ee88a | |||
ba2b1cd8e9 | |||
d9222075da | |||
101c32ed71 | |||
ddffaecf6d | |||
fd22a01a3c | |||
ed57781594 | |||
eeda86bd5e | |||
0b1f0a7e90 | |||
38c682955a | |||
01ed87907a | |||
58ff0003f7 | |||
f143166551 | |||
cd0d7258c3 | |||
b482725994 | |||
1b55ed5668 | |||
68377b3244 | |||
6947982848 | |||
437b367d3b | |||
969638fb94 | |||
f919204efa | |||
c378e22069 | |||
2b4de68e92 | |||
1941b9e0ac | |||
e429e0443a | |||
f43f942fcd | |||
7a06d0c2c2 | |||
e36408ad03 | |||
a0dd38a8be | |||
b988d9d657 | |||
c5c6fa00d7 | |||
6651fc6789 | |||
df223c3ff2 | |||
95a46b00c5 | |||
878e37a242 | |||
1338c7b203 | |||
f284752295 | |||
96e23d1851 | |||
7d788c6f3d | |||
5dea36bd69 | |||
11d16d2b77 | |||
e9f7f48d57 | |||
4c360fc1f0 | |||
578bb771bf | |||
522e5aac5f | |||
dd6d8856ec | |||
45efe54da8 | |||
cd99f11b1b | |||
fa60716d07 | |||
99b687c412 | |||
1d00bfe175 | |||
58b4591174 | |||
92fa448913 | |||
1acdc827a4 | |||
acb4cd24b8 | |||
eeb5a3ea1d | |||
52a03c76b6 | |||
439e365ea8 | |||
e29af58661 | |||
171585491d | |||
99b8182991 | |||
a4a3ce8a2e | |||
084017a673 | |||
4555940d0b | |||
382853cb33 | |||
44296e5293 | |||
b2902ec31c | |||
e7a021ab93 | |||
85f79c1a5e | |||
cf35a282b2 | |||
bb9237155c | |||
b09ee30dae | |||
72cd528429 | |||
2a013e20c8 | |||
ff35c7dbd3 | |||
ba172678f0 | |||
7222aba1b4 | |||
82b7da558c | |||
3982df1492 | |||
1514220812 | |||
d07b6811c6 | |||
bacfadff96 | |||
d7d17adf70 | |||
60f6b85083 | |||
e3237dc4ce | |||
659f820170 | |||
f7b39c0914 | |||
cd77e1cea1 | |||
f23e6f6f26 | |||
04e145b5eb | |||
003cfd9073 | |||
9749bb1fb1 | |||
3c2c826ad7 | |||
e496047888 | |||
51de34787a | |||
6c86f5f653 | |||
8f4ec8a0c0 | |||
51aadbb7f1 | |||
8af7745e80 | |||
610997923a | |||
d844328e0a | |||
e0d536777a | |||
cc19105c91 | |||
2d30554b2a | |||
401df79087 | |||
7cb0019594 | |||
ebe9eccbd2 | |||
df44c86f68 | |||
e083e229c9 | |||
b308cbd998 | |||
4dd4a69c7a | |||
c366e8790d | |||
ae5f866b37 | |||
f139b513c5 | |||
e4d52a1ac6 | |||
8c04f94bc9 | |||
3f599103fb | |||
076492042a | |||
34b06d6365 | |||
99f105cb66 | |||
7d3456b78c | |||
00555cf1ef | |||
eedb81ef9e | |||
d4b58960dc | |||
ee3ca35636 | |||
9a63797aa7 | |||
81dcf7b6be | |||
61f4819e02 | |||
86a650bfd5 | |||
cbaecdca28 | |||
cae44a6f0a | |||
28dc05c4a3 | |||
409674fba6 | |||
5f31b74177 | |||
522a443055 | |||
20a797cbeb | |||
43058241b4 | |||
cdc4ca8296 | |||
a7d881019b | |||
b89458611b | |||
4e4a231683 | |||
a63cb3c0f1 | |||
7775d3c0d6 | |||
397595afb5 | |||
7027900f49 | |||
49f87fb680 | |||
238de790a9 | |||
029235a652 | |||
44f3d79f68 | |||
7ebef72e92 | |||
2c018d1987 | |||
5cbe5db351 | |||
3212184146 | |||
349bbf137a | |||
c45ef099fe | |||
24823d0491 | |||
41eb8ab22f | |||
4e02a4c7a1 | |||
ce45c1bbdd | |||
078b388974 | |||
3ae7dd2ebb | |||
dab77acde4 | |||
83e7eaa795 | |||
cb7fbcf33e | |||
9a6e57aa00 | |||
a3e25d71cb | |||
9da0f60551 | |||
f347953042 | |||
48b4c20e24 | |||
6341788edf | |||
0e273418c7 | |||
e6bbc1abb5 | |||
13bbc90048 | |||
102b5a1282 | |||
01e4920876 | |||
da2794f7f7 | |||
9fb9898894 | |||
952eb4cf7c | |||
fad4f837c9 | |||
e6c44afca1 | |||
d023de0b99 | |||
0f01810005 | |||
bf03cf5237 | |||
a555dfc085 | |||
69fb48969e | |||
974c35c368 | |||
0fe9632a32 | |||
c9a60c4ff9 | |||
4c14894a0a | |||
8788a9f458 | |||
e198eae398 | |||
474b5e81a7 | |||
b97972eeb6 | |||
b7c7464c5f | |||
ae019f174c | |||
ea6062c85b | |||
9a4a506366 | |||
cb93b80d0d | |||
79e1c857e6 | |||
cc9d1f89dc | |||
d0f7ecfa9a | |||
1870433307 | |||
3a5ed43e97 | |||
8429761e8f | |||
c4e670addf | |||
0aeb54ca2e | |||
eec84fa39e | |||
22f58d5d1c | |||
9046e0d554 | |||
afa5d7cb8e | |||
3881e3cf96 | |||
0aa973ab0d | |||
baaf9b0701 | |||
02de2ee829 | |||
a570f5df74 | |||
38749bc4b6 | |||
e1d6b4c76f | |||
31f2c22477 | |||
6d0cd2d226 | |||
7335984ab7 | |||
78c3c7fc71 | |||
4551976246 | |||
9e04772b42 | |||
54c4002df7 | |||
91641a3746 | |||
c4ba6f795c | |||
0b9f34a5bf | |||
757c32f6c4 | |||
09bab18b48 | |||
7e53b195ea | |||
ab966ff311 | |||
7bb4a6f08f | |||
f50da9718f | |||
3965a0fd40 | |||
77d258f1d8 | |||
261702a693 | |||
cacb9f64e4 | |||
8ef3861a5c | |||
0b74e69611 | |||
c272b2e122 | |||
2d132b85c6 | |||
50608e9cd4 | |||
d87fef11a4 | |||
250208dc99 | |||
2237e8e230 | |||
d3c640088c | |||
8fd616aed2 | |||
d1e870de88 | |||
9bc0fb0bd1 | |||
d83bc0247a | |||
13e80cc635 | |||
b239937e00 | |||
e8a4d775e8 | |||
a6a3c1bd0f | |||
850338a76a | |||
63377d47a4 | |||
1d1c74a74e | |||
ef5c59e26d | |||
8b653f89bc | |||
9db8580b97 | |||
4a09264103 | |||
b8178e92b4 | |||
166cbba7c2 | |||
0c8d38d9c0 | |||
2c8c604f9a | |||
a967475826 | |||
158435761f | |||
0e8a924ea2 | |||
0b4f922405 | |||
7e0b2b5201 | |||
9445b0b598 | |||
f16d581f04 | |||
53dd885279 | |||
ae47d901a1 | |||
929b9de6e0 | |||
57dc7137b2 | |||
90a8422938 | |||
aa47a3a3a6 | |||
c2b7ed9c42 | |||
0f9f8ff494 | |||
1a53643457 | |||
906a66eec7 | |||
c013e49192 | |||
c28263ddc2 | |||
6d9639d0af | |||
badbf52c1c | |||
23b6f29ecf | |||
a41ba8852f | |||
b75fd70042 | |||
5dbb197a82 | |||
bd463d9343 | |||
c093c95881 | |||
f79ae2b73a | |||
4f96abfb7b | |||
26d8d8ea5b | |||
a73dbf8067 | |||
26cdc0441b | |||
fa4a4a4db5 | |||
d77301a353 | |||
8999c76181 | |||
fd1447a6e7 | |||
ea080e0f5d | |||
a101b52685 | |||
ae46010476 | |||
05e5d932f0 | |||
068480f606 | |||
c9fb5d5026 | |||
f3d6d2fdf4 | |||
37a9249a4a | |||
c765e2042f | |||
d327327166 | |||
96cde64c6e | |||
31a9f913ed | |||
af09280c38 | |||
a1b8cbc2fb | |||
18761b7352 | |||
ad487f4585 | |||
fb58d1f0b6 | |||
42a87b8354 | |||
97b0a1f21d | |||
5bd06895e9 | |||
eb380f023f | |||
1c318ab607 | |||
8f575a446d | |||
5969c95481 | |||
82cfcf8704 | |||
c287f51cb6 | |||
edebe082f3 | |||
77393074ba | |||
ed92d2354c | |||
58d1bef3b6 | |||
93f08c7547 | |||
a2895691ba | |||
f7f445c71e | |||
8da77eb689 | |||
965f850e68 | |||
216c290c4b | |||
68900c3087 | |||
018ed765ad | |||
f0fdeac703 | |||
6baa847092 | |||
10ec079762 | |||
6554d4c8c8 | |||
30a2578f38 | |||
82a7349e43 | |||
0224592a96 | |||
12672839f1 | |||
9bdb871307 | |||
56b3b6df27 | |||
adb1f50f00 | |||
e380e2c873 | |||
3977ebeadd | |||
9a7075252b | |||
96fbe954e9 | |||
b8450f4d4a | |||
b3db735415 | |||
959b75a441 | |||
f6a5153d99 | |||
ad1119a76c | |||
fe97851988 | |||
f2b8c16c2c | |||
f278ba4f22 | |||
52a815a13f | |||
213f35f61c | |||
7cc2b856cb | |||
aafb0cfb9c | |||
c5135f4839 | |||
478155af64 | |||
7a3bad2fcb | |||
4f48bc9b0c | |||
6f7a3145fe | |||
ba089b4771 | |||
9f8bae4b09 | |||
f89ba365bd | |||
50579f399e | |||
8a6fb5f733 | |||
83e9b85e1e | |||
3c7c3ddf60 | |||
2ef53b37cc | |||
3b49cba21c | |||
a4aa0c6444 | |||
4ebe9c5fcc | |||
e17bbacd17 | |||
47a7240d6b | |||
6539f9d9c3 | |||
3cb0911de3 | |||
144c9a903e | |||
57dd9b5461 | |||
6d168f46be | |||
318ca48157 | |||
bebb353b3f | |||
eb5ac1122c | |||
ae97dd9a3d | |||
11fdc501b1 | |||
4df19968b5 | |||
9ab7f8cdb1 | |||
5c5531fd47 | |||
e88ac871a3 | |||
2a2ad9bb65 | |||
81e60975cc | |||
a0669300c4 | |||
ba9758cf14 | |||
aa05241ff8 | |||
5b4a6c26ee | |||
364a8aac1c | |||
46078acf71 | |||
4b37909f09 | |||
90f9eeba83 | |||
98ee30643a | |||
a49c3a2320 | |||
4b80102e77 | |||
9201cf7e95 | |||
c2baf42145 | |||
d9a9615ed7 | |||
585a96a918 | |||
9a0d43d60b | |||
3671de81f9 | |||
f3b6df267e | |||
60619dca72 | |||
ed299b3db1 | |||
c7e484af89 | |||
19ecd8bd7f | |||
edb113c230 | |||
33cd2c5ef6 | |||
3400354d78 | |||
fc01263f68 | |||
399ae9f33d | |||
db88784655 | |||
77c73c478f | |||
37b39ec716 | |||
6d53461a68 | |||
33844129e9 | |||
0df71b34f3 | |||
3996449e88 | |||
2699670bf1 | |||
7a58ad019f | |||
9b372a4399 | |||
498527918c | |||
e363f3813e | |||
433dc98177 | |||
b6a73b635a | |||
e2713cd6f9 | |||
4b771d558a | |||
2f5f9f6e59 | |||
60368d4670 | |||
f5e318d968 | |||
612e59c3d3 | |||
fcbcb9bfaa | |||
3b0352a3d7 | |||
313c227a1f | |||
893ce74a33 | |||
e4329c4d37 | |||
069140974b | |||
1e826e32ae | |||
0579804cbb | |||
f15666e156 | |||
d4cb7e8868 | |||
657eb983cf | |||
fc800bff9d | |||
36648afc3d | |||
aa3aafbc0c | |||
525b5c4f36 | |||
706b2abb19 | |||
6b1d116a5e | |||
772767d0d9 | |||
7ebd191488 | |||
6ece2a3d5a | |||
1793b802b1 | |||
8184b7cf51 | |||
04bdb48cba | |||
7e13231807 | |||
199685ebd1 | |||
444869e3ca | |||
717f69d99a | |||
541f6a62d8 | |||
d8dcbdedd8 | |||
5712b621ca | |||
6073928978 | |||
e103ad8219 | |||
d5430070a2 | |||
f3d45e005e | |||
b60b4b2a45 | |||
bd1abe1857 | |||
8a3d1d7f22 | |||
e6d31ada4a | |||
19777c6623 | |||
33d84c82b0 | |||
1e19832171 | |||
fa659ad1df | |||
f517a35781 | |||
3f3424e49c | |||
1d8a316f13 | |||
b1f948bb60 | |||
24d9a22556 | |||
7ee2f411b0 | |||
9406f76a18 | |||
6092629679 | |||
349d0a300c | |||
0a7df6d9b9 | |||
70241a789d | |||
075c836d16 | |||
c9993a7237 | |||
3b8d4dece2 | |||
440120b087 | |||
e80fe312ee | |||
b141c9f0d1 | |||
3a3a61e316 | |||
6c08c3fa04 | |||
d4c9a41873 | |||
6347e7f043 | |||
9220eb6def | |||
af965cc3a6 | |||
9d088fa431 | |||
b8d3688c29 | |||
9907149513 | |||
645fcd60b3 | |||
0325a9b836 | |||
36052c2765 | |||
281efd2d5e | |||
44b958beaf | |||
2a020e5a21 | |||
c20e679f2c | |||
52c669fe5b | |||
382c16a522 | |||
ba9d5201cf | |||
2e2d722e3d | |||
b9edf0b1c5 | |||
07f793b0ed | |||
180b624cb3 | |||
b2280bc8ec | |||
f3f92ab425 | |||
503add6e2f | |||
d2bfb810d4 | |||
649cdd4e37 | |||
73a34493ac | |||
ce1c4c84f5 | |||
44beaf2989 | |||
d716210509 | |||
be5d1f8665 | |||
340b125b19 | |||
ad1f0d418c | |||
08373f0bd4 | |||
6959058560 | |||
d6ed6c0194 | |||
9ee87339a3 | |||
b7595e19e9 | |||
281e6bbedf | |||
fee91055b2 | |||
650ead63e5 | |||
b35fcaeaf9 | |||
81c22866bc | |||
25d892f525 | |||
9db1ecfdfc | |||
12d85e3c04 | |||
3101f2007a | |||
85b77ca49f | |||
bb8c5a973b | |||
0187fcab42 | |||
cfe4137bcc | |||
d2c3378c3e | |||
95f4bcea0a | |||
9bae5b610a | |||
7ce46e95cd | |||
84c1dc6283 | |||
15ca3381eb | |||
18d95d669b | |||
5137e5f35a | |||
314d26f1f1 | |||
82e5a8a7c0 | |||
9af96d3812 | |||
6f2a775841 | |||
f2b662801e | |||
60587c72bf | |||
d9d516d27e | |||
899d5321d4 | |||
5d20493f46 | |||
b5891c49b4 | |||
467ff87482 | |||
9beae48cf0 | |||
17731169e9 | |||
e27c41efd3 | |||
1f90defbfa | |||
b4bb34c95b | |||
64668fe694 | |||
9ebae161e2 | |||
729bc88d1f | |||
b1c2f03ae9 | |||
74cb3466ec | |||
2afdcaff35 | |||
1070cec852 | |||
ba9a33b1bc | |||
f474542382 | |||
0cb259f6cd | |||
1a7f5732bf | |||
2b8389cb64 | |||
8b8c080841 | |||
6201e09118 | |||
517fd8cd1d | |||
e228fc57ce | |||
46cc8b6975 | |||
443fd17a12 | |||
0b213eecef | |||
ae1a15f7c5 | |||
cea3005056 | |||
1d4b1753d6 | |||
f00b23a9ec | |||
e9afe03960 | |||
57d20a9794 | |||
35c5ca3340 | |||
7cc7f80ee2 | |||
4c75d819a4 | |||
838f91e156 | |||
8bea01b81d | |||
e115180731 | |||
e782efa614 | |||
806cd60474 | |||
0515765788 | |||
0fd25f048d | |||
14ff672c6e | |||
38fad5ac17 | |||
a31de83368 | |||
1fb3b53fc0 | |||
4d465116da | |||
74ce2c5062 | |||
fbeb6e72f2 | |||
c097f11ce3 | |||
b6ebf2acf6 | |||
e9c3d9d332 | |||
c232da2595 | |||
c759600c06 | |||
5f45968154 | |||
feb2fd0a63 | |||
fb944d9381 | |||
564634ba97 | |||
dd7468c446 | |||
eb00650b02 | |||
1d03a5f9a4 | |||
ea7d6b485d | |||
fd4fd95429 | |||
10b40821e5 | |||
518dd702a2 | |||
056953f2c1 | |||
9a57a08c72 | |||
26a81da2f0 | |||
57028ab423 | |||
c9e2fc27c8 | |||
2777a3e079 | |||
02ab96dc5f | |||
65746bd2e3 | |||
16d3bef255 | |||
0277c34310 | |||
f35ef0fe6f | |||
2c8dd18d55 | |||
6b5f31eef0 | |||
832b9ee934 | |||
73698e8ceb | |||
37fbd3f90e | |||
5300f20b78 | |||
f0e234a1d8 | |||
30163ab16f | |||
407145da94 | |||
c5e6eaa849 | |||
2d39cd0719 | |||
ebd7828dc8 | |||
877367d499 | |||
fd888bde8d | |||
7d2e12c3dd | |||
a7d3e9c2a2 | |||
f49e147b2f | |||
a902f92a14 | |||
e1573f8aed | |||
655779743b | |||
1a30167f6a | |||
2c58c56fbe | |||
288b851d05 | |||
0afb0fae0e | |||
a4702423cc | |||
f4aef61e5a | |||
cb210b2e61 | |||
a80ef26c42 | |||
2580b48d16 | |||
87d7894d71 | |||
7842768707 | |||
403908c7da | |||
8c8128b80a | |||
0d4b6ba665 | |||
b91fd9bc87 | |||
62d27c20c3 | |||
e811effe2a | |||
6156e28721 | |||
2b000f0061 | |||
843db27f72 | |||
bbdfe8a035 | |||
9da3bd7769 | |||
85fa81ad95 | |||
a5d74bcfd9 | |||
cf735a9fa1 | |||
9b051b8749 | |||
822311ed0c | |||
c39225ceac | |||
763125ce6d | |||
5ac4b42e33 | |||
cacb9a468f | |||
8623e4014b | |||
2d95a761b0 | |||
b2df50a858 | |||
8ddf10fc04 | |||
410537456a | |||
3aa7d69cc7 | |||
7a3dd7572c | |||
1b37c5d1ea | |||
67850f2cee | |||
88dd1e41c0 | |||
554185ed4a | |||
222dfa84b7 | |||
9b0c32c62c | |||
2bb926c7d0 | |||
861a18f977 | |||
cf15c4a59f | |||
2f3f27b672 | |||
3032dc6ce0 | |||
2b0db917e3 | |||
d73e53fbf0 | |||
6d29cc5df3 | |||
dc21fab450 | |||
5492d80135 | |||
a313524aa4 | |||
3edf9fa743 | |||
7d7deca342 | |||
053a0a4787 | |||
65b4bdf282 | |||
56780565f4 | |||
55ed0ffde0 | |||
7da4326885 | |||
eb57b61859 | |||
bb73d3c15e | |||
9b5ae9e191 | |||
2557a83dbe | |||
67ccb33dd5 | |||
85706166cc | |||
57d6003b65 | |||
9ce03d6e86 | |||
3d3f923ed8 | |||
42abcfe5fc | |||
473ff45267 | |||
496c8488bd | |||
df0f436e66 | |||
6ce619f0c0 | |||
3774f3655c | |||
0c66766d55 | |||
ba9ace71ba | |||
b19c200c6c | |||
29bd4c8c05 | |||
0dd2c24ab4 | |||
d93e36d768 | |||
1357057cbf | |||
74e93cbb93 | |||
45853a083c | |||
cb43e09899 | |||
bb1cd21367 | |||
5faa45847e | |||
0c2572b5ce | |||
aa5b3d41c4 | |||
516edbceea | |||
c72a02bf64 | |||
c3e9636e4f | |||
816ff6d3c5 | |||
5f1fedf8f0 | |||
a82234873e | |||
d1ee6e9d64 | |||
e7b4dd17b9 | |||
05f3af4901 | |||
630bed968e | |||
7c870a6fb8 | |||
52033a5d72 | |||
07e80df399 | |||
7e38df782c | |||
7f1cbc70a8 | |||
b81d7473e3 | |||
d7bef66cc5 | |||
04782c1716 | |||
a4b7d04e80 | |||
62cb111956 | |||
3171390f80 | |||
8fe61a43b0 | |||
f0ce6917fa | |||
118b42eb7e | |||
3bbcfe36e0 | |||
cdc7a744e3 | |||
fb0e7ca29d | |||
4e978c60cc | |||
c9ed8d91fa | |||
582d10e00d | |||
4684177df8 | |||
5198028c7b | |||
d6ddb7e29d | |||
9df9426c91 | |||
76aa1e8feb | |||
2841f19647 | |||
c6baabc99c | |||
44023015b6 | |||
bca6458301 | |||
7c0b893564 | |||
084bf4b039 | |||
e83de7ae00 | |||
1b81f7d517 | |||
3164e5bae0 | |||
81ae37cbd1 | |||
730ebcfcaa | |||
cce6e3537c | |||
311a67ee22 | |||
be143935cb | |||
d6cb548a5c | |||
1ba911912d | |||
343b86705f | |||
9f76f94a82 | |||
b02bb4d452 | |||
3bdba0617a | |||
1059314258 | |||
331c057273 | |||
8d3a0c2b0d | |||
16022b81c3 | |||
17f4701ee5 | |||
00c3fb791c | |||
98bc1f0833 | |||
a5969be6f6 | |||
de1d1d7087 | |||
d494b8dfbd | |||
cfc45e4f6e | |||
8d965029da | |||
1ad7ed3e1c | |||
d7f4509253 | |||
b47347d6b8 | |||
106b5ff214 | |||
d1dec8712e | |||
fe4d2c5b81 | |||
6cf3d36624 | |||
4dc4132818 | |||
1ab9941df6 | |||
0fc5f643ba | |||
c7995061c9 | |||
12cf519e37 | |||
40bce5e84c | |||
0556892e12 | |||
6c29664e35 | |||
155ad45292 | |||
66ad7190c0 | |||
4f22c18043 | |||
d83b06458c | |||
0d35571bbf | |||
211467fcc1 | |||
6390c1c7ac | |||
a0b498fbf5 | |||
cde96bb17e | |||
57bff5292d | |||
82446ee3c3 | |||
ffd998b015 | |||
51b2c01b0c | |||
c08ac5d0c4 | |||
59306c839b | |||
5fb8cb3e0b | |||
67d3e0d0f5 | |||
f387267c0f | |||
b65a850997 | |||
163219b656 | |||
a4f13de455 | |||
75ba867988 | |||
ae8d499942 | |||
e479c952f7 | |||
6637ba1bd7 | |||
883d8bb75b | |||
cfde57cb9f | |||
0674543ab1 | |||
954631d045 | |||
9fc366b3f7 | |||
8a5b09be70 | |||
7a3ca77471 | |||
e82d703288 | |||
c018166563 | |||
89d749e30a | |||
c056acee43 | |||
c6ec65616c | |||
f47f9025b7 | |||
d78687a3dc | |||
2a0dff32b2 | |||
00d73c0bf8 | |||
5e57ebc0ce | |||
3d7a6374ad | |||
4579fb25c6 | |||
e84d505f46 | |||
946bdef0d4 | |||
b60fc931b5 | |||
8affbc3db5 | |||
187ae9816e | |||
38fe643b25 | |||
fb7879fd17 | |||
7726cd9f39 | |||
0b065ad5d8 | |||
ec881018b3 | |||
f83fb59053 | |||
6eab1d5a9c | |||
94f2f799a4 | |||
cad3be2c66 | |||
c7d526c9ea | |||
bf994849e0 | |||
2dc8b9385e | |||
6ef6c79f24 | |||
3ad49efa00 | |||
c86d12b915 | |||
1447b031c6 | |||
e01d97df19 | |||
af60621d4f | |||
f485028d30 | |||
5878c201b3 | |||
b6d6f44678 | |||
19f505214b | |||
c24223ca85 | |||
b82be022b2 | |||
84a676403f | |||
afc358fb12 | |||
e925719151 | |||
b65357c55d | |||
c4cc1fe180 | |||
abea4a89da | |||
0cb252ada9 | |||
d5040e119a | |||
83363a5cf4 | |||
96db0b8ff4 | |||
b320dd306e | |||
36ff1ec7aa | |||
014c0ed98f | |||
45fa47100e | |||
db211317ff | |||
bbd75286a8 | |||
8bbbace790 | |||
3dec1cd4ab | |||
594f735d29 | |||
447db0c5f5 | |||
c57ed30c3e | |||
50f5f53e2b | |||
2fdab0d753 | |||
0d8937435d | |||
433d7ce81c | |||
4dba62e2fd | |||
2e494bde23 | |||
753ab77ef6 | |||
0d24b9a734 | |||
98c2bd638e | |||
0d370d09fb | |||
88174fe136 | |||
1c6469d29b | |||
ab5055ffad | |||
501f559783 | |||
f75456cc88 | |||
961f6caf21 | |||
097a4e58d7 | |||
73763c8114 | |||
1f7314747e | |||
4f49bd6e9b | |||
217a75c0f6 | |||
62fa6038e7 | |||
66d328d1e3 | |||
2ec7fda1f1 | |||
044b612d6e | |||
4aba46e247 | |||
91ac445fdf | |||
2a3d6a0a35 | |||
03495f851d | |||
16c74d1f8c | |||
74c7373362 | |||
7ee7d7fa67 | |||
962d5b8e8d | |||
2ca4b88702 | |||
ff635115dc | |||
a75f41c076 | |||
6dfb5299c9 | |||
a89b83cb11 | |||
52150b09cd | |||
61f71c1839 | |||
6fe3218b50 | |||
f0112ff3ab | |||
27ee971bba | |||
5ba5ef571c | |||
180cd38265 | |||
958cfdf4eb |
13
.babelrc
Normal file
13
.babelrc
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"presets": [[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"ie": "9"
|
||||
}
|
||||
}
|
||||
], "@babel/preset-flow"],
|
||||
"plugins": [
|
||||
"add-module-exports"
|
||||
]
|
||||
}
|
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@ -0,0 +1,17 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[{*.yml,package.json}]
|
||||
# The indent size used in the `package.json` file cannot be changed
|
||||
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
|
||||
indent_size = 2
|
26
.eslintrc
Normal file
26
.eslintrc
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json",
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": ["error", { "allow": ["warn", "error"] }],
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/class-name-casing": "off",
|
||||
"prettier/prettier": "error"
|
||||
}
|
||||
}
|
19
.github/ISSUE_TEMPLATE.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Please make sure you are testing with the latest [release of html2canvas](https://github.com/niklasvh/html2canvas/releases).
|
||||
Old versions are not supported and issues reported for them will be closed.
|
||||
|
||||
# Please follow the general troubleshooting steps first:
|
||||
|
||||
- [ ] You are using the latest [version](https://github.com/niklasvh/html2canvas/releases)
|
||||
- [ ] You are testing using the non-minified version of html2canvas and checked any potential issues reported in the console
|
||||
|
||||
<!-- You can erase any parts of this template not applicable to your Issue. -->
|
||||
|
||||
### Bug reports:
|
||||
|
||||
Please replace this line with a brief summary of your issue **AND** if possible an example on [jsfiddle](https://jsfiddle.net/).
|
||||
|
||||
### Specifications:
|
||||
|
||||
* html2canvas version tested with:
|
||||
* Browser & version:
|
||||
* Operating system:
|
37
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
37
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
A similar PR may already be submitted!
|
||||
Please search among the [Pull request](https://github.com/niklasvh/html2canvas/pulls) before creating one.
|
||||
|
||||
Thanks for submitting a pull request! Please provide enough information so that others can review your pull request:
|
||||
|
||||
Before opening a pull request, please make sure all the tests pass locally by running `npm test`.
|
||||
|
||||
**Summary**
|
||||
|
||||
<!-- Summary of the PR -->
|
||||
|
||||
This PR fixes/implements the following **bugs/features**
|
||||
|
||||
* [ ] Bug 1
|
||||
* [ ] Bug 2
|
||||
* [ ] Feature 1
|
||||
* [ ] Feature 2
|
||||
* [ ] Breaking changes
|
||||
|
||||
<!-- You can skip this if you're fixing a typo or adding an app to the Showcase. -->
|
||||
|
||||
Explain the **motivation** for making this change. What existing problem does the pull request solve?
|
||||
|
||||
<!-- Example: When "Adding a function to do X", explain why it is necessary to have a way to do X. -->
|
||||
|
||||
**Test plan (required)**
|
||||
|
||||
Demonstrate how the issue/feature can be replicated. For most cases, simply adding an appropriate html/css template into the [reftests](https://github.com/niklasvh/html2canvas/tree/master/tests/reftests) should be sufficient. Please see other tests there for reference.
|
||||
|
||||
**Code formatting**
|
||||
|
||||
Please make sure that code adheres to the project code formatting. Running `npm run format` will automatically format your code correctly.
|
||||
|
||||
**Closing issues**
|
||||
|
||||
<!-- Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if such). -->
|
||||
Fixes #
|
13
.github/no-response.yml
vendored
Normal file
13
.github/no-response.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# Configuration for probot-no-response - https://github.com/probot/no-response
|
||||
|
||||
# Number of days of inactivity before an Issue is closed for lack of response
|
||||
daysUntilClose: 14
|
||||
# Label requiring a response
|
||||
responseRequiredLabel: Needs More Information
|
||||
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed because there has been no response
|
||||
to our request for more information from the original author. With only the
|
||||
information that is currently in the issue, we don't have enough information
|
||||
to take action. Please reach out if you have or find the answers we need so
|
||||
that we can investigate further.
|
359
.github/workflows/ci.yml
vendored
Normal file
359
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,359 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- name: Npm install
|
||||
run: npm ci
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Pack
|
||||
run: |
|
||||
npm pack
|
||||
mv html2canvas-*.tgz html2canvas.tgz
|
||||
tar --list --verbose --file=html2canvas.tgz
|
||||
- name: Upload npm pack
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: npm
|
||||
path: html2canvas.tgz
|
||||
if-no-files-found: error
|
||||
- name: Upload dist
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
if-no-files-found: error
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
if-no-files-found: error
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- name: Npm install
|
||||
run: npm ci
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Unit tests
|
||||
run: npm run unittest
|
||||
browser-test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- os: ubuntu-latest
|
||||
name: Linux Firefox Stable
|
||||
targetBrowser: Firefox_Stable
|
||||
xvfb: true
|
||||
- os: ubuntu-latest
|
||||
name: Linux Chrome Stable
|
||||
targetBrowser: Chrome_Stable
|
||||
xvfb: true
|
||||
- os: macos-latest
|
||||
name: OSX Safari Stable
|
||||
targetBrowser: Safari_Stable
|
||||
- os: macos-10.15
|
||||
name: iOS Simulator Safari 12
|
||||
targetBrowser: Safari_IOS_12
|
||||
xcode: /Applications/Xcode_10.3.app
|
||||
- os: macos-10.15
|
||||
name: iOS Simulator Safari 13
|
||||
targetBrowser: Safari_IOS_13
|
||||
xcode: /Applications/Xcode_11.7.app
|
||||
- os: macos-10.15
|
||||
name: iOS Simulator Safari 14
|
||||
targetBrowser: Safari_IOS_14
|
||||
xcode: /Applications/Xcode_12.4.app
|
||||
- os: macos-11
|
||||
name: iOS Simulator Safari 15.0
|
||||
targetBrowser: Safari_IOS_15_0
|
||||
xcode: /Applications/Xcode_13.0.app
|
||||
- os: macos-11
|
||||
name: iOS Simulator Safari 15
|
||||
targetBrowser: Safari_IOS_15
|
||||
xcode: /Applications/Xcode_13.2.app
|
||||
- os: windows-latest
|
||||
name: Windows Internet Explorer 9 (Emulated)
|
||||
targetBrowser: IE_9
|
||||
- os: windows-latest
|
||||
name: Windows Internet Explorer 10 (Emulated)
|
||||
targetBrowser: IE_10
|
||||
- os: windows-latest
|
||||
name: Windows Internet Explorer 11
|
||||
targetBrowser: IE_11
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
name: ${{ matrix.config.name }}
|
||||
env:
|
||||
TARGET_BROWSER: ${{ matrix.config.targetBrowser }}
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- name: Npm install
|
||||
run: npm ci
|
||||
- name: Download library
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- name: Download test-runner
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
- name: xcode selection
|
||||
if: ${{ matrix.config.xcode != '' }}
|
||||
run: sudo xcode-select -s "${{ matrix.config.xcode }}"
|
||||
- name: Run browser tests
|
||||
if: ${{ matrix.config.xvfb != true }}
|
||||
run: npm run karma
|
||||
- name: Start Xvfb
|
||||
if: ${{ matrix.config.xvfb == true }}
|
||||
run: Xvfb :99 &
|
||||
- name: Run browser tests
|
||||
if: ${{ matrix.config.xvfb == true }}
|
||||
run: DISPLAY=:99 npm run karma
|
||||
- name: Upload screenshots
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: reftest-results
|
||||
path: tmp/reftests
|
||||
if-no-files-found: error
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: browser-test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download NPM package
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: npm
|
||||
path: npm
|
||||
- name: Download library
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- name: Create Github Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: ${{ contains(github.ref, '-rc.') || contains(github.ref, '-alpha.') }}
|
||||
- name: Upload html2canvas.js
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./dist/html2canvas.js
|
||||
asset_name: html2canvas.js
|
||||
asset_content_type: text/javascript
|
||||
- name: Upload html2canvas.min.js
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./dist/html2canvas.min.js
|
||||
asset_name: html2canvas.min.js
|
||||
asset_content_type: text/javascript
|
||||
- name: Upload html2canvas.esm.js
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./dist/html2canvas.esm.js
|
||||
asset_name: html2canvas.esm.js
|
||||
asset_content_type: text/javascript
|
||||
- name: Unpack npm
|
||||
run: cd npm && tar -xvzf "html2canvas.tgz"
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- name: NPM Publish
|
||||
run: cd npm/package && npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build docs
|
||||
needs: browser-test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- name: Npm install
|
||||
run: npm ci
|
||||
- name: Download library
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: dist
|
||||
path: www/static/dist
|
||||
- name: Download test results
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: reftest-results
|
||||
path: www/static/results
|
||||
- name: Copy reftests to docs website
|
||||
run: cp -R tests/reftests www/static/tests/reftests && cp -R tests/assets www/static/tests/assets && cp tests/test.js www/static/tests/test.js
|
||||
- name: Create reftest result index
|
||||
run: npm run build:reftest-result-list www/static/results/metadata www/src/results.json
|
||||
- name: Create reftest previewer
|
||||
run: npm run build:reftest-preview
|
||||
- name: Clean metadata folder
|
||||
run: rm -rf www/static/results/metadata
|
||||
- name: Build docs
|
||||
run: npm run build && cd www && npm install && npm run build && cd ..
|
||||
- name: Upload docs
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docs
|
||||
path: www/public
|
||||
if-no-files-found: error
|
||||
publish-docs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish Docs
|
||||
if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') }}
|
||||
needs: docs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Download docs
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: docs
|
||||
path: docs
|
||||
- name: Publish docs
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: docs
|
||||
SINGLE_COMMIT: true
|
||||
CLEAN: true
|
||||
diff-reftests:
|
||||
runs-on: ubuntu-latest
|
||||
name: Diff reftest screenshots
|
||||
needs: browser-test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- name: Npm install
|
||||
run: npm ci
|
||||
- name: Checkout current snapshots
|
||||
run: git checkout origin/gh-pages results
|
||||
- name: Download test results
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: reftest-results
|
||||
path: tmp/reftests
|
||||
- name: Run diff
|
||||
run: npm run reftests-diff
|
||||
continue-on-error: true
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: snapshot-diffs
|
||||
path: tmp/snapshot-diffs
|
37
.github/workflows/release.yml
vendored
Normal file
37
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Semantic version (major | minor | patch | premajor | preminor | prepatch | prerelease)'
|
||||
default: 'patch'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
version:
|
||||
name: Create version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Npm install
|
||||
run: npm ci
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config user.name "CI"
|
||||
git config user.email "niklasvh@gmail.com"
|
||||
- name: Create release
|
||||
run: npm run release -- --preset eslint --release-as ${{ github.event.inputs.version }}
|
||||
- name: Print details
|
||||
run: |
|
||||
cat package.json
|
||||
cat CHANGELOG.md
|
||||
git tag
|
||||
- name: Push git version
|
||||
run: git push --follow-tags origin master
|
24
.gitignore
vendored
24
.gitignore
vendored
@ -1,9 +1,19 @@
|
||||
/dist
|
||||
/tmp
|
||||
/build
|
||||
/nbproject/
|
||||
/images/
|
||||
/tests/templates/
|
||||
/tests/cache/
|
||||
/dist/
|
||||
/build/tmp.js
|
||||
index.html
|
||||
image.jpg
|
||||
screenshots_local.html
|
||||
/.project
|
||||
/.settings/
|
||||
node_modules/
|
||||
.envrc
|
||||
*.sublime-workspace
|
||||
*.baseline
|
||||
*.iml
|
||||
.idea/
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
debug.log
|
||||
tests/reftests.js
|
||||
*.log
|
||||
.rpt2_cache
|
||||
|
22
.npmignore
Normal file
22
.npmignore
Normal file
@ -0,0 +1,22 @@
|
||||
.github/
|
||||
.idea/
|
||||
.rpt2_cache
|
||||
build/
|
||||
configs/
|
||||
docs/
|
||||
examples/
|
||||
scripts/
|
||||
src/
|
||||
tests/
|
||||
www/
|
||||
tmp/
|
||||
*.iml
|
||||
.babelrc
|
||||
.editorconfig
|
||||
.eslintrc
|
||||
.npmignore
|
||||
.prettierrc
|
||||
jest.config.js
|
||||
karma.conf.js
|
||||
karma.js
|
||||
rollup.config.ts
|
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 4,
|
||||
"bracketSpacing": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
566
CHANGELOG.md
Normal file
566
CHANGELOG.md
Normal file
@ -0,0 +1,566 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [1.4.1](https://github.com/niklasvh/html2canvas/compare/v1.4.0...v1.4.1) (2022-01-22)
|
||||
|
||||
|
||||
### deps
|
||||
|
||||
* fix source maps (#2812) ([67c5e8d](https://github.com/niklasvh/html2canvas/commit/67c5e8dec4b2af9260a2b5b75b3399495fd1fee9)), closes [#2812](https://github.com/niklasvh/html2canvas/issues/2812)
|
||||
|
||||
### feat
|
||||
|
||||
* add support for `<video>` elements (#2788) ([181d1b1](https://github.com/niklasvh/html2canvas/commit/181d1b1103910d6e1b5277d5c007fc5e3006c6bf)), closes [#2788](https://github.com/niklasvh/html2canvas/issues/2788)
|
||||
|
||||
### fix
|
||||
|
||||
* Properties x and y of BoundingRect is undefined in old browser (#2797) ([e587a82](https://github.com/niklasvh/html2canvas/commit/e587a82dca01d9ada78cae34fd1bdb934e547f9b)), closes [#2797](https://github.com/niklasvh/html2canvas/issues/2797)
|
||||
* source maps (#2787) ([46db867](https://github.com/niklasvh/html2canvas/commit/46db86755f064828559a4b0b37310f3ae94f5494)), closes [#2787](https://github.com/niklasvh/html2canvas/issues/2787)
|
||||
|
||||
|
||||
|
||||
# [1.4.0](https://github.com/niklasvh/html2canvas/compare/v1.3.4...v1.4.0) (2022-01-01)
|
||||
|
||||
|
||||
### feat
|
||||
|
||||
* use native text segmenter where available (#2782) ([6521a48](https://github.com/niklasvh/html2canvas/commit/6521a487d78172f7179f7c973c1a3af40eb92009)), closes [#2782](https://github.com/niklasvh/html2canvas/issues/2782)
|
||||
|
||||
### fix
|
||||
|
||||
* adopted stylesheets (#2785) ([74696fa](https://github.com/niklasvh/html2canvas/commit/74696faf47c07b48b9c9587db0b999da1c08a8be)), closes [#2785](https://github.com/niklasvh/html2canvas/issues/2785)
|
||||
* ios text wrapping with 0 width rect (#2786) ([0476d06](https://github.com/niklasvh/html2canvas/commit/0476d065158c33d2020a9f602b3043e5e2f90c75)), closes [#2786](https://github.com/niklasvh/html2canvas/issues/2786)
|
||||
* reduce SLICE_STACK_SIZE to 50k (#2784) ([1cc853a](https://github.com/niklasvh/html2canvas/commit/1cc853a3186853eaca00af060f651697dc3497a9)), closes [#2784](https://github.com/niklasvh/html2canvas/issues/2784)
|
||||
|
||||
|
||||
|
||||
## [1.3.4](https://github.com/niklasvh/html2canvas/compare/v1.3.3...v1.3.4) (2021-12-29)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* add ios 15.0 testing (#2780) ([d922207](https://github.com/niklasvh/html2canvas/commit/d9222075daaed08884491b0563fc899ee0ced731)), closes [#2780](https://github.com/niklasvh/html2canvas/issues/2780)
|
||||
|
||||
### fix
|
||||
|
||||
* ios 15 font rendering crash (#2645) ([ba2b1cd](https://github.com/niklasvh/html2canvas/commit/ba2b1cd8e9a9d7932675d7abffce1526a609e769)), closes [#2645](https://github.com/niklasvh/html2canvas/issues/2645)
|
||||
|
||||
|
||||
|
||||
## [1.3.3](https://github.com/niklasvh/html2canvas/compare/v1.3.2...v1.3.3) (2021-11-23)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* fix macos action runners (#2757) ([ed57781](https://github.com/niklasvh/html2canvas/commit/ed577815949b6a565df54f2101cf6f0fb731c290)), closes [#2757](https://github.com/niklasvh/html2canvas/issues/2757)
|
||||
|
||||
### fix
|
||||
|
||||
* "offsets" text when font is big ([fd22a01](https://github.com/niklasvh/html2canvas/commit/fd22a01a3c9e39293f47caaed0c0e13d2dc8170c))
|
||||
* const enums (#2651) ([eeda86b](https://github.com/niklasvh/html2canvas/commit/eeda86bd5e81fb4e97675fe9bee3d4d15899997f)), closes [#2651](https://github.com/niklasvh/html2canvas/issues/2651)
|
||||
|
||||
|
||||
|
||||
## [1.3.2](https://github.com/niklasvh/html2canvas/compare/v1.3.1...v1.3.2) (2021-08-15)
|
||||
|
||||
|
||||
### docs
|
||||
|
||||
* add warning for webgl cloning with preserveDrawingBuffer=false (#2661) ([01ed879](https://github.com/niklasvh/html2canvas/commit/01ed87907ad9c7688880e2c5cb8ebc22ef73a4d8)), closes [#2661](https://github.com/niklasvh/html2canvas/issues/2661)
|
||||
* include src files on www (#2660) ([58ff000](https://github.com/niklasvh/html2canvas/commit/58ff0003f77d825ac027eeec95fa80c0123eaf8f)), closes [#2660](https://github.com/niklasvh/html2canvas/issues/2660)
|
||||
|
||||
### feat
|
||||
|
||||
* add support for data-html2canvas-debug property for debugging (#2658) ([cd0d725](https://github.com/niklasvh/html2canvas/commit/cd0d7258c3a93f2989d5d9ec0244ba2763ea2d23)), closes [#2658](https://github.com/niklasvh/html2canvas/issues/2658)
|
||||
|
||||
### fix
|
||||
|
||||
* disable transition properties (#2659) ([f143166](https://github.com/niklasvh/html2canvas/commit/f1431665513e0a4636fb167a241f4a0571ba728a)), closes [#2659](https://github.com/niklasvh/html2canvas/issues/2659)
|
||||
* overflows with absolutely positioned content (#2663) ([38c6829](https://github.com/niklasvh/html2canvas/commit/38c682955a9299ca7785af71d8f251df799405b0)), closes [#2663](https://github.com/niklasvh/html2canvas/issues/2663)
|
||||
|
||||
|
||||
|
||||
## [1.3.1](https://github.com/niklasvh/html2canvas/compare/v1.3.0...v1.3.1) (2021-08-14)
|
||||
|
||||
|
||||
### fix
|
||||
|
||||
* multi arg transition/animation duration (#2657) ([1b55ed5](https://github.com/niklasvh/html2canvas/commit/1b55ed5668dcbbe1c6d8d7e94736d8f2da2d31c5)), closes [#2657](https://github.com/niklasvh/html2canvas/issues/2657)
|
||||
|
||||
|
||||
|
||||
# [1.3.0](https://github.com/niklasvh/html2canvas/compare/v1.2.2...v1.3.0) (2021-08-13)
|
||||
|
||||
|
||||
### feat
|
||||
|
||||
* add rtl render support (#2653) ([6947982](https://github.com/niklasvh/html2canvas/commit/694798284838b16882e648914da0905818aa366c)), closes [#2653](https://github.com/niklasvh/html2canvas/issues/2653)
|
||||
* correctly break graphemes (#2652) ([437b367](https://github.com/niklasvh/html2canvas/commit/437b367d3ba9dfd7f9a4c8042ac8d00208c09770)), closes [#2652](https://github.com/niklasvh/html2canvas/issues/2652)
|
||||
|
||||
### fix
|
||||
|
||||
* correctly handle allowTaint canvas cloning (#2649) ([c378e22](https://github.com/niklasvh/html2canvas/commit/c378e220694c14cb7b3b4b8650a7757f8fc23c7a)), closes [#2649](https://github.com/niklasvh/html2canvas/issues/2649)
|
||||
* finish animation/transitions for elements (#2632) ([969638f](https://github.com/niklasvh/html2canvas/commit/969638fb94a0a14c64a667fa6e5689f79d9f1044)), closes [#2632](https://github.com/niklasvh/html2canvas/issues/2632)
|
||||
|
||||
### test
|
||||
|
||||
* add letter-spacing test for zwj emoji (#2650) ([f919204](https://github.com/niklasvh/html2canvas/commit/f919204efa06af219f155ca279f96124bb92862b)), closes [#2650](https://github.com/niklasvh/html2canvas/issues/2650)
|
||||
|
||||
|
||||
|
||||
## [1.2.2](https://github.com/niklasvh/html2canvas/compare/v1.2.1...v1.2.2) (2021-08-10)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* add ios15 target (#2564) ([e429e04](https://github.com/niklasvh/html2canvas/commit/e429e0443adf5c7ca3041b97a8157b8911302206)), closes [#2564](https://github.com/niklasvh/html2canvas/issues/2564)
|
||||
|
||||
### docs
|
||||
|
||||
* update test previewer (#2637) ([7a06d0c](https://github.com/niklasvh/html2canvas/commit/7a06d0c2c2f3b8a1d1a8a85c540f8288b782e8c6)), closes [#2637](https://github.com/niklasvh/html2canvas/issues/2637)
|
||||
|
||||
### fix
|
||||
|
||||
* parsing counter content in pseudo element (#2640) ([1941b9e](https://github.com/niklasvh/html2canvas/commit/1941b9e0acfd9243da0beaf70e1643cab1b4a963)), closes [#2640](https://github.com/niklasvh/html2canvas/issues/2640)
|
||||
* radial gradient ry check (#2631) ([a0dd38a](https://github.com/niklasvh/html2canvas/commit/a0dd38a8be4e540ae1c1f4b4e41f6c386f3e454f)), closes [#2631](https://github.com/niklasvh/html2canvas/issues/2631)
|
||||
* test for ios range line break error (#2635) ([f43f942](https://github.com/niklasvh/html2canvas/commit/f43f942fcd793dde9cdc6c0438f379ec3c05c405)), closes [#2635](https://github.com/niklasvh/html2canvas/issues/2635)
|
||||
|
||||
### test
|
||||
|
||||
* large base64 encoded background (#2636) ([e36408a](https://github.com/niklasvh/html2canvas/commit/e36408ad030fe31acd9969a37fe24c1621c0bd04)), closes [#2636](https://github.com/niklasvh/html2canvas/issues/2636)
|
||||
|
||||
|
||||
|
||||
## [1.2.1](https://github.com/niklasvh/html2canvas/compare/v1.2.0...v1.2.1) (2021-08-05)
|
||||
|
||||
|
||||
### fix
|
||||
|
||||
* none image (#2627) ([6651fc6](https://github.com/niklasvh/html2canvas/commit/6651fc6789d5902d171dc53b4094887870433018)), closes [#2627](https://github.com/niklasvh/html2canvas/issues/2627)
|
||||
* type import that is only available ts 3.8 or higher (#2629) ([c5c6fa0](https://github.com/niklasvh/html2canvas/commit/c5c6fa00d71f36ef963ba5170ebc7b668d39c407)), closes [#2629](https://github.com/niklasvh/html2canvas/issues/2629)
|
||||
|
||||
|
||||
|
||||
# [1.2.0](https://github.com/niklasvh/html2canvas/compare/v1.1.5...v1.2.0) (2021-08-04)
|
||||
|
||||
|
||||
### fix
|
||||
|
||||
* element cropping & scrolling (#2625) ([878e37a](https://github.com/niklasvh/html2canvas/commit/878e37a24272d0412fe589975ef8eed931c56e0b)), closes [#2625](https://github.com/niklasvh/html2canvas/issues/2625)
|
||||
* overflow-wrap break-word (#2626) ([95a46b0](https://github.com/niklasvh/html2canvas/commit/95a46b00c53563722c035a0e45fdf5fb507275e4)), closes [#2626](https://github.com/niklasvh/html2canvas/issues/2626)
|
||||
|
||||
### test
|
||||
|
||||
* element with scrolled window (#2624) ([1338c7b](https://github.com/niklasvh/html2canvas/commit/1338c7b203535d53509416358d74014200a994eb)), closes [#2624](https://github.com/niklasvh/html2canvas/issues/2624)
|
||||
|
||||
|
||||
|
||||
## [1.1.5](https://github.com/niklasvh/html2canvas/compare/v1.1.4...v1.1.5) (2021-08-02)
|
||||
|
||||
|
||||
### docs
|
||||
|
||||
* update README to github discussion Q/A ([5dea36b](https://github.com/niklasvh/html2canvas/commit/5dea36bd6964164e8ba3f8780309e792f5d16255))
|
||||
|
||||
### fix
|
||||
|
||||
* emoji line breaking (fix #1813) (#2621) ([7d788c6](https://github.com/niklasvh/html2canvas/commit/7d788c6f3d221b87f6b59fcda8517731340b2d1f)), closes [#1813](https://github.com/niklasvh/html2canvas/issues/1813) [#2621](https://github.com/niklasvh/html2canvas/issues/2621) [#1813](https://github.com/niklasvh/html2canvas/issues/1813)
|
||||
* natural sizes for images with srcset (#2622) ([96e23d1](https://github.com/niklasvh/html2canvas/commit/96e23d185198b7131cf0cfa31c14c165790464e9)), closes [#2622](https://github.com/niklasvh/html2canvas/issues/2622)
|
||||
|
||||
|
||||
|
||||
## [1.1.4](https://github.com/niklasvh/html2canvas/compare/v1.1.3...v1.1.4) (2021-07-15)
|
||||
|
||||
|
||||
### feat
|
||||
|
||||
* add support for webkit-text-stroke and paint-order (#2591) ([522e5aa](https://github.com/niklasvh/html2canvas/commit/522e5aac5fdad090953d095b5d558053a5e2d43d)), closes [#2591](https://github.com/niklasvh/html2canvas/issues/2591)
|
||||
|
||||
### fix
|
||||
|
||||
* don't copy 'all' css property (#2586) ([fa60716](https://github.com/niklasvh/html2canvas/commit/fa60716d07ed590ec64543a586a7960cbc8557df)), closes [#2586](https://github.com/niklasvh/html2canvas/issues/2586)
|
||||
* svg d path getting truncated on copy (#2589) ([dd6d885](https://github.com/niklasvh/html2canvas/commit/dd6d8856eca820a13a0990c467b9e531433fd4a9)), closes [#2589](https://github.com/niklasvh/html2canvas/issues/2589)
|
||||
* text position for form elements and list markers (#2588) ([cd99f11](https://github.com/niklasvh/html2canvas/commit/cd99f11b1b9eb1260a548a63e2a370a0a5ddafa0)), closes [#2588](https://github.com/niklasvh/html2canvas/issues/2588)
|
||||
* this.canvas.ownerDocument is undefined (#2590) ([45efe54](https://github.com/niklasvh/html2canvas/commit/45efe54da8145f97b9ee0463e686103280e3c8b1)), closes [#2590](https://github.com/niklasvh/html2canvas/issues/2590)
|
||||
* word-break seperators (#2593) ([e9f7f48](https://github.com/niklasvh/html2canvas/commit/e9f7f48d571304be14610a181feedca3c3b42864)), closes [#2593](https://github.com/niklasvh/html2canvas/issues/2593)
|
||||
|
||||
### test
|
||||
|
||||
* refactor language tests (#2594) ([4c360fc](https://github.com/niklasvh/html2canvas/commit/4c360fc1f059f4dcab71a79f9dc8a5b2e25411ea)), closes [#2594](https://github.com/niklasvh/html2canvas/issues/2594)
|
||||
* update box-shadow with radius ([578bb77](https://github.com/niklasvh/html2canvas/commit/578bb771bfeb7e81362e9e355d6cc9ae910e3920))
|
||||
|
||||
|
||||
|
||||
## [1.1.3](https://github.com/niklasvh/html2canvas/compare/v1.1.2...v1.1.3) (2021-07-14)
|
||||
|
||||
|
||||
### feat
|
||||
|
||||
* allow access to reference element in onclone (#2584) ([58b4591](https://github.com/niklasvh/html2canvas/commit/58b45911741c0dbbccd462b2976560bb3999eaef)), closes [#2584](https://github.com/niklasvh/html2canvas/issues/2584)
|
||||
* support for custom and slot elements (#2581) ([acb4cd2](https://github.com/niklasvh/html2canvas/commit/acb4cd24b85527908c02a60794768949578678f0)), closes [#2581](https://github.com/niklasvh/html2canvas/issues/2581)
|
||||
|
||||
### fix
|
||||
|
||||
* iframe load to ensure images are loaded (#2577) ([eeb5a3e](https://github.com/niklasvh/html2canvas/commit/eeb5a3ea1d6c94e0f6dcfd40695eb88ebb3e0041)), closes [#2577](https://github.com/niklasvh/html2canvas/issues/2577)
|
||||
* image blob rendering ([1acdc82](https://github.com/niklasvh/html2canvas/commit/1acdc827a4e05933c2f7c9558405c66b7cd82f58))
|
||||
* responsive svg images (#2583) ([92fa448](https://github.com/niklasvh/html2canvas/commit/92fa448913192d5e4e82bfe14f6644b669d4e6ef)), closes [#2583](https://github.com/niklasvh/html2canvas/issues/2583)
|
||||
|
||||
### test
|
||||
|
||||
* add test cases for text-stroke and textarea from (#1540 and #2132) (#2585) ([1d00bfe](https://github.com/niklasvh/html2canvas/commit/1d00bfe175d51e663d0bae88b6dbd10a266a71f1)), closes [#1540](https://github.com/niklasvh/html2canvas/issues/1540) [#2132](https://github.com/niklasvh/html2canvas/issues/2132) [#2585](https://github.com/niklasvh/html2canvas/issues/2585)
|
||||
|
||||
|
||||
|
||||
## [1.1.2](https://github.com/niklasvh/html2canvas/compare/v1.1.1...v1.1.2) (2021-07-13)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* implement screenshot diffing (#2571) ([e29af58](https://github.com/niklasvh/html2canvas/commit/e29af586618125bbad10ad6bee3d69fddbc5d22a)), closes [#2571](https://github.com/niklasvh/html2canvas/issues/2571)
|
||||
|
||||
### fix
|
||||
|
||||
* logger unique names (#2575) ([1715854](https://github.com/niklasvh/html2canvas/commit/171585491dd1bee4f30691328bd22e978f3ac79e)), closes [#2575](https://github.com/niklasvh/html2canvas/issues/2575)
|
||||
* text-shadow position with baseline (#2576) ([439e365](https://github.com/niklasvh/html2canvas/commit/439e365ea8c703b528778a268dcfc3bf9ccad6a9)), closes [#2576](https://github.com/niklasvh/html2canvas/issues/2576)
|
||||
|
||||
|
||||
|
||||
## [1.1.1](https://github.com/niklasvh/html2canvas/compare/v1.1.0...v1.1.1) (2021-07-12)
|
||||
|
||||
|
||||
### fix
|
||||
|
||||
* allow proxy url with parameters (#2100) ([a4a3ce8](https://github.com/niklasvh/html2canvas/commit/a4a3ce8a2eb6a4f43f1bc9a7935eb16f2b98a3cd)), closes [#2100](https://github.com/niklasvh/html2canvas/issues/2100)
|
||||
* crash on background-size with calc() (fix #2469) (#2569) ([084017a](https://github.com/niklasvh/html2canvas/commit/084017a67319a993d73c6bdf612dd8532f1b8dbe)), closes [#2469](https://github.com/niklasvh/html2canvas/issues/2469) [#2569](https://github.com/niklasvh/html2canvas/issues/2569)
|
||||
* handle unhandled promise rejections (#2568) ([4555940](https://github.com/niklasvh/html2canvas/commit/4555940d0bc17b7fd9327dd0164c382a3dbf1858)), closes [#2568](https://github.com/niklasvh/html2canvas/issues/2568)
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/niklasvh/html2canvas/compare/v1.0.0...v1.1.0) (2021-07-11)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* fail build if no artifacts uploaded (#2566) ([e7a021a](https://github.com/niklasvh/html2canvas/commit/e7a021ab931f3c0de060b4643e88fad85345c3d3)), closes [#2566](https://github.com/niklasvh/html2canvas/issues/2566)
|
||||
|
||||
### deps
|
||||
|
||||
* update dependencies with lint fixes (#2565) ([b2902ec](https://github.com/niklasvh/html2canvas/commit/b2902ec31c2a414ea26f864f8e00aa8846890ffc)), closes [#2565](https://github.com/niklasvh/html2canvas/issues/2565)
|
||||
|
||||
### docs
|
||||
|
||||
* update border-style support ([cf35a28](https://github.com/niklasvh/html2canvas/commit/cf35a282b2c9d41b601c3148e8c08fe7ba61a5f9))
|
||||
|
||||
### fix
|
||||
|
||||
* Ensure resizeImage's canvas has at least 1px of width and height (#2409) ([bb92371](https://github.com/niklasvh/html2canvas/commit/bb9237155cf0ec090432ee6c5d9c555eb6ffa81f)), closes [#2409](https://github.com/niklasvh/html2canvas/issues/2409)
|
||||
* text-decoration-line fallback (#2088) (#2567) ([44296e5](https://github.com/niklasvh/html2canvas/commit/44296e529368140ec06a937383e05f3074b19ee2)), closes [#2088](https://github.com/niklasvh/html2canvas/issues/2088) [#2567](https://github.com/niklasvh/html2canvas/issues/2567)
|
||||
* use baseline for text positioning (#2109) ([85f79c1](https://github.com/niklasvh/html2canvas/commit/85f79c1a5e4c0b422ace552c430dd389c8477a44)), closes [#2109](https://github.com/niklasvh/html2canvas/issues/2109)
|
||||
|
||||
|
||||
|
||||
# [1.0.0](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.7...v1.0.0) (2021-07-04)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* update docs publish action (#2451) ([7222aba](https://github.com/niklasvh/html2canvas/commit/7222aba1b42138c3d466525172411b3d9869095f)), closes [#2451](https://github.com/niklasvh/html2canvas/issues/2451)
|
||||
|
||||
### deps
|
||||
|
||||
* update www deps (#2525) ([2a013e2](https://github.com/niklasvh/html2canvas/commit/2a013e20c814b7dbaea98f54f0bde8f553eb79a2)), closes [#2525](https://github.com/niklasvh/html2canvas/issues/2525)
|
||||
|
||||
### feat
|
||||
|
||||
* add support for border-style dashed, dotted, double (#2531) ([72cd528](https://github.com/niklasvh/html2canvas/commit/72cd5284296e4cdb3fe88f2982ec7528604b6618))
|
||||
|
||||
### fix
|
||||
|
||||
* opacity with overflow hidden (#2450) ([82b7da5](https://github.com/niklasvh/html2canvas/commit/82b7da558c342e7f53d298bb1d843a5db86c3b21)), closes [#2450](https://github.com/niklasvh/html2canvas/issues/2450)
|
||||
* top right border radius (#2522) ([ba17267](https://github.com/niklasvh/html2canvas/commit/ba172678f07f962e9f54b398df087e86217d7a13))
|
||||
|
||||
### test
|
||||
|
||||
* update karma runner (#2524) ([ff35c7d](https://github.com/niklasvh/html2canvas/commit/ff35c7dbd33f863f5b614d778baf8cb1e8dded60)), closes [#2524](https://github.com/niklasvh/html2canvas/issues/2524)
|
||||
|
||||
|
||||
|
||||
# [1.0.0-rc.7](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.6...v1.0.0-rc.7) (2020-08-09)
|
||||
|
||||
|
||||
### fix
|
||||
|
||||
* concatenate contiguous font-family tokens (#2219) ([bacfadf](https://github.com/niklasvh/html2canvas/commit/bacfadff96d907d9e8ab4ef515ca6487de9e51fc)), closes [#2219](https://github.com/niklasvh/html2canvas/issues/2219)
|
||||
* external styles on svg elements (#2320) ([1514220](https://github.com/niklasvh/html2canvas/commit/1514220812cfb22d64d0974558d9c14fe90a41d3)), closes [#2320](https://github.com/niklasvh/html2canvas/issues/2320)
|
||||
|
||||
|
||||
|
||||
# [1.0.0-rc.6](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.5...v1.0.0-rc.6) (2020-08-08)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* Azure Pipelines: upgrade from macOS 10.13 -> 10.14 (#2204) ([c366e87](https://github.com/niklasvh/html2canvas/commit/c366e8790d346ea981b24b7425aef6bf6d7ebcec)), closes [#2204](https://github.com/niklasvh/html2canvas/issues/2204)
|
||||
* build docs (#2305) ([51de347](https://github.com/niklasvh/html2canvas/commit/51de34787ad8aba3f213800be45e878cddb064e9)), closes [#2305](https://github.com/niklasvh/html2canvas/issues/2305)
|
||||
|
||||
### fix
|
||||
|
||||
* #1868 Clone node, Setting className for SVG element raises error (#2079) ([f139b51](https://github.com/niklasvh/html2canvas/commit/f139b513c5cf9673dc727fd47124e0d779891e3a)), closes [#1868](https://github.com/niklasvh/html2canvas/issues/1868) [#2079](https://github.com/niklasvh/html2canvas/issues/2079) [#1868](https://github.com/niklasvh/html2canvas/issues/1868)
|
||||
* image loading="lazy" fix #2312 (#2314) ([f23e6f6](https://github.com/niklasvh/html2canvas/commit/f23e6f6f2690dc0dbd02621c3bb81025904e6647)), closes [#2312](https://github.com/niklasvh/html2canvas/issues/2312) [#2314](https://github.com/niklasvh/html2canvas/issues/2314)
|
||||
|
||||
|
||||
|
||||
# [1.0.0-rc.5](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.4...v1.0.0-rc.5) (2019-09-27)
|
||||
|
||||
|
||||
### fix
|
||||
|
||||
* correctly respect logging option (#2013) ([34b06d6365603c3b16664ab7804efe94c7945946](https://github.com/niklasvh/html2canvas/commit/34b06d6365603c3b16664ab7804efe94c7945946)), closes [#2013](https://github.com/niklasvh/html2canvas/issues/2013)
|
||||
* safari pseudo element content parsing (#2018) ([3f599103fb139f218ffe917800e74af2c7cc7ad5](https://github.com/niklasvh/html2canvas/commit/3f599103fb139f218ffe917800e74af2c7cc7ad5)), closes [#2018](https://github.com/niklasvh/html2canvas/issues/2018)
|
||||
* using existing canvas option (#2017) ([076492042a73d67b30e4562f2964200e07d25f5e](https://github.com/niklasvh/html2canvas/commit/076492042a73d67b30e4562f2964200e07d25f5e)), closes [#2017](https://github.com/niklasvh/html2canvas/issues/2017)
|
||||
|
||||
|
||||
|
||||
# [1.0.0-rc.4](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.3...v1.0.0-rc.4) (2019-09-22)
|
||||
|
||||
|
||||
### docs
|
||||
|
||||
* fix typo (#1864) ([9a63797aa7fb81454008745d2a1c069ca24339a4](https://github.com/niklasvh/html2canvas/commit/9a63797aa7fb81454008745d2a1c069ca24339a4)), closes [#1864](https://github.com/niklasvh/html2canvas/issues/1864)
|
||||
|
||||
### feat
|
||||
|
||||
* ignore unsupported image functions (#1873) ([61f4819e02102b112513d57b16ec7d37e989af20](https://github.com/niklasvh/html2canvas/commit/61f4819e02102b112513d57b16ec7d37e989af20)), closes [#1873](https://github.com/niklasvh/html2canvas/issues/1873)
|
||||
|
||||
### fix
|
||||
|
||||
* correctly render partial borders (fix #1920) (#2010) ([eedb81ef9e114366a7e286e975659360cf9d0983](https://github.com/niklasvh/html2canvas/commit/eedb81ef9e114366a7e286e975659360cf9d0983)), closes [#1920](https://github.com/niklasvh/html2canvas/issues/1920) [#2010](https://github.com/niklasvh/html2canvas/issues/2010)
|
||||
* nested z-index ordering (#2011) ([00555cf1efddfed5877811d8a03a326f9943ab06](https://github.com/niklasvh/html2canvas/commit/00555cf1efddfed5877811d8a03a326f9943ab06)), closes [#2011](https://github.com/niklasvh/html2canvas/issues/2011) [#1978](https://github.com/niklasvh/html2canvas/issues/1978)
|
||||
* null backgroundColor option as transparent (#2012) ([7d3456b78c37e7333db087601805b32ec7ca0253](https://github.com/niklasvh/html2canvas/commit/7d3456b78c37e7333db087601805b32ec7ca0253)), closes [#2012](https://github.com/niklasvh/html2canvas/issues/2012)
|
||||
* zero size iframe rendering (#1863) ([81dcf7b6be66920260a60908aa4b86e7530f6e17](https://github.com/niklasvh/html2canvas/commit/81dcf7b6be66920260a60908aa4b86e7530f6e17)), closes [#1863](https://github.com/niklasvh/html2canvas/issues/1863)
|
||||
|
||||
|
||||
|
||||
# [1.0.0-rc.3](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.2...v1.0.0-rc.3) (2019-05-30)
|
||||
|
||||
|
||||
### fix
|
||||
|
||||
* stack exceeding for css tokenizer (#1862) ([cbaecdca28cfaf9bd854e1b0c005cc8058208b36](https://github.com/niklasvh/html2canvas/commit/cbaecdca28cfaf9bd854e1b0c005cc8058208b36)), closes [#1862](https://github.com/niklasvh/html2canvas/issues/1862)
|
||||
* typescript options type definition (#1861) ([cae44a6f0a6649bd8a7c4250a20792bb5c2e5b42](https://github.com/niklasvh/html2canvas/commit/cae44a6f0a6649bd8a7c4250a20792bb5c2e5b42)), closes [#1861](https://github.com/niklasvh/html2canvas/issues/1861)
|
||||
|
||||
|
||||
|
||||
# [1.0.0-rc.2](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.1...v1.0.0-rc.2) (2019-05-29)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* refactor browser tests (#1804) ([a7d881019bfe1fd6404c341ca1c6fa69e0274ef5](https://github.com/niklasvh/html2canvas/commit/a7d881019bfe1fd6404c341ca1c6fa69e0274ef5)), closes [#1804](https://github.com/niklasvh/html2canvas/issues/1804)
|
||||
|
||||
### docs
|
||||
|
||||
* fix README documentation ([20a797cbeb21baca4ce5b9a0642a5959cdf94a51](https://github.com/niklasvh/html2canvas/commit/20a797cbeb21baca4ce5b9a0642a5959cdf94a51))
|
||||
* remove dead donation link (fix #1802) ([43058241b420a5dabe94b0a4e4f6534d16a75ec0](https://github.com/niklasvh/html2canvas/commit/43058241b420a5dabe94b0a4e4f6534d16a75ec0)), closes [#1802](https://github.com/niklasvh/html2canvas/issues/1802)
|
||||
|
||||
### fix
|
||||
|
||||
* multi token overflow #1850 (#1851) ([409674fba6f8038eb174b9c89360ef8b342971e9](https://github.com/niklasvh/html2canvas/commit/409674fba6f8038eb174b9c89360ef8b342971e9)), closes [#1850](https://github.com/niklasvh/html2canvas/issues/1850) [#1851](https://github.com/niklasvh/html2canvas/issues/1851)
|
||||
|
||||
### test
|
||||
|
||||
* include reftests previewer with docs website (#1799) ([cdc4ca8296570bf842e937c6fb7cc32a1ce2bc09](https://github.com/niklasvh/html2canvas/commit/cdc4ca8296570bf842e937c6fb7cc32a1ce2bc09)), closes [#1799](https://github.com/niklasvh/html2canvas/issues/1799)
|
||||
|
||||
|
||||
|
||||
# [1.0.0-rc.1](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.0...v1.0.0-rc.1) (2019-04-10)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* add ios simulator tests (#1794) ([a63cb3c0f132b1af915d9ef55a4c174f6e5502ce](https://github.com/niklasvh/html2canvas/commit/a63cb3c0f132b1af915d9ef55a4c174f6e5502ce)), closes [#1794](https://github.com/niklasvh/html2canvas/issues/1794)
|
||||
|
||||
### docs
|
||||
|
||||
* fix release date in changelog ([238de790a9f223becbc8726633c0f2a2dabf2cb7](https://github.com/niklasvh/html2canvas/commit/238de790a9f223becbc8726633c0f2a2dabf2cb7))
|
||||
* remove invalid `async` option from docs (fix #1769) (#1796) ([7775d3c0d6f3efca00611bedd5fc9200689a9f7a](https://github.com/niklasvh/html2canvas/commit/7775d3c0d6f3efca00611bedd5fc9200689a9f7a)), closes [#1769](https://github.com/niklasvh/html2canvas/issues/1769) [#1796](https://github.com/niklasvh/html2canvas/issues/1796)
|
||||
|
||||
### fix
|
||||
|
||||
* context scale for high resolution displays with foreignobjectrendering (#1782) ([7027900f4993dcd00745a4db045ed1c0e3255f8a](https://github.com/niklasvh/html2canvas/commit/7027900f4993dcd00745a4db045ed1c0e3255f8a)), closes [#1782](https://github.com/niklasvh/html2canvas/issues/1782)
|
||||
* don't apply text shadows on elements (#1795) ([397595afb59ee50f0d128abb5945b5b9ddc6650d](https://github.com/niklasvh/html2canvas/commit/397595afb59ee50f0d128abb5945b5b9ddc6650d)), closes [#1795](https://github.com/niklasvh/html2canvas/issues/1795)
|
||||
* safari data url taints (#1797) ([4e4a231683904dfdc1f82472ece5a160a158dbb8](https://github.com/niklasvh/html2canvas/commit/4e4a231683904dfdc1f82472ece5a160a158dbb8)), closes [#1797](https://github.com/niklasvh/html2canvas/issues/1797)
|
||||
|
||||
### test
|
||||
|
||||
* fix RefTestRenderer.js inclusion with karma ([49f87fb680dbfe1898b3aeb60f2f5c3a93bfbe6d](https://github.com/niklasvh/html2canvas/commit/49f87fb680dbfe1898b3aeb60f2f5c3a93bfbe6d))
|
||||
|
||||
|
||||
|
||||
# [1.0.0-rc.0](https://github.com/niklasvh/html2canvas/compare/v1.0.0-alpha.12...v1.0.0-rc.0) (2019-04-07)
|
||||
|
||||
|
||||
### build
|
||||
|
||||
* update webpack and babel (#1793) ([44f3d79f68836624c2673a86f9ad47c17ef843c3](https://github.com/niklasvh/html2canvas/commit/44f3d79f68836624c2673a86f9ad47c17ef843c3)), closes [#1793](https://github.com/niklasvh/html2canvas/issues/1793)
|
||||
|
||||
### ci
|
||||
|
||||
* automate changelog generation (#1792) ([7ebef72e927eaafd34a1792ece431d2a73109230](https://github.com/niklasvh/html2canvas/commit/7ebef72e927eaafd34a1792ece431d2a73109230)), closes [#1792](https://github.com/niklasvh/html2canvas/issues/1792)
|
||||
* Improve CI pipeline (#1790) ([c45ef099fe8f7142e174f4fce39448a370a987d5](https://github.com/niklasvh/html2canvas/commit/c45ef099fe8f7142e174f4fce39448a370a987d5)), closes [#1790](https://github.com/niklasvh/html2canvas/issues/1790)
|
||||
|
||||
### docs
|
||||
|
||||
* improve canvas size limit documentation (#1576) ([3212184146b33c3564c2f416e1bfda911737c38b](https://github.com/niklasvh/html2canvas/commit/3212184146b33c3564c2f416e1bfda911737c38b)), closes [#1576](https://github.com/niklasvh/html2canvas/issues/1576)
|
||||
|
||||
### fix
|
||||
|
||||
* enforce colorstop min 0 (#1743) ([349bbf137abd83464e074db3948fc79a541c2ef3](https://github.com/niklasvh/html2canvas/commit/349bbf137abd83464e074db3948fc79a541c2ef3)), closes [#1743](https://github.com/niklasvh/html2canvas/issues/1743)
|
||||
* prevent unhandled promise rejections for hidden frames (#1762) ([5cbe5db35155e3a9790a30de09feb17843053b7a](https://github.com/niklasvh/html2canvas/commit/5cbe5db35155e3a9790a30de09feb17843053b7a)), closes [#1762](https://github.com/niklasvh/html2canvas/issues/1762)
|
||||
* wrap .sheet.cssRules access in try...catch. (#1693) ([2c018d19875ced30caafdc40f84ca531de6e6f91](https://github.com/niklasvh/html2canvas/commit/2c018d19875ced30caafdc40f84ca531de6e6f91)), closes [#1693](https://github.com/niklasvh/html2canvas/issues/1693)
|
||||
|
||||
|
||||
|
||||
# [1.0.0-alpha.12](https://github.com/niklasvh/html2canvas/compare/v1.0.0-alpha.12...v1.0.0-alpha.13) (2018-04-05)
|
||||
* Fix white space appearing on element rendering (Fix #1438)
|
||||
* Reset canvas transform on finish (Fix #1494)
|
||||
|
||||
# v1.0.0-alpha.11 - 1.4.2018
|
||||
* Fix IE11 member not found error
|
||||
* Support blob image resources in non-foreignObjectRendering mode
|
||||
|
||||
# v1.0.0-alpha.10 - 15.2.2018
|
||||
* Re-introduce `onclone` option (Fix #1434)
|
||||
* Add `ignoreElements` predicate function option
|
||||
* Fix version console logging
|
||||
|
||||
# v1.0.0-alpha.9 - 7.1.2018
|
||||
* Fix dynamic style sheets
|
||||
* Fix > 50% border-radius values
|
||||
|
||||
# v1.0.0-alpha.8 - 2.1.2018
|
||||
* Use correct doctype in cloned Document (Fix #1298)
|
||||
* Fix individual border rendering (Fix #1349)
|
||||
|
||||
# v1.0.0-alpha.7 - 31.12.2017
|
||||
* Fix form input rendering (#1338)
|
||||
* Improve word line breaking algorithm
|
||||
|
||||
# v1.0.0-alpha.6 - 28.12.2017
|
||||
* Fix list-style: none (#1340)
|
||||
* Extend supported values for pseudo element content
|
||||
|
||||
# v1.0.0-alpha.5 - 21.12.2017
|
||||
* Fix underline positioning
|
||||
* Fix canvas rendering on Chrome
|
||||
* Fix overflow: auto
|
||||
* Added support for rendering list-style
|
||||
|
||||
v1.0.0-alpha.4 - 12.12.2017
|
||||
* Fix rendering with multiple fonts defined (Fix #796)
|
||||
* Add support for radial-gradients
|
||||
* Fix logging option (#1302)
|
||||
* Add support for rendering webgl canvas content (#646)
|
||||
* Fix external SVG loading with proxies (#802)
|
||||
|
||||
# v1.0.0-alpha.3 - 9.12.2017
|
||||
* Disable `foreignObjectRendering` by default (#1295)
|
||||
* Fix background-size when using background-origin and background-size: cover/contain (#1299)
|
||||
* Added support for background-origin: content-box (#1299)
|
||||
|
||||
# v1.0.0-alpha.2 - 7.12.2017
|
||||
* Fix scroll positions for CanvasRenderer (#1259)
|
||||
* Fix `data-html2canvas-ignore` attribute (#1253)
|
||||
* Fix decimal `letter-spacing` values (#1293)
|
||||
|
||||
# v1.0.0-alpha.1 - 5.12.2017
|
||||
* Complete rewrite of library
|
||||
##### Breaking Changes #####
|
||||
* Remove deprecated onrendered callback, calling `html2canvas` returns a `Promise<HTMLCanvasElement>`
|
||||
* Removed option `type`, same results can be achieved by assigning `x`, `y`, `scrollX`, `scrollY`, `width` and `height` properties.
|
||||
|
||||
## New featues / fixes
|
||||
* Add support for scaling canvas (defaults to device pixel ratio)
|
||||
* Add support for multiple text-shadows
|
||||
* Add support for multiple text-decorations
|
||||
* Add support for text-decoration-color
|
||||
* Add support for percentage values for border-radius
|
||||
* Correctly handle px and percentage values in linear-gradients
|
||||
* Correctly support all angle types for linear-gradients
|
||||
* Add support for multiple values for background-repeat, background-position and background-size
|
||||
|
||||
# v0.5.0-beta4 - 23.1.2016
|
||||
* Fix logger requiring access to window object
|
||||
* Derequire browserify build
|
||||
* Fix rendering of specific elements when window is scrolled and `type` isn't set to `view`
|
||||
|
||||
# v0.5.0-beta3 - 6.12.2015
|
||||
* Handle color names in linear gradients
|
||||
|
||||
# v0.5.0-beta2 - 20.10.2015
|
||||
* Remove Promise polyfill (use native or provide it yourself)
|
||||
|
||||
# v0.5.0-beta1 - 19.10.2015
|
||||
* Fix bug with unmatched color stops in gradients
|
||||
* Fix scrolling issues with iOS
|
||||
* Correctly handle named colors in gradients
|
||||
* Accept matrix3d transforms
|
||||
* Fix transparent colors breaking gradients
|
||||
* Preserve scrolling positions on render
|
||||
|
||||
# v0.5.0-alpha2 - 3.2.2015
|
||||
* Switch to using browserify for building
|
||||
* Fix (#517) Chrome stretches background images with 'auto' or single attributes
|
||||
|
||||
# v0.5.0-alpha - 19.1.2015
|
||||
* Complete rewrite of library
|
||||
* Switched interface to return Promise
|
||||
* Uses hidden iframe window to perform rendering, allowing async rendering and doesn't force the viewport to be scrolled to the top anymore.
|
||||
* Better support for unicode
|
||||
* Checkbox/radio button rendering
|
||||
* SVG rendering
|
||||
* iframe rendering
|
||||
* Changed format for proxy requests, permitting binary responses with CORS headers as well
|
||||
* Fixed many layering issues (see z-index tests)
|
||||
|
||||
# v0.4.1 - 7.9.2013
|
||||
* Added support for bower
|
||||
* Improved z-index ordering
|
||||
* Basic implementation for CSS transformations
|
||||
* Fixed inline text in top element
|
||||
* Basic implementation for text-shadow
|
||||
|
||||
# v0.4.0 - 30.1.2013
|
||||
* Added rendering tests with <a href="https://github.com/niklasvh/webdriver.js">webdriver</a>
|
||||
* Switched to using grunt for building
|
||||
* Removed support for IE<9, including any FlashCanvas bits
|
||||
* Support for border-radius
|
||||
* Support for multiple background images, size, and clipping
|
||||
* Support for :before and :after pseudo elements
|
||||
* Support for placeholder rendering
|
||||
* Reformatted all tests to small units to test specific features
|
||||
|
||||
# v0.3.4 - 26.6.2012
|
||||
|
||||
* Removed (last?) jQuery dependencies (<a href="https://github.com/niklasvh/html2canvas/commit/343b86705fe163766fcf735eb0217130e4bd5b17">niklasvh</a>)
|
||||
* SVG-powered rendering (<a href="https://github.com/niklasvh/html2canvas/commit/67d3e0d0f59a5a654caf71a2e3be6494ff146c75">niklasvh</a>)
|
||||
* Radial gradients (<a href="https://github.com/niklasvh/html2canvas/commit/4f22c18043a73c0c3bbf3b5e4d62714c56acd3c7">SunboX</a>)
|
||||
* Split renderers to their own objects (<a href="https://github.com/niklasvh/html2canvas/commit/94f2f799a457cd29a21cc56ef8c06f1697866739">niklasvh</a>)
|
||||
* Simplified API, cleaned up code (<a href="https://github.com/niklasvh/html2canvas/commit/c7d526c9eaa6a4abf4754d205fe1dee360c7660e">niklasvh</a>)
|
||||
|
||||
# v0.3.3 - 2.3.2012
|
||||
|
||||
* SVG taint fix, and additional taint testing options for rendering (<a href="https://github.com/niklasvh/html2canvas/commit/2dc8b9385e656696cb019d615bdfa1d98b17d5d4">niklasvh</a>)
|
||||
* Added support for CORS images and option to create canvas as tainted (<a href="https://github.com/niklasvh/html2canvas/commit/3ad49efa0032cde25c6ed32a39e35d1505d3b2ef">niklasvh</a>)
|
||||
* Improved minification saved ~1K! (<a href="https://github.com/cobexer/html2canvas/commit/b82be022b2b9240bd503e078ac980bde2b953e43">cobexer</a>)
|
||||
* Added integrated support for Flashcanvas (<a href="https://github.com/niklasvh/html2canvas/commit/e9257191519f67d74fd5e364d8dee3c0963ba5fc">niklasvh</a>)
|
||||
* Fixed a variety of legacy IE bugs (<a href="https://github.com/niklasvh/html2canvas/commit/b65357c55d0701017bafcd357bc654b54d458f8f">niklasvh</a>)
|
||||
|
||||
# v0.3.2 - 20.2.2012
|
||||
|
||||
* Added changelog!
|
||||
* Added bookmarklet (<a href="https://github.com/niklasvh/html2canvas/commit/b320dd306e1a2d32a3bc5a71b6ebf6d8c060cde5">cobexer</a>)
|
||||
* Option to select single element to render (<a href="https://github.com/niklasvh/html2canvas/commit/0cb252ada91c84ef411288b317c03e97da1f12ad">niklasvh</a>)
|
||||
* Fixed closure compiler warnings (<a href="https://github.com/niklasvh/html2canvas/commit/36ff1ec7aadcbdf66851a0b77f0b9e87e4a8e4a1">cobexer</a>)
|
||||
* Enable profiling in FF (<a href="https://github.com/niklasvh/html2canvas/commit/bbd75286a8406cf9e5aea01fdb7950d547edefb9">cobexer</a>)
|
37
LICENSE
37
LICENSE
@ -1,21 +1,22 @@
|
||||
/*
|
||||
* The MIT License
|
||||
Copyright (c) 2012 Niklas von Hertzen
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
73
README.md
Normal file
73
README.md
Normal file
@ -0,0 +1,73 @@
|
||||
html2canvas
|
||||
===========
|
||||
|
||||
[Homepage](https://html2canvas.hertzen.com) | [Downloads](https://github.com/niklasvh/html2canvas/releases) | [Questions](https://github.com/niklasvh/html2canvas/discussions/categories/q-a)
|
||||
|
||||
[](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||

|
||||
[](https://www.npmjs.org/package/html2canvas)
|
||||
[](https://www.npmjs.org/package/html2canvas)
|
||||
|
||||
#### JavaScript HTML renderer ####
|
||||
|
||||
The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser. The screenshot is based on the DOM and as such may not be 100% accurate to the real representation as it does not make an actual screenshot, but builds the screenshot based on the information available on the page.
|
||||
|
||||
|
||||
### How does it work? ###
|
||||
The script renders the current page as a canvas image, by reading the DOM and the different styles applied to the elements.
|
||||
|
||||
It does **not require any rendering from the server**, as the whole image is created on the **client's browser**. However, as it is heavily dependent on the browser, this library is *not suitable* to be used in nodejs.
|
||||
It doesn't magically circumvent any browser content policy restrictions either, so rendering cross-origin content will require a [proxy](https://github.com/niklasvh/html2canvas/wiki/Proxies) to get the content to the [same origin](http://en.wikipedia.org/wiki/Same_origin_policy).
|
||||
|
||||
The script is still in a **very experimental state**, so I don't recommend using it in a production environment nor start building applications with it yet, as there will be still major changes made.
|
||||
|
||||
### Browser compatibility ###
|
||||
|
||||
The library should work fine on the following browsers (with `Promise` polyfill):
|
||||
|
||||
* Firefox 3.5+
|
||||
* Google Chrome
|
||||
* Opera 12+
|
||||
* IE9+
|
||||
* Safari 6+
|
||||
|
||||
As each CSS property needs to be manually built to be supported, there are a number of properties that are not yet supported.
|
||||
|
||||
### Usage ###
|
||||
|
||||
The html2canvas library utilizes `Promise`s and expects them to be available in the global context. If you wish to
|
||||
support [older browsers](http://caniuse.com/#search=promise) that do not natively support `Promise`s, please include a polyfill such as
|
||||
[es6-promise](https://github.com/jakearchibald/es6-promise) before including `html2canvas`.
|
||||
|
||||
To render an `element` with html2canvas, simply call:
|
||||
` html2canvas(element[, options]);`
|
||||
|
||||
The function returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) containing the `<canvas>` element. Simply add a promise fulfillment handler to the promise using `then`:
|
||||
|
||||
html2canvas(document.body).then(function(canvas) {
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
|
||||
### Building ###
|
||||
|
||||
You can download ready builds [here](https://github.com/niklasvh/html2canvas/releases).
|
||||
|
||||
Clone git repository:
|
||||
|
||||
$ git clone git://github.com/niklasvh/html2canvas.git
|
||||
|
||||
Install dependencies:
|
||||
|
||||
$ npm install
|
||||
|
||||
Build browser bundle
|
||||
|
||||
$ npm run build
|
||||
|
||||
### Examples ###
|
||||
|
||||
For more information and examples, please visit the [homepage](https://html2canvas.hertzen.com) or try the [test console](https://html2canvas.hertzen.com/tests/).
|
||||
|
||||
### Contributing ###
|
||||
|
||||
If you wish to contribute to the project, please send the pull requests to the develop branch. Before submitting any changes, try and test that the changes work with all the support browsers. If some CSS property isn't supported or is incomplete, please create appropriate tests for it as well before submitting any code changes.
|
80
build.xml
80
build.xml
@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project name="html2canvas" basedir=".">
|
||||
<property name="src.dir" location="src"/>
|
||||
<property name="lib.dir" location="lib"/>
|
||||
<property name="build.dir" location="build"/>
|
||||
<property name="dist" location="dist"/>
|
||||
|
||||
|
||||
<property name="jquery-externs" value="jquery-1.4.4.externs.js"/>
|
||||
|
||||
|
||||
<property name="JS_NAME" value="html2canvas.js"/>
|
||||
<property name="JS_NAME_MIN" value="html2canvas.min.js"/>
|
||||
<property name="JQUERY_PLUGIN_NAME" value="jquery.plugin.html2canvas.js"/>
|
||||
|
||||
|
||||
<path id="sourcefiles">
|
||||
<fileset dir="${src.dir}" includes="LICENSE"/>
|
||||
<fileset dir="." includes="LICENSE"/>
|
||||
<fileset dir="${src.dir}" includes="Core.js"/>
|
||||
<fileset dir="${src.dir}" includes="Background.js"/>
|
||||
<fileset dir="${src.dir}" includes="Border.js"/>
|
||||
<fileset dir="${src.dir}" includes="Draw.js"/>
|
||||
<fileset dir="${src.dir}" includes="Forms.js"/>
|
||||
<fileset dir="${src.dir}" includes="Images.js"/>
|
||||
<fileset dir="${src.dir}" includes="Renderer.js"/>
|
||||
<fileset dir="${src.dir}" includes="Lists.js"/>
|
||||
<fileset dir="${src.dir}" includes="Text.js"/>
|
||||
<fileset dir="${src.dir}" includes="Traversing.js"/>
|
||||
<fileset dir="${src.dir}" includes="Util.js"/>
|
||||
</path>
|
||||
|
||||
<path id="jquery-plugin">
|
||||
<fileset dir="${src.dir}" includes="LICENSE"/>
|
||||
<fileset dir="." includes="LICENSE"/>
|
||||
<fileset dir="${src.dir}/plugins" includes="${JQUERY_PLUGIN_NAME}"/>
|
||||
</path>
|
||||
|
||||
<path id="minified">
|
||||
<fileset dir="${src.dir}" includes="LICENSE"/>
|
||||
<fileset dir="${build.dir}" includes="tmp.js"/>
|
||||
</path>
|
||||
|
||||
<target name="plugins">
|
||||
<concat fixlastline="yes" destfile="${build.dir}/${JQUERY_PLUGIN_NAME}">
|
||||
<path refid="jquery-plugin"/>
|
||||
</concat>
|
||||
</target>
|
||||
|
||||
|
||||
<target name="source">
|
||||
<concat fixlastline="yes" destfile="${build.dir}/${JS_NAME}">
|
||||
<path refid="sourcefiles"/>
|
||||
</concat>
|
||||
</target>
|
||||
|
||||
|
||||
<taskdef name="jscomp" classname="com.google.javascript.jscomp.ant.CompileTask"
|
||||
classpath="${lib.dir}/compiler.jar"/>
|
||||
|
||||
<target name="release">
|
||||
|
||||
<jscomp compilationLevel="simple" warning="verbose"
|
||||
debug="false"
|
||||
output="${build.dir}/tmp.js">
|
||||
<externs dir="${lib.dir}">
|
||||
<file name="${jquery-externs}"/>
|
||||
</externs>
|
||||
<sources dir="${build.dir}">
|
||||
<file name="${JS_NAME}" />
|
||||
</sources>
|
||||
</jscomp>
|
||||
|
||||
<concat fixlastline="yes" destfile="${build.dir}/${JS_NAME_MIN}">
|
||||
<path refid="minified"/>
|
||||
</concat>
|
||||
|
||||
</target>
|
||||
</project>
|
||||
|
2159
build/html2canvas.js
2159
build/html2canvas.js
File diff suppressed because it is too large
Load Diff
55
build/html2canvas.min.js
vendored
55
build/html2canvas.min.js
vendored
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* html2canvas v0.27 <http://html2canvas.hertzen.com>
|
||||
* Copyright (c) 2011 Niklas von Hertzen. All rights reserved.
|
||||
* http://www.twitter.com/niklasvh
|
||||
*
|
||||
* Released under MIT License
|
||||
*/
|
||||
|
||||
function html2canvas(a,b){this.opts=this.extendObj(b||{},{logging:!1,ready:function(a){document.body.appendChild(a.canvas)},storageReady:function(a){a.Renderer(a.contextStacks)},iframeDefault:"default",flashCanvasPath:"http://html2canvas.hertzen.com/external/flashcanvas/flashcanvas.js",renderViewport:!1,reorderZ:!0,throttle:!0,letterRendering:!1,proxyUrl:null,logger:function(a){window.console&&window.console.log?window.console.log(a):alert(a)},canvasWidth:0,canvasHeight:0,useOverflow:!0,renderOrder:"canvas flash html"});
|
||||
this.element=a;this.imagesLoaded=0;this.images=[];this.fontData=[];this.numDraws=0;this.contextStacks=[];this.ignoreElements="IFRAME|OBJECT|PARAM";this.needReorder=!1;this.blockElements=/(BR|PARAM)/;this.pageOrigin=window.location.protocol+window.location.host;this.queue=[];this.ignoreRe=RegExp("("+this.ignoreElements+")");this.support={rangeBounds:!1};if(document.createRange){var c=document.createRange();if(c.getBoundingClientRect){var d=document.createElement("boundtest");d.style.height="123px";
|
||||
d.style.display="block";document.getElementsByTagName("body")[0].appendChild(d);c.selectNode(d);if(c.getBoundingClientRect().height==123)this.support.rangeBounds=!0;document.getElementsByTagName("body")[0].removeChild(d)}}this.init();return this}
|
||||
html2canvas.prototype.init=function(){var a=this;this.log("Finding background-images");this.images.push("start");this.getImages(this.element);this.log("Finding images");this.each(this.element.ownerDocument.images,function(b,c){a.preloadImage(a.getAttr(c,"src"))});this.images.splice(0,1);this.images.length==0&&this.start()};
|
||||
html2canvas.prototype.start=function(){if(this.images.length==0||this.imagesLoaded==this.images.length/2){this.log("Finished loading "+this.imagesLoaded+" images, Started parsing");this.bodyOverflow=document.getElementsByTagName("body")[0].style.overflow;document.getElementsByTagName("body")[0].style.overflow="hidden";var a=new this.storageContext($(document).width(),$(document).height());a.opacity=this.getCSS(this.element,"opacity");this.parseElement(this.element,this.newElement(this.element,a))}};
|
||||
html2canvas.prototype.stackingContext=function(a){this.canvas=document.createElement("canvas");this.canvas.width=a;this.canvas.height=a;if(this.canvas.getContext)this.ctx=this.canvas.getContext("2d");this.ctx.textBaseline="bottom";return this.ctx};
|
||||
html2canvas.prototype.storageContext=function(a,b){this.storage=[];this.width=a;this.height=b;this.fillRect=function(a,b,e,f){this.storage.push({type:"function",name:"fillRect",arguments:[a,b,e,f]})};this.drawImage=function(a,b,e,f,g,h,i,j,m){this.storage.push({type:"function",name:"drawImage",arguments:[a,b,e,f,g,h,i,j,m]})};this.fillText=function(a,b,e){this.storage.push({type:"function",name:"fillText",arguments:[a,b,e]})};return this};
|
||||
html2canvas.prototype.finish=function(){this.log("Finished rendering");document.getElementsByTagName("body")[0].style.overflow=this.bodyOverflow;this.opts.ready(this)};
|
||||
html2canvas.prototype.drawBackground=function(a,b,c){var d=this.getCSS(a,"background-image").split(",")[0],e=this.getCSS(a,"background-repeat").split(",")[0];if(typeof d!="undefined"&&/^(1|none)$/.test(d)==!1&&/^(-webkit|-moz|linear-gradient|-o-)/.test(d)==!1){var d=this.backgroundImageUrl(d),f=this.loadImage(d),a=this.getBackgroundPosition(a,b,f);if(f)switch(e){case "repeat-x":this.drawbackgroundRepeatX(c,f,a,b.left,b.top,b.width,b.height);break;case "repeat-y":this.drawbackgroundRepeatY(c,f,a,b.left,
|
||||
b.top,b.width,b.height);break;case "no-repeat":var d=b.width-a.left,e=b.height-a.top,g=a.left,h=a.top,i=a.left+b.left,j=a.top+b.top;g<0?(g=Math.abs(g),i+=g,d=Math.min(b.width,f.width-g)):(d=Math.min(d,f.width),g=0);h<0?(h=Math.abs(h),j+=h,e=Math.min(b.height,f.height-h)):(e=Math.min(e,f.height),h=0);if(e>0&&d>0){this.drawImage(c,f,g,h,d,e,i,j,d,e);break}default:a.top-=Math.ceil(a.top/f.height)*f.height;for(d=b.top+a.top;d<b.height+b.top;)e=Math.min(f.height,b.height+b.top-d),e=Math.floor(d+f.height)>
|
||||
e+d?e+d-d:f.height,d<b.top?(g=b.top-d,d=b.top):g=0,this.drawbackgroundRepeatX(c,f,a,b.left,d,b.width,e),g>0&&(a.top+=g),d=Math.floor(d+f.height)-g}else this.log("Error loading background:"+d)}};html2canvas.prototype.backgroundImageUrl=function(a){a.substr(0,5)=='url("'?(a=a.substr(5),a=a.substr(0,a.length-2)):(a=a.substr(4),a=a.substr(0,a.length-1));return a};
|
||||
html2canvas.prototype.getBackgroundPosition=function(a,b,c){var d=(this.getCSS(a,"backgroundPosition").split(",")[0]||"0 0").split(" "),e;d.length==1&&(a=d,d=[],d[0]=a,d[1]=a);d[0].toString().indexOf("%")!=-1?(e=parseFloat(d[0])/100,a=b.width*e-c.width*e):a=parseInt(d[0],10);d[1].toString().indexOf("%")!=-1?(e=parseFloat(d[1])/100,b=b.height*e-c.height*e):b=parseInt(d[1],10);c={};c.top=b;c.left=a;return c};
|
||||
html2canvas.prototype.drawbackgroundRepeatY=function(a,b,c,d,e,f,g){var h=Math.min(b.width,f),i;c.top-=Math.ceil(c.top/b.height)*b.height;for(i=e+c.top;i<g+e;)f=Math.floor(i+b.height)>g+e?g+e-i:b.height,this.drawBackgroundRepeat(a,b,d+c.left,i,h,f,d,e),i=Math.floor(i+b.height)};
|
||||
html2canvas.prototype.drawbackgroundRepeatX=function(a,b,c,d,e,f,g){var g=Math.min(b.height,g),h,i;c.left-=Math.ceil(c.left/b.width)*b.width;for(i=d+c.left;i<f+d;)h=Math.floor(i+b.width)>f+d?f+d-i:b.width,this.drawBackgroundRepeat(a,b,i,e+c.top,h,g,d,e),i=Math.floor(i+b.width)};html2canvas.prototype.drawBackgroundRepeat=function(a,b,c,d,e,f,g,h){var i=0,j=0;g-c>0&&(i=g-c);h-d>0&&(j=h-d);this.drawImage(a,b,i,j,e-i,f-j,c+i,d+j,e-i,f-j)};
|
||||
html2canvas.prototype.getBorderData=function(a){var b=[],c=this;this.each(["top","right","bottom","left"],function(d,e){b.push({width:parseInt(c.getCSS(a,"border-"+e+"-width"),10),color:c.getCSS(a,"border-"+e+"-color")})});return b};
|
||||
html2canvas.prototype.drawBorders=function(a,b,c,d){var e=c.left,f=c.top,g=c.width,h=c.height,i=this.getBorderData(a),j=this;this.each(i,function(a,c){if(c.width>0){var k=e,o=f,n=g,p=h-i[2].width;switch(a){case 0:p=i[0].width;break;case 1:k=e+g-i[1].width;n=i[1].width;break;case 2:o=o+h-i[2].width;p=i[2].width;break;case 3:n=i[3].width}n={left:k,top:o,width:n,height:p};d&&(n=j.clipBounds(n,d));n.width>0&&n.height>0&&j.newRect(b,k,o,n.width,n.height,c.color)}});return i};
|
||||
html2canvas.prototype.newElement=function(a,b){var c=this.getBounds(a),d=c.left,e=c.top,f=c.width,g=c.height,h;h=this.getCSS(a,"background-color");var i=this.getCSS(a,"position"),b=b||{},j=this.setZ(this.getCSS(a,"zIndex"),i,b.zIndex,a.parentNode),m=this.getCSS(a,"opacity"),l={ctx:new this.storageContext,zIndex:j,opacity:m*b.opacity,cssPosition:i};if(b.clip)l.clip=$.extend({},b.clip),l.clip.height-=b.borders[2].width;if(this.opts.useOverflow&&/(hidden|scroll|auto)/.test(this.getCSS(a,"overflow"))&&
|
||||
!/(BODY)/i.test(a.nodeName))l.clip=l.clip?this.clipBounds(l.clip,c):c;i=j.children.push(l);m=j.children[i-1].ctx;this.setContextVariable(m,"globalAlpha",l.opacity);var k=this.drawBorders(a,m,c);l.borders=k;this.ignoreRe.test(a.nodeName)&&this.opts.iframeDefault!="transparent"&&(h=this.opts.iframeDefault=="default"?"#efefef":this.opts.iframeDefault);f={left:d+k[3].width,top:e+k[0].width,width:f-(k[1].width+k[3].width),height:g-(k[0].width+k[2].width)};l.clip&&(f=this.clipBounds(f,l.clip));f.height>
|
||||
0&&f.width>0&&(this.newRect(m,f.left,f.top,f.width,f.height,h),this.drawBackground(a,f,m));switch(a.nodeName){case "IMG":(h=this.loadImage(this.getAttr(a,"src")))?this.drawImage(m,h,0,0,h.width,h.height,d+parseInt(this.getCSS(a,"padding-left"),10)+k[3].width,e+parseInt(this.getCSS(a,"padding-top"),10)+k[0].width,c.width-(k[1].width+k[3].width+parseInt(this.getCSS(a,"padding-left"),10)+parseInt(this.getCSS(a,"padding-right"),10)),c.height-(k[0].width+k[2].width+parseInt(this.getCSS(a,"padding-top"),
|
||||
10)+parseInt(this.getCSS(a,"padding-bottom"),10))):this.log("Error loading <img>:"+this.getAttr(a,"src"));break;case "INPUT":/^(text|url|email|submit|button|reset)$/.test(a.type)&&a.value.length>0&&this.renderFormValue(a,c,l);break;case "TEXTAREA":a.value.length>0&&this.renderFormValue(a,c,l);break;case "SELECT":a.options.length>0&&this.renderFormValue(a,c,l);break;case "LI":this.drawListItem(a,l,f)}return j.children[i-1]};
|
||||
html2canvas.prototype.printText=function(a,b,c,d){this.trim(a).length>0&&(d.fillText(a,b,c),this.numDraws++)};html2canvas.prototype.newRect=function(a,b,c,d,e,f){f!="transparent"&&(this.setContextVariable(a,"fillStyle",f),a.fillRect(b,c,d,e),this.numDraws++)};html2canvas.prototype.drawImage=function(a,b,c,d,e,f,g,h,i,j){a.drawImage(b,c,d,e,f,g,h,i,j);this.numDraws++};
|
||||
html2canvas.prototype.renderFormValue=function(a,b,c){var d=document.createElement("valuewrap"),e=this;this.each(["lineHeight","textAlign","fontFamily","color","fontSize","paddingLeft","paddingTop","width","height","border","borderLeftWidth","borderTopWidth"],function(b,c){d.style[c]=e.getCSS(a,c)});d.style.borderColor="black";d.style.borderStyle="solid";d.style.display="block";d.style.position="absolute";if(/^(submit|reset|button|text|password)$/.test(a.type)||a.nodeName=="SELECT")d.style.lineHeight=
|
||||
e.getCSS(a,"height");d.style.top=b.top+"px";d.style.left=b.left+"px";b=document.createTextNode(a.nodeName=="SELECT"?a.options[a.selectedIndex].text:a.value);d.appendChild(b);$("body").append(d);this.newText(a,b,c);$(d).remove()};
|
||||
html2canvas.prototype.getImages=function(a){var b=this;this.ignoreRe.test(a.nodeName)||this.each($(a).contents(),function(a,d){RegExp("("+this.ignoreElements+")").test(d.nodeName)||b.getImages(d)});if(a.nodeType==1||typeof a.nodeType=="undefined")(a=this.getCSS(a,"background-image"))&&a!="1"&&a!="none"&&a.substring(0,7)!="-webkit"&&a.substring(0,3)!="-o-"&&a.substring(0,4)!="-moz"&&this.preloadImage(this.backgroundImageUrl(a.split(",")[0]))};
|
||||
html2canvas.prototype.loadImage=function(a){a=this.getIndex(this.images,a);return a!=-1?this.images[a+1]:!1};html2canvas.prototype.preloadImage=function(a){if(this.getIndex(this.images,a)==-1)if(this.isSameOrigin(a)){this.images.push(a);var b=new Image,c=this;$(b).load(function(){c.imagesLoaded++;c.start()});b.onerror=function(){c.images.splice(c.images.indexOf(b.src),2);c.start()};b.src=a;this.images.push(b)}else this.opts.proxyUrl&&(this.images.push(a),b=new Image,this.proxyGetImage(a,b),this.images.push(b))};
|
||||
html2canvas.prototype.proxyGetImage=function(a,b){var c=this,d=document.createElement("a");d.href=a;a=d.href;$.ajax({data:{xhr2:!1,url:a},url:this.opts.proxyUrl,dataType:"jsonp",success:function(d){d.substring(0,6)=="error:"?(c.images.splice(c.images.indexOf(a),2),c.start(),c.log("Proxy was unable to load "+a+" "+d)):(b.onload=function(){c.imagesLoaded++;c.start()},b.src=d)},error:function(){c.images.splice(c.images.indexOf(a),2);c.start()}})};
|
||||
html2canvas.prototype.Renderer=function(a){var b=this;this.log("Renderer initiated");this.each(this.opts.renderOrder.split(" "),function(c,d){switch(d){case "canvas":b.canvas=document.createElement("canvas");if(b.canvas.getContext)return b.canvasRenderer(a),b.log("Using canvas renderer"),!1;break;case "html":return b.log("Using HTML renderer"),!1}});return this};html2canvas.prototype.throttler=function(){};
|
||||
html2canvas.prototype.canvasRenderContext=function(a,b){b.textBaseline="bottom";var c=this;a.clip&&(b.save(),b.beginPath(),b.rect(a.clip.left,a.clip.top,a.clip.width,a.clip.height),b.clip());a.ctx.storage&&c.each(a.ctx.storage,function(a,e){switch(e.type){case "variable":b[e.name]=e.arguments;break;case "function":e.name=="fillRect"?b.fillRect(e.arguments[0],e.arguments[1],e.arguments[2],e.arguments[3]):e.name=="fillText"?b.fillText(e.arguments[0],e.arguments[1],e.arguments[2]):e.name=="drawImage"?
|
||||
e.arguments[8]>0&&e.arguments[7]&&b.drawImage(e.arguments[0],e.arguments[1],e.arguments[2],e.arguments[3],e.arguments[4],e.arguments[5],e.arguments[6],e.arguments[7],e.arguments[8]):c.log(e)}});a.clip&&b.restore()};html2canvas.prototype.canvasRenderStorage=function(a,b){for(;0<a.length;){var c=a.splice(0,1)[0];c.canvasPosition=c.canvasPosition||{};this.canvasRenderContext(c,b)}};
|
||||
html2canvas.prototype.canvasRenderer=function(a){this.sortZ(this.zStack);a=this.queue;this.canvas.width=Math.max($(document).width(),this.opts.canvasWidth);this.canvas.height=Math.max($(document).height(),this.opts.canvasHeight);this.ctx=this.canvas.getContext("2d");this.canvasRenderStorage(a,this.ctx)};html2canvas.prototype.setContextVariable=function(a,b,c){a.storage?a.storage.push({type:"variable",name:b,arguments:c}):a[b]=c};
|
||||
html2canvas.prototype.drawListItem=function(a,b,c){var d=this.getCSS(a,"list-style-position",!1);this.getListItem(a);var e=this.getCSS(a,"list-style-type",!1);if(/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(e)){var f=$(a).index()+1,g;e=="decimal"?g=f:e=="decimal-leading-zero"?g=f.toString().length==1?"0"+f.toString():f.toString():e=="upper-roman"?g=this.getListRoman(f):e=="lower-roman"?g=this.getListRoman(f).toLowerCase():
|
||||
e=="lower-alpha"?g=this.getListAlpha(f).toLowerCase():e=="upper-alpha"&&(g=this.getListAlpha(f));g+=". ";e=this.getListPosition(a,g);if(d=="inside")this.setFont(b.ctx,a,!1),a=c.left,c=e.bottom,this.printText(g,a,c,b.ctx)}};
|
||||
html2canvas.prototype.getListPosition=function(a,b){var c=document.createElement("boundelement");c.style.display="inline";var d=a.style.listStyleType;a.style.listStyleType="none";c.appendChild(document.createTextNode(b));a.insertBefore(c,a.firstChild);var e=this.getBounds(c);a.removeChild(c);a.style.listStyleType=d;return e};html2canvas.prototype.getListItem=function(){};html2canvas.prototype.getListAlpha=function(a){var b="";do b=String.fromCharCode(a%26+64)+b,a/=26;while(a*26>26);return b};
|
||||
html2canvas.prototype.getListRoman=function(a){var b=["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"],c=[1E3,900,500,400,100,90,50,40,10,9,5,4,1],d="";if(!(a<=0||a>=4E3)){for(var e=0;e<b.length;e++)for(;a>=c[e];)a-=c[e],d+=b[e];return d}};
|
||||
html2canvas.prototype.newText=function(a,b,c){var c=c.ctx,d=this.getCSS(a,"font-family"),e=this.getCSS(a,"font-size"),f=this.getCSS(a,"color"),g=this.getCSS(a,"text-decoration"),h=this.getCSS(a,"text-align"),i=this.getCSS(a,"letter-spacing");b.nodeValue=this.textTransform(b.nodeValue,this.getCSS(a,"text-transform"));if(this.trim(b.nodeValue).length>0){if(g!="none")var j=this.fontMetrics(d,e);h=h.replace(["-webkit-auto"],["auto"]);d=this.opts.letterRendering==!1&&/^(left|right|justify|auto)$/.test(h)&&
|
||||
/^(normal|none)$/.test(i)?b.nodeValue.split(/(\b| )/):b.nodeValue.split("");this.setFont(c,a,!1);a=b;for(b=0;b<d.length;b++)if(typeof a.nodeValue=="string"){e=a.splitText(d[b].length);if(g!="none"||this.trim(a.nodeValue).length!=0){if(this.support.rangeBounds)document.createRange?(h=document.createRange(),h.selectNode(a)):h=document.body.createTextRange(),h=h.getBoundingClientRect()?h.getBoundingClientRect():{};else{var i=a.parentNode,m=document.createElement("wrapper"),l=a.cloneNode(!0);m.appendChild(a.cloneNode(!0));
|
||||
i.replaceChild(m,a);h=this.getBounds(m);i.replaceChild(l,m)}this.printText(a.nodeValue,h.left,h.bottom,c);switch(g){case "underline":this.newRect(c,h.left,Math.round(h.top+j.baseline+j.lineWidth),h.width,1,f);break;case "overline":this.newRect(c,h.left,h.top,h.width,1,f);break;case "line-through":this.newRect(c,h.left,Math.ceil(h.top+j.middle+j.lineWidth),h.width,1,f)}}a=e}}};
|
||||
html2canvas.prototype.setFont=function(a,b,c){var d=this.getCSS(b,"font-family"),e=this.getCSS(b,"font-size"),f=this.getCSS(b,"color"),g=this.getCSS(b,"font-weight"),h=this.getCSS(b,"font-style"),b=this.getCSS(b,"font-variant");switch(g){case 401:g="bold";break;case 400:g="normal"}d=b+" "+g+" "+h+" "+e+" "+d;this.setContextVariable(a,"fillStyle",f);this.setContextVariable(a,"font",d);c?this.setContextVariable(a,"textAlign","right"):this.setContextVariable(a,"textAlign","left")};
|
||||
html2canvas.prototype.fontMetrics=function(a,b){var c=this.fontData.indexOf(a+"-"+b);if(c>-1)return this.fontData[c+1];c=document.createElement("div");document.getElementsByTagName("body")[0].appendChild(c);$(c).css({visibility:"hidden",fontFamily:a,fontSize:b,margin:0,padding:0});var d=document.createElement("img");d.src="http://html2canvas.hertzen.com/images/8.jpg";d.width=1;d.height=1;$(d).css({margin:0,padding:0});var e=document.createElement("span");$(e).css({fontFamily:a,fontSize:b,margin:0,
|
||||
padding:0});e.appendChild(document.createTextNode("Hidden Text"));c.appendChild(e);c.appendChild(d);var f=d.offsetTop-e.offsetTop+1;c.removeChild(e);c.appendChild(document.createTextNode("Hidden Text"));$(c).css("line-height","normal");$(d).css("vertical-align","super");d={baseline:f,lineWidth:1,middle:d.offsetTop-c.offsetTop+1};this.fontData.push(a+"-"+b);this.fontData.push(d);$(c).remove();return d};
|
||||
html2canvas.prototype.textTransform=function(a,b){switch(b){case "lowercase":return a.toLowerCase();case "capitalize":return a.replace(/(^|\s|:|-|\(|\))([a-z])/g,function(a,b,e){return b+e.toUpperCase()});case "uppercase":return a.toUpperCase();default:return a}};html2canvas.prototype.trim=function(a){return a.replace(/^\s*/,"").replace(/\s*$/,"")};
|
||||
html2canvas.prototype.parseElement=function(a,b){var c=this;this.each(a.children,function(a,e){c.parsing(e,b)});this.log("Render queue stored");this.opts.storageReady(this);this.finish()};html2canvas.prototype.parsing=function(a,b){if(this.getCSS(a,"display")!="none"&&this.getCSS(a,"visibility")!="hidden"){var c=this,b=this.newElement(a,b)||b;this.ignoreRe.test(a.nodeName)||this.each(this.contentsInZ(a),function(d,e){e.nodeType==1?c.parsing(e,b):e.nodeType==3&&c.newText(a,e,b)})}};
|
||||
html2canvas.prototype.log=function(a){this.opts.logging&&this.opts.logger(a)};html2canvas.prototype.withinBounds=function(a,b){return!a?!0:(a.left<=b.left||b.left+b.width<a.left)&&(a.top<=b.top||b.top+b.height<a.top)};html2canvas.prototype.clipBounds=function(a,b){var c=Math.max(a.left,b.left),d=Math.max(a.top,b.top);return{left:c,top:d,width:Math.min(a.left+a.width,b.left+b.width)-c,height:Math.min(a.top+a.height,b.top+b.height)-d}};
|
||||
html2canvas.prototype.getBounds=function(a){window.scroll(0,0);if(a.getBoundingClientRect){var a=a.getBoundingClientRect(),b={};b.top=a.top;b.bottom=a.bottom||a.top+a.height;b.left=a.left;b.width=a.width;b.height=a.height;return b}else return b=$(a).offset(),{left:b.left+this.getCSS(a,"border-left-width",!0),top:b.top+this.getCSS(a,"border-top-width",!0),width:$(a).innerWidth(),height:$(a).innerHeight()}};
|
||||
html2canvas.prototype.each=function(a,b){for(var b=b||function(){},c=0;c<a.length;c++)if(b(c,a[c])===!1)break};html2canvas.prototype.contentsInZ=function(a){return $(a).contents()};html2canvas.prototype.getAttr=function(a,b){return a.getAttribute(b)};html2canvas.prototype.extendObj=function(a,b){for(var c in a)b[c]=a[c];return b};html2canvas.prototype.zContext=function(a){return{zindex:a,children:[]}};
|
||||
html2canvas.prototype.setZ=function(a,b,c){return!c?this.zStack=new this.zContext(0):a!="auto"?(this.needReorder=!0,a=new this.zContext(a),c.children.push(a),a):c};html2canvas.prototype.sortZ=function(a){var b=[],c=[],d=this;this.each(a.children,function(a,f){f.children&&f.children.length>0?(b.push(f),c.push(f.zindex)):d.queue.push(f)});c.sort(function(a,b){return a-b});this.each(c,function(a,c){for(var g=0;g<=b.length;g++)if(b[g].zindex==c){g=b.splice(g,1);d.sortZ(g[0]);break}})};
|
||||
html2canvas.prototype.getContents=function(a){return a.nodeName=="iframe"?a.contentDocument||a.contentWindow.document:a.childNodes};html2canvas.prototype.getCSS=function(a,b,c){return c?parseInt($(a).css(b),10):$(a).css(b)};html2canvas.prototype.getIndex=function(a,b){if(a.indexOf)return a.indexOf(b);else{for(var c=0;c<a.length;c++)if(this[c]==b)return c;return-1}};html2canvas.prototype.isSameOrigin=function(a){var b=document.createElement("a");b.href=a;return b.protocol+b.host==this.pageOrigin};
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* html2canvas v0.25 <http://html2canvas.hertzen.com>
|
||||
* Copyright (c) 2011 Niklas von Hertzen. All rights reserved.
|
||||
* http://www.twitter.com/niklasvh
|
||||
*
|
||||
* Released under MIT License
|
||||
*/
|
||||
|
||||
/*
|
||||
* The MIT License
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* jQuery helper plugin for examples and tests
|
||||
*/
|
||||
|
||||
(function( $ ){
|
||||
$.fn.html2canvas = function(options) {
|
||||
|
||||
var date = new Date();
|
||||
var message,
|
||||
timeoutTimer,
|
||||
timer = date.getTime();
|
||||
|
||||
var object = $.extend({},{
|
||||
logging: false,
|
||||
proxyUrl: "http://html2canvas.appspot.com/", // running html2canvas-python proxy
|
||||
ready: function(renderer) {
|
||||
|
||||
var finishTime = new Date();
|
||||
// console.log((finishTime.getTime()-timer)/1000);
|
||||
|
||||
|
||||
document.body.appendChild(renderer.canvas);
|
||||
|
||||
|
||||
|
||||
var canvas = $(renderer.canvas);
|
||||
canvas.css('position','absolute')
|
||||
.css('left',0).css('top',0);
|
||||
|
||||
|
||||
|
||||
// $('body').append(canvas);
|
||||
$(canvas).siblings().toggle();
|
||||
|
||||
throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)/1000) + " seconds<br />Total of "+renderer.numDraws+" draws performed",4000);
|
||||
|
||||
|
||||
$(window).click(function(){
|
||||
if (!canvas.is(':visible')){
|
||||
$(canvas).toggle().siblings().toggle();
|
||||
throwMessage("Canvas Render visible");
|
||||
} else{
|
||||
$(canvas).siblings().toggle();
|
||||
$(canvas).toggle();
|
||||
throwMessage("Canvas Render hidden");
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
},options)
|
||||
|
||||
new html2canvas(this.get(0), object);
|
||||
|
||||
|
||||
function throwMessage(msg,duration){
|
||||
|
||||
window.clearTimeout(timeoutTimer);
|
||||
timeoutTimer = window.setTimeout(function(){
|
||||
message.fadeOut(function(){
|
||||
message.remove();
|
||||
});
|
||||
},duration || 2000);
|
||||
$(message).remove();
|
||||
message = $('<div />').html(msg).css({
|
||||
margin:0,
|
||||
padding:10,
|
||||
background: "#000",
|
||||
opacity:0.7,
|
||||
position:"fixed",
|
||||
top:10,
|
||||
right:10,
|
||||
fontFamily: 'Tahoma' ,
|
||||
color:'#fff',
|
||||
fontSize:12,
|
||||
borderRadius:12,
|
||||
width:'auto',
|
||||
height:'auto',
|
||||
textAlign:'center',
|
||||
textDecoration:'none'
|
||||
}).hide().fadeIn().appendTo('body');
|
||||
}
|
||||
|
||||
};
|
||||
})( jQuery );
|
||||
|
11
configs/base.json
Normal file
11
configs/base.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
10
configs/preview.json
Normal file
10
configs/preview.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./base",
|
||||
"include": [
|
||||
"../www/src/preview.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
10
configs/scripts.json
Normal file
10
configs/scripts.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./base",
|
||||
"include": [
|
||||
"scripts/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
186
demo2.html
186
demo2.html
@ -1,186 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<script type="text/javascript" src="external/jquery-1.6.2.min.js"></script>
|
||||
<script type="text/javascript" src="build/html2canvas.js"></script>
|
||||
<script type="text/javascript" src="build/jquery.plugin.html2canvas.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('body').html2canvas();
|
||||
// var ss = $('ul').offset();
|
||||
// alert(ss.left);
|
||||
});
|
||||
</script>
|
||||
<title>
|
||||
display/box/float/clear test
|
||||
</title>
|
||||
<style type="text/css">
|
||||
/* last modified: 1 Dec 98 */
|
||||
|
||||
html {
|
||||
font: 10px/1 Verdana, sans-serif;
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 1.5em;
|
||||
border: .5em solid black;
|
||||
padding: 0;
|
||||
width: 48em;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
dt {
|
||||
background-color: rgb(204,0,0);
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
width: 10.638%; /* refers to parent element's width of 47em. = 5em or 50px */
|
||||
height: 28em;
|
||||
border: .5em solid black;
|
||||
float: left;
|
||||
}
|
||||
|
||||
dd {
|
||||
float: right;
|
||||
margin: 0 0 0 1em;
|
||||
border: 1em solid black;
|
||||
padding: 1em;
|
||||
width: 34em;
|
||||
height: 27em;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: block; /* i.e., suppress marker */
|
||||
color: black;
|
||||
height: 9em;
|
||||
width: 5em;
|
||||
margin: 0;
|
||||
border: .5em solid black;
|
||||
padding: 1em;
|
||||
float: left;
|
||||
background-color: #FC0;
|
||||
}
|
||||
|
||||
#bar {
|
||||
background-color: black;
|
||||
color: white;
|
||||
width: 41.17%; /* = 14em */
|
||||
border: 0;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
#baz {
|
||||
margin: 1em 0;
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
form p {
|
||||
line-height: 1.9;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1em 1em 1em 2em;
|
||||
border-width: 1em 1.5em 2em .5em;
|
||||
border-style: solid;
|
||||
border-color: black;
|
||||
padding: 1em 0;
|
||||
width: 5em;
|
||||
height: 9em;
|
||||
float: left;
|
||||
background-color: #FC0;
|
||||
color: black;
|
||||
}
|
||||
|
||||
address {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
h1 {
|
||||
background-color: black;
|
||||
color: white;
|
||||
float: left;
|
||||
margin: 1em 0;
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<dl>
|
||||
<dt>
|
||||
toggle
|
||||
</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
the way
|
||||
</li>
|
||||
<li id="bar">
|
||||
<p>
|
||||
the world ends
|
||||
</p>
|
||||
<form action="./" method="get">
|
||||
<p>
|
||||
bang
|
||||
<input type="radio" name="foo" value="off">
|
||||
</p>
|
||||
<p>
|
||||
whimper
|
||||
<input type="radio" name="foo2" value="on">
|
||||
</p>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
i grow old
|
||||
</li>
|
||||
<li id="baz">
|
||||
pluot?
|
||||
</li>
|
||||
</ul>
|
||||
<blockquote>
|
||||
<address>
|
||||
bar maids,
|
||||
</address>
|
||||
</blockquote>
|
||||
<h1>
|
||||
sing to me, erbarme dich
|
||||
</h1>
|
||||
</dd>
|
||||
</dl>
|
||||
<p style="color: black; font-size: 1em; line-height: 1.3em; clear: both">
|
||||
This is a nonsensical document, but syntactically valid HTML 4.0. All 100% conformant CSS1 agents should be able to render the document elements above this paragraph <b>indistinguishably</b> (to the pixel) from this reference rendering, (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the <A HREF="sec5526c.htm" style="text-decoration:none">parent page</A>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
72
demo3.html
72
demo3.html
@ -1,72 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
|
||||
|
||||
<script type="text/javascript" src="external/jquery-1.6.2.min.js"></script>
|
||||
<script type="text/javascript" src="build/html2canvas.js"></script>
|
||||
<script type="text/javascript" src="build/jquery.plugin.html2canvas.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
|
||||
|
||||
$('body').html2canvas();
|
||||
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.feedback-overlay-black{
|
||||
background-color:#000;
|
||||
opacity:0.5;
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
div{
|
||||
padding:20px;
|
||||
margin:0 auto;
|
||||
border:5px solid black;
|
||||
}
|
||||
|
||||
h1{
|
||||
border-bottom:2px solid white;
|
||||
}
|
||||
|
||||
h2{
|
||||
background: #efefef;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background:red;">
|
||||
<div style="background:green;">
|
||||
<div style="background:blue;border-color:white;">
|
||||
<div style="background:yellow;"><div style="background:orange;"><h1>Heading</h1>
|
||||
Text that isn't wrapped in anything.
|
||||
<p>Followed by some text wrapped in a <b><p> paragraph.</b> </p>
|
||||
Maybe add a <a href="#">link</a> or a different style of <a href="#" style="background:white;" id="highlight">link with a highlight</a>.
|
||||
<hr />
|
||||
<h2>More content</h2>
|
||||
<div style="width:10px;height:10px;border-width:10px;padding:0;">a</div>
|
||||
</div></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
35
docs/configuration.md
Normal file
35
docs/configuration.md
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
title: "Options"
|
||||
description: "Explore the different configuration options available for html2canvas"
|
||||
previousUrl: "/getting-started"
|
||||
previousTitle: "Getting Started"
|
||||
nextUrl: "/features"
|
||||
nextTitle: "Features"
|
||||
---
|
||||
|
||||
These are all of the available configuration options.
|
||||
|
||||
| Name | Default | Description |
|
||||
| ------------- | :------: | ----------- |
|
||||
| allowTaint | `false` | Whether to allow cross-origin images to taint the canvas
|
||||
| backgroundColor | `#ffffff` | Canvas background color, if none is specified in DOM. Set `null` for transparent
|
||||
| canvas | `null` | Existing `canvas` element to use as a base for drawing on
|
||||
| foreignObjectRendering | `false` | Whether to use ForeignObject rendering if the browser supports it
|
||||
| imageTimeout | `15000` | Timeout for loading an image (in milliseconds). Set to `0` to disable timeout.
|
||||
| ignoreElements | `(element) => false` | Predicate function which removes the matching elements from the render.
|
||||
| logging | `true` | Enable logging for debug purposes
|
||||
| onclone | `null` | Callback function which is called when the Document has been cloned for rendering, can be used to modify the contents that will be rendered without affecting the original source document.
|
||||
| proxy | `null` | Url to the [proxy](/proxy/) which is to be used for loading cross-origin images. If left empty, cross-origin images won't be loaded.
|
||||
| removeContainer | `true` | Whether to cleanup the cloned DOM elements html2canvas creates temporarily
|
||||
| scale | `window.devicePixelRatio` | The scale to use for rendering. Defaults to the browsers device pixel ratio.
|
||||
| useCORS | `false` | Whether to attempt to load images from a server using CORS
|
||||
| width | `Element` width | The width of the `canvas`
|
||||
| height | `Element` height | The height of the `canvas`
|
||||
| x | `Element` x-offset | Crop canvas x-coordinate
|
||||
| y | `Element` y-offset| Crop canvas y-coordinate
|
||||
| scrollX | `Element` scrollX | The x-scroll position to used when rendering element, (for example if the Element uses `position: fixed`)
|
||||
| scrollY | `Element` scrollY | The y-scroll position to used when rendering element, (for example if the Element uses `position: fixed`)
|
||||
| windowWidth | `Window.innerWidth` | Window width to use when rendering `Element`, which may affect things like Media queries
|
||||
| windowHeight | `Window.innerHeight` | Window height to use when rendering `Element`, which may affect things like Media queries
|
||||
|
||||
If you wish to exclude certain `Element`s from getting rendered, you can add a `data-html2canvas-ignore` attribute to those elements and html2canvas will exclude them from the rendering.
|
42
docs/documentation.md
Normal file
42
docs/documentation.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
title: "About"
|
||||
description: "Learn about html2canvas, how it works and some of its limitations"
|
||||
nextUrl: "/getting-started"
|
||||
nextTitle: "Getting Started"
|
||||
---
|
||||
|
||||
Before you get started with the script, there are a few things that are good to know regarding the
|
||||
script and some of its limitations.
|
||||
|
||||
## Introduction
|
||||
The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser.
|
||||
The screenshot is based on the DOM and as such may not be 100% accurate to the real representation
|
||||
as it does not make an actual screenshot, but builds the screenshot based on the information
|
||||
available on the page.
|
||||
|
||||
## How it works
|
||||
The script traverses through the DOM of the page it is loaded on. It gathers information on all the elements
|
||||
there, which it then uses to build a representation of the page. In other words, it does not actually take a
|
||||
screenshot of the page, but builds a representation of it based on the properties it reads from the DOM.
|
||||
|
||||
|
||||
As a result, it is only able to render correctly properties that it understands, meaning there are many
|
||||
CSS properties which do not work. For a full list of supported CSS properties, check out the
|
||||
[supported features](/features/) page.
|
||||
|
||||
## Limitations
|
||||
All the images that the script uses need to reside under the [same origin](http://en.wikipedia.org/wiki/Same_origin_policy)
|
||||
for it to be able to read them without the assistance of a [proxy](/proxy/). Similarly, if you have other `canvas`
|
||||
elements on the page, which have been tainted with cross-origin content, they will become dirty and no longer readable by html2canvas.
|
||||
|
||||
The script doesn't render plugin content such as Flash or Java applets.
|
||||
|
||||
## Browser compatibility
|
||||
|
||||
The library should work fine on the following browsers (with `Promise` polyfill):
|
||||
- Firefox 3.5+
|
||||
- Google Chrome
|
||||
- Opera 12+
|
||||
- IE9+
|
||||
- Edge
|
||||
- Safari 6+
|
48
docs/faq.md
Normal file
48
docs/faq.md
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: "FAQ"
|
||||
description: "Explore Frequently Asked Questions regarding html2canvas"
|
||||
---
|
||||
|
||||
## Why aren't my images rendered?
|
||||
html2canvas does not get around content policy restrictions set by your browser. Drawing images that reside outside of
|
||||
the [origin](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) of the current page [taint the
|
||||
canvas](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image#What_is_a_tainted_canvas) that they are drawn upon. If the canvas gets tainted, it cannot be read anymore. As such, html2canvas implements
|
||||
methods to check whether an image would taint the canvas before applying it. If you have set the `allowTaint`
|
||||
[option](/configuration) to `false`, it will not draw the image.
|
||||
|
||||
If you wish to load images that reside outside of your pages origin, you can use a [proxy](/proxy) to load the images.
|
||||
|
||||
## Why is the produced canvas empty or cuts off half way through?
|
||||
Make sure that `canvas` element doesn't hit [browser limitations](https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element) for the `canvas` size or use the window configuration options to set a custom window size based on the `canvas` element:
|
||||
```
|
||||
await html2canvas(element, {
|
||||
windowWidth: element.scrollWidth,
|
||||
windowHeight: element.scrollHeight
|
||||
});
|
||||
```
|
||||
The window limitations vary by browser, operating system and system hardware.
|
||||
|
||||
### Chrome
|
||||
> Maximum height/width: 32,767 pixels
|
||||
> Maximum area: 268,435,456 pixels (e.g., 16,384 x 16,384)
|
||||
|
||||
### Firefox
|
||||
> Maximum height/width: 32,767 pixels
|
||||
> Maximum area: 472,907,776 pixels (e.g., 22,528 x 20,992)
|
||||
|
||||
### Internet Explorer
|
||||
> Maximum height/width: 8,192 pixels
|
||||
> Maximum area: N/A
|
||||
|
||||
### iOS
|
||||
> The maximum size for a canvas element is 3 megapixels for devices with less than 256 MB RAM and 5 megapixels for devices with greater or equal than 256 MB RAM
|
||||
|
||||
## Why doesn't CSS property X render correctly or only partially?
|
||||
As each CSS property needs to be manually coded to render correctly, html2canvas will *never* have full CSS support.
|
||||
The library tries to support the most [commonly used CSS properties](/features) to the extent that it can. If some CSS property
|
||||
is missing or incomplete and you feel that it should be part of the library, create test cases for it and a new issue for it.
|
||||
|
||||
## How do I get html2canvas to work in a browser extension?
|
||||
You shouldn't use html2canvas in a browser extension. Most browsers have native support for capturing screenshots from
|
||||
tabs within extensions. Relevant information for [Chrome](https://developer.chrome.com/extensions/tabs#method-captureVisibleTab) and
|
||||
[Firefox](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D#drawWindow()).
|
87
docs/features.md
Normal file
87
docs/features.md
Normal file
@ -0,0 +1,87 @@
|
||||
---
|
||||
title: "Features"
|
||||
description: "Discover the different features supported by html2canvas"
|
||||
---
|
||||
|
||||
Below is a list of all the supported CSS properties and values.
|
||||
|
||||
- background
|
||||
- background-clip (**Does not support `text`**)
|
||||
- background-color
|
||||
- background-image
|
||||
- url()
|
||||
- linear-gradient()
|
||||
- radial-gradient()
|
||||
- background-origin
|
||||
- background-position
|
||||
- background-size
|
||||
- border
|
||||
- border-color
|
||||
- border-radius
|
||||
- border-style
|
||||
- border-width
|
||||
- bottom
|
||||
- box-sizing
|
||||
- content
|
||||
- color
|
||||
- display
|
||||
- flex
|
||||
- float
|
||||
- font
|
||||
- font-family
|
||||
- font-size
|
||||
- font-style
|
||||
- font-variant
|
||||
- font-weight
|
||||
- height
|
||||
- left
|
||||
- letter-spacing
|
||||
- line-break
|
||||
- list-style
|
||||
- list-style-image
|
||||
- list-style-position
|
||||
- list-style-type
|
||||
- margin
|
||||
- max-height
|
||||
- max-width
|
||||
- min-height
|
||||
- min-width
|
||||
- opacity
|
||||
- overflow
|
||||
- overflow-wrap
|
||||
- padding
|
||||
- paint-order
|
||||
- position
|
||||
- right
|
||||
- text-align
|
||||
- text-decoration
|
||||
- text-decoration-color
|
||||
- text-decoration-line
|
||||
- text-decoration-style (**Only supports `solid`**)
|
||||
- text-shadow
|
||||
- text-transform
|
||||
- top
|
||||
- transform (**Limited support**)
|
||||
- visibility
|
||||
- white-space
|
||||
- width
|
||||
- webkit-text-stroke
|
||||
- word-break
|
||||
- word-spacing
|
||||
- word-wrap
|
||||
- z-index
|
||||
|
||||
## Unsupported CSS properties
|
||||
These CSS properties are **NOT** currently supported
|
||||
- [background-blend-mode](https://github.com/niklasvh/html2canvas/issues/966)
|
||||
- [border-image](https://github.com/niklasvh/html2canvas/issues/1287)
|
||||
- [box-decoration-break](https://github.com/niklasvh/html2canvas/issues/552)
|
||||
- [box-shadow](https://github.com/niklasvh/html2canvas/pull/1086)
|
||||
- [filter](https://github.com/niklasvh/html2canvas/issues/493)
|
||||
- [font-variant-ligatures](https://github.com/niklasvh/html2canvas/pull/1085)
|
||||
- [mix-blend-mode](https://github.com/niklasvh/html2canvas/issues/580)
|
||||
- [object-fit](https://github.com/niklasvh/html2canvas/issues/1064)
|
||||
- [repeating-linear-gradient()](https://github.com/niklasvh/html2canvas/issues/1162)
|
||||
- [writing-mode](https://github.com/niklasvh/html2canvas/issues/1258)
|
||||
- [zoom](https://github.com/niklasvh/html2canvas/issues/732)
|
||||
|
30
docs/getting-started.md
Normal file
30
docs/getting-started.md
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
title: "Getting Started"
|
||||
description: "Learn how to start using html2canvas"
|
||||
previousUrl: "/documentation"
|
||||
previousTitle: "About"
|
||||
nextUrl: "/configuration"
|
||||
nextTitle: "Configuration"
|
||||
---
|
||||
|
||||
## Installing
|
||||
|
||||
You can install `html2canvas` through npm or [download a built release](https://github.com/niklasvh/html2canvas/releases).
|
||||
|
||||
### npm
|
||||
|
||||
npm install html2canvas
|
||||
|
||||
```javascript
|
||||
import html2canvas from 'html2canvas';
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To render an `element` with html2canvas with some (optional) [options](/configuration/), simply call `html2canvas(element, options);`
|
||||
|
||||
```javascript
|
||||
html2canvas(document.body).then(function(canvas) {
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
```
|
12
docs/proxy.md
Normal file
12
docs/proxy.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "Proxy"
|
||||
description: "Browse different proxies available for supporting CORS content"
|
||||
---
|
||||
|
||||
html2canvas does not get around content policy restrictions set by your browser. Drawing images that reside outside of
|
||||
the origin of the current page taint the canvas that they are drawn upon. If the canvas gets tainted,
|
||||
it cannot be read anymore. If you wish to load images that reside outside of your pages origin, you can use a proxy to load the images.
|
||||
|
||||
## Available proxies
|
||||
|
||||
- [node.js](https://github.com/niklasvh/html2canvas-proxy-nodejs)
|
181
examples/demo.html
Normal file
181
examples/demo.html
Normal file
@ -0,0 +1,181 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
display/box/float/clear test
|
||||
</title>
|
||||
<style type="text/css">
|
||||
/* last modified: 1 Dec 98 */
|
||||
|
||||
html {
|
||||
font: 10px/1 Verdana, sans-serif;
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 1.5em;
|
||||
border: .5em solid black;
|
||||
padding: 0;
|
||||
width: 48em;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
dt {
|
||||
background-color: rgb(204,0,0);
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
width: 10.638%; /* refers to parent element's width of 47em. = 5em or 50px */
|
||||
height: 28em;
|
||||
border: .5em solid black;
|
||||
float: left;
|
||||
}
|
||||
|
||||
dd {
|
||||
float: right;
|
||||
margin: 0 0 0 1em;
|
||||
border: 1em solid black;
|
||||
padding: 1em;
|
||||
width: 34em;
|
||||
height: 27em;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: block; /* i.e., suppress marker */
|
||||
color: black;
|
||||
height: 9em;
|
||||
width: 5em;
|
||||
margin: 0;
|
||||
border: .5em solid black;
|
||||
padding: 1em;
|
||||
float: left;
|
||||
background-color: #FC0;
|
||||
}
|
||||
|
||||
#bar {
|
||||
background-color: black;
|
||||
color: white;
|
||||
width: 41.17%; /* = 14em */
|
||||
border: 0;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
#baz {
|
||||
margin: 1em 0;
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
form p {
|
||||
line-height: 1.9;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1em 1em 1em 2em;
|
||||
border-width: 1em 1.5em 2em .5em;
|
||||
border-style: solid;
|
||||
border-color: black;
|
||||
padding: 1em 0;
|
||||
width: 5em;
|
||||
height: 9em;
|
||||
float: left;
|
||||
background-color: #FC0;
|
||||
color: black;
|
||||
}
|
||||
|
||||
address {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
h1 {
|
||||
background-color: black;
|
||||
color: white;
|
||||
float: left;
|
||||
margin: 1em 0;
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<dl>
|
||||
<dt>
|
||||
toggle
|
||||
</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
the way
|
||||
</li>
|
||||
<li id="bar">
|
||||
<p>
|
||||
the world ends
|
||||
</p>
|
||||
<form action="./" method="get">
|
||||
<p>
|
||||
bang
|
||||
<input type="radio" name="foo" value="off">
|
||||
</p>
|
||||
<p>
|
||||
whimper
|
||||
<input type="radio" name="foo2" value="on">
|
||||
</p>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
i grow old
|
||||
</li>
|
||||
<li id="baz">
|
||||
pluot?
|
||||
</li>
|
||||
</ul>
|
||||
<blockquote>
|
||||
<address>
|
||||
bar maids,
|
||||
</address>
|
||||
</blockquote>
|
||||
<h1>
|
||||
sing to me, erbarme dich
|
||||
</h1>
|
||||
</dd>
|
||||
</dl>
|
||||
<p style="color: black; font-size: 1em; line-height: 1.3em; clear: both">
|
||||
This is a nonsensical document, but syntactically valid HTML 4.0. All 100% conformant CSS1 agents should be able to render the document elements above this paragraph <b>indistinguishably</b> (to the pixel) from this reference rendering, (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the <A HREF="sec5526c.htm" style="text-decoration:none">parent page</A>.
|
||||
</p>
|
||||
<script type="text/javascript" src="../dist/html2canvas.js"></script>
|
||||
<script type="text/javascript">
|
||||
html2canvas(document.body).then(function(canvas) {
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
63
examples/demo2.html
Normal file
63
examples/demo2.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<style>
|
||||
.feedback-overlay-black{
|
||||
background-color:#000;
|
||||
opacity:0.5;
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
div{
|
||||
padding:20px;
|
||||
margin:0 auto;
|
||||
border:5px solid black;
|
||||
}
|
||||
|
||||
h1{
|
||||
border-bottom:2px solid white;
|
||||
}
|
||||
|
||||
h2{
|
||||
background: #efefef;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background:red;">
|
||||
<div style="background:green;">
|
||||
<div style="background:blue;border-color:white;">
|
||||
<div style="background:yellow;"><div style="background:orange;"><h1>Heading</h1>
|
||||
Text that isn't wrapped in anything.
|
||||
<p>Followed by some text wrapped in a <b><p> paragraph.</b> </p>
|
||||
Maybe add a <a href="#">link</a> or a different style of <a href="#" style="background:white;" id="highlight">link with a highlight</a>.
|
||||
<hr />
|
||||
<h2>More content</h2>
|
||||
<div style="width:10px;height:10px;border-width:10px;padding:0;">a</div>
|
||||
</div></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../dist/html2canvas.js"></script>
|
||||
<script type="text/javascript">
|
||||
html2canvas(document.body).then(function(canvas) {
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
50
examples/existing_canvas.html
Normal file
50
examples/existing_canvas.html
Normal file
@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>Using an existing canvas to draw on</title>
|
||||
<style>
|
||||
canvas {
|
||||
border: 1px solid black;
|
||||
}
|
||||
button {
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
#content {
|
||||
background: rgba(100, 255, 255, 0.5);
|
||||
padding: 50px 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div><h1>HTML content to render:</h1>
|
||||
<div id="content">Render the content in this element <strong>only</strong> onto the existing canvas element</div>
|
||||
</div>
|
||||
<h1>Existing canvas:</h1>
|
||||
<canvas width="500" height="200"></canvas>
|
||||
<script type="text/javascript" src="../dist/html2canvas.js"></script>
|
||||
<button>Run html2canvas</button>
|
||||
<script type="text/javascript">
|
||||
var canvas = document.querySelector("canvas");
|
||||
var ctx = canvas.getContext("2d");
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(75,75,50,0,Math.PI*2,true); // Outer circle
|
||||
ctx.moveTo(110,75);
|
||||
ctx.arc(75,75,35,0,Math.PI,false); // Mouth (clockwise)
|
||||
ctx.moveTo(65,65);
|
||||
ctx.arc(60,65,5,0,Math.PI*2,true); // Left eye
|
||||
ctx.moveTo(95,65);
|
||||
ctx.arc(90,65,5,0,Math.PI*2,true); // Right eye
|
||||
ctx.stroke();
|
||||
|
||||
document.querySelector("button").addEventListener("click", function() {
|
||||
html2canvas(document.querySelector("#content"), {canvas: canvas, scale: 1}).then(function(canvas) {
|
||||
console.log('Drew on the existing canvas');
|
||||
});
|
||||
}, false);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
8981
external/jquery-1.6.2.js
vendored
8981
external/jquery-1.6.2.js
vendored
File diff suppressed because it is too large
Load Diff
18
external/jquery-1.6.2.min.js
vendored
18
external/jquery-1.6.2.min.js
vendored
File diff suppressed because one or more lines are too long
5
jest.config.js
Normal file
5
jest.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'jsdom',
|
||||
roots: ['src']
|
||||
};
|
295
karma.conf.js
Normal file
295
karma.conf.js
Normal file
@ -0,0 +1,295 @@
|
||||
// Karma configuration
|
||||
// Generated on Sat Aug 05 2017 23:42:26 GMT+0800 (Malay Peninsula Standard Time)
|
||||
|
||||
const path = require('path');
|
||||
const simctl = require('node-simctl');
|
||||
const iosSimulator = require('appium-ios-simulator');
|
||||
const listenAddress = 'localhost';
|
||||
const port = 9876;
|
||||
|
||||
const log = require('karma/lib/logger').create('launcher:MobileSafari');
|
||||
|
||||
module.exports = function(config) {
|
||||
// https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md
|
||||
const launchers = {
|
||||
Safari_IOS_9: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 5s',
|
||||
platform: 'iOS',
|
||||
sdk: '9.0'
|
||||
},
|
||||
Safari_IOS_10: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 5s',
|
||||
platform: 'iOS',
|
||||
sdk: '10.0'
|
||||
},
|
||||
Safari_IOS_12: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 5s',
|
||||
platform: 'iOS',
|
||||
sdk: '12.4'
|
||||
},
|
||||
Safari_IOS_13: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 8',
|
||||
platform: 'iOS',
|
||||
sdk: '13.7'
|
||||
},
|
||||
Safari_IOS_14: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 8',
|
||||
platform: 'iOS',
|
||||
sdk: '14.4'
|
||||
},
|
||||
Safari_IOS_15_0: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 13',
|
||||
platform: 'iOS',
|
||||
sdk: '15.0'
|
||||
},
|
||||
Safari_IOS_15: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 13',
|
||||
platform: 'iOS',
|
||||
sdk: '15.2'
|
||||
},
|
||||
SauceLabs_IE9: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
version: '9.0',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
SauceLabs_IE10: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
version: '10.0',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
SauceLabs_IE11: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
version: '11.0',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
SauceLabs_Edge18: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'MicrosoftEdge',
|
||||
version: '18.17763',
|
||||
platform: 'Windows 10'
|
||||
},
|
||||
SauceLabs_Android4: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Browser',
|
||||
platform: 'Android',
|
||||
version: '4.4',
|
||||
device: 'Android Emulator',
|
||||
},
|
||||
SauceLabs_iOS10_3: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Safari',
|
||||
platform: 'iOS',
|
||||
version: '10.3',
|
||||
device: 'iPhone 7 Plus Simulator'
|
||||
},
|
||||
SauceLabs_iOS9_3: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Safari',
|
||||
platform: 'iOS',
|
||||
version: '9.3',
|
||||
device: 'iPhone 6 Plus Simulator'
|
||||
},
|
||||
IE_9: {
|
||||
base: 'IE',
|
||||
'x-ua-compatible': 'IE=EmulateIE9',
|
||||
flags: ['-extoff']
|
||||
},
|
||||
IE_10: {
|
||||
base: 'IE',
|
||||
'x-ua-compatible': 'IE=EmulateIE10',
|
||||
flags: ['-extoff']
|
||||
},
|
||||
IE_11: {
|
||||
base: 'IE',
|
||||
flags: ['-extoff']
|
||||
},
|
||||
Safari_Stable: {
|
||||
base: 'SafariNative'
|
||||
},
|
||||
Chrome_Stable: {
|
||||
base: 'ChromeHeadless'
|
||||
},
|
||||
Firefox_Stable: {
|
||||
base: 'Firefox'
|
||||
}
|
||||
};
|
||||
|
||||
const ciLauncher = launchers[process.env.TARGET_BROWSER];
|
||||
|
||||
const customLaunchers = ciLauncher ? {target_browser: ciLauncher} : {
|
||||
stable_chrome: {
|
||||
base: 'ChromeHeadless'
|
||||
},
|
||||
stable_firefox: {
|
||||
base: 'Firefox'
|
||||
}
|
||||
};
|
||||
|
||||
const injectTypedArrayPolyfills = function(files) {
|
||||
files.unshift({
|
||||
pattern: path.resolve(__dirname, './node_modules/js-polyfills/typedarray.js'),
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false
|
||||
});
|
||||
};
|
||||
|
||||
injectTypedArrayPolyfills.$inject = ['config.files'];
|
||||
|
||||
const MobileSafari = function(baseBrowserDecorator, args) {
|
||||
if(process.platform !== "darwin"){
|
||||
log.error("This launcher only works in MacOS.");
|
||||
this._process.kill();
|
||||
return;
|
||||
}
|
||||
baseBrowserDecorator(this);
|
||||
this.on('start', url => {
|
||||
simctl.getDevices(args.sdk, args.platform).then(devices => {
|
||||
const d = devices.find(d => {
|
||||
return d.name === args.name;
|
||||
});
|
||||
|
||||
if (!d) {
|
||||
log.error(`No device found for sdk ${args.sdk} with name ${args.name}`);
|
||||
log.info(`Available devices:`, devices);
|
||||
this._process.kill();
|
||||
return;
|
||||
}
|
||||
|
||||
return iosSimulator.getSimulator(d.udid).then(device => {
|
||||
return simctl.bootDevice(d.udid).then(() => device);
|
||||
}).then(device => {
|
||||
return device.waitForBoot(60 * 5 * 1000).then(() => {
|
||||
return device.openUrl(url);
|
||||
});
|
||||
});
|
||||
}).catch(e => {
|
||||
console.log('err,', e);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
MobileSafari.prototype = {
|
||||
name: 'MobileSafari',
|
||||
DEFAULT_CMD: {
|
||||
darwin: '/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator',
|
||||
},
|
||||
ENV_CMD: null,
|
||||
};
|
||||
|
||||
MobileSafari.$inject = ['baseBrowserDecorator', 'args'];
|
||||
|
||||
config.set({
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '',
|
||||
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['mocha', 'inline-mocha-fix'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'build/testrunner.js',
|
||||
{ pattern: './tests/**/*', 'watched': true, 'included': false, 'served': true},
|
||||
{ pattern: './dist/**/*', 'watched': true, 'included': false, 'served': true},
|
||||
{ pattern: './node_modules/**/*', 'watched': true, 'included': false, 'served': true},
|
||||
],
|
||||
|
||||
plugins: [
|
||||
'karma-*',
|
||||
{
|
||||
'framework:inline-mocha-fix': ['factory', injectTypedArrayPolyfills]
|
||||
},
|
||||
{
|
||||
'launcher:MobileSafari': ['type', MobileSafari]
|
||||
}
|
||||
],
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
],
|
||||
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
},
|
||||
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['dots', 'junit'],
|
||||
|
||||
junitReporter: {
|
||||
outputDir: 'tmp/junit/'
|
||||
},
|
||||
|
||||
// web server listen address,
|
||||
listenAddress,
|
||||
|
||||
// web server port
|
||||
port,
|
||||
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: Object.keys(customLaunchers),
|
||||
|
||||
|
||||
customLaunchers,
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: true,
|
||||
|
||||
// Concurrency level
|
||||
// how many browser should be started simultaneous
|
||||
concurrency: 5,
|
||||
|
||||
proxies: {
|
||||
'/dist': `http://localhost:${port}/base/dist`,
|
||||
'/node_modules': `http://localhost:${port}/base/node_modules`,
|
||||
'/tests': `http://localhost:${port}/base/tests`,
|
||||
'/assets': `http://localhost:${port}/base/tests/assets`
|
||||
},
|
||||
|
||||
client: {
|
||||
mocha: {
|
||||
// change Karma's debug.html to the mocha web reporter
|
||||
reporter: 'html'
|
||||
}
|
||||
},
|
||||
|
||||
captureTimeout: 300000,
|
||||
|
||||
browserDisconnectTimeout: 60000,
|
||||
|
||||
browserNoActivityTimeout: 1200000
|
||||
})
|
||||
};
|
BIN
lib/compiler.jar
BIN
lib/compiler.jar
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
loading.gif
BIN
loading.gif
Binary file not shown.
Before Width: | Height: | Size: 8.0 KiB |
44894
package-lock.json
generated
Normal file
44894
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
124
package.json
Normal file
124
package.json
Normal file
@ -0,0 +1,124 @@
|
||||
{
|
||||
"title": "html2canvas",
|
||||
"name": "html2canvas",
|
||||
"description": "Screenshots with JavaScript",
|
||||
"main": "dist/html2canvas.js",
|
||||
"module": "dist/html2canvas.esm.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
"browser": "dist/html2canvas.js",
|
||||
"version": "1.4.1",
|
||||
"author": {
|
||||
"name": "Niklas von Hertzen",
|
||||
"email": "niklasvh@gmail.com",
|
||||
"url": "https://hertzen.com"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:niklasvh/html2canvas.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/niklasvh/html2canvas/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.4.3",
|
||||
"@babel/core": "^7.4.3",
|
||||
"@babel/preset-env": "^7.4.3",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"@rollup/plugin-commonjs": "^19.0.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@rollup/plugin-typescript": "^8.2.1",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/jest-image-snapshot": "^4.3.1",
|
||||
"@types/karma": "^6.3.0",
|
||||
"@types/mocha": "^8.2.3",
|
||||
"@types/node": "^16.3.1",
|
||||
"@types/platform": "^1.3.4",
|
||||
"@types/promise-polyfill": "^6.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||
"@typescript-eslint/parser": "^4.28.2",
|
||||
"appium-ios-simulator": "^3.10.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.5",
|
||||
"babel-plugin-add-module-exports": "^1.0.2",
|
||||
"babel-plugin-dev-expression": "^0.2.1",
|
||||
"base64-arraybuffer": "1.0.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"chai": "4.1.1",
|
||||
"chromeless": "^1.5.2",
|
||||
"cors": "^2.8.5",
|
||||
"es6-promise": "^4.2.8",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "3.4.0",
|
||||
"express": "^4.17.1",
|
||||
"filenamify-url": "1.0.0",
|
||||
"glob": "7.1.3",
|
||||
"html2canvas-proxy": "1.0.1",
|
||||
"jest": "^27.0.6",
|
||||
"jest-image-snapshot": "^4.5.1",
|
||||
"jquery": "^3.5.1",
|
||||
"js-polyfills": "^0.1.42",
|
||||
"karma": "^6.3.2",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-edge-launcher": "^0.4.2",
|
||||
"karma-firefox-launcher": "^2.1.0",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-junit-reporter": "^2.0.1",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-safarinative-launcher": "^1.1.0",
|
||||
"karma-sauce-launcher": "^2.0.2",
|
||||
"mocha": "^9.0.2",
|
||||
"node-simctl": "^5.3.0",
|
||||
"platform": "^1.3.6",
|
||||
"prettier": "^2.3.2",
|
||||
"replace-in-file": "^3.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.53.1",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"serve-index": "^1.9.1",
|
||||
"slash": "1.0.0",
|
||||
"standard-version": "^8.0.2",
|
||||
"ts-jest": "^27.0.3",
|
||||
"ts-loader": "^8.3.0",
|
||||
"ts-node": "^10.1.0",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript": "^4.3.5",
|
||||
"uglify-js": "^3.13.10",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist/ && rimraf build/ && mkdirp dist && mkdirp build",
|
||||
"build": "tsc --module commonjs && rollup -c rollup.config.ts && npm run build:create-reftest-list && npm run build:testrunner && npm run build:minify",
|
||||
"build:testrunner": "rollup -c tests/rollup.config.ts",
|
||||
"build:minify": "uglifyjs --compress --comments /^!/ -o dist/html2canvas.min.js --mangle -- dist/html2canvas.js",
|
||||
"build:reftest-result-list": "ts-node scripts/create-reftest-result-list.ts",
|
||||
"build:create-reftest-list": "ts-node scripts/create-reftest-list.ts tests/reftests/ignore.txt build/reftests.ts",
|
||||
"build:reftest-preview": "webpack --config www/webpack.config.js",
|
||||
"release": "standard-version",
|
||||
"format": "prettier --write \"{src,www/src,tests,scripts}/**/*.ts\"",
|
||||
"lint": "eslint src/**/*.ts --max-warnings 0",
|
||||
"test": "npm run lint && npm run unittest && npm run karma",
|
||||
"unittest": "jest",
|
||||
"reftests-diff": "mkdirp tmp/snapshots && jest --roots=tests --testMatch=**/reftest-diff.ts",
|
||||
"karma": "ts-node tests/karma",
|
||||
"watch": "rollup -c rollup.config.ts -w",
|
||||
"watch:unittest": "mocha --require ts-node/register --watch-extensions ts -w src/**/__tests__/*.ts",
|
||||
"start": "ts-node tests/server --port=8080 --cors=8081"
|
||||
},
|
||||
"homepage": "https://html2canvas.hertzen.com",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
}
|
||||
}
|
36
readme.md
36
readme.md
@ -1,36 +0,0 @@
|
||||
html2canvas
|
||||
===========
|
||||
|
||||
#### JavaScript HTML renderer ####
|
||||
|
||||
This script allows you to take "screenshots" of webpages or parts of it, directly on the users browser. The screenshot is based on the DOM and as such may not be 100% accurate to the real representation as it does not make an actual screenshot, but builds the screenshot based on the information available on the page.
|
||||
|
||||
|
||||
###How does it work?###
|
||||
The script renders the current page as a canvas image, by reading the DOM and the different styles applied to the elements. However, as many elements are displayed differently on different browsers and operating systems (such as form elements such as radio buttons or checkboxes) as well as
|
||||
|
||||
It does <b>not require any rendering from the server</b>, as the whole image is created on the <b>clients browser</b>. However, for browsers without <code>canvas</code> support alternatives such as <a href="http://flashcanvas.net/">flashcanvas</a> or <a href="http://excanvas.sourceforge.net/">ExplorerCanvas</a> are necessary to create the image.
|
||||
|
||||
Additionally, to render <code>iframe</code> content or images situated outside of the <a href="http://en.wikipedia.org/wiki/Same_origin_policy">same origin policy</a> a proxy will be necessary to load the content to the users browser.
|
||||
|
||||
The script is still in a very experimental state, so I don't recommend using it in a production environment nor start building applications with it yet, as there will be still major changes made. However, please do test it out and report your findings, especially if something should be working, but is displaying it incorrectly.
|
||||
|
||||
###Browser compatibility###
|
||||
|
||||
The script should work fine on the following browsers:
|
||||
|
||||
* Firefox 3.5+
|
||||
* Google Chrome
|
||||
* Newer versions of Opera (exactly how new is yet to be determined)
|
||||
* >=IE9 (Older versions compatible with the use of flashcanvas)
|
||||
|
||||
Note that the compatibility will most likely be increased in future builds, as many of the current restrictions have at least partial work arounds, which can be used with older browser versions.
|
||||
|
||||
###So what isn't included yet?###
|
||||
|
||||
There are still a lot of CSS properties missing, including most CSS3 properties such as <code>text-shadow</code>, <code>box-radius</code> etc. as well as all elements created by the browser, such as radio and checkbox buttons and list icons. I will compile a full list of supported elements and CSS properties soon.
|
||||
There is no support for <code>frame</code> and <code>object</code> content such as Flash.
|
||||
|
||||
### Examples ###
|
||||
|
||||
For more information and examples, please visit the <a href="http://html2canvas.hertzen.com">homepage</a> or try the <a href="http://html2canvas.hertzen.com/screenshots.html">test console</a>.
|
42
rollup.config.ts
Normal file
42
rollup.config.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import sourceMaps from 'rollup-plugin-sourcemaps';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import json from '@rollup/plugin-json';
|
||||
|
||||
const pkg = require('./package.json');
|
||||
|
||||
const banner = `/*!
|
||||
* ${pkg.title} ${pkg.version} <${pkg.homepage}>
|
||||
* Copyright (c) ${(new Date()).getFullYear()} ${pkg.author.name} <${pkg.author.url}>
|
||||
* Released under ${pkg.license} License
|
||||
*/`;
|
||||
|
||||
export default {
|
||||
input: `src/index.ts`,
|
||||
output: [
|
||||
{ file: pkg.main, name: pkg.name, format: 'umd', banner, sourcemap: true },
|
||||
{ file: pkg.module, format: 'esm', banner, sourcemap: true },
|
||||
],
|
||||
external: [],
|
||||
watch: {
|
||||
include: 'src/**',
|
||||
},
|
||||
plugins: [
|
||||
// Allow node_modules resolution, so you can use 'external' to control
|
||||
// which external modules to include in the bundle
|
||||
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
||||
resolve(),
|
||||
// Allow json resolution
|
||||
json(),
|
||||
// Compile TypeScript files
|
||||
typescript({ sourceMap: true, inlineSources: true }),
|
||||
// Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)
|
||||
commonjs({
|
||||
include: 'node_modules/**'
|
||||
}),
|
||||
|
||||
// Resolve source maps to the original source
|
||||
sourceMaps(),
|
||||
],
|
||||
}
|
326
screenshots.html
326
screenshots.html
@ -1,326 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>JavaScript screenshot creator</title>
|
||||
|
||||
<style type="text/css">
|
||||
a {
|
||||
color: #0B0B0B;
|
||||
background-color: #FDF9EE;
|
||||
padding: 0 8px;
|
||||
text-decoration: none;
|
||||
font: normal 12px/16px "Trebuchet MS", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0B0B0B;
|
||||
background-color: #EFEBDE;
|
||||
padding: 0 8px;
|
||||
text-decoration: none;
|
||||
font: normal 12px/16px "Trebuchet MS", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
body {
|
||||
font: normal 14px/19px Arial, Helvetica, sans-serif;
|
||||
background-color: white;
|
||||
color: #4E4628;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background-color: #EFEBDE;
|
||||
color: #0B0B0B;
|
||||
border: #C3BCA4 1px solid;
|
||||
|
||||
|
||||
|
||||
|
||||
font: normal 11px Arial, Helvetica, sans-serif;
|
||||
width:300px;
|
||||
height:150px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
background-color: white;
|
||||
color: #0B0B0B;
|
||||
font: normal 28px/46px Georgia, "Times New Roman", Times, serif;
|
||||
margin:0;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
h3 {
|
||||
|
||||
color: #786E4E;
|
||||
padding: 0 0 10px 55px;
|
||||
|
||||
|
||||
height: 37px;
|
||||
font: normal 24px/30px Georgia, "Times New Roman", Times, serif;
|
||||
}
|
||||
ul{
|
||||
float:left;
|
||||
margin:0;
|
||||
|
||||
}
|
||||
|
||||
table{
|
||||
margin:0 auto;
|
||||
width:400px;
|
||||
border:1px solid black;
|
||||
}
|
||||
#content{
|
||||
clear:both;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
#about{
|
||||
padding:0 10px;
|
||||
width:450px;
|
||||
float:left;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/javascript" src="external/jquery-1.6.2.min.js"></script>
|
||||
<script type="text/javascript" src="build/html2canvas.js?221"></script>
|
||||
<script type="text/javascript" src="build/jquery.plugin.html2canvas.js"></script>
|
||||
<script type="text/javascript" src="http://www.hertzen.com/js/ganalytics-heatmap.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var date = new Date();
|
||||
var message,
|
||||
timeoutTimer,
|
||||
timer;
|
||||
|
||||
var proxyUrl = "http://html2canvas.appspot.com";
|
||||
|
||||
function addRow(table,field,val){
|
||||
var tr = $('<tr />').appendTo( $(table));
|
||||
|
||||
tr.append($('<td />').css('font-weight','bold').text(field)).append($('<td />').text(val));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function throwMessage(msg,duration){
|
||||
|
||||
window.clearTimeout(timeoutTimer);
|
||||
timeoutTimer = window.setTimeout(function(){
|
||||
message.fadeOut(function(){
|
||||
message.remove();
|
||||
});
|
||||
},duration || 2000);
|
||||
$(message).remove();
|
||||
message = $('<div />').html(msg).css({
|
||||
margin:0,
|
||||
padding:10,
|
||||
background: "#000",
|
||||
opacity:0.7,
|
||||
position:"fixed",
|
||||
top:10,
|
||||
right:10,
|
||||
fontFamily: 'Tahoma' ,
|
||||
color:'#fff',
|
||||
fontSize:12,
|
||||
borderRadius:12,
|
||||
width:'auto',
|
||||
height:'auto',
|
||||
textAlign:'center',
|
||||
textDecoration:'none'
|
||||
}).hide().fadeIn().appendTo('body');
|
||||
}
|
||||
|
||||
$(function(){
|
||||
|
||||
$('ul li a').click(function(e){
|
||||
e.preventDefault();
|
||||
$('#url').val(this.href);
|
||||
$('button').click();
|
||||
})
|
||||
|
||||
var iframe,d;
|
||||
|
||||
|
||||
|
||||
|
||||
$('input[type="button"]').click(function(){
|
||||
$(iframe.contentWindow).unbind('load');
|
||||
$(iframe).contents().find('body').html2canvas({
|
||||
canvasHeight: d.body.scrollHeight,
|
||||
canvasWidth: d.body.scrollWidth,
|
||||
logging:true
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('button').click(function(){
|
||||
|
||||
$(this).prop('disabled',true);
|
||||
var url = $('#url').val();
|
||||
$('#content').append($('<img />').attr('src','loading.gif').css('margin-top',40));
|
||||
|
||||
var urlParts = document.createElement('a');
|
||||
urlParts.href = url;
|
||||
|
||||
$.ajax({
|
||||
data: {
|
||||
xhr2:false,
|
||||
url:urlParts.href
|
||||
|
||||
},
|
||||
url: proxyUrl,
|
||||
dataType: "jsonp",
|
||||
success: function(html){
|
||||
|
||||
|
||||
iframe = document.createElement('iframe');
|
||||
$(iframe).css({
|
||||
'visibility':'hidden'
|
||||
}).width($(window).width()).height($(window).height());
|
||||
$('#content').append(iframe);
|
||||
d = iframe.contentWindow.document;
|
||||
|
||||
d.open();
|
||||
|
||||
$(iframe.contentWindow).load(function(){
|
||||
|
||||
timer = date.getTime();
|
||||
|
||||
$(iframe).contents().find('body').html2canvas({
|
||||
canvasHeight: d.body.scrollHeight,
|
||||
canvasWidth: d.body.scrollWidth,
|
||||
logging:true,
|
||||
proxyUrl: proxyUrl,
|
||||
logger:function(msg){
|
||||
$('#logger').val(function(e,i){
|
||||
return i+"\n"+msg;
|
||||
});
|
||||
|
||||
},
|
||||
ready: function(renderer) {
|
||||
$('button').prop('disabled',false);
|
||||
$("#content").empty();
|
||||
var finishTime = new Date();
|
||||
|
||||
var table = $('<table />');
|
||||
$('#content')
|
||||
.append('<h2>Screenshot</h2>')
|
||||
.append(renderer.canvas)
|
||||
.append('<h3>Details</h3>')
|
||||
.append(table);
|
||||
|
||||
|
||||
|
||||
addRow(table,"Creation time",((finishTime.getTime()-timer)/1000) + " seconds");
|
||||
addRow(table,"Total draws", renderer.numDraws);
|
||||
addRow(table,"Context stacks", renderer.contextStacks.length);
|
||||
addRow(table,"Loaded images", renderer.images.length/2);
|
||||
addRow(table,"Performed z-index reorder", renderer.needReorder);
|
||||
addRow(table,"Used rangeBounds", renderer.support.rangeBounds);
|
||||
|
||||
|
||||
|
||||
throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)/1000) + " seconds<br />Total of "+renderer.numDraws+" draws performed",4000);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('base').attr('href',urlParts.protocol+"//"+urlParts.hostname+"/");
|
||||
html = html.replace("<head>","<head><base href='"+urlParts.protocol+"//"+urlParts.hostname+"/' />");
|
||||
if ($("#disablejs").prop('checked')){
|
||||
html = html.replace(/\<script/gi,"<!--<script");
|
||||
html = html.replace(/\<\/script\>/gi,"<\/script>-->");
|
||||
}
|
||||
// console.log(html);
|
||||
d.write(html);
|
||||
|
||||
d.close();
|
||||
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
var _gaq = _gaq || [];_gaq.push(['_setAccount', 'UA-188600-10']);_gaq.push(['_trackPageview']);(function() {var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);})();
|
||||
|
||||
</script>
|
||||
<base />
|
||||
</head>
|
||||
<body>
|
||||
<!-- <div style="background:red;padding:10px;color:#fff">
|
||||
App engine proxy is <a href="http://twitter.com/#!/Niklasvh/status/96265826713350144">temporarily out of use</a> due to exceeded bandwidth use. Please try again tomorrow or meanwhile check other examples <a href="http://html2canvas.hertzen.com/">here</a>.
|
||||
</div>-->
|
||||
|
||||
<div style="float:left;width:500px;">
|
||||
<h1>JavaScript screenshot creator</h1>
|
||||
<label for="url">Website URL:</label>
|
||||
<input type="url" id="url" value="http://www.yahoo.com" /><button>Get screenshot!</button>
|
||||
<!-- <input type="button" value="Try anyway" />--><br />
|
||||
|
||||
|
||||
<label for="disablejs">Disable JavaScript (recommended, doesn't work well with the proxy)</label> <input type="checkbox" id="disablejs" checked /><br />
|
||||
<small>Tested with Google Chrome 12, Firefox 4 and Opera 11.5</small>
|
||||
</div>
|
||||
<div style="float:right;">
|
||||
<div style="margin-left:17px;float:right;">
|
||||
<!-- Place this tag in your head or just before your close body tag -->
|
||||
<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
|
||||
|
||||
<!-- Place this tag where you want the +1 button to render -->
|
||||
<g:plusone size="tall"></g:plusone>
|
||||
</div>
|
||||
|
||||
<div style="float:right;">
|
||||
|
||||
<a href="http://twitter.com/share" class="twitter-share-button" data-url="http://html2canvas.hertzen.com/" data-text="html2canvas - screenshots with #JavaScript" data-count="vertical" data-via="niklasvh">Tweet</a><script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="clear:both;"></div>
|
||||
<h3>Recommended (tested) pages:</h3>
|
||||
|
||||
<ul>
|
||||
|
||||
<li><a href="http://www.yahoo.com">yahoo.com</a></li>
|
||||
<li><a href="http://www.google.com">google.com</a></li>
|
||||
<li><a href="https://github.com/niklasvh/html2canvas">github.com</a></li>
|
||||
<li><a href="http://www.smashingmagazine.com">smashingmagazine.com</a></li>
|
||||
<li><a href="http://www.mashable.com">mashable.com</a></li>
|
||||
<li><a href="http://www.facebook.com/google">facebook.com/google</a></li>
|
||||
<li><a href="http://www.youtube.com/">youtube.com</a></li>
|
||||
<li><a href="http://www.cnn.com/">cnn.com</a></li>
|
||||
|
||||
<li><a href="http://www.engadget.com/">engadget.com (lot of elements, very slow)</a></li>
|
||||
<li><a href="http://eu.battle.net/en/">battle.net</a></li>
|
||||
|
||||
|
||||
</ul>
|
||||
<div style="float:left;">
|
||||
<textarea id="logger"></textarea>
|
||||
</div>
|
||||
<div id="about"><b> About</b><br />
|
||||
The whole screenshot is created with JavaScript. The only server interaction that is happening on this page is the proxy for loading the external pages/images into JSONP/CORS enabled page and onwards onto the JavaScript renderer script.
|
||||
There are a lot of problems of loading external pages, even with a proxy, and as such many pages will not render at all. If you wish to try the script properly, I recommend you get a copy of the source from <a href="https://github.com/niklasvh/html2canvas">here</a> instead.
|
||||
</div>
|
||||
<div id="content"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
47
scripts/create-reftest-list.ts
Normal file
47
scripts/create-reftest-list.ts
Normal file
@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
import {readFileSync, writeFileSync} from 'fs';
|
||||
import {resolve, relative} from 'path';
|
||||
import {sync} from 'glob';
|
||||
|
||||
const slash = require('slash');
|
||||
|
||||
if (process.argv.length <= 2) {
|
||||
console.log('No ignore.txt file provided');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.argv.length <= 3) {
|
||||
console.log('No output file provided');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const path = resolve(__dirname, '../', process.argv[2]);
|
||||
const outputPath = resolve(__dirname, '../', process.argv[3]);
|
||||
const ignoredTests = readFileSync(path)
|
||||
.toString()
|
||||
.split(/\r\n|\r|\n/)
|
||||
.filter((l) => l.length)
|
||||
.reduce((acc: {[key: string]: string[]}, l) => {
|
||||
const m = l.match(/^(\[(.+)\])?(.+)$/i);
|
||||
if (m) {
|
||||
acc[m[3]] = m[2] ? m[2].split(',') : [];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const files: string[] = sync('../tests/reftests/**/*.html', {
|
||||
cwd: __dirname,
|
||||
root: resolve(__dirname, '../../')
|
||||
});
|
||||
|
||||
const testList = files.map((filename: string) => `/${slash(relative('../', filename))}`);
|
||||
writeFileSync(
|
||||
outputPath,
|
||||
[
|
||||
`export const testList: string[] = ${JSON.stringify(testList, null, 4)};`,
|
||||
`export const ignoredTests: {[key: string]: string[]} = ${JSON.stringify(ignoredTests, null, 4)};`
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
console.log(`${outputPath} updated`);
|
44
scripts/create-reftest-result-list.ts
Normal file
44
scripts/create-reftest-result-list.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import {readdirSync, readFileSync, writeFileSync} from 'fs';
|
||||
import {resolve} from 'path';
|
||||
|
||||
if (process.argv.length <= 2) {
|
||||
console.log('No metadata path provided');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.argv.length <= 3) {
|
||||
console.log('No output file given');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const path = resolve(__dirname, '../', process.argv[2]);
|
||||
const files = readdirSync(path);
|
||||
|
||||
interface RefTestMetadata {}
|
||||
|
||||
interface RefTestSingleMetadata extends RefTestMetadata {
|
||||
test?: string;
|
||||
}
|
||||
|
||||
interface RefTestResults {
|
||||
[key: string]: Array<RefTestMetadata>;
|
||||
}
|
||||
|
||||
const result: RefTestResults = files.reduce((result: RefTestResults, file) => {
|
||||
const json: RefTestSingleMetadata = JSON.parse(readFileSync(resolve(__dirname, path, file)).toString());
|
||||
if (json.test) {
|
||||
if (!result[json.test]) {
|
||||
result[json.test] = [];
|
||||
}
|
||||
|
||||
result[json.test].push(json);
|
||||
delete json.test;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
const output = resolve(__dirname, '../', process.argv[3]);
|
||||
writeFileSync(output, JSON.stringify(result));
|
||||
|
||||
console.log(`Wrote file ${output}`);
|
37
scripts/create-reftests.js
Normal file
37
scripts/create-reftests.js
Normal file
@ -0,0 +1,37 @@
|
||||
const {Chromeless} = require('chromeless');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const reftests = require('../tests/reftests');
|
||||
|
||||
const app = express();
|
||||
app.use('/', express.static(path.resolve(__dirname, '../')));
|
||||
|
||||
const listener = app.listen(0, () => {
|
||||
async function run() {
|
||||
const chromeless = new Chromeless();
|
||||
const tests = Object.keys(reftests.testList);
|
||||
let i = 0;
|
||||
while (tests[i]) {
|
||||
const filename = tests[i];
|
||||
i++;
|
||||
const reftest = await chromeless
|
||||
.goto(`http://localhost:${listener.address().port}${filename}?reftest&run=false`)
|
||||
.evaluate(() =>
|
||||
html2canvas(document.documentElement, {
|
||||
windowWidth: 800,
|
||||
windowHeight: 600,
|
||||
target: new RefTestRenderer()
|
||||
})
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, `..${filename.replace(/\.html$/i, '.txt')}`),
|
||||
reftest
|
||||
);
|
||||
}
|
||||
|
||||
await chromeless.end();
|
||||
}
|
||||
|
||||
run().catch(console.error.bind(console)).then(() => process.exit(0));
|
||||
});
|
155
scripts/parse-reftest.js
Normal file
155
scripts/parse-reftest.js
Normal file
@ -0,0 +1,155 @@
|
||||
const ACTION = /^\s*(\w+ ?\w*):\s+(.+)/;
|
||||
const TEXT = /^\s*\[(-?\d+), (-?\d+)\]:\s+(.+)/;
|
||||
const WINDOW_SIZE = /^\[(-?\d+), (-?\d+)\]$/;
|
||||
const RECTANGLE = /^\[(-?\d+), (-?\d+), (-?\d+), (-?\d+)\]\s+(.+)$/;
|
||||
const REPEAT = /^Image\s+\("(.+)"\)\s+\[(-?\d+), (-?\d+)\]\s+Size\s+\((-?\d+), (-?\d+)\)\s+(.+)$/;
|
||||
const PATH = /^Path \((.+)\)$/;
|
||||
const VECTOR = /^Vector\(x: (-?\d+), y: (-?\d+)\)$/;
|
||||
const BEZIER_CURVE = /^BezierCurve\(x0: (-?\d+), y0: (-?\d+), x1: (-?\d+), y1: (-?\d+), cx0: (-?\d+), cy0: (-?\d+), cx1: (-?\d+), cy1: (-?\d+)\)$/;
|
||||
const SHAPE = /^(rgba?\((:?.+)\)) (Path .+)$/;
|
||||
const CIRCLE = /^(rgba?\((:?.+)\)) Circle\(x: (-?\d+), y: (-?\d+), r: (-?\d+)\)$/;
|
||||
const IMAGE = /^Image\s+\("(.+)"\)\s+\(source:\s+\[(-?\d+), (-?\d+), (-?\d+), (-?\d+)\]\)\s+\(destination:\s+\[(-?\d+), (-?\d+), (-?\d+), (-?\d+)\]\)$/;
|
||||
const CANVAS = /^(Canvas)\s+\(source:\s+\[(-?\d+), (-?\d+), (-?\d+), (-?\d+)\]\)\s+\(destination:\s+\[(-?\d+), (-?\d+), (-?\d+), (-?\d+)\]\)$/;
|
||||
const GRADIENT = /^\[(-?\d+), (-?\d+), (-?\d+), (-?\d+)\]\s+linear-gradient\(x0: (-?\d+), x1: (-?\d+), y0: (-?\d+), y1: (-?\d+) (.+)\)$/;
|
||||
const TRANSFORM = /^\((-?\d+), (-?\d+)\) \[(.+)\]$/;
|
||||
|
||||
function parsePath(path) {
|
||||
const parts = path.match(PATH)[1];
|
||||
return parts.split(' > ').map(p => {
|
||||
const vector = p.match(VECTOR);
|
||||
if (vector) {
|
||||
return {
|
||||
type: 'Vector',
|
||||
x: parseInt(vector[1], 10),
|
||||
y: parseInt(vector[2], 10)
|
||||
};
|
||||
} else {
|
||||
const bezier = p.match(BEZIER_CURVE);
|
||||
return {
|
||||
type: 'BezierCurve',
|
||||
x0: parseInt(bezier[1], 10),
|
||||
y0: parseInt(bezier[2], 10),
|
||||
x1: parseInt(bezier[3], 10),
|
||||
y1: parseInt(bezier[4], 10),
|
||||
cx0: parseInt(bezier[5], 10),
|
||||
cy0: parseInt(bezier[6], 10),
|
||||
cx1: parseInt(bezier[7], 10),
|
||||
cy1: parseInt(bezier[8], 10)
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function parseRefTest(txt) {
|
||||
return txt.split(/\n/g).filter(l => l.length > 0).map((l, i) => {
|
||||
const parseAction = l.match(ACTION);
|
||||
if (!parseAction) {
|
||||
const text = l.match(TEXT);
|
||||
return {
|
||||
action: 'T',
|
||||
x: parseInt(text[1], 10),
|
||||
y: parseInt(text[2], 10),
|
||||
text: text[3],
|
||||
line: i + 1
|
||||
};
|
||||
}
|
||||
const args = parseAction[2];
|
||||
|
||||
const data = {
|
||||
action: parseAction[1],
|
||||
line: i + 1
|
||||
};
|
||||
|
||||
switch (data.action) {
|
||||
case 'Opacity':
|
||||
data.opacity = parseFloat(args);
|
||||
break;
|
||||
case 'Fill':
|
||||
data.color = args;
|
||||
break;
|
||||
case 'Clip':
|
||||
data.path = args.split(' | ').map(path => parsePath(path));
|
||||
break;
|
||||
case 'Window':
|
||||
const windowSize = args.match(WINDOW_SIZE);
|
||||
data.width = parseInt(windowSize[1], 10);
|
||||
data.height = parseInt(windowSize[2], 10);
|
||||
break;
|
||||
case 'Rectangle':
|
||||
const rectangle = args.match(RECTANGLE);
|
||||
data.x = parseInt(rectangle[1], 10);
|
||||
data.y = parseInt(rectangle[2], 10);
|
||||
data.width = parseInt(rectangle[3], 10);
|
||||
data.height = parseInt(rectangle[4], 10);
|
||||
data.color = rectangle[5];
|
||||
break;
|
||||
case 'Repeat':
|
||||
const repeat = args.match(REPEAT);
|
||||
data.imageSrc = repeat[1];
|
||||
data.x = parseInt(repeat[2], 10);
|
||||
data.y = parseInt(repeat[3], 10);
|
||||
data.width = parseInt(repeat[4], 10);
|
||||
data.height = parseInt(repeat[5], 10);
|
||||
data.path = parsePath(repeat[6]);
|
||||
break;
|
||||
case 'Shape':
|
||||
const shape = args.match(SHAPE);
|
||||
if (!shape) {
|
||||
const circle = args.match(CIRCLE);
|
||||
data.color = circle[1];
|
||||
data.path = [
|
||||
{
|
||||
type: 'Circle',
|
||||
x: parseInt(circle[2], 10),
|
||||
y: parseInt(circle[3], 10),
|
||||
r: parseInt(circle[4], 10)
|
||||
}
|
||||
];
|
||||
} else {
|
||||
data.color = shape[1];
|
||||
data.path = parsePath(shape[3]);
|
||||
}
|
||||
break;
|
||||
case 'Text':
|
||||
data.font = args;
|
||||
break;
|
||||
case 'Draw image':
|
||||
const image = args.match(IMAGE) ? args.match(IMAGE) : args.match(CANVAS);
|
||||
data.imageSrc = image[1];
|
||||
data.sx = parseInt(image[2], 10);
|
||||
data.xy = parseInt(image[3], 10);
|
||||
data.sw = parseInt(image[4], 10);
|
||||
data.sh = parseInt(image[5], 10);
|
||||
data.dx = parseInt(image[6], 10);
|
||||
data.dy = parseInt(image[7], 10);
|
||||
data.dw = parseInt(image[8], 10);
|
||||
data.dh = parseInt(image[9], 10);
|
||||
break;
|
||||
case 'Gradient':
|
||||
const gradient = args.match(GRADIENT);
|
||||
data.x = parseInt(gradient[1], 10);
|
||||
data.y = parseInt(gradient[2], 10);
|
||||
data.width = parseInt(gradient[3], 10);
|
||||
data.height = parseInt(gradient[4], 10);
|
||||
data.x0 = parseInt(gradient[5], 10);
|
||||
data.x1 = parseInt(gradient[6], 10);
|
||||
data.y0 = parseInt(gradient[7], 10);
|
||||
data.y1 = parseInt(gradient[8], 10);
|
||||
data.stops = gradient[9];
|
||||
break;
|
||||
case 'Transform':
|
||||
const transform = args.match(TRANSFORM);
|
||||
data.x = parseInt(transform[1], 10);
|
||||
data.y = parseInt(transform[2], 10);
|
||||
data.matrix = transform[3];
|
||||
break;
|
||||
default:
|
||||
console.log(args);
|
||||
throw new Error('Unhandled action ' + data.action);
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parseRefTest;
|
@ -1,300 +0,0 @@
|
||||
|
||||
|
||||
html2canvas.prototype.drawBackground = function(el,bounds,ctx){
|
||||
|
||||
// TODO add support for multi background-images
|
||||
var background_image = this.getCSS(el,"background-image").split(",")[0];
|
||||
var background_repeat = this.getCSS(el,"background-repeat").split(",")[0];
|
||||
|
||||
if (typeof background_image != "undefined" && /^(1|none)$/.test(background_image)==false && /^(-webkit|-moz|linear-gradient|-o-)/.test(background_image)==false){
|
||||
|
||||
background_image = this.backgroundImageUrl(background_image);
|
||||
var image = this.loadImage(background_image);
|
||||
|
||||
|
||||
var bgp = this.getBackgroundPosition(el,bounds,image),
|
||||
bgy;
|
||||
|
||||
if (image){
|
||||
switch(background_repeat){
|
||||
|
||||
case "repeat-x":
|
||||
this.drawbackgroundRepeatX(ctx,image,bgp,bounds.left,bounds.top,bounds.width,bounds.height);
|
||||
break;
|
||||
|
||||
case "repeat-y":
|
||||
this.drawbackgroundRepeatY(ctx,image,bgp,bounds.left,bounds.top,bounds.width,bounds.height);
|
||||
break;
|
||||
|
||||
case "no-repeat":
|
||||
/*
|
||||
this.drawBackgroundRepeat(
|
||||
ctx,
|
||||
image,
|
||||
bgp.left+bounds.left, // sx
|
||||
bgp.top+bounds.top, // sy
|
||||
Math.min(bounds.width,image.width),
|
||||
Math.min(bounds.height,image.height),
|
||||
bounds.left,
|
||||
bounds.top
|
||||
);*/
|
||||
|
||||
|
||||
// console.log($(el).css('background-image'));
|
||||
var bgw = bounds.width-bgp.left,
|
||||
bgh = bounds.height-bgp.top,
|
||||
bgsx = bgp.left,
|
||||
bgsy = bgp.top,
|
||||
bgdx = bgp.left+bounds.left,
|
||||
bgdy = bgp.top+bounds.top;
|
||||
|
||||
//
|
||||
// bgw = Math.min(bgw,image.width);
|
||||
// bgh = Math.min(bgh,image.height);
|
||||
|
||||
if (bgsx<0){
|
||||
bgsx = Math.abs(bgsx);
|
||||
bgdx += bgsx;
|
||||
bgw = Math.min(bounds.width,image.width-bgsx);
|
||||
}else{
|
||||
bgw = Math.min(bgw,image.width);
|
||||
bgsx = 0;
|
||||
}
|
||||
|
||||
if (bgsy<0){
|
||||
bgsy = Math.abs(bgsy);
|
||||
bgdy += bgsy;
|
||||
// bgh = bgh-bgsy;
|
||||
bgh = Math.min(bounds.height,image.height-bgsy);
|
||||
}else{
|
||||
bgh = Math.min(bgh,image.height);
|
||||
bgsy = 0;
|
||||
}
|
||||
|
||||
|
||||
// bgh = Math.abs(bgh);
|
||||
// bgw = Math.abs(bgw);
|
||||
if (bgh>0 && bgw > 0){
|
||||
this.drawImage(
|
||||
ctx,
|
||||
image,
|
||||
bgsx, // source X : 0
|
||||
bgsy, // source Y : 1695
|
||||
bgw, // source Width : 18
|
||||
bgh, // source Height : 1677
|
||||
bgdx, // destination X :906
|
||||
bgdy, // destination Y : 1020
|
||||
bgw, // destination width : 18
|
||||
bgh // destination height : 1677
|
||||
);
|
||||
|
||||
// ctx.drawImage(image,(bounds.left+bgp.left),(bounds.top+bgp.top));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
var height,
|
||||
add;
|
||||
|
||||
|
||||
bgp.top = bgp.top-Math.ceil(bgp.top/image.height)*image.height;
|
||||
|
||||
|
||||
for(bgy=(bounds.top+bgp.top);bgy<bounds.height+bounds.top;){
|
||||
|
||||
|
||||
|
||||
var h = Math.min(image.height,(bounds.height+bounds.top)-bgy);
|
||||
|
||||
|
||||
if ( Math.floor(bgy+image.height)>h+bgy){
|
||||
height = (h+bgy)-bgy;
|
||||
}else{
|
||||
height = image.height;
|
||||
}
|
||||
// console.log(height);
|
||||
|
||||
if (bgy<bounds.top){
|
||||
add = bounds.top-bgy;
|
||||
bgy = bounds.top;
|
||||
|
||||
}else{
|
||||
add = 0;
|
||||
}
|
||||
|
||||
this.drawbackgroundRepeatX(ctx,image,bgp,bounds.left,bgy,bounds.width,height);
|
||||
if (add>0){
|
||||
bgp.top += add;
|
||||
}
|
||||
bgy = Math.floor(bgy+image.height)-add;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
}else{
|
||||
|
||||
this.log("Error loading background:" + background_image);
|
||||
//console.log(images);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Function to retrieve the actual src of a background-image
|
||||
*/
|
||||
|
||||
html2canvas.prototype.backgroundImageUrl = function(src){
|
||||
if (src.substr(0,5)=='url("'){
|
||||
src = src.substr(5);
|
||||
src = src.substr(0,src.length-2);
|
||||
}else{
|
||||
src = src.substr(4);
|
||||
src = src.substr(0,src.length-1);
|
||||
}
|
||||
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Function to retrieve background-position, both in pixels and %
|
||||
*/
|
||||
|
||||
html2canvas.prototype.getBackgroundPosition = function(el,bounds,image){
|
||||
// TODO add support for multi image backgrounds
|
||||
|
||||
var bgpos = this.getCSS(el,"backgroundPosition").split(",")[0] || "0 0";
|
||||
// var bgpos = $(el).css("backgroundPosition") || "0 0";
|
||||
var bgposition = bgpos.split(" "),
|
||||
topPos,
|
||||
left,
|
||||
percentage;
|
||||
|
||||
if (bgposition.length==1){
|
||||
var val = bgposition,
|
||||
bgposition = [];
|
||||
|
||||
bgposition[0] = val,
|
||||
bgposition[1] = val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (bgposition[0].toString().indexOf("%")!=-1){
|
||||
|
||||
percentage = (parseFloat(bgposition[0])/100);
|
||||
left = ((bounds.width * percentage)-(image.width*percentage));
|
||||
|
||||
}else{
|
||||
left = parseInt(bgposition[0],10);
|
||||
}
|
||||
|
||||
if (bgposition[1].toString().indexOf("%")!=-1){
|
||||
|
||||
percentage = (parseFloat(bgposition[1])/100);
|
||||
topPos = ((bounds.height * percentage)-(image.height*percentage));
|
||||
}else{
|
||||
|
||||
topPos = parseInt(bgposition[1],10);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
var returnObj = {}
|
||||
/*
|
||||
"top": topPos,
|
||||
"left": left
|
||||
};*/
|
||||
|
||||
|
||||
returnObj.top = topPos;
|
||||
returnObj.left = left;
|
||||
|
||||
|
||||
|
||||
return returnObj;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
html2canvas.prototype.drawbackgroundRepeatY = function(ctx,image,bgp,x,y,w,h){
|
||||
|
||||
var height,
|
||||
width = Math.min(image.width,w),bgy;
|
||||
|
||||
bgp.top = bgp.top-Math.ceil(bgp.top/image.height)*image.height;
|
||||
|
||||
|
||||
for(bgy=(y+bgp.top);bgy<h+y;){
|
||||
|
||||
|
||||
if ( Math.floor(bgy+image.height)>h+y){
|
||||
height = (h+y)-bgy;
|
||||
}else{
|
||||
height = image.height;
|
||||
}
|
||||
this.drawBackgroundRepeat(ctx,image,x+bgp.left,bgy,width,height,x,y);
|
||||
|
||||
bgy = Math.floor(bgy+image.height);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
html2canvas.prototype.drawbackgroundRepeatX = function(ctx,image,bgp,x,y,w,h){
|
||||
|
||||
var height = Math.min(image.height,h),
|
||||
width,bgx;
|
||||
|
||||
|
||||
bgp.left = bgp.left-Math.ceil(bgp.left/image.width)*image.width;
|
||||
|
||||
|
||||
for(bgx=(x+bgp.left);bgx<w+x;){
|
||||
|
||||
if (Math.floor(bgx+image.width)>w+x){
|
||||
width = (w+x)-bgx;
|
||||
}else{
|
||||
width = image.width;
|
||||
}
|
||||
|
||||
this.drawBackgroundRepeat(ctx,image,bgx,(y+bgp.top),width,height,x,y);
|
||||
|
||||
bgx = Math.floor(bgx+image.width);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
html2canvas.prototype.drawBackgroundRepeat = function(ctx,image,x,y,width,height,elx,ely){
|
||||
var sourceX = 0,
|
||||
sourceY=0;
|
||||
if (elx-x>0){
|
||||
sourceX = elx-x;
|
||||
}
|
||||
|
||||
if (ely-y>0){
|
||||
sourceY = ely-y;
|
||||
}
|
||||
|
||||
this.drawImage(
|
||||
ctx,
|
||||
image,
|
||||
sourceX, // source X
|
||||
sourceY, // source Y
|
||||
width-sourceX, // source Width
|
||||
height-sourceY, // source Height
|
||||
x+sourceX, // destination X
|
||||
y+sourceY, // destination Y
|
||||
width-sourceX, // destination width
|
||||
height-sourceY // destination height
|
||||
);
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Function to provide border details for an element
|
||||
*/
|
||||
|
||||
html2canvas.prototype.getBorderData = function(el){
|
||||
|
||||
var borders = [];
|
||||
var _ = this;
|
||||
this.each(["top","right","bottom","left"],function(i,borderSide){
|
||||
borders.push({
|
||||
width: parseInt(_.getCSS(el,'border-'+borderSide+'-width'),10),
|
||||
color: _.getCSS(el,'border-'+borderSide+'-color')
|
||||
});
|
||||
});
|
||||
|
||||
return borders;
|
||||
|
||||
}
|
||||
|
||||
html2canvas.prototype.drawBorders = function(el,ctx, bounds,clip){
|
||||
|
||||
|
||||
var x = bounds.left;
|
||||
var y = bounds.top;
|
||||
var w = bounds.width;
|
||||
var h = bounds.height;
|
||||
|
||||
/*
|
||||
* TODO add support for different border-style's than solid
|
||||
*/
|
||||
var borders = this.getBorderData(el);
|
||||
var _ = this;
|
||||
|
||||
this.each(borders,function(borderSide,borderData){
|
||||
if (borderData.width>0){
|
||||
var bx = x,
|
||||
by = y,
|
||||
bw = w,
|
||||
bh = h-(borders[2].width);
|
||||
switch(borderSide){
|
||||
case 0:
|
||||
// top border
|
||||
bh = borders[0].width;
|
||||
break;
|
||||
case 1:
|
||||
// right border
|
||||
bx = x+w-(borders[1].width);
|
||||
bw = borders[1].width;
|
||||
break;
|
||||
case 2:
|
||||
// bottom border
|
||||
by = (by+h)-(borders[2].width);
|
||||
bh = borders[2].width;
|
||||
break;
|
||||
case 3:
|
||||
// left border
|
||||
bw = borders[3].width;
|
||||
break;
|
||||
}
|
||||
|
||||
var borderBounds = {
|
||||
left:bx,
|
||||
top:by,
|
||||
width: bw,
|
||||
height:bh
|
||||
};
|
||||
|
||||
if (clip){
|
||||
borderBounds = _.clipBounds(borderBounds,clip);
|
||||
}
|
||||
|
||||
|
||||
if (borderBounds.width>0 && borderBounds.height>0){
|
||||
_.newRect(ctx,bx,by,borderBounds.width,borderBounds.height,borderData.color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return borders;
|
||||
|
||||
};
|
260
src/Core.js
260
src/Core.js
@ -1,260 +0,0 @@
|
||||
/**
|
||||
* Creates a render of the element el
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
function html2canvas(el, userOptions) {
|
||||
|
||||
var options = userOptions || {};
|
||||
|
||||
|
||||
this.opts = this.extendObj(options, {
|
||||
logging: false,
|
||||
ready: function (stack) {
|
||||
document.body.appendChild(stack.canvas);
|
||||
},
|
||||
storageReady: function(obj){
|
||||
obj.Renderer(obj.contextStacks);
|
||||
},
|
||||
iframeDefault: "default",
|
||||
flashCanvasPath: "http://html2canvas.hertzen.com/external/flashcanvas/flashcanvas.js",
|
||||
renderViewport: false,
|
||||
reorderZ: true,
|
||||
throttle:true,
|
||||
letterRendering:false,
|
||||
proxyUrl: null,
|
||||
logger: function(a){
|
||||
if (window.console && window.console.log){
|
||||
window.console.log(a);
|
||||
}else{
|
||||
alert(a);
|
||||
}
|
||||
},
|
||||
canvasWidth:0,
|
||||
canvasHeight:0,
|
||||
useOverflow: true,
|
||||
renderOrder: "canvas flash html"
|
||||
});
|
||||
|
||||
this.element = el;
|
||||
|
||||
var imageLoaded,
|
||||
canvas,
|
||||
ctx,
|
||||
bgx,
|
||||
bgy,
|
||||
image;
|
||||
this.imagesLoaded = 0;
|
||||
this.images = [];
|
||||
this.fontData = [];
|
||||
this.numDraws = 0;
|
||||
this.contextStacks = [];
|
||||
this.ignoreElements = "IFRAME|OBJECT|PARAM";
|
||||
this.needReorder = false;
|
||||
this.blockElements = new RegExp("(BR|PARAM)");
|
||||
this.pageOrigin = window.location.protocol + window.location.host;
|
||||
this.queue = [];
|
||||
|
||||
this.ignoreRe = new RegExp("("+this.ignoreElements+")");
|
||||
|
||||
|
||||
this.support = {
|
||||
rangeBounds: false
|
||||
|
||||
};
|
||||
|
||||
// Test whether we can use ranges to measure bounding boxes
|
||||
// Opera doesn't provide valid bounds.height/bottom even though it supports the method.
|
||||
|
||||
|
||||
if (document.createRange){
|
||||
var r = document.createRange();
|
||||
//this.support.rangeBounds = new Boolean(r.getBoundingClientRect);
|
||||
if (r.getBoundingClientRect){
|
||||
var testElement = document.createElement('boundtest');
|
||||
testElement.style.height = "123px";
|
||||
testElement.style.display = "block";
|
||||
document.getElementsByTagName('body')[0].appendChild(testElement);
|
||||
|
||||
r.selectNode(testElement);
|
||||
var rangeBounds = r.getBoundingClientRect();
|
||||
var rangeHeight = rangeBounds.height;
|
||||
|
||||
if (rangeHeight==123){
|
||||
this.support.rangeBounds = true;
|
||||
}
|
||||
document.getElementsByTagName('body')[0].removeChild(testElement);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Start script
|
||||
this.init();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
html2canvas.prototype.init = function(){
|
||||
|
||||
var _ = this;
|
||||
/*
|
||||
this.ctx = new this.stackingContext($(document).width(),$(document).height());
|
||||
|
||||
if (!this.ctx){
|
||||
// canvas not initialized, let's kill it here
|
||||
this.log('Canvas not available');
|
||||
return;
|
||||
}
|
||||
|
||||
this.canvas = this.ctx.canvas;
|
||||
*/
|
||||
this.log('Finding background-images');
|
||||
|
||||
this.images.push('start');
|
||||
|
||||
this.getImages(this.element);
|
||||
|
||||
this.log('Finding images');
|
||||
// console.log(this.element.ownerDocument);
|
||||
|
||||
|
||||
|
||||
this.each(this.element.ownerDocument.images,function(i,e){
|
||||
_.preloadImage(_.getAttr(e,'src'));
|
||||
});
|
||||
this.images.splice(0,1);
|
||||
// console.log(this.images);
|
||||
if (this.images.length == 0){
|
||||
this.start();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether all assets have been loaded and start traversing the DOM
|
||||
*/
|
||||
|
||||
html2canvas.prototype.start = function(){
|
||||
// console.log(this.images);
|
||||
if (this.images.length == 0 || this.imagesLoaded==this.images.length/2){
|
||||
|
||||
this.log('Finished loading '+this.imagesLoaded+' images, Started parsing');
|
||||
this.bodyOverflow = document.getElementsByTagName('body')[0].style.overflow;
|
||||
document.getElementsByTagName('body')[0].style.overflow = "hidden";
|
||||
var rootStack = new this.storageContext($(document).width(),$(document).height());
|
||||
rootStack.opacity = this.getCSS(this.element,"opacity");
|
||||
var stack = this.newElement(this.element,rootStack);
|
||||
|
||||
|
||||
this.parseElement(this.element,stack);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
html2canvas.prototype.stackingContext = function(width,height){
|
||||
this.canvas = document.createElement('canvas');
|
||||
|
||||
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = width;
|
||||
|
||||
if (!this.canvas.getContext){
|
||||
|
||||
// TODO include Flashcanvas
|
||||
/*
|
||||
var script = document.createElement('script');
|
||||
script.type = "text/javascript";
|
||||
script.src = this.opts.flashCanvasPath;
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(script, s);
|
||||
|
||||
if (typeof FlashCanvas != "undefined") {
|
||||
|
||||
FlashCanvas.initElement(this.canvas);
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
} */
|
||||
|
||||
}else{
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
}
|
||||
|
||||
// set common settings for canvas
|
||||
this.ctx.textBaseline = "bottom";
|
||||
|
||||
return this.ctx;
|
||||
|
||||
}
|
||||
|
||||
html2canvas.prototype.storageContext = function(width,height){
|
||||
this.storage = [];
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
//this.zIndex;
|
||||
|
||||
// todo simplify this whole section
|
||||
this.fillRect = function(x, y, w, h){
|
||||
this.storage.push(
|
||||
{
|
||||
type: "function",
|
||||
name:"fillRect",
|
||||
arguments:[x,y,w,h]
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.drawImage = function(image,sx,sy,sw,sh,dx,dy,dw,dh){
|
||||
this.storage.push(
|
||||
{
|
||||
type: "function",
|
||||
name:"drawImage",
|
||||
arguments:[image,sx,sy,sw,sh,dx,dy,dw,dh]
|
||||
});
|
||||
};
|
||||
|
||||
this.fillText = function(currentText,x,y){
|
||||
|
||||
this.storage.push(
|
||||
{
|
||||
type: "function",
|
||||
name:"fillText",
|
||||
arguments:[currentText,x,y]
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Finished rendering, send callback
|
||||
*/
|
||||
|
||||
html2canvas.prototype.finish = function(){
|
||||
|
||||
this.log("Finished rendering");
|
||||
|
||||
document.getElementsByTagName('body')[0].style.overflow = this.bodyOverflow;
|
||||
/*
|
||||
if (this.opts.renderViewport){
|
||||
// let's crop it to viewport only then
|
||||
var newCanvas = document.createElement('canvas');
|
||||
var newctx = newCanvas.getContext('2d');
|
||||
newCanvas.width = window.innerWidth;
|
||||
newCanvas.height = window.innerHeight;
|
||||
|
||||
}*/
|
||||
this.opts.ready(this);
|
||||
}
|
||||
|
226
src/Draw.js
226
src/Draw.js
@ -1,226 +0,0 @@
|
||||
html2canvas.prototype.newElement = function(el,parentStack){
|
||||
|
||||
var bounds = this.getBounds(el);
|
||||
|
||||
var x = bounds.left;
|
||||
var y = bounds.top;
|
||||
var w = bounds.width;
|
||||
var h = bounds.height;
|
||||
var _ = this,
|
||||
image;
|
||||
var bgcolor = this.getCSS(el,"background-color");
|
||||
|
||||
var cssPosition = this.getCSS(el,"position");
|
||||
parentStack = parentStack || {};
|
||||
|
||||
//var zindex = this.formatZ(this.getCSS(el,"zIndex"),cssPosition,parentStack.zIndex,el.parentNode);
|
||||
|
||||
var zindex = this.setZ(this.getCSS(el,"zIndex"),cssPosition,parentStack.zIndex,el.parentNode);
|
||||
|
||||
//console.log(el.nodeName+":"+zindex+":"+this.getCSS(el,"position")+":"+this.numDraws+":"+this.getCSS(el,"z-index"))
|
||||
|
||||
var opacity = this.getCSS(el,"opacity");
|
||||
|
||||
|
||||
var stack = {
|
||||
ctx: new this.storageContext(),
|
||||
zIndex: zindex,
|
||||
opacity: opacity*parentStack.opacity,
|
||||
cssPosition: cssPosition
|
||||
};
|
||||
|
||||
|
||||
|
||||
// TODO correct overflow for absolute content residing under a static position
|
||||
if (parentStack.clip){
|
||||
stack.clip = $.extend({}, parentStack.clip);
|
||||
//stack.clip = parentStack.clip;
|
||||
stack.clip.height = stack.clip.height - parentStack.borders[2].width;
|
||||
}
|
||||
|
||||
|
||||
if (this.opts.useOverflow && /(hidden|scroll|auto)/.test(this.getCSS(el,"overflow")) && !/(BODY)/i.test(el.nodeName)){
|
||||
if (stack.clip){
|
||||
stack.clip = this.clipBounds(stack.clip,bounds);
|
||||
}else{
|
||||
stack.clip = bounds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
/*
|
||||
var stackLength = this.contextStacks.push(stack);
|
||||
|
||||
var ctx = this.contextStacks[stackLength-1].ctx;
|
||||
*/
|
||||
|
||||
var stackLength = zindex.children.push(stack);
|
||||
|
||||
var ctx = zindex.children[stackLength-1].ctx;
|
||||
|
||||
this.setContextVariable(ctx,"globalAlpha",stack.opacity);
|
||||
|
||||
// draw element borders
|
||||
var borders = this.drawBorders(el, ctx, bounds);
|
||||
stack.borders = borders;
|
||||
|
||||
// let's modify clip area for child elements, so borders dont get overwritten
|
||||
|
||||
/*
|
||||
if (stack.clip){
|
||||
stack.clip.width = stack.clip.width-(borders[1].width);
|
||||
stack.clip.height = stack.clip.height-(borders[2].width);
|
||||
}
|
||||
*/
|
||||
if (this.ignoreRe.test(el.nodeName) && this.opts.iframeDefault != "transparent"){
|
||||
if (this.opts.iframeDefault=="default"){
|
||||
bgcolor = "#efefef";
|
||||
/*
|
||||
* TODO write X over frame
|
||||
*/
|
||||
}else{
|
||||
bgcolor = this.opts.iframeDefault;
|
||||
}
|
||||
}
|
||||
|
||||
// draw base element bgcolor
|
||||
|
||||
var bgbounds = {
|
||||
left: x+borders[3].width,
|
||||
top: y+borders[0].width,
|
||||
width: w-(borders[1].width+borders[3].width),
|
||||
height: h-(borders[0].width+borders[2].width)
|
||||
};
|
||||
|
||||
//if (this.withinBounds(stack.clip,bgbounds)){
|
||||
|
||||
if (stack.clip){
|
||||
bgbounds = this.clipBounds(bgbounds,stack.clip);
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
if (bgbounds.height>0 && bgbounds.width>0){
|
||||
this.newRect(
|
||||
ctx,
|
||||
bgbounds.left,
|
||||
bgbounds.top,
|
||||
bgbounds.width,
|
||||
bgbounds.height,
|
||||
bgcolor
|
||||
);
|
||||
|
||||
this.drawBackground(el,bgbounds,ctx);
|
||||
}
|
||||
|
||||
switch(el.nodeName){
|
||||
case "IMG":
|
||||
image = _.loadImage(_.getAttr(el,'src'));
|
||||
if (image){
|
||||
// console.log(image.width);
|
||||
this.drawImage(
|
||||
ctx,
|
||||
image,
|
||||
0, //sx
|
||||
0, //sy
|
||||
image.width, //sw
|
||||
image.height, //sh
|
||||
x+parseInt(_.getCSS(el,'padding-left'),10) + borders[3].width, //dx
|
||||
y+parseInt(_.getCSS(el,'padding-top'),10) + borders[0].width, // dy
|
||||
bounds.width - (borders[1].width + borders[3].width + parseInt(_.getCSS(el,'padding-left'),10) + parseInt(_.getCSS(el,'padding-right'),10)), //dw
|
||||
bounds.height - (borders[0].width + borders[2].width + parseInt(_.getCSS(el,'padding-top'),10) + parseInt(_.getCSS(el,'padding-bottom'),10)) //dh
|
||||
);
|
||||
|
||||
}else {
|
||||
this.log("Error loading <img>:" + _.getAttr(el,'src'));
|
||||
}
|
||||
break;
|
||||
case "INPUT":
|
||||
// TODO add all relevant type's, i.e. HTML5 new stuff
|
||||
// todo add support for placeholder attribute for browsers which support it
|
||||
if (/^(text|url|email|submit|button|reset)$/.test(el.type) && el.value.length > 0){
|
||||
|
||||
this.renderFormValue(el,bounds,stack);
|
||||
|
||||
|
||||
/*
|
||||
this just doesn't work well enough
|
||||
|
||||
this.newText(el,{
|
||||
nodeValue:el.value,
|
||||
splitText: function(){
|
||||
return this;
|
||||
},
|
||||
formValue:true
|
||||
},stack);
|
||||
*/
|
||||
}
|
||||
break;
|
||||
case "TEXTAREA":
|
||||
if (el.value.length > 0){
|
||||
this.renderFormValue(el,bounds,stack);
|
||||
}
|
||||
break;
|
||||
case "SELECT":
|
||||
if (el.options.length > 0){
|
||||
this.renderFormValue(el,bounds,stack);
|
||||
}
|
||||
break;
|
||||
case "LI":
|
||||
this.drawListItem(el,stack,bgbounds);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// return this.contextStacks[stackLength-1];
|
||||
return zindex.children[stackLength-1];
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Function to draw the text on the canvas
|
||||
*/
|
||||
|
||||
html2canvas.prototype.printText = function(currentText,x,y,ctx){
|
||||
if (this.trim(currentText).length>0){
|
||||
|
||||
ctx.fillText(currentText,x,y);
|
||||
this.numDraws++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Drawing a rectangle
|
||||
html2canvas.prototype.newRect = function(ctx,x,y,w,h,bgcolor){
|
||||
|
||||
if (bgcolor!="transparent"){
|
||||
this.setContextVariable(ctx,"fillStyle",bgcolor);
|
||||
ctx.fillRect (x, y, w, h);
|
||||
this.numDraws++;
|
||||
}
|
||||
}
|
||||
|
||||
html2canvas.prototype.drawImage = function(ctx,image,sx,sy,sw,sh,dx,dy,dw,dh){
|
||||
ctx.drawImage(
|
||||
image,
|
||||
sx, //sx
|
||||
sy, //sy
|
||||
sw, //sw
|
||||
sh, //sh
|
||||
dx, //dx
|
||||
dy, // dy
|
||||
dw, //dw
|
||||
dh //dh
|
||||
);
|
||||
this.numDraws++;
|
||||
|
||||
}
|
38
src/Forms.js
38
src/Forms.js
@ -1,38 +0,0 @@
|
||||
html2canvas.prototype.renderFormValue = function(el,bounds,stack){
|
||||
|
||||
var valueWrap = document.createElement('valuewrap'),
|
||||
_ = this;
|
||||
|
||||
this.each(['lineHeight','textAlign','fontFamily','color','fontSize','paddingLeft','paddingTop','width','height','border','borderLeftWidth','borderTopWidth'],function(i,style){
|
||||
valueWrap.style[style] = _.getCSS(el,style);
|
||||
});
|
||||
|
||||
valueWrap.style.borderColor = "black";
|
||||
valueWrap.style.borderStyle = "solid";
|
||||
valueWrap.style.display = "block";
|
||||
valueWrap.style.position = "absolute";
|
||||
if (/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName == "SELECT"){
|
||||
valueWrap.style.lineHeight = _.getCSS(el,"height");
|
||||
}
|
||||
|
||||
|
||||
valueWrap.style.top = bounds.top+"px";
|
||||
valueWrap.style.left = bounds.left+"px";
|
||||
if (el.nodeName == "SELECT"){
|
||||
// TODO increase accuracy of text position
|
||||
var textValue = el.options[el.selectedIndex].text;
|
||||
} else{
|
||||
var textValue = el.value;
|
||||
|
||||
}
|
||||
var textNode = document.createTextNode(textValue);
|
||||
|
||||
valueWrap.appendChild(textNode);
|
||||
$('body').append(valueWrap);
|
||||
|
||||
this.newText(el,textNode,stack);
|
||||
|
||||
$(valueWrap).remove();
|
||||
|
||||
|
||||
}
|
137
src/Images.js
137
src/Images.js
@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Function to find all images from <img> and background-image
|
||||
*/
|
||||
html2canvas.prototype.getImages = function(el) {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (!this.ignoreRe.test(el.nodeName)){
|
||||
// TODO remove jQuery dependancy
|
||||
this.each($(el).contents(),function(i,element){
|
||||
var ignRe = new RegExp("("+this.ignoreElements+")");
|
||||
if (!ignRe.test(element.nodeName)){
|
||||
self.getImages(element);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (el.nodeType==1 || typeof el.nodeType == "undefined"){
|
||||
var background_image = this.getCSS(el,'background-image');
|
||||
|
||||
if (background_image && background_image != "1" && background_image != "none" && background_image.substring(0,7)!="-webkit" && background_image.substring(0,3)!="-o-" && background_image.substring(0,4)!="-moz"){
|
||||
// TODO add multi image background support
|
||||
var src = this.backgroundImageUrl(background_image.split(",")[0]);
|
||||
this.preloadImage(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Load image from storage
|
||||
*/
|
||||
|
||||
html2canvas.prototype.loadImage = function(src){
|
||||
|
||||
var imgIndex = this.getIndex(this.images,src);
|
||||
if (imgIndex!=-1){
|
||||
return this.images[imgIndex+1];
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
html2canvas.prototype.preloadImage = function(src){
|
||||
|
||||
|
||||
if (this.getIndex(this.images,src)==-1){
|
||||
if(src.substr(0, 5) == 'data:'){
|
||||
//Base64 src
|
||||
this.images.push(src);
|
||||
var img = new Image();
|
||||
img.src = src;
|
||||
this.images.push(img);
|
||||
this.imagesLoaded++;
|
||||
this.start();
|
||||
}else if (this.isSameOrigin(src)){
|
||||
this.images.push(src);
|
||||
// console.log('a'+src);
|
||||
var img = new Image();
|
||||
// TODO remove jQuery dependancy
|
||||
var _ = this;
|
||||
$(img).load(function(){
|
||||
_.imagesLoaded++;
|
||||
_.start();
|
||||
|
||||
});
|
||||
img.onerror = function(){
|
||||
_.images.splice(_.images.indexOf(img.src),2);
|
||||
// _.imagesLoaded++;
|
||||
_.start();
|
||||
}
|
||||
img.src = src;
|
||||
this.images.push(img);
|
||||
}else if (this.opts.proxyUrl){
|
||||
// console.log('b'+src);
|
||||
this.images.push(src);
|
||||
var img = new Image();
|
||||
this.proxyGetImage(src,img);
|
||||
this.images.push(img);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
html2canvas.prototype.proxyGetImage = function(url,img){
|
||||
var _ = this;
|
||||
|
||||
var link = document.createElement("a");
|
||||
link.href = url;
|
||||
url = link.href; // work around for pages with base href="" set
|
||||
|
||||
|
||||
// todo remove jQuery dependency and enable xhr2 requests where available (no need for base64 / json)
|
||||
$.ajax({
|
||||
data:{
|
||||
xhr2:false,
|
||||
url:url
|
||||
},
|
||||
url: this.opts.proxyUrl,
|
||||
dataType: "jsonp",
|
||||
success: function(a){
|
||||
|
||||
if (a.substring(0,6)=="error:"){
|
||||
_.images.splice(_.images.indexOf(url),2);
|
||||
_.start();
|
||||
_.log('Proxy was unable to load '+url+' '+a);
|
||||
}else{
|
||||
// document.createElement(a);
|
||||
// console.log(img);
|
||||
img.onload = function(){
|
||||
// console.log('w'+img.width);
|
||||
_.imagesLoaded++;
|
||||
_.start();
|
||||
|
||||
}
|
||||
|
||||
img.src = a;
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
error: function(){
|
||||
|
||||
_.images.splice(_.images.indexOf(url),2);
|
||||
// _.imagesLoaded++;
|
||||
_.start();
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
/*
|
||||
* html2canvas v0.27 <http://html2canvas.hertzen.com>
|
||||
* Copyright (c) 2011 Niklas von Hertzen. All rights reserved.
|
||||
* http://www.twitter.com/niklasvh
|
||||
*
|
||||
* Released under MIT License
|
||||
*/
|
121
src/Lists.js
121
src/Lists.js
@ -1,121 +0,0 @@
|
||||
html2canvas.prototype.drawListItem = function(element,stack,elBounds){
|
||||
|
||||
|
||||
var position = this.getCSS(element,"list-style-position",false);
|
||||
|
||||
|
||||
var item = this.getListItem(element),
|
||||
x,
|
||||
y;
|
||||
|
||||
var type = this.getCSS(element,"list-style-type",false);
|
||||
|
||||
if (/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)){
|
||||
// TODO remove jQuery dependency
|
||||
var currentIndex = $(element).index()+1,
|
||||
text;
|
||||
|
||||
if (type == "decimal"){
|
||||
text = currentIndex;
|
||||
}else if (type == "decimal-leading-zero"){
|
||||
if (currentIndex.toString().length == 1){
|
||||
text = currentIndex = "0" + currentIndex.toString();
|
||||
}else{
|
||||
text = currentIndex.toString();
|
||||
}
|
||||
|
||||
}else if (type == "upper-roman"){
|
||||
text = this.getListRoman(currentIndex);
|
||||
}else if (type == "lower-roman"){
|
||||
text = this.getListRoman(currentIndex).toLowerCase();
|
||||
}else if (type == "lower-alpha"){
|
||||
text = this.getListAlpha(currentIndex).toLowerCase();
|
||||
}else if (type == "upper-alpha"){
|
||||
text = this.getListAlpha(currentIndex);
|
||||
}
|
||||
|
||||
text += ". ";
|
||||
var listBounds = this.getListPosition(element,text);
|
||||
|
||||
if (position == "inside"){
|
||||
this.setFont(stack.ctx,element,false);
|
||||
x = elBounds.left;
|
||||
}else{
|
||||
return; /* TODO really need to figure out some more accurate way to try and find the position.
|
||||
as defined in http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-position, it does not even have a specified "correct" position, so each browser
|
||||
may display it whatever way it feels like.
|
||||
"The position of the list-item marker adjacent to floats is undefined in CSS 2.1. CSS 2.1 does not specify the precise location of the marker box or its position in the painting order"
|
||||
*/
|
||||
this.setFont(stack.ctx,element,true);
|
||||
x = elBounds.left-10;
|
||||
}
|
||||
|
||||
y = listBounds.bottom;
|
||||
|
||||
|
||||
this.printText(text, x, y, stack.ctx);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
html2canvas.prototype.getListPosition = function(element,val){
|
||||
var boundElement = document.createElement("boundelement");
|
||||
boundElement.style.display = "inline";
|
||||
//boundElement.style.width = "1px";
|
||||
//boundElement.style.height = "1px";
|
||||
|
||||
var type = element.style.listStyleType;
|
||||
element.style.listStyleType = "none";
|
||||
|
||||
boundElement.appendChild(document.createTextNode(val));
|
||||
|
||||
|
||||
element.insertBefore(boundElement,element.firstChild);
|
||||
|
||||
|
||||
var bounds = this.getBounds(boundElement);
|
||||
element.removeChild(boundElement);
|
||||
element.style.listStyleType = type;
|
||||
return bounds;
|
||||
|
||||
|
||||
}
|
||||
|
||||
html2canvas.prototype.getListItem = function(element){
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
html2canvas.prototype.getListAlpha = function(number){
|
||||
var tmp = "";
|
||||
do{
|
||||
var modulus = number % 26;
|
||||
tmp = String.fromCharCode((modulus) + 64) + tmp;
|
||||
number = number / 26;
|
||||
}while((number*26) > 26);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
html2canvas.prototype.getListRoman = function(number){
|
||||
var romanArray = ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"],
|
||||
decimal = [1000,900,500,400,100,90,50,40,10,9,5,4,1],
|
||||
roman = "";
|
||||
|
||||
if (number <= 0 || number >= 4000) return;
|
||||
for (var v=0; v<romanArray.length; v++) {
|
||||
while (number >= decimal[v]) {
|
||||
number -= decimal[v];
|
||||
roman += romanArray[v];
|
||||
}
|
||||
}
|
||||
|
||||
return roman;
|
||||
|
||||
|
||||
}
|
348
src/Renderer.js
348
src/Renderer.js
@ -1,348 +0,0 @@
|
||||
html2canvas.prototype.Renderer = function(queue){
|
||||
|
||||
var _ = this;
|
||||
|
||||
this.log('Renderer initiated');
|
||||
|
||||
this.each(this.opts.renderOrder.split(" "),function(i,renderer){
|
||||
|
||||
switch(renderer){
|
||||
case "canvas":
|
||||
_.canvas = document.createElement('canvas');
|
||||
if (_.canvas.getContext){
|
||||
_.canvasRenderer(queue);
|
||||
_.log('Using canvas renderer');
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "flash":
|
||||
/*
|
||||
var script = document.createElement('script');
|
||||
script.type = "text/javascript";
|
||||
script.src = _.opts.flashCanvasPath;
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(script, s);
|
||||
|
||||
|
||||
if (typeof FlashCanvas != "undefined") {
|
||||
_.canvas = initCanvas(document.getElementById("testflash"));
|
||||
FlashCanvas.initElement(_.canvas);
|
||||
_.ctx = _.canvas.getContext("2d");
|
||||
// _.canvas = document.createElement('canvas');
|
||||
//
|
||||
_.log('Using Flashcanvas renderer');
|
||||
_.canvasRenderer(queue);
|
||||
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
break;
|
||||
case "html":
|
||||
// TODO add renderer
|
||||
_.log("Using HTML renderer");
|
||||
return false;
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
// this.log('No renderer chosen, rendering quit');
|
||||
return this;
|
||||
|
||||
// this.canvasRenderer(queue);
|
||||
|
||||
/*
|
||||
if (!this.canvas.getContext){
|
||||
|
||||
|
||||
}*/
|
||||
// TODO include Flashcanvas
|
||||
/*
|
||||
var script = document.createElement('script');
|
||||
script.type = "text/javascript";
|
||||
script.src = this.opts.flashCanvasPath;
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(script, s);
|
||||
|
||||
if (typeof FlashCanvas != "undefined") {
|
||||
|
||||
FlashCanvas.initElement(this.canvas);
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
} */
|
||||
|
||||
}
|
||||
|
||||
|
||||
html2canvas.prototype.throttler = function(queue){
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
html2canvas.prototype.canvasRenderContext = function(storageContext,ctx){
|
||||
|
||||
// set common settings for canvas
|
||||
ctx.textBaseline = "bottom";
|
||||
var _ = this;
|
||||
|
||||
|
||||
if (storageContext.clip){
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
// console.log(storageContext);
|
||||
ctx.rect(storageContext.clip.left,storageContext.clip.top,storageContext.clip.width,storageContext.clip.height);
|
||||
ctx.clip();
|
||||
|
||||
}
|
||||
|
||||
if (storageContext.ctx.storage){
|
||||
_.each(storageContext.ctx.storage,function(a,renderItem){
|
||||
|
||||
|
||||
switch(renderItem.type){
|
||||
case "variable":
|
||||
ctx[renderItem.name] = renderItem.arguments;
|
||||
break;
|
||||
case "function":
|
||||
if (renderItem.name=="fillRect"){
|
||||
|
||||
ctx.fillRect(
|
||||
renderItem.arguments[0],
|
||||
renderItem.arguments[1],
|
||||
renderItem.arguments[2],
|
||||
renderItem.arguments[3]
|
||||
);
|
||||
}else if(renderItem.name=="fillText"){
|
||||
// console.log(renderItem.arguments[0]);
|
||||
ctx.fillText(renderItem.arguments[0],renderItem.arguments[1],renderItem.arguments[2]);
|
||||
|
||||
}else if(renderItem.name=="drawImage"){
|
||||
// console.log(renderItem);
|
||||
// console.log(renderItem.arguments[0].width);
|
||||
if (renderItem.arguments[8] > 0 && renderItem.arguments[7]){
|
||||
ctx.drawImage(
|
||||
renderItem.arguments[0],
|
||||
renderItem.arguments[1],
|
||||
renderItem.arguments[2],
|
||||
renderItem.arguments[3],
|
||||
renderItem.arguments[4],
|
||||
renderItem.arguments[5],
|
||||
renderItem.arguments[6],
|
||||
renderItem.arguments[7],
|
||||
renderItem.arguments[8]
|
||||
);
|
||||
}
|
||||
}else{
|
||||
_.log(renderItem);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
if (storageContext.clip){
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
html2canvas.prototype.canvasRenderContextChildren = function(storageContext,parentctx){
|
||||
var ctx = storageContext.canvas.getContext('2d');
|
||||
|
||||
storageContext.canvasPosition = storageContext.canvasPosition || {};
|
||||
this.canvasRenderContext(storageContext,ctx);
|
||||
|
||||
|
||||
for (var s = 0; s<this.queue.length;){
|
||||
if (storageContext.parentStack && this.queue[s].canvas === storageContext.parentStack.canvas){
|
||||
var substorageContext = this.queue.splice(s,1)[0];
|
||||
|
||||
if (substorageContext.ctx.storage[5] && substorageContext.ctx.storage[5].arguments[0]=="Highlights"){
|
||||
console.log('ssssssadasda');
|
||||
}
|
||||
|
||||
this.canvasRenderContextChildren(substorageContext,ctx);
|
||||
// this.canvasRenderContext(substorageContext,ctx);
|
||||
// this.canvasRenderStorage(this.queue,ctx);
|
||||
|
||||
|
||||
}else{
|
||||
s++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (storageContext.ctx.storage[5] && storageContext.ctx.storage[5].arguments[0]=="Highlights"){
|
||||
$('body').append(parentctx.canvas);
|
||||
}
|
||||
//var parentctx = this.canvas.getContext("2d");
|
||||
|
||||
if (storageContext.canvas.width>0 && storageContext.canvas.height > 0){
|
||||
parentctx.drawImage(storageContext.canvas,(storageContext.canvasPosition.x || 0),(storageContext.canvasPosition.y || 0));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
html2canvas.prototype.canvasRenderStorage = function(queue,parentctx){
|
||||
|
||||
|
||||
|
||||
|
||||
for (var i = 0; i<queue.length;){
|
||||
var storageContext = queue.splice(0,1)[0];
|
||||
|
||||
|
||||
|
||||
// storageContext.removeOverflow = storageContext.removeOverflow || {};
|
||||
/*
|
||||
if (storageContext.canvas){
|
||||
this.canvasRenderContextChildren(storageContext,parentctx);
|
||||
|
||||
var ctx = storageContext.canvas.getContext('2d');
|
||||
|
||||
storageContext.canvasPosition = storageContext.canvasPosition || {};
|
||||
this.canvasRenderContext(storageContext,ctx);
|
||||
|
||||
for (var s = 0; s<this.queue.length;){
|
||||
if (this.queue[s].canvas === storageContext.canvas){
|
||||
var substorageContext = this.queue.splice(s,1)[0];
|
||||
substorageContext.canvasPosition = storageContext.canvasPosition || {};
|
||||
|
||||
this.canvasRenderContext(substorageContext,ctx);
|
||||
// this.canvasRenderStorage(this.queue,ctx);
|
||||
|
||||
}else{
|
||||
s++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//var parentctx = this.canvas.getContext("2d");
|
||||
|
||||
if (storageContext.canvas.width>0 && storageContext.canvas.height > 0){
|
||||
parentctx.drawImage(storageContext.canvas,(storageContext.canvasPosition.x || 0),(storageContext.canvasPosition.y || 0));
|
||||
}
|
||||
ctx = parentctx;
|
||||
|
||||
}else{
|
||||
*/
|
||||
storageContext.canvasPosition = storageContext.canvasPosition || {};
|
||||
this.canvasRenderContext(storageContext,parentctx);
|
||||
// }
|
||||
|
||||
|
||||
/*
|
||||
if (storageContext.newCanvas){
|
||||
var ctx = _.canvas.getContext("2d");
|
||||
ctx.drawImage(canvas,(storageContext.removeOverflow.left || 0),(storageContext.removeOverflow.top || 0));
|
||||
_.ctx = ctx;
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
html2canvas.prototype.canvasRenderer = function(queue){
|
||||
var _ = this;
|
||||
this.sortZ(this.zStack);
|
||||
queue = this.queue;
|
||||
//console.log(queue);
|
||||
|
||||
//queue = this.sortQueue(queue);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
this.canvas.width = Math.max($(document).width(),this.opts.canvasWidth);
|
||||
this.canvas.height = Math.max($(document).height(),this.opts.canvasHeight);
|
||||
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
|
||||
this.canvasRenderStorage(queue,this.ctx);
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* Sort elements based on z-index and position attributes
|
||||
*/
|
||||
|
||||
/*
|
||||
html2canvas.prototype.sortQueue = function(queue){
|
||||
if (!this.opts.reorderZ || !this.needReorder) return queue;
|
||||
|
||||
var longest = 0;
|
||||
this.each(queue,function(i,e){
|
||||
if (longest<e.zIndex.length){
|
||||
longest = e.zIndex.length;
|
||||
}
|
||||
});
|
||||
|
||||
var counter = 0;
|
||||
//console.log(((queue.length).toString().length)-(count.length).toString().length);
|
||||
this.each(queue,function(i,e){
|
||||
|
||||
var more = ((queue.length).toString().length)-((counter).toString().length);
|
||||
while(longest>e.zIndex.length){
|
||||
e.zIndex += "0";
|
||||
}
|
||||
e.zIndex = e.zIndex+counter;
|
||||
|
||||
while((longest+more+(counter).toString().length)>e.zIndex.length){
|
||||
e.zIndex += "0";
|
||||
}
|
||||
counter++;
|
||||
// console.log(e.zIndex);
|
||||
});
|
||||
|
||||
|
||||
|
||||
queue = queue.sort(function(a,b){
|
||||
|
||||
if (a.zIndex < b.zIndex) return -1;
|
||||
if (a.zIndex > b.zIndex) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
return queue;
|
||||
}
|
||||
*/
|
||||
|
||||
html2canvas.prototype.setContextVariable = function(ctx,variable,value){
|
||||
if (!ctx.storage){
|
||||
ctx[variable] = value;
|
||||
}else{
|
||||
ctx.storage.push(
|
||||
{
|
||||
type: "variable",
|
||||
name:variable,
|
||||
arguments:value
|
||||
});
|
||||
}
|
||||
|
||||
}
|
288
src/Text.js
288
src/Text.js
@ -1,288 +0,0 @@
|
||||
|
||||
html2canvas.prototype.newText = function(el,textNode,stack,form){
|
||||
var ctx = stack.ctx;
|
||||
var family = this.getCSS(el,"font-family");
|
||||
var size = this.getCSS(el,"font-size");
|
||||
var color = this.getCSS(el,"color");
|
||||
|
||||
|
||||
|
||||
var text_decoration = this.getCSS(el,"text-decoration");
|
||||
var text_align = this.getCSS(el,"text-align");
|
||||
|
||||
|
||||
var letter_spacing = this.getCSS(el,"letter-spacing");
|
||||
|
||||
// apply text-transform:ation to the text
|
||||
textNode.nodeValue = this.textTransform(textNode.nodeValue,this.getCSS(el,"text-transform"));
|
||||
var text = this.trim(textNode.nodeValue);
|
||||
|
||||
//text = $.trim(text);
|
||||
if (text.length>0){
|
||||
|
||||
|
||||
|
||||
if (text_decoration!="none"){
|
||||
var metrics = this.fontMetrics(family,size);
|
||||
|
||||
}
|
||||
|
||||
var renderList,
|
||||
renderWords = false;
|
||||
|
||||
|
||||
text_align = text_align.replace(["-webkit-auto"],["auto"])
|
||||
|
||||
|
||||
if (this.opts.letterRendering == false && /^(left|right|justify|auto)$/.test(text_align) && /^(normal|none)$/.test(letter_spacing)){
|
||||
// this.setContextVariable(ctx,"textAlign",text_align);
|
||||
renderWords = true;
|
||||
renderList = textNode.nodeValue.split(/(\b| )/);
|
||||
|
||||
}else{
|
||||
// this.setContextVariable(ctx,"textAlign","left");
|
||||
renderList = textNode.nodeValue.split("");
|
||||
}
|
||||
|
||||
this.setFont(ctx,el,false);
|
||||
|
||||
|
||||
/*
|
||||
if (stack.clip){
|
||||
ctx.rect (stack.clip.left, stack.clip.top, stack.clip.width, stack.clip.height);
|
||||
ctx.clip();
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
var oldTextNode = textNode;
|
||||
for(var c=0;c<renderList.length;c++){
|
||||
|
||||
|
||||
// IE 9 bug
|
||||
if (typeof oldTextNode.nodeValue != "string"){
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO only do the splitting for non-range prints
|
||||
var newTextNode = oldTextNode.splitText(renderList[c].length);
|
||||
|
||||
if (text_decoration!="none" || this.trim(oldTextNode.nodeValue).length != 0){
|
||||
|
||||
|
||||
|
||||
|
||||
if (this.support.rangeBounds){
|
||||
// getBoundingClientRect is supported for ranges
|
||||
if (document.createRange){
|
||||
var range = document.createRange();
|
||||
range.selectNode(oldTextNode);
|
||||
}else{
|
||||
// TODO add IE support
|
||||
var range = document.body.createTextRange();
|
||||
}
|
||||
if (range.getBoundingClientRect()){
|
||||
var bounds = range.getBoundingClientRect();
|
||||
}else{
|
||||
var bounds = {};
|
||||
}
|
||||
}else{
|
||||
// it isn't supported, so let's wrap it inside an element instead and the bounds there
|
||||
|
||||
var parent = oldTextNode.parentNode;
|
||||
var wrapElement = document.createElement('wrapper');
|
||||
var backupText = oldTextNode.cloneNode(true);
|
||||
wrapElement.appendChild(oldTextNode.cloneNode(true));
|
||||
parent.replaceChild(wrapElement,oldTextNode);
|
||||
|
||||
var bounds = this.getBounds(wrapElement);
|
||||
|
||||
parent.replaceChild(backupText,wrapElement);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// console.log(range);
|
||||
// console.log("'"+oldTextNode.nodeValue+"'"+bounds.left)
|
||||
this.printText(oldTextNode.nodeValue,bounds.left,bounds.bottom,ctx);
|
||||
|
||||
switch(text_decoration) {
|
||||
case "underline":
|
||||
// Draws a line at the baseline of the font
|
||||
// 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
|
||||
this.newRect(ctx,bounds.left,Math.round(bounds.top+metrics.baseline+metrics.lineWidth),bounds.width,1,color);
|
||||
break;
|
||||
case "overline":
|
||||
this.newRect(ctx,bounds.left,bounds.top,bounds.width,1,color);
|
||||
break;
|
||||
case "line-through":
|
||||
// TODO try and find exact position for line-through
|
||||
this.newRect(ctx,bounds.left,Math.ceil(bounds.top+metrics.middle+metrics.lineWidth),bounds.width,1,color);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
oldTextNode = newTextNode;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
html2canvas.prototype.setFont = function(ctx,el,align){
|
||||
|
||||
var family = this.getCSS(el,"font-family");
|
||||
var size = this.getCSS(el,"font-size");
|
||||
var color = this.getCSS(el,"color");
|
||||
|
||||
var bold = this.getCSS(el,"font-weight");
|
||||
var font_style = this.getCSS(el,"font-style");
|
||||
var font_variant = this.getCSS(el,"font-variant");
|
||||
|
||||
switch(bold){
|
||||
case 401:
|
||||
bold = "bold";
|
||||
break;
|
||||
case 400:
|
||||
bold = "normal";
|
||||
break;
|
||||
}
|
||||
|
||||
var font = font_variant+" "+bold+" "+font_style+" "+size+" "+family;
|
||||
|
||||
|
||||
this.setContextVariable(ctx,"fillStyle",color);
|
||||
this.setContextVariable(ctx,"font",font);
|
||||
if (align){
|
||||
this.setContextVariable(ctx,"textAlign","right");
|
||||
}else{
|
||||
this.setContextVariable(ctx,"textAlign","left");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Function to find baseline for font with a specicic size
|
||||
*/
|
||||
html2canvas.prototype.fontMetrics = function(font,fontSize){
|
||||
|
||||
|
||||
var findMetrics = this.fontData.indexOf(font+"-"+fontSize);
|
||||
|
||||
if (findMetrics>-1){
|
||||
return this.fontData[findMetrics+1];
|
||||
}
|
||||
|
||||
var container = document.createElement('div');
|
||||
document.getElementsByTagName('body')[0].appendChild(container);
|
||||
|
||||
// jquery to speed this up, TODO remove jquery dependancy
|
||||
$(container).css({
|
||||
visibility:'hidden',
|
||||
fontFamily:font,
|
||||
fontSize:fontSize,
|
||||
margin:0,
|
||||
padding:0
|
||||
});
|
||||
|
||||
|
||||
|
||||
var img = document.createElement('img');
|
||||
|
||||
// TODO add another image
|
||||
img.src = "http://html2canvas.hertzen.com/images/8.jpg";
|
||||
img.width = 1;
|
||||
img.height = 1;
|
||||
|
||||
$(img).css({
|
||||
margin:0,
|
||||
padding:0
|
||||
});
|
||||
var span = document.createElement('span');
|
||||
|
||||
$(span).css({
|
||||
fontFamily:font,
|
||||
fontSize:fontSize,
|
||||
margin:0,
|
||||
padding:0
|
||||
});
|
||||
|
||||
|
||||
span.appendChild(document.createTextNode('Hidden Text'));
|
||||
container.appendChild(span);
|
||||
container.appendChild(img);
|
||||
var baseline = (img.offsetTop-span.offsetTop)+1;
|
||||
|
||||
container.removeChild(span);
|
||||
container.appendChild(document.createTextNode('Hidden Text'));
|
||||
|
||||
$(container).css('line-height','normal');
|
||||
$(img).css("vertical-align","super");
|
||||
var middle = (img.offsetTop-container.offsetTop)+1;
|
||||
|
||||
var metricsObj = {
|
||||
baseline: baseline,
|
||||
lineWidth: 1,
|
||||
middle: middle
|
||||
};
|
||||
|
||||
|
||||
this.fontData.push(font+"-"+fontSize);
|
||||
this.fontData.push(metricsObj);
|
||||
|
||||
$(container).remove();
|
||||
|
||||
|
||||
|
||||
return metricsObj;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Function to apply text-transform attribute to text
|
||||
*/
|
||||
html2canvas.prototype.textTransform = function(text,transform){
|
||||
switch(transform){
|
||||
case "lowercase":
|
||||
return text.toLowerCase();
|
||||
break;
|
||||
|
||||
case "capitalize":
|
||||
return text.replace( /(^|\s|:|-|\(|\))([a-z])/g , function(m,p1,p2){
|
||||
return p1+p2.toUpperCase();
|
||||
} );
|
||||
break;
|
||||
|
||||
case "uppercase":
|
||||
return text.toUpperCase();
|
||||
break;
|
||||
default:
|
||||
return text;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*Function to trim whitespace from text
|
||||
*/
|
||||
html2canvas.prototype.trim = function(text) {
|
||||
return text.replace(/^\s*/, "").replace(/\s*$/, "");
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
|
||||
|
||||
html2canvas.prototype.parseElement = function(element,stack){
|
||||
var _ = this;
|
||||
this.each(element.children,function(index,el){
|
||||
_.parsing(el,stack);
|
||||
});
|
||||
|
||||
this.log('Render queue stored');
|
||||
this.opts.storageReady(this);
|
||||
this.finish();
|
||||
}
|
||||
|
||||
|
||||
|
||||
html2canvas.prototype.parsing = function(el,stack){
|
||||
|
||||
if (this.getCSS(el,'display') != "none" && this.getCSS(el,'visibility')!="hidden"){
|
||||
|
||||
var _ = this;
|
||||
|
||||
stack = this.newElement(el,stack) || stack;
|
||||
|
||||
var ctx = stack.ctx;
|
||||
|
||||
if (!this.ignoreRe.test(el.nodeName)){
|
||||
// TODO remove jQuery dependancy
|
||||
this.each(this.contentsInZ(el),function(cid,node){
|
||||
|
||||
if (node.nodeType==1){
|
||||
// element
|
||||
_.parsing(node,stack);
|
||||
}else if (node.nodeType==3){
|
||||
_.newText(el,node,stack);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
285
src/Util.js
285
src/Util.js
@ -1,285 +0,0 @@
|
||||
|
||||
// Simple logger
|
||||
html2canvas.prototype.log = function(a){
|
||||
if (this.opts.logging){
|
||||
|
||||
this.opts.logger(a);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
html2canvas.prototype.withinBounds = function(src,dst){
|
||||
if (!src) return true;
|
||||
// return true;
|
||||
return (
|
||||
(src.left <= dst.left || dst.left+dst.width < src.left) &&
|
||||
(src.top <= dst.top || dst.top+dst.height < src.top)
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
html2canvas.prototype.clipBounds = function(src,dst){
|
||||
|
||||
var x = Math.max(src.left,dst.left);
|
||||
var y = Math.max(src.top,dst.top);
|
||||
|
||||
var x2 = Math.min((src.left+src.width),(dst.left+dst.width));
|
||||
var y2 = Math.min((src.top+src.height),(dst.top+dst.height));
|
||||
|
||||
return {
|
||||
left:x,
|
||||
top:y,
|
||||
width:x2-x,
|
||||
height:y2-y
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function to provide bounds for element
|
||||
* @return {Bounds} object with position and dimension information
|
||||
*/
|
||||
html2canvas.prototype.getBounds = function(el){
|
||||
|
||||
window.scroll(0,0);
|
||||
|
||||
if (el.getBoundingClientRect){
|
||||
var clientRect = el.getBoundingClientRect();
|
||||
var bounds = {};
|
||||
// need to create new object, as clientrect bounds can't be modified, thanks pufuwozu
|
||||
// TODO add scroll position to bounds, so no scrolling of window necessary
|
||||
bounds.top = clientRect.top;
|
||||
bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
|
||||
bounds.left = clientRect.left;
|
||||
bounds.width = clientRect.width;
|
||||
bounds.height = clientRect.height;
|
||||
|
||||
|
||||
|
||||
return bounds;
|
||||
}else{
|
||||
|
||||
// TODO remove jQuery dependancy
|
||||
var p = $(el).offset();
|
||||
|
||||
return {
|
||||
left: p.left + this.getCSS(el,"border-left-width",true),
|
||||
top: p.top + this.getCSS(el,"border-top-width",true),
|
||||
width:$(el).innerWidth(),
|
||||
height:$(el).innerHeight()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Function for looping through array
|
||||
*/
|
||||
html2canvas.prototype.each = function(arrayLoop,callbackFunc){
|
||||
callbackFunc = callbackFunc || function(){};
|
||||
for (var i=0;i<arrayLoop.length;i++){
|
||||
if (callbackFunc(i,arrayLoop[i]) === false) return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Function to get childNodes of an element in the order they should be rendered (based on z-index)
|
||||
* reference http://www.w3.org/TR/CSS21/zindex.html
|
||||
*/
|
||||
|
||||
html2canvas.prototype.contentsInZ = function(el){
|
||||
|
||||
// TODO remove jQuery dependency
|
||||
|
||||
var contents = $(el).contents();
|
||||
|
||||
return contents;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Function for fetching the element attribute
|
||||
*/
|
||||
html2canvas.prototype.getAttr = function(el,attribute){
|
||||
return el.getAttribute(attribute);
|
||||
//return $(el).attr(attribute);
|
||||
}
|
||||
|
||||
/*
|
||||
* Function to extend object
|
||||
*/
|
||||
html2canvas.prototype.extendObj = function(options,defaults){
|
||||
for (var key in options){
|
||||
defaults[key] = options[key];
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
|
||||
/*
|
||||
*todo remove this function
|
||||
html2canvas.prototype.leadingZero = function(num,size){
|
||||
|
||||
var s = "000000000" + num;
|
||||
return s.substr(s.length-size);
|
||||
}
|
||||
*/
|
||||
|
||||
html2canvas.prototype.zContext = function(zindex){
|
||||
return {
|
||||
zindex: zindex,
|
||||
children: []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
html2canvas.prototype.setZ = function(zindex,position,parentZ,parentNode){
|
||||
// TODO fix static elements overlapping relative/absolute elements under same stack, if they are defined after them
|
||||
if (!parentZ){
|
||||
this.zStack = new this.zContext(0);
|
||||
return this.zStack;
|
||||
}
|
||||
|
||||
if (zindex!="auto"){
|
||||
this.needReorder = true;
|
||||
var newContext = new this.zContext(zindex);
|
||||
parentZ.children.push(newContext);
|
||||
|
||||
return newContext;
|
||||
|
||||
}else {
|
||||
return parentZ;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
html2canvas.prototype.sortZ = function(zStack){
|
||||
var subStacks = [];
|
||||
var stackValues = [];
|
||||
var _ = this;
|
||||
|
||||
this.each(zStack.children, function(i,stackChild){
|
||||
if (stackChild.children && stackChild.children.length > 0){
|
||||
subStacks.push(stackChild);
|
||||
stackValues.push(stackChild.zindex);
|
||||
}else{
|
||||
|
||||
|
||||
_.queue.push(stackChild);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
stackValues.sort(function(a,b){
|
||||
return a - b
|
||||
});
|
||||
|
||||
this.each(stackValues, function(i,zValue){
|
||||
for (var s = 0;s<=subStacks.length;s++){
|
||||
if (subStacks[s].zindex == zValue){
|
||||
var stackChild = subStacks.splice(s,1);
|
||||
_.sortZ(stackChild[0]);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
*todo remove this function
|
||||
|
||||
html2canvas.prototype.formatZ = function(zindex,position,parentZ,parentNode){
|
||||
|
||||
if (!parentZ){
|
||||
parentZ = "0";
|
||||
}
|
||||
|
||||
|
||||
if (position!="static" && parentZ.charAt(0)=="0"){
|
||||
this.needReorder = true;
|
||||
parentZ = "1"+parentZ.slice(1);
|
||||
}
|
||||
|
||||
if (zindex=="auto"){
|
||||
var parentPosition = this.getCSS(parentNode,"position");
|
||||
if (parentPosition!="static" && typeof parentPosition != "undefined"){
|
||||
zindex = 0;
|
||||
}
|
||||
else{
|
||||
return parentZ;
|
||||
}
|
||||
}
|
||||
|
||||
var b = this.leadingZero(this.numDraws,9);
|
||||
|
||||
|
||||
var s = this.leadingZero(zindex+1,9);
|
||||
|
||||
// var s = "000000000" + num;
|
||||
return parentZ+""+""+s+""+b;
|
||||
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Get element childNodes
|
||||
*/
|
||||
|
||||
html2canvas.prototype.getContents = function(el){
|
||||
return (el.nodeName == "iframe" ) ?
|
||||
el.contentDocument || el.contentWindow.document :
|
||||
el.childNodes;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Function for fetching the css attribute
|
||||
* TODO remove jQuery dependancy
|
||||
*/
|
||||
html2canvas.prototype.getCSS = function(el,attribute,intOnly){
|
||||
if (intOnly){
|
||||
return parseInt($(el).css(attribute),10);
|
||||
}else{
|
||||
return $(el).css(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
html2canvas.prototype.getIndex = function(array,src){
|
||||
|
||||
if (array.indexOf){
|
||||
return array.indexOf(src);
|
||||
}else{
|
||||
for(var i = 0; i < array.length; i++){
|
||||
if(this[i] == src) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
html2canvas.prototype.isSameOrigin = function(url){
|
||||
var link = document.createElement("a");
|
||||
link.href = url;
|
||||
|
||||
return ((link.protocol + link.host) == this.pageOrigin);
|
||||
}
|
93
src/__tests__/index.ts
Normal file
93
src/__tests__/index.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import html2canvas from '../index';
|
||||
|
||||
import {CanvasRenderer} from '../render/canvas/canvas-renderer';
|
||||
import {DocumentCloner} from '../dom/document-cloner';
|
||||
import {COLORS} from '../css/types/color';
|
||||
|
||||
jest.mock('../core/logger');
|
||||
jest.mock('../css/layout/bounds');
|
||||
jest.mock('../dom/document-cloner');
|
||||
jest.mock('../dom/node-parser', () => {
|
||||
return {
|
||||
isBodyElement: () => false,
|
||||
isHTMLElement: () => false,
|
||||
parseTree: jest.fn().mockImplementation(() => {
|
||||
return {styles: {}};
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../render/stacking-context');
|
||||
jest.mock('../render/canvas/canvas-renderer');
|
||||
|
||||
describe('html2canvas', () => {
|
||||
const element = {
|
||||
ownerDocument: {
|
||||
defaultView: {
|
||||
pageXOffset: 12,
|
||||
pageYOffset: 34
|
||||
}
|
||||
}
|
||||
} as HTMLElement;
|
||||
|
||||
it('should render with an element', async () => {
|
||||
DocumentCloner.destroy = jest.fn().mockReturnValue(true);
|
||||
await html2canvas(element);
|
||||
expect(CanvasRenderer).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
cache: expect.any(Object),
|
||||
logger: expect.any(Object),
|
||||
windowBounds: expect.objectContaining({left: 12, top: 34})
|
||||
}),
|
||||
expect.objectContaining({
|
||||
backgroundColor: 0xffffffff,
|
||||
scale: 1,
|
||||
height: 50,
|
||||
width: 200,
|
||||
x: 0,
|
||||
y: 0,
|
||||
canvas: undefined
|
||||
})
|
||||
);
|
||||
expect(DocumentCloner.destroy).toBeCalled();
|
||||
});
|
||||
|
||||
it('should have transparent background with backgroundColor: null', async () => {
|
||||
await html2canvas(element, {backgroundColor: null});
|
||||
expect(CanvasRenderer).toHaveBeenLastCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
backgroundColor: COLORS.TRANSPARENT
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should use existing canvas when given as option', async () => {
|
||||
const canvas = {} as HTMLCanvasElement;
|
||||
await html2canvas(element, {canvas});
|
||||
expect(CanvasRenderer).toHaveBeenLastCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
canvas
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not remove cloned window when removeContainer: false', async () => {
|
||||
DocumentCloner.destroy = jest.fn();
|
||||
await html2canvas(element, {removeContainer: false});
|
||||
expect(CanvasRenderer).toHaveBeenLastCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
backgroundColor: 0xffffffff,
|
||||
scale: 1,
|
||||
height: 50,
|
||||
width: 200,
|
||||
x: 0,
|
||||
y: 0,
|
||||
canvas: undefined
|
||||
})
|
||||
);
|
||||
expect(DocumentCloner.destroy).not.toBeCalled();
|
||||
});
|
||||
});
|
1
src/core/__mocks__/cache-storage.ts
Normal file
1
src/core/__mocks__/cache-storage.ts
Normal file
@ -0,0 +1 @@
|
||||
export class CacheStorage {}
|
19
src/core/__mocks__/context.ts
Normal file
19
src/core/__mocks__/context.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {logger, Logger} from './logger';
|
||||
|
||||
export class Context {
|
||||
readonly logger: Logger = logger;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
readonly _cache: {[key: string]: Promise<any>} = {};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
readonly cache: any;
|
||||
|
||||
constructor() {
|
||||
this.cache = {
|
||||
addImage: jest.fn().mockImplementation((src: string): Promise<void> => {
|
||||
const result = Promise.resolve();
|
||||
this._cache[src] = result;
|
||||
return result;
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
8
src/core/__mocks__/features.ts
Normal file
8
src/core/__mocks__/features.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export const FEATURES = {
|
||||
SUPPORT_RANGE_BOUNDS: true,
|
||||
SUPPORT_SVG_DRAWING: true,
|
||||
SUPPORT_FOREIGNOBJECT_DRAWING: true,
|
||||
SUPPORT_CORS_IMAGES: true,
|
||||
SUPPORT_RESPONSE_TYPE: true,
|
||||
SUPPORT_CORS_XHR: true
|
||||
};
|
22
src/core/__mocks__/logger.ts
Normal file
22
src/core/__mocks__/logger.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export class Logger {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
debug(): void {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
static create(): void {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
static destroy(): void {}
|
||||
|
||||
static getInstance(): Logger {
|
||||
return logger;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
info(): void {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
error(): void {}
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
273
src/core/__tests__/cache-storage.ts
Normal file
273
src/core/__tests__/cache-storage.ts
Normal file
@ -0,0 +1,273 @@
|
||||
import {deepStrictEqual, fail} from 'assert';
|
||||
import {FEATURES} from '../features';
|
||||
import {CacheStorage} from '../cache-storage';
|
||||
import {Context} from '../context';
|
||||
import {Bounds} from '../../css/layout/bounds';
|
||||
|
||||
const proxy = 'http://example.com/proxy';
|
||||
|
||||
const createMockContext = (origin: string, opts = {}) => {
|
||||
const context = {
|
||||
location: {
|
||||
href: origin
|
||||
},
|
||||
document: {
|
||||
createElement(_name: string) {
|
||||
let _href = '';
|
||||
return {
|
||||
set href(value: string) {
|
||||
_href = value;
|
||||
},
|
||||
get href() {
|
||||
return _href;
|
||||
},
|
||||
get protocol() {
|
||||
return new URL(_href).protocol;
|
||||
},
|
||||
get hostname() {
|
||||
return new URL(_href).hostname;
|
||||
},
|
||||
get port() {
|
||||
return new URL(_href).port;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CacheStorage.setContext(context as Window);
|
||||
|
||||
return new Context(
|
||||
{
|
||||
logging: false,
|
||||
imageTimeout: 0,
|
||||
useCORS: false,
|
||||
allowTaint: false,
|
||||
proxy,
|
||||
...opts
|
||||
},
|
||||
new Bounds(0, 0, 0, 0)
|
||||
);
|
||||
};
|
||||
|
||||
const images: ImageMock[] = [];
|
||||
const xhr: XMLHttpRequestMock[] = [];
|
||||
const sleep = async (timeout: number) => await new Promise((resolve) => setTimeout(resolve, timeout));
|
||||
|
||||
class ImageMock {
|
||||
src?: string;
|
||||
crossOrigin?: string;
|
||||
onload?: () => void;
|
||||
constructor() {
|
||||
images.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
class XMLHttpRequestMock {
|
||||
sent: boolean;
|
||||
status: number;
|
||||
timeout: number;
|
||||
method?: string;
|
||||
url?: string;
|
||||
response?: string;
|
||||
onload?: () => void;
|
||||
ontimeout?: () => void;
|
||||
constructor() {
|
||||
this.sent = false;
|
||||
this.status = 500;
|
||||
this.timeout = 5000;
|
||||
xhr.push(this);
|
||||
}
|
||||
|
||||
async load(status: number, response: string) {
|
||||
this.response = response;
|
||||
this.status = status;
|
||||
if (this.onload) {
|
||||
this.onload();
|
||||
}
|
||||
await sleep(0);
|
||||
}
|
||||
|
||||
open(method: string, url: string) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
}
|
||||
send() {
|
||||
this.sent = true;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(global, 'Image', {value: ImageMock, writable: true});
|
||||
Object.defineProperty(global, 'XMLHttpRequest', {
|
||||
value: XMLHttpRequestMock,
|
||||
writable: true
|
||||
});
|
||||
|
||||
const setFeatures = (opts: {[key: string]: boolean} = {}) => {
|
||||
const defaults: {[key: string]: boolean} = {
|
||||
SUPPORT_SVG_DRAWING: true,
|
||||
SUPPORT_CORS_IMAGES: true,
|
||||
SUPPORT_CORS_XHR: true,
|
||||
SUPPORT_RESPONSE_TYPE: false
|
||||
};
|
||||
|
||||
Object.keys(defaults).forEach((key) => {
|
||||
Object.defineProperty(FEATURES, key, {
|
||||
value: typeof opts[key] === 'boolean' ? opts[key] : defaults[key],
|
||||
writable: true
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('cache-storage', () => {
|
||||
beforeEach(() => setFeatures());
|
||||
afterEach(() => {
|
||||
xhr.splice(0, xhr.length);
|
||||
images.splice(0, images.length);
|
||||
});
|
||||
it('addImage adds images to cache', async () => {
|
||||
const {cache} = createMockContext('http://example.com', {proxy: null});
|
||||
await cache.addImage('http://example.com/test.jpg');
|
||||
await cache.addImage('http://example.com/test2.jpg');
|
||||
|
||||
deepStrictEqual(images.length, 2);
|
||||
deepStrictEqual(images[0].src, 'http://example.com/test.jpg');
|
||||
deepStrictEqual(images[1].src, 'http://example.com/test2.jpg');
|
||||
});
|
||||
|
||||
it('addImage should not add duplicate entries', async () => {
|
||||
const {cache} = createMockContext('http://example.com');
|
||||
await cache.addImage('http://example.com/test.jpg');
|
||||
await cache.addImage('http://example.com/test.jpg');
|
||||
|
||||
deepStrictEqual(images.length, 1);
|
||||
deepStrictEqual(images[0].src, 'http://example.com/test.jpg');
|
||||
});
|
||||
|
||||
describe('svg', () => {
|
||||
it('should add svg images correctly', async () => {
|
||||
const {cache} = createMockContext('http://example.com');
|
||||
await cache.addImage('http://example.com/test.svg');
|
||||
await cache.addImage('http://example.com/test2.svg');
|
||||
|
||||
deepStrictEqual(images.length, 2);
|
||||
deepStrictEqual(images[0].src, 'http://example.com/test.svg');
|
||||
deepStrictEqual(images[1].src, 'http://example.com/test2.svg');
|
||||
});
|
||||
|
||||
it('should omit svg images if not supported', async () => {
|
||||
setFeatures({SUPPORT_SVG_DRAWING: false});
|
||||
const {cache} = createMockContext('http://example.com');
|
||||
await cache.addImage('http://example.com/test.svg');
|
||||
await cache.addImage('http://example.com/test2.svg');
|
||||
|
||||
deepStrictEqual(images.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cross-origin', () => {
|
||||
it('addImage should not add images it cannot load/render', async () => {
|
||||
const {cache} = createMockContext('http://example.com', {
|
||||
proxy: undefined
|
||||
});
|
||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||
deepStrictEqual(images.length, 0);
|
||||
});
|
||||
|
||||
it('addImage should add images if tainting enabled', async () => {
|
||||
const {cache} = createMockContext('http://example.com', {
|
||||
allowTaint: true,
|
||||
proxy: undefined
|
||||
});
|
||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||
deepStrictEqual(images.length, 1);
|
||||
deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
|
||||
deepStrictEqual(images[0].crossOrigin, undefined);
|
||||
});
|
||||
|
||||
it('addImage should add images if cors enabled', async () => {
|
||||
const {cache} = createMockContext('http://example.com', {useCORS: true});
|
||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||
deepStrictEqual(images.length, 1);
|
||||
deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
|
||||
deepStrictEqual(images[0].crossOrigin, 'anonymous');
|
||||
});
|
||||
|
||||
it('addImage should not add images if cors enabled but not supported', async () => {
|
||||
setFeatures({SUPPORT_CORS_IMAGES: false});
|
||||
|
||||
const {cache} = createMockContext('http://example.com', {
|
||||
useCORS: true,
|
||||
proxy: undefined
|
||||
});
|
||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||
deepStrictEqual(images.length, 0);
|
||||
});
|
||||
|
||||
it('addImage should not add images to proxy if cors enabled', async () => {
|
||||
const {cache} = createMockContext('http://example.com', {useCORS: true});
|
||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||
deepStrictEqual(images.length, 1);
|
||||
deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
|
||||
deepStrictEqual(images[0].crossOrigin, 'anonymous');
|
||||
});
|
||||
|
||||
it('addImage should use proxy ', async () => {
|
||||
const {cache} = createMockContext('http://example.com');
|
||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||
deepStrictEqual(xhr.length, 1);
|
||||
deepStrictEqual(
|
||||
xhr[0].url,
|
||||
`${proxy}?url=${encodeURIComponent('http://html2canvas.hertzen.com/test.jpg')}&responseType=text`
|
||||
);
|
||||
await xhr[0].load(200, '<data response>');
|
||||
|
||||
deepStrictEqual(images.length, 1);
|
||||
deepStrictEqual(images[0].src, '<data response>');
|
||||
});
|
||||
|
||||
it('proxy should respect imageTimeout', async () => {
|
||||
const {cache} = createMockContext('http://example.com', {
|
||||
imageTimeout: 10
|
||||
});
|
||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||
|
||||
deepStrictEqual(xhr.length, 1);
|
||||
deepStrictEqual(
|
||||
xhr[0].url,
|
||||
`${proxy}?url=${encodeURIComponent('http://html2canvas.hertzen.com/test.jpg')}&responseType=text`
|
||||
);
|
||||
deepStrictEqual(xhr[0].timeout, 10);
|
||||
if (xhr[0].ontimeout) {
|
||||
xhr[0].ontimeout();
|
||||
}
|
||||
try {
|
||||
await cache.match('http://html2canvas.hertzen.com/test.jpg');
|
||||
fail('Expected result to timeout');
|
||||
} catch (e) {}
|
||||
});
|
||||
});
|
||||
|
||||
it('match should return cache entry', async () => {
|
||||
const {cache} = createMockContext('http://example.com');
|
||||
await cache.addImage('http://example.com/test.jpg');
|
||||
|
||||
if (images[0].onload) {
|
||||
images[0].onload();
|
||||
}
|
||||
|
||||
const response = await cache.match('http://example.com/test.jpg');
|
||||
|
||||
deepStrictEqual(response.src, 'http://example.com/test.jpg');
|
||||
});
|
||||
|
||||
it('image should respect imageTimeout', async () => {
|
||||
const {cache} = createMockContext('http://example.com', {imageTimeout: 10});
|
||||
cache.addImage('http://example.com/test.jpg');
|
||||
|
||||
try {
|
||||
await cache.match('http://example.com/test.jpg');
|
||||
fail('Expected result to timeout');
|
||||
} catch (e) {}
|
||||
});
|
||||
});
|
30
src/core/__tests__/logger.ts
Normal file
30
src/core/__tests__/logger.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Logger} from '../logger';
|
||||
|
||||
describe('logger', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let infoSpy: any;
|
||||
|
||||
beforeEach(() => {
|
||||
infoSpy = jest.spyOn(console, 'info').mockImplementation(() => {
|
||||
// do nothing
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
infoSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should call console.info when logger enabled', () => {
|
||||
const id = Math.random().toString();
|
||||
const logger = new Logger({id, enabled: true});
|
||||
logger.info('testing');
|
||||
expect(infoSpy).toHaveBeenLastCalledWith(id, expect.stringMatching(/\d+ms/), 'testing');
|
||||
});
|
||||
|
||||
it("shouldn't call console.info when logger disabled", () => {
|
||||
const id = Math.random().toString();
|
||||
const logger = new Logger({id, enabled: false});
|
||||
logger.info('testing');
|
||||
expect(infoSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
1
src/core/bitwise.ts
Normal file
1
src/core/bitwise.ts
Normal file
@ -0,0 +1 @@
|
||||
export const contains = (bit: number, value: number): boolean => (bit & value) !== 0;
|
177
src/core/cache-storage.ts
Normal file
177
src/core/cache-storage.ts
Normal file
@ -0,0 +1,177 @@
|
||||
import {FEATURES} from './features';
|
||||
import {Context} from './context';
|
||||
|
||||
export class CacheStorage {
|
||||
private static _link?: HTMLAnchorElement;
|
||||
private static _origin = 'about:blank';
|
||||
|
||||
static getOrigin(url: string): string {
|
||||
const link = CacheStorage._link;
|
||||
if (!link) {
|
||||
return 'about:blank';
|
||||
}
|
||||
|
||||
link.href = url;
|
||||
link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
|
||||
return link.protocol + link.hostname + link.port;
|
||||
}
|
||||
|
||||
static isSameOrigin(src: string): boolean {
|
||||
return CacheStorage.getOrigin(src) === CacheStorage._origin;
|
||||
}
|
||||
|
||||
static setContext(window: Window): void {
|
||||
CacheStorage._link = window.document.createElement('a');
|
||||
CacheStorage._origin = CacheStorage.getOrigin(window.location.href);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ResourceOptions {
|
||||
imageTimeout: number;
|
||||
useCORS: boolean;
|
||||
allowTaint: boolean;
|
||||
proxy?: string;
|
||||
}
|
||||
|
||||
export class Cache {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private readonly _cache: {[key: string]: Promise<any>} = {};
|
||||
|
||||
constructor(private readonly context: Context, private readonly _options: ResourceOptions) {}
|
||||
|
||||
addImage(src: string): Promise<void> {
|
||||
const result = Promise.resolve();
|
||||
if (this.has(src)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (isBlobImage(src) || isRenderable(src)) {
|
||||
(this._cache[src] = this.loadImage(src)).catch(() => {
|
||||
// prevent unhandled rejection
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
match(src: string): Promise<any> {
|
||||
return this._cache[src];
|
||||
}
|
||||
|
||||
private async loadImage(key: string) {
|
||||
const isSameOrigin = CacheStorage.isSameOrigin(key);
|
||||
const useCORS =
|
||||
!isInlineImage(key) && this._options.useCORS === true && FEATURES.SUPPORT_CORS_IMAGES && !isSameOrigin;
|
||||
const useProxy =
|
||||
!isInlineImage(key) &&
|
||||
!isSameOrigin &&
|
||||
!isBlobImage(key) &&
|
||||
typeof this._options.proxy === 'string' &&
|
||||
FEATURES.SUPPORT_CORS_XHR &&
|
||||
!useCORS;
|
||||
if (
|
||||
!isSameOrigin &&
|
||||
this._options.allowTaint === false &&
|
||||
!isInlineImage(key) &&
|
||||
!isBlobImage(key) &&
|
||||
!useProxy &&
|
||||
!useCORS
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let src = key;
|
||||
if (useProxy) {
|
||||
src = await this.proxy(src);
|
||||
}
|
||||
|
||||
this.context.logger.debug(`Added image ${key.substring(0, 256)}`);
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
//ios safari 10.3 taints canvas with data urls unless crossOrigin is set to anonymous
|
||||
if (isInlineBase64Image(src) || useCORS) {
|
||||
img.crossOrigin = 'anonymous';
|
||||
}
|
||||
img.src = src;
|
||||
if (img.complete === true) {
|
||||
// Inline XML images may fail to parse, throwing an Error later on
|
||||
setTimeout(() => resolve(img), 500);
|
||||
}
|
||||
if (this._options.imageTimeout > 0) {
|
||||
setTimeout(
|
||||
() => reject(`Timed out (${this._options.imageTimeout}ms) loading image`),
|
||||
this._options.imageTimeout
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private has(key: string): boolean {
|
||||
return typeof this._cache[key] !== 'undefined';
|
||||
}
|
||||
|
||||
keys(): Promise<string[]> {
|
||||
return Promise.resolve(Object.keys(this._cache));
|
||||
}
|
||||
|
||||
private proxy(src: string): Promise<string> {
|
||||
const proxy = this._options.proxy;
|
||||
|
||||
if (!proxy) {
|
||||
throw new Error('No proxy defined');
|
||||
}
|
||||
|
||||
const key = src.substring(0, 256);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const responseType = FEATURES.SUPPORT_RESPONSE_TYPE ? 'blob' : 'text';
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200) {
|
||||
if (responseType === 'text') {
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => resolve(reader.result as string), false);
|
||||
reader.addEventListener('error', (e) => reject(e), false);
|
||||
reader.readAsDataURL(xhr.response);
|
||||
}
|
||||
} else {
|
||||
reject(`Failed to proxy resource ${key} with status code ${xhr.status}`);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = reject;
|
||||
const queryString = proxy.indexOf('?') > -1 ? '&' : '?';
|
||||
xhr.open('GET', `${proxy}${queryString}url=${encodeURIComponent(src)}&responseType=${responseType}`);
|
||||
|
||||
if (responseType !== 'text' && xhr instanceof XMLHttpRequest) {
|
||||
xhr.responseType = responseType;
|
||||
}
|
||||
|
||||
if (this._options.imageTimeout) {
|
||||
const timeout = this._options.imageTimeout;
|
||||
xhr.timeout = timeout;
|
||||
xhr.ontimeout = () => reject(`Timed out (${timeout}ms) proxying ${key}`);
|
||||
}
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const INLINE_SVG = /^data:image\/svg\+xml/i;
|
||||
const INLINE_BASE64 = /^data:image\/.*;base64,/i;
|
||||
const INLINE_IMG = /^data:image\/.*/i;
|
||||
|
||||
const isRenderable = (src: string): boolean => FEATURES.SUPPORT_SVG_DRAWING || !isSVG(src);
|
||||
const isInlineImage = (src: string): boolean => INLINE_IMG.test(src);
|
||||
const isInlineBase64Image = (src: string): boolean => INLINE_BASE64.test(src);
|
||||
const isBlobImage = (src: string): boolean => src.substr(0, 4) === 'blob';
|
||||
|
||||
const isSVG = (src: string): boolean => src.substr(-3).toLowerCase() === 'svg' || INLINE_SVG.test(src);
|
21
src/core/context.ts
Normal file
21
src/core/context.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {Logger} from './logger';
|
||||
import {Cache, ResourceOptions} from './cache-storage';
|
||||
import {Bounds} from '../css/layout/bounds';
|
||||
|
||||
export type ContextOptions = {
|
||||
logging: boolean;
|
||||
cache?: Cache;
|
||||
} & ResourceOptions;
|
||||
|
||||
export class Context {
|
||||
private readonly instanceName = `#${Context.instanceCount++}`;
|
||||
readonly logger: Logger;
|
||||
readonly cache: Cache;
|
||||
|
||||
private static instanceCount = 1;
|
||||
|
||||
constructor(options: ContextOptions, public windowBounds: Bounds) {
|
||||
this.logger = new Logger({id: this.instanceName, enabled: options.logging});
|
||||
this.cache = options.cache ?? new Cache(this, options);
|
||||
}
|
||||
}
|
29
src/core/debugger.ts
Normal file
29
src/core/debugger.ts
Normal file
@ -0,0 +1,29 @@
|
||||
const elementDebuggerAttribute = 'data-html2canvas-debug';
|
||||
export const enum DebuggerType {
|
||||
NONE,
|
||||
ALL,
|
||||
CLONE,
|
||||
PARSE,
|
||||
RENDER
|
||||
}
|
||||
|
||||
const getElementDebugType = (element: Element): DebuggerType => {
|
||||
const attribute = element.getAttribute(elementDebuggerAttribute);
|
||||
switch (attribute) {
|
||||
case 'all':
|
||||
return DebuggerType.ALL;
|
||||
case 'clone':
|
||||
return DebuggerType.CLONE;
|
||||
case 'parse':
|
||||
return DebuggerType.PARSE;
|
||||
case 'render':
|
||||
return DebuggerType.RENDER;
|
||||
default:
|
||||
return DebuggerType.NONE;
|
||||
}
|
||||
};
|
||||
|
||||
export const isDebugging = (element: Element, type: Omit<DebuggerType, DebuggerType.NONE>): boolean => {
|
||||
const elementType = getElementDebugType(element);
|
||||
return elementType === DebuggerType.ALL || type === elementType;
|
||||
};
|
222
src/core/features.ts
Normal file
222
src/core/features.ts
Normal file
@ -0,0 +1,222 @@
|
||||
import {fromCodePoint, toCodePoints} from 'css-line-break';
|
||||
|
||||
const testRangeBounds = (document: Document) => {
|
||||
const TEST_HEIGHT = 123;
|
||||
|
||||
if (document.createRange) {
|
||||
const range = document.createRange();
|
||||
if (range.getBoundingClientRect) {
|
||||
const testElement = document.createElement('boundtest');
|
||||
testElement.style.height = `${TEST_HEIGHT}px`;
|
||||
testElement.style.display = 'block';
|
||||
document.body.appendChild(testElement);
|
||||
|
||||
range.selectNode(testElement);
|
||||
const rangeBounds = range.getBoundingClientRect();
|
||||
const rangeHeight = Math.round(rangeBounds.height);
|
||||
document.body.removeChild(testElement);
|
||||
if (rangeHeight === TEST_HEIGHT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const testIOSLineBreak = (document: Document) => {
|
||||
const testElement = document.createElement('boundtest');
|
||||
testElement.style.width = '50px';
|
||||
testElement.style.display = 'block';
|
||||
testElement.style.fontSize = '12px';
|
||||
testElement.style.letterSpacing = '0px';
|
||||
testElement.style.wordSpacing = '0px';
|
||||
document.body.appendChild(testElement);
|
||||
const range = document.createRange();
|
||||
|
||||
testElement.innerHTML = typeof ''.repeat === 'function' ? '👨'.repeat(10) : '';
|
||||
|
||||
const node = testElement.firstChild as Text;
|
||||
|
||||
const textList = toCodePoints(node.data).map((i) => fromCodePoint(i));
|
||||
let offset = 0;
|
||||
let prev: DOMRect = {} as DOMRect;
|
||||
|
||||
// ios 13 does not handle range getBoundingClientRect line changes correctly #2177
|
||||
const supports = textList.every((text, i) => {
|
||||
range.setStart(node, offset);
|
||||
range.setEnd(node, offset + text.length);
|
||||
const rect = range.getBoundingClientRect();
|
||||
|
||||
offset += text.length;
|
||||
const boundAhead = rect.x > prev.x || rect.y > prev.y;
|
||||
|
||||
prev = rect;
|
||||
if (i === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return boundAhead;
|
||||
});
|
||||
|
||||
document.body.removeChild(testElement);
|
||||
return supports;
|
||||
};
|
||||
|
||||
const testCORS = (): boolean => typeof new Image().crossOrigin !== 'undefined';
|
||||
|
||||
const testResponseType = (): boolean => typeof new XMLHttpRequest().responseType === 'string';
|
||||
|
||||
const testSVG = (document: Document): boolean => {
|
||||
const img = new Image();
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
img.src = `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>`;
|
||||
|
||||
try {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
canvas.toDataURL();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const isGreenPixel = (data: Uint8ClampedArray): boolean =>
|
||||
data[0] === 0 && data[1] === 255 && data[2] === 0 && data[3] === 255;
|
||||
|
||||
const testForeignObject = (document: Document): Promise<boolean> => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const size = 100;
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
return Promise.reject(false);
|
||||
}
|
||||
ctx.fillStyle = 'rgb(0, 255, 0)';
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
|
||||
const img = new Image();
|
||||
const greenImageSrc = canvas.toDataURL();
|
||||
img.src = greenImageSrc;
|
||||
const svg = createForeignObjectSVG(size, size, 0, 0, img);
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
|
||||
return loadSerializedSVG(svg)
|
||||
.then((img: HTMLImageElement) => {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const data = ctx.getImageData(0, 0, size, size).data;
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
|
||||
const node = document.createElement('div');
|
||||
node.style.backgroundImage = `url(${greenImageSrc})`;
|
||||
node.style.height = `${size}px`;
|
||||
// Firefox 55 does not render inline <img /> tags
|
||||
return isGreenPixel(data)
|
||||
? loadSerializedSVG(createForeignObjectSVG(size, size, 0, 0, node))
|
||||
: Promise.reject(false);
|
||||
})
|
||||
.then((img: HTMLImageElement) => {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
// Edge does not render background-images
|
||||
return isGreenPixel(ctx.getImageData(0, 0, size, size).data);
|
||||
})
|
||||
.catch(() => false);
|
||||
};
|
||||
|
||||
export const createForeignObjectSVG = (
|
||||
width: number,
|
||||
height: number,
|
||||
x: number,
|
||||
y: number,
|
||||
node: Node
|
||||
): SVGForeignObjectElement => {
|
||||
const xmlns = 'http://www.w3.org/2000/svg';
|
||||
const svg = document.createElementNS(xmlns, 'svg');
|
||||
const foreignObject = document.createElementNS(xmlns, 'foreignObject');
|
||||
svg.setAttributeNS(null, 'width', width.toString());
|
||||
svg.setAttributeNS(null, 'height', height.toString());
|
||||
|
||||
foreignObject.setAttributeNS(null, 'width', '100%');
|
||||
foreignObject.setAttributeNS(null, 'height', '100%');
|
||||
foreignObject.setAttributeNS(null, 'x', x.toString());
|
||||
foreignObject.setAttributeNS(null, 'y', y.toString());
|
||||
foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true');
|
||||
svg.appendChild(foreignObject);
|
||||
|
||||
foreignObject.appendChild(node);
|
||||
|
||||
return svg;
|
||||
};
|
||||
|
||||
export const loadSerializedSVG = (svg: Node): Promise<HTMLImageElement> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
|
||||
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(new XMLSerializer().serializeToString(svg))}`;
|
||||
});
|
||||
};
|
||||
|
||||
export const FEATURES = {
|
||||
get SUPPORT_RANGE_BOUNDS(): boolean {
|
||||
'use strict';
|
||||
const value = testRangeBounds(document);
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_RANGE_BOUNDS', {value});
|
||||
return value;
|
||||
},
|
||||
get SUPPORT_WORD_BREAKING(): boolean {
|
||||
'use strict';
|
||||
const value = FEATURES.SUPPORT_RANGE_BOUNDS && testIOSLineBreak(document);
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_WORD_BREAKING', {value});
|
||||
return value;
|
||||
},
|
||||
get SUPPORT_SVG_DRAWING(): boolean {
|
||||
'use strict';
|
||||
const value = testSVG(document);
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_SVG_DRAWING', {value});
|
||||
return value;
|
||||
},
|
||||
get SUPPORT_FOREIGNOBJECT_DRAWING(): Promise<boolean> {
|
||||
'use strict';
|
||||
const value =
|
||||
typeof Array.from === 'function' && typeof window.fetch === 'function'
|
||||
? testForeignObject(document)
|
||||
: Promise.resolve(false);
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_FOREIGNOBJECT_DRAWING', {value});
|
||||
return value;
|
||||
},
|
||||
get SUPPORT_CORS_IMAGES(): boolean {
|
||||
'use strict';
|
||||
const value = testCORS();
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_CORS_IMAGES', {value});
|
||||
return value;
|
||||
},
|
||||
get SUPPORT_RESPONSE_TYPE(): boolean {
|
||||
'use strict';
|
||||
const value = testResponseType();
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_RESPONSE_TYPE', {value});
|
||||
return value;
|
||||
},
|
||||
get SUPPORT_CORS_XHR(): boolean {
|
||||
'use strict';
|
||||
const value = 'withCredentials' in new XMLHttpRequest();
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_CORS_XHR', {value});
|
||||
return value;
|
||||
},
|
||||
get SUPPORT_NATIVE_TEXT_SEGMENTATION(): boolean {
|
||||
'use strict';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const value = !!(typeof Intl !== 'undefined' && (Intl as any).Segmenter);
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_NATIVE_TEXT_SEGMENTATION', {value});
|
||||
return value;
|
||||
}
|
||||
};
|
72
src/core/logger.ts
Normal file
72
src/core/logger.ts
Normal file
@ -0,0 +1,72 @@
|
||||
export interface LoggerOptions {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
static instances: {[key: string]: Logger} = {};
|
||||
|
||||
private readonly id: string;
|
||||
private readonly enabled: boolean;
|
||||
private readonly start: number;
|
||||
|
||||
constructor({id, enabled}: LoggerOptions) {
|
||||
this.id = id;
|
||||
this.enabled = enabled;
|
||||
this.start = Date.now();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
debug(...args: unknown[]): void {
|
||||
if (this.enabled) {
|
||||
// eslint-disable-next-line no-console
|
||||
if (typeof window !== 'undefined' && window.console && typeof console.debug === 'function') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(this.id, `${this.getTime()}ms`, ...args);
|
||||
} else {
|
||||
this.info(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTime(): number {
|
||||
return Date.now() - this.start;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
info(...args: unknown[]): void {
|
||||
if (this.enabled) {
|
||||
// eslint-disable-next-line no-console
|
||||
if (typeof window !== 'undefined' && window.console && typeof console.info === 'function') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(this.id, `${this.getTime()}ms`, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
warn(...args: unknown[]): void {
|
||||
if (this.enabled) {
|
||||
// eslint-disable-next-line no-console
|
||||
if (typeof window !== 'undefined' && window.console && typeof console.warn === 'function') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(this.id, `${this.getTime()}ms`, ...args);
|
||||
} else {
|
||||
this.info(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
error(...args: unknown[]): void {
|
||||
if (this.enabled) {
|
||||
// eslint-disable-next-line no-console
|
||||
if (typeof window !== 'undefined' && window.console && typeof console.error === 'function') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(this.id, `${this.getTime()}ms`, ...args);
|
||||
} else {
|
||||
this.info(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
src/core/util.ts
Normal file
1
src/core/util.ts
Normal file
@ -0,0 +1 @@
|
||||
export const SMALL_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
49
src/css/IPropertyDescriptor.ts
Normal file
49
src/css/IPropertyDescriptor.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {CSSValue} from './syntax/parser';
|
||||
import {CSSTypes} from './types';
|
||||
import {Context} from '../core/context';
|
||||
|
||||
export const enum PropertyDescriptorParsingType {
|
||||
VALUE,
|
||||
LIST,
|
||||
IDENT_VALUE,
|
||||
TYPE_VALUE,
|
||||
TOKEN_VALUE
|
||||
}
|
||||
|
||||
export interface IPropertyDescriptor {
|
||||
name: string;
|
||||
type: PropertyDescriptorParsingType;
|
||||
initialValue: string;
|
||||
prefix: boolean;
|
||||
}
|
||||
|
||||
export interface IPropertyIdentValueDescriptor<T> extends IPropertyDescriptor {
|
||||
type: PropertyDescriptorParsingType.IDENT_VALUE;
|
||||
parse: (context: Context, token: string) => T;
|
||||
}
|
||||
|
||||
export interface IPropertyTypeValueDescriptor extends IPropertyDescriptor {
|
||||
type: PropertyDescriptorParsingType.TYPE_VALUE;
|
||||
format: CSSTypes;
|
||||
}
|
||||
|
||||
export interface IPropertyValueDescriptor<T> extends IPropertyDescriptor {
|
||||
type: PropertyDescriptorParsingType.VALUE;
|
||||
parse: (context: Context, token: CSSValue) => T;
|
||||
}
|
||||
|
||||
export interface IPropertyListDescriptor<T> extends IPropertyDescriptor {
|
||||
type: PropertyDescriptorParsingType.LIST;
|
||||
parse: (context: Context, tokens: CSSValue[]) => T;
|
||||
}
|
||||
|
||||
export interface IPropertyTokenValueDescriptor extends IPropertyDescriptor {
|
||||
type: PropertyDescriptorParsingType.TOKEN_VALUE;
|
||||
}
|
||||
|
||||
export type CSSPropertyDescriptor<T> =
|
||||
| IPropertyValueDescriptor<T>
|
||||
| IPropertyListDescriptor<T>
|
||||
| IPropertyIdentValueDescriptor<T>
|
||||
| IPropertyTypeValueDescriptor
|
||||
| IPropertyTokenValueDescriptor;
|
7
src/css/ITypeDescriptor.ts
Normal file
7
src/css/ITypeDescriptor.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import {CSSValue} from './syntax/parser';
|
||||
import {Context} from '../core/context';
|
||||
|
||||
export interface ITypeDescriptor<T> {
|
||||
name: string;
|
||||
parse: (context: Context, value: CSSValue) => T;
|
||||
}
|
321
src/css/index.ts
Normal file
321
src/css/index.ts
Normal file
@ -0,0 +1,321 @@
|
||||
import {CSSPropertyDescriptor, PropertyDescriptorParsingType} from './IPropertyDescriptor';
|
||||
import {backgroundClip} from './property-descriptors/background-clip';
|
||||
import {backgroundColor} from './property-descriptors/background-color';
|
||||
import {backgroundImage} from './property-descriptors/background-image';
|
||||
import {backgroundOrigin} from './property-descriptors/background-origin';
|
||||
import {backgroundPosition} from './property-descriptors/background-position';
|
||||
import {backgroundRepeat} from './property-descriptors/background-repeat';
|
||||
import {backgroundSize} from './property-descriptors/background-size';
|
||||
import {
|
||||
borderBottomColor,
|
||||
borderLeftColor,
|
||||
borderRightColor,
|
||||
borderTopColor
|
||||
} from './property-descriptors/border-color';
|
||||
import {
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius
|
||||
} from './property-descriptors/border-radius';
|
||||
import {
|
||||
borderBottomStyle,
|
||||
borderLeftStyle,
|
||||
borderRightStyle,
|
||||
borderTopStyle
|
||||
} from './property-descriptors/border-style';
|
||||
import {
|
||||
borderBottomWidth,
|
||||
borderLeftWidth,
|
||||
borderRightWidth,
|
||||
borderTopWidth
|
||||
} from './property-descriptors/border-width';
|
||||
import {color} from './property-descriptors/color';
|
||||
import {direction} from './property-descriptors/direction';
|
||||
import {display, DISPLAY} from './property-descriptors/display';
|
||||
import {float, FLOAT} from './property-descriptors/float';
|
||||
import {letterSpacing} from './property-descriptors/letter-spacing';
|
||||
import {lineBreak} from './property-descriptors/line-break';
|
||||
import {lineHeight} from './property-descriptors/line-height';
|
||||
import {listStyleImage} from './property-descriptors/list-style-image';
|
||||
import {listStylePosition} from './property-descriptors/list-style-position';
|
||||
import {listStyleType} from './property-descriptors/list-style-type';
|
||||
import {marginBottom, marginLeft, marginRight, marginTop} from './property-descriptors/margin';
|
||||
import {overflow, OVERFLOW} from './property-descriptors/overflow';
|
||||
import {overflowWrap} from './property-descriptors/overflow-wrap';
|
||||
import {paddingBottom, paddingLeft, paddingRight, paddingTop} from './property-descriptors/padding';
|
||||
import {textAlign} from './property-descriptors/text-align';
|
||||
import {position, POSITION} from './property-descriptors/position';
|
||||
import {textShadow} from './property-descriptors/text-shadow';
|
||||
import {textTransform} from './property-descriptors/text-transform';
|
||||
import {transform} from './property-descriptors/transform';
|
||||
import {transformOrigin} from './property-descriptors/transform-origin';
|
||||
import {visibility, VISIBILITY} from './property-descriptors/visibility';
|
||||
import {wordBreak} from './property-descriptors/word-break';
|
||||
import {zIndex} from './property-descriptors/z-index';
|
||||
import {CSSValue, isIdentToken, Parser} from './syntax/parser';
|
||||
import {Tokenizer} from './syntax/tokenizer';
|
||||
import {Color, color as colorType, isTransparent} from './types/color';
|
||||
import {angle} from './types/angle';
|
||||
import {image} from './types/image';
|
||||
import {time} from './types/time';
|
||||
import {opacity} from './property-descriptors/opacity';
|
||||
import {textDecorationColor} from './property-descriptors/text-decoration-color';
|
||||
import {textDecorationLine} from './property-descriptors/text-decoration-line';
|
||||
import {isLengthPercentage, LengthPercentage, ZERO_LENGTH} from './types/length-percentage';
|
||||
import {fontFamily} from './property-descriptors/font-family';
|
||||
import {fontSize} from './property-descriptors/font-size';
|
||||
import {isLength} from './types/length';
|
||||
import {fontWeight} from './property-descriptors/font-weight';
|
||||
import {fontVariant} from './property-descriptors/font-variant';
|
||||
import {fontStyle} from './property-descriptors/font-style';
|
||||
import {contains} from '../core/bitwise';
|
||||
import {content} from './property-descriptors/content';
|
||||
import {counterIncrement} from './property-descriptors/counter-increment';
|
||||
import {counterReset} from './property-descriptors/counter-reset';
|
||||
import {duration} from './property-descriptors/duration';
|
||||
import {quotes} from './property-descriptors/quotes';
|
||||
import {boxShadow} from './property-descriptors/box-shadow';
|
||||
import {paintOrder} from './property-descriptors/paint-order';
|
||||
import {webkitTextStrokeColor} from './property-descriptors/webkit-text-stroke-color';
|
||||
import {webkitTextStrokeWidth} from './property-descriptors/webkit-text-stroke-width';
|
||||
import {Context} from '../core/context';
|
||||
|
||||
export class CSSParsedDeclaration {
|
||||
animationDuration: ReturnType<typeof duration.parse>;
|
||||
backgroundClip: ReturnType<typeof backgroundClip.parse>;
|
||||
backgroundColor: Color;
|
||||
backgroundImage: ReturnType<typeof backgroundImage.parse>;
|
||||
backgroundOrigin: ReturnType<typeof backgroundOrigin.parse>;
|
||||
backgroundPosition: ReturnType<typeof backgroundPosition.parse>;
|
||||
backgroundRepeat: ReturnType<typeof backgroundRepeat.parse>;
|
||||
backgroundSize: ReturnType<typeof backgroundSize.parse>;
|
||||
borderTopColor: Color;
|
||||
borderRightColor: Color;
|
||||
borderBottomColor: Color;
|
||||
borderLeftColor: Color;
|
||||
borderTopLeftRadius: ReturnType<typeof borderTopLeftRadius.parse>;
|
||||
borderTopRightRadius: ReturnType<typeof borderTopRightRadius.parse>;
|
||||
borderBottomRightRadius: ReturnType<typeof borderBottomRightRadius.parse>;
|
||||
borderBottomLeftRadius: ReturnType<typeof borderBottomLeftRadius.parse>;
|
||||
borderTopStyle: ReturnType<typeof borderTopStyle.parse>;
|
||||
borderRightStyle: ReturnType<typeof borderRightStyle.parse>;
|
||||
borderBottomStyle: ReturnType<typeof borderBottomStyle.parse>;
|
||||
borderLeftStyle: ReturnType<typeof borderLeftStyle.parse>;
|
||||
borderTopWidth: ReturnType<typeof borderTopWidth.parse>;
|
||||
borderRightWidth: ReturnType<typeof borderRightWidth.parse>;
|
||||
borderBottomWidth: ReturnType<typeof borderBottomWidth.parse>;
|
||||
borderLeftWidth: ReturnType<typeof borderLeftWidth.parse>;
|
||||
boxShadow: ReturnType<typeof boxShadow.parse>;
|
||||
color: Color;
|
||||
direction: ReturnType<typeof direction.parse>;
|
||||
display: ReturnType<typeof display.parse>;
|
||||
float: ReturnType<typeof float.parse>;
|
||||
fontFamily: ReturnType<typeof fontFamily.parse>;
|
||||
fontSize: LengthPercentage;
|
||||
fontStyle: ReturnType<typeof fontStyle.parse>;
|
||||
fontVariant: ReturnType<typeof fontVariant.parse>;
|
||||
fontWeight: ReturnType<typeof fontWeight.parse>;
|
||||
letterSpacing: ReturnType<typeof letterSpacing.parse>;
|
||||
lineBreak: ReturnType<typeof lineBreak.parse>;
|
||||
lineHeight: CSSValue;
|
||||
listStyleImage: ReturnType<typeof listStyleImage.parse>;
|
||||
listStylePosition: ReturnType<typeof listStylePosition.parse>;
|
||||
listStyleType: ReturnType<typeof listStyleType.parse>;
|
||||
marginTop: CSSValue;
|
||||
marginRight: CSSValue;
|
||||
marginBottom: CSSValue;
|
||||
marginLeft: CSSValue;
|
||||
opacity: ReturnType<typeof opacity.parse>;
|
||||
overflowX: OVERFLOW;
|
||||
overflowY: OVERFLOW;
|
||||
overflowWrap: ReturnType<typeof overflowWrap.parse>;
|
||||
paddingTop: LengthPercentage;
|
||||
paddingRight: LengthPercentage;
|
||||
paddingBottom: LengthPercentage;
|
||||
paddingLeft: LengthPercentage;
|
||||
paintOrder: ReturnType<typeof paintOrder.parse>;
|
||||
position: ReturnType<typeof position.parse>;
|
||||
textAlign: ReturnType<typeof textAlign.parse>;
|
||||
textDecorationColor: Color;
|
||||
textDecorationLine: ReturnType<typeof textDecorationLine.parse>;
|
||||
textShadow: ReturnType<typeof textShadow.parse>;
|
||||
textTransform: ReturnType<typeof textTransform.parse>;
|
||||
transform: ReturnType<typeof transform.parse>;
|
||||
transformOrigin: ReturnType<typeof transformOrigin.parse>;
|
||||
visibility: ReturnType<typeof visibility.parse>;
|
||||
webkitTextStrokeColor: Color;
|
||||
webkitTextStrokeWidth: ReturnType<typeof webkitTextStrokeWidth.parse>;
|
||||
wordBreak: ReturnType<typeof wordBreak.parse>;
|
||||
zIndex: ReturnType<typeof zIndex.parse>;
|
||||
|
||||
constructor(context: Context, declaration: CSSStyleDeclaration) {
|
||||
this.animationDuration = parse(context, duration, declaration.animationDuration);
|
||||
this.backgroundClip = parse(context, backgroundClip, declaration.backgroundClip);
|
||||
this.backgroundColor = parse(context, backgroundColor, declaration.backgroundColor);
|
||||
this.backgroundImage = parse(context, backgroundImage, declaration.backgroundImage);
|
||||
this.backgroundOrigin = parse(context, backgroundOrigin, declaration.backgroundOrigin);
|
||||
this.backgroundPosition = parse(context, backgroundPosition, declaration.backgroundPosition);
|
||||
this.backgroundRepeat = parse(context, backgroundRepeat, declaration.backgroundRepeat);
|
||||
this.backgroundSize = parse(context, backgroundSize, declaration.backgroundSize);
|
||||
this.borderTopColor = parse(context, borderTopColor, declaration.borderTopColor);
|
||||
this.borderRightColor = parse(context, borderRightColor, declaration.borderRightColor);
|
||||
this.borderBottomColor = parse(context, borderBottomColor, declaration.borderBottomColor);
|
||||
this.borderLeftColor = parse(context, borderLeftColor, declaration.borderLeftColor);
|
||||
this.borderTopLeftRadius = parse(context, borderTopLeftRadius, declaration.borderTopLeftRadius);
|
||||
this.borderTopRightRadius = parse(context, borderTopRightRadius, declaration.borderTopRightRadius);
|
||||
this.borderBottomRightRadius = parse(context, borderBottomRightRadius, declaration.borderBottomRightRadius);
|
||||
this.borderBottomLeftRadius = parse(context, borderBottomLeftRadius, declaration.borderBottomLeftRadius);
|
||||
this.borderTopStyle = parse(context, borderTopStyle, declaration.borderTopStyle);
|
||||
this.borderRightStyle = parse(context, borderRightStyle, declaration.borderRightStyle);
|
||||
this.borderBottomStyle = parse(context, borderBottomStyle, declaration.borderBottomStyle);
|
||||
this.borderLeftStyle = parse(context, borderLeftStyle, declaration.borderLeftStyle);
|
||||
this.borderTopWidth = parse(context, borderTopWidth, declaration.borderTopWidth);
|
||||
this.borderRightWidth = parse(context, borderRightWidth, declaration.borderRightWidth);
|
||||
this.borderBottomWidth = parse(context, borderBottomWidth, declaration.borderBottomWidth);
|
||||
this.borderLeftWidth = parse(context, borderLeftWidth, declaration.borderLeftWidth);
|
||||
this.boxShadow = parse(context, boxShadow, declaration.boxShadow);
|
||||
this.color = parse(context, color, declaration.color);
|
||||
this.direction = parse(context, direction, declaration.direction);
|
||||
this.display = parse(context, display, declaration.display);
|
||||
this.float = parse(context, float, declaration.cssFloat);
|
||||
this.fontFamily = parse(context, fontFamily, declaration.fontFamily);
|
||||
this.fontSize = parse(context, fontSize, declaration.fontSize);
|
||||
this.fontStyle = parse(context, fontStyle, declaration.fontStyle);
|
||||
this.fontVariant = parse(context, fontVariant, declaration.fontVariant);
|
||||
this.fontWeight = parse(context, fontWeight, declaration.fontWeight);
|
||||
this.letterSpacing = parse(context, letterSpacing, declaration.letterSpacing);
|
||||
this.lineBreak = parse(context, lineBreak, declaration.lineBreak);
|
||||
this.lineHeight = parse(context, lineHeight, declaration.lineHeight);
|
||||
this.listStyleImage = parse(context, listStyleImage, declaration.listStyleImage);
|
||||
this.listStylePosition = parse(context, listStylePosition, declaration.listStylePosition);
|
||||
this.listStyleType = parse(context, listStyleType, declaration.listStyleType);
|
||||
this.marginTop = parse(context, marginTop, declaration.marginTop);
|
||||
this.marginRight = parse(context, marginRight, declaration.marginRight);
|
||||
this.marginBottom = parse(context, marginBottom, declaration.marginBottom);
|
||||
this.marginLeft = parse(context, marginLeft, declaration.marginLeft);
|
||||
this.opacity = parse(context, opacity, declaration.opacity);
|
||||
const overflowTuple = parse(context, overflow, declaration.overflow);
|
||||
this.overflowX = overflowTuple[0];
|
||||
this.overflowY = overflowTuple[overflowTuple.length > 1 ? 1 : 0];
|
||||
this.overflowWrap = parse(context, overflowWrap, declaration.overflowWrap);
|
||||
this.paddingTop = parse(context, paddingTop, declaration.paddingTop);
|
||||
this.paddingRight = parse(context, paddingRight, declaration.paddingRight);
|
||||
this.paddingBottom = parse(context, paddingBottom, declaration.paddingBottom);
|
||||
this.paddingLeft = parse(context, paddingLeft, declaration.paddingLeft);
|
||||
this.paintOrder = parse(context, paintOrder, declaration.paintOrder);
|
||||
this.position = parse(context, position, declaration.position);
|
||||
this.textAlign = parse(context, textAlign, declaration.textAlign);
|
||||
this.textDecorationColor = parse(
|
||||
context,
|
||||
textDecorationColor,
|
||||
declaration.textDecorationColor ?? declaration.color
|
||||
);
|
||||
this.textDecorationLine = parse(
|
||||
context,
|
||||
textDecorationLine,
|
||||
declaration.textDecorationLine ?? declaration.textDecoration
|
||||
);
|
||||
this.textShadow = parse(context, textShadow, declaration.textShadow);
|
||||
this.textTransform = parse(context, textTransform, declaration.textTransform);
|
||||
this.transform = parse(context, transform, declaration.transform);
|
||||
this.transformOrigin = parse(context, transformOrigin, declaration.transformOrigin);
|
||||
this.visibility = parse(context, visibility, declaration.visibility);
|
||||
this.webkitTextStrokeColor = parse(context, webkitTextStrokeColor, declaration.webkitTextStrokeColor);
|
||||
this.webkitTextStrokeWidth = parse(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth);
|
||||
this.wordBreak = parse(context, wordBreak, declaration.wordBreak);
|
||||
this.zIndex = parse(context, zIndex, declaration.zIndex);
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
return this.display > 0 && this.opacity > 0 && this.visibility === VISIBILITY.VISIBLE;
|
||||
}
|
||||
|
||||
isTransparent(): boolean {
|
||||
return isTransparent(this.backgroundColor);
|
||||
}
|
||||
|
||||
isTransformed(): boolean {
|
||||
return this.transform !== null;
|
||||
}
|
||||
|
||||
isPositioned(): boolean {
|
||||
return this.position !== POSITION.STATIC;
|
||||
}
|
||||
|
||||
isPositionedWithZIndex(): boolean {
|
||||
return this.isPositioned() && !this.zIndex.auto;
|
||||
}
|
||||
|
||||
isFloating(): boolean {
|
||||
return this.float !== FLOAT.NONE;
|
||||
}
|
||||
|
||||
isInlineLevel(): boolean {
|
||||
return (
|
||||
contains(this.display, DISPLAY.INLINE) ||
|
||||
contains(this.display, DISPLAY.INLINE_BLOCK) ||
|
||||
contains(this.display, DISPLAY.INLINE_FLEX) ||
|
||||
contains(this.display, DISPLAY.INLINE_GRID) ||
|
||||
contains(this.display, DISPLAY.INLINE_LIST_ITEM) ||
|
||||
contains(this.display, DISPLAY.INLINE_TABLE)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CSSParsedPseudoDeclaration {
|
||||
content: ReturnType<typeof content.parse>;
|
||||
quotes: ReturnType<typeof quotes.parse>;
|
||||
|
||||
constructor(context: Context, declaration: CSSStyleDeclaration) {
|
||||
this.content = parse(context, content, declaration.content);
|
||||
this.quotes = parse(context, quotes, declaration.quotes);
|
||||
}
|
||||
}
|
||||
|
||||
export class CSSParsedCounterDeclaration {
|
||||
counterIncrement: ReturnType<typeof counterIncrement.parse>;
|
||||
counterReset: ReturnType<typeof counterReset.parse>;
|
||||
|
||||
constructor(context: Context, declaration: CSSStyleDeclaration) {
|
||||
this.counterIncrement = parse(context, counterIncrement, declaration.counterIncrement);
|
||||
this.counterReset = parse(context, counterReset, declaration.counterReset);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const parse = (context: Context, descriptor: CSSPropertyDescriptor<any>, style?: string | null) => {
|
||||
const tokenizer = new Tokenizer();
|
||||
const value = style !== null && typeof style !== 'undefined' ? style.toString() : descriptor.initialValue;
|
||||
tokenizer.write(value);
|
||||
const parser = new Parser(tokenizer.read());
|
||||
switch (descriptor.type) {
|
||||
case PropertyDescriptorParsingType.IDENT_VALUE:
|
||||
const token = parser.parseComponentValue();
|
||||
return descriptor.parse(context, isIdentToken(token) ? token.value : descriptor.initialValue);
|
||||
case PropertyDescriptorParsingType.VALUE:
|
||||
return descriptor.parse(context, parser.parseComponentValue());
|
||||
case PropertyDescriptorParsingType.LIST:
|
||||
return descriptor.parse(context, parser.parseComponentValues());
|
||||
case PropertyDescriptorParsingType.TOKEN_VALUE:
|
||||
return parser.parseComponentValue();
|
||||
case PropertyDescriptorParsingType.TYPE_VALUE:
|
||||
switch (descriptor.format) {
|
||||
case 'angle':
|
||||
return angle.parse(context, parser.parseComponentValue());
|
||||
case 'color':
|
||||
return colorType.parse(context, parser.parseComponentValue());
|
||||
case 'image':
|
||||
return image.parse(context, parser.parseComponentValue());
|
||||
case 'length':
|
||||
const length = parser.parseComponentValue();
|
||||
return isLength(length) ? length : ZERO_LENGTH;
|
||||
case 'length-percentage':
|
||||
const value = parser.parseComponentValue();
|
||||
return isLengthPercentage(value) ? value : ZERO_LENGTH;
|
||||
case 'time':
|
||||
return time.parse(context, parser.parseComponentValue());
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
4
src/css/layout/__mocks__/bounds.ts
Normal file
4
src/css/layout/__mocks__/bounds.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const {Bounds} = jest.requireActual('../bounds');
|
||||
export const parseBounds = (): typeof Bounds => {
|
||||
return new Bounds(0, 0, 200, 50);
|
||||
};
|
58
src/css/layout/bounds.ts
Normal file
58
src/css/layout/bounds.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import {Context} from '../../core/context';
|
||||
|
||||
export class Bounds {
|
||||
constructor(readonly left: number, readonly top: number, readonly width: number, readonly height: number) {}
|
||||
|
||||
add(x: number, y: number, w: number, h: number): Bounds {
|
||||
return new Bounds(this.left + x, this.top + y, this.width + w, this.height + h);
|
||||
}
|
||||
|
||||
static fromClientRect(context: Context, clientRect: ClientRect): Bounds {
|
||||
return new Bounds(
|
||||
clientRect.left + context.windowBounds.left,
|
||||
clientRect.top + context.windowBounds.top,
|
||||
clientRect.width,
|
||||
clientRect.height
|
||||
);
|
||||
}
|
||||
|
||||
static fromDOMRectList(context: Context, domRectList: DOMRectList): Bounds {
|
||||
const domRect = Array.from(domRectList).find((rect) => rect.width !== 0);
|
||||
return domRect
|
||||
? new Bounds(
|
||||
domRect.left + context.windowBounds.left,
|
||||
domRect.top + context.windowBounds.top,
|
||||
domRect.width,
|
||||
domRect.height
|
||||
)
|
||||
: Bounds.EMPTY;
|
||||
}
|
||||
|
||||
static EMPTY = new Bounds(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
export const parseBounds = (context: Context, node: Element): Bounds => {
|
||||
return Bounds.fromClientRect(context, node.getBoundingClientRect());
|
||||
};
|
||||
|
||||
export const parseDocumentSize = (document: Document): Bounds => {
|
||||
const body = document.body;
|
||||
const documentElement = document.documentElement;
|
||||
|
||||
if (!body || !documentElement) {
|
||||
throw new Error(`Unable to get document size`);
|
||||
}
|
||||
const width = Math.max(
|
||||
Math.max(body.scrollWidth, documentElement.scrollWidth),
|
||||
Math.max(body.offsetWidth, documentElement.offsetWidth),
|
||||
Math.max(body.clientWidth, documentElement.clientWidth)
|
||||
);
|
||||
|
||||
const height = Math.max(
|
||||
Math.max(body.scrollHeight, documentElement.scrollHeight),
|
||||
Math.max(body.offsetHeight, documentElement.offsetHeight),
|
||||
Math.max(body.clientHeight, documentElement.clientHeight)
|
||||
);
|
||||
|
||||
return new Bounds(0, 0, width, height);
|
||||
};
|
158
src/css/layout/text.ts
Normal file
158
src/css/layout/text.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import {OVERFLOW_WRAP} from '../property-descriptors/overflow-wrap';
|
||||
import {CSSParsedDeclaration} from '../index';
|
||||
import {fromCodePoint, LineBreaker, toCodePoints} from 'css-line-break';
|
||||
import {splitGraphemes} from 'text-segmentation';
|
||||
import {Bounds, parseBounds} from './bounds';
|
||||
import {FEATURES} from '../../core/features';
|
||||
import {Context} from '../../core/context';
|
||||
|
||||
export class TextBounds {
|
||||
readonly text: string;
|
||||
readonly bounds: Bounds;
|
||||
|
||||
constructor(text: string, bounds: Bounds) {
|
||||
this.text = text;
|
||||
this.bounds = bounds;
|
||||
}
|
||||
}
|
||||
|
||||
export const parseTextBounds = (
|
||||
context: Context,
|
||||
value: string,
|
||||
styles: CSSParsedDeclaration,
|
||||
node: Text
|
||||
): TextBounds[] => {
|
||||
const textList = breakText(value, styles);
|
||||
const textBounds: TextBounds[] = [];
|
||||
let offset = 0;
|
||||
textList.forEach((text) => {
|
||||
if (styles.textDecorationLine.length || text.trim().length > 0) {
|
||||
if (FEATURES.SUPPORT_RANGE_BOUNDS) {
|
||||
const clientRects = createRange(node, offset, text.length).getClientRects();
|
||||
if (clientRects.length > 1) {
|
||||
const subSegments = segmentGraphemes(text);
|
||||
let subOffset = 0;
|
||||
subSegments.forEach((subSegment) => {
|
||||
textBounds.push(
|
||||
new TextBounds(
|
||||
subSegment,
|
||||
Bounds.fromDOMRectList(
|
||||
context,
|
||||
createRange(node, subOffset + offset, subSegment.length).getClientRects()
|
||||
)
|
||||
)
|
||||
);
|
||||
subOffset += subSegment.length;
|
||||
});
|
||||
} else {
|
||||
textBounds.push(new TextBounds(text, Bounds.fromDOMRectList(context, clientRects)));
|
||||
}
|
||||
} else {
|
||||
const replacementNode = node.splitText(text.length);
|
||||
textBounds.push(new TextBounds(text, getWrapperBounds(context, node)));
|
||||
node = replacementNode;
|
||||
}
|
||||
} else if (!FEATURES.SUPPORT_RANGE_BOUNDS) {
|
||||
node = node.splitText(text.length);
|
||||
}
|
||||
offset += text.length;
|
||||
});
|
||||
|
||||
return textBounds;
|
||||
};
|
||||
|
||||
const getWrapperBounds = (context: Context, node: Text): Bounds => {
|
||||
const ownerDocument = node.ownerDocument;
|
||||
if (ownerDocument) {
|
||||
const wrapper = ownerDocument.createElement('html2canvaswrapper');
|
||||
wrapper.appendChild(node.cloneNode(true));
|
||||
const parentNode = node.parentNode;
|
||||
if (parentNode) {
|
||||
parentNode.replaceChild(wrapper, node);
|
||||
const bounds = parseBounds(context, wrapper);
|
||||
if (wrapper.firstChild) {
|
||||
parentNode.replaceChild(wrapper.firstChild, wrapper);
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
return Bounds.EMPTY;
|
||||
};
|
||||
|
||||
const createRange = (node: Text, offset: number, length: number): Range => {
|
||||
const ownerDocument = node.ownerDocument;
|
||||
if (!ownerDocument) {
|
||||
throw new Error('Node has no owner document');
|
||||
}
|
||||
const range = ownerDocument.createRange();
|
||||
range.setStart(node, offset);
|
||||
range.setEnd(node, offset + length);
|
||||
return range;
|
||||
};
|
||||
|
||||
export const segmentGraphemes = (value: string): string[] => {
|
||||
if (FEATURES.SUPPORT_NATIVE_TEXT_SEGMENTATION) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const segmenter = new (Intl as any).Segmenter(void 0, {granularity: 'grapheme'});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return Array.from(segmenter.segment(value)).map((segment: any) => segment.segment);
|
||||
}
|
||||
|
||||
return splitGraphemes(value);
|
||||
};
|
||||
|
||||
const segmentWords = (value: string, styles: CSSParsedDeclaration): string[] => {
|
||||
if (FEATURES.SUPPORT_NATIVE_TEXT_SEGMENTATION) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const segmenter = new (Intl as any).Segmenter(void 0, {
|
||||
granularity: 'word'
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return Array.from(segmenter.segment(value)).map((segment: any) => segment.segment);
|
||||
}
|
||||
|
||||
return breakWords(value, styles);
|
||||
};
|
||||
|
||||
const breakText = (value: string, styles: CSSParsedDeclaration): string[] => {
|
||||
return styles.letterSpacing !== 0 ? segmentGraphemes(value) : segmentWords(value, styles);
|
||||
};
|
||||
|
||||
// https://drafts.csswg.org/css-text/#word-separator
|
||||
const wordSeparators = [0x0020, 0x00a0, 0x1361, 0x10100, 0x10101, 0x1039, 0x1091];
|
||||
|
||||
const breakWords = (str: string, styles: CSSParsedDeclaration): string[] => {
|
||||
const breaker = LineBreaker(str, {
|
||||
lineBreak: styles.lineBreak,
|
||||
wordBreak: styles.overflowWrap === OVERFLOW_WRAP.BREAK_WORD ? 'break-word' : styles.wordBreak
|
||||
});
|
||||
|
||||
const words = [];
|
||||
let bk;
|
||||
|
||||
while (!(bk = breaker.next()).done) {
|
||||
if (bk.value) {
|
||||
const value = bk.value.slice();
|
||||
const codePoints = toCodePoints(value);
|
||||
let word = '';
|
||||
codePoints.forEach((codePoint) => {
|
||||
if (wordSeparators.indexOf(codePoint) === -1) {
|
||||
word += fromCodePoint(codePoint);
|
||||
} else {
|
||||
if (word.length) {
|
||||
words.push(word);
|
||||
}
|
||||
words.push(fromCodePoint(codePoint));
|
||||
word = '';
|
||||
}
|
||||
});
|
||||
|
||||
if (word.length) {
|
||||
words.push(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return words;
|
||||
};
|
59
src/css/property-descriptors/__tests__/background-tests.ts
Normal file
59
src/css/property-descriptors/__tests__/background-tests.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import {deepStrictEqual} from 'assert';
|
||||
import {Parser} from '../../syntax/parser';
|
||||
import {backgroundImage} from '../background-image';
|
||||
import {CSSImageType} from '../../types/image';
|
||||
import {pack} from '../../types/color';
|
||||
import {deg} from '../../types/angle';
|
||||
|
||||
jest.mock('../../../core/context');
|
||||
import {Context} from '../../../core/context';
|
||||
|
||||
jest.mock('../../../core/features');
|
||||
|
||||
const backgroundImageParse = (context: Context, value: string) =>
|
||||
backgroundImage.parse(context, Parser.parseValues(value));
|
||||
|
||||
describe('property-descriptors', () => {
|
||||
let context: Context;
|
||||
beforeEach(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
context = new Context({} as any, {} as any);
|
||||
});
|
||||
describe('background-image', () => {
|
||||
it('none', () => {
|
||||
deepStrictEqual(backgroundImageParse(context, 'none'), []);
|
||||
expect(context.cache.addImage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('url(test.jpg), url(test2.jpg)', () => {
|
||||
deepStrictEqual(
|
||||
backgroundImageParse(context, 'url(http://example.com/test.jpg), url(http://example.com/test2.jpg)'),
|
||||
[
|
||||
{url: 'http://example.com/test.jpg', type: CSSImageType.URL},
|
||||
{url: 'http://example.com/test2.jpg', type: CSSImageType.URL}
|
||||
]
|
||||
);
|
||||
expect(context.cache.addImage).toHaveBeenCalledWith('http://example.com/test.jpg');
|
||||
expect(context.cache.addImage).toHaveBeenCalledWith('http://example.com/test2.jpg');
|
||||
});
|
||||
|
||||
it(`linear-gradient(to bottom, rgba(255,255,0,0.5), rgba(0,0,255,0.5)), url('https://html2canvas.hertzen.com')`, () =>
|
||||
deepStrictEqual(
|
||||
backgroundImageParse(
|
||||
context,
|
||||
`linear-gradient(to bottom, rgba(255,255,0,0.5), rgba(0,0,255,0.5)), url('https://html2canvas.hertzen.com')`
|
||||
),
|
||||
[
|
||||
{
|
||||
angle: deg(180),
|
||||
type: CSSImageType.LINEAR_GRADIENT,
|
||||
stops: [
|
||||
{color: pack(255, 255, 0, 0.5), stop: null},
|
||||
{color: pack(0, 0, 255, 0.5), stop: null}
|
||||
]
|
||||
},
|
||||
{url: 'https://html2canvas.hertzen.com', type: CSSImageType.URL}
|
||||
]
|
||||
));
|
||||
});
|
||||
});
|
25
src/css/property-descriptors/__tests__/font-family.ts
Normal file
25
src/css/property-descriptors/__tests__/font-family.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {deepEqual} from 'assert';
|
||||
import {Parser} from '../../syntax/parser';
|
||||
import {fontFamily} from '../font-family';
|
||||
import {Context} from '../../../core/context';
|
||||
|
||||
const fontFamilyParse = (value: string) => fontFamily.parse({} as Context, Parser.parseValues(value));
|
||||
|
||||
describe('property-descriptors', () => {
|
||||
describe('font-family', () => {
|
||||
it('sans-serif', () => deepEqual(fontFamilyParse('sans-serif'), ['sans-serif']));
|
||||
|
||||
it('great fonts 40 library', () =>
|
||||
deepEqual(fontFamilyParse('great fonts 40 library'), ["'great fonts 40 library'"]));
|
||||
|
||||
it('preferred font, "quoted fallback font", font', () =>
|
||||
deepEqual(fontFamilyParse('preferred font, "quoted fallback font", font'), [
|
||||
"'preferred font'",
|
||||
"'quoted fallback font'",
|
||||
'font'
|
||||
]));
|
||||
|
||||
it("'escaping test\\'s font'", () =>
|
||||
deepEqual(fontFamilyParse("'escaping test\\'s font'"), ["'escaping test's font'"]));
|
||||
});
|
||||
});
|
87
src/css/property-descriptors/__tests__/paint-order.ts
Normal file
87
src/css/property-descriptors/__tests__/paint-order.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {deepStrictEqual} from 'assert';
|
||||
import {Parser} from '../../syntax/parser';
|
||||
import {paintOrder, PAINT_ORDER_LAYER} from '../paint-order';
|
||||
import {Context} from '../../../core/context';
|
||||
|
||||
const paintOrderParse = (value: string) => paintOrder.parse({} as Context, Parser.parseValues(value));
|
||||
|
||||
describe('property-descriptors', () => {
|
||||
describe('paint-order', () => {
|
||||
it('none', () =>
|
||||
deepStrictEqual(paintOrderParse('none'), [
|
||||
PAINT_ORDER_LAYER.FILL,
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.MARKERS
|
||||
]));
|
||||
|
||||
it('EMPTY', () =>
|
||||
deepStrictEqual(paintOrderParse(''), [
|
||||
PAINT_ORDER_LAYER.FILL,
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.MARKERS
|
||||
]));
|
||||
|
||||
it('other values', () =>
|
||||
deepStrictEqual(paintOrderParse('other values'), [
|
||||
PAINT_ORDER_LAYER.FILL,
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.MARKERS
|
||||
]));
|
||||
|
||||
it('normal', () =>
|
||||
deepStrictEqual(paintOrderParse('normal'), [
|
||||
PAINT_ORDER_LAYER.FILL,
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.MARKERS
|
||||
]));
|
||||
|
||||
it('stroke', () =>
|
||||
deepStrictEqual(paintOrderParse('stroke'), [
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.FILL,
|
||||
PAINT_ORDER_LAYER.MARKERS
|
||||
]));
|
||||
|
||||
it('fill', () =>
|
||||
deepStrictEqual(paintOrderParse('fill'), [
|
||||
PAINT_ORDER_LAYER.FILL,
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.MARKERS
|
||||
]));
|
||||
|
||||
it('markers', () =>
|
||||
deepStrictEqual(paintOrderParse('markers'), [
|
||||
PAINT_ORDER_LAYER.MARKERS,
|
||||
PAINT_ORDER_LAYER.FILL,
|
||||
PAINT_ORDER_LAYER.STROKE
|
||||
]));
|
||||
|
||||
it('stroke fill', () =>
|
||||
deepStrictEqual(paintOrderParse('stroke fill'), [
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.FILL,
|
||||
PAINT_ORDER_LAYER.MARKERS
|
||||
]));
|
||||
|
||||
it('markers stroke', () =>
|
||||
deepStrictEqual(paintOrderParse('markers stroke'), [
|
||||
PAINT_ORDER_LAYER.MARKERS,
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.FILL
|
||||
]));
|
||||
|
||||
it('markers stroke fill', () =>
|
||||
deepStrictEqual(paintOrderParse('markers stroke fill'), [
|
||||
PAINT_ORDER_LAYER.MARKERS,
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.FILL
|
||||
]));
|
||||
|
||||
it('stroke fill markers', () =>
|
||||
deepStrictEqual(paintOrderParse('stroke fill markers'), [
|
||||
PAINT_ORDER_LAYER.STROKE,
|
||||
PAINT_ORDER_LAYER.FILL,
|
||||
PAINT_ORDER_LAYER.MARKERS
|
||||
]));
|
||||
});
|
||||
});
|
94
src/css/property-descriptors/__tests__/text-shadow.ts
Normal file
94
src/css/property-descriptors/__tests__/text-shadow.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import {deepStrictEqual} from 'assert';
|
||||
import {Parser} from '../../syntax/parser';
|
||||
import {color, COLORS} from '../../types/color';
|
||||
import {textShadow} from '../text-shadow';
|
||||
import {FLAG_INTEGER, DimensionToken, TokenType} from '../../syntax/tokenizer';
|
||||
import {ZERO_LENGTH} from '../../types/length-percentage';
|
||||
import {Context} from '../../../core/context';
|
||||
|
||||
const textShadowParse = (value: string) => textShadow.parse({} as Context, Parser.parseValues(value));
|
||||
const colorParse = (value: string) => color.parse({} as Context, Parser.parseValue(value));
|
||||
const dimension = (number: number, unit: string): DimensionToken => ({
|
||||
flags: FLAG_INTEGER,
|
||||
number,
|
||||
unit,
|
||||
type: TokenType.DIMENSION_TOKEN
|
||||
});
|
||||
|
||||
describe('property-descriptors', () => {
|
||||
describe('text-shadow', () => {
|
||||
it('none', () => deepStrictEqual(textShadowParse('none'), []));
|
||||
|
||||
it('1px 1px 2px pink', () =>
|
||||
deepStrictEqual(textShadowParse('1px 1px 2px pink'), [
|
||||
{
|
||||
color: colorParse('pink'),
|
||||
offsetX: dimension(1, 'px'),
|
||||
offsetY: dimension(1, 'px'),
|
||||
blur: dimension(2, 'px')
|
||||
}
|
||||
]));
|
||||
|
||||
it('#fc0 1px 0 10px', () =>
|
||||
deepStrictEqual(textShadowParse('#fc0 1px 0 10px'), [
|
||||
{
|
||||
color: colorParse('#fc0'),
|
||||
offsetX: dimension(1, 'px'),
|
||||
offsetY: ZERO_LENGTH,
|
||||
blur: dimension(10, 'px')
|
||||
}
|
||||
]));
|
||||
|
||||
it('5px 5px #558abb', () =>
|
||||
deepStrictEqual(textShadowParse('5px 5px #558abb'), [
|
||||
{
|
||||
color: colorParse('#558abb'),
|
||||
offsetX: dimension(5, 'px'),
|
||||
offsetY: dimension(5, 'px'),
|
||||
blur: ZERO_LENGTH
|
||||
}
|
||||
]));
|
||||
|
||||
it('white 2px 5px', () =>
|
||||
deepStrictEqual(textShadowParse('white 2px 5px'), [
|
||||
{
|
||||
color: colorParse('#fff'),
|
||||
offsetX: dimension(2, 'px'),
|
||||
offsetY: dimension(5, 'px'),
|
||||
blur: ZERO_LENGTH
|
||||
}
|
||||
]));
|
||||
|
||||
it('white 2px 5px', () =>
|
||||
deepStrictEqual(textShadowParse('5px 10px'), [
|
||||
{
|
||||
color: COLORS.TRANSPARENT,
|
||||
offsetX: dimension(5, 'px'),
|
||||
offsetY: dimension(10, 'px'),
|
||||
blur: ZERO_LENGTH
|
||||
}
|
||||
]));
|
||||
|
||||
it('1px 1px 2px red, 0 0 1em blue, 0 0 2em blue', () =>
|
||||
deepStrictEqual(textShadowParse('1px 1px 2px red, 0 0 1em blue, 0 0 2em blue'), [
|
||||
{
|
||||
color: colorParse('red'),
|
||||
offsetX: dimension(1, 'px'),
|
||||
offsetY: dimension(1, 'px'),
|
||||
blur: dimension(2, 'px')
|
||||
},
|
||||
{
|
||||
color: colorParse('blue'),
|
||||
offsetX: ZERO_LENGTH,
|
||||
offsetY: ZERO_LENGTH,
|
||||
blur: dimension(1, 'em')
|
||||
},
|
||||
{
|
||||
color: colorParse('blue'),
|
||||
offsetX: ZERO_LENGTH,
|
||||
offsetY: ZERO_LENGTH,
|
||||
blur: dimension(2, 'em')
|
||||
}
|
||||
]));
|
||||
});
|
||||
});
|
18
src/css/property-descriptors/__tests__/transform-tests.ts
Normal file
18
src/css/property-descriptors/__tests__/transform-tests.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {transform} from '../transform';
|
||||
import {Parser} from '../../syntax/parser';
|
||||
import {deepStrictEqual} from 'assert';
|
||||
import {Context} from '../../../core/context';
|
||||
const parseValue = (value: string) => transform.parse({} as Context, Parser.parseValue(value));
|
||||
|
||||
describe('property-descriptors', () => {
|
||||
describe('transform', () => {
|
||||
it('none', () => deepStrictEqual(parseValue('none'), null));
|
||||
it('matrix(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)', () =>
|
||||
deepStrictEqual(parseValue('matrix(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)'), [1, 2, 3, 4, 5, 6]));
|
||||
it('matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)', () =>
|
||||
deepStrictEqual(
|
||||
parseValue('matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)'),
|
||||
[1, 0, 0, 1, 0, 0]
|
||||
));
|
||||
});
|
||||
});
|
30
src/css/property-descriptors/background-clip.ts
Normal file
30
src/css/property-descriptors/background-clip.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
import {CSSValue, isIdentToken} from '../syntax/parser';
|
||||
import {Context} from '../../core/context';
|
||||
export const enum BACKGROUND_CLIP {
|
||||
BORDER_BOX = 0,
|
||||
PADDING_BOX = 1,
|
||||
CONTENT_BOX = 2
|
||||
}
|
||||
|
||||
export type BackgroundClip = BACKGROUND_CLIP[];
|
||||
|
||||
export const backgroundClip: IPropertyListDescriptor<BackgroundClip> = {
|
||||
name: 'background-clip',
|
||||
initialValue: 'border-box',
|
||||
prefix: false,
|
||||
type: PropertyDescriptorParsingType.LIST,
|
||||
parse: (_context: Context, tokens: CSSValue[]): BackgroundClip => {
|
||||
return tokens.map((token) => {
|
||||
if (isIdentToken(token)) {
|
||||
switch (token.value) {
|
||||
case 'padding-box':
|
||||
return BACKGROUND_CLIP.PADDING_BOX;
|
||||
case 'content-box':
|
||||
return BACKGROUND_CLIP.CONTENT_BOX;
|
||||
}
|
||||
}
|
||||
return BACKGROUND_CLIP.BORDER_BOX;
|
||||
});
|
||||
}
|
||||
};
|
9
src/css/property-descriptors/background-color.ts
Normal file
9
src/css/property-descriptors/background-color.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {IPropertyTypeValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
|
||||
export const backgroundColor: IPropertyTypeValueDescriptor = {
|
||||
name: `background-color`,
|
||||
initialValue: 'transparent',
|
||||
prefix: false,
|
||||
type: PropertyDescriptorParsingType.TYPE_VALUE,
|
||||
format: 'color'
|
||||
};
|
27
src/css/property-descriptors/background-image.ts
Normal file
27
src/css/property-descriptors/background-image.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import {TokenType} from '../syntax/tokenizer';
|
||||
import {ICSSImage, image, isSupportedImage} from '../types/image';
|
||||
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
import {CSSValue, nonFunctionArgSeparator} from '../syntax/parser';
|
||||
import {Context} from '../../core/context';
|
||||
|
||||
export const backgroundImage: IPropertyListDescriptor<ICSSImage[]> = {
|
||||
name: 'background-image',
|
||||
initialValue: 'none',
|
||||
type: PropertyDescriptorParsingType.LIST,
|
||||
prefix: false,
|
||||
parse: (context: Context, tokens: CSSValue[]) => {
|
||||
if (tokens.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const first = tokens[0];
|
||||
|
||||
if (first.type === TokenType.IDENT_TOKEN && first.value === 'none') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return tokens
|
||||
.filter((value) => nonFunctionArgSeparator(value) && isSupportedImage(value))
|
||||
.map((value) => image.parse(context, value));
|
||||
}
|
||||
};
|
31
src/css/property-descriptors/background-origin.ts
Normal file
31
src/css/property-descriptors/background-origin.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
import {CSSValue, isIdentToken} from '../syntax/parser';
|
||||
import {Context} from '../../core/context';
|
||||
|
||||
export const enum BACKGROUND_ORIGIN {
|
||||
BORDER_BOX = 0,
|
||||
PADDING_BOX = 1,
|
||||
CONTENT_BOX = 2
|
||||
}
|
||||
|
||||
export type BackgroundOrigin = BACKGROUND_ORIGIN[];
|
||||
|
||||
export const backgroundOrigin: IPropertyListDescriptor<BackgroundOrigin> = {
|
||||
name: 'background-origin',
|
||||
initialValue: 'border-box',
|
||||
prefix: false,
|
||||
type: PropertyDescriptorParsingType.LIST,
|
||||
parse: (_context: Context, tokens: CSSValue[]): BackgroundOrigin => {
|
||||
return tokens.map((token) => {
|
||||
if (isIdentToken(token)) {
|
||||
switch (token.value) {
|
||||
case 'padding-box':
|
||||
return BACKGROUND_ORIGIN.PADDING_BOX;
|
||||
case 'content-box':
|
||||
return BACKGROUND_ORIGIN.CONTENT_BOX;
|
||||
}
|
||||
}
|
||||
return BACKGROUND_ORIGIN.BORDER_BOX;
|
||||
});
|
||||
}
|
||||
};
|
19
src/css/property-descriptors/background-position.ts
Normal file
19
src/css/property-descriptors/background-position.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {PropertyDescriptorParsingType, IPropertyListDescriptor} from '../IPropertyDescriptor';
|
||||
import {CSSValue, parseFunctionArgs} from '../syntax/parser';
|
||||
import {isLengthPercentage, LengthPercentageTuple, parseLengthPercentageTuple} from '../types/length-percentage';
|
||||
import {Context} from '../../core/context';
|
||||
export type BackgroundPosition = BackgroundImagePosition[];
|
||||
|
||||
export type BackgroundImagePosition = LengthPercentageTuple;
|
||||
|
||||
export const backgroundPosition: IPropertyListDescriptor<BackgroundPosition> = {
|
||||
name: 'background-position',
|
||||
initialValue: '0% 0%',
|
||||
type: PropertyDescriptorParsingType.LIST,
|
||||
prefix: false,
|
||||
parse: (_context: Context, tokens: CSSValue[]): BackgroundPosition => {
|
||||
return parseFunctionArgs(tokens)
|
||||
.map((values: CSSValue[]) => values.filter(isLengthPercentage))
|
||||
.map(parseLengthPercentageTuple);
|
||||
}
|
||||
};
|
44
src/css/property-descriptors/background-repeat.ts
Normal file
44
src/css/property-descriptors/background-repeat.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
import {CSSValue, isIdentToken, parseFunctionArgs} from '../syntax/parser';
|
||||
import {Context} from '../../core/context';
|
||||
export type BackgroundRepeat = BACKGROUND_REPEAT[];
|
||||
|
||||
export const enum BACKGROUND_REPEAT {
|
||||
REPEAT = 0,
|
||||
NO_REPEAT = 1,
|
||||
REPEAT_X = 2,
|
||||
REPEAT_Y = 3
|
||||
}
|
||||
|
||||
export const backgroundRepeat: IPropertyListDescriptor<BackgroundRepeat> = {
|
||||
name: 'background-repeat',
|
||||
initialValue: 'repeat',
|
||||
prefix: false,
|
||||
type: PropertyDescriptorParsingType.LIST,
|
||||
parse: (_context: Context, tokens: CSSValue[]): BackgroundRepeat => {
|
||||
return parseFunctionArgs(tokens)
|
||||
.map((values) =>
|
||||
values
|
||||
.filter(isIdentToken)
|
||||
.map((token) => token.value)
|
||||
.join(' ')
|
||||
)
|
||||
.map(parseBackgroundRepeat);
|
||||
}
|
||||
};
|
||||
|
||||
const parseBackgroundRepeat = (value: string): BACKGROUND_REPEAT => {
|
||||
switch (value) {
|
||||
case 'no-repeat':
|
||||
return BACKGROUND_REPEAT.NO_REPEAT;
|
||||
case 'repeat-x':
|
||||
case 'repeat no-repeat':
|
||||
return BACKGROUND_REPEAT.REPEAT_X;
|
||||
case 'repeat-y':
|
||||
case 'no-repeat repeat':
|
||||
return BACKGROUND_REPEAT.REPEAT_Y;
|
||||
case 'repeat':
|
||||
default:
|
||||
return BACKGROUND_REPEAT.REPEAT;
|
||||
}
|
||||
};
|
27
src/css/property-descriptors/background-size.ts
Normal file
27
src/css/property-descriptors/background-size.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
import {CSSValue, isIdentToken, parseFunctionArgs} from '../syntax/parser';
|
||||
import {isLengthPercentage, LengthPercentage} from '../types/length-percentage';
|
||||
import {StringValueToken} from '../syntax/tokenizer';
|
||||
import {Context} from '../../core/context';
|
||||
|
||||
export enum BACKGROUND_SIZE {
|
||||
AUTO = 'auto',
|
||||
CONTAIN = 'contain',
|
||||
COVER = 'cover'
|
||||
}
|
||||
|
||||
export type BackgroundSizeInfo = LengthPercentage | StringValueToken;
|
||||
export type BackgroundSize = BackgroundSizeInfo[][];
|
||||
|
||||
export const backgroundSize: IPropertyListDescriptor<BackgroundSize> = {
|
||||
name: 'background-size',
|
||||
initialValue: '0',
|
||||
prefix: false,
|
||||
type: PropertyDescriptorParsingType.LIST,
|
||||
parse: (_context: Context, tokens: CSSValue[]): BackgroundSize => {
|
||||
return parseFunctionArgs(tokens).map((values) => values.filter(isBackgroundSizeInfoToken));
|
||||
}
|
||||
};
|
||||
|
||||
const isBackgroundSizeInfoToken = (value: CSSValue): value is BackgroundSizeInfo =>
|
||||
isIdentToken(value) || isLengthPercentage(value);
|
13
src/css/property-descriptors/border-color.ts
Normal file
13
src/css/property-descriptors/border-color.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import {IPropertyTypeValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
const borderColorForSide = (side: string): IPropertyTypeValueDescriptor => ({
|
||||
name: `border-${side}-color`,
|
||||
initialValue: 'transparent',
|
||||
prefix: false,
|
||||
type: PropertyDescriptorParsingType.TYPE_VALUE,
|
||||
format: 'color'
|
||||
});
|
||||
|
||||
export const borderTopColor: IPropertyTypeValueDescriptor = borderColorForSide('top');
|
||||
export const borderRightColor: IPropertyTypeValueDescriptor = borderColorForSide('right');
|
||||
export const borderBottomColor: IPropertyTypeValueDescriptor = borderColorForSide('bottom');
|
||||
export const borderLeftColor: IPropertyTypeValueDescriptor = borderColorForSide('left');
|
19
src/css/property-descriptors/border-radius.ts
Normal file
19
src/css/property-descriptors/border-radius.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
import {CSSValue} from '../syntax/parser';
|
||||
import {isLengthPercentage, LengthPercentageTuple, parseLengthPercentageTuple} from '../types/length-percentage';
|
||||
import {Context} from '../../core/context';
|
||||
export type BorderRadius = LengthPercentageTuple;
|
||||
|
||||
const borderRadiusForSide = (side: string): IPropertyListDescriptor<BorderRadius> => ({
|
||||
name: `border-radius-${side}`,
|
||||
initialValue: '0 0',
|
||||
prefix: false,
|
||||
type: PropertyDescriptorParsingType.LIST,
|
||||
parse: (_context: Context, tokens: CSSValue[]): BorderRadius =>
|
||||
parseLengthPercentageTuple(tokens.filter(isLengthPercentage))
|
||||
});
|
||||
|
||||
export const borderTopLeftRadius: IPropertyListDescriptor<BorderRadius> = borderRadiusForSide('top-left');
|
||||
export const borderTopRightRadius: IPropertyListDescriptor<BorderRadius> = borderRadiusForSide('top-right');
|
||||
export const borderBottomRightRadius: IPropertyListDescriptor<BorderRadius> = borderRadiusForSide('bottom-right');
|
||||
export const borderBottomLeftRadius: IPropertyListDescriptor<BorderRadius> = borderRadiusForSide('bottom-left');
|
34
src/css/property-descriptors/border-style.ts
Normal file
34
src/css/property-descriptors/border-style.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import {IPropertyIdentValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
import {Context} from '../../core/context';
|
||||
export const enum BORDER_STYLE {
|
||||
NONE = 0,
|
||||
SOLID = 1,
|
||||
DASHED = 2,
|
||||
DOTTED = 3,
|
||||
DOUBLE = 4
|
||||
}
|
||||
|
||||
const borderStyleForSide = (side: string): IPropertyIdentValueDescriptor<BORDER_STYLE> => ({
|
||||
name: `border-${side}-style`,
|
||||
initialValue: 'solid',
|
||||
prefix: false,
|
||||
type: PropertyDescriptorParsingType.IDENT_VALUE,
|
||||
parse: (_context: Context, style: string): BORDER_STYLE => {
|
||||
switch (style) {
|
||||
case 'none':
|
||||
return BORDER_STYLE.NONE;
|
||||
case 'dashed':
|
||||
return BORDER_STYLE.DASHED;
|
||||
case 'dotted':
|
||||
return BORDER_STYLE.DOTTED;
|
||||
case 'double':
|
||||
return BORDER_STYLE.DOUBLE;
|
||||
}
|
||||
return BORDER_STYLE.SOLID;
|
||||
}
|
||||
});
|
||||
|
||||
export const borderTopStyle: IPropertyIdentValueDescriptor<BORDER_STYLE> = borderStyleForSide('top');
|
||||
export const borderRightStyle: IPropertyIdentValueDescriptor<BORDER_STYLE> = borderStyleForSide('right');
|
||||
export const borderBottomStyle: IPropertyIdentValueDescriptor<BORDER_STYLE> = borderStyleForSide('bottom');
|
||||
export const borderLeftStyle: IPropertyIdentValueDescriptor<BORDER_STYLE> = borderStyleForSide('left');
|
20
src/css/property-descriptors/border-width.ts
Normal file
20
src/css/property-descriptors/border-width.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {IPropertyValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
import {CSSValue, isDimensionToken} from '../syntax/parser';
|
||||
import {Context} from '../../core/context';
|
||||
const borderWidthForSide = (side: string): IPropertyValueDescriptor<number> => ({
|
||||
name: `border-${side}-width`,
|
||||
initialValue: '0',
|
||||
type: PropertyDescriptorParsingType.VALUE,
|
||||
prefix: false,
|
||||
parse: (_context: Context, token: CSSValue): number => {
|
||||
if (isDimensionToken(token)) {
|
||||
return token.number;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
export const borderTopWidth: IPropertyValueDescriptor<number> = borderWidthForSide('top');
|
||||
export const borderRightWidth: IPropertyValueDescriptor<number> = borderWidthForSide('right');
|
||||
export const borderBottomWidth: IPropertyValueDescriptor<number> = borderWidthForSide('bottom');
|
||||
export const borderLeftWidth: IPropertyValueDescriptor<number> = borderWidthForSide('left');
|
60
src/css/property-descriptors/box-shadow.ts
Normal file
60
src/css/property-descriptors/box-shadow.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import {PropertyDescriptorParsingType, IPropertyListDescriptor} from '../IPropertyDescriptor';
|
||||
import {CSSValue, isIdentWithValue, parseFunctionArgs} from '../syntax/parser';
|
||||
import {ZERO_LENGTH} from '../types/length-percentage';
|
||||
import {color, Color} from '../types/color';
|
||||
import {isLength, Length} from '../types/length';
|
||||
import {Context} from '../../core/context';
|
||||
|
||||
export type BoxShadow = BoxShadowItem[];
|
||||
interface BoxShadowItem {
|
||||
inset: boolean;
|
||||
color: Color;
|
||||
offsetX: Length;
|
||||
offsetY: Length;
|
||||
blur: Length;
|
||||
spread: Length;
|
||||
}
|
||||
|
||||
export const boxShadow: IPropertyListDescriptor<BoxShadow> = {
|
||||
name: 'box-shadow',
|
||||
initialValue: 'none',
|
||||
type: PropertyDescriptorParsingType.LIST,
|
||||
prefix: false,
|
||||
parse: (context: Context, tokens: CSSValue[]): BoxShadow => {
|
||||
if (tokens.length === 1 && isIdentWithValue(tokens[0], 'none')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return parseFunctionArgs(tokens).map((values: CSSValue[]) => {
|
||||
const shadow: BoxShadowItem = {
|
||||
color: 0x000000ff,
|
||||
offsetX: ZERO_LENGTH,
|
||||
offsetY: ZERO_LENGTH,
|
||||
blur: ZERO_LENGTH,
|
||||
spread: ZERO_LENGTH,
|
||||
inset: false
|
||||
};
|
||||
let c = 0;
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const token = values[i];
|
||||
if (isIdentWithValue(token, 'inset')) {
|
||||
shadow.inset = true;
|
||||
} else if (isLength(token)) {
|
||||
if (c === 0) {
|
||||
shadow.offsetX = token;
|
||||
} else if (c === 1) {
|
||||
shadow.offsetY = token;
|
||||
} else if (c === 2) {
|
||||
shadow.blur = token;
|
||||
} else {
|
||||
shadow.spread = token;
|
||||
}
|
||||
c++;
|
||||
} else {
|
||||
shadow.color = color.parse(context, token);
|
||||
}
|
||||
}
|
||||
return shadow;
|
||||
});
|
||||
}
|
||||
};
|
9
src/css/property-descriptors/color.ts
Normal file
9
src/css/property-descriptors/color.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {IPropertyTypeValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
|
||||
export const color: IPropertyTypeValueDescriptor = {
|
||||
name: `color`,
|
||||
initialValue: 'transparent',
|
||||
prefix: false,
|
||||
type: PropertyDescriptorParsingType.TYPE_VALUE,
|
||||
format: 'color'
|
||||
};
|
26
src/css/property-descriptors/content.ts
Normal file
26
src/css/property-descriptors/content.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {TokenType} from '../syntax/tokenizer';
|
||||
import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||
import {CSSValue} from '../syntax/parser';
|
||||
import {Context} from '../../core/context';
|
||||
|
||||
export type Content = CSSValue[];
|
||||
|
||||
export const content: IPropertyListDescriptor<Content> = {
|
||||
name: 'content',
|
||||
initialValue: 'none',
|
||||
type: PropertyDescriptorParsingType.LIST,
|
||||
prefix: false,
|
||||
parse: (_context: Context, tokens: CSSValue[]) => {
|
||||
if (tokens.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const first = tokens[0];
|
||||
|
||||
if (first.type === TokenType.IDENT_TOKEN && first.value === 'none') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user