mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-05 03:50:29 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56eed3db21 | ||
|
|
ce634e174c | ||
|
|
1a55d8d44b | ||
|
|
0a3dd8e48f | ||
|
|
72b70be757 | ||
|
|
f638f8e4db | ||
|
|
24b060a134 | ||
|
|
6dca41aeb4 | ||
|
|
2a8ce12b6e | ||
|
|
d03594a09d | ||
|
|
d4e270a061 | ||
|
|
f0dd9b7153 | ||
|
|
af0d1caaf7 | ||
|
|
d79f875ac3 | ||
|
|
3426da5c10 | ||
|
|
ba5821e43f | ||
|
|
fac51527c8 | ||
|
|
8b80a616a7 | ||
|
|
29d5a3d75e | ||
|
|
cbf7be541b | ||
|
|
6ac0599ce3 | ||
|
|
3ce02c9ef7 | ||
|
|
4e7bf6ec49 | ||
|
|
b0b670d58a | ||
|
|
b0e78f4435 | ||
|
|
8c99f3ab5f | ||
|
|
27dfe79636 | ||
|
|
d8210be3c1 | ||
|
|
1a6b38c077 | ||
|
|
7abb219565 | ||
|
|
1e663cbe6c | ||
|
|
efb8c4fe47 | ||
|
|
9c796a873c | ||
|
|
2f1178ddd0 | ||
|
|
49ab2ef17f | ||
|
|
f7a8a60440 | ||
|
|
4edb1718cc | ||
|
|
5e019de5ae | ||
|
|
57b6fe8b15 | ||
|
|
4d3468c06f | ||
|
|
af92a591b9 | ||
|
|
cd59d087d3 | ||
|
|
dafb6f8e18 | ||
|
|
6f42d75b73 | ||
|
|
6e8e012d3d | ||
|
|
7eec9a0d64 | ||
|
|
4d3e8b964f | ||
|
|
5a5e155cc2 | ||
|
|
5992cae40b | ||
|
|
538e940525 | ||
|
|
853182d6a3 | ||
|
|
2b56c45177 | ||
|
|
3c605eef56 | ||
|
|
4a7dc7675b | ||
|
|
e9b34ec957 | ||
|
|
c62187ee95 | ||
|
|
594fa11677 | ||
|
|
e26b5f6a28 | ||
|
|
463750037c | ||
|
|
fc43b112d8 | ||
|
|
33d15b9890 | ||
|
|
8cfd91ccb3 | ||
|
|
5222f6c0bf | ||
|
|
6314db7b88 | ||
|
|
02113e2579 | ||
|
|
7e2576e3df | ||
|
|
c595c8f12a | ||
|
|
0d430027df | ||
|
|
d6537217e6 | ||
|
|
2dd3dd2d4b | ||
|
|
2f96455896 | ||
|
|
eb6813108a | ||
|
|
448035b77b | ||
|
|
ab8f4c20a8 | ||
|
|
d86de593ca | ||
|
|
9aae81e73c | ||
|
|
007095a6a6 | ||
|
|
2f868d4d0e | ||
|
|
2be3d6858c | ||
|
|
edd959f345 | ||
|
|
b0a186839a | ||
|
|
70dc47d25c | ||
|
|
231c012e99 | ||
|
|
6a75673cbe | ||
|
|
66f3628811 | ||
|
|
4c314b8a13 | ||
|
|
0584def180 | ||
|
|
ccdb4dd8e5 | ||
|
|
733c4f0bf9 | ||
|
|
dde07a28bd | ||
|
|
4bd4ef9a7b | ||
|
|
12885ed914 | ||
|
|
6ce0d69ba5 | ||
|
|
167b7e8f90 | ||
|
|
a25c6511ce | ||
|
|
6d8ef7d694 | ||
|
|
b5a69f9191 | ||
|
|
2651471a84 | ||
|
|
5a6d6bc411 | ||
|
|
872a32929e | ||
|
|
46bf0fbb57 | ||
|
|
94351926ba | ||
|
|
b90d074820 | ||
|
|
3a5f146d1b | ||
|
|
1a41545fff | ||
|
|
4fd0b1d3e6 | ||
|
|
c10511203e | ||
|
|
4d1dc2a838 | ||
|
|
4874f1e8a7 | ||
|
|
2560a31cdd | ||
|
|
15c0435dde | ||
|
|
daacdd2a2e | ||
|
|
060d57639e | ||
|
|
90fe106664 | ||
|
|
daae736907 | ||
|
|
044b2eae99 | ||
|
|
ea3f42db84 | ||
|
|
72d20bba09 | ||
|
|
5a7927572f | ||
|
|
866d40edc8 | ||
|
|
9ae5c93886 | ||
|
|
a44ad4d7d3 | ||
|
|
4c4701ef37 | ||
|
|
fe65e9e999 | ||
|
|
ff6a09ae72 | ||
|
|
81929c7f5e | ||
|
|
2d37ac9f77 | ||
|
|
3aefd7bb06 | ||
|
|
c5ec840d10 | ||
|
|
597e36904e | ||
|
|
e01f80867b | ||
|
|
d69566ebef | ||
|
|
d931a71589 | ||
|
|
2117d70837 | ||
|
|
9e980cdea2 | ||
|
|
31f6f977f9 | ||
|
|
83ff47db9b | ||
|
|
bfc32874ce | ||
|
|
f404ff021a | ||
|
|
4e2d269dc3 | ||
|
|
e95a631909 | ||
|
|
47af6863bd | ||
|
|
9c90d08a14 | ||
|
|
89bde584ec | ||
|
|
a36e2d6ae6 | ||
|
|
f96cd4e516 | ||
|
|
a453313ff3 | ||
|
|
9132dd42b5 | ||
|
|
f35224f16f | ||
|
|
62041f4677 | ||
|
|
4f57cb076f | ||
|
|
627288ca13 | ||
|
|
480acfdf44 | ||
|
|
7888fca6fa | ||
|
|
921a5b4249 | ||
|
|
4bb43fda08 | ||
|
|
acdca59f18 | ||
|
|
829b13f8ff | ||
|
|
4339d21d41 | ||
|
|
1d78b8ec72 | ||
|
|
bf513bbcb9 | ||
|
|
7831571696 | ||
|
|
c449a85745 | ||
|
|
8e606b652c | ||
|
|
3fa731aa6e | ||
|
|
121c3d891a | ||
|
|
32db7cb173 | ||
|
|
1713fb0991 | ||
|
|
47d6871bc5 | ||
|
|
be36588468 | ||
|
|
532ba68f4f | ||
|
|
5bcdd8deeb | ||
|
|
7f6bebf21f | ||
|
|
0f65fdb2f4 | ||
|
|
09d1860396 | ||
|
|
5b4b40fe81 | ||
|
|
6ac503efae | ||
|
|
c72185ed0b | ||
|
|
aee3e0a65a | ||
|
|
5392c1be76 | ||
|
|
356128c60d | ||
|
|
8dc7509ceb | ||
|
|
cba16fe750 | ||
|
|
a55614d5ab | ||
|
|
6b4e9f2a94 | ||
|
|
75e76c9619 | ||
|
|
6529b0e9b7 | ||
|
|
78cb827ec7 | ||
|
|
dbcb9cc153 | ||
|
|
1d2cd3f805 | ||
|
|
053f22a87b | ||
|
|
e8f26c4a70 | ||
|
|
9635c398d6 | ||
|
|
5bf54f1dad | ||
|
|
ee17fa8760 | ||
|
|
61fde80a90 | ||
|
|
ca95925de9 | ||
|
|
706e74f084 | ||
|
|
e1a4a31458 | ||
|
|
866ecfed2d | ||
|
|
10b63b5f89 | ||
|
|
af5b95e93c | ||
|
|
b8f3d4e582 | ||
|
|
3672ecf91d | ||
|
|
7c8c7600ae | ||
|
|
42b2615882 | ||
|
|
02548b80b1 | ||
|
|
2ed0575ca3 | ||
|
|
54778a7e43 | ||
|
|
612654e7d3 | ||
|
|
59de529311 | ||
|
|
e5901f662f | ||
|
|
5a4be8e7f2 | ||
|
|
d9183028d5 | ||
|
|
fae0aa4c04 | ||
|
|
55915a2901 | ||
|
|
f2a8b77733 | ||
|
|
c8d04e51b9 | ||
|
|
b003355a0c | ||
|
|
79ae42d73a | ||
|
|
2835e7e5f9 | ||
|
|
a59a1f41cd | ||
|
|
0447cfc032 | ||
|
|
7070bae927 | ||
|
|
a3e64f6340 | ||
|
|
45496fcd2e | ||
|
|
115bc70ad1 | ||
|
|
e59ce4feac | ||
|
|
ea0a8755bf | ||
|
|
21c924a3e6 | ||
|
|
58cd1a8893 | ||
|
|
57a6422e61 | ||
|
|
0ef279e0da | ||
|
|
8dcd936873 | ||
|
|
5ef0b40dea | ||
|
|
6f02f2d2b7 | ||
|
|
b21f602cca | ||
|
|
b09fc21e66 | ||
|
|
fa6355b98a | ||
|
|
53bcf09566 | ||
|
|
d2dc459ba9 | ||
|
|
71ced499e7 | ||
|
|
994fd81626 | ||
|
|
4487f2c1c6 | ||
|
|
39024e1a63 | ||
|
|
01c6148904 | ||
|
|
fc264f796a | ||
|
|
94c60f01c8 | ||
|
|
ad5811efb0 | ||
|
|
1ada0cee13 | ||
|
|
d7c14f821a | ||
|
|
98371733a6 | ||
|
|
6c53646e94 | ||
|
|
4812d5b389 | ||
|
|
45c941b664 | ||
|
|
e55c2ed405 | ||
|
|
4b875a638c | ||
|
|
b71d6c9b96 | ||
|
|
d63d1683c7 | ||
|
|
0347013df1 | ||
|
|
412f2f1acb | ||
|
|
a471bbcf4d | ||
|
|
e1843e17b3 | ||
|
|
ac3ad68b56 | ||
|
|
745698a5ba | ||
|
|
dc1dbb036c | ||
|
|
175c52bb1f | ||
|
|
1d9b7c2a07 | ||
|
|
1534ce4d85 | ||
|
|
72e8c9f7b3 | ||
|
|
3cab1d37c7 | ||
|
|
14e2c53030 | ||
|
|
65335a2b88 | ||
|
|
0ce043c18e | ||
|
|
58a0053778 | ||
|
|
f24bf4ea98 | ||
|
|
1b76465b21 | ||
|
|
741e4fa7bc | ||
|
|
135027fb23 | ||
|
|
878c63d8ba | ||
|
|
fb53fa0d7e | ||
|
|
661b66b765 | ||
|
|
9215a5a943 | ||
|
|
6b119660b0 | ||
|
|
c6ea2ca595 | ||
|
|
7a6cd61286 | ||
|
|
bef12f1d23 | ||
|
|
dd2ee5d5a1 | ||
|
|
3362b63350 | ||
|
|
21bc0a50f0 | ||
|
|
947ae84da8 | ||
|
|
f5d4bde317 | ||
|
|
973c32e500 | ||
|
|
f169308439 | ||
|
|
8b6da83ba7 | ||
|
|
fc941350b3 | ||
|
|
a45f7fb48e | ||
|
|
5f889bcc0a | ||
|
|
3c668bddb1 | ||
|
|
4bde98f2e4 | ||
|
|
0957319e3d | ||
|
|
f41c51eb61 | ||
|
|
5e6af6c0d9 | ||
|
|
249c8ccdfc | ||
|
|
e6c43d38ca | ||
|
|
0c53a4ace6 | ||
|
|
815bd42229 | ||
|
|
4f66a6a41f | ||
|
|
7a248ba882 | ||
|
|
078525171c | ||
|
|
bbffdad609 | ||
|
|
9f11ffa325 | ||
|
|
847ac79f05 | ||
|
|
48c74de326 | ||
|
|
7e9e8f4afd | ||
|
|
14557c123c | ||
|
|
668ea89a48 | ||
|
|
ea53689bba | ||
|
|
6f5c75b913 | ||
|
|
ef3dab5aeb | ||
|
|
1e9175b5e5 | ||
|
|
7f022ea35d | ||
|
|
0040317e48 | ||
|
|
0c19c0d9a3 | ||
|
|
eff1548ebe | ||
|
|
18cdfac8cd | ||
|
|
7587a5f94a | ||
|
|
ac296cdb7f | ||
|
|
81e82918bf | ||
|
|
e37d2d539e | ||
|
|
54c6091e10 | ||
|
|
b612548b78 | ||
|
|
28b4f3ebc4 | ||
|
|
7527187812 | ||
|
|
b040aef729 | ||
|
|
121de29d80 | ||
|
|
df3567a61f | ||
|
|
af763d73c1 | ||
|
|
b9f128f8c7 | ||
|
|
478ccf3eda | ||
|
|
b4779bb49b | ||
|
|
4e1f8c5a24 | ||
|
|
f4a583aa63 | ||
|
|
8c97768d74 | ||
|
|
9c3f652553 | ||
|
|
a3692db354 | ||
|
|
33d7c6ba52 | ||
|
|
4f9431b23e | ||
|
|
0b458ce8c8 | ||
|
|
7f42080a38 | ||
|
|
a8b2b241d1 | ||
|
|
b9dacf5537 | ||
|
|
d92ee1a003 | ||
|
|
4663ae7834 | ||
|
|
2311125d92 | ||
|
|
2807489f2b | ||
|
|
0ea7c69b25 | ||
|
|
bb433b4afb | ||
|
|
84f13cdb93 | ||
|
|
f4a01abffb | ||
|
|
9bc846249e | ||
|
|
cf8dd5ec2c | ||
|
|
2dd58cc894 | ||
|
|
5934430bfc | ||
|
|
2879557d6c | ||
|
|
c67e639dee | ||
|
|
7e61a543e7 | ||
|
|
9cec7fa459 | ||
|
|
75dad6e75d | ||
|
|
0f9eb1bc0b | ||
|
|
63707f0a18 | ||
|
|
0ac37a87bc | ||
|
|
5cc2ec3416 | ||
|
|
412bfcd96b | ||
|
|
d408149720 | ||
|
|
f393962746 | ||
|
|
f4ab00e195 | ||
|
|
651793bffa | ||
|
|
dc87af90ae | ||
|
|
e72df17c38 | ||
|
|
51fab1cbf8 | ||
|
|
1b3df67c47 | ||
|
|
095d68df83 | ||
|
|
5c956a5b15 | ||
|
|
d2e3dc1d89 | ||
|
|
934610522f | ||
|
|
ad4cacd108 | ||
|
|
a55a69487e | ||
|
|
d17969a50d | ||
|
|
ac20897e2a | ||
|
|
c745d55d32 | ||
|
|
2ff4272133 | ||
|
|
1ffe63f9ed | ||
|
|
c80b5e24c6 | ||
|
|
a8c49e1cc4 | ||
|
|
bfe4e5b663 | ||
|
|
2f0d48c01b | ||
|
|
816179edef | ||
|
|
b1a9715c52 | ||
|
|
23602bc8db | ||
|
|
00aef19114 | ||
|
|
38c42fd197 | ||
|
|
4efab56e48 | ||
|
|
12677f1647 | ||
|
|
ffddca2a7d | ||
|
|
6920c999c8 | ||
|
|
fa8bd1c525 | ||
|
|
afa18931c6 | ||
|
|
0ed5e958b1 | ||
|
|
5fb6b416f3 | ||
|
|
ec178bd6d9 | ||
|
|
5b2d764a10 | ||
|
|
e64595a42f | ||
|
|
bd8ebfebcb | ||
|
|
ed6e6c01be | ||
|
|
635f031e2c | ||
|
|
d3d8eeb4ae | ||
|
|
e60dcc5ad6 | ||
|
|
7d66f3d4e5 | ||
|
|
ecfa571215 | ||
|
|
046a421591 | ||
|
|
1cbb8e487b | ||
|
|
bd654a8bb6 | ||
|
|
9d0b68e18c | ||
|
|
a1fc2fc3c6 | ||
|
|
fefc433e0b | ||
|
|
d1bac8675b | ||
|
|
2d64ce330a | ||
|
|
4a0a985ed6 | ||
|
|
54ed64d326 | ||
|
|
4eb4efffa2 | ||
|
|
2e8cd3fafd | ||
|
|
c4f389055a | ||
|
|
882f135abc | ||
|
|
e22229475a | ||
|
|
7e7409de08 | ||
|
|
1d747ffcef | ||
|
|
b19c68c284 | ||
|
|
3673099646 | ||
|
|
2bdacb7507 | ||
|
|
a617279c2d | ||
|
|
245b48464b | ||
|
|
6c1962587a | ||
|
|
84b70e5048 | ||
|
|
ba179c4b57 | ||
|
|
9a7cf1636b | ||
|
|
cfea5e734a | ||
|
|
59ebb4d358 | ||
|
|
d133f72442 | ||
|
|
a55595e818 | ||
|
|
1435f77d89 | ||
|
|
c789fd8afb | ||
|
|
dd36fdde7a | ||
|
|
86c8086979 | ||
|
|
a90f74c719 | ||
|
|
2cc248c81b | ||
|
|
0884de48cd | ||
|
|
bd44108296 | ||
|
|
9278774b8a | ||
|
|
9d362b1cf8 | ||
|
|
816dc311ae | ||
|
|
1dbf907eeb | ||
|
|
6a8d8cf268 | ||
|
|
f951cad66c | ||
|
|
f88f872162 | ||
|
|
3f5298d1e7 | ||
|
|
a5c853891f | ||
|
|
850eaffc92 | ||
|
|
6e227a3aa4 | ||
|
|
e3216dddc5 | ||
|
|
3ca19af354 | ||
|
|
b732ee7925 | ||
|
|
6d19c5f2e4 | ||
|
|
0bf33ccd5b | ||
|
|
a9e3128347 | ||
|
|
49df18f07c | ||
|
|
5d74e8c3ac | ||
|
|
d2bb7a6727 | ||
|
|
3a985f745a | ||
|
|
ac55a42cf3 | ||
|
|
252ebf962d | ||
|
|
013cc175a6 | ||
|
|
5119fd08f9 | ||
|
|
db3ffd3030 | ||
|
|
37b5956bd5 | ||
|
|
b4a2c98497 | ||
|
|
1d986aedb1 | ||
|
|
3d82955b81 | ||
|
|
57b143cb9c | ||
|
|
59d07a1444 | ||
|
|
56801bd26e | ||
|
|
5e5975791f | ||
|
|
01dc3005d7 | ||
|
|
23b072ea10 | ||
|
|
8bf52b9ef1 | ||
|
|
6c0e9c7232 | ||
|
|
477839f106 | ||
|
|
0a718dac8f | ||
|
|
969b74dfb5 | ||
|
|
1569b66d5c | ||
|
|
31f43b557f | ||
|
|
dae6f1b15f | ||
|
|
b107acb00c | ||
|
|
47c1be6df1 | ||
|
|
11368314dd | ||
|
|
70f0834fc0 | ||
|
|
dfd0ebcfc5 | ||
|
|
f39b54a8ca | ||
|
|
16bc667daf | ||
|
|
3a9fc44355 | ||
|
|
db38061ad9 | ||
|
|
a95ce74b58 | ||
|
|
2de36b6605 | ||
|
|
3b45de70a6 | ||
|
|
da9e7a8b11 | ||
|
|
6cb614fadb | ||
|
|
e4b6ae32ed | ||
|
|
2403e13d26 | ||
|
|
3ecdcb13e8 | ||
|
|
88eac45452 | ||
|
|
3db95665e5 | ||
|
|
767028a654 | ||
|
|
cb4db2d762 | ||
|
|
03ecaf8603 | ||
|
|
f2aabab68a | ||
|
|
77da6d7a7d | ||
|
|
000ef5bef9 | ||
|
|
58c78b09d2 | ||
|
|
d7e06ce361 | ||
|
|
9f09a01c08 | ||
|
|
e117ab93bd | ||
|
|
e55c2f6b6a | ||
|
|
e477689b1f | ||
|
|
a6274a6a02 | ||
|
|
6b7eeea51a | ||
|
|
272a8cd038 | ||
|
|
18b469a334 | ||
|
|
ff4e6672f1 | ||
|
|
ea5db9d22d | ||
|
|
2ede256de2 | ||
|
|
571ce07af6 | ||
|
|
8d26378611 | ||
|
|
b60c254c74 | ||
|
|
37d95e249d | ||
|
|
01c4d3bd7c | ||
|
|
38596dc47c | ||
|
|
d5244649a3 | ||
|
|
4f86960265 | ||
|
|
67e3887165 | ||
|
|
f1b03d076c | ||
|
|
667385ee1c | ||
|
|
b479abf6cf | ||
|
|
e21ee9605b | ||
|
|
3093dcbf4a | ||
|
|
b6105408e0 | ||
|
|
5232102ac0 | ||
|
|
89d7cd8e3d | ||
|
|
d2a1450bc0 | ||
|
|
0fe94795cc | ||
|
|
30fe0b52f1 | ||
|
|
92d5e40ac3 | ||
|
|
7c09768109 | ||
|
|
3748463d38 | ||
|
|
65f99f0646 | ||
|
|
22ff7f435c | ||
|
|
1625ba0f45 | ||
|
|
7d614a6ca7 | ||
|
|
e474d11730 | ||
|
|
3d8a0866a9 | ||
|
|
f0c163f475 | ||
|
|
4df7d797c4 | ||
|
|
f57ea089e9 | ||
|
|
45df666152 | ||
|
|
1d68691331 | ||
|
|
86cbaf6cd3 | ||
|
|
73795dbe13 | ||
|
|
255d8402c2 | ||
|
|
ded9809e4c | ||
|
|
5028e31e8a | ||
|
|
0cf546ab51 | ||
|
|
ec96caf1a4 | ||
|
|
1401b9ee15 | ||
|
|
0ebfaa48a3 | ||
|
|
ad131db536 | ||
|
|
0d9ffe2997 | ||
|
|
d32251014e | ||
|
|
c2147a2f20 | ||
|
|
90998edb25 | ||
|
|
84848ab902 | ||
|
|
2aaea50031 | ||
|
|
5fbf48601e | ||
|
|
4cec7bbe58 | ||
|
|
e505c70b2f | ||
|
|
841428f2e2 | ||
|
|
957b78fafd | ||
|
|
34f12655c1 | ||
|
|
74d7af283e | ||
|
|
5ef73edea2 | ||
|
|
9b099c6a6d | ||
|
|
146e3f07d4 | ||
|
|
e8563a1392 | ||
|
|
d66287a55b | ||
|
|
0c67620b6a | ||
|
|
0695361469 | ||
|
|
88561b2fcb | ||
|
|
9686c38d7f | ||
|
|
5b7f1fc85f | ||
|
|
2c6d36037a | ||
|
|
98c65d2b45 | ||
|
|
1e7ef30fac | ||
|
|
0eb7dc7114 | ||
|
|
9e96abfbb1 | ||
|
|
9220507f63 | ||
|
|
0882bc9bc0 | ||
|
|
85d90c3382 | ||
|
|
6251b25195 | ||
|
|
88260c26ab | ||
|
|
582e5bf9cb | ||
|
|
6e47777c15 | ||
|
|
2e64e8e8d4 | ||
|
|
93d012c92a | ||
|
|
f5953d8c87 | ||
|
|
64153e698b | ||
|
|
518c18664d | ||
|
|
dbbad3f5d9 | ||
|
|
3e38e5b175 | ||
|
|
3fb779b231 | ||
|
|
988949dbf2 | ||
|
|
4f7f477898 | ||
|
|
c94d15f305 | ||
|
|
9283e89f19 | ||
|
|
a386fd26fc | ||
|
|
32ba24782f | ||
|
|
a119837c8a | ||
|
|
b9499b228a | ||
|
|
3cae20a10c | ||
|
|
f7a1eef5ec | ||
|
|
7351cb2e32 | ||
|
|
7313fa08a8 | ||
|
|
1174a6dbaa | ||
|
|
c37218b86d | ||
|
|
efa91e7a72 | ||
|
|
c8391d5886 | ||
|
|
43d747b3c5 | ||
|
|
e6c8eaf23c | ||
|
|
832e28aaca | ||
|
|
404dd544ab | ||
|
|
87d37a8be4 | ||
|
|
561ac12304 | ||
|
|
450815db96 | ||
|
|
eea81e7fd6 | ||
|
|
dffcfb77f3 | ||
|
|
b4ff2f68f8 | ||
|
|
1c7cc36472 | ||
|
|
b42d991157 | ||
|
|
f129b732ae | ||
|
|
179de7ef83 | ||
|
|
dc0f3b30ad | ||
|
|
3cf435d59d | ||
|
|
75b1d67fbd | ||
|
|
d1d354359c | ||
|
|
a9e007af66 | ||
|
|
f19e901a65 | ||
|
|
45be8b832d | ||
|
|
d36c1f4ab9 | ||
|
|
49cccf95b4 | ||
|
|
271279505b | ||
|
|
7ab602a05f | ||
|
|
04587be28b | ||
|
|
5d8b83e105 | ||
|
|
36ebb3b429 | ||
|
|
174eb0631e | ||
|
|
bd957717a2 | ||
|
|
56f1b46389 | ||
|
|
db72cf6d6b | ||
|
|
13484e3e44 | ||
|
|
abba411cf0 | ||
|
|
86eaf1f045 | ||
|
|
2e50d4f717 | ||
|
|
a491d359e3 | ||
|
|
888ef253e7 | ||
|
|
dc86c780d6 | ||
|
|
92636f33c5 | ||
|
|
943abdd77a | ||
|
|
9b1fd41cf1 | ||
|
|
f98f77281e | ||
|
|
655ac7ed08 | ||
|
|
220d5d59c3 | ||
|
|
3a6ce7b25e | ||
|
|
4fe8ba6110 | ||
|
|
7986107b57 | ||
|
|
073e6b12bf | ||
|
|
93e26ea178 | ||
|
|
f9ddd51eb1 | ||
|
|
70f31aa90e | ||
|
|
612f6f9a2d | ||
|
|
200359d2e2 | ||
|
|
3fe1069e2f | ||
|
|
6eb6d879f3 | ||
|
|
55afb2f61e | ||
|
|
325519b9f7 | ||
|
|
a929524f09 | ||
|
|
5c1b00e9d1 | ||
|
|
e4a477567b | ||
|
|
50e31b922b | ||
|
|
b616ec0fbf | ||
|
|
5e66d42f59 | ||
|
|
75df8a48cb | ||
|
|
fa10e97a00 | ||
|
|
981aff3c5a | ||
|
|
a85f21dcb5 | ||
|
|
eb60bbe771 | ||
|
|
3fbb97d24b | ||
|
|
25687fa049 | ||
|
|
9c57adf634 | ||
|
|
9e1aeb178a | ||
|
|
09d3afa1da | ||
|
|
a14c046b89 | ||
|
|
4fb3b0ec1c | ||
|
|
73a8156c80 | ||
|
|
0c1e89f6f4 | ||
|
|
2c85ca74e9 | ||
|
|
f6d432eaa7 | ||
|
|
627e932441 | ||
|
|
febf664f74 | ||
|
|
544bc2f0b6 | ||
|
|
b745831526 | ||
|
|
c010327daa | ||
|
|
c2520f7736 | ||
|
|
62c373d76a | ||
|
|
2d99a28b07 | ||
|
|
adc511193d | ||
|
|
4825997299 | ||
|
|
e0f5e71a55 | ||
|
|
36b98eb1af | ||
|
|
80e504032c | ||
|
|
b059ac6ada | ||
|
|
a6dcca0313 | ||
|
|
eb9ff4a1a0 | ||
|
|
0bad67a40f | ||
|
|
53a03599e4 | ||
|
|
21b9b58610 | ||
|
|
bf6e212a2d | ||
|
|
23e40f9ef9 | ||
|
|
fb8c8c07ae | ||
|
|
67b17138f0 | ||
|
|
3f585e2676 | ||
|
|
b9b98294b5 | ||
|
|
180befda84 | ||
|
|
704ad60b26 | ||
|
|
359dd48b2a | ||
|
|
61390d97fe | ||
|
|
b8dcf39772 | ||
|
|
da115e97c2 | ||
|
|
5f49cac1b2 | ||
|
|
4a23dabd52 | ||
|
|
8f628e2b5a | ||
|
|
7787e1d9ac | ||
|
|
21c928365a | ||
|
|
531e1ad1c6 | ||
|
|
abf7ae8a00 | ||
|
|
90346d7ea9 | ||
|
|
59584813e4 | ||
|
|
7eeae0bda5 | ||
|
|
4db4c6c719 | ||
|
|
b57c9a8575 | ||
|
|
446b399be2 | ||
|
|
d9045efcb0 | ||
|
|
dfd56c12d5 | ||
|
|
8337c8b45b | ||
|
|
e46f612d4e | ||
|
|
bc3de197c8 | ||
|
|
cf61212515 | ||
|
|
5e9ac1c492 | ||
|
|
2910a3f72d | ||
|
|
50f62a815c | ||
|
|
ac8ea1311d | ||
|
|
ec830880e7 | ||
|
|
584acc68d0 | ||
|
|
41970ec330 | ||
|
|
d7fce5987d | ||
|
|
3137b0e2c4 | ||
|
|
1ac0f751e8 | ||
|
|
01f5aca5aa | ||
|
|
c5799954dc | ||
|
|
1f7238d0b5 | ||
|
|
1fc962082a | ||
|
|
cc891f2b0a | ||
|
|
280348763d | ||
|
|
4a4a47158f | ||
|
|
c58fcec497 | ||
|
|
11ea5d21c5 | ||
|
|
ad28b5450a | ||
|
|
7907f20907 | ||
|
|
e44f4960b6 | ||
|
|
2ee1dbf542 | ||
|
|
5f8e94c41f | ||
|
|
e399315798 | ||
|
|
4cec0e2be4 | ||
|
|
b51d611c8a | ||
|
|
e34b106e10 | ||
|
|
0491c1118e | ||
|
|
809508b492 | ||
|
|
38713ef563 | ||
|
|
3d0b829af8 | ||
|
|
6f47b3f16f | ||
|
|
8332616346 | ||
|
|
a347cbda3d | ||
|
|
c9b9482cb6 | ||
|
|
d52d2e3647 | ||
|
|
8a96426254 | ||
|
|
6e865efb2a | ||
|
|
534eb01010 | ||
|
|
05110c7f6a | ||
|
|
94504d4b75 | ||
|
|
d9561d79b9 | ||
|
|
771de32cf0 | ||
|
|
d694a674ac | ||
|
|
0914acf46e | ||
|
|
f8b8744b7b | ||
|
|
d6e7a89e9f | ||
|
|
444be0587b | ||
|
|
5b7319b520 | ||
|
|
55f4beb86d | ||
|
|
47c70b43e5 | ||
|
|
c6e9e29e84 | ||
|
|
f39a29e2cc | ||
|
|
2b0ddfebfe | ||
|
|
bfcf1f5fc3 | ||
|
|
528b14f604 | ||
|
|
14afebb5f0 | ||
|
|
bddd11c07b | ||
|
|
bb42a5aec0 | ||
|
|
c6fb46cbf2 | ||
|
|
2f8086ad59 | ||
|
|
659a4b1a32 | ||
|
|
13221f8bee | ||
|
|
f22488120b | ||
|
|
34d65300d7 | ||
|
|
7b09956493 | ||
|
|
5e488ac071 | ||
|
|
b1116b01c9 | ||
|
|
4e5d747cac | ||
|
|
1ab535f873 | ||
|
|
12c61a2d53 | ||
|
|
a77cd6380c | ||
|
|
578f569c0a | ||
|
|
183c1f0d7a | ||
|
|
8bae2b662a | ||
|
|
b4c77acee7 | ||
|
|
c9aa895894 | ||
|
|
704045b024 | ||
|
|
ac0094368b | ||
|
|
c4a67b35c7 | ||
|
|
bd6fd53d8b | ||
|
|
665275be1e | ||
|
|
210af94722 | ||
|
|
1aae928689 | ||
|
|
c32fed3add | ||
|
|
fe32de047e | ||
|
|
cd3529bf7e | ||
|
|
9e99cf6b77 | ||
|
|
7f5dafcd2f | ||
|
|
be59c2b8ea | ||
|
|
33e558b290 | ||
|
|
68343a4c9a | ||
|
|
b2e6978649 | ||
|
|
a25ec05e17 | ||
|
|
3d59dea821 | ||
|
|
35e7281bc3 | ||
|
|
1637ed6b44 | ||
|
|
ac32e09fe9 | ||
|
|
650ff1fca5 | ||
|
|
09c554a3e6 | ||
|
|
9602fef1e8 | ||
|
|
cc926d0b07 | ||
|
|
d2769a02f5 | ||
|
|
3fcf9757f7 | ||
|
|
59510899bb | ||
|
|
f390fc92dd | ||
|
|
2a45e26efe | ||
|
|
33ebcf4479 | ||
|
|
30b16a6bf1 | ||
|
|
b29395a011 | ||
|
|
e5cd2e6ab5 |
@@ -0,0 +1,2 @@
|
||||
# Dev-only: route TSA/certificate requests through the built-in Vite CORS proxy
|
||||
VITE_CORS_PROXY_URL=/cors-proxy
|
||||
@@ -0,0 +1,33 @@
|
||||
# BentoPDF Environment Variables
|
||||
# Copy this file to .env.production and configure as needed.
|
||||
|
||||
# CORS Proxy for digital signature certificate chain fetching
|
||||
VITE_CORS_PROXY_URL=
|
||||
VITE_CORS_PROXY_SECRET=
|
||||
|
||||
# WASM Module URLs
|
||||
# Pre-configured defaults enable advanced PDF features out of the box.
|
||||
# For air-gapped / offline deployments, point these to your internal server (e.g., /wasm/pymupdf/).
|
||||
VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/[email protected]/
|
||||
VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/[email protected]/assets/
|
||||
VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/[email protected]/dist/
|
||||
|
||||
# OCR assets (optional)
|
||||
# Set all three together for self-hosted or air-gapped OCR.
|
||||
# Leave empty to use Tesseract.js runtime defaults.
|
||||
VITE_TESSERACT_WORKER_URL=
|
||||
VITE_TESSERACT_CORE_URL=
|
||||
VITE_TESSERACT_LANG_URL=
|
||||
VITE_TESSERACT_AVAILABLE_LANGUAGES=
|
||||
VITE_OCR_FONT_BASE_URL=
|
||||
|
||||
# Default UI language (build-time)
|
||||
# Supported: en, ar, be, fr, de, es, zh, zh-TW, vi, tr, id, it, pt, nl, da, ko, sv, ru
|
||||
VITE_DEFAULT_LANGUAGE=
|
||||
|
||||
# Custom branding (build-time)
|
||||
# Replace the default BentoPDF branding with your own.
|
||||
# Place your logo file in the public/ folder and set the path relative to it.
|
||||
VITE_BRAND_NAME=
|
||||
VITE_BRAND_LOGO=
|
||||
VITE_FOOTER_TEXT=
|
||||
@@ -1,80 +0,0 @@
|
||||
---
|
||||
name: '🐛 Bug / 💡 Feature / ❓ Question'
|
||||
about: 'Report a bug, request a feature, or ask a question about BentoPDF'
|
||||
title: '(Bug) <short title>, (Feature) <short title>, or (Question) <short title>'
|
||||
labels: ['needs triage']
|
||||
assignees: []
|
||||
---
|
||||
|
||||
## Type of Issue
|
||||
|
||||
Please check one:
|
||||
|
||||
- [ ] 🐛 Bug Report <!-- Label: bug -->
|
||||
- [ ] 💡 Feature Request <!-- Label: feature -->
|
||||
- [ ] ❓ Question / Help <!-- Label: question -->
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Provide a clear and concise description of the issue, feature request, or question.
|
||||
|
||||
---
|
||||
|
||||
## Steps to Reproduce (for Bugs)
|
||||
|
||||
1. Go to '...'
|
||||
2. Run '...'
|
||||
3. Observe error: '...'
|
||||
|
||||
**Expected Behavior:**
|
||||
Describe what you expected BentoPDF to do.
|
||||
|
||||
**Actual Behavior:**
|
||||
Describe what actually happened, including error messages.
|
||||
|
||||
---
|
||||
|
||||
## Feature Request Details (if applicable)
|
||||
|
||||
- What functionality are you requesting?
|
||||
- Why is this useful?
|
||||
- Any example or context to illustrate it?
|
||||
|
||||
---
|
||||
|
||||
## Question Details (if applicable)
|
||||
|
||||
- What is your question?
|
||||
- What have you tried so far?
|
||||
- Any relevant code snippet or scenario?
|
||||
|
||||
---
|
||||
|
||||
## Screenshots / Logs (if applicable)
|
||||
|
||||
Attach any screenshots, logs, or stack traces that help explain the problem or question.
|
||||
|
||||
---
|
||||
|
||||
## Environment
|
||||
|
||||
- **OS:** (e.g., macOS 14.0 / Ubuntu 22.04 / Windows 11)
|
||||
- **Dependencies / setup details (if any):**
|
||||
|
||||
---
|
||||
|
||||
## 💭 Additional Context
|
||||
|
||||
Any other information, suggestions, or references that might help maintainers.
|
||||
|
||||
---
|
||||
|
||||
✅ **Title Format Reminder:**
|
||||
|
||||
- `(Bug) Text alignment incorrect on multi-line paragraphs`
|
||||
- `(Feature) Add support for custom PDF metadata`
|
||||
- `(Question) How to embed custom fonts?`
|
||||
|
||||
---
|
||||
@@ -0,0 +1,122 @@
|
||||
name: 🐛 Bug Report
|
||||
description: Report a bug in BentoPDF
|
||||
title: "(Bug) "
|
||||
labels: ["bug", "needs triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## ⚠️ Important Notice
|
||||
**Bug reports without logs or a sample file demonstrating the issue will not be investigated.**
|
||||
Please help us help you by providing the information needed to reproduce and fix the problem.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Provide a clear and concise description of the bug.
|
||||
placeholder: What happened? What did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: How can we reproduce this issue?
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Upload file '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: console-logs
|
||||
attributes:
|
||||
label: Console Logs
|
||||
description: Open browser DevTools (F12 → Console tab) and paste any errors here.
|
||||
placeholder: Paste console logs here...
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: sample-file
|
||||
attributes:
|
||||
label: Sample PDF or File
|
||||
description: |
|
||||
Attach a sample PDF that reproduces the issue, or describe how to create one.
|
||||
If you cannot share the original, create a minimal example that shows the problem.
|
||||
placeholder: Drag and drop your file here, or describe how to reproduce with any PDF...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: browser
|
||||
attributes:
|
||||
label: Browser
|
||||
description: Which browser are you using?
|
||||
options:
|
||||
- Chrome
|
||||
- Firefox
|
||||
- Safari
|
||||
- Edge
|
||||
- Brave
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: browser-version
|
||||
attributes:
|
||||
label: Browser Version
|
||||
description: e.g., Chrome 120, Firefox 121
|
||||
placeholder: "120"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
options:
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux
|
||||
- iOS
|
||||
- Android
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: bentopdf-version
|
||||
attributes:
|
||||
label: BentoPDF Version
|
||||
description: Check the footer or package.json
|
||||
placeholder: "1.15.4"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Any other information that might help us debug this issue.
|
||||
placeholder: Screenshots, network errors, stack traces, etc.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
options:
|
||||
- label: I have included console logs from the browser DevTools
|
||||
required: true
|
||||
- label: I have attached a sample file or described how to reproduce the issue
|
||||
required: true
|
||||
- label: I have searched existing issues to ensure this is not a duplicate
|
||||
required: true
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💬 Discord Community
|
||||
url: https://discord.gg/Bgq3Ay3f2w
|
||||
about: Join our Discord for quick questions and community support
|
||||
- name: 📖 Documentation
|
||||
url: https://github.com/nicholaschen09/BentoPDF#readme
|
||||
about: Check the README for setup and usage instructions
|
||||
@@ -0,0 +1,39 @@
|
||||
name: 💡 Feature Request
|
||||
description: Suggest a new feature for BentoPDF
|
||||
title: "(Feature) "
|
||||
labels: ["enhancement", "needs triage"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Feature Description
|
||||
description: What functionality are you requesting?
|
||||
placeholder: Describe the feature you'd like to see...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: use-case
|
||||
attributes:
|
||||
label: Use Case
|
||||
description: Why is this feature useful? What problem does it solve?
|
||||
placeholder: Explain why you need this feature...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: Examples
|
||||
description: Any examples, mockups, or references to illustrate the feature?
|
||||
placeholder: Links to similar features, screenshots, etc.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Any other information about the feature request.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,30 @@
|
||||
name: ❓ Question
|
||||
description: Ask a question about BentoPDF
|
||||
title: "(Question) "
|
||||
labels: ["question"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: Question
|
||||
description: What would you like to know?
|
||||
placeholder: Your question here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: tried
|
||||
attributes:
|
||||
label: What have you tried?
|
||||
description: What solutions have you already attempted?
|
||||
placeholder: Describe what you've tried so far...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Any relevant code snippets, screenshots, or scenarios.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,28 @@
|
||||
name: BentoPDF CodeQL config
|
||||
|
||||
paths-ignore:
|
||||
- dist
|
||||
- dist-test
|
||||
- coverage
|
||||
- node_modules
|
||||
- vendor
|
||||
- bentopdf-pymupdf-wasm
|
||||
- libreoffice-wasm-package
|
||||
- bentopdf-airgap-bundle
|
||||
- public/pdfjs-viewer
|
||||
- public/pdfjs-annotation-viewer
|
||||
- public/libreoffice-wasm
|
||||
- public/coherentpdf.browser.min.js
|
||||
- public/workers
|
||||
- public/embedpdf
|
||||
- docs/.vitepress
|
||||
- '**/*.min.js'
|
||||
- '**/*.d.ts'
|
||||
|
||||
query-filters:
|
||||
- exclude:
|
||||
id: js/log-injection
|
||||
- exclude:
|
||||
id: js/tainted-format-string
|
||||
- exclude:
|
||||
id: js/file-system-race
|
||||
@@ -13,3 +13,8 @@ updates:
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
|
||||
- package-ecosystem: 'docker'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
|
||||
@@ -12,9 +12,11 @@ jobs:
|
||||
# New job to build dist and create release
|
||||
build-and-release:
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
if: github.repository == 'alam00000/bentopdf' && startsWith(github.ref, 'refs/tags/')
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
HUSKY: 0
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -27,6 +29,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test:run
|
||||
|
||||
- name: Build distribution
|
||||
run: npm run build
|
||||
|
||||
@@ -40,19 +45,23 @@ jobs:
|
||||
run: npm run package
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
||||
with:
|
||||
files: dist-${{ steps.version.outputs.version }}.zip
|
||||
files: |
|
||||
dist-${{ steps.version.outputs.version }}.zip
|
||||
dist-simple-${{ steps.version.outputs.version }}.zip
|
||||
generate_release_notes: true
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
docker-build-and-push:
|
||||
# Build each platform natively in parallel, then merge manifests
|
||||
build-amd64:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'alam00000/bentopdf'
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -67,26 +76,17 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version and determine release type
|
||||
- name: Extract version
|
||||
id: version
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
@@ -101,38 +101,227 @@ jobs:
|
||||
echo "is_release=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# Build and push for releases (with 'latest' tag)
|
||||
- name: Build and push ${{ matrix.mode.name }} image (release)
|
||||
- name: Build and push amd64 ${{ matrix.mode.name }} (release)
|
||||
if: steps.version.outputs.is_release == 'true'
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||
with:
|
||||
push: true
|
||||
build-args: |
|
||||
SIMPLE_MODE=${{ matrix.mode.simple_mode }}
|
||||
tags: |
|
||||
bentopdf/bentopdf${{ matrix.mode.suffix }}:latest
|
||||
bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }}
|
||||
bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:latest
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }}
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64
|
||||
platforms: linux/amd64
|
||||
no-cache: true
|
||||
|
||||
# Build and push for main branch (with 'edge' tag)
|
||||
- name: Build and push ${{ matrix.mode.name }} image (edge)
|
||||
- name: Build and push amd64 ${{ matrix.mode.name }} (edge)
|
||||
if: steps.version.outputs.is_release == 'false'
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||
with:
|
||||
push: true
|
||||
build-args: |
|
||||
SIMPLE_MODE=${{ matrix.mode.simple_mode }}
|
||||
tags: |
|
||||
bentopdf/bentopdf${{ matrix.mode.suffix }}:edge
|
||||
bentopdf/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }}
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64
|
||||
platforms: linux/amd64
|
||||
cache-from: type=gha,scope=amd64-${{ matrix.mode.name }}
|
||||
cache-to: type=gha,mode=max,scope=amd64-${{ matrix.mode.name }}
|
||||
|
||||
build-arm64:
|
||||
runs-on: ubuntu-24.04-arm # Native ARM64 runner
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
mode:
|
||||
- name: default
|
||||
simple_mode: false
|
||||
suffix: ""
|
||||
- name: simple
|
||||
simple_mode: true
|
||||
suffix: "-simple"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version
|
||||
id: version
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
|
||||
echo "is_release=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
SHORT_SHA=${GITHUB_SHA::7}
|
||||
echo "version=edge" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||
echo "is_release=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build and push arm64 ${{ matrix.mode.name }} (release)
|
||||
if: steps.version.outputs.is_release == 'true'
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||
with:
|
||||
push: true
|
||||
build-args: |
|
||||
SIMPLE_MODE=${{ matrix.mode.simple_mode }}
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
|
||||
platforms: linux/arm64
|
||||
no-cache: true
|
||||
|
||||
- name: Build and push arm64 ${{ matrix.mode.name }} (edge)
|
||||
if: steps.version.outputs.is_release == 'false'
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||
with:
|
||||
push: true
|
||||
build-args: |
|
||||
SIMPLE_MODE=${{ matrix.mode.simple_mode }}
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64
|
||||
platforms: linux/arm64
|
||||
cache-from: type=gha,scope=arm64-${{ matrix.mode.name }}
|
||||
cache-to: type=gha,mode=max,scope=arm64-${{ matrix.mode.name }}
|
||||
|
||||
# Merge GHCR manifests after both platforms are built
|
||||
merge-manifests-ghcr:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-amd64, build-arm64]
|
||||
permissions:
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
mode:
|
||||
- name: default
|
||||
suffix: ""
|
||||
- name: simple
|
||||
suffix: "-simple"
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version
|
||||
id: version
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
|
||||
echo "is_release=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
SHORT_SHA=${GITHUB_SHA::7}
|
||||
echo "version=edge" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||
echo "is_release=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create and push GHCR manifest (release)
|
||||
if: steps.version.outputs.is_release == 'true'
|
||||
run: |
|
||||
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:latest \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
|
||||
|
||||
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }} \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
|
||||
|
||||
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }} \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
|
||||
|
||||
- name: Create and push GHCR manifest (edge)
|
||||
if: steps.version.outputs.is_release == 'false'
|
||||
run: |
|
||||
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64
|
||||
|
||||
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64
|
||||
|
||||
# Copy images from GHCR to DockerHub
|
||||
push-to-dockerhub:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [merge-manifests-ghcr]
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
strategy:
|
||||
matrix:
|
||||
mode:
|
||||
- name: default
|
||||
suffix: ""
|
||||
- name: simple
|
||||
suffix: "-simple"
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Extract version
|
||||
id: version
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
|
||||
echo "is_release=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
SHORT_SHA=${GITHUB_SHA::7}
|
||||
echo "version=edge" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||
echo "is_release=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Copy images to DockerHub (release)
|
||||
if: steps.version.outputs.is_release == 'true'
|
||||
run: |
|
||||
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:latest \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
|
||||
|
||||
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }} \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
|
||||
|
||||
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }} \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
|
||||
|
||||
- name: Copy images to DockerHub (edge)
|
||||
if: steps.version.outputs.is_release == 'false'
|
||||
run: |
|
||||
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:edge \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64
|
||||
|
||||
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64
|
||||
|
||||
@@ -11,7 +11,6 @@ permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
cla-check:
|
||||
@@ -28,16 +27,15 @@ jobs:
|
||||
)
|
||||
steps:
|
||||
- name: "CLA Assistant"
|
||||
uses: cla-assistant/github-action@v2.5.0
|
||||
uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_ASSISTANT_TOKEN }}
|
||||
with:
|
||||
path-to-signatures: 'signatures/cla.json'
|
||||
path-to-document: 'https://github.com/${{ github.repository }}/blob/main/ICLA.md'
|
||||
|
||||
# Branch to store signatures
|
||||
branch: 'main'
|
||||
branch: 'cla-signatures'
|
||||
|
||||
# Allowlist for bot accounts (won't need to sign CLA)
|
||||
allowlist: 'bot*,*[bot],dependabot*,renovate*,github-actions*'
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 6 * * 1'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
actions: read
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [javascript-typescript]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: security-extended
|
||||
config-file: ./.github/codeql-config.yml
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Update Docker Downloads Badge
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 4 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: docker-downloads-badge
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
update:
|
||||
if: github.repository == 'alam00000/bentopdf'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Sum GHCR download counts
|
||||
id: counts
|
||||
run: |
|
||||
set -euo pipefail
|
||||
total=0
|
||||
for pkg in bentopdf bentopdf-simple; do
|
||||
raw=$(curl -sf --retry 3 --retry-delay 5 --max-time 30 \
|
||||
"https://ghcr-badge.elias.eu.org/api/alam00000/bentopdf/$pkg" \
|
||||
| jq -e -r '.downloadCountRaw')
|
||||
case "$raw" in ''|*[!0-9]*) echo "::error::invalid count for $pkg: $raw"; exit 1 ;; esac
|
||||
echo "$pkg: $raw"
|
||||
total=$((total + raw))
|
||||
done
|
||||
if [ "$total" -le 0 ]; then echo "::error::total is zero"; exit 1; fi
|
||||
message=$(awk -v n="$total" 'BEGIN{
|
||||
if (n >= 1000000) printf "%.1fM", n/1000000;
|
||||
else if (n >= 1000) printf "%.1fk", n/1000;
|
||||
else printf "%d", n
|
||||
}')
|
||||
echo "total=$total" >> "$GITHUB_OUTPUT"
|
||||
echo "message=$message" >> "$GITHUB_OUTPUT"
|
||||
echo "combined: $total -> $message"
|
||||
|
||||
- name: Publish badge JSON to badges branch
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MESSAGE: ${{ steps.counts.outputs.message }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir badges && cd badges
|
||||
jq -n --arg message "$MESSAGE" \
|
||||
'{schemaVersion: 1, label: "Docker Downloads", message: $message, color: "blue", namedLogo: "docker"}' \
|
||||
> docker-downloads.json
|
||||
cat docker-downloads.json
|
||||
git init -q -b badges
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add docker-downloads.json
|
||||
git commit -qm "chore: update docker downloads badge ($MESSAGE)"
|
||||
git push -qf "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" badges
|
||||
@@ -0,0 +1,35 @@
|
||||
name: SEO audit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/*.html'
|
||||
- 'public/locales/**'
|
||||
- 'scripts/**'
|
||||
- 'translations/**'
|
||||
- 'docs/**'
|
||||
- 'src/partials/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'vite.config.ts'
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
HUSKY: 0
|
||||
SITE_URL: https://www.bentopdf.com
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: npm
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: SEO audit
|
||||
run: npm run seo-audit
|
||||
@@ -1,14 +1,30 @@
|
||||
name: Update Sponsors
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Daily
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 6 * * 1'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'alam00000/bentopdf'
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: JamesIves/github-sponsors-readme-action@v1
|
||||
- uses: JamesIves/github-sponsors-readme-action@02650b8cd445fc16dfef73195f9c406dce041623 # v1.6.1
|
||||
with:
|
||||
token: ${{ secrets.SPONSORS_TOKEN }}
|
||||
file: 'README.md'
|
||||
file: 'README.md'
|
||||
active-only: false
|
||||
- uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
|
||||
with:
|
||||
commit-message: 'docs: update sponsors'
|
||||
title: 'docs: update sponsors'
|
||||
body: Updates the sponsors list in the README from GitHub Sponsors.
|
||||
branch: chore/update-sponsors
|
||||
delete-branch: true
|
||||
|
||||
@@ -1,62 +1,68 @@
|
||||
# Simple workflow for deploying static content to GitHub Pages
|
||||
name: Deploy static content to Pages
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
|
||||
# Configure these variables under your repo settings.
|
||||
# You will likely need to set BASE_URL to be the name of your repo eg 'bentopdf'
|
||||
env:
|
||||
SIMPLE_MODE: ${{ vars.SIMPLE_MODE }}
|
||||
BASE_URL: ${{ vars.BASE_URL }}/
|
||||
|
||||
jobs:
|
||||
# Single deploy job since we're just deploying
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build distribution
|
||||
run: |
|
||||
# export SIMPLE_MODE=${{ vars.SIMPLE_MODE }}
|
||||
# export BASE_URL=${{ vars.BASE_URL }}/
|
||||
npm run build
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: dist
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
# Simple workflow for deploying static content to GitHub Pages
|
||||
name: Deploy static content to Pages
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
|
||||
# Configure these variables under your repo settings.
|
||||
# You will likely need to set BASE_URL to be the name of your repo eg 'bentopdf'
|
||||
env:
|
||||
SIMPLE_MODE: ${{ vars.SIMPLE_MODE }}
|
||||
BASE_URL: ${{ vars.BASE_URL }}/
|
||||
SITE_URL: ${{ vars.SITE_URL }}
|
||||
ALLOW_INDEXING: ${{ vars.ALLOW_INDEXING }}
|
||||
HUSKY: 0
|
||||
|
||||
jobs:
|
||||
# Single deploy job since we're just deploying
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build distribution
|
||||
run: |
|
||||
# export SIMPLE_MODE=${{ vars.SIMPLE_MODE }}
|
||||
# export BASE_URL=${{ vars.BASE_URL }}/
|
||||
npm run build
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: dist
|
||||
name: github-pages-deployment
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
with:
|
||||
artifact_name: github-pages-deployment
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: tests-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
HUSKY: 0
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Typecheck
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test:run
|
||||
@@ -0,0 +1,102 @@
|
||||
name: Trivy Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 6 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
scan-image:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
image:
|
||||
- name: bentopdf
|
||||
file: Dockerfile
|
||||
- name: bentopdf-nonroot
|
||||
file: Dockerfile.nonroot
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Build image for scanning
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.image.file }}
|
||||
push: false
|
||||
load: true
|
||||
tags: ${{ matrix.image.name }}:scan
|
||||
no-cache: true
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||
with:
|
||||
image-ref: ${{ matrix.image.name }}:scan
|
||||
format: sarif
|
||||
output: trivy-${{ matrix.image.name }}.sarif
|
||||
severity: CRITICAL,HIGH
|
||||
exit-code: '1'
|
||||
|
||||
- name: Upload Trivy results to GitHub Security
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
if: always() && hashFiles('trivy-*.sarif') != ''
|
||||
with:
|
||||
sarif_file: trivy-${{ matrix.image.name }}.sarif
|
||||
category: trivy-${{ matrix.image.name }}
|
||||
|
||||
scan-dependencies:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Scan npm dependencies with Trivy
|
||||
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||
with:
|
||||
scan-type: fs
|
||||
scan-ref: .
|
||||
format: sarif
|
||||
output: trivy-deps.sarif
|
||||
severity: CRITICAL,HIGH
|
||||
scanners: vuln
|
||||
|
||||
- name: Upload dependency scan results
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
if: always() && hashFiles('trivy-deps.sarif') != ''
|
||||
with:
|
||||
sarif_file: trivy-deps.sarif
|
||||
category: trivy-dependencies
|
||||
|
||||
scan-config:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Scan Dockerfiles for misconfigurations
|
||||
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||
with:
|
||||
scan-type: config
|
||||
scan-ref: .
|
||||
format: sarif
|
||||
output: trivy-config.sarif
|
||||
severity: CRITICAL,HIGH
|
||||
|
||||
- name: Upload config scan results
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
if: always() && hashFiles('trivy-config.sarif') != ''
|
||||
with:
|
||||
sarif_file: trivy-config.sarif
|
||||
category: trivy-config
|
||||
@@ -0,0 +1,270 @@
|
||||
name: Update PDF Engine and Viewer
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '17 4 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: update-engine
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORK_URL: https://github.com/alam00000/bentopdf-pdfium-viewer
|
||||
HUSKY: 0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'alam00000/bentopdf'
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
run: ${{ steps.gate.outputs.run }}
|
||||
version: ${{ steps.gate.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Read current upstream marker
|
||||
id: current
|
||||
run: |
|
||||
if [ -f vendor/bentopdf-viewer/.upstream-version ]; then
|
||||
echo "version=$(cat vendor/bentopdf-viewer/.upstream-version)" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "version=" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Read fork HEAD
|
||||
id: upstream
|
||||
run: echo "version=$(git ls-remote "$FORK_URL" refs/heads/main | cut -c1-12)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Should update?
|
||||
id: gate
|
||||
env:
|
||||
CURRENT: ${{ steps.current.outputs.version }}
|
||||
UPSTREAM: ${{ steps.upstream.outputs.version }}
|
||||
run: |
|
||||
if ! [[ "$UPSTREAM" =~ ^[0-9a-f]{12}$ ]]; then
|
||||
echo "::error::Unexpected fork HEAD: $UPSTREAM"
|
||||
exit 1
|
||||
fi
|
||||
echo "version=$UPSTREAM" >> "$GITHUB_OUTPUT"
|
||||
if [ "$UPSTREAM" = "$CURRENT" ]; then
|
||||
echo "run=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Already on $CURRENT."
|
||||
else
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Updating $CURRENT -> $UPSTREAM"
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Enable corepack (pnpm)
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
run: corepack enable
|
||||
|
||||
- name: Clone the viewer fork
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
run: git clone --depth 1 --branch main "$FORK_URL" ./_fork
|
||||
|
||||
- name: Require a published engine
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
run: |
|
||||
test -s ./_fork/packages/pdfium/src/vendor/pdfium.wasm
|
||||
test -s ./_fork/packages/pdfium/src/vendor/pdfium.js
|
||||
|
||||
- name: Pack the engine
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
id: engine
|
||||
env:
|
||||
SHA: ${{ steps.gate.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VENDOR=./_fork/packages/pdfium/src/vendor
|
||||
STAGE=$(mktemp -d)/package
|
||||
mkdir -p "$STAGE"
|
||||
cp "$VENDOR/pdfium.js" "$STAGE/editcore.js"
|
||||
cp "$VENDOR/pdfium.wasm" "$STAGE/editcore.wasm"
|
||||
cat > "$STAGE/package.json" <<JSON
|
||||
{
|
||||
"name": "bentopdf-pdfium",
|
||||
"version": "0.0.0-$SHA",
|
||||
"type": "module",
|
||||
"main": "editcore.js",
|
||||
"exports": { ".": "./editcore.js", "./editcore.wasm": "./editcore.wasm" },
|
||||
"license": "AGPL-3.0"
|
||||
}
|
||||
JSON
|
||||
mkdir -p vendor/bentopdf-pdfium
|
||||
OUT="vendor/bentopdf-pdfium/bentopdf-pdfium-$SHA.tgz"
|
||||
tar -czf "$OUT" -C "$(dirname "$STAGE")" package
|
||||
find vendor/bentopdf-pdfium -maxdepth 1 -name 'bentopdf-pdfium*.tgz' ! -name "$(basename "$OUT")" -delete
|
||||
echo "tarball=$OUT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build the viewer
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
working-directory: ./_fork
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile
|
||||
pnpm run build:snippet
|
||||
|
||||
- name: Pack the viewer without a second engine
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
id: viewer
|
||||
env:
|
||||
SHA: ${{ steps.gate.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
WORK=$(mktemp -d)
|
||||
(cd ./_fork && npm pack ./viewers/snippet --pack-destination "$WORK" >/dev/null)
|
||||
TMP=$(mktemp -d)
|
||||
tar -xzf "$WORK"/*.tgz -C "$TMP"
|
||||
rm -f "$TMP/package/dist/pdfium.wasm"
|
||||
PKG="$TMP/package/package.json" node - <<'NODE'
|
||||
const fs = require('fs');
|
||||
const p = process.env.PKG;
|
||||
const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
|
||||
pkg.name = 'bentopdf-viewer';
|
||||
pkg.dependencies = pkg.dependencies || {};
|
||||
// The rollup bundle inlines every @embedpdf/* package, so these
|
||||
// declared deps would only pull unused copies from upstream npm.
|
||||
for (const k of Object.keys(pkg.dependencies)) {
|
||||
if (k.startsWith('@embedpdf/')) delete pkg.dependencies[k];
|
||||
if (k === 'preact') pkg.dependencies[k] = '^10.17.0';
|
||||
}
|
||||
fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n');
|
||||
NODE
|
||||
mkdir -p vendor/bentopdf-viewer
|
||||
OUT="vendor/bentopdf-viewer/bentopdf-viewer-$SHA.tgz"
|
||||
tar -czf "$OUT" -C "$TMP" package
|
||||
find vendor/bentopdf-viewer -maxdepth 1 -name 'bentopdf-viewer-*.tgz' ! -name "$(basename "$OUT")" -delete
|
||||
echo "tarball=$OUT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Point dependencies at the new tarballs
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
env:
|
||||
ENGINE_TARBALL: ${{ steps.engine.outputs.tarball }}
|
||||
VIEWER_TARBALL: ${{ steps.viewer.outputs.tarball }}
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
pkg.dependencies['bentopdf-pdfium'] = 'file:' + process.env.ENGINE_TARBALL;
|
||||
pkg.dependencies['bentopdf-viewer'] = 'file:' + process.env.VIEWER_TARBALL;
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
"
|
||||
|
||||
- name: Cleanup upstream clone
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
run: rm -rf ./_fork
|
||||
|
||||
- name: Install and verify one shared engine
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
npm install
|
||||
test -s node_modules/bentopdf-pdfium/editcore.wasm
|
||||
if find node_modules/bentopdf-viewer -name '*.wasm' | grep -q .; then
|
||||
echo "::error::viewer package still ships a wasm; the engine would be duplicated"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q "bentopdf-pdfium/editcore.wasm" src/js/logic/edit-pdf-page.ts; then
|
||||
echo "::error::the PDF editor no longer points at the shared engine"
|
||||
exit 1
|
||||
fi
|
||||
echo "engine sha256: $(sha256sum node_modules/bentopdf-pdfium/editcore.wasm | cut -c1-16)"
|
||||
|
||||
- name: Typecheck and test
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
run: |
|
||||
npx tsc --noEmit
|
||||
npm run test:run
|
||||
|
||||
- name: Write upstream marker
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
env:
|
||||
SHA: ${{ steps.gate.outputs.version }}
|
||||
run: |
|
||||
echo "$SHA" > vendor/bentopdf-viewer/.upstream-version
|
||||
echo "$SHA" > vendor/bentopdf-pdfium/.upstream-version
|
||||
|
||||
- name: Upload update
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: engine-update
|
||||
include-hidden-files: true
|
||||
retention-days: 1
|
||||
path: |
|
||||
package.json
|
||||
package-lock.json
|
||||
vendor/bentopdf-pdfium/
|
||||
vendor/bentopdf-viewer/
|
||||
|
||||
pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: needs.build.outputs.run == 'true'
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download update
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: engine-update
|
||||
path: ${{ runner.temp }}/engine-update
|
||||
|
||||
- name: Apply update
|
||||
id: apply
|
||||
env:
|
||||
UPDATE: ${{ runner.temp }}/engine-update
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf vendor/bentopdf-pdfium vendor/bentopdf-viewer
|
||||
mkdir -p vendor
|
||||
cp -R "$UPDATE/vendor/bentopdf-pdfium" "$UPDATE/vendor/bentopdf-viewer" vendor/
|
||||
cp "$UPDATE/package.json" "$UPDATE/package-lock.json" .
|
||||
{
|
||||
echo 'checksums<<EOF'
|
||||
sha256sum vendor/bentopdf-pdfium/*.tgz vendor/bentopdf-viewer/*.tgz
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
|
||||
with:
|
||||
commit-message: 'build(deps): bump PDF engine and viewer to ${{ needs.build.outputs.version }}'
|
||||
title: 'build(deps): bump PDF engine and viewer to ${{ needs.build.outputs.version }}'
|
||||
body: |
|
||||
Both vendored packages built from one clone of the viewer fork at
|
||||
`${{ needs.build.outputs.version }}`, so the text editor and the
|
||||
PDF editor run the same engine.
|
||||
|
||||
- `bentopdf-pdfium` — the engine wasm, packed from the fork's published `src/vendor`
|
||||
- `bentopdf-viewer` — the snippet bundle, packed **without** `dist/pdfium.wasm`
|
||||
|
||||
The viewer loads the shared engine through `EmbedPDF.init({ wasmUrl })`.
|
||||
CI verified a single wasm on disk, typecheck, and the test suite.
|
||||
|
||||
Built in a read-only job. Tarball checksums:
|
||||
|
||||
```
|
||||
${{ steps.apply.outputs.checksums }}
|
||||
```
|
||||
branch: chore/update-pdf-engine
|
||||
delete-branch: true
|
||||
+35
-4
@@ -12,6 +12,9 @@ dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.npm-cache
|
||||
.env
|
||||
.env.production
|
||||
.env.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
@@ -26,7 +29,35 @@ dist-ssr
|
||||
# Coverage reports
|
||||
coverage/
|
||||
*.lcov
|
||||
# will be deleted later
|
||||
new-changes-to-port
|
||||
pymupdf-wasm
|
||||
serve_test
|
||||
|
||||
# Generated sitemap
|
||||
public/sitemap.xml
|
||||
|
||||
# Generated by scripts/generate-security-headers.mjs at build time
|
||||
security-headers.conf
|
||||
|
||||
# CodeQL local scan artifacts (npm run security:codeql)
|
||||
codeql-db/
|
||||
codeql-results.sarif
|
||||
codeql-results.csv
|
||||
|
||||
#backup
|
||||
.seo-backup
|
||||
libreoffice-wasm-package
|
||||
|
||||
# helm chart
|
||||
/bentopdf-*.tgz
|
||||
|
||||
# test
|
||||
dist-test
|
||||
test
|
||||
# Sibling checkouts of the engine/viewer forks
|
||||
bentopdf-pdfium-viewer/
|
||||
bentopdf-pymupdf-wasm/
|
||||
|
||||
/blog/*.html
|
||||
!/blog/author-alam.html
|
||||
/src/partials/tool-links-static.html
|
||||
/security-headers-docs.conf
|
||||
/dist-*.zip
|
||||
/docs/.vitepress/cache/
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
|
||||
# ============================================
|
||||
# 1. SECURITY HEADERS (UPDATED FOR CDN COMPATIBILITY)
|
||||
# ============================================
|
||||
<IfModule mod_headers.c>
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'unsafe-eval' blob: https://cdn.jsdelivr.net/npm/@bentopdf/[email protected]/ https://cdn.jsdelivr.net/npm/@bentopdf/[email protected]/assets/ https://cdn.jsdelivr.net/npm/[email protected]/dist/ https://cdn.jsdelivr.net/npm/[email protected]/dist/ https://cdn.jsdelivr.net/npm/[email protected]/; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https:; font-src 'self' data: https://fonts.gstatic.com https://rawcdn.githack.com; connect-src 'self' blob: https://api.github.com https://fonts.gstatic.com https://cdn.jsdelivr.net https://bentopdf-cors-proxy.bentopdf.workers.dev https://rawcdn.githack.com; object-src 'none'; base-uri 'self'; frame-src 'self' blob:; frame-ancestors 'self'; form-action 'self'"
|
||||
</IfModule>
|
||||
|
||||
# Block source maps from being served
|
||||
<FilesMatch "\.map$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# ============================================
|
||||
# 2. BROWSER CACHING
|
||||
# ============================================
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresByType image/jpeg "access plus 1 year"
|
||||
ExpiresByType image/png "access plus 1 year"
|
||||
ExpiresByType image/gif "access plus 1 year"
|
||||
ExpiresByType image/webp "access plus 1 year"
|
||||
ExpiresByType image/svg+xml "access plus 1 year"
|
||||
ExpiresByType image/x-icon "access plus 1 year"
|
||||
ExpiresByType font/woff2 "access plus 1 year"
|
||||
ExpiresByType font/woff "access plus 1 year"
|
||||
ExpiresByType font/ttf "access plus 1 year"
|
||||
ExpiresByType text/css "access plus 1 month"
|
||||
ExpiresByType application/javascript "access plus 1 month"
|
||||
ExpiresByType application/wasm "access plus 1 year"
|
||||
ExpiresByType application/gzip "access plus 1 year"
|
||||
ExpiresByType text/html "access plus 0 seconds"
|
||||
</IfModule>
|
||||
|
||||
# ============================================
|
||||
# 3. COMPRESSION (STANDARD)
|
||||
# ============================================
|
||||
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
|
||||
SetEnvIfNoCase Request_URI "\.br$" no-gzip
|
||||
SetEnvIfNoCase Request_URI "\.wasm$" no-gzip
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/plain
|
||||
AddOutputFilterByType DEFLATE text/html
|
||||
AddOutputFilterByType DEFLATE text/xml
|
||||
AddOutputFilterByType DEFLATE text/css
|
||||
AddOutputFilterByType DEFLATE application/xml
|
||||
AddOutputFilterByType DEFLATE application/xhtml+xml
|
||||
AddOutputFilterByType DEFLATE application/rss+xml
|
||||
AddOutputFilterByType DEFLATE application/javascript
|
||||
AddOutputFilterByType DEFLATE application/x-javascript
|
||||
AddOutputFilterByType DEFLATE application/json
|
||||
AddOutputFilterByType DEFLATE image/svg+xml
|
||||
AddOutputFilterByType DEFLATE font/woff
|
||||
AddOutputFilterByType DEFLATE font/ttf
|
||||
</IfModule>
|
||||
|
||||
# ============================================
|
||||
# 4. MIME TYPES & SPECIAL FILE HANDLING
|
||||
# ============================================
|
||||
AddType application/javascript .js .mjs
|
||||
AddType application/wasm .wasm
|
||||
AddType font/woff2 .woff2
|
||||
AddType font/woff .woff
|
||||
AddType image/webp .webp
|
||||
|
||||
<FilesMatch "soffice\.wasm\.gz$">
|
||||
ForceType application/wasm
|
||||
Header set Content-Encoding "gzip"
|
||||
Header set Cross-Origin-Resource-Policy "cross-origin"
|
||||
Header append Vary Accept-Encoding
|
||||
</FilesMatch>
|
||||
|
||||
<FilesMatch "soffice\.data\.gz$">
|
||||
ForceType application/octet-stream
|
||||
Header set Content-Encoding "gzip"
|
||||
Header append Vary Accept-Encoding
|
||||
</FilesMatch>
|
||||
|
||||
# ============================================
|
||||
# 5. REDIRECTS & ROUTING
|
||||
# ============================================
|
||||
# Canonical WWW
|
||||
RewriteCond %{HTTP_HOST} ^bentopdf\.com [NC]
|
||||
RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301]
|
||||
|
||||
# Force HTTPS
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# Remove trailing slash (except for language root directories)
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} !^/(ar|be|da|de|es|fr|id|it|ja|ko|nl|pl|pt|ru|sk|sv|tr|uk|vi|zh|zh-TW)/$
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [R=301,L]
|
||||
|
||||
# Existing files/dirs - serve directly
|
||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# ============================================
|
||||
# 5.1. LANGUAGE ROUTES (MUST BE BEFORE .html extension rule)
|
||||
# ============================================
|
||||
# English prefix redirects to root
|
||||
RewriteRule ^en/?$ / [R=301,L]
|
||||
RewriteRule ^en/(.+)$ /$1 [R=301,L]
|
||||
|
||||
# Language prefix root (e.g., /de/ -> /de/index.html)
|
||||
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
|
||||
RewriteRule ^(ar|be|da|de|es|fr|id|it|ja|ko|nl|pl|pt|ru|sk|sv|tr|uk|vi|zh|zh-TW)/?$ /$1/index.html [L]
|
||||
|
||||
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
|
||||
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
|
||||
RewriteRule ^(ar|be|da|de|es|fr|id|it|ja|ko|nl|pl|pt|ru|sk|sv|tr|uk|vi|zh|zh-TW)/([^/]+)/?$ /$1/$2.html [L]
|
||||
|
||||
# ============================================
|
||||
# 5.5. DOCS ROUTING (VitePress)
|
||||
# ============================================
|
||||
RewriteCond %{REQUEST_URI} ^/docs
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME}\.html -f
|
||||
RewriteRule ^(.*)$ $1.html [L]
|
||||
|
||||
# ============================================
|
||||
# 6. ADD .HTML EXTENSION IF FILE EXISTS (ROOT LEVEL ONLY)
|
||||
# ============================================
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME}.html -f
|
||||
RewriteRule ^([^/]+)$ $1.html [L]
|
||||
|
||||
# ============================================
|
||||
# 7. ERROR PAGES
|
||||
# ============================================
|
||||
ErrorDocument 404 /404.html
|
||||
@@ -0,0 +1 @@
|
||||
npx lint-staged
|
||||
@@ -0,0 +1,11 @@
|
||||
# Handlebars partials with template syntax inside HTML attributes
|
||||
src/partials/footer.html
|
||||
|
||||
blog/posts/
|
||||
|
||||
public/pdfjs-viewer/
|
||||
public/pdfjs-annotation-viewer/
|
||||
|
||||
translations/source.json
|
||||
translations/pages.json
|
||||
translations/ui-source.json
|
||||
@@ -0,0 +1,5 @@
|
||||
DS-0002
|
||||
|
||||
KSV-0125
|
||||
|
||||
DS-0013
|
||||
@@ -0,0 +1,2 @@
|
||||
https://www.bentopdf.com/funding.json
|
||||
https://bentopdf.com/funding.json
|
||||
@@ -0,0 +1,176 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>404 - Page Not Found | BentoPDF</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Oops! The page you're looking for doesn't exist. Return to BentoPDF's homepage to access our free PDF tools."
|
||||
/>
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href="https://www.bentopdf.com/404" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://www.bentopdf.com/404" />
|
||||
<meta property="og:title" content="404 - Page Not Found | BentoPDF" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="The page you're looking for doesn't exist."
|
||||
/>
|
||||
<meta property="og:site_name" content="BentoPDF" />
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:title" content="404 - Page Not Found" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="The page you're looking for doesn't exist."
|
||||
/>
|
||||
<meta name="twitter:site" content="@BentoPDF" />
|
||||
|
||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||
|
||||
<!-- Web App Manifest -->
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="192x192"
|
||||
href="/images/favicon-192x192.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="512x512"
|
||||
href="/images/favicon-512x512.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/images/apple-touch-icon.png"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||
</head>
|
||||
|
||||
<body class="antialiased bg-gray-900 text-gray-300">
|
||||
{{> navbar }}
|
||||
|
||||
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
||||
<section id="404-content" class="text-center py-20 md:py-32">
|
||||
<!-- 404 Number -->
|
||||
<div class="mb-8">
|
||||
<h1
|
||||
class="text-9xl md:text-[12rem] font-bold text-indigo-500 leading-none"
|
||||
>
|
||||
404
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Main heading -->
|
||||
<h2 class="text-3xl md:text-5xl font-bold text-white mb-4">
|
||||
Page Not Found
|
||||
</h2>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-lg md:text-xl text-gray-400 mb-8 max-w-2xl mx-auto">
|
||||
Oops! The page you're looking for seems to have wandered off into the
|
||||
digital void. Don't worry though, our PDF tools are still right where
|
||||
you left them.
|
||||
</p>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div
|
||||
class="flex flex-col sm:flex-row gap-4 justify-center items-center"
|
||||
>
|
||||
<a
|
||||
href="index.html"
|
||||
class="inline-flex items-center gap-3 pl-8 pr-2 py-2 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1"
|
||||
>
|
||||
<span>Back to Home</span>
|
||||
<span class="bg-white text-indigo-600 rounded-full p-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||
<polyline points="9 22 9 12 15 12 15 22" />
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/index.html#tools-header"
|
||||
class="inline-block px-8 py-3 rounded-full bg-gray-800 text-white font-semibold border border-gray-700 hover:bg-gray-700 focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-gray-700 transition-all duration-200"
|
||||
>
|
||||
Browse All Tools
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Helpful links -->
|
||||
<div class="mt-16">
|
||||
<p class="text-gray-500 mb-4">Or try these popular tools:</p>
|
||||
<div class="flex flex-wrap justify-center gap-3">
|
||||
<a
|
||||
href="/merge-pdf"
|
||||
class="px-4 py-2 bg-gray-800 text-gray-300 rounded-lg hover:bg-gray-700 hover:text-white transition-colors text-sm"
|
||||
>
|
||||
Merge PDF
|
||||
</a>
|
||||
<a
|
||||
href="/split-pdf"
|
||||
class="px-4 py-2 bg-gray-800 text-gray-300 rounded-lg hover:bg-gray-700 hover:text-white transition-colors text-sm"
|
||||
>
|
||||
Split PDF
|
||||
</a>
|
||||
<a
|
||||
href="/compress-pdf"
|
||||
class="px-4 py-2 bg-gray-800 text-gray-300 rounded-lg hover:bg-gray-700 hover:text-white transition-colors text-sm"
|
||||
>
|
||||
Compress PDF
|
||||
</a>
|
||||
<a
|
||||
href="/edit-pdf"
|
||||
class="px-4 py-2 bg-gray-800 text-gray-300 rounded-lg hover:bg-gray-700 hover:text-white transition-colors text-sm"
|
||||
>
|
||||
Edit PDF
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{{> footer }}
|
||||
|
||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||
<script type="module" src="src/js/main.ts"></script>
|
||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||
|
||||
<!-- JSON-LD Schema -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebPage",
|
||||
"name": "404 - Page Not Found",
|
||||
"description": "The page you're looking for doesn't exist on BentoPDF.",
|
||||
"url": "https://www.bentopdf.com/404"
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,107 @@
|
||||
# BentoPDF Commercial License
|
||||
|
||||
Thank you for purchasing a BentoPDF commercial license. These are the full terms of your license. The [licensing page](https://www.bentopdf.com/licensing) summarizes them.
|
||||
|
||||
These terms apply to licenses purchased on or after 3 November 2026. A license purchased before that date keeps the terms that were published when it was bought.
|
||||
|
||||
## Definitions
|
||||
|
||||
- **You** means the person or legal entity that bought the license.
|
||||
- **Affiliate** means any entity that controls you, that you control, or that is under common control with you.
|
||||
- **Product** means one application built with BentoPDF that you offer under one name. The same application on several platforms (web, desktop, iOS, Android) or on several domains under the same name counts as one Product. The same application offered under a different name or brand counts as a separate Product. If you build BentoPDF into a larger application, such as a CRM or a document management system, that larger application is the Product.
|
||||
- **End user** means anyone who uses your Product.
|
||||
|
||||
## What the license covers
|
||||
|
||||
- Use of BentoPDF in one closed-source Product, without the AGPL-3.0 obligation to disclose your source code.
|
||||
- Unlimited end users, customers, devices and deployments of that Product, including staging environments, regional deployments and separate instances for individual customers.
|
||||
- Private modifications, including a full redesign, your own branding, removing the BentoPDF branding (Simple Mode), and adding your own features.
|
||||
- Running the Product yourself, or distributing it to end users under your own terms, including through app stores.
|
||||
- Contractors and agencies working on the Product on your behalf.
|
||||
- Lifetime updates and email support.
|
||||
|
||||
No license key is required or provided.
|
||||
|
||||
## One license per product
|
||||
|
||||
- The license must be held by the person or company that owns the Product. An employee cannot buy a license personally for their employer's Product.
|
||||
- Each additional Product needs its own license. If you want to cover several Products, email us about a catalogue license.
|
||||
- An agency or freelancer building a Product for a client needs one license per client Product, held by the client. You can buy it for your client and hand it over with the Product.
|
||||
- You can transfer the license only together with the Product, for example when you sell the Product or your business. Email us when you do. After a transfer, you can no longer use the license.
|
||||
|
||||
## What the license does not allow
|
||||
|
||||
- Sublicensing, reselling or redistributing BentoPDF or your Product's source code to third parties, including as a template, starter kit, boilerplate or white-label product that others can rebrand and run as their own. Handing a Product and its license to a client, or transferring them as described above, is fine.
|
||||
- Reseller or white-label programs that let your customers offer your Product to their own users as their own product. Letting customers add their logo or colours inside your Product is fine.
|
||||
- Building a developer product from BentoPDF, such as an SDK, library, component, plugin, API or hosted service that lets other developers or other software use BentoPDF's features.
|
||||
- Removing or changing the copyright and license notices in BentoPDF's source files. You may remove BentoPDF branding from the interface.
|
||||
|
||||
Your end user terms must not give end users any rights to BentoPDF's code beyond using your Product, and must not allow them to extract BentoPDF or its components for use outside your Product.
|
||||
|
||||
[OEM terms](mailto:[email protected]) are available for redistribution, reselling and developer products.
|
||||
|
||||
## Bundled component: Hyper Compress
|
||||
|
||||
**Building on BentoPDF? Hyper Compress is included.** The license includes [Hyper Compress](https://github.com/alam00000/bentopdf-hyper-compress) as a bundled component, at no extra cost, when all of the following conditions are met.
|
||||
|
||||
### 1. Your size
|
||||
|
||||
The combined gross revenue of you and your Affiliates, plus any investment or grants you have raised, must be under US$1 million over the most recent 12 months. Splitting work across entities does not change this. If we ask, you must show that you meet this condition.
|
||||
|
||||
If you reach US$1 million, buy a [Hyper Compress Business license](https://www.bentopdf.com/hyper-compress#pricing) within 30 days, or remove Hyper Compress from your Product.
|
||||
|
||||
### 2. Only through BentoPDF's own features
|
||||
|
||||
Hyper Compress may run only through the BentoPDF features that use it, as they ship in BentoPDF releases. Today these are the Compress PDF tool, including its preset pages, and the Compress step of the PDF Workflow builder. You may restyle, translate and rearrange these features and change their default settings.
|
||||
|
||||
### 3. On the end user's device, started by the end user
|
||||
|
||||
Hyper Compress must run on the end user's own device, in their browser or in your Product's desktop or mobile app, on files they choose, when they start it in your Product's interface. You may not:
|
||||
|
||||
- run it on servers, serverless functions, containers, headless browsers or with browser automation;
|
||||
- expose it to other software through an API, webhook, integration, plugin, extension or command line;
|
||||
- start it from your own features, such as automatic compression on upload, AI assistants, scheduled jobs or background processing.
|
||||
|
||||
### 4. Not a compression product
|
||||
|
||||
Hyper Compress is included for Products that are general PDF toolkits. It is not included for a Product whose value comes entirely or mostly from compression. In particular, it is not included for a Product that:
|
||||
|
||||
- has a name, domain, app store listing or main marketing centred on compression;
|
||||
- opens on the compression tool as its home page or default screen;
|
||||
- offers its end users fewer than 10 other BentoPDF tools alongside compression;
|
||||
- includes an edition, app, site or plan that offers compression on its own or as its main feature.
|
||||
|
||||
### 5. The engine stays intact
|
||||
|
||||
You may not modify Hyper Compress's files, including its WebAssembly build and worker, load them outside the features in condition 2, or make them available for use elsewhere, for example by hosting them for other sites or apps.
|
||||
|
||||
### Other uses
|
||||
|
||||
Any use of Hyper Compress outside these conditions needs a separate [Hyper Compress license](https://www.bentopdf.com/hyper-compress#pricing), from $99/year, or OEM terms.
|
||||
|
||||
Which components are bundled can change in future BentoPDF releases. A bundled component in a release you have received stays covered by these terms for that release.
|
||||
|
||||
## Kura
|
||||
|
||||
Kura is not a bundled component and is always licensed separately, with [commercial licenses](https://www.bentopdf.com/kura#pricing) from $99/year. This applies even if a future BentoPDF release uses Kura, unless that release states otherwise.
|
||||
|
||||
## Third-party components
|
||||
|
||||
The commercial license covers BentoPDF's own code. Third-party components keep their own licenses:
|
||||
|
||||
- **PyMuPDF, Ghostscript and CoherentPDF** are licensed under AGPL-3.0. By default they load at runtime from a CDN. If you host, bundle or modify them yourself, their AGPL-3.0 terms apply to them.
|
||||
- Other components are distributed under their own open-source licenses. Keep their license and notice files in your deployment.
|
||||
|
||||
## Breaches
|
||||
|
||||
Any use outside these terms is not licensed. If we tell you about a breach and you fix it within 30 days, or buy the license that use requires, your license continues. If you do not, or if the same breach happens again, your license ends. For unlicensed use, you owe the list price of the license that use required, from the date the use began.
|
||||
|
||||
None of this limits your rights under AGPL-3.0 if you comply with it instead.
|
||||
|
||||
## Refunds
|
||||
|
||||
No refunds are given after purchase.
|
||||
|
||||
## Questions
|
||||
|
||||
Email [[email protected]](mailto:[email protected]) with your purchase details.
|
||||
+1
-1
@@ -81,7 +81,7 @@ When submitting code contributions, please use our [pull request template](.gith
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bento-pdf
|
||||
cd bentopdf
|
||||
npm install
|
||||
```
|
||||
|
||||
|
||||
+76
-10
@@ -3,40 +3,106 @@
|
||||
ARG BASE_URL=
|
||||
|
||||
# Build stage
|
||||
FROM node:20-alpine AS builder
|
||||
FROM mirror.gcr.io/library/node:26-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
COPY vendor ./vendor
|
||||
RUN npm ci
|
||||
ENV HUSKY=0
|
||||
RUN npm config set fetch-retries 5 && \
|
||||
npm config set fetch-retry-mintimeout 60000 && \
|
||||
npm config set fetch-retry-maxtimeout 300000 && \
|
||||
npm config set fetch-timeout 600000 && \
|
||||
npm ci
|
||||
COPY . .
|
||||
|
||||
# Build without type checking (vite build only)
|
||||
# Pass SIMPLE_MODE environment variable if provided
|
||||
ARG SIMPLE_MODE=false
|
||||
ENV SIMPLE_MODE=$SIMPLE_MODE
|
||||
ARG COMPRESSION_MODE=all
|
||||
ARG DISABLE_GITHUB_STARS=false
|
||||
ENV DISABLE_GITHUB_STARS=$DISABLE_GITHUB_STARS
|
||||
ARG COMPRESSION_MODE=o
|
||||
ENV COMPRESSION_MODE=$COMPRESSION_MODE
|
||||
|
||||
# global arg to local arg
|
||||
# global arg to local arg - BASE_URL is read from env by vite.config.ts
|
||||
ARG BASE_URL
|
||||
ENV BASE_URL=$BASE_URL
|
||||
|
||||
RUN if [ -z "$BASE_URL" ]; then \
|
||||
npm run build -- --mode production; \
|
||||
else \
|
||||
npm run build -- --base=${BASE_URL} --mode production; \
|
||||
fi
|
||||
# WASM module URLs (pre-configured defaults)
|
||||
# Override these for air-gapped or self-hosted WASM deployments
|
||||
ARG VITE_WASM_PYMUPDF_URL
|
||||
ARG VITE_WASM_GS_URL
|
||||
ARG VITE_WASM_CPDF_URL
|
||||
ENV VITE_WASM_PYMUPDF_URL=$VITE_WASM_PYMUPDF_URL
|
||||
ENV VITE_WASM_GS_URL=$VITE_WASM_GS_URL
|
||||
ENV VITE_WASM_CPDF_URL=$VITE_WASM_CPDF_URL
|
||||
|
||||
# OCR asset URLs (optional, used for self-hosted or air-gapped OCR)
|
||||
ARG VITE_TESSERACT_WORKER_URL
|
||||
ARG VITE_TESSERACT_CORE_URL
|
||||
ARG VITE_TESSERACT_LANG_URL
|
||||
ARG VITE_TESSERACT_AVAILABLE_LANGUAGES
|
||||
ARG VITE_OCR_FONT_BASE_URL
|
||||
ENV VITE_TESSERACT_WORKER_URL=$VITE_TESSERACT_WORKER_URL
|
||||
ENV VITE_TESSERACT_CORE_URL=$VITE_TESSERACT_CORE_URL
|
||||
ENV VITE_TESSERACT_LANG_URL=$VITE_TESSERACT_LANG_URL
|
||||
ENV VITE_TESSERACT_AVAILABLE_LANGUAGES=$VITE_TESSERACT_AVAILABLE_LANGUAGES
|
||||
ENV VITE_OCR_FONT_BASE_URL=$VITE_OCR_FONT_BASE_URL
|
||||
|
||||
# Default UI language (e.g. en, fr, de, es, zh, ar)
|
||||
ARG VITE_DEFAULT_LANGUAGE
|
||||
ENV VITE_DEFAULT_LANGUAGE=$VITE_DEFAULT_LANGUAGE
|
||||
|
||||
# Custom branding (e.g. VITE_BRAND_NAME=MyCompany VITE_BRAND_LOGO=my-logo.svg)
|
||||
ARG VITE_BRAND_NAME
|
||||
ARG VITE_BRAND_LOGO
|
||||
ARG VITE_FOOTER_TEXT
|
||||
ENV VITE_BRAND_NAME=$VITE_BRAND_NAME
|
||||
ENV VITE_BRAND_LOGO=$VITE_BRAND_LOGO
|
||||
ENV VITE_FOOTER_TEXT=$VITE_FOOTER_TEXT
|
||||
|
||||
ARG DISABLE_TOOLS
|
||||
ENV DISABLE_TOOLS=$DISABLE_TOOLS
|
||||
|
||||
# Public-facing canonical site URL. Defaults to the official site so self-hosters
|
||||
# consolidate SEO signals back to bentopdf.com. A custom SITE_URL marks every page
|
||||
# noindex unless ALLOW_INDEXING=true is also set.
|
||||
ARG SITE_URL=https://www.bentopdf.com
|
||||
ENV SITE_URL=$SITE_URL
|
||||
ARG ALLOW_INDEXING=false
|
||||
ENV ALLOW_INDEXING=$ALLOW_INDEXING
|
||||
|
||||
ENV NODE_OPTIONS="--max-old-space-size=3072"
|
||||
|
||||
RUN --mount=type=secret,id=VITE_CORS_PROXY_URL,required=false \
|
||||
--mount=type=secret,id=VITE_CORS_PROXY_SECRET,required=false \
|
||||
VITE_CORS_PROXY_URL=$(cat /run/secrets/VITE_CORS_PROXY_URL 2>/dev/null || echo "") \
|
||||
VITE_CORS_PROXY_SECRET=$(cat /run/secrets/VITE_CORS_PROXY_SECRET 2>/dev/null || echo "") \
|
||||
npm run build:with-docs
|
||||
|
||||
# Production stage
|
||||
FROM nginxinc/nginx-unprivileged:stable-alpine-slim
|
||||
FROM quay.io/nginx/nginx-unprivileged:alpine-slim
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf"
|
||||
LABEL org.opencontainers.image.url="https://github.com/alam00000/bentopdf"
|
||||
|
||||
# global arg to local arg
|
||||
ARG BASE_URL
|
||||
|
||||
# Set this to "true" to disable Nginx listening on IPv6
|
||||
ENV DISABLE_IPV6=false
|
||||
ENV PORT=8080
|
||||
|
||||
USER root
|
||||
RUN apk upgrade --no-cache
|
||||
USER nginx
|
||||
|
||||
COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html${BASE_URL%/}
|
||||
COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --chown=nginx:nginx --from=builder /app/security-headers.conf /etc/nginx/security-headers.conf
|
||||
COPY --chown=nginx:nginx --from=builder /app/security-headers-docs.conf /etc/nginx/security-headers-docs.conf
|
||||
COPY --chown=nginx:nginx --chmod=755 nginx-ipv6.sh /docker-entrypoint.d/99-disable-ipv6.sh
|
||||
COPY --chown=nginx:nginx --chmod=755 nginx-noindex.sh /docker-entrypoint.d/98-noindex.sh
|
||||
RUN mkdir -p /etc/nginx/tmp && chown -R nginx:nginx /etc/nginx/tmp
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# Non-root Dockerfile — supports PUID/PGID environment variables (LSIO-style)
|
||||
# Usage: docker build -f Dockerfile.nonroot -t bentopdf .
|
||||
# docker run -d -p 3000:8080 -e PUID=1000 -e PGID=1000 bentopdf
|
||||
|
||||
ARG BASE_URL=
|
||||
|
||||
# Build stage (identical to main Dockerfile)
|
||||
FROM mirror.gcr.io/library/node:26-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
COPY vendor ./vendor
|
||||
ENV HUSKY=0
|
||||
RUN npm config set fetch-retries 5 && \
|
||||
npm config set fetch-retry-mintimeout 60000 && \
|
||||
npm config set fetch-retry-maxtimeout 300000 && \
|
||||
npm config set fetch-timeout 600000 && \
|
||||
npm ci
|
||||
COPY . .
|
||||
|
||||
ARG SIMPLE_MODE=false
|
||||
ENV SIMPLE_MODE=$SIMPLE_MODE
|
||||
ARG DISABLE_GITHUB_STARS=false
|
||||
ENV DISABLE_GITHUB_STARS=$DISABLE_GITHUB_STARS
|
||||
ARG COMPRESSION_MODE=o
|
||||
ENV COMPRESSION_MODE=$COMPRESSION_MODE
|
||||
|
||||
ARG BASE_URL
|
||||
ENV BASE_URL=$BASE_URL
|
||||
|
||||
ARG VITE_WASM_PYMUPDF_URL
|
||||
ARG VITE_WASM_GS_URL
|
||||
ARG VITE_WASM_CPDF_URL
|
||||
ENV VITE_WASM_PYMUPDF_URL=$VITE_WASM_PYMUPDF_URL
|
||||
ENV VITE_WASM_GS_URL=$VITE_WASM_GS_URL
|
||||
ENV VITE_WASM_CPDF_URL=$VITE_WASM_CPDF_URL
|
||||
|
||||
ARG VITE_TESSERACT_WORKER_URL
|
||||
ARG VITE_TESSERACT_CORE_URL
|
||||
ARG VITE_TESSERACT_LANG_URL
|
||||
ARG VITE_TESSERACT_AVAILABLE_LANGUAGES
|
||||
ARG VITE_OCR_FONT_BASE_URL
|
||||
ENV VITE_TESSERACT_WORKER_URL=$VITE_TESSERACT_WORKER_URL
|
||||
ENV VITE_TESSERACT_CORE_URL=$VITE_TESSERACT_CORE_URL
|
||||
ENV VITE_TESSERACT_LANG_URL=$VITE_TESSERACT_LANG_URL
|
||||
ENV VITE_TESSERACT_AVAILABLE_LANGUAGES=$VITE_TESSERACT_AVAILABLE_LANGUAGES
|
||||
ENV VITE_OCR_FONT_BASE_URL=$VITE_OCR_FONT_BASE_URL
|
||||
|
||||
# Default UI language (e.g. en, fr, de, es, zh, ar)
|
||||
ARG VITE_DEFAULT_LANGUAGE
|
||||
ENV VITE_DEFAULT_LANGUAGE=$VITE_DEFAULT_LANGUAGE
|
||||
|
||||
# Custom branding (e.g. VITE_BRAND_NAME=MyCompany VITE_BRAND_LOGO=my-logo.svg)
|
||||
ARG VITE_BRAND_NAME
|
||||
ARG VITE_BRAND_LOGO
|
||||
ARG VITE_FOOTER_TEXT
|
||||
ENV VITE_BRAND_NAME=$VITE_BRAND_NAME
|
||||
ENV VITE_BRAND_LOGO=$VITE_BRAND_LOGO
|
||||
ENV VITE_FOOTER_TEXT=$VITE_FOOTER_TEXT
|
||||
|
||||
# Public-facing canonical site URL. See Dockerfile for details.
|
||||
ARG SITE_URL=https://www.bentopdf.com
|
||||
ENV SITE_URL=$SITE_URL
|
||||
ARG ALLOW_INDEXING=false
|
||||
ENV ALLOW_INDEXING=$ALLOW_INDEXING
|
||||
|
||||
ENV NODE_OPTIONS="--max-old-space-size=3072"
|
||||
|
||||
RUN --mount=type=secret,id=VITE_CORS_PROXY_URL,required=false \
|
||||
--mount=type=secret,id=VITE_CORS_PROXY_SECRET,required=false \
|
||||
VITE_CORS_PROXY_URL=$(cat /run/secrets/VITE_CORS_PROXY_URL 2>/dev/null || echo "") \
|
||||
VITE_CORS_PROXY_SECRET=$(cat /run/secrets/VITE_CORS_PROXY_SECRET 2>/dev/null || echo "") \
|
||||
npm run build:with-docs
|
||||
|
||||
# Production stage — uses standard nginx (starts as root, drops to PUID/PGID)
|
||||
FROM nginx:alpine-slim
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf"
|
||||
LABEL org.opencontainers.image.url="https://github.com/alam00000/bentopdf"
|
||||
|
||||
ARG BASE_URL
|
||||
|
||||
ENV PUID=1000
|
||||
ENV PGID=1000
|
||||
ENV DISABLE_IPV6=false
|
||||
ENV PORT=8080
|
||||
|
||||
RUN apk upgrade --no-cache && apk add --no-cache su-exec
|
||||
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html${BASE_URL%/}
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --from=builder /app/security-headers.conf /etc/nginx/security-headers.conf
|
||||
COPY --from=builder /app/security-headers-docs.conf /etc/nginx/security-headers-docs.conf
|
||||
COPY --chmod=755 entrypoint.sh /entrypoint.sh
|
||||
COPY --chmod=755 nginx-noindex.sh /usr/local/bin/bentopdf-noindex.sh
|
||||
|
||||
RUN mkdir -p /etc/nginx/tmp \
|
||||
/var/cache/nginx/client_temp \
|
||||
/var/cache/nginx/proxy_temp \
|
||||
/var/cache/nginx/fastcgi_temp \
|
||||
/var/cache/nginx/uwsgi_temp \
|
||||
/var/cache/nginx/scgi_temp
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
+5
-5
@@ -38,7 +38,7 @@ npm run release:major # Major: 1.0.0 → 2.0.0 (breaking changes)
|
||||
**What Happens:**
|
||||
|
||||
- ✅ Your feature commit stays as-is
|
||||
- ✅ Version gets bumped in `package.json`
|
||||
- ✅ Version gets bumped in `package.json` and `chart/Chart.yaml`
|
||||
- ✅ New release commit is created
|
||||
- ✅ Git tag is created (e.g., `v1.0.1`)
|
||||
- ✅ Everything gets pushed to GitHub
|
||||
@@ -215,16 +215,16 @@ git reset --hard HEAD~1
|
||||
1. **GitHub Actions Triggered**: Workflow starts building Docker image
|
||||
2. **Docker Build**: Multi-architecture image created
|
||||
3. **Docker Push**: Images pushed to Docker Hub with tags:
|
||||
- `bentopdf/bentopdf:latest`
|
||||
- `bentopdf/bentopdf:1.0.1`
|
||||
- `bentopdf/bentopdf:v1.0.1`
|
||||
- `bentopdfteam/bentopdf:latest`
|
||||
- `bentopdfteam/bentopdf:1.0.1`
|
||||
- `bentopdfteam/bentopdf:v1.0.1`
|
||||
|
||||
### **End Result:**
|
||||
|
||||
Users can immediately pull your new version:
|
||||
|
||||
```bash
|
||||
docker pull bentopdf/bentopdf:1.0.1
|
||||
docker pull bentopdfteam/bentopdf:1.0.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+110
-60
@@ -1,21 +1,25 @@
|
||||
# Simple Mode for BentoPDF
|
||||
# Simple Mode for BentoPDF (the Self-Hosted build)
|
||||
|
||||
Simple Mode is designed for internal organizational use where you want to hide all branding and marketing content, showing only the essential PDF tools for your users.
|
||||
Simple Mode is what powers the **Self-Hosted build** of BentoPDF (`bentopdf-simple`). It is **functionally identical** to the Commercial build that powers bentopdf.com — every PDF tool is present and behaves the same. It just hides the marketing chrome (hero, FAQ, testimonials, footer) that only makes sense on the public bentopdf.com site or on a commercial public-facing deployment.
|
||||
|
||||
## What Simple Mode Does
|
||||
> **Simple Mode is not a feature-reduced "lite" version.** Every PDF tool — merge, split, edit, sign, OCR, Office conversion, every other tool — works identically in both builds. The only difference is the marketing UI around the tools.
|
||||
|
||||
When enabled, Simple Mode will:
|
||||
The Commercial build (`ghcr.io/alam00000/bentopdf:latest`) is used by bentopdf.com itself and by commercial license holders running public-facing PDF deployments where the full marketing site makes sense.
|
||||
|
||||
- Hide the navigation bar
|
||||
- Hide the hero section with marketing content
|
||||
- Hide the features section
|
||||
- Hide the security/compliance section
|
||||
- Hide the FAQ section
|
||||
- Hide the testimonials section
|
||||
- Hide the support section
|
||||
- Hide the footer
|
||||
- Update the page title to "PDF Tools"
|
||||
- Make the tools section more prominent
|
||||
## What Simple Mode Hides
|
||||
|
||||
When enabled, Simple Mode hides the following bentopdf.com-specific marketing UI:
|
||||
|
||||
- Navigation bar
|
||||
- Hero section with marketing content
|
||||
- Features section
|
||||
- Security/compliance section
|
||||
- FAQ section
|
||||
- Testimonials section
|
||||
- Support section
|
||||
- Footer
|
||||
|
||||
It also updates the page title to "PDF Tools" and makes the tools section more prominent. **No PDF tools are removed or disabled.**
|
||||
|
||||
## How to Enable Simple Mode
|
||||
|
||||
@@ -23,27 +27,35 @@ When enabled, Simple Mode will:
|
||||
|
||||
Use the pre-built Simple Mode image directly:
|
||||
|
||||
**Using GitHub Container Registry (Recommended):**
|
||||
|
||||
```bash
|
||||
# Docker
|
||||
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
|
||||
# Podman
|
||||
podman run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
**Using Docker Hub:**
|
||||
|
||||
```bash
|
||||
docker run -p 3000:8080 bentopdf/bentopdf-simple:latest
|
||||
# Docker
|
||||
docker run -p 3000:8080 bentopdfteam/bentopdf-simple:latest
|
||||
|
||||
# Podman
|
||||
podman run -p 3000:8080 docker.io/bentopdfteam/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
**Using GitHub Container Registry:**
|
||||
|
||||
```bash
|
||||
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
Or with Docker Compose:
|
||||
Or with Docker Compose / Podman Compose:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
bentopdf:
|
||||
# Using Docker Hub
|
||||
image: bentopdf/bentopdf-simple:latest
|
||||
# Or using GitHub Container Registry
|
||||
# image: ghcr.io/alam00000/bentopdf-simple:latest
|
||||
# Using GitHub Container Registry (Recommended)
|
||||
image: ghcr.io/alam00000/bentopdf-simple:latest
|
||||
# Or using Docker Hub
|
||||
# image: bentopdfteam/bentopdf-simple:latest
|
||||
container_name: bentopdf
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
@@ -105,9 +117,13 @@ This automatically builds and serves Simple Mode on `http://localhost:3000`.
|
||||
### Method 2: Using Pre-built Image (Easiest for Production)
|
||||
|
||||
```bash
|
||||
# Pull and run the Simple Mode image
|
||||
docker pull bentopdf/bentopdf-simple:latest
|
||||
docker run -p 3000:8080 bentopdf/bentopdf-simple:latest
|
||||
# Docker - Pull and run the Simple Mode image
|
||||
docker pull ghcr.io/alam00000/bentopdf-simple:latest
|
||||
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
|
||||
# Podman
|
||||
podman pull ghcr.io/alam00000/bentopdf-simple:latest
|
||||
podman run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
Open `http://localhost:3000` in your browser.
|
||||
@@ -124,18 +140,20 @@ npx serve dist -p 3000
|
||||
|
||||
Open `http://localhost:3000` in your browser.
|
||||
|
||||
### Method 4: Compare Both Modes
|
||||
### Method 4: Compare Both Builds Side-by-Side
|
||||
|
||||
```bash
|
||||
# Test Normal Mode
|
||||
docker run -p 3000:8080 bentopdf/bentopdf:latest
|
||||
# Commercial build (the bentopdf.com look)
|
||||
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
|
||||
|
||||
# Test Simple Mode
|
||||
docker run -p 3001:8080 bentopdf/bentopdf-simple:latest
|
||||
# Self-Hosted build (Simple Mode)
|
||||
docker run -p 3001:8080 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
|
||||
# Podman users: replace 'docker' with 'podman'
|
||||
```
|
||||
|
||||
- Normal Mode: `http://localhost:3000`
|
||||
- Simple Mode: `http://localhost:3001`
|
||||
- Commercial build: `http://localhost:3000`
|
||||
- Self-Hosted build: `http://localhost:3001`
|
||||
|
||||
## 🔍 What to Look For
|
||||
|
||||
@@ -149,52 +167,84 @@ When Simple Mode is working correctly, you should see:
|
||||
- ❌ No hero section with "The PDF Toolkit built for privacy"
|
||||
- ❌ No features, FAQ, testimonials, or footer sections
|
||||
|
||||
## 📦 Available Docker Images
|
||||
## 📦 Available Container Images
|
||||
|
||||
### Normal Mode (Full Branding)
|
||||
### Self-Hosted build (Simple Mode) — recommended for self-hosting
|
||||
|
||||
**Docker Hub:**
|
||||
|
||||
- `bentopdf/bentopdf:latest`
|
||||
- `bentopdf/bentopdf:v1.0.0` (versioned)
|
||||
|
||||
**GitHub Container Registry:**
|
||||
|
||||
- `ghcr.io/alam00000/bentopdf:latest`
|
||||
- `ghcr.io/alam00000/bentopdf:v1.0.0` (versioned)
|
||||
|
||||
### Simple Mode (Clean Interface)
|
||||
|
||||
**Docker Hub:**
|
||||
|
||||
- `bentopdf/bentopdf-simple:latest`
|
||||
- `bentopdf/bentopdf-simple:v1.0.0` (versioned)
|
||||
|
||||
**GitHub Container Registry:**
|
||||
**GitHub Container Registry (Recommended):**
|
||||
|
||||
- `ghcr.io/alam00000/bentopdf-simple:latest`
|
||||
- `ghcr.io/alam00000/bentopdf-simple:v1.0.0` (versioned)
|
||||
|
||||
**Docker Hub:**
|
||||
|
||||
- `bentopdfteam/bentopdf-simple:latest`
|
||||
- `bentopdfteam/bentopdf-simple:v1.0.0` (versioned)
|
||||
|
||||
### Commercial build — used by bentopdf.com and commercial license holders
|
||||
|
||||
The full marketing site, including hero/FAQ/testimonials/footer. Pull this only if you specifically want the bentopdf.com look — for example, you're running a public-facing PDF deployment under a commercial license.
|
||||
|
||||
**GitHub Container Registry (Recommended):**
|
||||
|
||||
- `ghcr.io/alam00000/bentopdf:latest`
|
||||
- `ghcr.io/alam00000/bentopdf:v1.0.0` (versioned)
|
||||
|
||||
**Docker Hub:**
|
||||
|
||||
- `bentopdfteam/bentopdf:latest`
|
||||
- `bentopdfteam/bentopdf:v1.0.0` (versioned)
|
||||
|
||||
## 🚀 Production Deployment Examples
|
||||
|
||||
### Internal Company Tool
|
||||
### Docker Compose / Podman Compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
bentopdf:
|
||||
image: bentopdf/bentopdf-simple:latest
|
||||
image: ghcr.io/alam00000/bentopdf-simple:latest # Recommended
|
||||
# image: bentopdfteam/bentopdf-simple:latest # Alternative: Docker Hub
|
||||
container_name: bentopdf
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '80:80'
|
||||
- '80:8080'
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
```
|
||||
|
||||
### Podman Quadlet (Linux Systemd)
|
||||
|
||||
Create `~/.config/containers/systemd/bentopdf-simple.container`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=BentoPDF Simple Mode
|
||||
After=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=ghcr.io/alam00000/bentopdf-simple:latest
|
||||
ContainerName=bentopdf-simple
|
||||
PublishPort=80:8080
|
||||
AutoUpdate=registry
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
Enable and start:
|
||||
|
||||
```bash
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now bentopdf-simple
|
||||
```
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
- **Pre-built images**: Use `bentopdf/bentopdf-simple:latest` for Simple Mode
|
||||
- **Pre-built images**: Use `ghcr.io/alam00000/bentopdf-simple:latest` for Simple Mode (recommended)
|
||||
- **Environment variables**: `SIMPLE_MODE=true` only works during build, not runtime
|
||||
- **Build-time optimization**: Simple Mode uses dead code elimination for smaller bundles
|
||||
- **Same functionality**: All PDF tools work identically in both modes
|
||||
|
||||
+138
-63
@@ -1,63 +1,138 @@
|
||||
# Hosting BentoPDF as a static website
|
||||
|
||||
As an alternative to runnning BenotPDF locally or in a Docker container, you can very easily host it as a set of static web pages. Here are a few examples:
|
||||
|
||||
## Netlify
|
||||
|
||||
### Netlify - static deployment
|
||||
|
||||
One of simplest ways to host BentoPDF is to create a project at [Netlify](https://www.netlify.com/) and create a static deployment. You can accomplish this by first downloading the pre-built distribution file from our [GitHub releases](https://github.com/alam00000/bentopdf/releases). Each release includes a `dist-{version}.zip` file that contains all necessary files for self-hosting.
|
||||
|
||||
1. Go to [BentoPDF Releases](https://github.com/alam00000/bentopdf/releases)
|
||||
2. Download the latest `dist-{version}.zip` file
|
||||
3. Next, if you have not already done so, create a Netlify account and log in.
|
||||
4. From your Netlify projects page, add a new project and select "Deploy manually".
|
||||
5. Drag and drop to upload the Bentopdf zip file you downloaded in step 3.
|
||||
6. Your BentoPDF deployment should now be working! Optionally, you can go into Project Configuration and change the project name.
|
||||
|
||||
When a new BentoPDF release becomes available, you will need to repeat steps 1-3, then go into "Deploys" and upload the new release.
|
||||
|
||||
### Netlify - dynamic deployment
|
||||
|
||||
Alternatively, you can configure a Netlify project to automatically deploy whenever your BentoPDF is updated.
|
||||
|
||||
1. If you have not done so already, create a fork of BentoPDF into your own Github account.
|
||||
2. If you have not already done so, create a Netlify account and log in.
|
||||
3. From your Netlify projects page, add a new project and select 'Import an existing project'.
|
||||
4. Select the GitHub button, authorize Netlify, then choose where to install the integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork.
|
||||
5. Select your repo and give it a project name. (add environment variables?) Then click the blue 'Deploy' button.
|
||||
6. The Netlify build & deploy process will kick off. Once it finishes, you can click on the provided URL `https://[projectname]/netlify.app` to view your deployment of BentoPDF.
|
||||
|
||||
Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within Netlify.
|
||||
|
||||
If you want to use BentoPDF's simple mode, go into Deploy Settings, then Environment Variables, and Add a variable. Add `SIMPLE_MODE` and set it to `true`. You will need to manually kick a new build to get this to take effect.
|
||||
|
||||
## Vercel
|
||||
|
||||
Vercel provides similar services to Netlify dynamic hosting.
|
||||
|
||||
1. If you have not done so already, create a fork of BentoPDF into your own Github account.
|
||||
2. If you have not already done so, create a Vercel account and log in.
|
||||
3. From your Vercel Overview page, select 'Add new project'
|
||||
4. Under 'Import Git Repository' and then choose 'Add GitHub Account'. Follow the prompts to authorize Vercel integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork.
|
||||
5. Select 'Import' to import the repo into Vercel.
|
||||
6. Under 'Framework Preset', select 'Vite' and save the settings.
|
||||
7. The Vercel build & deploy process will kick off. Once it finishes, you can click on the provided link to view your deployment of BentoPDF.
|
||||
|
||||
Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within BentoPDF.
|
||||
|
||||
If you want to use BentoPDF's simple mode, go into Project Settings, then Environment Variables, and Create a new variable. Add `SIMPLE_MODE` and set it to `true`. You will need to redeploy to get this to take effect.
|
||||
|
||||
## GitHub Pages
|
||||
|
||||
You can also host your own instance of BentoPDF using GitHub pages. An advantage of this over the other options is you are able to do everything in GitHub without any third party service.
|
||||
|
||||
1. If you have not done so already, create a fork of BentoPDF into your own Github account.
|
||||
2. From your fork, go to `Settings->Pages`, and change the 'Source' to 'GitHub Actions'
|
||||
3. Go to `Settings->Secrets and Variables > Actions`, then select 'Variables', and add the repository variable `BASE_URL`. Set the value to `/bentopdf`. *If you've renamed the repo to something other than bentopdf, put that here*.
|
||||
4. Go to `Actions` in the top menu, and select 'I understand' to enable Actions
|
||||
5. Within Actions, on the left, select 'Deploy static content to Pages', and then on the right select 'Run workflow', and in the dropdown, 'Run Workflow'. The action will not run to build BentoPDF and deploy it to GitHub Pages.
|
||||
|
||||
When the build completes, you can find the website at `https://[your-github-username]/bentopdf`
|
||||
|
||||
If/when you merge changes from the source BentoPDF repository, the build and deploy action will automatically be kicked off and the new version will be automatically deployed to GitHub Pages.
|
||||
# Hosting BentoPDF as a static website
|
||||
|
||||
As an alternative to running BentoPDF locally or in a Docker container, you can easily host it as a set of static web pages. Here are a few examples:
|
||||
|
||||
## Netlify
|
||||
|
||||
### Netlify - static deployment
|
||||
|
||||
One of the simplest ways to host BentoPDF is to create a project at [Netlify](https://www.netlify.com/) and create a static deployment. You can accomplish this by first downloading the pre-built distribution file from our [GitHub releases](https://github.com/alam00000/bentopdf/releases). Each release includes a `dist-{version}.zip` file that contains all necessary files for self-hosting.
|
||||
|
||||
1. Go to [BentoPDF Releases](https://github.com/alam00000/bentopdf/releases)
|
||||
2. Download the latest `dist-{version}.zip` file
|
||||
3. Next, if you have not already done so, create a Netlify account and log in.
|
||||
4. From your Netlify projects page, add a new project and select "Deploy manually".
|
||||
5. Drag and drop to upload the BentoPDF zip file you downloaded in step 2.
|
||||
6. Your BentoPDF deployment should now be working! Optionally, you can go into Project Configuration and change the project name.
|
||||
|
||||
When a new BentoPDF release becomes available, you will need to repeat steps 1-3, then go into "Deploys" and upload the new release.
|
||||
|
||||
### Netlify - dynamic deployment
|
||||
|
||||
Alternatively, you can configure a Netlify project to automatically deploy whenever your BentoPDF is updated.
|
||||
|
||||
1. If you have not done so already, create a fork of BentoPDF into your own GitHub account.
|
||||
2. If you have not already done so, create a Netlify account and log in.
|
||||
3. From your Netlify projects page, add a new project and select 'Import an existing project'.
|
||||
4. Select the GitHub button, authorize Netlify, then choose where to install the integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork.
|
||||
5. Select your repo and give it a project name. (add environment variables?) Then click the blue 'Deploy' button.
|
||||
6. The Netlify build & deploy process will kick off. Once it finishes, you can click on the provided URL `https://[projectname]/netlify.app` to view your deployment of BentoPDF.
|
||||
|
||||
Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within Netlify.
|
||||
|
||||
If you want to use BentoPDF's simple mode, go into Deploy Settings, then Environment Variables, and Add a variable. Add `SIMPLE_MODE` and set it to `true`. You will need to manually kick a new build to get this to take effect.
|
||||
|
||||
## Vercel
|
||||
|
||||
Vercel provides similar services to Netlify dynamic hosting.
|
||||
|
||||
1. If you have not done so already, create a fork of BentoPDF into your own GitHub account.
|
||||
2. If you have not already done so, create a Vercel account and log in.
|
||||
3. From your Vercel Overview page, select 'Add new project'
|
||||
4. Under 'Import Git Repository' and then choose 'Add GitHub Account'. Follow the prompts to authorize Vercel integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork.
|
||||
5. Select 'Import' to import the repo into Vercel.
|
||||
6. Under 'Framework Preset', select 'Vite' and save the settings.
|
||||
7. The Vercel build & deploy process will kick off. Once it finishes, you can click on the provided link to view your deployment of BentoPDF.
|
||||
|
||||
Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within Vercel.
|
||||
|
||||
If you want to use BentoPDF's simple mode, go into Project Settings, then Environment Variables, and Create a new variable. Add `SIMPLE_MODE` and set it to `true`. You will need to redeploy to get this to take effect.
|
||||
|
||||
## GitHub Pages
|
||||
|
||||
You can also host your own instance of BentoPDF using GitHub Pages. An advantage of this over the other options is you are able to do everything in GitHub without any third-party service.
|
||||
|
||||
1. If you have not done so already, create a fork of BentoPDF into your own GitHub account.
|
||||
2. From your fork, go to `Settings->Pages`, and change the 'Source' to 'GitHub Actions'
|
||||
3. Go to `Settings->Secrets and Variables > Actions`, then select 'Variables', and add the repository variable `BASE_URL`. Set the value to `/bentopdf`. _If you've renamed the repo to something other than bentopdf, put that here_.
|
||||
4. Go to `Actions` in the top menu, and select 'I understand' to enable Actions
|
||||
5. Within Actions, on the left, select 'Deploy static content to Pages', and then on the right select 'Run workflow', and in the dropdown, 'Run Workflow'. The action will now run to build BentoPDF and deploy it to GitHub Pages.
|
||||
|
||||
When the build completes, you can find the website at `https://[your-github-username]/bentopdf`
|
||||
|
||||
If/when you merge changes from the source BentoPDF repository, the build and deploy action will automatically be kicked off and the new version will be automatically deployed to GitHub Pages.
|
||||
|
||||
> **Note:** GitHub Pages cannot set custom response headers, so a GitHub Pages deployment is served without BentoPDF's Content-Security-Policy and other security headers. If you need them, use one of the other hosts below or put a proxy or CDN that can add headers in front of your Pages site.
|
||||
|
||||
## Security headers
|
||||
|
||||
BentoPDF ships a Content-Security-Policy and other security headers for each static host:
|
||||
|
||||
- **Netlify and Cloudflare Pages** read `_headers` from the deployed folder. `npm run build` writes `dist/_headers` with `scripts/generate-security-headers.mjs`, so any `VITE_WASM_*`, `VITE_TESSERACT_*`, `VITE_CORS_PROXY_URL` or `VITE_OCR_FONT_BASE_URL` values you set at build time are allowed by the policy. If you add your own `public/_headers`, the build ships yours instead. The release `dist-{version}.zip` already contains it.
|
||||
- **Vercel** uses the `headers` block in `vercel.json`. It holds the default policy, so if you point the `VITE_*` URLs at your own servers, update the Content-Security-Policy there to match the `dist/_headers` your build produces.
|
||||
- **`npx serve`** reads `serve.json` from the folder it serves. To use the repository's `serve.json` with the build output, run `npx serve dist -c ../serve.json`.
|
||||
- **GitHub Pages** cannot set headers (see the note above).
|
||||
|
||||
## SEO and search engine indexing
|
||||
|
||||
By default, every self-hosted BentoPDF build emits canonical URLs and an
|
||||
hreflang/`x-default` set that point back to `https://www.bentopdf.com`. This
|
||||
is intentional for two reasons:
|
||||
|
||||
1. **It consolidates SEO signals to the official site** so your instance
|
||||
doesn't compete with bentopdf.com on brand searches like "bentopdf".
|
||||
2. **It keeps your instance off public search results.** If you self-host
|
||||
on a small VPS, a home server, or anywhere with limited bandwidth, you
|
||||
probably don't want strangers from Google landing on it. With the
|
||||
default canonical pointing back to the official site, search engines
|
||||
treat your instance as a copy and won't surface it for organic traffic
|
||||
and your bandwidth is reserved for the people you actually share the URL
|
||||
with.
|
||||
|
||||
You have two clean overrides depending on what you want.
|
||||
|
||||
### Run your own SEO instance (claim canonical for yourself)
|
||||
|
||||
If you operate a public deployment and want it to be the indexed copy for
|
||||
your audience, set `SITE_URL` at build time to your origin and also set
|
||||
`ALLOW_INDEXING=true`. The build will emit canonicals, hreflang and
|
||||
sitemap URLs pointing at your domain. Without `ALLOW_INDEXING=true`, a
|
||||
build with a custom `SITE_URL` marks every page `noindex`.
|
||||
|
||||
- Docker:
|
||||
```sh
|
||||
docker build --build-arg SITE_URL=https://pdf.example.com --build-arg ALLOW_INDEXING=true -t bentopdf-self .
|
||||
```
|
||||
- Netlify, Vercel, GitHub Pages, or any other static host: set `SITE_URL`
|
||||
and `ALLOW_INDEXING=true` in your project's environment variables and
|
||||
re-deploy. The build script reads them during `npm run build`. For the
|
||||
GitHub Pages workflow, add both as repository variables.
|
||||
|
||||
### Stay invisible to search engines (no SEO competition either way)
|
||||
|
||||
Set the runtime environment variable `ROBOTS_NOINDEX=true`. The container
|
||||
then sends an `X-Robots-Tag: noindex, follow` header with every response.
|
||||
The canonical link still points at the official site by default, so when
|
||||
crawlers do follow internal links they still see bentopdf.com as the
|
||||
authoritative source. `ROBOTS_NOINDEX` only works in the Docker image; if
|
||||
you serve the files yourself, build with your own `SITE_URL` instead (see
|
||||
above).
|
||||
|
||||
```sh
|
||||
docker run -d -p 8080:8080 -e ROBOTS_NOINDEX=true ghcr.io/alam00000/bentopdf:latest
|
||||
```
|
||||
|
||||
A build with your own `SITE_URL` and without `ALLOW_INDEXING=true` is
|
||||
already `noindex`, so it neither competes with bentopdf.com nor advertises
|
||||
itself. `ROBOTS_NOINDEX=true` does the same at runtime for the prebuilt
|
||||
images.
|
||||
|
||||
### Note on Simple Mode
|
||||
|
||||
Simple Mode (`SIMPLE_MODE=true`) controls the **UI** — it ships a stripped-down
|
||||
homepage suitable for company intranets and single-purpose deployments. It
|
||||
does **not** by itself control whether your instance is indexed by search
|
||||
engines. A public-facing simple-mode deployment is fully indexable by default,
|
||||
just like the full-mode build. If you want a simple-mode deployment hidden
|
||||
from search engines, combine `SIMPLE_MODE=true` at build time with
|
||||
`ROBOTS_NOINDEX=true` at runtime.
|
||||
|
||||
+193
-49
@@ -20,13 +20,42 @@ This guide will help you add new languages or improve existing translations for
|
||||
BentoPDF uses **i18next** for internationalization (i18n). Currently supported languages:
|
||||
|
||||
- **English** (`en`) - Default
|
||||
- **Belarusian** (`be`)
|
||||
- **German** (`de`)
|
||||
- **Spanish** (`es`)
|
||||
- **French** (`fr`)
|
||||
- **Italian** (`it`)
|
||||
- **Portuguese** (`pt`)
|
||||
- **Turkish** (`tr`)
|
||||
- **Vietnamese** (`vi`)
|
||||
- **Indonesian** (`id`)
|
||||
- **Chinese** (`zh`)
|
||||
- **Traditional Chinese (Taiwan)** (`zh-TW`)
|
||||
- **Korean** (`ko`)
|
||||
- **Russian** (`ru`)
|
||||
- **Ukrainian** (`uk`)
|
||||
- **Slovak** (`sk`)
|
||||
- **Arabic** (`ar`)
|
||||
- **Dutch** (`nl`)
|
||||
- **Danish** (`da`)
|
||||
- **Swedish** (`sv`)
|
||||
- **Japanese** (`ja`)
|
||||
- **Persian (Farsi)** (`fa`)
|
||||
|
||||
The app automatically detects the language from the URL path:
|
||||
- `/en/` → English
|
||||
|
||||
- `/` or `/en/` → English (default)
|
||||
- `/de/` → German
|
||||
- `/vi/` → Vietnamese
|
||||
- `/fr/` → French
|
||||
- etc.
|
||||
|
||||
### Architecture
|
||||
|
||||
BentoPDF uses a **static pre-rendering** approach for SEO-optimized i18n:
|
||||
|
||||
1. **Build time**: `scripts/generate-i18n-pages.mjs` generates localized HTML files in `dist/{lang}/`
|
||||
2. **Dev/Preview**: `languageRouterPlugin` in `vite.config.ts` handles URL rewriting
|
||||
3. **Production**: Nginx serves static files directly from language directories
|
||||
|
||||
---
|
||||
|
||||
@@ -34,50 +63,52 @@ The app automatically detects the language from the URL path:
|
||||
|
||||
**To improve existing translations:**
|
||||
|
||||
1. Navigate to `public/locales/{language}/common.json`
|
||||
1. Navigate to `public/locales/{language}/common.json` and `public/locales/{language}/tools.json`
|
||||
2. Find the key you want to update
|
||||
3. Change the translation value
|
||||
4. Save and test
|
||||
|
||||
**To add a new language (e.g., Spanish):**
|
||||
**To add a new language (e.g., Japanese `ja`):**
|
||||
|
||||
1. Copy `public/locales/en/common.json` to `public/locales/es/common.json`
|
||||
2. Translate all values in `es/common.json`
|
||||
3. Add Spanish to `supportedLanguages` in `src/js/i18n/i18n.ts`
|
||||
4. Add Spanish name to `languageNames` in `src/js/i18n/i18n.ts`
|
||||
5. Test thoroughly
|
||||
1. Copy `public/locales/en/` to `public/locales/ja/`
|
||||
2. Translate all values in both `ja/common.json` and `ja/tools.json`
|
||||
3. Add Japanese to `supportedLanguages` and `languageNames` in `src/js/i18n/i18n.ts`
|
||||
4. Add `'ja'` to `SUPPORTED_LANGUAGES` in `vite.config.ts`
|
||||
5. Restart the dev server
|
||||
6. Run `npm run build` to generate static language pages
|
||||
7. Test thoroughly
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Language
|
||||
|
||||
Let's add **French** as an example:
|
||||
Let's add **Spanish** as an example:
|
||||
|
||||
### Step 1: Create Translation File
|
||||
### Step 1: Create Translation Files
|
||||
|
||||
```bash
|
||||
# Create the directory
|
||||
mkdir -p public/locales/fr
|
||||
mkdir -p public/locales/es
|
||||
|
||||
# Copy the English template
|
||||
cp public/locales/en/common.json public/locales/fr/common.json
|
||||
cp public/locales/en/common.json public/locales/es/common.json
|
||||
```
|
||||
|
||||
### Step 2: Translate the JSON File
|
||||
### Step 2: Translate the JSON Files
|
||||
|
||||
Open `public/locales/fr/common.json` and translate all the values:
|
||||
Open `public/locales/es/common.json` and translate all the values:
|
||||
|
||||
```json
|
||||
{
|
||||
"nav": {
|
||||
"home": "Accueil",
|
||||
"about": "À propos",
|
||||
"contact": "Contact",
|
||||
"allTools": "Tous les outils"
|
||||
"home": "Inicio",
|
||||
"about": "Acerca de",
|
||||
"contact": "Contacto",
|
||||
"allTools": "Todas las herramientas"
|
||||
},
|
||||
"hero": {
|
||||
"title": "Votre boîte à outils PDF gratuite et sécurisée",
|
||||
"subtitle": "Fusionnez, divisez, compressez et modifiez des PDF directement dans votre navigateur."
|
||||
"title": "Tu conjunto de herramientas PDF gratuito y seguro",
|
||||
"subtitle": "Combina, divide, comprime y edita archivos PDF directamente en tu navegador."
|
||||
}
|
||||
// ... continue translating all keys
|
||||
}
|
||||
@@ -86,40 +117,78 @@ Open `public/locales/fr/common.json` and translate all the values:
|
||||
⚠️ **Important**: Only translate the **values**, NOT the keys!
|
||||
|
||||
✅ **Correct:**
|
||||
|
||||
```json
|
||||
"home": "Accueil"
|
||||
"home": "Inicio"
|
||||
```
|
||||
|
||||
❌ **Wrong:**
|
||||
|
||||
```json
|
||||
"accueil": "Accueil"
|
||||
"inicio": "Inicio"
|
||||
```
|
||||
|
||||
Then do the same for `public/locales/es/tools.json` to translate all tool names and descriptions.
|
||||
|
||||
### Step 3: Register the Language
|
||||
|
||||
Edit `src/js/i18n/i18n.ts`:
|
||||
|
||||
```typescript
|
||||
// Add 'fr' to supported languages
|
||||
export const supportedLanguages = ['en', 'de', 'fr'] as const;
|
||||
export const supportedLanguages = ['en', 'de', 'es', 'fr', 'zh', 'vi'] as const;
|
||||
export type SupportedLanguage = (typeof supportedLanguages)[number];
|
||||
|
||||
// Add French display name
|
||||
export const languageNames: Record<SupportedLanguage, string> = {
|
||||
en: 'English',
|
||||
de: 'Deutsch',
|
||||
fr: 'Français', // ← Add this
|
||||
en: 'English',
|
||||
de: 'Deutsch',
|
||||
fr: 'Français', // ← Add this
|
||||
};
|
||||
```
|
||||
|
||||
### Step 4: Test Your Translation
|
||||
### Step 4: Update Vite Configuration
|
||||
|
||||
In `vite.config.ts`, add your language to the `SUPPORTED_LANGUAGES` array:
|
||||
|
||||
```typescript
|
||||
const SUPPORTED_LANGUAGES = [
|
||||
'en',
|
||||
'de',
|
||||
'es',
|
||||
'zh',
|
||||
'zh-TW',
|
||||
'vi',
|
||||
'it',
|
||||
'id',
|
||||
'tr',
|
||||
'fr',
|
||||
'pt',
|
||||
'ja',
|
||||
] as const;
|
||||
```
|
||||
|
||||
> **Important**: This is required for both dev server routing and the build-time i18n generation.
|
||||
|
||||
### Step 5: Test Your Translation
|
||||
|
||||
```bash
|
||||
# Start the dev server
|
||||
# Restart the dev server
|
||||
npm run dev
|
||||
|
||||
# Visit the French version
|
||||
# http://localhost:5173/fr/
|
||||
# Visit the Japanese version
|
||||
# http://localhost:5173/ja/
|
||||
```
|
||||
|
||||
### Step 6: Build and Verify Static Files
|
||||
|
||||
```bash
|
||||
# Run build (includes i18n page generation)
|
||||
npm run build
|
||||
|
||||
# Verify files were created
|
||||
ls dist/ja/
|
||||
# Should show: index.html, merge-pdf.html, etc.
|
||||
```
|
||||
|
||||
---
|
||||
@@ -207,6 +276,7 @@ Tool names and descriptions are defined in `src/js/config/tools.ts` and use a sp
|
||||
```
|
||||
|
||||
In translations:
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
@@ -234,14 +304,15 @@ console.log(message); // "Error" or "Fehler" depending on language
|
||||
For input placeholders:
|
||||
|
||||
```html
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search for a tool..."
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search for a tool..."
|
||||
data-i18n-placeholder="tools.searchPlaceholder"
|
||||
/>
|
||||
```
|
||||
|
||||
In `common.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
@@ -257,6 +328,7 @@ In `common.json`:
|
||||
### Manual Testing
|
||||
|
||||
1. **Start development server:**
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
@@ -265,7 +337,11 @@ In `common.json`:
|
||||
- English: `http://localhost:5173/en/`
|
||||
- German: `http://localhost:5173/de/`
|
||||
- Vietnamese: `http://localhost:5173/vi/`
|
||||
- Your new language: `http://localhost:5173/fr/`
|
||||
- Indonesian: `http://localhost:5173/id/`
|
||||
- Chinese: `http://localhost:5173/zh/`
|
||||
- Traditional Chinese (Taiwan): `http://localhost:5173/zh-TW/`
|
||||
- French: `http://localhost:5173/fr/`
|
||||
- Your new language: `http://localhost:5173/es/`
|
||||
|
||||
3. **Check these pages:**
|
||||
- Homepage (`/`)
|
||||
@@ -289,11 +365,12 @@ Check for missing translations:
|
||||
node scripts/check-translations.js
|
||||
```
|
||||
|
||||
*(If this script doesn't exist, you may need to create it or manually compare JSON files)*
|
||||
_(If this script doesn't exist, you may need to create it or manually compare JSON files)_
|
||||
|
||||
### Browser Testing
|
||||
|
||||
Test in different browsers:
|
||||
|
||||
- Chrome/Edge
|
||||
- Firefox
|
||||
- Safari
|
||||
@@ -307,11 +384,13 @@ Test in different browsers:
|
||||
BentoPDF is **friendly, clear, and professional**. Match this tone in your translations.
|
||||
|
||||
✅ **Good:**
|
||||
|
||||
```json
|
||||
"hero.title": "Ihr kostenloses und sicheres PDF-Toolkit"
|
||||
```
|
||||
|
||||
❌ **Too formal:**
|
||||
|
||||
```json
|
||||
"hero.title": "Ihr gebührenfreies und gesichertes Werkzeug für PDF-Dokumente"
|
||||
```
|
||||
@@ -322,7 +401,7 @@ Some strings contain HTML or special characters:
|
||||
|
||||
```json
|
||||
{
|
||||
"faq.analytics.answer": "We care about your privacy. BentoPDF does not track personal information. We use <a href=\"https://simpleanalytics.com\" class=\"text-indigo-400 hover:underline\" target=\"_blank\" rel=\"noopener noreferrer\">Simple Analytics</a> solely to see anonymous visit counts."
|
||||
"faq.analytics.answer": "No. BentoPDF does not track you at all. We do not use cookies, analytics, or any tracking scripts, not even on the live website at bentopdf.com. We never know who you are or what you do with the tools."
|
||||
}
|
||||
```
|
||||
|
||||
@@ -330,7 +409,7 @@ When translating, **keep the HTML tags intact**:
|
||||
|
||||
```json
|
||||
{
|
||||
"faq.analytics.answer": "Wir schätzen Ihre Privatsphäre. BentoPDF verfolgt keine persönlichen Informationen. Wir verwenden <a href=\"https://simpleanalytics.com\" class=\"text-indigo-400 hover:underline\" target=\"_blank\" rel=\"noopener noreferrer\">Simple Analytics</a> ausschließlich, um anonyme Besucherzahlen zu sehen."
|
||||
"faq.analytics.answer": "Nein. BentoPDF verfolgt Sie in keiner Weise. Wir verwenden keine Cookies, keine Analysetools und keine Tracking-Skripte – auch nicht auf der Live-Website bentopdf.com. Wir wissen nie, wer Sie sind oder was Sie mit den Tools tun."
|
||||
}
|
||||
```
|
||||
|
||||
@@ -339,6 +418,7 @@ When translating, **keep the HTML tags intact**:
|
||||
If your language has complex plural rules or gender distinctions, consult the [i18next pluralization guide](https://www.i18next.com/translation-function/plurals).
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"pages": "page",
|
||||
@@ -349,6 +429,7 @@ Example:
|
||||
### 4. Don't Translate Brand Names or Legal Terms
|
||||
|
||||
Keep these as-is:
|
||||
|
||||
- BentoPDF
|
||||
- PDF
|
||||
- GitHub
|
||||
@@ -361,6 +442,7 @@ Keep these as-is:
|
||||
### 5. Technical Terms
|
||||
|
||||
For technical terms, use commonly accepted translations in your language:
|
||||
|
||||
- "Merge" → "Fusionner" (French), "Zusammenführen" (German)
|
||||
- "Split" → "Diviser" (French), "Teilen" (German)
|
||||
- "Compress" → "Compresser" (French), "Komprimieren" (German)
|
||||
@@ -380,6 +462,7 @@ If a translation is much longer, test it visually to ensure it doesn't break the
|
||||
### Issue: Translations Not Showing Up
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Clear your browser cache
|
||||
2. Hard refresh (Ctrl+F5 or Cmd+Shift+R)
|
||||
3. Check browser console for errors
|
||||
@@ -388,22 +471,26 @@ If a translation is much longer, test it visually to ensure it doesn't break the
|
||||
### Issue: Some Text Still in English
|
||||
|
||||
**Possible causes:**
|
||||
|
||||
1. Missing translation key in your language file
|
||||
2. Missing `data-i18n` attribute in HTML
|
||||
3. Hardcoded text in JavaScript
|
||||
|
||||
**Solution:**
|
||||
|
||||
- Compare your language file with `en/common.json` to find missing keys
|
||||
- Search the codebase for hardcoded strings
|
||||
|
||||
### Issue: JSON Syntax Error
|
||||
|
||||
**Symptoms:**
|
||||
|
||||
```
|
||||
SyntaxError: Unexpected token } in JSON at position 1234
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
|
||||
- Use a JSON validator: https://jsonlint.com/
|
||||
- Common mistakes:
|
||||
- Trailing comma after last item
|
||||
@@ -414,15 +501,50 @@ SyntaxError: Unexpected token } in JSON at position 1234
|
||||
|
||||
**Solution:**
|
||||
Make sure you added the language to both arrays in `i18n.ts`:
|
||||
|
||||
```typescript
|
||||
export const supportedLanguages = ['en', 'de', 'fr']; // ← Add here
|
||||
export const supportedLanguages = ['en', 'de', 'es', 'fr', 'zh', 'vi']; // ← Add here
|
||||
export const languageNames = {
|
||||
en: 'English',
|
||||
de: 'Deutsch',
|
||||
fr: 'Français', // ← And here
|
||||
en: 'English',
|
||||
de: 'Deutsch',
|
||||
es: 'Español',
|
||||
fr: 'Français', // ← And here
|
||||
zh: '中文',
|
||||
vi: 'Tiếng Việt',
|
||||
};
|
||||
```
|
||||
|
||||
### Issue: 404 Error When Accessing Language Pages
|
||||
|
||||
**Symptoms:**
|
||||
Visiting `http://localhost:5173/ja/about.html` shows a 404 error page.
|
||||
|
||||
**Solution:**
|
||||
You need to add your language code to `SUPPORTED_LANGUAGES` in `vite.config.ts`:
|
||||
|
||||
```typescript
|
||||
const SUPPORTED_LANGUAGES = [
|
||||
'en',
|
||||
'de',
|
||||
'es',
|
||||
'zh',
|
||||
'zh-TW',
|
||||
'vi',
|
||||
'it',
|
||||
'id',
|
||||
'tr',
|
||||
'fr',
|
||||
'pt',
|
||||
'ja',
|
||||
] as const;
|
||||
```
|
||||
|
||||
After updating, restart the dev server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Checklist
|
||||
@@ -430,11 +552,15 @@ export const languageNames = {
|
||||
When adding a new language, make sure these files are updated:
|
||||
|
||||
- [ ] `public/locales/{lang}/common.json` - Main translation file
|
||||
- [ ] `public/locales/{lang}/tools.json` - Tools translation file
|
||||
- [ ] `src/js/i18n/i18n.ts` - Add to `supportedLanguages` and `languageNames`
|
||||
- [ ] `vite.config.ts` - Add to `SUPPORTED_LANGUAGES` array
|
||||
- [ ] Test all pages: homepage, about, contact, FAQ, tool pages
|
||||
- [ ] Test settings modal and shortcuts
|
||||
- [ ] Test language switcher in footer
|
||||
- [ ] Verify URL routing works (`/{lang}/`)
|
||||
- [ ] Run `npm run build` and verify `dist/{lang}/` folder is created
|
||||
- [ ] Test that all tools load correctly
|
||||
|
||||
---
|
||||
|
||||
@@ -470,13 +596,31 @@ Thank you for contributing to BentoPDF! 🎉
|
||||
|
||||
Current translation coverage:
|
||||
|
||||
| Language | Code | Status | Maintainer |
|
||||
|----------|------|--------|------------|
|
||||
| English | `en` | ✅ Complete | Core team |
|
||||
| German | `de` | 🚧 In Progress | Core team |
|
||||
| Vietnamese | `vi` | ✅ Complete | Community |
|
||||
| Your Language | `??` | 🚧 In Progress | You? |
|
||||
| Language | Code | Status | Maintainer |
|
||||
| ------------------- | ------- | -------------- | ---------- |
|
||||
| English | `en` | ✅ Complete | Core team |
|
||||
| German | `de` | ✅ Complete | Community |
|
||||
| Spanish | `es` | ✅ Complete | Community |
|
||||
| French | `fr` | ✅ Complete | Community |
|
||||
| Italian | `it` | ✅ Complete | Community |
|
||||
| Portuguese | `pt` | ✅ Complete | Community |
|
||||
| Turkish | `tr` | ✅ Complete | Community |
|
||||
| Vietnamese | `vi` | ✅ Complete | Community |
|
||||
| Indonesian | `id` | ✅ Complete | Community |
|
||||
| Chinese | `zh` | ✅ Complete | Community |
|
||||
| Traditional Chinese | `zh-TW` | ✅ Complete | Community |
|
||||
| Korean | `ko` | ✅ Complete | Community |
|
||||
| Russian | `ru` | ✅ Complete | Community |
|
||||
| Ukrainian | `uk` | ✅ Complete | Community |
|
||||
| Slovak | `sk` | ✅ Complete | Community |
|
||||
| Arabic | `ar` | ✅ Complete | Community |
|
||||
| Dutch | `nl` | ✅ Complete | Community |
|
||||
| Danish | `da` | ✅ Complete | Community |
|
||||
| Swedish | `sv` | ✅ Complete | Community |
|
||||
| Japanese | `ja` | ✅ Complete | Community |
|
||||
| Persian (Farsi) | `fa` | ✅ Complete | Community |
|
||||
| Your Language | `??` | 🚧 In Progress | You? |
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: December 2025
|
||||
**Last Updated**: June 2026
|
||||
|
||||
+365
-277
@@ -1,290 +1,378 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>About Bentopdf - Fast, Private, and Free PDF Tools</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="alternate" hreflang="en" href="/en/about.html" />
|
||||
<link rel="alternate" hreflang="de" href="/de/about.html" />
|
||||
<link rel="alternate" hreflang="vi" href="/vi/about.html" />
|
||||
<link rel="alternate" hreflang="x-default" href="/en/about.html" />
|
||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>About BentoPDF - Privacy-First Free PDF Tools | Our Story</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Learn about BentoPDF - 100% free, privacy-first PDF toolkit. All tools run in your browser, no uploads, no signup. Fast, secure, and forever free."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="about bentopdf, privacy-first pdf, free pdf tools, browser-based pdf, open source pdf"
|
||||
/>
|
||||
<meta name="author" content="BentoPDF" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
|
||||
<body class="antialiased bg-gray-900 text-gray-300">
|
||||
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
|
||||
<span class="text-white font-bold text-xl ml-2">
|
||||
<a href="index.html">BentoPDF</a>
|
||||
</span>
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href="https://www.bentopdf.com/about" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://www.bentopdf.com/about" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="About BentoPDF - Privacy-First Free PDF Tools"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="100% free PDF toolkit. Privacy-first, browser-based, no signups. Learn about our mission and values."
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://www.bentopdf.com/images/og-about.png"
|
||||
/>
|
||||
<meta property="og:site_name" content="BentoPDF" />
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="About BentoPDF" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Privacy-first, free PDF tools that run in your browser."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://www.bentopdf.com/images/twitter-about.png"
|
||||
/>
|
||||
<meta name="twitter:site" content="@BentoPDF" />
|
||||
|
||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||
<link href="/src/css/landing.css" rel="stylesheet" />
|
||||
|
||||
<!-- Web App Manifest -->
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="192x192"
|
||||
href="/images/favicon-192x192.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="512x512"
|
||||
href="/images/favicon-512x512.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/images/apple-touch-icon.png"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||
</head>
|
||||
|
||||
<body class="lp antialiased">
|
||||
{{> navbar }}
|
||||
|
||||
<div id="app">
|
||||
<section id="story-section" class="lp-sec lp-sec--first">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-story">
|
||||
<div class="lp-story-head">
|
||||
<h1 class="lp-h2">
|
||||
<span class="line">Hi, I'm Alam.</span>
|
||||
<span class="line dim">I build BentoPDF.</span>
|
||||
</h1>
|
||||
<div class="lp-story-by">
|
||||
<img
|
||||
class="lp-avatar"
|
||||
src="/images/author-alam.jpg"
|
||||
alt="Alam"
|
||||
width="44"
|
||||
height="44"
|
||||
/>
|
||||
<a
|
||||
href="https://github.com/alam00000"
|
||||
target="_blank"
|
||||
rel="me noopener noreferrer"
|
||||
>GitHub</a
|
||||
>
|
||||
<a
|
||||
href="https://www.linkedin.com/in/abdullah-alam01/"
|
||||
target="_blank"
|
||||
rel="me noopener noreferrer"
|
||||
>LinkedIn</a
|
||||
>
|
||||
<a
|
||||
href="https://x.com/alamdraws"
|
||||
target="_blank"
|
||||
rel="me noopener noreferrer"
|
||||
>X</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lp-story-body">
|
||||
<p>
|
||||
BentoPDF started off as just a simple internal tool I used for
|
||||
quickly merging and splitting PDFs.
|
||||
</p>
|
||||
<p>
|
||||
At some point, I decided to
|
||||
<a
|
||||
href="https://www.reddit.com/r/webdev/comments/1nr937y/i_made_bentopdf_a_privacy_first_pdf_toolkit_that/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>share it on Reddit</a
|
||||
>. Someone in the comments
|
||||
<a
|
||||
href="https://www.reddit.com/r/webdev/comments/1nr937y/comment/ngdeqby/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>asked if it was open source</a
|
||||
>. I thought, “Why not?” So a couple of weeks later, I
|
||||
<a
|
||||
href="https://github.com/alam00000/bentopdf"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>put the code on GitHub</a
|
||||
>.
|
||||
</p>
|
||||
<p>
|
||||
I posted it on
|
||||
<a
|
||||
href="https://www.reddit.com/r/selfhosted/comments/1o5borw/bentopdf_is_a_self_hostable_pdf_toolkit/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>r/selfhosted</a
|
||||
>
|
||||
the next day, and that's when things really started to take off.
|
||||
</p>
|
||||
<p>
|
||||
I honestly never thought BentoPDF would grow into what it is
|
||||
today. It started as a little tool I made for myself, and
|
||||
somehow it turned into a much bigger project used by people all
|
||||
over the world.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lp-grid lp-grid--4 lp-cross">
|
||||
<div class="lp-cell lp-stat lp-cell--accent lp-reveal">
|
||||
<b>15K+</b><span>GitHub stars</span>
|
||||
</div>
|
||||
<div class="lp-cell lp-stat lp-cell--white lp-reveal">
|
||||
<b>10M+</b><span>Downloads</span>
|
||||
</div>
|
||||
<div class="lp-cell lp-stat lp-cell--hatch lp-reveal">
|
||||
<b>1.3K+</b><span>Forks</span>
|
||||
</div>
|
||||
<a
|
||||
href="https://github.com/alam00000/bentopdf/graphs/contributors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="lp-cell lp-stat lp-cell--raised lp-reveal lp-stat-link"
|
||||
>
|
||||
<b>60+</b
|
||||
><span>Contributors <i data-lucide="arrow-up-right"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="lp-story-outro">
|
||||
<p>
|
||||
I don't build BentoPDF alone anymore. More than 60 people have
|
||||
contributed code, translations and fixes, and thousands more have
|
||||
starred it, shared it, reported bugs and asked for features. Thank
|
||||
you.
|
||||
</p>
|
||||
<p>Enough about me. Here's more about BentoPDF.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
|
||||
<a href="/about.html" class="nav-link" data-i18n="nav.about">About</a>
|
||||
<a href="/contact.html" class="nav-link" data-i18n="nav.contact">Contact</a>
|
||||
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing">Licensing</a>
|
||||
<a href="index.html#tools-header" class="nav-link" data-i18n="nav.allTools">All Tools</a>
|
||||
<section id="mission-section" class="lp-sec lp-band">
|
||||
<div class="lp-frame">
|
||||
<figure class="lp-manifesto lp-reveal">
|
||||
<h2 class="lp-h2">
|
||||
<span class="line" data-i18n="about.mission.title">Our</span>
|
||||
<span class="line dim" data-i18n="about.mission.titleDim"
|
||||
>Mission</span
|
||||
>
|
||||
</h2>
|
||||
<blockquote
|
||||
class="lp-manifesto-text"
|
||||
data-i18n="about.mission.description"
|
||||
>
|
||||
To provide the most comprehensive PDF toolbox that respects your
|
||||
privacy and never asks for payment. We believe essential document
|
||||
tools should be accessible to everyone, everywhere, without
|
||||
barriers.
|
||||
</blockquote>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Mobile Hamburger Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-button" type="button"
|
||||
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
|
||||
aria-controls="mobile-menu" aria-expanded="false">
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<!-- Hamburger Icon -->
|
||||
<svg id="menu-icon" class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
<!-- Close Icon -->
|
||||
<svg id="close-icon" class="hidden h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
<section id="philosophy-section" class="lp-sec">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-head lp-head--center">
|
||||
<div>
|
||||
<h2 class="lp-h2">
|
||||
<span class="line" data-i18n="about.hero.title"
|
||||
>PDF tools should be</span
|
||||
>
|
||||
<span class="line dim" data-i18n="about.hero.subtitle"
|
||||
>fast, private, and free.</span
|
||||
>
|
||||
</h2>
|
||||
<p class="lp-head-p" data-i18n="about.hero.noCompromises">
|
||||
No compromises.
|
||||
</p>
|
||||
<p class="lp-head-p" data-i18n="about.philosophy.description">
|
||||
Every BentoPDF tool runs locally in your browser. Your files are
|
||||
never uploaded, nobody else sees your documents, and nothing
|
||||
tracks what you do.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lp-grid lp-grid--3 lp-cross">
|
||||
<div class="lp-cell lp-stat lp-cell--accent lp-reveal">
|
||||
<b>0</b
|
||||
><span data-i18n="features.noUploads.title">No Uploads</span>
|
||||
</div>
|
||||
<div class="lp-cell lp-stat lp-cell--white lp-reveal">
|
||||
<b>0</b><span data-i18n="hero.noSignups">No Signups</span>
|
||||
</div>
|
||||
<div class="lp-cell lp-stat lp-cell--hatch lp-reveal">
|
||||
<b>$0</b
|
||||
><span data-i18n="features.foreverFree.title">Forever Free</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="why-bentopdf" class="lp-sec lp-band">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-head lp-head--center">
|
||||
<div>
|
||||
<h2 class="lp-h2">
|
||||
<span class="line" data-i18n="about.whyBentopdf.title"
|
||||
>Why</span
|
||||
>
|
||||
<span class="line dim">BentoPDF?</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lp-grid lp-grid--2 lp-cross">
|
||||
<div class="lp-cell lp-reveal">
|
||||
<span class="lp-tile"><i data-lucide="zap"></i></span>
|
||||
<h3 data-i18n="about.whyBentopdf.speed.title">Built for Speed</h3>
|
||||
<p data-i18n="about.whyBentopdf.speed.description">
|
||||
There's no upload or download step. Files are processed right in
|
||||
your browser using WebAssembly, so you're never waiting on a
|
||||
server.
|
||||
</p>
|
||||
</div>
|
||||
<div class="lp-cell lp-reveal">
|
||||
<span class="lp-tile"
|
||||
><i data-lucide="badge-dollar-sign"></i
|
||||
></span>
|
||||
<h3 data-i18n="about.whyBentopdf.free.title">Free to Use</h3>
|
||||
<p data-i18n="about.whyBentopdf.free.description">
|
||||
Every tool is free, with no trials, no subscriptions and no
|
||||
watermarks. Development is funded by commercial licenses.
|
||||
</p>
|
||||
</div>
|
||||
<div class="lp-cell lp-reveal">
|
||||
<span class="lp-tile"><i data-lucide="user-plus"></i></span>
|
||||
<h3 data-i18n="about.whyBentopdf.noAccount.title">
|
||||
No Account Required
|
||||
</h3>
|
||||
<p data-i18n="about.whyBentopdf.noAccount.description">
|
||||
Open any tool and start right away. No email, no password, no
|
||||
personal information.
|
||||
</p>
|
||||
</div>
|
||||
<div class="lp-cell lp-reveal">
|
||||
<span class="lp-tile"><i data-lucide="code-2"></i></span>
|
||||
<h3 data-i18n="about.whyBentopdf.openSource.title">
|
||||
Open Source
|
||||
</h3>
|
||||
<p data-i18n="about.whyBentopdf.openSource.description">
|
||||
The code is public on GitHub under AGPL-3.0, so you can check
|
||||
exactly how your files are handled. It builds on open-source
|
||||
projects like PDF.js, pdf-lib, qpdf and LibreOffice.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="cta-section" class="lp-sec">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-cta">
|
||||
<h2 class="lp-h2" data-i18n="about.cta.title">
|
||||
Ready to get started?
|
||||
</h2>
|
||||
<p data-i18n="about.cta.description">
|
||||
Join thousands of users who trust BentoPDF for their daily
|
||||
document needs. Experience the difference that privacy and
|
||||
performance can make.
|
||||
</p>
|
||||
<div class="lp-cta-actions">
|
||||
<span class="lp-brackets">
|
||||
<a
|
||||
href="index.html#tools-header"
|
||||
class="lp-btn lp-btn--primary"
|
||||
>
|
||||
<span data-i18n="about.cta.button">Explore All Tools</span>
|
||||
<i data-lucide="arrow-right"></i>
|
||||
</a>
|
||||
</span>
|
||||
<a
|
||||
href="https://www.bentopdf.com/docs"
|
||||
class="lp-btn lp-btn--ghost"
|
||||
>
|
||||
<i data-lucide="book-open"></i>
|
||||
<span data-i18n="nav.docs">Docs</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Dropdown -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
||||
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
||||
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home">Home</a>
|
||||
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about">About</a>
|
||||
<a href="/contact.html" class="mobile-nav-link" data-i18n="nav.contact">Contact</a>
|
||||
<a href="/licensing.html" class="mobile-nav-link" data-i18n="nav.licensing">Licensing</a>
|
||||
<a href="index.html#tools-header" class="mobile-nav-link" data-i18n="nav.allTools">All Tools</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{{> footer }}
|
||||
|
||||
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
||||
<section id="about-hero" class="text-center py-16 md:py-24">
|
||||
<h1 class="text-3xl md:text-6xl font-bold text-white mb-4">
|
||||
<span data-i18n="about.hero.title">We believe PDF tools should be</span>
|
||||
<span class="marker-slanted" data-i18n="about.hero.subtitle">fast, private, and free.</span>
|
||||
</h1>
|
||||
<p class="text-lg md:text-xl text-gray-400" data-i18n="about.hero.noCompromises">No compromises.</p>
|
||||
</section>
|
||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||
<script type="module" src="src/js/main.ts"></script>
|
||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||
<script type="module" src="src/js/landing.ts"></script>
|
||||
|
||||
<div class="section-divider"></div>
|
||||
|
||||
<section id="mission-section" class="py-16 max-w-4xl mx-auto">
|
||||
<div class="text-center">
|
||||
<i data-lucide="rocket" class="w-16 h-16 text-indigo-400 mx-auto mb-6"></i>
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-white mb-4" data-i18n="about.mission.title">
|
||||
Our Mission
|
||||
</h2>
|
||||
<p class="text-lg text-gray-400 leading-relaxed" data-i18n="about.mission.description">
|
||||
To provide the most comprehensive PDF toolbox that respects your
|
||||
privacy and never asks for payment. We believe essential document
|
||||
tools should be accessible to everyone, everywhere, without
|
||||
barriers.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="bg-gray-800 rounded-xl p-8 md:p-12 my-16 border border-gray-700">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div class="text-center md:text-left">
|
||||
<span class="text-indigo-400 font-bold uppercase" data-i18n="about.philosophy.label">Our Core
|
||||
Philosophy</span>
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-white mt-2 mb-4" data-i18n="about.philosophy.title">
|
||||
Privacy First. Always.
|
||||
</h2>
|
||||
<p class="text-gray-400 leading-relaxed" data-i18n="about.philosophy.description">
|
||||
In an era where data is a commodity, we take a different approach.
|
||||
All processing for Bentopdf tools happens locally in your browser.
|
||||
This means your files never touch our servers, we never see your
|
||||
documents, and we don't track what you do. Your documents remain
|
||||
completely and unequivocally private. It's not just a feature;
|
||||
it's our foundation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative w-48 h-48">
|
||||
<div class="absolute inset-0 bg-indigo-500 rounded-full opacity-20 animate-pulse"></div>
|
||||
<div class="absolute inset-4 bg-indigo-500 rounded-full opacity-30 animate-pulse delay-500"></div>
|
||||
<i data-lucide="shield-check" class="w-48 h-48 text-indigo-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="why-Bentopdf" class="py-16">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-center text-white mb-12">
|
||||
<span data-i18n="about.whyBentopdf.title">Why</span> <span class="marker-slanted">Bentopdf?</span>
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
|
||||
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
||||
<i data-lucide="zap" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-white" data-i18n="about.whyBentopdf.speed.title">Built for Speed</h3>
|
||||
<p class="text-gray-400 mt-2" data-i18n="about.whyBentopdf.speed.description">
|
||||
No waiting for uploads or downloads to a server. By processing
|
||||
files directly in your browser using modern web technologies
|
||||
like WebAssembly, we offer unparalleled speed for all our tools.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
||||
<i data-lucide="badge-dollar-sign" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-white" data-i18n="about.whyBentopdf.free.title">Completely Free</h3>
|
||||
<p class="text-gray-400 mt-2" data-i18n="about.whyBentopdf.free.description">
|
||||
No trials, no subscriptions, no hidden fees, and no "premium"
|
||||
features held hostage. We believe powerful PDF tools should be a
|
||||
public utility, not a profit center.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
||||
<i data-lucide="user-plus" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-white" data-i18n="about.whyBentopdf.noAccount.title">No Account Required
|
||||
</h3>
|
||||
<p class="text-gray-400 mt-2" data-i18n="about.whyBentopdf.noAccount.description">
|
||||
Start using any tool immediately. We don't need your email, a
|
||||
password, or any personal information. Your workflow should be
|
||||
frictionless and anonymous.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-800 p-6 rounded-lg flex items-start gap-4">
|
||||
<i data-lucide="code-2" class="w-10 h-10 text-indigo-400 flex-shrink-0 mt-1"></i>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-white" data-i18n="about.whyBentopdf.openSource.title">Open Source Spirit
|
||||
</h3>
|
||||
<p class="text-gray-400 mt-2" data-i18n="about.whyBentopdf.openSource.description">
|
||||
Built with transparency in mind. We leverage incredible
|
||||
open-source libraries like PDF-lib and PDF.js, and believe in
|
||||
the community-driven effort to make powerful tools accessible to
|
||||
everyone.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="section-divider"></div>
|
||||
|
||||
<section id="cta-section" class="text-center py-16">
|
||||
<h2 class="text-3xl font-bold text-white mb-4" data-i18n="about.cta.title">
|
||||
Ready to get started?
|
||||
</h2>
|
||||
<p class="text-lg text-gray-400 mb-8 max-w-2xl mx-auto" data-i18n="about.cta.description">
|
||||
Join thousands of users who trust Bentopdf for their daily document
|
||||
needs. Experience the difference that privacy and performance can
|
||||
make.
|
||||
</p>
|
||||
<a href="index.html#tools-header"
|
||||
class="inline-block px-8 py-3 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1"
|
||||
data-i18n="about.cta.button">
|
||||
Explore All Tools
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<footer class="mt-16 border-t-2 border-gray-700 py-8">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
||||
<div class="mb-8 md:mb-0">
|
||||
<div class="flex items-center justify-center md:justify-start mb-4">
|
||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
||||
<span class="text-xl font-bold text-white">BentoPDF</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
|
||||
© 2025 BentoPDF. All rights reserved.
|
||||
</p>
|
||||
<p class="text-gray-500 text-xs mt-2">
|
||||
<span data-i18n="footer.version">Version</span> <span id="app-version"></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">Company</h3>
|
||||
<ul class="space-y-2 text-gray-400">
|
||||
<li>
|
||||
<a href="/about.html" class="hover:text-indigo-400" data-i18n="footer.aboutUs">About Us</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/faq.html" class="hover:text-indigo-400" data-i18n="footer.faqLink">FAQ</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/contact.html" class="hover:text-indigo-400" data-i18n="footer.contactUs">Contact Us</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">Legal</h3>
|
||||
<ul class="space-y-2 text-gray-400">
|
||||
<li>
|
||||
<a href="/licensing.html" class="hover:text-indigo-400" data-i18n="nav.licensing">Licensing</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/terms.html" class="hover:text-indigo-400" data-i18n="footer.termsAndConditions">Terms and
|
||||
Conditions</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/privacy.html" class="hover:text-indigo-400" data-i18n="footer.privacyPolicy">Privacy Policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">Follow Us</h3>
|
||||
<div class="flex justify-center md:justify-start space-x-4">
|
||||
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
||||
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://discord.gg/Bgq3Ay3f2w" target="_blank" rel="noopener noreferrer"
|
||||
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.instagram.com/thebentopdf/" class="text-gray-400 hover:text-indigo-400"
|
||||
title="Instagram">
|
||||
<i data-lucide="instagram"></i>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/company/bentopdf/" class="text-gray-400 hover:text-indigo-400"
|
||||
title="LinkedIn">
|
||||
<i data-lucide="linkedin"></i>
|
||||
</a>
|
||||
<a href="https://x.com/BentoPDF" class="text-gray-400 hover:text-indigo-400" title="X (Twitter)">
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||
<script type="module" src="src/version.ts"></script>
|
||||
<script type="module" src="src/js/main.ts"></script>
|
||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<!-- JSON-LD Schema -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "AboutPage",
|
||||
"name": "About BentoPDF",
|
||||
"description": "How BentoPDF started, told by its creator Alam",
|
||||
"url": "https://www.bentopdf.com/about",
|
||||
"mainEntity": {
|
||||
"@type": "Person",
|
||||
"name": "Alam",
|
||||
"url": "https://www.bentopdf.com/blog/author-alam",
|
||||
"image": "https://www.bentopdf.com/images/author-alam.jpg",
|
||||
"jobTitle": "Creator and maintainer of BentoPDF",
|
||||
"sameAs": [
|
||||
"https://github.com/alam00000",
|
||||
"https://www.linkedin.com/in/abdullah-alam01/",
|
||||
"https://x.com/alamdraws"
|
||||
]
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Advertise on BentoPDF - Homepage Logo Ads | BentoPDF</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Put your logo on the BentoPDF homepage. Eight slots, a flat monthly price and no tracking. From $129 a month, cancel anytime."
|
||||
/>
|
||||
<meta name="author" content="BentoPDF" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
|
||||
<link rel="canonical" href="https://www.bentopdf.com/advertise" />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://www.bentopdf.com/advertise" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Advertise on BentoPDF - Homepage Logo Ads | BentoPDF"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Put your logo on the BentoPDF homepage. Eight slots, a flat monthly price and no tracking. From $129 a month, cancel anytime."
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://www.bentopdf.com/images/og-tools.png"
|
||||
/>
|
||||
<meta property="og:site_name" content="BentoPDF" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Advertise on BentoPDF - Homepage Logo Ads | BentoPDF"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Put your logo on the BentoPDF homepage. Eight slots, a flat monthly price and no tracking. From $129 a month, cancel anytime."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://www.bentopdf.com/images/og-tools.png"
|
||||
/>
|
||||
<meta name="twitter:site" content="@BentoPDF" />
|
||||
|
||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||
<link href="/src/css/landing.css" rel="stylesheet" />
|
||||
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="192x192"
|
||||
href="/images/favicon-192x192.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="512x512"
|
||||
href="/images/favicon-512x512.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/images/apple-touch-icon.png"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||
</head>
|
||||
|
||||
<body class="lp antialiased">
|
||||
{{> navbar }}
|
||||
|
||||
<div id="app">
|
||||
<section id="ad-paid" class="lp-sec" hidden>
|
||||
<div class="lp-frame">
|
||||
<div class="lp-pad--tight">
|
||||
<div class="lp-note">
|
||||
<i data-lucide="circle-check"></i>
|
||||
<p>
|
||||
<strong>Payment received, thanks.</strong>
|
||||
We review every ad and put it live within 24 hours. To change
|
||||
anything, email
|
||||
<a
|
||||
href="mailto:[email protected]?subject=Ad%20for%20BentoPDF"
|
||||
class="text-indigo-400 hover:underline"
|
||||
>[email protected]</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="ad-failed" class="lp-sec" hidden>
|
||||
<div class="lp-frame">
|
||||
<div class="lp-pad--tight">
|
||||
<div class="lp-note">
|
||||
<i data-lucide="circle-alert"></i>
|
||||
<p>
|
||||
<strong>Checkout didn't go through.</strong>
|
||||
Nothing was charged. Try again below or email
|
||||
<a
|
||||
href="mailto:[email protected]?subject=Advertise%20on%20BentoPDF"
|
||||
class="text-indigo-400 hover:underline"
|
||||
>[email protected]</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="advertise-hero" class="lp-sec lp-sec--first">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-head lp-head--center">
|
||||
<div>
|
||||
<h1 class="lp-h1">
|
||||
<span class="line">Advertise on</span>
|
||||
<span class="line dim">BentoPDF</span>
|
||||
</h1>
|
||||
<p class="lp-head-p">
|
||||
Put your logo on the BentoPDF homepage, right above the tools
|
||||
people open to work on their PDFs. Eight slots, a flat monthly
|
||||
price, and no tracking of any kind.
|
||||
</p>
|
||||
<div class="lp-actions">
|
||||
<span class="lp-brackets">
|
||||
<a href="#pricing" class="lp-btn lp-btn--primary">
|
||||
<span>See pricing</span>
|
||||
<i data-lucide="arrow-right"></i>
|
||||
</a>
|
||||
</span>
|
||||
<a
|
||||
href="mailto:[email protected]?subject=Advertise%20on%20BentoPDF"
|
||||
class="lp-btn lp-btn--ghost"
|
||||
>
|
||||
<i data-lucide="mail"></i>
|
||||
<span>Email us</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="pricing" class="lp-sec lp-band">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-head lp-head--center">
|
||||
<div>
|
||||
<h2 class="lp-h2">
|
||||
<span class="line">Pick a</span>
|
||||
<span class="line dim">row</span>
|
||||
</h2>
|
||||
<p class="lp-head-p">
|
||||
Billed monthly. Cancel anytime and your ad stays up until the
|
||||
end of the month you paid for.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lp-grid lp-grid--2">
|
||||
<div class="lp-cell lp-tier lp-cell--accent lp-reveal">
|
||||
<h3>Top row</h3>
|
||||
<p class="lp-tier-for">
|
||||
Four slots, first in line above the tools
|
||||
</p>
|
||||
<div class="lp-tier-price"><b>$199</b><span>/ month</span></div>
|
||||
<ul>
|
||||
<li><i data-lucide="check"></i>Larger tile in the first row</li>
|
||||
<li><i data-lucide="check"></i>Your logo, name and link</li>
|
||||
<li>
|
||||
<i data-lucide="check"></i>On every language version of the
|
||||
homepage
|
||||
</li>
|
||||
<li><i data-lucide="check"></i>Cancel anytime</li>
|
||||
</ul>
|
||||
<a
|
||||
href="https://www.bentopdf.com/api/ad-checkout?row=top"
|
||||
class="lp-btn lp-btn--primary lp-btn--sm"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Book the top row</a
|
||||
>
|
||||
</div>
|
||||
<div class="lp-cell lp-tier lp-reveal">
|
||||
<h3>Second row</h3>
|
||||
<p class="lp-tier-for">Four slots, right below the top row</p>
|
||||
<div class="lp-tier-price"><b>$129</b><span>/ month</span></div>
|
||||
<ul>
|
||||
<li>
|
||||
<i data-lucide="check"></i>Standard tile in the second row
|
||||
</li>
|
||||
<li><i data-lucide="check"></i>Your logo, name and link</li>
|
||||
<li>
|
||||
<i data-lucide="check"></i>On every language version of the
|
||||
homepage
|
||||
</li>
|
||||
<li><i data-lucide="check"></i>Cancel anytime</li>
|
||||
</ul>
|
||||
<a
|
||||
href="https://www.bentopdf.com/api/ad-checkout?row=second"
|
||||
class="lp-btn lp-btn--ghost lp-btn--sm"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Book the second row</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="lp-sec">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-head lp-head--center">
|
||||
<div>
|
||||
<h2 class="lp-h2">
|
||||
<span class="line">How it</span>
|
||||
<span class="line dim">works</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lp-grid lp-grid--3">
|
||||
<div class="lp-cell lp-reveal">
|
||||
<span class="lp-tile"><i data-lucide="credit-card"></i></span>
|
||||
<h3>Book a row</h3>
|
||||
<p>
|
||||
Checkout is handled by Dodo Payments, our merchant of record, so
|
||||
you get a proper invoice with sales tax taken care of.
|
||||
</p>
|
||||
</div>
|
||||
<div class="lp-cell lp-reveal">
|
||||
<span class="lp-tile"><i data-lucide="image"></i></span>
|
||||
<h3>Add your ad</h3>
|
||||
<p>
|
||||
Checkout asks for the name to show, your link, and a link to
|
||||
your logo as a square SVG or PNG of at least 128 by 128 pixels.
|
||||
</p>
|
||||
</div>
|
||||
<div class="lp-cell lp-reveal">
|
||||
<span class="lp-tile"><i data-lucide="rocket"></i></span>
|
||||
<h3>Go live</h3>
|
||||
<p>
|
||||
We review every ad by hand and put it live within 24 hours. Need
|
||||
a new logo or link later? Just email us.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="lp-sec">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-pad--tight">
|
||||
<div class="lp-note lp-reveal">
|
||||
<i data-lucide="eye-off"></i>
|
||||
<p>
|
||||
<strong
|
||||
>We don't track our visitors, so we don't track your ad
|
||||
either.</strong
|
||||
>
|
||||
No pixels, no scripts, no impression or click counts. Your logo
|
||||
is served from bentopdf.com and links straight to your site. To
|
||||
measure visits, add UTM parameters to your link and read them in
|
||||
your own analytics.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="lp-sec">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-head lp-head--center">
|
||||
<div>
|
||||
<h2 class="lp-h2">
|
||||
<span class="line">Frequently asked</span>
|
||||
<span class="line dim">questions</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lp-faq-wrap">
|
||||
<div id="faq-accordion" class="lp-faq lp-reveal">
|
||||
<div class="faq-item">
|
||||
<button
|
||||
class="faq-question w-full flex justify-between items-center text-left"
|
||||
type="button"
|
||||
>
|
||||
<span>What can I advertise?</span>
|
||||
<i data-lucide="plus" class="faq-icon"></i>
|
||||
</button>
|
||||
<div
|
||||
class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<p>
|
||||
Software, services and products that are useful to people
|
||||
who work with documents. We don't accept gambling, adult
|
||||
content, malware, or anything misleading. If we turn your ad
|
||||
down, we refund you in full.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<button
|
||||
class="faq-question w-full flex justify-between items-center text-left"
|
||||
type="button"
|
||||
>
|
||||
<span>Where does my ad show?</span>
|
||||
<i data-lucide="plus" class="faq-icon"></i>
|
||||
</button>
|
||||
<div
|
||||
class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<p>
|
||||
On the bentopdf.com homepage, in every language, directly
|
||||
above the tool grid. Self-hosted copies of BentoPDF never
|
||||
show ads.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<button
|
||||
class="faq-question w-full flex justify-between items-center text-left"
|
||||
type="button"
|
||||
>
|
||||
<span>Can you tell me how many people saw my ad?</span>
|
||||
<i data-lucide="plus" class="faq-icon"></i>
|
||||
</button>
|
||||
<div
|
||||
class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<p>
|
||||
No. BentoPDF has no analytics, so there are no impression or
|
||||
click numbers to share. Add UTM parameters to your link to
|
||||
see the visits we send you in your own analytics.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<button
|
||||
class="faq-question w-full flex justify-between items-center text-left"
|
||||
type="button"
|
||||
>
|
||||
<span>How do I cancel?</span>
|
||||
<i data-lucide="plus" class="faq-icon"></i>
|
||||
</button>
|
||||
<div
|
||||
class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<p>
|
||||
Email us and we'll cancel your subscription. Your ad stays
|
||||
up until the end of the month you paid for.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="lp-sec">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-cta">
|
||||
<h2 class="lp-h2">Questions before you book?</h2>
|
||||
<p>Email us about slots, pricing or anything else.</p>
|
||||
<div class="lp-cta-actions">
|
||||
<span class="lp-brackets">
|
||||
<a
|
||||
href="mailto:[email protected]?subject=Advertise%20on%20BentoPDF"
|
||||
class="lp-btn lp-btn--primary"
|
||||
>
|
||||
<span>[email protected]</span>
|
||||
<i data-lucide="arrow-right"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{{> footer }}
|
||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||
<script type="module" src="src/js/main.ts"></script>
|
||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||
<script type="module" src="src/js/landing.ts"></script>
|
||||
<script type="module" src="src/js/advertise.ts"></script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebPage",
|
||||
"name": "Advertise on BentoPDF - Homepage Logo Ads | BentoPDF",
|
||||
"url": "https://www.bentopdf.com/advertise"
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,92 @@
|
||||
const MODES = {
|
||||
live: {
|
||||
api: 'https://live.dodopayments.com',
|
||||
products: new Map([
|
||||
['top', 'pdt_0Nqv9McKI1iCBXb7z4DB7'],
|
||||
['second', 'pdt_0Nqv9OYDLwWZsu1hzpZ0c'],
|
||||
]),
|
||||
},
|
||||
test: {
|
||||
api: 'https://test.dodopayments.com',
|
||||
products: new Map([
|
||||
['top', 'pdt_0Nqt0672sYJEtrkIb2K6m'],
|
||||
['second', 'pdt_0Nqt069LgdQFPo1tFS1yF'],
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
const FIELDS = [
|
||||
{
|
||||
key: 'ad_name',
|
||||
label: 'Name on the ad',
|
||||
field_type: 'text',
|
||||
placeholder: 'Acme Invoices',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'ad_link',
|
||||
label: 'Link',
|
||||
field_type: 'url',
|
||||
placeholder: 'https://example.com',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'ad_logo',
|
||||
label: 'Logo link (square SVG or PNG)',
|
||||
field_type: 'url',
|
||||
placeholder: 'https://example.com/logo.svg',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
function redirect(location) {
|
||||
return new Response(null, {
|
||||
status: 303,
|
||||
headers: { Location: location, 'Cache-Control': 'no-store' },
|
||||
});
|
||||
}
|
||||
|
||||
async function createCheckout(key, api, productId, page) {
|
||||
const response = await fetch(`${api}/checkouts`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${key}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
product_cart: [{ product_id: productId, quantity: 1 }],
|
||||
custom_fields: FIELDS,
|
||||
return_url: `${page}?paid=1`,
|
||||
cancel_url: `${page}#pricing`,
|
||||
}),
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
const body = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(`Dodo returned ${response.status}: ${body}`);
|
||||
}
|
||||
const { checkout_url } = JSON.parse(body);
|
||||
if (
|
||||
typeof checkout_url !== 'string' ||
|
||||
!checkout_url.startsWith('https://')
|
||||
) {
|
||||
throw new Error('Dodo returned no checkout URL');
|
||||
}
|
||||
return checkout_url;
|
||||
}
|
||||
|
||||
export async function GET(request) {
|
||||
const url = new URL(request.url);
|
||||
const page = `${url.origin}/advertise`;
|
||||
const key = process.env.DODO_PAYMENTS_API_KEY ?? '';
|
||||
const mode = key.startsWith('dodo_live_') ? MODES.live : MODES.test;
|
||||
const productId = mode.products.get(url.searchParams.get('row'));
|
||||
if (!productId) return redirect(`${page}#pricing`);
|
||||
|
||||
try {
|
||||
return redirect(await createCheckout(key, mode.api, productId, page));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return redirect(`${page}?checkout=failed`);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,149 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Alam, Creator of BentoPDF</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Alam builds BentoPDF, the open-source, privacy-first PDF toolkit that runs entirely in your browser. Every post on this blog is written by him."
|
||||
/>
|
||||
<meta name="robots" content="index, follow" />
|
||||
<link rel="canonical" href="https://www.bentopdf.com/blog/author-alam" />
|
||||
<meta property="og:type" content="profile" />
|
||||
<meta
|
||||
property="og:url"
|
||||
content="https://www.bentopdf.com/blog/author-alam"
|
||||
/>
|
||||
<meta property="og:title" content="Alam, Creator of BentoPDF" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="The maintainer behind BentoPDF and the author of every post on this blog."
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://www.bentopdf.com/images/author-alam.jpg"
|
||||
/>
|
||||
<meta property="og:site_name" content="BentoPDF" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body class="antialiased bg-gray-900">
|
||||
{{> navbar }}
|
||||
|
||||
<section class="max-w-3xl mx-auto px-4 py-12">
|
||||
<div class="flex items-center gap-5 mb-8">
|
||||
<img
|
||||
src="/images/author-alam.jpg"
|
||||
alt="Alam"
|
||||
width="80"
|
||||
height="80"
|
||||
class="rounded-full"
|
||||
/>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-white">Alam</h1>
|
||||
<p class="text-gray-400">Creator and maintainer of BentoPDF</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-gray-300 leading-relaxed mb-5">
|
||||
I’m Alam, and I build
|
||||
<a href="/" class="text-indigo-300 hover:text-indigo-400">BentoPDF</a>,
|
||||
a free, open-source PDF toolkit with 100+ tools that run entirely in
|
||||
your browser.
|
||||
</p>
|
||||
<p class="text-gray-300 leading-relaxed mb-5">
|
||||
There’s no uploading your files to a server, no account to create, and
|
||||
no tracking. Your documents stay on your device. The
|
||||
<a
|
||||
href="https://github.com/alam00000/bentopdf"
|
||||
rel="noopener"
|
||||
class="text-indigo-300 hover:text-indigo-400"
|
||||
>source code</a
|
||||
>
|
||||
is public, so you can see how it works yourself. BentoPDF is
|
||||
AGPL-licensed and self-hostable.
|
||||
</p>
|
||||
<p class="text-gray-300 leading-relaxed mb-5">
|
||||
I write every post on this blog myself. I’ll write about PDFs, how they
|
||||
work, things I learn while building BentoPDF, and occasionally things
|
||||
that go wrong.
|
||||
</p>
|
||||
<p class="text-gray-300 leading-relaxed mb-5">
|
||||
When I compare BentoPDF with another PDF tool, I’ll link to its
|
||||
documentation so you can check what I’m saying. If there’s something
|
||||
BentoPDF doesn’t do well, I’ll say that too.
|
||||
</p>
|
||||
<p class="text-gray-300 leading-relaxed mb-5">
|
||||
And if I get something wrong, please
|
||||
<a
|
||||
href="https://github.com/alam00000/bentopdf/issues"
|
||||
rel="noopener"
|
||||
class="text-indigo-300 hover:text-indigo-400"
|
||||
>open an issue</a
|
||||
>. I’ll fix it and add a note to the post.
|
||||
</p>
|
||||
|
||||
<p class="text-gray-300 leading-relaxed mb-5">
|
||||
You can find me on
|
||||
<a
|
||||
href="https://github.com/alam00000"
|
||||
rel="me noopener"
|
||||
class="text-indigo-300 hover:text-indigo-400"
|
||||
>GitHub</a
|
||||
>,
|
||||
<a
|
||||
href="https://www.linkedin.com/in/abdullah-alam01/"
|
||||
rel="me noopener"
|
||||
class="text-indigo-300 hover:text-indigo-400"
|
||||
>LinkedIn</a
|
||||
>
|
||||
and
|
||||
<a
|
||||
href="https://x.com/alamdraws"
|
||||
rel="me noopener"
|
||||
class="text-indigo-300 hover:text-indigo-400"
|
||||
>X</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<p class="text-gray-300 leading-relaxed">
|
||||
<a
|
||||
href="/blog"
|
||||
class="inline-flex items-center gap-2 text-indigo-300 hover:text-indigo-400 font-semibold"
|
||||
>Read the blog <i class="ph ph-arrow-right" aria-hidden="true"></i
|
||||
></a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{> footer }}
|
||||
<script type="module" src="/src/js/utils/lucide-init.ts"></script>
|
||||
<script type="module">
|
||||
import '@phosphor-icons/web/regular';
|
||||
</script>
|
||||
<script type="module" src="/src/js/mobileMenu.ts"></script>
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ProfilePage",
|
||||
"mainEntity": {
|
||||
"@type": "Person",
|
||||
"name": "Alam",
|
||||
"url": "https://www.bentopdf.com/blog/author-alam",
|
||||
"image": "https://www.bentopdf.com/images/author-alam.jpg",
|
||||
"jobTitle": "Creator and maintainer of BentoPDF",
|
||||
"sameAs": [
|
||||
"https://github.com/alam00000",
|
||||
"https://www.linkedin.com/in/abdullah-alam01/",
|
||||
"https://x.com/alamdraws"
|
||||
]
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
{
|
||||
"title": "How to Edit a PDF Without Uploading It Anywhere | BentoPDF Blog",
|
||||
"h1": "How to edit a PDF without uploading it anywhere",
|
||||
"ogTitle": "How to Edit a PDF Without Uploading It Anywhere",
|
||||
"breadcrumb": "Edit a PDF without uploading",
|
||||
"description": "Edit PDF text, fill forms, redact, and sign in your browser while the file stays on your device. Step by step, with the right tool for each job.",
|
||||
"card": "Rewrite text, fill forms, redact properly, and sign, locally on your device.",
|
||||
"date": "2026-09-03",
|
||||
"cta": {
|
||||
"heading": "Edit your PDF now",
|
||||
"text": "Free, no signup. Your file stays on your device.",
|
||||
"href": "/edit-pdf",
|
||||
"label": "Open the PDF editor"
|
||||
}
|
||||
}
|
||||
---
|
||||
You can edit a PDF in your browser without uploading the file to a server.
|
||||
|
||||
Open [BentoPDF's PDF editor](/edit-pdf), pick your file, make your changes, and download the result. The PDF stays on your device. There's no account and no watermark.
|
||||
|
||||
But “edit a PDF” can mean a few different things. You might want to change existing text, fill out a form, add a signature, redact something, or just highlight a paragraph. The right tool depends on what you're trying to do.
|
||||
|
||||
## Why edit a PDF without uploading it?
|
||||
|
||||
Think about the PDFs you normally edit.
|
||||
|
||||
Contracts. Bank statements. Tax documents. Forms with your address or ID number.
|
||||
|
||||
Those aren't necessarily files you want to upload to a random “free PDF editor.”
|
||||
|
||||
With an on-device PDF editor, the file stays on your computer while you're working on it.
|
||||
|
||||
You can even check this yourself. Open your browser's developer tools, go to the Network tab, and watch what happens while you edit. The PDF itself doesn't need to be sent to a server.
|
||||
|
||||
## How to edit the actual text in a PDF
|
||||
|
||||
If you want to change words that are already in the PDF, use [Edit PDF Text](/edit-pdf-text).
|
||||
|
||||
1. Open the tool.
|
||||
2. Drop in your PDF.
|
||||
3. Click on the text you want to change.
|
||||
4. Type your changes.
|
||||
5. Download the edited PDF.
|
||||
|
||||
BentoPDF loads the PDF in your browser and does the editing on your device.
|
||||
|
||||
This is one of the harder things to do with PDFs because PDF text doesn't work quite like text in a Word document. The PDF is mostly concerned with putting things at particular positions on a page, so changing a sentence can mean figuring out how the surrounding text should move.
|
||||
|
||||
It works well for ordinary documents, but PDFs with unusual embedded fonts or very complicated layouts can be trickier. I'd always open the downloaded PDF and check it.
|
||||
|
||||
## How to annotate a PDF
|
||||
|
||||
If you don't need to change the existing text, annotating a PDF is much simpler.
|
||||
|
||||
The [PDF editor](/edit-pdf) lets you add highlights, comments, shapes, stamps, text boxes, and images.
|
||||
|
||||
Open the PDF, add your annotations, and download the result.
|
||||
|
||||
Again, the editing happens in your browser rather than on a remote server.
|
||||
|
||||
## How to fill out a PDF form
|
||||
|
||||
There are two common kinds of PDF forms.
|
||||
|
||||
If the PDF has actual form fields, open it in [PDF Form Filler](/form-filler) and type into the fields. It handles standard AcroForm fields, and also XFA forms, which are the legacy XML-based format. XFA is worth calling out because Chrome and most other viewers show those files as blank pages, which is why some government forms tell you to open them in Adobe Reader. Firefox and BentoPDF's form filler render them properly.
|
||||
|
||||
If it's a scanned or flattened form, there aren't any fields to click. In that case, you can put text boxes over the blank spaces in the [PDF editor](/edit-pdf) and type into them. If you'd rather have real fields, [Create PDF Form](/form-creator) can add them to the document first.
|
||||
|
||||
This is especially useful for government and HR forms, which often contain personal information I'd rather not upload somewhere just to fill in a few boxes.
|
||||
|
||||
If the PDF is a scan and you actually need to edit the scanned text itself, you'll need OCR first. [BentoPDF's OCR tool](/ocr-pdf) can recognize the text in the document while keeping the processing on your device.
|
||||
|
||||
## How to redact a PDF properly
|
||||
|
||||
This one is important because putting a black box over text isn't necessarily redaction.
|
||||
|
||||
If you draw a rectangle over a name, the original name may still exist underneath it. It might still be selectable, searchable, or extractable.
|
||||
|
||||
Proper redaction removes the underlying content.
|
||||
|
||||
The [PDF editor](/edit-pdf) has redaction tools that permanently remove the underlying content rather than drawing over it. For documents where you also want to strip metadata or other hidden content, [Sanitize PDF](/sanitize-pdf) can help with that.
|
||||
|
||||
There's also a privacy reason to do redaction locally: if you upload the original, unredacted PDF to a redaction service, that service has already received the information you're trying to hide.
|
||||
|
||||
## How to sign a PDF without uploading it
|
||||
|
||||
[Sign PDF](/sign-pdf) lets you add a drawn, typed, or image signature to a PDF. Place it on the page, resize it, and optionally flatten it so it becomes part of the page instead of an annotation someone can drag away later.
|
||||
|
||||
The signature is added on your device, so you don't need to upload the document to a signing service just to place a signature on it.
|
||||
|
||||
One thing to keep in mind: adding a signature image to a PDF is not the same thing as applying a cryptographic digital signature. If the recipient needs a signature that can actually be verified, use [Digital Signature](/digital-sign-pdf) instead. It signs the document with an X.509 certificate (PKCS#12 .pfx and .p12, or PEM), which proves who signed it and shows whether anything changed afterwards. You can also add a visible signature block on top of that. The certificate password and private key stay in your browser, same as the PDF.
|
||||
|
||||
## Can you edit a PDF offline?
|
||||
|
||||
Yes.
|
||||
|
||||
There's an easy way to test it: load the editor, turn off Wi-Fi, and edit a PDF.
|
||||
|
||||
The PDF processing code runs in your browser using WebAssembly. Once the application has loaded, the actual PDF processing doesn't need a server.
|
||||
|
||||
This is also something you can inspect yourself in your browser's developer tools. The website is how you get the software; it doesn't have to be where your PDF gets processed.
|
||||
|
||||
If you want to run the whole thing yourself, BentoPDF is [open source and self-hostable](https://github.com/alam00000/bentopdf). You can run your own instance instead of relying on ours.
|
||||
|
||||
## What about scanned PDFs?
|
||||
|
||||
A scanned PDF is basically a collection of images.
|
||||
|
||||
If you want to change the words in it, an ordinary PDF text editor can't simply edit them because there isn't any text there yet.
|
||||
|
||||
You'll need OCR (optical character recognition) to turn the pixels into text.
|
||||
|
||||
You can either OCR the PDF first and then edit the resulting text, or, if you're just filling out a scanned form, put text boxes on top of the existing page.
|
||||
|
||||
## Is there a file size limit?
|
||||
|
||||
There isn't an imposed file-size limit.
|
||||
|
||||
The practical limit is your device's available memory. Normal PDFs are usually fine, but a thousand-page scanned archive is a very different workload from a five-page contract.
|
||||
|
||||
For very large PDFs, a desktop application may be a better choice.
|
||||
|
||||
## Will editing a digitally signed PDF break the signature?
|
||||
|
||||
Yes.
|
||||
|
||||
Changing a digitally signed PDF will invalidate its existing signature. This isn't specific to BentoPDF; it's a consequence of changing a document that has already been signed.
|
||||
|
||||
So the usual order is:
|
||||
|
||||
**Edit first. Sign last.**
|
||||
|
||||
If you need to make changes to a signed document, make sure you understand the signature requirements before doing so.
|
||||
@@ -0,0 +1,152 @@
|
||||
---
|
||||
{
|
||||
"title": "iLovePDF Alternative With No Uploads: BentoPDF vs iLovePDF",
|
||||
"h1": "BentoPDF vs iLovePDF: a free iLovePDF alternative that doesn't upload your files",
|
||||
"ogTitle": "iLovePDF Alternative With No Uploads: BentoPDF vs iLovePDF",
|
||||
"breadcrumb": "iLovePDF alternative",
|
||||
"description": "BentoPDF vs iLovePDF compared. BentoPDF is free, has no file limits and runs in your browser, so your PDFs are never uploaded. See where each one wins.",
|
||||
"card": "BentoPDF vs iLovePDF side by side: uploads, free plan limits, offline use, and the tools you get for free.",
|
||||
"date": "2026-10-28",
|
||||
"cta": {
|
||||
"heading": "Try it on your own PDF",
|
||||
"text": "Free, no signup. Your file stays on your device.",
|
||||
"href": "/",
|
||||
"label": "Open BentoPDF"
|
||||
}
|
||||
}
|
||||
---
|
||||
If you're looking for an iLovePDF alternative, the biggest difference with BentoPDF is where your file goes.
|
||||
|
||||
iLovePDF uploads your PDF and processes it on its servers. BentoPDF processes it in your browser, on your own device. It's free, there's no account, and there's no cap on file size or the number of files.
|
||||
|
||||
I build BentoPDF, so keep that in mind. Whenever I say what iLovePDF does, I've linked to iLovePDF's own pages so you can check.
|
||||
|
||||
## Why people look for an iLovePDF alternative
|
||||
|
||||
iLovePDF is a good tool. People usually switch for one of these reasons:
|
||||
|
||||
- **Your file gets uploaded.** Every task sends the document to iLovePDF's servers. That's a problem for contracts, payslips and ID scans.
|
||||
- **The free plan has limits.** Word to PDF takes one file of up to 15 MB. Merge stops at 25 files. That's from iLovePDF's [pricing page](https://www.ilovepdf.com/pricing).
|
||||
- **Some tools are Premium only.** PDF to PDF/A, Workflows and the apps are all paid.
|
||||
- **It doesn't work offline.** The work happens on a server, so the website needs a connection.
|
||||
|
||||
## BentoPDF vs iLovePDF: side by side
|
||||
|
||||
Here's how BentoPDF compares with iLovePDF's free plan:
|
||||
|
||||
| Feature | iLovePDF (free plan) | BentoPDF |
|
||||
| --- | --- | --- |
|
||||
| Where your file is processed | Uploaded to iLovePDF's servers | In your browser, never uploaded |
|
||||
| Price | Free with limits, Premium is paid | Free, every tool |
|
||||
| Word to PDF | 1 file, up to 15 MB | No cap on files or size |
|
||||
| Merge PDF | Up to 25 files, 100 MB | No cap on files or size |
|
||||
| Compress PDF | 2 files at a time | No cap on files |
|
||||
| Edit PDF | 1 file | No cap on files |
|
||||
| PDF to PDF/A | Premium only | Free |
|
||||
| Workflows | Premium only | Free |
|
||||
| Works offline | No (web version) | Yes, once a tool has loaded |
|
||||
| Open source | No | Yes, AGPL |
|
||||
| Run it on your own server | No | Yes, with Docker or as static files |
|
||||
| Desktop and mobile apps | Premium only | No, browser only |
|
||||
| What happens to your files | Deleted within two hours, signed documents kept up to five years | Never uploaded in the first place |
|
||||
|
||||
iLovePDF's limits come from its [pricing page](https://www.ilovepdf.com/pricing), and the file deletion details from its [security page](https://www.ilovepdf.com/help/security).
|
||||
|
||||
The only real limit in BentoPDF is your device's memory. A 500-page scan needs a lot more of it than a two-page letter.
|
||||
|
||||
## Your files never leave your device
|
||||
|
||||
iLovePDF's security page says processed files are "automatically and permanently deleted within two hours." It also says signed documents are kept "for a maximum of 5 years in compliance with legal requirements."
|
||||
|
||||
Two hours is reasonable. But "we delete your file later" is a weaker promise than "we never get your file."
|
||||
|
||||
BentoPDF's tools run in your browser using WebAssembly, so your PDF doesn't go anywhere. You can check this yourself:
|
||||
|
||||
1. Open your browser's developer tools and go to the Network tab.
|
||||
2. Process a file in BentoPDF.
|
||||
3. Watch the requests. Your PDF isn't in them.
|
||||
|
||||
Or load a tool, turn off Wi-Fi, and use it anyway.
|
||||
|
||||
## BentoPDF tools for the iLovePDF tools you use
|
||||
|
||||
Switching is easy. These are the BentoPDF versions of iLovePDF's most used tools:
|
||||
|
||||
| iLovePDF tool | BentoPDF tool |
|
||||
| --- | --- |
|
||||
| Merge PDF | [Merge PDF](/merge-pdf) |
|
||||
| Split PDF | [Split PDF](/split-pdf) |
|
||||
| Compress PDF | [Compress PDF](/compress-pdf) |
|
||||
| PDF to Word | [PDF to Word](/pdf-to-word) |
|
||||
| Word to PDF | [Word to PDF](/word-to-pdf) |
|
||||
| PDF to Excel | [PDF to Excel](/pdf-to-excel) |
|
||||
| Excel to PDF | [Excel to PDF](/excel-to-pdf) |
|
||||
| PowerPoint to PDF | [PowerPoint to PDF](/powerpoint-to-pdf) |
|
||||
| PDF to JPG | [PDF to JPG](/pdf-to-jpg) |
|
||||
| JPG to PDF | [JPG to PDF](/jpg-to-pdf) |
|
||||
| Edit PDF and Redact PDF | [Edit PDF](/edit-pdf) |
|
||||
| Sign PDF | [Sign PDF](/sign-pdf) |
|
||||
| OCR PDF | [OCR PDF](/ocr-pdf) |
|
||||
| Organize PDF | [Organize PDF](/organize-pdf) |
|
||||
| Rotate PDF | [Rotate PDF](/rotate-pdf) |
|
||||
| Crop PDF | [Crop PDF](/crop-pdf) |
|
||||
| Add page numbers | [Page Numbers](/page-numbers) |
|
||||
| Add watermark | [Add Watermark](/add-watermark) |
|
||||
| Protect PDF | [Protect PDF](/protect-pdf) |
|
||||
| Unlock PDF | [Unlock PDF](/unlock-pdf) |
|
||||
| Repair PDF | [Repair PDF](/repair-pdf) |
|
||||
| Compare PDF | [Compare PDFs](/compare-pdfs) |
|
||||
| PDF to PDF/A | [PDF to PDF/A](/pdf-to-pdfa) |
|
||||
|
||||
## Free tools you won't find on iLovePDF
|
||||
|
||||
BentoPDF has over 100 tools. These ones aren't in the tool list on iLovePDF's pricing page:
|
||||
|
||||
- **More file types to PDF:** [EPUB](/epub-to-pdf), [MOBI](/mobi-to-pdf), [Apple Pages](/pages-to-pdf), [Visio](/vsd-to-pdf), [XPS](/xps-to-pdf) and [Markdown](/markdown-to-pdf).
|
||||
- **Certificate signatures:** [Digital Signature](/digital-sign-pdf) signs with your own X.509 certificate, and [Validate Signature](/validate-signature-pdf) checks whether a signed PDF was changed.
|
||||
- **Legal and office work:** [Bates numbering](/bates-numbering), [table of contents](/table-of-contents) and [PDF booklets](/pdf-booklet).
|
||||
- **Privacy clean-up:** [Sanitize PDF](/sanitize-pdf) and [Remove Metadata](/remove-metadata) strip out hidden content before you share a file.
|
||||
- **Your own forms:** [Create PDF Form](/form-creator) adds real fillable fields to any PDF.
|
||||
- **Automation:** the [Workflow Builder](/pdf-workflow) chains tools together, so you can run the same steps on every file.
|
||||
|
||||
## BentoPDF for teams and companies
|
||||
|
||||
Because BentoPDF runs in the browser, you can [host your own copy](/docs/self-hosting) for your whole team. Run it inside your company network with Docker, and nobody has to send files to an outside website.
|
||||
|
||||
If you want to put your own branding on it, or build it into a closed-source product, there's a [commercial license](/licensing). That's also what pays for the project.
|
||||
|
||||
## When iLovePDF is the better pick
|
||||
|
||||
I'd rather tell you now than have you find out later:
|
||||
|
||||
- **You need an app.** iLovePDF has desktop and mobile apps with Premium. BentoPDF only runs in the browser.
|
||||
- **You need PDF to PowerPoint, AI summaries or translation.** BentoPDF doesn't have those.
|
||||
- **You need team accounts.** iLovePDF's Business plan has team management, single sign-on and an account manager.
|
||||
|
||||
## Is there a free alternative to iLovePDF?
|
||||
|
||||
Yes. BentoPDF is free, with no account, no watermark and no cap on file size or the number of files. That includes the tools that are Premium on iLovePDF, like PDF to PDF/A and Workflows.
|
||||
|
||||
## Is iLovePDF safe to use?
|
||||
|
||||
iLovePDF is a legitimate service. Its security page says it uses HTTPS and encryption, holds ISO/IEC 27001 certification, and deletes processed files within two hours. The thing to know is that your file is uploaded to its servers. If you'd rather a file never leaves your device, use a tool that works in your browser, like BentoPDF.
|
||||
|
||||
## Does iLovePDF keep your files?
|
||||
|
||||
According to its security page, processed files are deleted automatically within two hours, and you can delete them sooner from the download screen. Signed documents are the exception. iLovePDF keeps those for up to five years for legal reasons.
|
||||
|
||||
## Is there an iLovePDF alternative that works offline?
|
||||
|
||||
Yes. Once a BentoPDF tool has loaded, it works without an internet connection, because the processing happens in your browser.
|
||||
|
||||
## Is there an open source alternative to iLovePDF?
|
||||
|
||||
Yes. BentoPDF is [open source](https://github.com/alam00000/bentopdf) under the AGPL, and you can run your own copy.
|
||||
|
||||
## Which one should you use?
|
||||
|
||||
If you need the apps, PDF to PowerPoint or the AI tools, use iLovePDF.
|
||||
|
||||
For everything else, try BentoPDF. Your files stay on your device, and you won't hit a limit halfway through a job.
|
||||
|
||||
If I've got something wrong about iLovePDF, [open an issue](https://github.com/alam00000/bentopdf/issues). I'll fix it and add a note to the post.
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
{
|
||||
"title": "PDF24 Alternative With No Uploads: BentoPDF vs PDF24",
|
||||
"h1": "BentoPDF vs PDF24: a PDF24 alternative that doesn't upload your files",
|
||||
"ogTitle": "PDF24 Alternative With No Uploads: BentoPDF vs PDF24",
|
||||
"breadcrumb": "PDF24 alternative",
|
||||
"description": "BentoPDF vs PDF24 compared. Both are free with no limits, but PDF24 uploads your files to its servers. BentoPDF runs in your browser on Mac, Windows and Linux.",
|
||||
"card": "BentoPDF vs PDF24 side by side: uploads, offline use on any computer, open source, and the tools you get for free.",
|
||||
"date": "2026-10-28",
|
||||
"cta": {
|
||||
"heading": "Try it on your own PDF",
|
||||
"text": "Free, no signup. Your file stays on your device.",
|
||||
"href": "/",
|
||||
"label": "Open BentoPDF"
|
||||
}
|
||||
}
|
||||
---
|
||||
If you're looking for a PDF24 alternative, the biggest difference with BentoPDF is where your file goes.
|
||||
|
||||
PDF24's online tools upload your PDF and process it on PDF24's servers. BentoPDF processes it in your browser, on your own device. Both are free, and neither needs an account.
|
||||
|
||||
I build BentoPDF, so keep that in mind. Whenever I say what PDF24 does, I've linked to PDF24's own pages so you can check.
|
||||
|
||||
## Why people look for a PDF24 alternative
|
||||
|
||||
PDF24 is a good, free tool. People usually look elsewhere for one of these reasons:
|
||||
|
||||
- **Your file gets uploaded.** PDF24's [FAQ](https://tools.pdf24.org/en/faq) says its online tools process files on its servers, which are in EU data centers.
|
||||
- **The offline version is Windows only.** PDF24 Creator keeps files on your PC, but the FAQ says "Users of other operating systems should continue to use PDF24 Tools." On a Mac, Linux machine or Chromebook, that means uploading.
|
||||
- **It's paid for with ads.** PDF24's own [homepage](https://tools.pdf24.org/en/) says the free tools are financed by advertising on its pages.
|
||||
- **It isn't open source.** You can't check the code or run PDF24's online tools on your own server.
|
||||
|
||||
## BentoPDF vs PDF24: side by side
|
||||
|
||||
Here's how BentoPDF compares with PDF24's online tools:
|
||||
|
||||
| Feature | PDF24 online tools | BentoPDF |
|
||||
| --- | --- | --- |
|
||||
| Where your file is processed | Uploaded to PDF24's servers | In your browser, never uploaded |
|
||||
| What happens to your files | Deleted from the server after one hour | Never uploaded in the first place |
|
||||
| Price | Free | Free |
|
||||
| Account needed | No | No |
|
||||
| How it's paid for | Advertising on its pages | Commercial licenses |
|
||||
| Works offline | Only with PDF24 Creator, on Windows | Yes, in your browser, once a tool has loaded |
|
||||
| No-upload use on Mac, Linux or Chromebook | No | Yes |
|
||||
| Open source | No | Yes, AGPL |
|
||||
| Run it on your own server | No | Yes, with Docker or as static files |
|
||||
| Workflow automation | No | Yes, the free [Workflow Builder](/pdf-workflow) |
|
||||
|
||||
PDF24's details come from its [FAQ](https://tools.pdf24.org/en/faq) and its [Merge PDF page](https://tools.pdf24.org/en/merge-pdf), which says "Files are automatically deleted from the PDF24 server after one hour."
|
||||
|
||||
## Your files never leave your device
|
||||
|
||||
One hour on a server in the EU is a decent policy. But "we delete your file after an hour" is still a weaker promise than "we never get your file."
|
||||
|
||||
BentoPDF's tools run in your browser using WebAssembly, so your PDF doesn't go anywhere. You can check this yourself:
|
||||
|
||||
1. Open your browser's developer tools and go to the Network tab.
|
||||
2. Process a file in BentoPDF.
|
||||
3. Watch the requests. Your PDF isn't in them.
|
||||
|
||||
Or load a tool, turn off Wi-Fi, and use it anyway. That works on a Mac, a Linux laptop or a Chromebook, not only on Windows.
|
||||
|
||||
## BentoPDF tools for the PDF24 tools you use
|
||||
|
||||
Switching is easy. These are the BentoPDF versions of PDF24's most used tools:
|
||||
|
||||
| PDF24 tool | BentoPDF tool |
|
||||
| --- | --- |
|
||||
| Merge PDF | [Merge PDF](/merge-pdf) |
|
||||
| Split PDF | [Split PDF](/split-pdf) |
|
||||
| Compress PDF | [Compress PDF](/compress-pdf) |
|
||||
| Edit PDF and Annotate PDF | [Edit PDF](/edit-pdf) |
|
||||
| Sign PDF | [Sign PDF](/sign-pdf) |
|
||||
| PDF to Word | [PDF to Word](/pdf-to-word) |
|
||||
| Word to PDF | [Word to PDF](/word-to-pdf) |
|
||||
| PDF to Excel | [PDF to Excel](/pdf-to-excel) |
|
||||
| Excel to PDF | [Excel to PDF](/excel-to-pdf) |
|
||||
| PowerPoint to PDF | [PowerPoint to PDF](/powerpoint-to-pdf) |
|
||||
| Images to PDF | [Image to PDF](/image-to-pdf) |
|
||||
| PDF to JPG | [PDF to JPG](/pdf-to-jpg) |
|
||||
| PDF to PNG | [PDF to PNG](/pdf-to-png) |
|
||||
| PDF OCR | [OCR PDF](/ocr-pdf) |
|
||||
| Rearrange PDF pages | [Organize PDF](/organize-pdf) |
|
||||
| Remove PDF pages | [Delete Pages](/delete-pages) |
|
||||
| Extract PDF pages | [Extract Pages](/extract-pages) |
|
||||
| Rotate PDF pages | [Rotate PDF](/rotate-pdf) |
|
||||
| Crop PDF | [Crop PDF](/crop-pdf) |
|
||||
| Add page numbers | [Page Numbers](/page-numbers) |
|
||||
| Add watermark | [Add Watermark](/add-watermark) |
|
||||
| PDF Overlay | [Overlay PDF](/overlay-pdf) |
|
||||
| Pages per sheet | [N-Up PDF](/n-up-pdf) |
|
||||
| Create booklet | [PDF Booklet](/pdf-booklet) |
|
||||
| Protect PDF | [Protect PDF](/protect-pdf) |
|
||||
| Unlock PDF | [Unlock PDF](/unlock-pdf) |
|
||||
| Redact PDF | [Edit PDF](/edit-pdf) |
|
||||
| Fill out PDF | [PDF Form Filler](/form-filler) |
|
||||
| Make PDF fillable | [Create PDF Form](/form-creator) |
|
||||
| Flatten PDF | [Flatten PDF](/flatten-pdf) |
|
||||
| Repair PDF | [Repair PDF](/repair-pdf) |
|
||||
| PDF to PDF/A | [PDF to PDF/A](/pdf-to-pdfa) |
|
||||
| Compare PDFs | [Compare PDFs](/compare-pdfs) |
|
||||
| Verify PDF signatures | [Validate Signature](/validate-signature-pdf) |
|
||||
| Remove PDF metadata | [Remove Metadata](/remove-metadata) |
|
||||
| Web optimize PDF | [Linearize PDF](/linearize-pdf) |
|
||||
|
||||
## Free tools you won't find on PDF24
|
||||
|
||||
BentoPDF has over 100 tools. These ones aren't in PDF24's [list of all tools](https://tools.pdf24.org/en/all-tools):
|
||||
|
||||
- **More file types to PDF:** [MOBI](/mobi-to-pdf), [FB2](/fb2-to-pdf), [CBZ comics](/cbz-to-pdf), [Apple Pages](/pages-to-pdf), [Visio](/vsd-to-pdf), [XPS](/xps-to-pdf), [Photoshop PSD](/psd-to-pdf), [emails](/email-to-pdf) and [CSV](/csv-to-pdf).
|
||||
- **Certificate signatures:** [Digital Signature](/digital-sign-pdf) signs with your own X.509 certificate, and [Timestamp PDF](/timestamp-pdf) adds a trusted timestamp.
|
||||
- **Legal and office work:** [Bates numbering](/bates-numbering) and [table extraction](/extract-tables) into CSV, JSON or Markdown.
|
||||
- **Scans:** [Deskew PDF](/deskew-pdf) straightens crooked pages, and [Remove Blank Pages](/remove-blank-pages) drops empty ones.
|
||||
- **Double-sided scans:** [Duplex Collate](/duplex-collate) and [Alternate Merge](/alternate-merge) put fronts and backs back in page order.
|
||||
- **Privacy clean-up:** [Sanitize PDF](/sanitize-pdf) strips scripts, metadata and other hidden content before you share a file.
|
||||
- **Attachments:** [add](/add-attachments), [edit](/edit-attachments) or [extract](/extract-attachments) files embedded in a PDF.
|
||||
- **Automation:** the [Workflow Builder](/pdf-workflow) chains tools together, so you can run the same steps on every file.
|
||||
|
||||
## BentoPDF for teams and companies
|
||||
|
||||
Because BentoPDF runs in the browser, you can [host your own copy](/docs/self-hosting) for your whole team. Run it inside your company network with Docker, and nobody has to send files to an outside website. It works the same on every computer, whatever operating system your staff use.
|
||||
|
||||
If you want to put your own branding on it, or build it into a closed-source product, there's a [commercial license](/licensing). That's also what pays for the project.
|
||||
|
||||
## When PDF24 is the better pick
|
||||
|
||||
- **You want a Windows desktop app.** PDF24 Creator is free and works offline on Windows. BentoPDF runs in the browser.
|
||||
- **You need a tool BentoPDF doesn't have.** PDF24 can convert PDF to PowerPoint, EPUB and HTML, and it has e-invoice and invoice tools. BentoPDF doesn't do those.
|
||||
|
||||
## Is there a PDF24 alternative for Mac?
|
||||
|
||||
Yes. PDF24 Creator only runs on Windows, so on a Mac you'd be uploading files to PDF24's online tools. BentoPDF runs in Safari, Chrome or Firefox on a Mac, processes files on the Mac itself, and keeps working offline once a tool has loaded.
|
||||
|
||||
## Is PDF24 safe to use?
|
||||
|
||||
PDF24 is a legitimate service run by Geek Software GmbH, a German company. Its FAQ says its processing servers are in EU data centers and that uploaded files are deleted after one hour. The thing to know is that your file is uploaded to those servers. If you'd rather a file never leaves your device, use a tool that works in your browser, like BentoPDF.
|
||||
|
||||
## Is PDF24 really free?
|
||||
|
||||
Yes. PDF24's homepage says its tools are free without restrictions, paid for by advertising on its pages. BentoPDF is free too, and it's paid for by commercial licenses for companies.
|
||||
|
||||
## Does PDF24 work offline?
|
||||
|
||||
Only PDF24 Creator, the Windows desktop app. The online tools need a connection because they process files on PDF24's servers. BentoPDF works offline in your browser once a tool has loaded, on any operating system.
|
||||
|
||||
## Is there an open source alternative to PDF24?
|
||||
|
||||
Yes. BentoPDF is [open source](https://github.com/alam00000/bentopdf) under the AGPL, and you can run your own copy.
|
||||
|
||||
## Which one should you use?
|
||||
|
||||
If you're on Windows and want a desktop app, or you need PDF to PowerPoint or e-invoices, use PDF24.
|
||||
|
||||
For everything else, try BentoPDF. Your files stay on your device, on any computer.
|
||||
|
||||
If I've got something wrong about PDF24, [open an issue](https://github.com/alam00000/bentopdf/issues). I'll fix it and add a note to the post.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: v2
|
||||
name: bentopdf
|
||||
description: BentoPDF static frontend served by NGINX
|
||||
icon: https://raw.githubusercontent.com/spwoodcock/bentopdf/refs/heads/main/public/favicon.ico
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 1.0.10
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: '2.8.11'
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
# BentoPDF Helm Chart
|
||||
|
||||
Deploys **BentoPDF** as a **single NGINX container** serving the static frontend.
|
||||
|
||||
## Quickstart
|
||||
|
||||
### Option 1: Port-forward (testing)
|
||||
|
||||
```bash
|
||||
helm install bentopdf ./chart
|
||||
kubectl port-forward deploy/bentopdf 8080:8080
|
||||
# open http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
### Option 2: Ingress
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
hosts:
|
||||
- host: bentopdf.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
```
|
||||
|
||||
### Option 3: Gateway API (Gateway + HTTPRoute)
|
||||
|
||||
```yaml
|
||||
gateway:
|
||||
enabled: true
|
||||
gatewayClassName: 'cloudflare' # or your gateway class
|
||||
|
||||
httpRoute:
|
||||
enabled: true
|
||||
hostnames:
|
||||
- pdfs.example.com
|
||||
```
|
||||
|
||||
**Note:** Both Gateway and HTTPRoute default to the release namespace. Omit `namespace` fields to use the release namespace automatically.
|
||||
|
||||
If you have an existing Gateway, set `gateway.enabled=false` and configure `httpRoute.parentRefs`:
|
||||
|
||||
```yaml
|
||||
gateway:
|
||||
enabled: false
|
||||
|
||||
httpRoute:
|
||||
enabled: true
|
||||
parentRefs:
|
||||
- name: existing-gateway
|
||||
namespace: gateway-namespace
|
||||
sectionName: http
|
||||
hostnames:
|
||||
- pdfs.example.com
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Image
|
||||
|
||||
- **`image.repository`**: container image repo (default: `ghcr.io/alam00000/bentopdf-simple`)
|
||||
- **`image.tag`**: image tag (default: `Chart.appVersion`)
|
||||
- **`image.pullPolicy`**: default `IfNotPresent`
|
||||
|
||||
### Ports
|
||||
|
||||
- **`containerPort`**: container listen port (**8080** for the BentoPDF nginx image)
|
||||
- **`service.port`**: Service port exposed in-cluster (default **80**)
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: DISABLE_IPV6
|
||||
value: 'true'
|
||||
```
|
||||
|
||||
## Publish this chart to GHCR (OCI) for testing/deploying
|
||||
|
||||
### Build And Push OCI
|
||||
|
||||
```bash
|
||||
echo "$GHCR_TOKEN" | helm registry login ghcr.io -u "$GHCR_USERNAME" --password-stdin
|
||||
|
||||
cd chart
|
||||
helm package .
|
||||
|
||||
# produces bentopdf-<version>.tgz
|
||||
helm push bentopdf-*.tgz oci://ghcr.io/$GHCR_USERNAME/charts
|
||||
```
|
||||
|
||||
This could be automated as part of a GitHub workflow.
|
||||
|
||||
### Deploy
|
||||
|
||||
```bash
|
||||
helm upgrade --install bentopdf oci://ghcr.io/$GHCR_USERNAME/charts/bentopdf --version 1.0.0
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.httpRoute.enabled }}
|
||||
{{- $defaultGatewayName := .Values.gateway.name -}}
|
||||
{{- if not $defaultGatewayName -}}
|
||||
{{- $defaultGatewayName = printf "%s-gateway" (include "bentopdf.name" .) -}}
|
||||
{{- end -}}
|
||||
{{- $defaultGatewayNs := .Release.Namespace -}}
|
||||
{{- if .Values.gateway.enabled -}}
|
||||
{{- $defaultGatewayNs = default .Release.Namespace .Values.gateway.namespace -}}
|
||||
{{- end -}}
|
||||
{{- $gatewayName := $defaultGatewayName -}}
|
||||
{{- $gatewayNs := $defaultGatewayNs -}}
|
||||
{{- if and .Values.httpRoute.parentRefs (first .Values.httpRoute.parentRefs) -}}
|
||||
{{- $firstRef := first .Values.httpRoute.parentRefs -}}
|
||||
{{- $gatewayName = default $defaultGatewayName $firstRef.name -}}
|
||||
{{- $gatewayNs = default $defaultGatewayNs $firstRef.namespace -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.httpRoute.hostnames }}
|
||||
export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }}
|
||||
echo "Visit http://$APP_HOSTNAME to use your application"
|
||||
{{- else }}
|
||||
echo "HTTPRoute is enabled. Configure httpRoute.hostnames to see your URL here."
|
||||
{{- end }}
|
||||
NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules.
|
||||
The rules can be set for path, method, header and query parameters.
|
||||
You can check the gateway configuration with 'kubectl get --namespace {{ $gatewayNs }} gateway/{{ $gatewayName }} -o yaml'
|
||||
{{- else if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "bentopdf.name" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "bentopdf.name" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "bentopdf.name" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "bentopdf.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
||||
@@ -0,0 +1,33 @@
|
||||
{{/*
|
||||
Expand the name of the bentopdf
|
||||
*/}}
|
||||
{{- define "bentopdf.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "bentopdf.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "bentopdf.labels" -}}
|
||||
helm.sh/chart: {{ include "bentopdf.chart" . }}
|
||||
{{ include "bentopdf.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "bentopdf.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "bentopdf.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,80 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "bentopdf.name" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "bentopdf.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "bentopdf.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "bentopdf.labels" . | nindent 8 }}
|
||||
{{- with .Values.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.podSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- with .Values.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.containerPort }}
|
||||
protocol: TCP
|
||||
{{- with .Values.env }}
|
||||
env:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.livenessProbe }}
|
||||
livenessProbe:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.readinessProbe }}
|
||||
readinessProbe:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: nginx-tmp
|
||||
mountPath: /etc/nginx/tmp
|
||||
- name: nginx-cache
|
||||
mountPath: /var/cache/nginx
|
||||
- name: nginx-run
|
||||
mountPath: /var/run
|
||||
{{- if .Values.extraVolumeMounts }}
|
||||
{{- toYaml .Values.extraVolumeMounts | nindent 12 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: nginx-tmp
|
||||
emptyDir: {}
|
||||
- name: nginx-cache
|
||||
emptyDir: {}
|
||||
- name: nginx-run
|
||||
emptyDir: {}
|
||||
{{- if .Values.extraVolumes }}
|
||||
{{- toYaml .Values.extraVolumes | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,22 @@
|
||||
{{- if .Values.gateway.enabled -}}
|
||||
{{- $gatewayName := .Values.gateway.name -}}
|
||||
{{- if not $gatewayName -}}
|
||||
{{- $gatewayName = printf "%s-gateway" (include "bentopdf.name" .) -}}
|
||||
{{- end -}}
|
||||
{{- $gatewayNs := default .Release.Namespace .Values.gateway.namespace -}}
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: {{ $gatewayName }}
|
||||
namespace: {{ $gatewayNs }}
|
||||
labels:
|
||||
{{- include "bentopdf.labels" . | nindent 4 }}
|
||||
{{- with .Values.gateway.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
gatewayClassName: {{ required "values.gateway.gatewayClassName is required when gateway.enabled=true" .Values.gateway.gatewayClassName }}
|
||||
listeners:
|
||||
{{- toYaml .Values.gateway.listeners | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,52 @@
|
||||
{{- if .Values.httpRoute.enabled -}}
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: {{ include "bentopdf.name" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "bentopdf.labels" . | nindent 4 }}
|
||||
{{- with .Values.httpRoute.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
parentRefs:
|
||||
{{- if .Values.httpRoute.parentRefs }}
|
||||
{{- range $ref := .Values.httpRoute.parentRefs }}
|
||||
- name: {{ $.Values.gateway.name | default $ref.name | quote }}
|
||||
namespace: {{ $ref.namespace | default (default $.Release.Namespace $.Values.gateway.namespace) | quote }}
|
||||
sectionName: {{ $ref.sectionName | default "http" | quote }}
|
||||
{{- with $ref.kind }}
|
||||
kind: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with $ref.group }}
|
||||
group: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
# Default parentRef when parentRefs is empty or not set
|
||||
- name: {{ .Values.gateway.name | default (printf "%s-gateway" (include "bentopdf.name" $)) | quote }}
|
||||
namespace: {{ .Values.gateway.namespace | default .Release.Namespace | quote }}
|
||||
sectionName: "http"
|
||||
{{- end }}
|
||||
{{- with .Values.httpRoute.hostnames }}
|
||||
hostnames:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.httpRoute.rules }}
|
||||
- {{- with .matches }}
|
||||
matches:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .filters }}
|
||||
filters:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
backendRefs:
|
||||
- name: {{ include "bentopdf.name" $ }}
|
||||
port: {{ $.Values.service.port }}
|
||||
weight: 1
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,44 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "bentopdf.name" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "bentopdf.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.ingress.className }}
|
||||
ingressClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- with .pathType }}
|
||||
pathType: {{ . }}
|
||||
{{- end }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "bentopdf.name" $ }}
|
||||
port:
|
||||
number: {{ $.Values.service.port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "bentopdf.name" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "bentopdf.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "bentopdf.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "bentopdf.name" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "bentopdf.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['-qO-', 'http://{{ include "bentopdf.name" . }}:{{ .Values.service.port }}/']
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,94 @@
|
||||
# Default values for the BentoPDF chart (single nginx static frontend).
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
# Image built from this repo's `Dockerfile` (nginx serving static frontend).
|
||||
repository: ghcr.io/alam00000/bentopdf-simple
|
||||
pullPolicy: IfNotPresent
|
||||
tag: ''
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ''
|
||||
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
# Container listen port (BentoPDF nginx image listens on 8080).
|
||||
containerPort: 8080
|
||||
|
||||
env: []
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ''
|
||||
annotations: {}
|
||||
hosts:
|
||||
- host: bentopdf.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
|
||||
# Gateway API (optional)
|
||||
gateway:
|
||||
enabled: false
|
||||
name: '' # default: release name + "-gateway" suffix
|
||||
namespace: '' # to override the namespace
|
||||
annotations: {}
|
||||
gatewayClassName: '' # required when enabled=true
|
||||
listeners:
|
||||
- name: http
|
||||
protocol: HTTP
|
||||
port: 80
|
||||
allowedRoutes:
|
||||
namespaces:
|
||||
from: Same # Allow routes from the same namespace
|
||||
|
||||
httpRoute:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
# parentRefs: # Omit entirely to use default (auto-references gateway in same namespace)
|
||||
# - name: "" # default: gateway.name or {release-name}-gateway
|
||||
# namespace: "" # to override the namespace
|
||||
# sectionName: "http"
|
||||
hostnames:
|
||||
- bentopdf.local
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
fsGroup: 101
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
resources: {}
|
||||
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
|
||||
extraVolumes: []
|
||||
|
||||
extraVolumeMounts: []
|
||||
@@ -0,0 +1,104 @@
|
||||
# WASM Proxy Setup Guide
|
||||
|
||||
BentoPDF uses a Cloudflare Worker to proxy WASM library requests, bypassing CORS restrictions when loading AGPL-licensed components (PyMuPDF, Ghostscript, CoherentPDF) from external sources.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Deploy the Worker
|
||||
|
||||
```bash
|
||||
cd cloudflare
|
||||
npx wrangler login
|
||||
npx wrangler deploy -c wasm-wrangler.toml
|
||||
```
|
||||
|
||||
### 2. Configure Source URLs
|
||||
|
||||
Set environment secrets with the base URLs for your WASM files:
|
||||
|
||||
```bash
|
||||
# Option A: Interactive prompts
|
||||
npx wrangler secret put PYMUPDF_SOURCE -c wasm-wrangler.toml
|
||||
npx wrangler secret put GS_SOURCE -c wasm-wrangler.toml
|
||||
npx wrangler secret put CPDF_SOURCE -c wasm-wrangler.toml
|
||||
|
||||
# Option B: Set via Cloudflare Dashboard
|
||||
# Go to Workers & Pages > bentopdf-wasm-proxy > Settings > Variables
|
||||
```
|
||||
|
||||
**Recommended Source URLs:**
|
||||
|
||||
- PYMUPDF_SOURCE: `https://cdn.jsdelivr.net/npm/@bentopdf/[email protected]/`
|
||||
- GS_SOURCE: `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/`
|
||||
- CPDF_SOURCE: `https://cdn.jsdelivr.net/npm/coherentpdf/dist/`
|
||||
|
||||
> **Note:** You can use your own hosted WASM files instead of the recommended URLs. Just ensure your files match the expected directory structure and file names that BentoPDF expects for each module.
|
||||
|
||||
### 3. Configure BentoPDF
|
||||
|
||||
**Option A: Environment variables (recommended — zero-config for users)**
|
||||
|
||||
Set these in `.env.production` or pass as Docker build args:
|
||||
|
||||
```bash
|
||||
VITE_WASM_PYMUPDF_URL=https://bentopdf-wasm-proxy.<your-subdomain>.workers.dev/pymupdf/
|
||||
VITE_WASM_GS_URL=https://bentopdf-wasm-proxy.<your-subdomain>.workers.dev/gs/
|
||||
VITE_WASM_CPDF_URL=https://bentopdf-wasm-proxy.<your-subdomain>.workers.dev/cpdf/
|
||||
```
|
||||
|
||||
**Option B: Manual per-user configuration**
|
||||
|
||||
In BentoPDF's Advanced Settings (wasm-settings.html), enter:
|
||||
|
||||
| Module | URL |
|
||||
| ----------- | ------------------------------------------------------------------- |
|
||||
| PyMuPDF | `https://bentopdf-wasm-proxy.<your-subdomain>.workers.dev/pymupdf/` |
|
||||
| Ghostscript | `https://bentopdf-wasm-proxy.<your-subdomain>.workers.dev/gs/` |
|
||||
| CoherentPDF | `https://bentopdf-wasm-proxy.<your-subdomain>.workers.dev/cpdf/` |
|
||||
|
||||
## Custom Domain (Optional)
|
||||
|
||||
To use a custom domain like `wasm.bentopdf.com`:
|
||||
|
||||
1. Add route in `wasm-wrangler.toml`:
|
||||
|
||||
```toml
|
||||
routes = [
|
||||
{ pattern = "wasm.bentopdf.com/*", zone_name = "bentopdf.com" }
|
||||
]
|
||||
```
|
||||
|
||||
2. Add DNS record in Cloudflare:
|
||||
- Type: AAAA
|
||||
- Name: wasm
|
||||
- Content: 100::
|
||||
- Proxied: Yes
|
||||
|
||||
3. Redeploy:
|
||||
|
||||
```bash
|
||||
npx wrangler deploy -c wasm-wrangler.toml
|
||||
```
|
||||
|
||||
## Security Features
|
||||
|
||||
- **Origin validation**: Only allows requests from configured origins
|
||||
- **Rate limiting**: 100 requests/minute per IP, per /64 for IPv6 (requires KV namespace; fails open on KV errors)
|
||||
- **File type restrictions**: Only WASM-related files (.js, .wasm, .data, etc.)
|
||||
- **Size limits**: Max 100MB per file
|
||||
- **Caching**: Reduces origin requests and improves performance
|
||||
|
||||
## Self-Hosting Notes
|
||||
|
||||
1. Update `ALLOWED_ORIGINS` in `wasm-proxy-worker.js` to include your domain
|
||||
2. Host your WASM files on any origin (R2, S3, or any CDN)
|
||||
3. Set source URLs as secrets in your worker
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Endpoint | Description |
|
||||
| ------------ | -------------------------------------- |
|
||||
| `/` | Health check, shows configured modules |
|
||||
| `/pymupdf/*` | PyMuPDF WASM files |
|
||||
| `/gs/*` | Ghostscript WASM files |
|
||||
| `/cpdf/*` | CoherentPDF files |
|
||||
@@ -0,0 +1,671 @@
|
||||
/**
|
||||
* BentoPDF CORS Proxy Worker
|
||||
*
|
||||
* This Cloudflare Worker proxies certificate requests for the digital signing tool.
|
||||
* It fetches certificates from external CAs that don't have CORS headers enabled
|
||||
* and returns them with proper CORS headers.
|
||||
*
|
||||
*
|
||||
* Deploy: npx wrangler deploy
|
||||
*
|
||||
* Required Environment Variables (set in wrangler.toml or Cloudflare dashboard):
|
||||
* - PROXY_SECRET: Shared secret for HMAC signature verification
|
||||
*/
|
||||
|
||||
const ALLOWED_PATH_PATTERNS = [
|
||||
/\.(crt|cer|pem|der|p7c|p7b)$/i,
|
||||
/(^|\/)certs(\/|$)/i,
|
||||
/(^|\/)ocsp(\/|$)/i,
|
||||
/(^|\/)crl(\/|$)/i,
|
||||
/(^|\/)caissuers(\/|$)/i,
|
||||
];
|
||||
|
||||
const ALLOWED_TSA_HOSTS = new Set([
|
||||
'timestamp.digicert.com',
|
||||
'timestamp.sectigo.com',
|
||||
'ts.ssl.com',
|
||||
'freetsa.org',
|
||||
'tsa.mesign.com',
|
||||
]);
|
||||
|
||||
const ALLOWED_ORIGINS = ['https://www.bentopdf.com', 'https://bentopdf.com'];
|
||||
|
||||
const SAFE_CONTENT_TYPES = [
|
||||
'application/x-x509-ca-cert',
|
||||
'application/pkix-cert',
|
||||
'application/x-pem-file',
|
||||
'application/pkcs7-mime',
|
||||
'application/octet-stream',
|
||||
'application/timestamp-reply',
|
||||
'text/plain',
|
||||
];
|
||||
|
||||
const MAX_TIMESTAMP_AGE_MS = 5 * 60 * 1000;
|
||||
|
||||
const RATE_LIMIT_MAX_REQUESTS = 60;
|
||||
const RATE_LIMIT_WINDOW_MS = 60 * 1000;
|
||||
|
||||
const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
let warnedNoProxySecret = false;
|
||||
|
||||
async function verifySignature(message, signature, secret) {
|
||||
try {
|
||||
const encoder = new TextEncoder();
|
||||
const key = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
encoder.encode(secret),
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['verify']
|
||||
);
|
||||
|
||||
const signatureBytes = new Uint8Array(
|
||||
signature.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
|
||||
);
|
||||
|
||||
return await crypto.subtle.verify(
|
||||
'HMAC',
|
||||
key,
|
||||
signatureBytes,
|
||||
encoder.encode(message)
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Signature verification error:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isAllowedOrigin(origin) {
|
||||
if (!origin) return false;
|
||||
return ALLOWED_ORIGINS.includes(origin);
|
||||
}
|
||||
|
||||
function parseIPv4(address) {
|
||||
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(address);
|
||||
if (!match) return null;
|
||||
const octets = match.slice(1).map(Number);
|
||||
if (octets.some((octet) => octet > 255)) return null;
|
||||
return octets.reduce((value, octet) => value * 256 + octet, 0);
|
||||
}
|
||||
|
||||
function parseIPv6(address) {
|
||||
let text = address.toLowerCase();
|
||||
const embedded = /^(.*:)(\d+\.\d+\.\d+\.\d+)$/.exec(text);
|
||||
if (embedded) {
|
||||
const ipv4 = parseIPv4(embedded[2]);
|
||||
if (ipv4 === null) return null;
|
||||
text = `${embedded[1]}${Math.floor(ipv4 / 65536).toString(16)}:${(ipv4 % 65536).toString(16)}`;
|
||||
}
|
||||
const sections = text.split('::');
|
||||
if (sections.length > 2) return null;
|
||||
const head = sections[0] ? sections[0].split(':') : [];
|
||||
const tail =
|
||||
sections.length === 2 && sections[1] ? sections[1].split(':') : [];
|
||||
const missing = 8 - head.length - tail.length;
|
||||
if (sections.length === 1 ? missing !== 0 : missing < 1) return null;
|
||||
const groups = [...head, ...Array(missing).fill('0'), ...tail];
|
||||
if (!groups.every((group) => /^[0-9a-f]{1,4}$/.test(group))) return null;
|
||||
return groups.map((group) => parseInt(group, 16));
|
||||
}
|
||||
|
||||
const BLOCKED_IPV4_RANGES = [
|
||||
'0.0.0.0/8',
|
||||
'10.0.0.0/8',
|
||||
'100.64.0.0/10',
|
||||
'127.0.0.0/8',
|
||||
'169.254.0.0/16',
|
||||
'172.16.0.0/12',
|
||||
'192.0.0.0/24',
|
||||
'192.0.2.0/24',
|
||||
'192.168.0.0/16',
|
||||
'198.18.0.0/15',
|
||||
'198.51.100.0/24',
|
||||
'203.0.113.0/24',
|
||||
'224.0.0.0/4',
|
||||
'240.0.0.0/4',
|
||||
].map((cidr) => {
|
||||
const [address, prefixLength] = cidr.split('/');
|
||||
return { base: parseIPv4(address), size: 2 ** (32 - Number(prefixLength)) };
|
||||
});
|
||||
|
||||
const BLOCKED_IPV6_RANGES = [
|
||||
'::/128',
|
||||
'::1/128',
|
||||
'::/96',
|
||||
'::ffff:0:0/96',
|
||||
'64:ff9b::/96',
|
||||
'64:ff9b:1::/48',
|
||||
'100::/64',
|
||||
'2001::/32',
|
||||
'2001:db8::/32',
|
||||
'2002::/16',
|
||||
'fc00::/7',
|
||||
'fe80::/10',
|
||||
'fec0::/10',
|
||||
'ff00::/8',
|
||||
].map((cidr) => {
|
||||
const [address, prefixLength] = cidr.split('/');
|
||||
return { groups: parseIPv6(address), prefixLength: Number(prefixLength) };
|
||||
});
|
||||
|
||||
function isBlockedIPv4(value) {
|
||||
return BLOCKED_IPV4_RANGES.some(
|
||||
({ base, size }) => value >= base && value < base + size
|
||||
);
|
||||
}
|
||||
|
||||
function isBlockedIPv6(groups) {
|
||||
return BLOCKED_IPV6_RANGES.some((range) =>
|
||||
range.groups.every((group, index) => {
|
||||
const bitsInGroup = Math.min(
|
||||
16,
|
||||
Math.max(0, range.prefixLength - index * 16)
|
||||
);
|
||||
const groupMask = (0xffff << (16 - bitsInGroup)) & 0xffff;
|
||||
return (groups[index] & groupMask) === (group & groupMask);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function isBlockedIp(address) {
|
||||
const ipv4 = parseIPv4(address);
|
||||
if (ipv4 !== null) return isBlockedIPv4(ipv4);
|
||||
const ipv6Groups = parseIPv6(address);
|
||||
if (ipv6Groups !== null) return isBlockedIPv6(ipv6Groups);
|
||||
return true;
|
||||
}
|
||||
|
||||
function isIpLiteral(host) {
|
||||
return /^[\d.]+$/.test(host) || host.includes(':');
|
||||
}
|
||||
|
||||
function getRateLimitKey(ip) {
|
||||
const ipv6Groups = ip.includes(':') ? parseIPv6(ip) : null;
|
||||
if (!ipv6Groups) return ip;
|
||||
return `${ipv6Groups
|
||||
.slice(0, 4)
|
||||
.map((group) => group.toString(16))
|
||||
.join(':')}::/64`;
|
||||
}
|
||||
|
||||
function isPrivateOrReservedHost(hostname) {
|
||||
const host = hostname
|
||||
.toLowerCase()
|
||||
.replace(/^\[|\]$/g, '')
|
||||
.replace(/\.$/, '');
|
||||
|
||||
if (['localhost', 'localhost.localdomain'].includes(host)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isIpLiteral(host) && isBlockedIp(host);
|
||||
}
|
||||
|
||||
async function hostnameResolvesToPrivate(hostname) {
|
||||
const clean = hostname
|
||||
.toLowerCase()
|
||||
.replace(/^\[|\]$/g, '')
|
||||
.replace(/\.$/, '');
|
||||
if (isPrivateOrReservedHost(clean)) return true;
|
||||
if (isIpLiteral(clean)) return false;
|
||||
const ips = [];
|
||||
for (const type of ['A', 'AAAA']) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://1.1.1.1/dns-query?name=${encodeURIComponent(clean)}&type=${type}`,
|
||||
{ headers: { accept: 'application/dns-json' } }
|
||||
);
|
||||
if (!res.ok) return true;
|
||||
const data = await res.json();
|
||||
for (const ans of data.Answer || []) {
|
||||
if (ans && (ans.type === 1 || ans.type === 28) && ans.data) {
|
||||
ips.push(String(ans.data));
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (ips.length === 0) return true;
|
||||
return ips.some((ip) => isBlockedIp(ip));
|
||||
}
|
||||
|
||||
function isValidCertificateUrl(urlString) {
|
||||
try {
|
||||
const url = new URL(urlString);
|
||||
|
||||
if (!['http:', 'https:'].includes(url.protocol)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPrivateOrReservedHost(url.hostname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ALLOWED_PATH_PATTERNS.some((pattern) => pattern.test(url.pathname));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidTsaUrl(urlString) {
|
||||
try {
|
||||
const url = new URL(urlString);
|
||||
if (!['http:', 'https:'].includes(url.protocol)) return false;
|
||||
if (isPrivateOrReservedHost(url.hostname)) return false;
|
||||
return ALLOWED_TSA_HOSTS.has(url.hostname);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getSafeContentType(upstreamContentType) {
|
||||
if (!upstreamContentType) return 'application/octet-stream';
|
||||
const match = SAFE_CONTENT_TYPES.find((ct) =>
|
||||
upstreamContentType.startsWith(ct)
|
||||
);
|
||||
return match || 'application/octet-stream';
|
||||
}
|
||||
|
||||
function corsHeaders(origin) {
|
||||
return {
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
}
|
||||
|
||||
function handleOptions(request) {
|
||||
const origin = request.headers.get('Origin');
|
||||
if (!isAllowedOrigin(origin)) {
|
||||
return new Response(null, { status: 403 });
|
||||
}
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: corsHeaders(origin),
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request, env) {
|
||||
const url = new URL(request.url);
|
||||
const origin = request.headers.get('Origin');
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return handleOptions(request);
|
||||
}
|
||||
|
||||
// NOTE: If you are selfhosting this proxy, you can remove this check, or can set it to only accept requests from your own domain
|
||||
// SECURITY: The Origin allow-list and the optional PROXY_SECRET HMAC are anti-abuse controls, NOT a security boundary — the Origin
|
||||
// header is forgeable by non-browser clients and the HMAC secret ships in the public client bundle. The real SSRF defense is the
|
||||
// private/reserved-IP resolution check (hostnameResolvesToPrivate). Deployments running this off Cloudflare cannot fully close the
|
||||
// DNS-rebinding gap in code and MUST add network egress filtering (see docs/self-hosting/cors-proxy.md).
|
||||
if (!isAllowedOrigin(origin)) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Forbidden',
|
||||
message: 'This proxy only accepts requests from allowed origins',
|
||||
}),
|
||||
{
|
||||
status: 403,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (request.method !== 'GET' && request.method !== 'POST') {
|
||||
return new Response('Method not allowed', {
|
||||
status: 405,
|
||||
headers: corsHeaders(origin),
|
||||
});
|
||||
}
|
||||
|
||||
const targetUrl = url.searchParams.get('url');
|
||||
|
||||
if (!targetUrl) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Missing url parameter',
|
||||
usage: 'GET /?url=<certificate_url> or POST /?url=<tsa_url>',
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const requestContentType = request.headers.get('Content-Type') || '';
|
||||
const isTsaQuery =
|
||||
request.method === 'POST' &&
|
||||
requestContentType === 'application/timestamp-query';
|
||||
|
||||
if (isTsaQuery) {
|
||||
if (!isValidTsaUrl(targetUrl)) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Disallowed TSA host',
|
||||
message: `Only known RFC 3161 TSA hosts are accepted: ${[...ALLOWED_TSA_HOSTS].join(', ')}`,
|
||||
}),
|
||||
{
|
||||
status: 403,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (!isValidCertificateUrl(targetUrl)) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Invalid or disallowed URL',
|
||||
message:
|
||||
'Only certificate-related URLs are allowed (*.crt, *.cer, *.pem, /certs/, /ocsp, /crl)',
|
||||
}),
|
||||
{
|
||||
status: 403,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (env.PROXY_SECRET) {
|
||||
const timestamp = url.searchParams.get('t');
|
||||
const signature = url.searchParams.get('sig');
|
||||
|
||||
if (!timestamp || !signature) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Missing authentication parameters',
|
||||
message:
|
||||
'Request must include timestamp (t) and signature (sig) parameters',
|
||||
}),
|
||||
{
|
||||
status: 401,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const requestTime = parseInt(timestamp, 10);
|
||||
const now = Date.now();
|
||||
if (
|
||||
isNaN(requestTime) ||
|
||||
requestTime > now + 60000 ||
|
||||
now - requestTime > MAX_TIMESTAMP_AGE_MS
|
||||
) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Request expired or invalid timestamp',
|
||||
message: 'Timestamp must be within 5 minutes of current time',
|
||||
}),
|
||||
{
|
||||
status: 401,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const message = `${targetUrl}${timestamp}`;
|
||||
const isValid = await verifySignature(
|
||||
message,
|
||||
signature,
|
||||
env.PROXY_SECRET
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Invalid signature',
|
||||
message: 'Request signature verification failed',
|
||||
}),
|
||||
{
|
||||
status: 401,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (!warnedNoProxySecret) {
|
||||
warnedNoProxySecret = true;
|
||||
console.warn(
|
||||
'[CORS Proxy] PROXY_SECRET is not set — request signatures are not verified, so the proxy is callable by any client that can ' +
|
||||
'send an allowed Origin header. Set PROXY_SECRET to deter casual abuse (note: it ships in the public client bundle, so it is ' +
|
||||
'not a real authentication boundary), and add network egress filtering if running off Cloudflare.'
|
||||
);
|
||||
}
|
||||
|
||||
const clientIP = request.headers.get('CF-Connecting-IP') || 'unknown';
|
||||
const rateLimitKey = `ratelimit:${getRateLimitKey(clientIP)}`;
|
||||
const now = Date.now();
|
||||
|
||||
if (env.RATE_LIMIT_KV) {
|
||||
try {
|
||||
const rateLimitData = await env.RATE_LIMIT_KV.get(rateLimitKey, {
|
||||
type: 'json',
|
||||
});
|
||||
const requests = rateLimitData?.requests || [];
|
||||
|
||||
const recentRequests = requests.filter(
|
||||
(t) => now - t < RATE_LIMIT_WINDOW_MS
|
||||
);
|
||||
|
||||
if (recentRequests.length >= RATE_LIMIT_MAX_REQUESTS) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Rate limit exceeded',
|
||||
message: `Maximum ${RATE_LIMIT_MAX_REQUESTS} requests per minute. Please try again later.`,
|
||||
retryAfter: Math.ceil(
|
||||
(recentRequests[0] + RATE_LIMIT_WINDOW_MS - now) / 1000
|
||||
),
|
||||
}),
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
'Retry-After': Math.ceil(
|
||||
(recentRequests[0] + RATE_LIMIT_WINDOW_MS - now) / 1000
|
||||
).toString(),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
recentRequests.push(now);
|
||||
await env.RATE_LIMIT_KV.put(
|
||||
rateLimitKey,
|
||||
JSON.stringify({ requests: recentRequests }),
|
||||
{
|
||||
expirationTtl: 120,
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'[CORS Proxy] Rate limit KV error, allowing request:',
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
'[CORS Proxy] RATE_LIMIT_KV not configured — rate limiting is disabled'
|
||||
);
|
||||
}
|
||||
|
||||
let targetHostname;
|
||||
try {
|
||||
targetHostname = new URL(targetUrl).hostname;
|
||||
} catch {
|
||||
targetHostname = '';
|
||||
}
|
||||
if (!targetHostname || (await hostnameResolvesToPrivate(targetHostname))) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Forbidden destination',
|
||||
message: 'The requested host is not permitted',
|
||||
}),
|
||||
{
|
||||
status: 403,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const upstreamInit = {
|
||||
method: request.method,
|
||||
redirect: 'manual',
|
||||
signal: AbortSignal.timeout(10000),
|
||||
headers: {
|
||||
'User-Agent': 'BentoPDF-CertProxy/1.0',
|
||||
},
|
||||
};
|
||||
|
||||
if (isTsaQuery) {
|
||||
upstreamInit.headers['Content-Type'] = 'application/timestamp-query';
|
||||
upstreamInit.body = await request.arrayBuffer();
|
||||
}
|
||||
|
||||
const response = await fetch(targetUrl, upstreamInit);
|
||||
|
||||
if (response.status >= 300 && response.status < 400) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Redirect blocked',
|
||||
message: 'Upstream redirects are not allowed',
|
||||
}),
|
||||
{
|
||||
status: 502,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Failed to fetch certificate',
|
||||
status: response.status,
|
||||
}),
|
||||
{
|
||||
status: response.status,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Check Content-Length header first (fast reject for known-large responses)
|
||||
const contentLength = parseInt(
|
||||
response.headers.get('Content-Length') || '0',
|
||||
10
|
||||
);
|
||||
if (contentLength > MAX_FILE_SIZE_BYTES) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'File too large',
|
||||
message: `Certificate file exceeds maximum size of ${MAX_FILE_SIZE_BYTES / 1024}KB`,
|
||||
}),
|
||||
{
|
||||
status: 413,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const chunks = [];
|
||||
let totalSize = 0;
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
totalSize += value.byteLength;
|
||||
if (totalSize > MAX_FILE_SIZE_BYTES) {
|
||||
reader.cancel();
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'File too large',
|
||||
message: `Certificate file exceeds maximum size of ${MAX_FILE_SIZE_BYTES / 1024}KB`,
|
||||
}),
|
||||
{
|
||||
status: 413,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
chunks.push(value);
|
||||
}
|
||||
|
||||
const certData = new Uint8Array(totalSize);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
certData.set(chunk, offset);
|
||||
offset += chunk.byteLength;
|
||||
}
|
||||
|
||||
return new Response(certData, {
|
||||
status: 200,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': getSafeContentType(
|
||||
response.headers.get('Content-Type')
|
||||
),
|
||||
'Content-Length': totalSize.toString(),
|
||||
'Cache-Control': 'no-store',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Proxy fetch error:', error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Proxy error',
|
||||
message: 'Failed to fetch the requested certificate',
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,417 @@
|
||||
/**
|
||||
* BentoPDF WASM Proxy Worker
|
||||
*
|
||||
* This Cloudflare Worker proxies WASM module requests to bypass CORS restrictions.
|
||||
* It fetches WASM libraries (PyMuPDF, Ghostscript, CoherentPDF) from configured sources
|
||||
* and serves them with proper CORS headers.
|
||||
*
|
||||
* Endpoints:
|
||||
* - /pymupdf/* - Proxies to PyMuPDF WASM source
|
||||
* - /gs/* - Proxies to Ghostscript WASM source
|
||||
* - /cpdf/* - Proxies to CoherentPDF WASM source
|
||||
*
|
||||
* Deploy: cd cloudflare && npx wrangler deploy -c wasm-wrangler.toml
|
||||
*
|
||||
* Required Environment Variables (set in Cloudflare dashboard):
|
||||
* - PYMUPDF_SOURCE: Base URL for PyMuPDF WASM files (e.g., https://cdn.example.com/pymupdf)
|
||||
* - GS_SOURCE: Base URL for Ghostscript WASM files (e.g., https://cdn.example.com/gs)
|
||||
* - CPDF_SOURCE: Base URL for CoherentPDF files (e.g., https://cdn.example.com/cpdf)
|
||||
*/
|
||||
|
||||
const ALLOWED_ORIGINS = ['https://www.bentopdf.com', 'https://bentopdf.com'];
|
||||
|
||||
const MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024;
|
||||
|
||||
const RATE_LIMIT_MAX_REQUESTS = 100;
|
||||
const RATE_LIMIT_WINDOW_MS = 60 * 1000;
|
||||
|
||||
const CACHE_TTL_SECONDS = 604800;
|
||||
|
||||
const ALLOWED_EXTENSIONS = [
|
||||
'.js',
|
||||
'.mjs',
|
||||
'.wasm',
|
||||
'.data',
|
||||
'.py',
|
||||
'.so',
|
||||
'.zip',
|
||||
'.json',
|
||||
'.mem',
|
||||
'.asm.js',
|
||||
'.worker.js',
|
||||
'.whl',
|
||||
];
|
||||
|
||||
const SAFE_SUBPATH = /^\/[A-Za-z0-9._/-]*$/;
|
||||
|
||||
function isSafeSubpath(subpath) {
|
||||
return (
|
||||
SAFE_SUBPATH.test(subpath) &&
|
||||
!subpath.split('/').some((segment) => segment === '.' || segment === '..')
|
||||
);
|
||||
}
|
||||
|
||||
function getRateLimitKey(ip) {
|
||||
if (!ip.includes(':') || ip.includes('.')) return ip;
|
||||
const sections = ip.toLowerCase().split('::');
|
||||
if (sections.length > 2) return ip;
|
||||
const leadingGroups = sections[0] ? sections[0].split(':') : [];
|
||||
const trailingGroups = sections[1] ? sections[1].split(':') : [];
|
||||
const missing = 8 - leadingGroups.length - trailingGroups.length;
|
||||
if (sections.length === 1 ? missing !== 0 : missing < 1) return ip;
|
||||
const groups = [
|
||||
...leadingGroups,
|
||||
...Array(missing).fill('0'),
|
||||
...trailingGroups,
|
||||
];
|
||||
if (!groups.every((group) => /^[0-9a-f]{1,4}$/.test(group))) return ip;
|
||||
return `${groups
|
||||
.slice(0, 4)
|
||||
.map((group) => parseInt(group, 16).toString(16))
|
||||
.join(':')}::/64`;
|
||||
}
|
||||
|
||||
function isAllowedOrigin(origin) {
|
||||
if (!origin) return true; // Allow no-origin requests (e.g., direct browser navigation)
|
||||
return ALLOWED_ORIGINS.some((allowed) =>
|
||||
origin.startsWith(allowed.replace(/\/$/, ''))
|
||||
);
|
||||
}
|
||||
|
||||
function isAllowedFile(pathname) {
|
||||
const ext = pathname.substring(pathname.lastIndexOf('.')).toLowerCase();
|
||||
if (ALLOWED_EXTENSIONS.includes(ext)) return true;
|
||||
|
||||
if (!pathname.includes('.') || pathname.endsWith('/')) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function corsHeaders(origin) {
|
||||
return {
|
||||
'Access-Control-Allow-Origin': origin || '*',
|
||||
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Range, Cache-Control',
|
||||
'Access-Control-Expose-Headers':
|
||||
'Content-Length, Content-Range, Content-Type',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
};
|
||||
}
|
||||
|
||||
function handleOptions(request) {
|
||||
const origin = request.headers.get('Origin');
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: corsHeaders(origin),
|
||||
});
|
||||
}
|
||||
|
||||
function getContentType(pathname) {
|
||||
const ext = pathname.substring(pathname.lastIndexOf('.')).toLowerCase();
|
||||
const contentTypes = {
|
||||
'.js': 'application/javascript',
|
||||
'.mjs': 'application/javascript',
|
||||
'.wasm': 'application/wasm',
|
||||
'.json': 'application/json',
|
||||
'.data': 'application/octet-stream',
|
||||
'.py': 'text/x-python',
|
||||
'.so': 'application/octet-stream',
|
||||
'.zip': 'application/zip',
|
||||
'.mem': 'application/octet-stream',
|
||||
};
|
||||
return contentTypes[ext] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
async function proxyRequest(request, env, sourceBaseUrl, subpath, origin) {
|
||||
if (!sourceBaseUrl) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Source not configured',
|
||||
message: 'This WASM module source URL has not been configured.',
|
||||
}),
|
||||
{
|
||||
status: 503,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedBase = sourceBaseUrl.endsWith('/')
|
||||
? sourceBaseUrl.slice(0, -1)
|
||||
: sourceBaseUrl;
|
||||
const normalizedPath = subpath.startsWith('/') ? subpath : `/${subpath}`;
|
||||
|
||||
let targetUrl = null;
|
||||
if (isSafeSubpath(normalizedPath)) {
|
||||
try {
|
||||
const candidate = new URL(`${normalizedBase}${normalizedPath}`).href;
|
||||
if (candidate.startsWith(new URL(`${normalizedBase}/`).href)) {
|
||||
targetUrl = candidate;
|
||||
}
|
||||
} catch {
|
||||
targetUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetUrl) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Invalid path',
|
||||
message: 'The requested path is not allowed.',
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAllowedFile(normalizedPath)) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Forbidden file type',
|
||||
message: 'Only WASM-related file types are allowed.',
|
||||
}),
|
||||
{
|
||||
status: 403,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const cacheKey = new Request(targetUrl, request);
|
||||
const cache = caches.default;
|
||||
let response = await cache.match(cacheKey);
|
||||
|
||||
if (!response) {
|
||||
response = await fetch(targetUrl, {
|
||||
headers: {
|
||||
'User-Agent': 'BentoPDF-WASM-Proxy/1.0',
|
||||
Accept: '*/*',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Failed to fetch resource',
|
||||
status: response.status,
|
||||
}),
|
||||
{
|
||||
status: response.status,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const contentLength = parseInt(
|
||||
response.headers.get('Content-Length') || '0',
|
||||
10
|
||||
);
|
||||
if (contentLength > MAX_FILE_SIZE_BYTES) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'File too large',
|
||||
message: `File exceeds maximum size of ${MAX_FILE_SIZE_BYTES / 1024 / 1024}MB`,
|
||||
}),
|
||||
{
|
||||
status: 413,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
response = new Response(response.body, response);
|
||||
response.headers.set(
|
||||
'Cache-Control',
|
||||
`public, max-age=${CACHE_TTL_SECONDS}`
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
await cache.put(cacheKey, response.clone());
|
||||
}
|
||||
}
|
||||
|
||||
const bodyData = await response.arrayBuffer();
|
||||
|
||||
return new Response(bodyData, {
|
||||
status: 200,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': getContentType(normalizedPath),
|
||||
'Content-Length': bodyData.byteLength.toString(),
|
||||
'Cache-Control': `public, max-age=${CACHE_TTL_SECONDS}`,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[WASM Proxy] Fetch error:', error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Proxy error',
|
||||
message: 'Failed to fetch the requested resource',
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request, env, ctx) {
|
||||
const url = new URL(request.url);
|
||||
const pathname = url.pathname;
|
||||
const origin = request.headers.get('Origin');
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return handleOptions(request);
|
||||
}
|
||||
|
||||
if (!isAllowedOrigin(origin)) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Forbidden',
|
||||
message:
|
||||
'Origin not allowed. Add your domain to ALLOWED_ORIGINS if self-hosting.',
|
||||
}),
|
||||
{
|
||||
status: 403,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders(origin),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
||||
return new Response('Method not allowed', {
|
||||
status: 405,
|
||||
headers: corsHeaders(origin),
|
||||
});
|
||||
}
|
||||
|
||||
if (env.RATE_LIMIT_KV) {
|
||||
const clientIP = request.headers.get('CF-Connecting-IP') || 'unknown';
|
||||
const rateLimitKey = `wasm-ratelimit:${getRateLimitKey(clientIP)}`;
|
||||
const now = Date.now();
|
||||
|
||||
try {
|
||||
const rateLimitData = await env.RATE_LIMIT_KV.get(rateLimitKey, {
|
||||
type: 'json',
|
||||
});
|
||||
const requests = rateLimitData?.requests || [];
|
||||
const recentRequests = requests.filter(
|
||||
(t) => now - t < RATE_LIMIT_WINDOW_MS
|
||||
);
|
||||
|
||||
if (recentRequests.length >= RATE_LIMIT_MAX_REQUESTS) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Rate limit exceeded',
|
||||
message: `Maximum ${RATE_LIMIT_MAX_REQUESTS} requests per minute.`,
|
||||
}),
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
'Retry-After': '60',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
recentRequests.push(now);
|
||||
await env.RATE_LIMIT_KV.put(
|
||||
rateLimitKey,
|
||||
JSON.stringify({ requests: recentRequests }),
|
||||
{
|
||||
expirationTtl: 120,
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'[WASM Proxy] Rate limit KV error, allowing request:',
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/pymupdf/')) {
|
||||
const subpath = pathname.replace('/pymupdf', '');
|
||||
return proxyRequest(request, env, env.PYMUPDF_SOURCE, subpath, origin);
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/gs/')) {
|
||||
const subpath = pathname.replace('/gs', '');
|
||||
return proxyRequest(request, env, env.GS_SOURCE, subpath, origin);
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/cpdf/')) {
|
||||
const subpath = pathname.replace('/cpdf', '');
|
||||
return proxyRequest(request, env, env.CPDF_SOURCE, subpath, origin);
|
||||
}
|
||||
|
||||
if (pathname === '/' || pathname === '/health') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
service: 'BentoPDF WASM Proxy',
|
||||
version: '1.0.0',
|
||||
endpoints: {
|
||||
pymupdf: '/pymupdf/*',
|
||||
gs: '/gs/*',
|
||||
cpdf: '/cpdf/*',
|
||||
},
|
||||
configured: {
|
||||
pymupdf: !!env.PYMUPDF_SOURCE,
|
||||
gs: !!env.GS_SOURCE,
|
||||
cpdf: !!env.CPDF_SOURCE,
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Not Found',
|
||||
message: 'Use /pymupdf/*, /gs/*, or /cpdf/* endpoints',
|
||||
}),
|
||||
{
|
||||
status: 404,
|
||||
headers: {
|
||||
...corsHeaders(origin),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
name = "bentopdf-wasm-proxy"
|
||||
main = "wasm-proxy-worker.js"
|
||||
compatibility_date = "2024-01-01"
|
||||
|
||||
# =============================================================================
|
||||
# DEPLOYMENT
|
||||
# =============================================================================
|
||||
# Deploy this worker:
|
||||
# cd cloudflare
|
||||
# npx wrangler deploy -c wasm-wrangler.toml
|
||||
#
|
||||
# Set environment secrets (one of the following methods):
|
||||
# Option A: Cloudflare Dashboard
|
||||
# Go to Workers & Pages > bentopdf-wasm-proxy > Settings > Variables
|
||||
# Add: PYMUPDF_SOURCE, GS_SOURCE, CPDF_SOURCE
|
||||
#
|
||||
# Option B: Wrangler CLI
|
||||
# npx wrangler secret put PYMUPDF_SOURCE -c wasm-wrangler.toml
|
||||
# npx wrangler secret put GS_SOURCE -c wasm-wrangler.toml
|
||||
# npx wrangler secret put CPDF_SOURCE -c wasm-wrangler.toml
|
||||
|
||||
# =============================================================================
|
||||
# WASM SOURCE URLS
|
||||
# =============================================================================
|
||||
# Set these as secrets in the Cloudflare dashboard or via wrangler:
|
||||
#
|
||||
# PYMUPDF_SOURCE: Base URL to PyMuPDF WASM files
|
||||
# Example: https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm/assets
|
||||
# https://your-bucket.r2.cloudflarestorage.com/pymupdf
|
||||
#
|
||||
# GS_SOURCE: Base URL to Ghostscript WASM files
|
||||
# Example: https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets
|
||||
# https://your-bucket.r2.cloudflarestorage.com/gs
|
||||
#
|
||||
# CPDF_SOURCE: Base URL to CoherentPDF files
|
||||
# Example: https://cdn.jsdelivr.net/npm/coherentpdf/cpdf
|
||||
# https://your-bucket.r2.cloudflarestorage.com/cpdf
|
||||
|
||||
# =============================================================================
|
||||
# USAGE FROM BENTOPDF
|
||||
# =============================================================================
|
||||
# In BentoPDF's WASM Settings page, configure URLs like:
|
||||
# PyMuPDF: https://wasm.bentopdf.com/pymupdf/
|
||||
# Ghostscript: https://wasm.bentopdf.com/gs/
|
||||
# CoherentPDF: https://wasm.bentopdf.com/cpdf/
|
||||
|
||||
# =============================================================================
|
||||
# RATE LIMITING (Optional but recommended)
|
||||
# =============================================================================
|
||||
# Create KV namespace:
|
||||
# npx wrangler kv namespace create "RATE_LIMIT_KV"
|
||||
#
|
||||
# Then uncomment and update the ID below:
|
||||
# [[kv_namespaces]]
|
||||
# binding = "RATE_LIMIT_KV"
|
||||
# id = "<YOUR_KV_NAMESPACE_ID>"
|
||||
|
||||
# Use the same KV namespace as the CORS proxy if you want shared rate limiting
|
||||
[[kv_namespaces]]
|
||||
binding = "RATE_LIMIT_KV"
|
||||
id = "b88e030b308941118cd484e3fcb3ae49"
|
||||
|
||||
# =============================================================================
|
||||
# CUSTOM DOMAIN (Optional)
|
||||
# =============================================================================
|
||||
# If you want a custom domain like wasm.bentopdf.com:
|
||||
# routes = [
|
||||
# { pattern = "wasm.bentopdf.com/*", zone_name = "bentopdf.com" }
|
||||
# ]
|
||||
@@ -0,0 +1,45 @@
|
||||
name = "bentopdf-cors-proxy"
|
||||
main = "cors-proxy-worker.js"
|
||||
compatibility_date = "2024-01-01"
|
||||
|
||||
# Deploy to Cloudflare's global network
|
||||
# If you are self-hosting, change the name to your worker name
|
||||
# Run: npx wrangler deploy
|
||||
|
||||
# =============================================================================
|
||||
# SECURITY FEATURES
|
||||
# =============================================================================
|
||||
#
|
||||
# 1. SIGNATURE VERIFICATION (Optional - for anti-spoofing)
|
||||
# - Generate secret: openssl rand -hex 32
|
||||
# - Set secret: npx wrangler secret put PROXY_SECRET
|
||||
# - Note: Secret is visible in frontend JS, so provides limited protection
|
||||
#
|
||||
# 2. RATE LIMITING (Recommended - requires KV)
|
||||
# - Create KV namespace: npx wrangler kv namespace create "RATE_LIMIT_KV"
|
||||
# - Uncomment the kv_namespaces section below with the returned ID
|
||||
# - Limits: 60 requests per IP per minute
|
||||
#
|
||||
# 3. FILE SIZE LIMIT
|
||||
# - Automatic: Rejects files larger than 1MB
|
||||
# - Certificates are typically <10KB, so this prevents abuse
|
||||
#
|
||||
# 4. URL RESTRICTIONS
|
||||
# - Only certificate URLs allowed (*.crt, *.cer, *.pem, /certs/, etc.)
|
||||
# - Blocks private IPs (localhost, 10.x, 192.168.x, 172.16-31.x)
|
||||
|
||||
# =============================================================================
|
||||
# KV NAMESPACE FOR RATE LIMITING
|
||||
# =============================================================================
|
||||
[[kv_namespaces]]
|
||||
binding = "RATE_LIMIT_KV"
|
||||
id = "b88e030b308941118cd484e3fcb3ae49"
|
||||
|
||||
# Optional: Custom domain routing
|
||||
# routes = [
|
||||
# { pattern = "cors-proxy.bentopdf.com/*", zone_name = "bentopdf.com" }
|
||||
# ]
|
||||
|
||||
# Optional: Environment variables (for non-secret config)
|
||||
# [vars]
|
||||
# ALLOWED_ORIGINS = "https://www.bentopdf.com,https://bentopdf.com"
|
||||
+147
-161
@@ -1,183 +1,169 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>Contact BentoPDF - Get in Touch | Support</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Contact BentoPDF for support, questions, or feedback. Reach us at [email protected] - we're here to help with your PDF needs."
|
||||
/>
|
||||
<meta name="author" content="BentoPDF" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Contact Us - BentoPDF</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||
<link rel="apple-touch-icon" href="/images/favicon.png" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="alternate" hreflang="en" href="/en/contact.html" />
|
||||
<link rel="alternate" hreflang="de" href="/de/contact.html" />
|
||||
<link rel="alternate" hreflang="vi" href="/vi/contact.html" />
|
||||
<link rel="alternate" hreflang="x-default" href="/en/contact.html" />
|
||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href="https://www.bentopdf.com/contact" />
|
||||
|
||||
<body class="antialiased bg-gray-900 text-gray-300">
|
||||
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
|
||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
|
||||
<span class="text-white font-bold text-xl ml-2">
|
||||
<a href="index.html">BentoPDF</a>
|
||||
</span>
|
||||
</div>
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://www.bentopdf.com/contact" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Contact BentoPDF - Get in Touch | Support"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Contact BentoPDF for support, questions, or feedback. Reach us at [email protected] - we're here to help with your PDF needs."
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://www.bentopdf.com/images/og-contact.png"
|
||||
/>
|
||||
<meta property="og:site_name" content="BentoPDF" />
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
|
||||
<a href="/about.html" class="nav-link" data-i18n="nav.about">About</a>
|
||||
<a href="/contact.html" class="nav-link" data-i18n="nav.contact">Contact</a>
|
||||
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing">Licensing</a>
|
||||
<a href="index.html#tools-header" class="nav-link" data-i18n="nav.allTools">All Tools</a>
|
||||
</div>
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Contact BentoPDF - Get in Touch | Support"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Contact BentoPDF for support, questions, or feedback. Reach us at [email protected] - we're here to help with your PDF needs."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://www.bentopdf.com/images/twitter-contact.png"
|
||||
/>
|
||||
<meta name="twitter:site" content="@BentoPDF" />
|
||||
|
||||
<!-- Mobile Hamburger Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-button" type="button"
|
||||
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
|
||||
aria-controls="mobile-menu" aria-expanded="false">
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<!-- Hamburger Icon -->
|
||||
<svg id="menu-icon" class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
<!-- Close Icon -->
|
||||
<svg id="close-icon" class="hidden h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<link href="/src/css/styles.css" rel="stylesheet" />
|
||||
<link href="/src/css/landing.css" rel="stylesheet" />
|
||||
|
||||
<!-- Mobile Menu Dropdown -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
|
||||
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
|
||||
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home">Home</a>
|
||||
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about">About</a>
|
||||
<a href="/contact.html" class="mobile-nav-link" data-i18n="nav.contact">Contact</a>
|
||||
<a href="/licensing.html" class="mobile-nav-link" data-i18n="nav.licensing">Licensing</a>
|
||||
<a href="index.html#tools-header" class="mobile-nav-link" data-i18n="nav.allTools">All Tools</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Web App Manifest -->
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
|
||||
<section id="contact-hero" class="text-center py-16 md:py-24">
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-white mb-4" data-i18n="contact.title">
|
||||
Get in Touch
|
||||
</h1>
|
||||
<p class="text-lg md:text-xl text-gray-400 max-w-3xl mx-auto" data-i18n="contact.subtitle">
|
||||
We'd love to hear from you. Whether you have a question, feedback, or
|
||||
a feature request, please don't hesitate to reach out.
|
||||
</p>
|
||||
</section>
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="192x192"
|
||||
href="/images/favicon-192x192.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="512x512"
|
||||
href="/images/favicon-512x512.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/images/apple-touch-icon.png"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||
</head>
|
||||
|
||||
<div class="max-w-2xl mx-auto text-center py-8">
|
||||
<p class="text-lg text-gray-400">
|
||||
<span data-i18n="contact.email">You can reach us directly by email at:</span>
|
||||
<a href="mailto:[email protected]"
|
||||
class="text-indigo-400 underline hover:text-indigo-300">[email protected]</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<body class="lp antialiased">
|
||||
{{> navbar }}
|
||||
|
||||
<footer class="mt-16 border-t-2 border-gray-700 py-8">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
|
||||
<div class="mb-8 md:mb-0">
|
||||
<div class="flex items-center justify-center md:justify-start mb-4">
|
||||
<img src="/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
|
||||
<span class="text-xl font-bold text-white">BentoPDF</span>
|
||||
<div id="app">
|
||||
<section id="contact-hero" class="lp-sec lp-sec--first">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-cta">
|
||||
<div>
|
||||
<h1 class="lp-h2">
|
||||
<span class="line" data-i18n="contact.title">Get in Touch</span>
|
||||
</h1>
|
||||
<p class="lp-head-p" data-i18n="contact.subtitle">
|
||||
We'd love to hear from you. Whether you have a question,
|
||||
feedback, or a feature request, please don't hesitate to reach
|
||||
out.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
|
||||
© 2025 BentoPDF. All rights reserved.
|
||||
</p>
|
||||
<p class="text-gray-500 text-xs mt-2">
|
||||
<span data-i18n="footer.version">Version</span> <span id="app-version"></span>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div>
|
||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">Company</h3>
|
||||
<ul class="space-y-2 text-gray-400">
|
||||
<li>
|
||||
<a href="/about.html" class="hover:text-indigo-400" data-i18n="footer.aboutUs">About Us</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/faq.html" class="hover:text-indigo-400" data-i18n="footer.faqLink">FAQ</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/contact.html" class="hover:text-indigo-400" data-i18n="footer.contactUs">Contact Us</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<section id="contact-channels" class="lp-sec lp-band">
|
||||
<div class="lp-frame">
|
||||
<div class="lp-grid lp-grid--2 lp-cross">
|
||||
<a class="lp-cell lp-reveal" href="mailto:[email protected]">
|
||||
<span class="lp-tile"><i data-lucide="mail"></i></span>
|
||||
<h3 data-i18n="contact.emailTitle">Email</h3>
|
||||
<p data-i18n="contact.email">
|
||||
You can reach us directly by email at:
|
||||
</p>
|
||||
<b>[email protected]</b>
|
||||
</a>
|
||||
|
||||
<div>
|
||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">Legal</h3>
|
||||
<ul class="space-y-2 text-gray-400">
|
||||
<li>
|
||||
<a href="/licensing.html" class="hover:text-indigo-400" data-i18n="nav.licensing">Licensing</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/terms.html" class="hover:text-indigo-400" data-i18n="footer.termsAndConditions">Terms and
|
||||
Conditions</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/privacy.html" class="hover:text-indigo-400" data-i18n="footer.privacyPolicy">Privacy Policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a
|
||||
class="lp-cell lp-reveal"
|
||||
href="https://github.com/alam00000/bentopdf/issues"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class="lp-tile"><i data-lucide="github"></i></span>
|
||||
<h3>GitHub</h3>
|
||||
<p data-i18n="contact.githubText">
|
||||
Report a bug, request a feature, or read the source.
|
||||
</p>
|
||||
<b>github.com/alam00000/bentopdf</b>
|
||||
</a>
|
||||
|
||||
<div>
|
||||
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">Follow Us</h3>
|
||||
<div class="flex justify-center md:justify-start space-x-4">
|
||||
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
|
||||
class="text-gray-400 hover:text-indigo-400" title="GitHub">
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<a
|
||||
class="lp-cell lp-reveal"
|
||||
href="https://discord.gg/Bgq3Ay3f2w"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class="lp-tile"><i data-lucide="message-circle"></i></span>
|
||||
<h3>Discord</h3>
|
||||
<p data-i18n="contact.discordText">
|
||||
Join the community to chat with us and other users.
|
||||
</p>
|
||||
<b>discord.gg/Bgq3Ay3f2w</b>
|
||||
</a>
|
||||
<a href="https://discord.gg/Bgq3Ay3f2w" target="_blank" rel="noopener noreferrer"
|
||||
class="text-gray-400 hover:text-indigo-400" title="Discord">
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.instagram.com/thebentopdf/" class="text-gray-400 hover:text-indigo-400"
|
||||
title="Instagram">
|
||||
<i data-lucide="instagram"></i>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/company/bentopdf/" class="text-gray-400 hover:text-indigo-400"
|
||||
title="LinkedIn">
|
||||
<i data-lucide="linkedin"></i>
|
||||
</a>
|
||||
<a href="https://x.com/BentoPDF" class="text-gray-400 hover:text-indigo-400" title="X (Twitter)">
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
||||
</svg>
|
||||
|
||||
<a class="lp-cell lp-reveal" href="faq.html">
|
||||
<span class="lp-tile"><i data-lucide="help-circle"></i></span>
|
||||
<h3 data-i18n="footer.faqLink">FAQ</h3>
|
||||
<p data-i18n="contact.faqText">
|
||||
Find quick answers to the questions we get most often.
|
||||
</p>
|
||||
<b data-i18n="contact.faqLink">Read the FAQ</b>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||
<script type="module" src="src/version.ts"></script>
|
||||
<script type="module" src="src/js/main.ts"></script>
|
||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||
</body>
|
||||
{{> footer }}
|
||||
|
||||
</html>
|
||||
<script type="module" src="src/js/utils/lucide-init.ts"></script>
|
||||
<script type="module" src="src/js/main.ts"></script>
|
||||
<script type="module" src="src/js/mobileMenu.ts"></script>
|
||||
<script type="module" src="src/js/landing.ts"></script>
|
||||
<!-- JSON-LD Schema -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ContactPage",
|
||||
"name": "Contact BentoPDF - Get in Touch | Support",
|
||||
"url": "https://www.bentopdf.com/contact"
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,3 +9,6 @@ services:
|
||||
ports:
|
||||
- '8080:8080'
|
||||
restart: unless-stopped
|
||||
# For IPv4-only environments
|
||||
#environment:
|
||||
# - DISABLE_IPV6=true
|
||||
|
||||
+10
-3
@@ -1,9 +1,16 @@
|
||||
services:
|
||||
bentopdf:
|
||||
# simple mode - bentopdf/bentopdf-simple:latest
|
||||
# default mode - bentopdf/bentopdf:latest
|
||||
image: bentopdf/bentopdf-simple:latest
|
||||
# GitHub Container Registry (Recommended)
|
||||
# Self-Hosted build - ghcr.io/alam00000/bentopdf-simple:latest
|
||||
# Commercial build - ghcr.io/alam00000/bentopdf:latest
|
||||
# Docker Hub (Alternative)
|
||||
# Self-Hosted build - bentopdfteam/bentopdf-simple:latest
|
||||
# Commercial build - bentopdfteam/bentopdf:latest
|
||||
image: ghcr.io/alam00000/bentopdf-simple:latest
|
||||
container_name: bentopdf
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '8080:8080'
|
||||
# For IPv4-only environments
|
||||
#environment:
|
||||
# - DISABLE_IPV6=true
|
||||
|
||||
-275
@@ -1,275 +0,0 @@
|
||||
import {
|
||||
useMediaQuery
|
||||
} from "./chunk-2CLQ7TTZ.js";
|
||||
import {
|
||||
computed,
|
||||
ref,
|
||||
shallowRef,
|
||||
watch
|
||||
} from "./chunk-LE5NDSFD.js";
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/index.js
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/base.css";
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/icons.css";
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
|
||||
import "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
|
||||
import VPBadge from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
|
||||
import Layout from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/Layout.vue";
|
||||
import { default as default2 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
|
||||
import { default as default3 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
|
||||
import { default as default4 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
|
||||
import { default as default5 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPFeatures.vue";
|
||||
import { default as default6 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPHomeContent.vue";
|
||||
import { default as default7 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
|
||||
import { default as default8 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
|
||||
import { default as default9 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
|
||||
import { default as default10 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
|
||||
import { default as default11 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPLink.vue";
|
||||
import { default as default12 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPNavBarSearch.vue";
|
||||
import { default as default13 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPSocialLink.vue";
|
||||
import { default as default14 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPSocialLinks.vue";
|
||||
import { default as default15 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
|
||||
import { default as default16 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
|
||||
import { default as default17 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
|
||||
import { default as default18 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
|
||||
import { default as default19 } from "/Users/abdullahtapadar/Desktop/workspaceforbento/bento-pdf/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
|
||||
import { onContentUpdated } from "vitepress";
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/composables/outline.js
|
||||
import { getScrollOffset } from "vitepress";
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/support/utils.js
|
||||
import { withBase } from "vitepress";
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/composables/data.js
|
||||
import { useData as useData$ } from "vitepress";
|
||||
var useData = useData$;
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/support/utils.js
|
||||
function ensureStartingSlash(path) {
|
||||
return path.startsWith("/") ? path : `/${path}`;
|
||||
}
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/support/sidebar.js
|
||||
function getSidebar(_sidebar, path) {
|
||||
if (Array.isArray(_sidebar))
|
||||
return addBase(_sidebar);
|
||||
if (_sidebar == null)
|
||||
return [];
|
||||
path = ensureStartingSlash(path);
|
||||
const dir = Object.keys(_sidebar).sort((a, b) => {
|
||||
return b.split("/").length - a.split("/").length;
|
||||
}).find((dir2) => {
|
||||
return path.startsWith(ensureStartingSlash(dir2));
|
||||
});
|
||||
const sidebar = dir ? _sidebar[dir] : [];
|
||||
return Array.isArray(sidebar) ? addBase(sidebar) : addBase(sidebar.items, sidebar.base);
|
||||
}
|
||||
function getSidebarGroups(sidebar) {
|
||||
const groups = [];
|
||||
let lastGroupIndex = 0;
|
||||
for (const index in sidebar) {
|
||||
const item = sidebar[index];
|
||||
if (item.items) {
|
||||
lastGroupIndex = groups.push(item);
|
||||
continue;
|
||||
}
|
||||
if (!groups[lastGroupIndex]) {
|
||||
groups.push({ items: [] });
|
||||
}
|
||||
groups[lastGroupIndex].items.push(item);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
function addBase(items, _base) {
|
||||
return [...items].map((_item) => {
|
||||
const item = { ..._item };
|
||||
const base = item.base || _base;
|
||||
if (base && item.link)
|
||||
item.link = base + item.link;
|
||||
if (item.items)
|
||||
item.items = addBase(item.items, base);
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/composables/sidebar.js
|
||||
function useSidebar() {
|
||||
const { frontmatter, page, theme: theme2 } = useData();
|
||||
const is960 = useMediaQuery("(min-width: 960px)");
|
||||
const isOpen = ref(false);
|
||||
const _sidebar = computed(() => {
|
||||
const sidebarConfig = theme2.value.sidebar;
|
||||
const relativePath = page.value.relativePath;
|
||||
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [];
|
||||
});
|
||||
const sidebar = ref(_sidebar.value);
|
||||
watch(_sidebar, (next, prev) => {
|
||||
if (JSON.stringify(next) !== JSON.stringify(prev))
|
||||
sidebar.value = _sidebar.value;
|
||||
});
|
||||
const hasSidebar = computed(() => {
|
||||
return frontmatter.value.sidebar !== false && sidebar.value.length > 0 && frontmatter.value.layout !== "home";
|
||||
});
|
||||
const leftAside = computed(() => {
|
||||
if (hasAside)
|
||||
return frontmatter.value.aside == null ? theme2.value.aside === "left" : frontmatter.value.aside === "left";
|
||||
return false;
|
||||
});
|
||||
const hasAside = computed(() => {
|
||||
if (frontmatter.value.layout === "home")
|
||||
return false;
|
||||
if (frontmatter.value.aside != null)
|
||||
return !!frontmatter.value.aside;
|
||||
return theme2.value.aside !== false;
|
||||
});
|
||||
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value);
|
||||
const sidebarGroups = computed(() => {
|
||||
return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];
|
||||
});
|
||||
function open() {
|
||||
isOpen.value = true;
|
||||
}
|
||||
function close() {
|
||||
isOpen.value = false;
|
||||
}
|
||||
function toggle() {
|
||||
isOpen.value ? close() : open();
|
||||
}
|
||||
return {
|
||||
isOpen,
|
||||
sidebar,
|
||||
sidebarGroups,
|
||||
hasSidebar,
|
||||
hasAside,
|
||||
leftAside,
|
||||
isSidebarEnabled,
|
||||
open,
|
||||
close,
|
||||
toggle
|
||||
};
|
||||
}
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/composables/outline.js
|
||||
var ignoreRE = /\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/;
|
||||
var resolvedHeaders = [];
|
||||
function getHeaders(range) {
|
||||
const headers = [
|
||||
...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")
|
||||
].filter((el) => el.id && el.hasChildNodes()).map((el) => {
|
||||
const level = Number(el.tagName[1]);
|
||||
return {
|
||||
element: el,
|
||||
title: serializeHeader(el),
|
||||
link: "#" + el.id,
|
||||
level
|
||||
};
|
||||
});
|
||||
return resolveHeaders(headers, range);
|
||||
}
|
||||
function serializeHeader(h) {
|
||||
let ret = "";
|
||||
for (const node of h.childNodes) {
|
||||
if (node.nodeType === 1) {
|
||||
if (ignoreRE.test(node.className))
|
||||
continue;
|
||||
ret += node.textContent;
|
||||
} else if (node.nodeType === 3) {
|
||||
ret += node.textContent;
|
||||
}
|
||||
}
|
||||
return ret.trim();
|
||||
}
|
||||
function resolveHeaders(headers, range) {
|
||||
if (range === false) {
|
||||
return [];
|
||||
}
|
||||
const levelsRange = (typeof range === "object" && !Array.isArray(range) ? range.level : range) || 2;
|
||||
const [high, low] = typeof levelsRange === "number" ? [levelsRange, levelsRange] : levelsRange === "deep" ? [2, 6] : levelsRange;
|
||||
return buildTree(headers, high, low);
|
||||
}
|
||||
function buildTree(data, min, max) {
|
||||
resolvedHeaders.length = 0;
|
||||
const result = [];
|
||||
const stack = [];
|
||||
data.forEach((item) => {
|
||||
const node = { ...item, children: [] };
|
||||
let parent = stack[stack.length - 1];
|
||||
while (parent && parent.level >= node.level) {
|
||||
stack.pop();
|
||||
parent = stack[stack.length - 1];
|
||||
}
|
||||
if (node.element.classList.contains("ignore-header") || parent && "shouldIgnore" in parent) {
|
||||
stack.push({ level: node.level, shouldIgnore: true });
|
||||
return;
|
||||
}
|
||||
if (node.level > max || node.level < min)
|
||||
return;
|
||||
resolvedHeaders.push({ element: node.element, link: node.link });
|
||||
if (parent)
|
||||
parent.children.push(node);
|
||||
else
|
||||
result.push(node);
|
||||
stack.push(node);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
|
||||
function useLocalNav() {
|
||||
const { theme: theme2, frontmatter } = useData();
|
||||
const headers = shallowRef([]);
|
||||
const hasLocalNav = computed(() => {
|
||||
return headers.value.length > 0;
|
||||
});
|
||||
onContentUpdated(() => {
|
||||
headers.value = getHeaders(frontmatter.value.outline ?? theme2.value.outline);
|
||||
});
|
||||
return {
|
||||
headers,
|
||||
hasLocalNav
|
||||
};
|
||||
}
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
|
||||
var theme = {
|
||||
Layout,
|
||||
enhanceApp: ({ app }) => {
|
||||
app.component("Badge", VPBadge);
|
||||
}
|
||||
};
|
||||
var without_fonts_default = theme;
|
||||
export {
|
||||
default2 as VPBadge,
|
||||
default3 as VPButton,
|
||||
default4 as VPDocAsideSponsors,
|
||||
default5 as VPFeatures,
|
||||
default6 as VPHomeContent,
|
||||
default7 as VPHomeFeatures,
|
||||
default8 as VPHomeHero,
|
||||
default9 as VPHomeSponsors,
|
||||
default10 as VPImage,
|
||||
default11 as VPLink,
|
||||
default12 as VPNavBarSearch,
|
||||
default13 as VPSocialLink,
|
||||
default14 as VPSocialLinks,
|
||||
default15 as VPSponsors,
|
||||
default16 as VPTeamMembers,
|
||||
default17 as VPTeamPage,
|
||||
default18 as VPTeamPageSection,
|
||||
default19 as VPTeamPageTitle,
|
||||
without_fonts_default as default,
|
||||
useLocalNav,
|
||||
useSidebar
|
||||
};
|
||||
//# sourceMappingURL=@theme_index.js.map
|
||||
File diff suppressed because one or more lines are too long
-58
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"hash": "9bc6d4f1",
|
||||
"configHash": "de601e5a",
|
||||
"lockfileHash": "6c1ba38e",
|
||||
"browserHash": "669b23ae",
|
||||
"optimized": {
|
||||
"vue": {
|
||||
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
||||
"file": "vue.js",
|
||||
"fileHash": "25632ac1",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > @vue/devtools-api": {
|
||||
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
|
||||
"file": "vitepress___@vue_devtools-api.js",
|
||||
"fileHash": "f01e74c0",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > @vueuse/core": {
|
||||
"src": "../../../../node_modules/@vueuse/core/index.mjs",
|
||||
"file": "vitepress___@vueuse_core.js",
|
||||
"fileHash": "8c581a91",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > @vueuse/integrations/useFocusTrap": {
|
||||
"src": "../../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
|
||||
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
|
||||
"fileHash": "2cd049fb",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > mark.js/src/vanilla.js": {
|
||||
"src": "../../../../node_modules/mark.js/src/vanilla.js",
|
||||
"file": "vitepress___mark__js_src_vanilla__js.js",
|
||||
"fileHash": "8f240f8c",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > minisearch": {
|
||||
"src": "../../../../node_modules/minisearch/dist/es/index.js",
|
||||
"file": "vitepress___minisearch.js",
|
||||
"fileHash": "4848f633",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@theme/index": {
|
||||
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
|
||||
"file": "@theme_index.js",
|
||||
"fileHash": "b83632c3",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
"chunks": {
|
||||
"chunk-2CLQ7TTZ": {
|
||||
"file": "chunk-2CLQ7TTZ.js"
|
||||
},
|
||||
"chunk-LE5NDSFD": {
|
||||
"file": "chunk-LE5NDSFD.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
-9719
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-12824
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,583 +0,0 @@
|
||||
import {
|
||||
DefaultMagicKeysAliasMap,
|
||||
StorageSerializers,
|
||||
TransitionPresets,
|
||||
assert,
|
||||
breakpointsAntDesign,
|
||||
breakpointsBootstrapV5,
|
||||
breakpointsElement,
|
||||
breakpointsMasterCss,
|
||||
breakpointsPrimeFlex,
|
||||
breakpointsQuasar,
|
||||
breakpointsSematic,
|
||||
breakpointsTailwind,
|
||||
breakpointsVuetify,
|
||||
breakpointsVuetifyV2,
|
||||
breakpointsVuetifyV3,
|
||||
bypassFilter,
|
||||
camelize,
|
||||
clamp,
|
||||
cloneFnJSON,
|
||||
computedAsync,
|
||||
computedEager,
|
||||
computedInject,
|
||||
computedWithControl,
|
||||
containsProp,
|
||||
controlledRef,
|
||||
createEventHook,
|
||||
createFetch,
|
||||
createFilterWrapper,
|
||||
createGlobalState,
|
||||
createInjectionState,
|
||||
createRef,
|
||||
createReusableTemplate,
|
||||
createSharedComposable,
|
||||
createSingletonPromise,
|
||||
createTemplatePromise,
|
||||
createUnrefFn,
|
||||
customStorageEventName,
|
||||
debounceFilter,
|
||||
defaultDocument,
|
||||
defaultLocation,
|
||||
defaultNavigator,
|
||||
defaultWindow,
|
||||
executeTransition,
|
||||
extendRef,
|
||||
formatDate,
|
||||
formatTimeAgo,
|
||||
get,
|
||||
getLifeCycleTarget,
|
||||
getSSRHandler,
|
||||
hasOwn,
|
||||
hyphenate,
|
||||
identity,
|
||||
increaseWithUnit,
|
||||
injectLocal,
|
||||
invoke,
|
||||
isClient,
|
||||
isDef,
|
||||
isDefined,
|
||||
isIOS,
|
||||
isObject,
|
||||
isWorker,
|
||||
makeDestructurable,
|
||||
mapGamepadToXbox360Controller,
|
||||
noop,
|
||||
normalizeDate,
|
||||
notNullish,
|
||||
now,
|
||||
objectEntries,
|
||||
objectOmit,
|
||||
objectPick,
|
||||
onClickOutside,
|
||||
onElementRemoval,
|
||||
onKeyDown,
|
||||
onKeyPressed,
|
||||
onKeyStroke,
|
||||
onKeyUp,
|
||||
onLongPress,
|
||||
onStartTyping,
|
||||
pausableFilter,
|
||||
promiseTimeout,
|
||||
provideLocal,
|
||||
provideSSRWidth,
|
||||
pxValue,
|
||||
rand,
|
||||
reactify,
|
||||
reactifyObject,
|
||||
reactiveComputed,
|
||||
reactiveOmit,
|
||||
reactivePick,
|
||||
refAutoReset,
|
||||
refDebounced,
|
||||
refDefault,
|
||||
refThrottled,
|
||||
refWithControl,
|
||||
resolveRef,
|
||||
resolveUnref,
|
||||
set,
|
||||
setSSRHandler,
|
||||
syncRef,
|
||||
syncRefs,
|
||||
templateRef,
|
||||
throttleFilter,
|
||||
timestamp,
|
||||
toArray,
|
||||
toReactive,
|
||||
toRef,
|
||||
toRefs,
|
||||
toValue,
|
||||
tryOnBeforeMount,
|
||||
tryOnBeforeUnmount,
|
||||
tryOnMounted,
|
||||
tryOnScopeDispose,
|
||||
tryOnUnmounted,
|
||||
unrefElement,
|
||||
until,
|
||||
useActiveElement,
|
||||
useAnimate,
|
||||
useArrayDifference,
|
||||
useArrayEvery,
|
||||
useArrayFilter,
|
||||
useArrayFind,
|
||||
useArrayFindIndex,
|
||||
useArrayFindLast,
|
||||
useArrayIncludes,
|
||||
useArrayJoin,
|
||||
useArrayMap,
|
||||
useArrayReduce,
|
||||
useArraySome,
|
||||
useArrayUnique,
|
||||
useAsyncQueue,
|
||||
useAsyncState,
|
||||
useBase64,
|
||||
useBattery,
|
||||
useBluetooth,
|
||||
useBreakpoints,
|
||||
useBroadcastChannel,
|
||||
useBrowserLocation,
|
||||
useCached,
|
||||
useClipboard,
|
||||
useClipboardItems,
|
||||
useCloned,
|
||||
useColorMode,
|
||||
useConfirmDialog,
|
||||
useCountdown,
|
||||
useCounter,
|
||||
useCssVar,
|
||||
useCurrentElement,
|
||||
useCycleList,
|
||||
useDark,
|
||||
useDateFormat,
|
||||
useDebounceFn,
|
||||
useDebouncedRefHistory,
|
||||
useDeviceMotion,
|
||||
useDeviceOrientation,
|
||||
useDevicePixelRatio,
|
||||
useDevicesList,
|
||||
useDisplayMedia,
|
||||
useDocumentVisibility,
|
||||
useDraggable,
|
||||
useDropZone,
|
||||
useElementBounding,
|
||||
useElementByPoint,
|
||||
useElementHover,
|
||||
useElementSize,
|
||||
useElementVisibility,
|
||||
useEventBus,
|
||||
useEventListener,
|
||||
useEventSource,
|
||||
useEyeDropper,
|
||||
useFavicon,
|
||||
useFetch,
|
||||
useFileDialog,
|
||||
useFileSystemAccess,
|
||||
useFocus,
|
||||
useFocusWithin,
|
||||
useFps,
|
||||
useFullscreen,
|
||||
useGamepad,
|
||||
useGeolocation,
|
||||
useIdle,
|
||||
useImage,
|
||||
useInfiniteScroll,
|
||||
useIntersectionObserver,
|
||||
useInterval,
|
||||
useIntervalFn,
|
||||
useKeyModifier,
|
||||
useLastChanged,
|
||||
useLocalStorage,
|
||||
useMagicKeys,
|
||||
useManualRefHistory,
|
||||
useMediaControls,
|
||||
useMediaQuery,
|
||||
useMemoize,
|
||||
useMemory,
|
||||
useMounted,
|
||||
useMouse,
|
||||
useMouseInElement,
|
||||
useMousePressed,
|
||||
useMutationObserver,
|
||||
useNavigatorLanguage,
|
||||
useNetwork,
|
||||
useNow,
|
||||
useObjectUrl,
|
||||
useOffsetPagination,
|
||||
useOnline,
|
||||
usePageLeave,
|
||||
useParallax,
|
||||
useParentElement,
|
||||
usePerformanceObserver,
|
||||
usePermission,
|
||||
usePointer,
|
||||
usePointerLock,
|
||||
usePointerSwipe,
|
||||
usePreferredColorScheme,
|
||||
usePreferredContrast,
|
||||
usePreferredDark,
|
||||
usePreferredLanguages,
|
||||
usePreferredReducedMotion,
|
||||
usePreferredReducedTransparency,
|
||||
usePrevious,
|
||||
useRafFn,
|
||||
useRefHistory,
|
||||
useResizeObserver,
|
||||
useSSRWidth,
|
||||
useScreenOrientation,
|
||||
useScreenSafeArea,
|
||||
useScriptTag,
|
||||
useScroll,
|
||||
useScrollLock,
|
||||
useSessionStorage,
|
||||
useShare,
|
||||
useSorted,
|
||||
useSpeechRecognition,
|
||||
useSpeechSynthesis,
|
||||
useStepper,
|
||||
useStorage,
|
||||
useStorageAsync,
|
||||
useStyleTag,
|
||||
useSupported,
|
||||
useSwipe,
|
||||
useTemplateRefsList,
|
||||
useTextDirection,
|
||||
useTextSelection,
|
||||
useTextareaAutosize,
|
||||
useThrottleFn,
|
||||
useThrottledRefHistory,
|
||||
useTimeAgo,
|
||||
useTimeout,
|
||||
useTimeoutFn,
|
||||
useTimeoutPoll,
|
||||
useTimestamp,
|
||||
useTitle,
|
||||
useToNumber,
|
||||
useToString,
|
||||
useToggle,
|
||||
useTransition,
|
||||
useUrlSearchParams,
|
||||
useUserMedia,
|
||||
useVModel,
|
||||
useVModels,
|
||||
useVibrate,
|
||||
useVirtualList,
|
||||
useWakeLock,
|
||||
useWebNotification,
|
||||
useWebSocket,
|
||||
useWebWorker,
|
||||
useWebWorkerFn,
|
||||
useWindowFocus,
|
||||
useWindowScroll,
|
||||
useWindowSize,
|
||||
watchArray,
|
||||
watchAtMost,
|
||||
watchDebounced,
|
||||
watchDeep,
|
||||
watchIgnorable,
|
||||
watchImmediate,
|
||||
watchOnce,
|
||||
watchPausable,
|
||||
watchThrottled,
|
||||
watchTriggerable,
|
||||
watchWithFilter,
|
||||
whenever
|
||||
} from "./chunk-2CLQ7TTZ.js";
|
||||
import "./chunk-LE5NDSFD.js";
|
||||
export {
|
||||
DefaultMagicKeysAliasMap,
|
||||
StorageSerializers,
|
||||
TransitionPresets,
|
||||
assert,
|
||||
computedAsync as asyncComputed,
|
||||
refAutoReset as autoResetRef,
|
||||
breakpointsAntDesign,
|
||||
breakpointsBootstrapV5,
|
||||
breakpointsElement,
|
||||
breakpointsMasterCss,
|
||||
breakpointsPrimeFlex,
|
||||
breakpointsQuasar,
|
||||
breakpointsSematic,
|
||||
breakpointsTailwind,
|
||||
breakpointsVuetify,
|
||||
breakpointsVuetifyV2,
|
||||
breakpointsVuetifyV3,
|
||||
bypassFilter,
|
||||
camelize,
|
||||
clamp,
|
||||
cloneFnJSON,
|
||||
computedAsync,
|
||||
computedEager,
|
||||
computedInject,
|
||||
computedWithControl,
|
||||
containsProp,
|
||||
computedWithControl as controlledComputed,
|
||||
controlledRef,
|
||||
createEventHook,
|
||||
createFetch,
|
||||
createFilterWrapper,
|
||||
createGlobalState,
|
||||
createInjectionState,
|
||||
reactify as createReactiveFn,
|
||||
createRef,
|
||||
createReusableTemplate,
|
||||
createSharedComposable,
|
||||
createSingletonPromise,
|
||||
createTemplatePromise,
|
||||
createUnrefFn,
|
||||
customStorageEventName,
|
||||
debounceFilter,
|
||||
refDebounced as debouncedRef,
|
||||
watchDebounced as debouncedWatch,
|
||||
defaultDocument,
|
||||
defaultLocation,
|
||||
defaultNavigator,
|
||||
defaultWindow,
|
||||
computedEager as eagerComputed,
|
||||
executeTransition,
|
||||
extendRef,
|
||||
formatDate,
|
||||
formatTimeAgo,
|
||||
get,
|
||||
getLifeCycleTarget,
|
||||
getSSRHandler,
|
||||
hasOwn,
|
||||
hyphenate,
|
||||
identity,
|
||||
watchIgnorable as ignorableWatch,
|
||||
increaseWithUnit,
|
||||
injectLocal,
|
||||
invoke,
|
||||
isClient,
|
||||
isDef,
|
||||
isDefined,
|
||||
isIOS,
|
||||
isObject,
|
||||
isWorker,
|
||||
makeDestructurable,
|
||||
mapGamepadToXbox360Controller,
|
||||
noop,
|
||||
normalizeDate,
|
||||
notNullish,
|
||||
now,
|
||||
objectEntries,
|
||||
objectOmit,
|
||||
objectPick,
|
||||
onClickOutside,
|
||||
onElementRemoval,
|
||||
onKeyDown,
|
||||
onKeyPressed,
|
||||
onKeyStroke,
|
||||
onKeyUp,
|
||||
onLongPress,
|
||||
onStartTyping,
|
||||
pausableFilter,
|
||||
watchPausable as pausableWatch,
|
||||
promiseTimeout,
|
||||
provideLocal,
|
||||
provideSSRWidth,
|
||||
pxValue,
|
||||
rand,
|
||||
reactify,
|
||||
reactifyObject,
|
||||
reactiveComputed,
|
||||
reactiveOmit,
|
||||
reactivePick,
|
||||
refAutoReset,
|
||||
refDebounced,
|
||||
refDefault,
|
||||
refThrottled,
|
||||
refWithControl,
|
||||
resolveRef,
|
||||
resolveUnref,
|
||||
set,
|
||||
setSSRHandler,
|
||||
syncRef,
|
||||
syncRefs,
|
||||
templateRef,
|
||||
throttleFilter,
|
||||
refThrottled as throttledRef,
|
||||
watchThrottled as throttledWatch,
|
||||
timestamp,
|
||||
toArray,
|
||||
toReactive,
|
||||
toRef,
|
||||
toRefs,
|
||||
toValue,
|
||||
tryOnBeforeMount,
|
||||
tryOnBeforeUnmount,
|
||||
tryOnMounted,
|
||||
tryOnScopeDispose,
|
||||
tryOnUnmounted,
|
||||
unrefElement,
|
||||
until,
|
||||
useActiveElement,
|
||||
useAnimate,
|
||||
useArrayDifference,
|
||||
useArrayEvery,
|
||||
useArrayFilter,
|
||||
useArrayFind,
|
||||
useArrayFindIndex,
|
||||
useArrayFindLast,
|
||||
useArrayIncludes,
|
||||
useArrayJoin,
|
||||
useArrayMap,
|
||||
useArrayReduce,
|
||||
useArraySome,
|
||||
useArrayUnique,
|
||||
useAsyncQueue,
|
||||
useAsyncState,
|
||||
useBase64,
|
||||
useBattery,
|
||||
useBluetooth,
|
||||
useBreakpoints,
|
||||
useBroadcastChannel,
|
||||
useBrowserLocation,
|
||||
useCached,
|
||||
useClipboard,
|
||||
useClipboardItems,
|
||||
useCloned,
|
||||
useColorMode,
|
||||
useConfirmDialog,
|
||||
useCountdown,
|
||||
useCounter,
|
||||
useCssVar,
|
||||
useCurrentElement,
|
||||
useCycleList,
|
||||
useDark,
|
||||
useDateFormat,
|
||||
refDebounced as useDebounce,
|
||||
useDebounceFn,
|
||||
useDebouncedRefHistory,
|
||||
useDeviceMotion,
|
||||
useDeviceOrientation,
|
||||
useDevicePixelRatio,
|
||||
useDevicesList,
|
||||
useDisplayMedia,
|
||||
useDocumentVisibility,
|
||||
useDraggable,
|
||||
useDropZone,
|
||||
useElementBounding,
|
||||
useElementByPoint,
|
||||
useElementHover,
|
||||
useElementSize,
|
||||
useElementVisibility,
|
||||
useEventBus,
|
||||
useEventListener,
|
||||
useEventSource,
|
||||
useEyeDropper,
|
||||
useFavicon,
|
||||
useFetch,
|
||||
useFileDialog,
|
||||
useFileSystemAccess,
|
||||
useFocus,
|
||||
useFocusWithin,
|
||||
useFps,
|
||||
useFullscreen,
|
||||
useGamepad,
|
||||
useGeolocation,
|
||||
useIdle,
|
||||
useImage,
|
||||
useInfiniteScroll,
|
||||
useIntersectionObserver,
|
||||
useInterval,
|
||||
useIntervalFn,
|
||||
useKeyModifier,
|
||||
useLastChanged,
|
||||
useLocalStorage,
|
||||
useMagicKeys,
|
||||
useManualRefHistory,
|
||||
useMediaControls,
|
||||
useMediaQuery,
|
||||
useMemoize,
|
||||
useMemory,
|
||||
useMounted,
|
||||
useMouse,
|
||||
useMouseInElement,
|
||||
useMousePressed,
|
||||
useMutationObserver,
|
||||
useNavigatorLanguage,
|
||||
useNetwork,
|
||||
useNow,
|
||||
useObjectUrl,
|
||||
useOffsetPagination,
|
||||
useOnline,
|
||||
usePageLeave,
|
||||
useParallax,
|
||||
useParentElement,
|
||||
usePerformanceObserver,
|
||||
usePermission,
|
||||
usePointer,
|
||||
usePointerLock,
|
||||
usePointerSwipe,
|
||||
usePreferredColorScheme,
|
||||
usePreferredContrast,
|
||||
usePreferredDark,
|
||||
usePreferredLanguages,
|
||||
usePreferredReducedMotion,
|
||||
usePreferredReducedTransparency,
|
||||
usePrevious,
|
||||
useRafFn,
|
||||
useRefHistory,
|
||||
useResizeObserver,
|
||||
useSSRWidth,
|
||||
useScreenOrientation,
|
||||
useScreenSafeArea,
|
||||
useScriptTag,
|
||||
useScroll,
|
||||
useScrollLock,
|
||||
useSessionStorage,
|
||||
useShare,
|
||||
useSorted,
|
||||
useSpeechRecognition,
|
||||
useSpeechSynthesis,
|
||||
useStepper,
|
||||
useStorage,
|
||||
useStorageAsync,
|
||||
useStyleTag,
|
||||
useSupported,
|
||||
useSwipe,
|
||||
useTemplateRefsList,
|
||||
useTextDirection,
|
||||
useTextSelection,
|
||||
useTextareaAutosize,
|
||||
refThrottled as useThrottle,
|
||||
useThrottleFn,
|
||||
useThrottledRefHistory,
|
||||
useTimeAgo,
|
||||
useTimeout,
|
||||
useTimeoutFn,
|
||||
useTimeoutPoll,
|
||||
useTimestamp,
|
||||
useTitle,
|
||||
useToNumber,
|
||||
useToString,
|
||||
useToggle,
|
||||
useTransition,
|
||||
useUrlSearchParams,
|
||||
useUserMedia,
|
||||
useVModel,
|
||||
useVModels,
|
||||
useVibrate,
|
||||
useVirtualList,
|
||||
useWakeLock,
|
||||
useWebNotification,
|
||||
useWebSocket,
|
||||
useWebWorker,
|
||||
useWebWorkerFn,
|
||||
useWindowFocus,
|
||||
useWindowScroll,
|
||||
useWindowSize,
|
||||
watchArray,
|
||||
watchAtMost,
|
||||
watchDebounced,
|
||||
watchDeep,
|
||||
watchIgnorable,
|
||||
watchImmediate,
|
||||
watchOnce,
|
||||
watchPausable,
|
||||
watchThrottled,
|
||||
watchTriggerable,
|
||||
watchWithFilter,
|
||||
whenever
|
||||
};
|
||||
//# sourceMappingURL=vitepress___@vueuse_core.js.map
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
-1334
File diff suppressed because it is too large
Load Diff
-7
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-1813
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Vendored
-347
@@ -1,347 +0,0 @@
|
||||
import {
|
||||
BaseTransition,
|
||||
BaseTransitionPropsValidators,
|
||||
Comment,
|
||||
DeprecationTypes,
|
||||
EffectScope,
|
||||
ErrorCodes,
|
||||
ErrorTypeStrings,
|
||||
Fragment,
|
||||
KeepAlive,
|
||||
ReactiveEffect,
|
||||
Static,
|
||||
Suspense,
|
||||
Teleport,
|
||||
Text,
|
||||
TrackOpTypes,
|
||||
Transition,
|
||||
TransitionGroup,
|
||||
TriggerOpTypes,
|
||||
VueElement,
|
||||
assertNumber,
|
||||
callWithAsyncErrorHandling,
|
||||
callWithErrorHandling,
|
||||
camelize,
|
||||
capitalize,
|
||||
cloneVNode,
|
||||
compatUtils,
|
||||
compile,
|
||||
computed,
|
||||
createApp,
|
||||
createBaseVNode,
|
||||
createBlock,
|
||||
createCommentVNode,
|
||||
createElementBlock,
|
||||
createHydrationRenderer,
|
||||
createPropsRestProxy,
|
||||
createRenderer,
|
||||
createSSRApp,
|
||||
createSlots,
|
||||
createStaticVNode,
|
||||
createTextVNode,
|
||||
createVNode,
|
||||
customRef,
|
||||
defineAsyncComponent,
|
||||
defineComponent,
|
||||
defineCustomElement,
|
||||
defineEmits,
|
||||
defineExpose,
|
||||
defineModel,
|
||||
defineOptions,
|
||||
defineProps,
|
||||
defineSSRCustomElement,
|
||||
defineSlots,
|
||||
devtools,
|
||||
effect,
|
||||
effectScope,
|
||||
getCurrentInstance,
|
||||
getCurrentScope,
|
||||
getCurrentWatcher,
|
||||
getTransitionRawChildren,
|
||||
guardReactiveProps,
|
||||
h,
|
||||
handleError,
|
||||
hasInjectionContext,
|
||||
hydrate,
|
||||
hydrateOnIdle,
|
||||
hydrateOnInteraction,
|
||||
hydrateOnMediaQuery,
|
||||
hydrateOnVisible,
|
||||
initCustomFormatter,
|
||||
initDirectivesForSSR,
|
||||
inject,
|
||||
isMemoSame,
|
||||
isProxy,
|
||||
isReactive,
|
||||
isReadonly,
|
||||
isRef,
|
||||
isRuntimeOnly,
|
||||
isShallow,
|
||||
isVNode,
|
||||
markRaw,
|
||||
mergeDefaults,
|
||||
mergeModels,
|
||||
mergeProps,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
normalizeClass,
|
||||
normalizeProps,
|
||||
normalizeStyle,
|
||||
onActivated,
|
||||
onBeforeMount,
|
||||
onBeforeUnmount,
|
||||
onBeforeUpdate,
|
||||
onDeactivated,
|
||||
onErrorCaptured,
|
||||
onMounted,
|
||||
onRenderTracked,
|
||||
onRenderTriggered,
|
||||
onScopeDispose,
|
||||
onServerPrefetch,
|
||||
onUnmounted,
|
||||
onUpdated,
|
||||
onWatcherCleanup,
|
||||
openBlock,
|
||||
patchProp,
|
||||
popScopeId,
|
||||
provide,
|
||||
proxyRefs,
|
||||
pushScopeId,
|
||||
queuePostFlushCb,
|
||||
reactive,
|
||||
readonly,
|
||||
ref,
|
||||
registerRuntimeCompiler,
|
||||
render,
|
||||
renderList,
|
||||
renderSlot,
|
||||
resolveComponent,
|
||||
resolveDirective,
|
||||
resolveDynamicComponent,
|
||||
resolveFilter,
|
||||
resolveTransitionHooks,
|
||||
setBlockTracking,
|
||||
setDevtoolsHook,
|
||||
setTransitionHooks,
|
||||
shallowReactive,
|
||||
shallowReadonly,
|
||||
shallowRef,
|
||||
ssrContextKey,
|
||||
ssrUtils,
|
||||
stop,
|
||||
toDisplayString,
|
||||
toHandlerKey,
|
||||
toHandlers,
|
||||
toRaw,
|
||||
toRef,
|
||||
toRefs,
|
||||
toValue,
|
||||
transformVNodeArgs,
|
||||
triggerRef,
|
||||
unref,
|
||||
useAttrs,
|
||||
useCssModule,
|
||||
useCssVars,
|
||||
useHost,
|
||||
useId,
|
||||
useModel,
|
||||
useSSRContext,
|
||||
useShadowRoot,
|
||||
useSlots,
|
||||
useTemplateRef,
|
||||
useTransitionState,
|
||||
vModelCheckbox,
|
||||
vModelDynamic,
|
||||
vModelRadio,
|
||||
vModelSelect,
|
||||
vModelText,
|
||||
vShow,
|
||||
version,
|
||||
warn,
|
||||
watch,
|
||||
watchEffect,
|
||||
watchPostEffect,
|
||||
watchSyncEffect,
|
||||
withAsyncContext,
|
||||
withCtx,
|
||||
withDefaults,
|
||||
withDirectives,
|
||||
withKeys,
|
||||
withMemo,
|
||||
withModifiers,
|
||||
withScopeId
|
||||
} from "./chunk-LE5NDSFD.js";
|
||||
export {
|
||||
BaseTransition,
|
||||
BaseTransitionPropsValidators,
|
||||
Comment,
|
||||
DeprecationTypes,
|
||||
EffectScope,
|
||||
ErrorCodes,
|
||||
ErrorTypeStrings,
|
||||
Fragment,
|
||||
KeepAlive,
|
||||
ReactiveEffect,
|
||||
Static,
|
||||
Suspense,
|
||||
Teleport,
|
||||
Text,
|
||||
TrackOpTypes,
|
||||
Transition,
|
||||
TransitionGroup,
|
||||
TriggerOpTypes,
|
||||
VueElement,
|
||||
assertNumber,
|
||||
callWithAsyncErrorHandling,
|
||||
callWithErrorHandling,
|
||||
camelize,
|
||||
capitalize,
|
||||
cloneVNode,
|
||||
compatUtils,
|
||||
compile,
|
||||
computed,
|
||||
createApp,
|
||||
createBlock,
|
||||
createCommentVNode,
|
||||
createElementBlock,
|
||||
createBaseVNode as createElementVNode,
|
||||
createHydrationRenderer,
|
||||
createPropsRestProxy,
|
||||
createRenderer,
|
||||
createSSRApp,
|
||||
createSlots,
|
||||
createStaticVNode,
|
||||
createTextVNode,
|
||||
createVNode,
|
||||
customRef,
|
||||
defineAsyncComponent,
|
||||
defineComponent,
|
||||
defineCustomElement,
|
||||
defineEmits,
|
||||
defineExpose,
|
||||
defineModel,
|
||||
defineOptions,
|
||||
defineProps,
|
||||
defineSSRCustomElement,
|
||||
defineSlots,
|
||||
devtools,
|
||||
effect,
|
||||
effectScope,
|
||||
getCurrentInstance,
|
||||
getCurrentScope,
|
||||
getCurrentWatcher,
|
||||
getTransitionRawChildren,
|
||||
guardReactiveProps,
|
||||
h,
|
||||
handleError,
|
||||
hasInjectionContext,
|
||||
hydrate,
|
||||
hydrateOnIdle,
|
||||
hydrateOnInteraction,
|
||||
hydrateOnMediaQuery,
|
||||
hydrateOnVisible,
|
||||
initCustomFormatter,
|
||||
initDirectivesForSSR,
|
||||
inject,
|
||||
isMemoSame,
|
||||
isProxy,
|
||||
isReactive,
|
||||
isReadonly,
|
||||
isRef,
|
||||
isRuntimeOnly,
|
||||
isShallow,
|
||||
isVNode,
|
||||
markRaw,
|
||||
mergeDefaults,
|
||||
mergeModels,
|
||||
mergeProps,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
normalizeClass,
|
||||
normalizeProps,
|
||||
normalizeStyle,
|
||||
onActivated,
|
||||
onBeforeMount,
|
||||
onBeforeUnmount,
|
||||
onBeforeUpdate,
|
||||
onDeactivated,
|
||||
onErrorCaptured,
|
||||
onMounted,
|
||||
onRenderTracked,
|
||||
onRenderTriggered,
|
||||
onScopeDispose,
|
||||
onServerPrefetch,
|
||||
onUnmounted,
|
||||
onUpdated,
|
||||
onWatcherCleanup,
|
||||
openBlock,
|
||||
patchProp,
|
||||
popScopeId,
|
||||
provide,
|
||||
proxyRefs,
|
||||
pushScopeId,
|
||||
queuePostFlushCb,
|
||||
reactive,
|
||||
readonly,
|
||||
ref,
|
||||
registerRuntimeCompiler,
|
||||
render,
|
||||
renderList,
|
||||
renderSlot,
|
||||
resolveComponent,
|
||||
resolveDirective,
|
||||
resolveDynamicComponent,
|
||||
resolveFilter,
|
||||
resolveTransitionHooks,
|
||||
setBlockTracking,
|
||||
setDevtoolsHook,
|
||||
setTransitionHooks,
|
||||
shallowReactive,
|
||||
shallowReadonly,
|
||||
shallowRef,
|
||||
ssrContextKey,
|
||||
ssrUtils,
|
||||
stop,
|
||||
toDisplayString,
|
||||
toHandlerKey,
|
||||
toHandlers,
|
||||
toRaw,
|
||||
toRef,
|
||||
toRefs,
|
||||
toValue,
|
||||
transformVNodeArgs,
|
||||
triggerRef,
|
||||
unref,
|
||||
useAttrs,
|
||||
useCssModule,
|
||||
useCssVars,
|
||||
useHost,
|
||||
useId,
|
||||
useModel,
|
||||
useSSRContext,
|
||||
useShadowRoot,
|
||||
useSlots,
|
||||
useTemplateRef,
|
||||
useTransitionState,
|
||||
vModelCheckbox,
|
||||
vModelDynamic,
|
||||
vModelRadio,
|
||||
vModelSelect,
|
||||
vModelText,
|
||||
vShow,
|
||||
version,
|
||||
warn,
|
||||
watch,
|
||||
watchEffect,
|
||||
watchPostEffect,
|
||||
watchSyncEffect,
|
||||
withAsyncContext,
|
||||
withCtx,
|
||||
withDefaults,
|
||||
withDirectives,
|
||||
withKeys,
|
||||
withMemo,
|
||||
withModifiers,
|
||||
withScopeId
|
||||
};
|
||||
//# sourceMappingURL=vue.js.map
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
+208
-54
@@ -1,74 +1,228 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import type { DefaultTheme } from 'vitepress'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { nav, sidebar } from './navigation.mjs'
|
||||
import { DESCRIPTION, FOOTER_MESSAGE } from './i18n/theme-labels.mjs'
|
||||
import { completeDocsLanguages } from '../../scripts/docs-languages.mjs'
|
||||
|
||||
const SITE_URL = 'https://www.bentopdf.com'
|
||||
|
||||
const docsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const labelsDir = path.join(docsDir, '.vitepress', 'i18n')
|
||||
|
||||
const LANGUAGE_NAMES: Record<string, string> = {
|
||||
ar: 'العربية',
|
||||
be: 'Беларуская',
|
||||
ca: 'Català',
|
||||
da: 'Dansk',
|
||||
de: 'Deutsch',
|
||||
es: 'Español',
|
||||
fa: 'فارسی',
|
||||
fr: 'Français',
|
||||
id: 'Bahasa Indonesia',
|
||||
it: 'Italiano',
|
||||
ja: '日本語',
|
||||
ko: '한국어',
|
||||
nl: 'Nederlands',
|
||||
pl: 'Polski',
|
||||
pt: 'Português',
|
||||
ru: 'Русский',
|
||||
sk: 'Slovenčina',
|
||||
sv: 'Svenska',
|
||||
tr: 'Türkçe',
|
||||
uk: 'Українська',
|
||||
vi: 'Tiếng Việt',
|
||||
zh: '简体中文',
|
||||
'zh-TW': '繁體中文'
|
||||
}
|
||||
|
||||
const RTL_LANGUAGES = new Set(['ar', 'fa'])
|
||||
const ENGLISH_ONLY_DOCS = new Set(['licensing'])
|
||||
|
||||
const { enabled, incomplete } = completeDocsLanguages(
|
||||
docsDir,
|
||||
Object.keys(LANGUAGE_NAMES),
|
||||
process.env.DOCS_LANGUAGES
|
||||
)
|
||||
for (const { lang, missing, total } of incomplete) {
|
||||
console.warn(`[docs] Skipping "${lang}": ${missing} of ${total} pages are not translated yet`)
|
||||
}
|
||||
const excluded = Object.keys(LANGUAGE_NAMES)
|
||||
.filter((lang) => !enabled.includes(lang))
|
||||
.map((lang) => `${lang}/**`)
|
||||
|
||||
type Labels = Record<string, string>
|
||||
|
||||
function readLabels(lang: string): Labels {
|
||||
const file = path.join(labelsDir, `${lang}.json`)
|
||||
return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf-8')) : {}
|
||||
}
|
||||
|
||||
function localizedLink(link: string | undefined, lang: string) {
|
||||
return link && link.startsWith('/') ? `/${lang}${link}` : link
|
||||
}
|
||||
|
||||
type LocalizableItem = {
|
||||
text?: string
|
||||
link?: string
|
||||
items?: LocalizableItem[]
|
||||
}
|
||||
|
||||
function localizeItems<T extends LocalizableItem>(
|
||||
items: T[],
|
||||
lang: string,
|
||||
translate: (text: string) => string
|
||||
): T[] {
|
||||
return items.map((item) => ({
|
||||
...item,
|
||||
...(item.text ? { text: translate(item.text) } : {}),
|
||||
...(item.link ? { link: localizedLink(item.link, lang) } : {}),
|
||||
...(item.items ? { items: localizeItems(item.items, lang, translate) } : {})
|
||||
}))
|
||||
}
|
||||
|
||||
function localeThemeConfig(lang: string): DefaultTheme.Config {
|
||||
const labels = readLabels(lang)
|
||||
const translate = (text: string) => labels[text] || text
|
||||
return {
|
||||
nav: localizeItems(nav, lang, translate),
|
||||
sidebar: Object.fromEntries(
|
||||
Object.entries(sidebar).map(([base, items]) => [
|
||||
`/${lang}${base}`,
|
||||
localizeItems(items as DefaultTheme.SidebarItem[], lang, translate)
|
||||
])
|
||||
),
|
||||
footer: {
|
||||
message: translate(FOOTER_MESSAGE),
|
||||
copyright: 'Copyright © 2026 BentoPDF'
|
||||
},
|
||||
outline: { label: translate('On this page') },
|
||||
docFooter: {
|
||||
prev: translate('Previous page'),
|
||||
next: translate('Next page')
|
||||
},
|
||||
returnToTopLabel: translate('Return to top'),
|
||||
sidebarMenuLabel: translate('Menu'),
|
||||
darkModeSwitchLabel: translate('Appearance'),
|
||||
lightModeSwitchTitle: translate('Switch to light theme'),
|
||||
darkModeSwitchTitle: translate('Switch to dark theme'),
|
||||
skipToContentLabel: translate('Skip to content'),
|
||||
langMenuLabel: translate('Change language')
|
||||
}
|
||||
}
|
||||
|
||||
function localeSearch(lang: string) {
|
||||
const labels = readLabels(lang)
|
||||
const translate = (text: string) => labels[text] || text
|
||||
return {
|
||||
translations: {
|
||||
button: {
|
||||
buttonText: translate('Search'),
|
||||
buttonAriaLabel: translate('Search')
|
||||
},
|
||||
modal: {
|
||||
displayDetails: translate('Display detailed list'),
|
||||
resetButtonTitle: translate('Reset search'),
|
||||
backButtonTitle: translate('Close search'),
|
||||
noResultsText: translate('No results for'),
|
||||
footer: {
|
||||
selectText: translate('to select'),
|
||||
navigateText: translate('to navigate'),
|
||||
closeText: translate('to close')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const locales = Object.fromEntries(
|
||||
enabled.map((lang) => {
|
||||
const labels = readLabels(lang)
|
||||
return [
|
||||
lang,
|
||||
{
|
||||
label: LANGUAGE_NAMES[lang],
|
||||
lang,
|
||||
dir: RTL_LANGUAGES.has(lang) ? 'rtl' : 'ltr',
|
||||
link: `/${lang}/`,
|
||||
title: 'BentoPDF Docs',
|
||||
description: labels[DESCRIPTION] || DESCRIPTION,
|
||||
themeConfig: localeThemeConfig(lang)
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
function pageUrl(lang: string, pagePath: string) {
|
||||
const prefix = lang === 'en' ? '' : `/${lang}`
|
||||
const suffix = pagePath === 'index' ? '' : `/${pagePath.replace(/\/index$/, '')}`
|
||||
return `${SITE_URL}/docs${prefix}${suffix}`
|
||||
}
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: "BentoPDF Docs",
|
||||
description: "Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit",
|
||||
description: DESCRIPTION,
|
||||
base: '/docs/',
|
||||
cleanUrls: true,
|
||||
metaChunk: true,
|
||||
srcExclude: excluded,
|
||||
|
||||
locales: {
|
||||
root: { label: 'English', lang: 'en-US' },
|
||||
...locales
|
||||
},
|
||||
|
||||
transformPageData(pageData) {
|
||||
const relPath = pageData.relativePath.replace(/\.md$/, '')
|
||||
const pageLang = enabled.find((lang) => relPath.startsWith(`${lang}/`))
|
||||
const pagePath = pageLang ? relPath.slice(pageLang.length + 1) : relPath
|
||||
const englishOnly = ENGLISH_ONLY_DOCS.has(pagePath)
|
||||
const canonicalPath = englishOnly ? pagePath : relPath
|
||||
const slug = canonicalPath === 'index' ? '' : canonicalPath.replace(/\/index$/, '')
|
||||
const canonicalUrl = slug ? `${SITE_URL}/docs/${slug}` : `${SITE_URL}/docs`
|
||||
pageData.frontmatter.head ??= []
|
||||
pageData.frontmatter.head.push(['link', { rel: 'canonical', href: canonicalUrl }])
|
||||
if (enabled.length === 0 || englishOnly) return
|
||||
for (const lang of ['en', ...enabled]) {
|
||||
const source = lang === 'en' ? pagePath : `${lang}/${pagePath}`
|
||||
if (!fs.existsSync(path.join(docsDir, `${source}.md`))) continue
|
||||
pageData.frontmatter.head.push([
|
||||
'link',
|
||||
{ rel: 'alternate', hreflang: lang, href: pageUrl(lang, pagePath) }
|
||||
])
|
||||
}
|
||||
pageData.frontmatter.head.push([
|
||||
'link',
|
||||
{ rel: 'alternate', hreflang: 'x-default', href: pageUrl('en', pagePath) }
|
||||
])
|
||||
},
|
||||
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
logo: '/images/favicon.svg',
|
||||
logo: '/images/favicon-no-bg.svg',
|
||||
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Getting Started', link: '/getting-started' },
|
||||
{ text: 'Tools', link: '/tools/' },
|
||||
{ text: 'Self-Hosting', link: '/self-hosting/' },
|
||||
{ text: 'Contributing', link: '/contributing' },
|
||||
{ text: 'Commercial License', link: '/licensing' }
|
||||
],
|
||||
nav,
|
||||
|
||||
sidebar: {
|
||||
'/tools/': [
|
||||
{
|
||||
text: 'Tools Reference',
|
||||
items: [
|
||||
{ text: 'Overview', link: '/tools/' }
|
||||
]
|
||||
}
|
||||
],
|
||||
'/self-hosting/': [
|
||||
{
|
||||
text: 'Self-Hosting Guide',
|
||||
items: [
|
||||
{ text: 'Overview', link: '/self-hosting/' },
|
||||
{ text: 'Docker', link: '/self-hosting/docker' },
|
||||
{ text: 'Vercel', link: '/self-hosting/vercel' },
|
||||
{ text: 'Netlify', link: '/self-hosting/netlify' },
|
||||
{ text: 'Cloudflare Pages', link: '/self-hosting/cloudflare' },
|
||||
{ text: 'AWS S3 + CloudFront', link: '/self-hosting/aws' },
|
||||
{ text: 'Hostinger', link: '/self-hosting/hostinger' },
|
||||
{ text: 'Nginx', link: '/self-hosting/nginx' },
|
||||
{ text: 'Apache', link: '/self-hosting/apache' }
|
||||
]
|
||||
}
|
||||
],
|
||||
'/': [
|
||||
{
|
||||
text: 'Guide',
|
||||
items: [
|
||||
{ text: 'Getting Started', link: '/getting-started' },
|
||||
{ text: 'Tools Reference', link: '/tools/' },
|
||||
{ text: 'Self-Hosting', link: '/self-hosting/' },
|
||||
{ text: 'Contributing', link: '/contributing' },
|
||||
{ text: 'Commercial License', link: '/licensing' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
sidebar,
|
||||
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/bentopdf/bentopdf' },
|
||||
{ icon: 'github', link: 'https://github.com/alam00000/bentopdf' },
|
||||
{ icon: 'discord', link: 'https://discord.gg/Bgq3Ay3f2w' }
|
||||
],
|
||||
|
||||
footer: {
|
||||
message: 'Dual-licensed under AGPL-3.0 and Commercial License.',
|
||||
copyright: 'Copyright © 2025 BentoPDF'
|
||||
message: FOOTER_MESSAGE,
|
||||
copyright: 'Copyright © 2026 BentoPDF'
|
||||
},
|
||||
|
||||
search: {
|
||||
provider: 'local'
|
||||
provider: 'local',
|
||||
options: {
|
||||
locales: Object.fromEntries(
|
||||
enabled.map((lang) => [lang, localeSearch(lang)])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "إضافة مرفقات",
|
||||
"Add Blank Page": "إضافة صفحة فارغة",
|
||||
"Add Page Labels": "إضافة تسميات الصفحات",
|
||||
"Add Stamps": "إضافة أختام",
|
||||
"Add Watermark": "إضافة علامة مائية",
|
||||
"Adjust Colors": "ضبط الألوان",
|
||||
"Alternate & Mix Pages": "تبديل ومزج الصفحات",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "المظهر",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "لون الخلفية",
|
||||
"Bates Numbering": "ترقيم بيتس",
|
||||
"BMP to PDF": "BMP إلى PDF",
|
||||
"CBZ to PDF": "CBZ إلى PDF",
|
||||
"Change language": "تغيير اللغة",
|
||||
"Change Permissions": "تغيير الأذونات",
|
||||
"Change Text Color": "تغيير لون النص",
|
||||
"Close search": "إغلاق البحث",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "دمج في صفحة واحدة",
|
||||
"Commercial License": "الترخيص التجاري",
|
||||
"Compare PDFs": "مقارنة ملفات PDF",
|
||||
"Compress PDF": "ضغط PDF",
|
||||
"Contributing": "المساهمة",
|
||||
"Convert from PDF": "التحويل من PDF",
|
||||
"Convert to PDF": "التحويل إلى PDF",
|
||||
"Create PDF Form": "إنشاء نموذج PDF",
|
||||
"Crop PDF": "قص PDF",
|
||||
"CSV to PDF": "CSV إلى PDF",
|
||||
"Delete Pages": "حذف الصفحات",
|
||||
"Deskew PDF": "تصحيح انحراف PDF",
|
||||
"Digital Signature": "التوقيع الرقمي",
|
||||
"Display detailed list": "عرض القائمة التفصيلية",
|
||||
"Divide Pages": "تقسيم الصفحات",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "توثيق BentoPDF، مجموعة أدوات PDF المجانية ومفتوحة المصدر والمصممة للخصوصية",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "مرخّص بترخيصين: AGPL-3.0 والترخيص التجاري.",
|
||||
"Duplex Collate": "ترتيب المسح على الوجهين",
|
||||
"Edit & Annotate": "تعديل وتعليق",
|
||||
"Edit Attachments": "تعديل المرفقات",
|
||||
"Edit Bookmarks": "تعديل الإشارات المرجعية",
|
||||
"Edit Metadata": "تعديل البيانات الوصفية",
|
||||
"Email to PDF": "بريد إلكتروني إلى PDF",
|
||||
"EPUB to PDF": "EPUB إلى PDF",
|
||||
"Excel to PDF": "Excel إلى PDF",
|
||||
"Extract Attachments": "استخراج المرفقات",
|
||||
"Extract Images": "استخراج الصور",
|
||||
"Extract Pages": "استخراج الصفحات",
|
||||
"Extract Tables": "استخراج الجداول",
|
||||
"FB2 to PDF": "FB2 إلى PDF",
|
||||
"Fix Page Size": "توحيد حجم الصفحة",
|
||||
"Flatten PDF": "تسطيح PDF",
|
||||
"Font to Outline": "تحويل الخطوط إلى مخططات",
|
||||
"Getting Started": "البدء",
|
||||
"Guide": "الدليل",
|
||||
"Header & Footer": "رأس وتذييل",
|
||||
"HEIC to PDF": "HEIC إلى PDF",
|
||||
"Home": "الرئيسية",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "صور إلى PDF",
|
||||
"Invert Colors": "عكس الألوان",
|
||||
"JPG to PDF": "JPG إلى PDF",
|
||||
"JSON to PDF": "JSON إلى PDF",
|
||||
"Linearize PDF": "تحسين PDF للويب",
|
||||
"Markdown to PDF": "Markdown إلى PDF",
|
||||
"Menu": "القائمة",
|
||||
"Merge PDF": "دمج PDF",
|
||||
"MOBI to PDF": "MOBI إلى PDF",
|
||||
"N-Up PDF": "N-Up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "الصفحة التالية",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "لا توجد نتائج لـ",
|
||||
"OCR PDF": "التعرف الضوئي على PDF",
|
||||
"ODG to PDF": "ODG إلى PDF",
|
||||
"ODP to PDF": "ODP إلى PDF",
|
||||
"ODS to PDF": "ODS إلى PDF",
|
||||
"ODT to PDF": "ODT إلى PDF",
|
||||
"On this page": "في هذه الصفحة",
|
||||
"Optimize & Repair": "التحسين والإصلاح",
|
||||
"Organize & Duplicate": "تكرار وتنظيم",
|
||||
"Organize & Manage": "التنظيم والإدارة",
|
||||
"Overview": "نظرة عامة",
|
||||
"Page Dimensions": "أبعاد الصفحة",
|
||||
"Page Numbers": "أرقام الصفحات",
|
||||
"PDF Booklet": "كتيّب PDF",
|
||||
"PDF Editor": "محرر PDF",
|
||||
"PDF Form Filler": "ملء نماذج PDF",
|
||||
"PDF Multi Tool": "أداة PDF المتعددة",
|
||||
"PDF OCG": "طبقات PDF OCG",
|
||||
"PDF to BMP": "PDF إلى BMP",
|
||||
"PDF to CBZ": "PDF إلى CBZ",
|
||||
"PDF to CSV": "PDF إلى CSV",
|
||||
"PDF to Excel": "PDF إلى Excel",
|
||||
"PDF to Greyscale": "PDF إلى تدرج الرمادي",
|
||||
"PDF to JPG": "PDF إلى JPG",
|
||||
"PDF to JSON": "PDF إلى JSON",
|
||||
"PDF to Markdown": "PDF إلى Markdown",
|
||||
"PDF to PDF/A": "PDF إلى PDF/A",
|
||||
"PDF to PNG": "PDF إلى PNG",
|
||||
"PDF to SVG": "PDF إلى SVG",
|
||||
"PDF to Text": "PDF إلى نص",
|
||||
"PDF to TIFF": "PDF إلى TIFF",
|
||||
"PDF to WebP": "PDF إلى WebP",
|
||||
"PDF to Word": "PDF إلى Word",
|
||||
"PDF Workflow Builder": "منشئ سير عمل PDF",
|
||||
"PDFs to ZIP": "PDF إلى ZIP",
|
||||
"PNG to PDF": "PNG إلى PDF",
|
||||
"Posterize PDF": "تقسيم PDF إلى ملصقات",
|
||||
"PowerPoint to PDF": "PowerPoint إلى PDF",
|
||||
"Prepare PDF for AI": "تحضير PDF للذكاء الاصطناعي",
|
||||
"Previous page": "الصفحة السابقة",
|
||||
"Protect PDF": "حماية PDF",
|
||||
"PSD to PDF": "PSD إلى PDF",
|
||||
"PUB to PDF": "PUB إلى PDF",
|
||||
"Rasterize PDF": "تحويل PDF إلى صور نقطية",
|
||||
"Remove Annotations": "إزالة التعليقات",
|
||||
"Remove Blank Pages": "إزالة الصفحات الفارغة",
|
||||
"Remove Metadata": "إزالة البيانات الوصفية",
|
||||
"Remove Restrictions": "إزالة القيود",
|
||||
"Repair PDF": "إصلاح PDF",
|
||||
"Reset search": "إعادة تعيين البحث",
|
||||
"Return to top": "العودة إلى الأعلى",
|
||||
"Reverse Pages": "عكس الصفحات",
|
||||
"Rotate by Custom Degrees": "تدوير بزاوية مخصصة",
|
||||
"Rotate PDF": "تدوير PDF",
|
||||
"RTF to PDF": "RTF إلى PDF",
|
||||
"Sanitize PDF": "تنظيف PDF",
|
||||
"Scanner Effect": "تأثير الماسح الضوئي",
|
||||
"Search": "بحث",
|
||||
"Secure PDF": "تأمين PDF",
|
||||
"Self-Hosting": "الاستضافة الذاتية",
|
||||
"Self-Hosting Guide": "دليل الاستضافة الذاتية",
|
||||
"Sign PDF": "توقيع PDF",
|
||||
"Skip to content": "انتقل إلى المحتوى",
|
||||
"Split PDF": "تقسيم PDF",
|
||||
"SVG to PDF": "SVG إلى PDF",
|
||||
"Switch to dark theme": "التبديل إلى المظهر الداكن",
|
||||
"Switch to light theme": "التبديل إلى المظهر الفاتح",
|
||||
"Table of Contents": "جدول المحتويات",
|
||||
"Text to PDF": "نص إلى PDF",
|
||||
"TIFF to PDF": "TIFF إلى PDF",
|
||||
"to close": "للإغلاق",
|
||||
"to navigate": "للتنقل",
|
||||
"to select": "للاختيار",
|
||||
"Tools": "الأدوات",
|
||||
"Tools Reference": "مرجع الأدوات",
|
||||
"Unlock PDF": "إلغاء قفل PDF",
|
||||
"Validate Signature": "التحقق من توقيع PDF",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "عرض البيانات الوصفية",
|
||||
"VSD to PDF": "VSD إلى PDF",
|
||||
"WebP to PDF": "WebP إلى PDF",
|
||||
"Word to PDF": "Word إلى PDF",
|
||||
"WPD to PDF": "WPD إلى PDF",
|
||||
"WPS to PDF": "WPS إلى PDF",
|
||||
"XML to PDF": "XML إلى PDF",
|
||||
"XPS to PDF": "XPS إلى PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Дадаць далучэнні",
|
||||
"Add Blank Page": "Дадаць пустую старонку",
|
||||
"Add Page Labels": "Дадаць пазнакі старонак",
|
||||
"Add Stamps": "Дадаць штампы",
|
||||
"Add Watermark": "Дадаць вадзяны знак",
|
||||
"Adjust Colors": "Наладзіць колеры",
|
||||
"Alternate & Mix Pages": "Чаргаваць і змяшаць старонкі",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Знешні выгляд",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Колер фону",
|
||||
"Bates Numbering": "Нумарацыя Бейтса",
|
||||
"BMP to PDF": "BMP у PDF",
|
||||
"CBZ to PDF": "CBZ у PDF",
|
||||
"Change language": "Змяніць мову",
|
||||
"Change Permissions": "Змяніць дазволы",
|
||||
"Change Text Color": "Змяніць колер тэксту",
|
||||
"Close search": "Закрыць пошук",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Аб'яднаць у адну старонку",
|
||||
"Commercial License": "Камерцыйная ліцэнзія",
|
||||
"Compare PDFs": "Параўнаць PDF",
|
||||
"Compress PDF": "Сціснуць PDF",
|
||||
"Contributing": "Удзел у распрацоўцы",
|
||||
"Convert from PDF": "Канвертацыя з PDF",
|
||||
"Convert to PDF": "Канвертацыя ў PDF",
|
||||
"Create PDF Form": "Стварыць форму PDF",
|
||||
"Crop PDF": "Абрэзаць PDF",
|
||||
"CSV to PDF": "CSV у PDF",
|
||||
"Delete Pages": "Выдаліць старонкі",
|
||||
"Deskew PDF": "Выпрастаць PDF",
|
||||
"Digital Signature": "Лічбавы подпіс",
|
||||
"Display detailed list": "Паказаць падрабязны спіс",
|
||||
"Divide Pages": "Падзяліць старонкі",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Дакументацыя BentoPDF: бясплатны набор інструментаў PDF з адкрытым кодам і пашанай да прыватнасці",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Двайная ліцэнзія: AGPL-3.0 і камерцыйная ліцэнзія.",
|
||||
"Duplex Collate": "Сартаванне двухбаковага сканавання",
|
||||
"Edit & Annotate": "Рэдагаванне і анатацыі",
|
||||
"Edit Attachments": "Рэдагаваць далучэнні",
|
||||
"Edit Bookmarks": "Рэдагаваць закладкі",
|
||||
"Edit Metadata": "Рэдагаваць метаданыя",
|
||||
"Email to PDF": "Email у PDF",
|
||||
"EPUB to PDF": "EPUB у PDF",
|
||||
"Excel to PDF": "Excel у PDF",
|
||||
"Extract Attachments": "Выняць далучэнні",
|
||||
"Extract Images": "Выняць відарысы",
|
||||
"Extract Pages": "Выняць старонкі",
|
||||
"Extract Tables": "Выняць табліцы",
|
||||
"FB2 to PDF": "FB2 у PDF",
|
||||
"Fix Page Size": "Уніфікаваць памер старонак",
|
||||
"Flatten PDF": "Звесці PDF",
|
||||
"Font to Outline": "Шрыфт у контуры",
|
||||
"Getting Started": "Пачатак працы",
|
||||
"Guide": "Кіраўніцтва",
|
||||
"Header & Footer": "Верхні і ніжні калантытул",
|
||||
"HEIC to PDF": "HEIC у PDF",
|
||||
"Home": "Галоўная",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Відарысы ў PDF",
|
||||
"Invert Colors": "Інвертаваць колеры",
|
||||
"JPG to PDF": "JPG у PDF",
|
||||
"JSON to PDF": "JSON у PDF",
|
||||
"Linearize PDF": "Зрабіць лінейны PDF",
|
||||
"Markdown to PDF": "Markdown у PDF",
|
||||
"Menu": "Меню",
|
||||
"Merge PDF": "Аб'яднаць PDF",
|
||||
"MOBI to PDF": "MOBI у PDF",
|
||||
"N-Up PDF": "N-Up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Наступная старонка",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Няма вынікаў для",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODG у PDF",
|
||||
"ODP to PDF": "ODP у PDF",
|
||||
"ODS to PDF": "ODS у PDF",
|
||||
"ODT to PDF": "ODT у PDF",
|
||||
"On this page": "На гэтай старонцы",
|
||||
"Optimize & Repair": "Аптымізацыя і аднаўленне",
|
||||
"Organize & Duplicate": "Арганізацыя і дубліраванне",
|
||||
"Organize & Manage": "Арганізацыя і кіраванне",
|
||||
"Overview": "Агляд",
|
||||
"Page Dimensions": "Памеры старонкі",
|
||||
"Page Numbers": "Нумары старонак",
|
||||
"PDF Booklet": "Буклет PDF",
|
||||
"PDF Editor": "Рэдактар PDF",
|
||||
"PDF Form Filler": "Запоўніць форму PDF",
|
||||
"PDF Multi Tool": "Мультыінструмент PDF",
|
||||
"PDF OCG": "PDF OCG",
|
||||
"PDF to BMP": "PDF у BMP",
|
||||
"PDF to CBZ": "PDF у CBZ",
|
||||
"PDF to CSV": "PDF у CSV",
|
||||
"PDF to Excel": "PDF у Excel",
|
||||
"PDF to Greyscale": "PDF у градацыі шэрага",
|
||||
"PDF to JPG": "PDF у JPG",
|
||||
"PDF to JSON": "PDF у JSON",
|
||||
"PDF to Markdown": "PDF у Markdown",
|
||||
"PDF to PDF/A": "PDF у PDF/A",
|
||||
"PDF to PNG": "PDF у PNG",
|
||||
"PDF to SVG": "PDF у SVG",
|
||||
"PDF to Text": "PDF у тэкст",
|
||||
"PDF to TIFF": "PDF у TIFF",
|
||||
"PDF to WebP": "PDF у WebP",
|
||||
"PDF to Word": "PDF у Word",
|
||||
"PDF Workflow Builder": "Канструктар рабочага працэсу PDF",
|
||||
"PDFs to ZIP": "PDF у ZIP",
|
||||
"PNG to PDF": "PNG у PDF",
|
||||
"Posterize PDF": "Пераўтварыць у постэр",
|
||||
"PowerPoint to PDF": "PowerPoint у PDF",
|
||||
"Prepare PDF for AI": "Падрыхтаваць PDF для ШI",
|
||||
"Previous page": "Папярэдняя старонка",
|
||||
"Protect PDF": "Абараніць PDF",
|
||||
"PSD to PDF": "PSD у PDF",
|
||||
"PUB to PDF": "PUB у PDF",
|
||||
"Rasterize PDF": "Растарызаваць PDF",
|
||||
"Remove Annotations": "Выдаліць анатацыі",
|
||||
"Remove Blank Pages": "Выдаліць пустыя старонкі",
|
||||
"Remove Metadata": "Выдаліць метаданыя",
|
||||
"Remove Restrictions": "Выдаліць абмежаванні",
|
||||
"Repair PDF": "Аднавіць PDF",
|
||||
"Reset search": "Скінуць пошук",
|
||||
"Return to top": "Вярнуцца ўгару",
|
||||
"Reverse Pages": "Адваротны парадак старонак",
|
||||
"Rotate by Custom Degrees": "Павярнуць на зададзены вугал",
|
||||
"Rotate PDF": "Павярнуць PDF",
|
||||
"RTF to PDF": "RTF у PDF",
|
||||
"Sanitize PDF": "Ачысціць PDF",
|
||||
"Scanner Effect": "Эфект сканера",
|
||||
"Search": "Пошук",
|
||||
"Secure PDF": "Бяспека PDF",
|
||||
"Self-Hosting": "Уласны хостынг",
|
||||
"Self-Hosting Guide": "Кіраўніцтва па ўласным хостынгу",
|
||||
"Sign PDF": "Падпісаць PDF",
|
||||
"Skip to content": "Перайсці да зместу",
|
||||
"Split PDF": "Падзяліць PDF",
|
||||
"SVG to PDF": "SVG у PDF",
|
||||
"Switch to dark theme": "Уключыць цёмную тэму",
|
||||
"Switch to light theme": "Уключыць светлую тэму",
|
||||
"Table of Contents": "Змест",
|
||||
"Text to PDF": "Тэкст у PDF",
|
||||
"TIFF to PDF": "TIFF у PDF",
|
||||
"to close": "каб закрыць",
|
||||
"to navigate": "для навігацыі",
|
||||
"to select": "каб выбраць",
|
||||
"Tools": "Інструменты",
|
||||
"Tools Reference": "Даведнік па інструментах",
|
||||
"Unlock PDF": "Разблакаваць PDF",
|
||||
"Validate Signature": "Праверыць подпіс",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Праглядзець метаданыя",
|
||||
"VSD to PDF": "VSD у PDF",
|
||||
"WebP to PDF": "WebP у PDF",
|
||||
"Word to PDF": "Word у PDF",
|
||||
"WPD to PDF": "WPD у PDF",
|
||||
"WPS to PDF": "WPS у PDF",
|
||||
"XML to PDF": "XML у PDF",
|
||||
"XPS to PDF": "XPS у PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Afegeix fitxers adjunts",
|
||||
"Add Blank Page": "Afegeix una pàgina en blanc",
|
||||
"Add Page Labels": "Afegeix etiquetes de pàgina",
|
||||
"Add Stamps": "Afegeix segells",
|
||||
"Add Watermark": "Afegeix una marca d'aigua",
|
||||
"Adjust Colors": "Ajusta els colors",
|
||||
"Alternate & Mix Pages": "Alterna i barreja pàgines",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Aparença",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Color de fons",
|
||||
"Bates Numbering": "Numeració Bates",
|
||||
"BMP to PDF": "BMP a PDF",
|
||||
"CBZ to PDF": "CBZ a PDF",
|
||||
"Change language": "Canvia l'idioma",
|
||||
"Change Permissions": "Canvia els permisos",
|
||||
"Change Text Color": "Canvia el color del text",
|
||||
"Close search": "Tanca la cerca",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Combina en una única pàgina",
|
||||
"Commercial License": "Llicència comercial",
|
||||
"Compare PDFs": "Compara PDF",
|
||||
"Compress PDF": "Comprimeix PDF",
|
||||
"Contributing": "Contribuir",
|
||||
"Convert from PDF": "Converteix des de PDF",
|
||||
"Convert to PDF": "Converteix a PDF",
|
||||
"Create PDF Form": "Crea un formulari PDF",
|
||||
"Crop PDF": "Retalla PDF",
|
||||
"CSV to PDF": "CSV a PDF",
|
||||
"Delete Pages": "Suprimeix pàgines",
|
||||
"Deskew PDF": "Redreça PDF",
|
||||
"Digital Signature": "Signatura digital de PDF",
|
||||
"Display detailed list": "Mostra la llista detallada",
|
||||
"Divide Pages": "Divideix pàgines",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Documentació de BentoPDF: el conjunt d'eines PDF gratuït, de codi obert i pensat per protegir la privadesa",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Doble llicència: AGPL-3.0 i llicència comercial.",
|
||||
"Duplex Collate": "Ordena les cares d'un escaneig a doble cara",
|
||||
"Edit & Annotate": "Edita i anota",
|
||||
"Edit Attachments": "Edita els fitxers adjunts",
|
||||
"Edit Bookmarks": "Edita els marcadors",
|
||||
"Edit Metadata": "Edita les metadades",
|
||||
"Email to PDF": "Correu electrònic a PDF",
|
||||
"EPUB to PDF": "EPUB a PDF",
|
||||
"Excel to PDF": "Excel a PDF",
|
||||
"Extract Attachments": "Extreu fitxers adjunts",
|
||||
"Extract Images": "Extreu imatges",
|
||||
"Extract Pages": "Extreu pàgines",
|
||||
"Extract Tables": "Extreu taules de PDF",
|
||||
"FB2 to PDF": "FB2 a PDF",
|
||||
"Fix Page Size": "Uniformitza la mida de pàgina",
|
||||
"Flatten PDF": "Aplana PDF",
|
||||
"Font to Outline": "Converteix tipus de lletra en contorns",
|
||||
"Getting Started": "Primers passos",
|
||||
"Guide": "Guia",
|
||||
"Header & Footer": "Capçalera i peu de pàgina",
|
||||
"HEIC to PDF": "HEIC a PDF",
|
||||
"Home": "Inici",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Imatges a PDF",
|
||||
"Invert Colors": "Inverteix els colors",
|
||||
"JPG to PDF": "JPG a PDF",
|
||||
"JSON to PDF": "JSON a PDF",
|
||||
"Linearize PDF": "Linealitza PDF",
|
||||
"Markdown to PDF": "Markdown a PDF",
|
||||
"Menu": "Menú",
|
||||
"Merge PDF": "Fusiona PDF",
|
||||
"MOBI to PDF": "MOBI a PDF",
|
||||
"N-Up PDF": "PDF amb diverses pàgines per full",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Pàgina següent",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Cap resultat per a",
|
||||
"OCR PDF": "Reconeixement OCR de PDF",
|
||||
"ODG to PDF": "ODG a PDF",
|
||||
"ODP to PDF": "ODP a PDF",
|
||||
"ODS to PDF": "ODS a PDF",
|
||||
"ODT to PDF": "ODT a PDF",
|
||||
"On this page": "En aquesta pàgina",
|
||||
"Optimize & Repair": "Optimitza i repara",
|
||||
"Organize & Duplicate": "Duplica i organitza",
|
||||
"Organize & Manage": "Organitza i gestiona",
|
||||
"Overview": "Visió general",
|
||||
"Page Dimensions": "Dimensions de pàgina",
|
||||
"Page Numbers": "Números de pàgina",
|
||||
"PDF Booklet": "Fullet PDF",
|
||||
"PDF Editor": "Editor de PDF",
|
||||
"PDF Form Filler": "Emplena formularis PDF",
|
||||
"PDF Multi Tool": "Eina PDF multifunció",
|
||||
"PDF OCG": "Capes OCG del PDF",
|
||||
"PDF to BMP": "PDF a BMP",
|
||||
"PDF to CBZ": "PDF a CBZ",
|
||||
"PDF to CSV": "PDF a CSV",
|
||||
"PDF to Excel": "PDF a Excel",
|
||||
"PDF to Greyscale": "PDF a escala de grisos",
|
||||
"PDF to JPG": "PDF a JPG",
|
||||
"PDF to JSON": "PDF a JSON",
|
||||
"PDF to Markdown": "PDF a Markdown",
|
||||
"PDF to PDF/A": "PDF a PDF/A",
|
||||
"PDF to PNG": "PDF a PNG",
|
||||
"PDF to SVG": "PDF a SVG",
|
||||
"PDF to Text": "PDF a text",
|
||||
"PDF to TIFF": "PDF a TIFF",
|
||||
"PDF to WebP": "PDF a WebP",
|
||||
"PDF to Word": "PDF a Word",
|
||||
"PDF Workflow Builder": "Creador de fluxos de treball PDF",
|
||||
"PDFs to ZIP": "PDF a ZIP",
|
||||
"PNG to PDF": "PNG a PDF",
|
||||
"Posterize PDF": "Divideix PDF en format pòster",
|
||||
"PowerPoint to PDF": "PowerPoint a PDF",
|
||||
"Prepare PDF for AI": "Prepara PDF per a IA",
|
||||
"Previous page": "Pàgina anterior",
|
||||
"Protect PDF": "Protegeix PDF",
|
||||
"PSD to PDF": "PSD a PDF",
|
||||
"PUB to PDF": "PUB a PDF",
|
||||
"Rasterize PDF": "Rasteritza PDF",
|
||||
"Remove Annotations": "Elimina les anotacions",
|
||||
"Remove Blank Pages": "Elimina les pàgines en blanc",
|
||||
"Remove Metadata": "Elimina les metadades",
|
||||
"Remove Restrictions": "Elimina les restriccions",
|
||||
"Repair PDF": "Repara PDF",
|
||||
"Reset search": "Restableix la cerca",
|
||||
"Return to top": "Torna a dalt",
|
||||
"Reverse Pages": "Inverteix l'ordre de les pàgines",
|
||||
"Rotate by Custom Degrees": "Gira amb un angle personalitzat",
|
||||
"Rotate PDF": "Gira PDF",
|
||||
"RTF to PDF": "RTF a PDF",
|
||||
"Sanitize PDF": "Neteja PDF",
|
||||
"Scanner Effect": "Efecte d'escàner",
|
||||
"Search": "Cerca",
|
||||
"Secure PDF": "Xifra PDF",
|
||||
"Self-Hosting": "Autoallotjament",
|
||||
"Self-Hosting Guide": "Guia d'autoallotjament",
|
||||
"Sign PDF": "Signa PDF",
|
||||
"Skip to content": "Vés al contingut",
|
||||
"Split PDF": "Divideix PDF",
|
||||
"SVG to PDF": "SVG a PDF",
|
||||
"Switch to dark theme": "Canvia al tema fosc",
|
||||
"Switch to light theme": "Canvia al tema clar",
|
||||
"Table of Contents": "Taula de continguts",
|
||||
"Text to PDF": "Text a PDF",
|
||||
"TIFF to PDF": "TIFF a PDF",
|
||||
"to close": "per tancar",
|
||||
"to navigate": "per navegar",
|
||||
"to select": "per seleccionar",
|
||||
"Tools": "Eines",
|
||||
"Tools Reference": "Referència d'eines",
|
||||
"Unlock PDF": "Desbloqueja PDF",
|
||||
"Validate Signature": "Valida la signatura d'un PDF",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Visualitza les metadades",
|
||||
"VSD to PDF": "VSD a PDF",
|
||||
"WebP to PDF": "WebP a PDF",
|
||||
"Word to PDF": "Word a PDF",
|
||||
"WPD to PDF": "WPD a PDF",
|
||||
"WPS to PDF": "WPS a PDF",
|
||||
"XML to PDF": "XML a PDF",
|
||||
"XPS to PDF": "XPS a PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Tilføj vedhæftninger",
|
||||
"Add Blank Page": "Tilføj tom side",
|
||||
"Add Page Labels": "Tilføj sideetiketter",
|
||||
"Add Stamps": "Tilføj stempler",
|
||||
"Add Watermark": "Tilføj vandmærke",
|
||||
"Adjust Colors": "Justér farver",
|
||||
"Alternate & Mix Pages": "Alternér og miks sider",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Udseende",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Baggrundsfarve",
|
||||
"Bates Numbering": "Bates-nummerering",
|
||||
"BMP to PDF": "BMP til PDF",
|
||||
"CBZ to PDF": "CBZ til PDF",
|
||||
"Change language": "Skift sprog",
|
||||
"Change Permissions": "Skift tilladelser",
|
||||
"Change Text Color": "Skift tekstfarve",
|
||||
"Close search": "Luk søgning",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Kombinér til én side",
|
||||
"Commercial License": "Kommerciel licens",
|
||||
"Compare PDFs": "Sammenlign PDF’er",
|
||||
"Compress PDF": "Komprimer PDF",
|
||||
"Contributing": "Bidrag",
|
||||
"Convert from PDF": "Konvertér fra PDF",
|
||||
"Convert to PDF": "Konvertér til PDF",
|
||||
"Create PDF Form": "Opret PDF-formular",
|
||||
"Crop PDF": "Beskær PDF",
|
||||
"CSV to PDF": "CSV til PDF",
|
||||
"Delete Pages": "Slet sider",
|
||||
"Deskew PDF": "Ret skæve sider",
|
||||
"Digital Signature": "Digital signatur",
|
||||
"Display detailed list": "Vis detaljeret liste",
|
||||
"Divide Pages": "Opdel sider",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Dokumentation for BentoPDF: Den gratis, open source PDF-værktøjskasse med privatliv i første række",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Dobbelt licenseret under AGPL-3.0 og Kommerciel licens.",
|
||||
"Duplex Collate": "Sortér dupleks-scanning",
|
||||
"Edit & Annotate": "Redigér og annotér",
|
||||
"Edit Attachments": "Redigér vedhæftninger",
|
||||
"Edit Bookmarks": "Redigér bogmærker",
|
||||
"Edit Metadata": "Redigér metadata",
|
||||
"Email to PDF": "E-mail til PDF",
|
||||
"EPUB to PDF": "EPUB til PDF",
|
||||
"Excel to PDF": "Excel til PDF",
|
||||
"Extract Attachments": "Udtræk vedhæftninger",
|
||||
"Extract Images": "Udtræk billeder",
|
||||
"Extract Pages": "Udtræk sider",
|
||||
"Extract Tables": "Udtræk tabeller",
|
||||
"FB2 to PDF": "FB2 til PDF",
|
||||
"Fix Page Size": "Ret sidestørrelse",
|
||||
"Flatten PDF": "Fladgør PDF",
|
||||
"Font to Outline": "Skrifttype til kontur",
|
||||
"Getting Started": "Kom i gang",
|
||||
"Guide": "Guide",
|
||||
"Header & Footer": "Sidehoved og sidefod",
|
||||
"HEIC to PDF": "HEIC til PDF",
|
||||
"Home": "Hjem",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Billeder til PDF",
|
||||
"Invert Colors": "Invertér farver",
|
||||
"JPG to PDF": "JPG til PDF",
|
||||
"JSON to PDF": "JSON til PDF",
|
||||
"Linearize PDF": "Lineariser PDF",
|
||||
"Markdown to PDF": "Markdown til PDF",
|
||||
"Menu": "Menu",
|
||||
"Merge PDF": "Flet PDF",
|
||||
"MOBI to PDF": "MOBI til PDF",
|
||||
"N-Up PDF": "N-Up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Næste side",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Ingen resultater for",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODG til PDF",
|
||||
"ODP to PDF": "ODP til PDF",
|
||||
"ODS to PDF": "ODS til PDF",
|
||||
"ODT to PDF": "ODT til PDF",
|
||||
"On this page": "På denne side",
|
||||
"Optimize & Repair": "Optimér og reparér",
|
||||
"Organize & Duplicate": "Organisér og dupliker",
|
||||
"Organize & Manage": "Organisér og administrér",
|
||||
"Overview": "Oversigt",
|
||||
"Page Dimensions": "Sidestørrelser",
|
||||
"Page Numbers": "Sidetal",
|
||||
"PDF Booklet": "PDF-hæfte",
|
||||
"PDF Editor": "PDF-editor",
|
||||
"PDF Form Filler": "PDF-formularudfylder",
|
||||
"PDF Multi Tool": "PDF-multiværktøj",
|
||||
"PDF OCG": "PDF OCG",
|
||||
"PDF to BMP": "PDF til BMP",
|
||||
"PDF to CBZ": "PDF til CBZ",
|
||||
"PDF to CSV": "PDF til CSV",
|
||||
"PDF to Excel": "PDF til Excel",
|
||||
"PDF to Greyscale": "PDF til gråtoner",
|
||||
"PDF to JPG": "PDF til JPG",
|
||||
"PDF to JSON": "PDF til JSON",
|
||||
"PDF to Markdown": "PDF til Markdown",
|
||||
"PDF to PDF/A": "PDF til PDF/A",
|
||||
"PDF to PNG": "PDF til PNG",
|
||||
"PDF to SVG": "PDF til SVG",
|
||||
"PDF to Text": "PDF til tekst",
|
||||
"PDF to TIFF": "PDF til TIFF",
|
||||
"PDF to WebP": "PDF til WebP",
|
||||
"PDF to Word": "PDF til Word",
|
||||
"PDF Workflow Builder": "PDF-workflowbygger",
|
||||
"PDFs to ZIP": "PDF’er til ZIP",
|
||||
"PNG to PDF": "PNG til PDF",
|
||||
"Posterize PDF": "Posterisér PDF",
|
||||
"PowerPoint to PDF": "PowerPoint til PDF",
|
||||
"Prepare PDF for AI": "Forbered PDF til AI",
|
||||
"Previous page": "Forrige side",
|
||||
"Protect PDF": "Beskyt PDF",
|
||||
"PSD to PDF": "PSD til PDF",
|
||||
"PUB to PDF": "PUB til PDF",
|
||||
"Rasterize PDF": "Rasterisér PDF",
|
||||
"Remove Annotations": "Fjern annotationer",
|
||||
"Remove Blank Pages": "Fjern tomme sider",
|
||||
"Remove Metadata": "Fjern metadata",
|
||||
"Remove Restrictions": "Fjern begrænsninger",
|
||||
"Repair PDF": "Reparér PDF",
|
||||
"Reset search": "Nulstil søgning",
|
||||
"Return to top": "Tilbage til toppen",
|
||||
"Reverse Pages": "Vend sider",
|
||||
"Rotate by Custom Degrees": "Roter med brugerdefineret vinkel",
|
||||
"Rotate PDF": "Roter PDF",
|
||||
"RTF to PDF": "RTF til PDF",
|
||||
"Sanitize PDF": "Rens PDF",
|
||||
"Scanner Effect": "Scannereffekt",
|
||||
"Search": "Søg",
|
||||
"Secure PDF": "Sikr PDF",
|
||||
"Self-Hosting": "Selvhosting",
|
||||
"Self-Hosting Guide": "Guide til selvhosting",
|
||||
"Sign PDF": "Underskriv PDF",
|
||||
"Skip to content": "Gå til indhold",
|
||||
"Split PDF": "Opdel PDF",
|
||||
"SVG to PDF": "SVG til PDF",
|
||||
"Switch to dark theme": "Skift til mørkt tema",
|
||||
"Switch to light theme": "Skift til lyst tema",
|
||||
"Table of Contents": "Indholdsfortegnelse",
|
||||
"Text to PDF": "Tekst til PDF",
|
||||
"TIFF to PDF": "TIFF til PDF",
|
||||
"to close": "for at lukke",
|
||||
"to navigate": "for at navigere",
|
||||
"to select": "for at vælge",
|
||||
"Tools": "Værktøjer",
|
||||
"Tools Reference": "Værktøjsreference",
|
||||
"Unlock PDF": "Lås PDF op",
|
||||
"Validate Signature": "Validér signatur",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Vis metadata",
|
||||
"VSD to PDF": "VSD til PDF",
|
||||
"WebP to PDF": "WebP til PDF",
|
||||
"Word to PDF": "Word til PDF",
|
||||
"WPD to PDF": "WPD til PDF",
|
||||
"WPS to PDF": "WPS til PDF",
|
||||
"XML to PDF": "XML til PDF",
|
||||
"XPS to PDF": "XPS til PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Anhänge hinzufügen",
|
||||
"Add Blank Page": "Leere Seite hinzufügen",
|
||||
"Add Page Labels": "Seitenbeschriftungen hinzufügen",
|
||||
"Add Stamps": "Stempel hinzufügen",
|
||||
"Add Watermark": "Wasserzeichen hinzufügen",
|
||||
"Adjust Colors": "Farben anpassen",
|
||||
"Alternate & Mix Pages": "Seiten abwechselnd mischen",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Darstellung",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Hintergrundfarbe",
|
||||
"Bates Numbering": "Bates-Nummerierung",
|
||||
"BMP to PDF": "BMP zu PDF",
|
||||
"CBZ to PDF": "CBZ zu PDF",
|
||||
"Change language": "Sprache ändern",
|
||||
"Change Permissions": "Berechtigungen ändern",
|
||||
"Change Text Color": "Textfarbe ändern",
|
||||
"Close search": "Suche schließen",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Zu einer Seite kombinieren",
|
||||
"Commercial License": "Kommerzielle Lizenz",
|
||||
"Compare PDFs": "PDFs vergleichen",
|
||||
"Compress PDF": "PDF komprimieren",
|
||||
"Contributing": "Mitwirken",
|
||||
"Convert from PDF": "Aus PDF konvertieren",
|
||||
"Convert to PDF": "In PDF konvertieren",
|
||||
"Create PDF Form": "PDF-Formular erstellen",
|
||||
"Crop PDF": "PDF zuschneiden",
|
||||
"CSV to PDF": "CSV zu PDF",
|
||||
"Delete Pages": "Seiten löschen",
|
||||
"Deskew PDF": "PDF entzerren",
|
||||
"Digital Signature": "Digitale PDF-Signatur",
|
||||
"Display detailed list": "Detaillierte Liste anzeigen",
|
||||
"Divide Pages": "Seiten teilen",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Dokumentation für BentoPDF: das kostenlose Open-Source-PDF-Toolkit mit Fokus auf Privatsphäre",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Doppelt lizenziert unter AGPL-3.0 und kommerzieller Lizenz.",
|
||||
"Duplex Collate": "Duplex-Zusammenführung",
|
||||
"Edit & Annotate": "Bearbeiten & Annotieren",
|
||||
"Edit Attachments": "Anhänge bearbeiten",
|
||||
"Edit Bookmarks": "Lesezeichen bearbeiten",
|
||||
"Edit Metadata": "Metadaten bearbeiten",
|
||||
"Email to PDF": "E-Mail zu PDF",
|
||||
"EPUB to PDF": "EPUB zu PDF",
|
||||
"Excel to PDF": "Excel zu PDF",
|
||||
"Extract Attachments": "Anhänge extrahieren",
|
||||
"Extract Images": "Bilder extrahieren",
|
||||
"Extract Pages": "Seiten extrahieren",
|
||||
"Extract Tables": "PDF-Tabellen extrahieren",
|
||||
"FB2 to PDF": "FB2 zu PDF",
|
||||
"Fix Page Size": "Seitengröße reparieren",
|
||||
"Flatten PDF": "PDF reduzieren",
|
||||
"Font to Outline": "Schriftart zu Umriss",
|
||||
"Getting Started": "Erste Schritte",
|
||||
"Guide": "Anleitung",
|
||||
"Header & Footer": "Kopf- & Fußzeile",
|
||||
"HEIC to PDF": "HEIC zu PDF",
|
||||
"Home": "Startseite",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Bilder zu PDF",
|
||||
"Invert Colors": "Farben invertieren",
|
||||
"JPG to PDF": "JPG zu PDF",
|
||||
"JSON to PDF": "JSON zu PDF",
|
||||
"Linearize PDF": "PDF linearisieren",
|
||||
"Markdown to PDF": "Markdown zu PDF",
|
||||
"Menu": "Menü",
|
||||
"Merge PDF": "PDF zusammenführen",
|
||||
"MOBI to PDF": "MOBI zu PDF",
|
||||
"N-Up PDF": "N-Up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Nächste Seite",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Keine Ergebnisse für",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODG zu PDF",
|
||||
"ODP to PDF": "ODP zu PDF",
|
||||
"ODS to PDF": "ODS zu PDF",
|
||||
"ODT to PDF": "ODT zu PDF",
|
||||
"On this page": "Auf dieser Seite",
|
||||
"Optimize & Repair": "Optimieren & Reparieren",
|
||||
"Organize & Duplicate": "Duplizieren & Organisieren",
|
||||
"Organize & Manage": "Organisieren & Verwalten",
|
||||
"Overview": "Übersicht",
|
||||
"Page Dimensions": "Seitenmaße",
|
||||
"Page Numbers": "Seitenzahlen",
|
||||
"PDF Booklet": "PDF-Broschüre",
|
||||
"PDF Editor": "PDF-Editor",
|
||||
"PDF Form Filler": "PDF-Formular ausfüllen",
|
||||
"PDF Multi Tool": "PDF-Multitool",
|
||||
"PDF OCG": "PDF-Ebenen (OCG)",
|
||||
"PDF to BMP": "PDF zu BMP",
|
||||
"PDF to CBZ": "PDF zu CBZ",
|
||||
"PDF to CSV": "PDF zu CSV",
|
||||
"PDF to Excel": "PDF zu Excel",
|
||||
"PDF to Greyscale": "PDF zu Graustufen",
|
||||
"PDF to JPG": "PDF zu JPG",
|
||||
"PDF to JSON": "PDF zu JSON",
|
||||
"PDF to Markdown": "PDF zu Markdown",
|
||||
"PDF to PDF/A": "PDF zu PDF/A",
|
||||
"PDF to PNG": "PDF zu PNG",
|
||||
"PDF to SVG": "PDF zu SVG",
|
||||
"PDF to Text": "PDF zu Text",
|
||||
"PDF to TIFF": "PDF zu TIFF",
|
||||
"PDF to WebP": "PDF zu WebP",
|
||||
"PDF to Word": "PDF zu Word",
|
||||
"PDF Workflow Builder": "PDF-Workflow-Builder",
|
||||
"PDFs to ZIP": "PDFs zu ZIP",
|
||||
"PNG to PDF": "PNG zu PDF",
|
||||
"Posterize PDF": "PDF posterisieren",
|
||||
"PowerPoint to PDF": "PowerPoint zu PDF",
|
||||
"Prepare PDF for AI": "PDF für KI vorbereiten",
|
||||
"Previous page": "Vorherige Seite",
|
||||
"Protect PDF": "PDF schützen",
|
||||
"PSD to PDF": "PSD zu PDF",
|
||||
"PUB to PDF": "PUB zu PDF",
|
||||
"Rasterize PDF": "PDF rastern",
|
||||
"Remove Annotations": "Annotationen entfernen",
|
||||
"Remove Blank Pages": "Leere Seiten entfernen",
|
||||
"Remove Metadata": "Metadaten entfernen",
|
||||
"Remove Restrictions": "Beschränkungen entfernen",
|
||||
"Repair PDF": "PDF reparieren",
|
||||
"Reset search": "Suche zurücksetzen",
|
||||
"Return to top": "Zurück nach oben",
|
||||
"Reverse Pages": "Seiten umkehren",
|
||||
"Rotate by Custom Degrees": "Um benutzerdefinierte Grad drehen",
|
||||
"Rotate PDF": "PDF drehen",
|
||||
"RTF to PDF": "RTF zu PDF",
|
||||
"Sanitize PDF": "PDF bereinigen",
|
||||
"Scanner Effect": "Scanner-Effekt",
|
||||
"Search": "Suchen",
|
||||
"Secure PDF": "PDF sichern",
|
||||
"Self-Hosting": "Selbst hosten",
|
||||
"Self-Hosting Guide": "Anleitung zum Selbst-Hosting",
|
||||
"Sign PDF": "PDF unterschreiben",
|
||||
"Skip to content": "Zum Inhalt springen",
|
||||
"Split PDF": "PDF teilen",
|
||||
"SVG to PDF": "SVG zu PDF",
|
||||
"Switch to dark theme": "Zum dunklen Design wechseln",
|
||||
"Switch to light theme": "Zum hellen Design wechseln",
|
||||
"Table of Contents": "Inhaltsverzeichnis",
|
||||
"Text to PDF": "Text zu PDF",
|
||||
"TIFF to PDF": "TIFF zu PDF",
|
||||
"to close": "zum Schließen",
|
||||
"to navigate": "zum Navigieren",
|
||||
"to select": "zum Auswählen",
|
||||
"Tools": "Werkzeuge",
|
||||
"Tools Reference": "Werkzeugübersicht",
|
||||
"Unlock PDF": "PDF entsperren",
|
||||
"Validate Signature": "PDF-Signatur überprüfen",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Metadaten anzeigen",
|
||||
"VSD to PDF": "VSD zu PDF",
|
||||
"WebP to PDF": "WebP zu PDF",
|
||||
"Word to PDF": "Word zu PDF",
|
||||
"WPD to PDF": "WPD zu PDF",
|
||||
"WPS to PDF": "WPS zu PDF",
|
||||
"XML to PDF": "XML zu PDF",
|
||||
"XPS to PDF": "XPS zu PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Agregar adjuntos",
|
||||
"Add Blank Page": "Agregar página en blanco",
|
||||
"Add Page Labels": "Agregar etiquetas de página",
|
||||
"Add Stamps": "Agregar sellos",
|
||||
"Add Watermark": "Agregar marca de agua",
|
||||
"Adjust Colors": "Ajustar colores",
|
||||
"Alternate & Mix Pages": "Alternar y mezclar páginas",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Apariencia",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Color de fondo",
|
||||
"Bates Numbering": "Numeración Bates",
|
||||
"BMP to PDF": "BMP a PDF",
|
||||
"CBZ to PDF": "CBZ a PDF",
|
||||
"Change language": "Cambiar idioma",
|
||||
"Change Permissions": "Cambiar permisos",
|
||||
"Change Text Color": "Cambiar color de texto",
|
||||
"Close search": "Cerrar búsqueda",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Combinar en una sola página",
|
||||
"Commercial License": "Licencia comercial",
|
||||
"Compare PDFs": "Comparar PDFs",
|
||||
"Compress PDF": "Comprimir PDF",
|
||||
"Contributing": "Contribuir",
|
||||
"Convert from PDF": "Convertir desde PDF",
|
||||
"Convert to PDF": "Convertir a PDF",
|
||||
"Create PDF Form": "Crear formulario PDF",
|
||||
"Crop PDF": "Recortar PDF",
|
||||
"CSV to PDF": "CSV a PDF",
|
||||
"Delete Pages": "Eliminar páginas",
|
||||
"Deskew PDF": "Enderezar PDF",
|
||||
"Digital Signature": "Firma Digital PDF",
|
||||
"Display detailed list": "Mostrar lista detallada",
|
||||
"Divide Pages": "Dividir páginas",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Documentación de BentoPDF: el kit de herramientas PDF gratuito, de código abierto y centrado en la privacidad",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Con doble licencia: AGPL-3.0 y licencia comercial.",
|
||||
"Duplex Collate": "Intercalar escaneo a doble cara",
|
||||
"Edit & Annotate": "Editar y anotar",
|
||||
"Edit Attachments": "Editar adjuntos",
|
||||
"Edit Bookmarks": "Editar marcadores",
|
||||
"Edit Metadata": "Editar metadatos",
|
||||
"Email to PDF": "Email a PDF",
|
||||
"EPUB to PDF": "EPUB a PDF",
|
||||
"Excel to PDF": "Excel a PDF",
|
||||
"Extract Attachments": "Extraer adjuntos",
|
||||
"Extract Images": "Extraer imágenes",
|
||||
"Extract Pages": "Extraer páginas",
|
||||
"Extract Tables": "Extraer Tablas de PDF",
|
||||
"FB2 to PDF": "FB2 a PDF",
|
||||
"Fix Page Size": "Ajustar tamaño de página",
|
||||
"Flatten PDF": "Aplanar PDF",
|
||||
"Font to Outline": "Fuente a contorno",
|
||||
"Getting Started": "Primeros pasos",
|
||||
"Guide": "Guía",
|
||||
"Header & Footer": "Encabezado y pie de página",
|
||||
"HEIC to PDF": "HEIC a PDF",
|
||||
"Home": "Inicio",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Imágenes a PDF",
|
||||
"Invert Colors": "Invertir colores",
|
||||
"JPG to PDF": "JPG a PDF",
|
||||
"JSON to PDF": "JSON a PDF",
|
||||
"Linearize PDF": "Linealizar PDF",
|
||||
"Markdown to PDF": "Markdown a PDF",
|
||||
"Menu": "Menú",
|
||||
"Merge PDF": "Fusionar PDF",
|
||||
"MOBI to PDF": "MOBI a PDF",
|
||||
"N-Up PDF": "N-Up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Página siguiente",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Sin resultados para",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODG a PDF",
|
||||
"ODP to PDF": "ODP a PDF",
|
||||
"ODS to PDF": "ODS a PDF",
|
||||
"ODT to PDF": "ODT a PDF",
|
||||
"On this page": "En esta página",
|
||||
"Optimize & Repair": "Optimizar y reparar",
|
||||
"Organize & Duplicate": "Duplicar y Organizar",
|
||||
"Organize & Manage": "Organizar y gestionar",
|
||||
"Overview": "Resumen",
|
||||
"Page Dimensions": "Dimensiones de página",
|
||||
"Page Numbers": "Números de página",
|
||||
"PDF Booklet": "Folleto PDF",
|
||||
"PDF Editor": "Editor PDF",
|
||||
"PDF Form Filler": "Rellenar formularios PDF",
|
||||
"PDF Multi Tool": "Multiherramienta PDF",
|
||||
"PDF OCG": "Capas PDF (OCG)",
|
||||
"PDF to BMP": "PDF a BMP",
|
||||
"PDF to CBZ": "PDF a CBZ",
|
||||
"PDF to CSV": "PDF a CSV",
|
||||
"PDF to Excel": "PDF a Excel",
|
||||
"PDF to Greyscale": "PDF a escala de grises",
|
||||
"PDF to JPG": "PDF a JPG",
|
||||
"PDF to JSON": "PDF a JSON",
|
||||
"PDF to Markdown": "PDF a Markdown",
|
||||
"PDF to PDF/A": "PDF a PDF/A",
|
||||
"PDF to PNG": "PDF a PNG",
|
||||
"PDF to SVG": "PDF a SVG",
|
||||
"PDF to Text": "PDF a texto",
|
||||
"PDF to TIFF": "PDF a TIFF",
|
||||
"PDF to WebP": "PDF a WebP",
|
||||
"PDF to Word": "PDF a Word",
|
||||
"PDF Workflow Builder": "Constructor de flujos de trabajo PDF",
|
||||
"PDFs to ZIP": "PDFs a ZIP",
|
||||
"PNG to PDF": "PNG a PDF",
|
||||
"Posterize PDF": "Posterizar PDF",
|
||||
"PowerPoint to PDF": "PowerPoint a PDF",
|
||||
"Prepare PDF for AI": "Preparar PDF para IA",
|
||||
"Previous page": "Página anterior",
|
||||
"Protect PDF": "Proteger PDF",
|
||||
"PSD to PDF": "PSD a PDF",
|
||||
"PUB to PDF": "PUB a PDF",
|
||||
"Rasterize PDF": "Rasterizar PDF",
|
||||
"Remove Annotations": "Eliminar anotaciones",
|
||||
"Remove Blank Pages": "Eliminar páginas en blanco",
|
||||
"Remove Metadata": "Eliminar metadatos",
|
||||
"Remove Restrictions": "Eliminar restricciones",
|
||||
"Repair PDF": "Reparar PDF",
|
||||
"Reset search": "Restablecer búsqueda",
|
||||
"Return to top": "Volver arriba",
|
||||
"Reverse Pages": "Invertir páginas",
|
||||
"Rotate by Custom Degrees": "Rotar por grados personalizados",
|
||||
"Rotate PDF": "Rotar PDF",
|
||||
"RTF to PDF": "RTF a PDF",
|
||||
"Sanitize PDF": "Sanear PDF",
|
||||
"Scanner Effect": "Efecto escáner",
|
||||
"Search": "Buscar",
|
||||
"Secure PDF": "Proteger PDF",
|
||||
"Self-Hosting": "Autoalojamiento",
|
||||
"Self-Hosting Guide": "Guía de autoalojamiento",
|
||||
"Sign PDF": "Firmar PDF",
|
||||
"Skip to content": "Saltar al contenido",
|
||||
"Split PDF": "Dividir PDF",
|
||||
"SVG to PDF": "SVG a PDF",
|
||||
"Switch to dark theme": "Cambiar al tema oscuro",
|
||||
"Switch to light theme": "Cambiar al tema claro",
|
||||
"Table of Contents": "Índice",
|
||||
"Text to PDF": "Texto a PDF",
|
||||
"TIFF to PDF": "TIFF a PDF",
|
||||
"to close": "para cerrar",
|
||||
"to navigate": "para navegar",
|
||||
"to select": "para seleccionar",
|
||||
"Tools": "Herramientas",
|
||||
"Tools Reference": "Referencia de herramientas",
|
||||
"Unlock PDF": "Desbloquear PDF",
|
||||
"Validate Signature": "Validar Firma PDF",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Ver metadatos",
|
||||
"VSD to PDF": "VSD a PDF",
|
||||
"WebP to PDF": "WebP a PDF",
|
||||
"Word to PDF": "Word a PDF",
|
||||
"WPD to PDF": "WPD a PDF",
|
||||
"WPS to PDF": "WPS a PDF",
|
||||
"XML to PDF": "XML a PDF",
|
||||
"XPS to PDF": "XPS a PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "افزودن پیوست",
|
||||
"Add Blank Page": "افزودن صفحهٔ خالی",
|
||||
"Add Page Labels": "افزودن برچسب صفحه",
|
||||
"Add Stamps": "افزودن مهر",
|
||||
"Add Watermark": "افزودن واترمارک",
|
||||
"Adjust Colors": "تنظیم رنگها",
|
||||
"Alternate & Mix Pages": "ادغام یکدرمیان صفحات",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "ظاهر",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "رنگ پسزمینه",
|
||||
"Bates Numbering": "شمارهگذاری Bates",
|
||||
"BMP to PDF": "BMP به PDF",
|
||||
"CBZ to PDF": "CBZ به PDF",
|
||||
"Change language": "تغییر زبان",
|
||||
"Change Permissions": "تغییر مجوزها",
|
||||
"Change Text Color": "تغییر رنگ متن",
|
||||
"Close search": "بستن جستجو",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "ترکیب در یک صفحه",
|
||||
"Commercial License": "مجوز تجاری",
|
||||
"Compare PDFs": "مقایسهٔ PDFها",
|
||||
"Compress PDF": "فشردهسازی PDF",
|
||||
"Contributing": "مشارکت",
|
||||
"Convert from PDF": "تبدیل از PDF",
|
||||
"Convert to PDF": "تبدیل به PDF",
|
||||
"Create PDF Form": "ساخت فرم PDF",
|
||||
"Crop PDF": "برش PDF",
|
||||
"CSV to PDF": "CSV به PDF",
|
||||
"Delete Pages": "حذف صفحات",
|
||||
"Deskew PDF": "صافکردن PDF",
|
||||
"Digital Signature": "امضای دیجیتال",
|
||||
"Display detailed list": "نمایش فهرست تفصیلی",
|
||||
"Divide Pages": "تقسیم صفحات",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "مستندات BentoPDF، مجموعه ابزار PDF رایگان، متنباز و حریمخصوصیمحور",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "دارای مجوز دوگانه: AGPL-3.0 و مجوز تجاری.",
|
||||
"Duplex Collate": "تلفیق دورو",
|
||||
"Edit & Annotate": "ویرایش و حاشیهنویسی",
|
||||
"Edit Attachments": "ویرایش پیوستها",
|
||||
"Edit Bookmarks": "ویرایش نشانکها",
|
||||
"Edit Metadata": "ویرایش فراداده",
|
||||
"Email to PDF": "ایمیل به PDF",
|
||||
"EPUB to PDF": "EPUB به PDF",
|
||||
"Excel to PDF": "Excel به PDF",
|
||||
"Extract Attachments": "استخراج پیوستها",
|
||||
"Extract Images": "استخراج تصاویر",
|
||||
"Extract Pages": "استخراج صفحات",
|
||||
"Extract Tables": "استخراج جدولهای PDF",
|
||||
"FB2 to PDF": "FB2 به PDF",
|
||||
"Fix Page Size": "اصلاح اندازهٔ صفحه",
|
||||
"Flatten PDF": "مسطحسازی PDF",
|
||||
"Font to Outline": "تبدیل قلم به خطوط برداری",
|
||||
"Getting Started": "شروع کار",
|
||||
"Guide": "راهنما",
|
||||
"Header & Footer": "سرصفحه و پاصفحه",
|
||||
"HEIC to PDF": "HEIC به PDF",
|
||||
"Home": "خانه",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "تصاویر به PDF",
|
||||
"Invert Colors": "وارونهسازی رنگها",
|
||||
"JPG to PDF": "JPG به PDF",
|
||||
"JSON to PDF": "JSON به PDF",
|
||||
"Linearize PDF": "خطیسازی PDF",
|
||||
"Markdown to PDF": "Markdown به PDF",
|
||||
"Menu": "منو",
|
||||
"Merge PDF": "ادغام PDF",
|
||||
"MOBI to PDF": "MOBI به PDF",
|
||||
"N-Up PDF": "N-Up کردن PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "صفحهٔ بعد",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "نتیجهای یافت نشد برای",
|
||||
"OCR PDF": "OCR روی PDF",
|
||||
"ODG to PDF": "ODG به PDF",
|
||||
"ODP to PDF": "ODP به PDF",
|
||||
"ODS to PDF": "ODS به PDF",
|
||||
"ODT to PDF": "ODT به PDF",
|
||||
"On this page": "در این صفحه",
|
||||
"Optimize & Repair": "بهینهسازی و ترمیم",
|
||||
"Organize & Duplicate": "تکثیر و سازماندهی",
|
||||
"Organize & Manage": "سازماندهی و مدیریت",
|
||||
"Overview": "نمای کلی",
|
||||
"Page Dimensions": "ابعاد صفحه",
|
||||
"Page Numbers": "شمارهگذاری صفحات",
|
||||
"PDF Booklet": "دفترچهٔ PDF",
|
||||
"PDF Editor": "ویرایشگر PDF",
|
||||
"PDF Form Filler": "پرکنندهٔ فرم PDF",
|
||||
"PDF Multi Tool": "ابزار چندکارهٔ PDF",
|
||||
"PDF OCG": "لایههای OCG در PDF",
|
||||
"PDF to BMP": "PDF به BMP",
|
||||
"PDF to CBZ": "PDF به CBZ",
|
||||
"PDF to CSV": "PDF به CSV",
|
||||
"PDF to Excel": "PDF به Excel",
|
||||
"PDF to Greyscale": "PDF به خاکستری",
|
||||
"PDF to JPG": "PDF به JPG",
|
||||
"PDF to JSON": "PDF به JSON",
|
||||
"PDF to Markdown": "PDF به Markdown",
|
||||
"PDF to PDF/A": "PDF به PDF/A",
|
||||
"PDF to PNG": "PDF به PNG",
|
||||
"PDF to SVG": "PDF به SVG",
|
||||
"PDF to Text": "PDF به متن",
|
||||
"PDF to TIFF": "PDF به TIFF",
|
||||
"PDF to WebP": "PDF به WebP",
|
||||
"PDF to Word": "PDF به Word",
|
||||
"PDF Workflow Builder": "سازندهٔ گردش کار PDF",
|
||||
"PDFs to ZIP": "PDFها به ZIP",
|
||||
"PNG to PDF": "PNG به PDF",
|
||||
"Posterize PDF": "پوستریکردن PDF",
|
||||
"PowerPoint to PDF": "PowerPoint به PDF",
|
||||
"Prepare PDF for AI": "آمادهسازی PDF برای هوش مصنوعی",
|
||||
"Previous page": "صفحهٔ قبل",
|
||||
"Protect PDF": "محافظت از PDF",
|
||||
"PSD to PDF": "PSD به PDF",
|
||||
"PUB to PDF": "PUB به PDF",
|
||||
"Rasterize PDF": "رستر کردن PDF",
|
||||
"Remove Annotations": "حذف حاشیهنویسیها",
|
||||
"Remove Blank Pages": "حذف صفحات خالی",
|
||||
"Remove Metadata": "حذف فراداده",
|
||||
"Remove Restrictions": "حذف محدودیتها",
|
||||
"Repair PDF": "ترمیم PDF",
|
||||
"Reset search": "بازنشانی جستجو",
|
||||
"Return to top": "بازگشت به بالا",
|
||||
"Reverse Pages": "وارونهسازی ترتیب صفحات",
|
||||
"Rotate by Custom Degrees": "چرخاندن با زاویهٔ دلخواه",
|
||||
"Rotate PDF": "چرخاندن PDF",
|
||||
"RTF to PDF": "RTF به PDF",
|
||||
"Sanitize PDF": "پاکسازی PDF",
|
||||
"Scanner Effect": "جلوهٔ اسکنر",
|
||||
"Search": "جستجو",
|
||||
"Secure PDF": "ایمنسازی PDF",
|
||||
"Self-Hosting": "میزبانی شخصی",
|
||||
"Self-Hosting Guide": "راهنمای میزبانی شخصی",
|
||||
"Sign PDF": "امضای PDF",
|
||||
"Skip to content": "رفتن به محتوا",
|
||||
"Split PDF": "تقسیم PDF",
|
||||
"SVG to PDF": "SVG به PDF",
|
||||
"Switch to dark theme": "تغییر به پوستهٔ تیره",
|
||||
"Switch to light theme": "تغییر به پوستهٔ روشن",
|
||||
"Table of Contents": "فهرست مطالب",
|
||||
"Text to PDF": "متن به PDF",
|
||||
"TIFF to PDF": "TIFF به PDF",
|
||||
"to close": "برای بستن",
|
||||
"to navigate": "برای پیمایش",
|
||||
"to select": "برای انتخاب",
|
||||
"Tools": "ابزارها",
|
||||
"Tools Reference": "مرجع ابزارها",
|
||||
"Unlock PDF": "باز کردن قفل PDF",
|
||||
"Validate Signature": "اعتبارسنجی امضا",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "مشاهدهٔ فراداده",
|
||||
"VSD to PDF": "VSD به PDF",
|
||||
"WebP to PDF": "WebP به PDF",
|
||||
"Word to PDF": "Word به PDF",
|
||||
"WPD to PDF": "WPD به PDF",
|
||||
"WPS to PDF": "WPS به PDF",
|
||||
"XML to PDF": "XML به PDF",
|
||||
"XPS to PDF": "XPS به PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Ajouter des pièces jointes",
|
||||
"Add Blank Page": "Ajouter une page vierge",
|
||||
"Add Page Labels": "Ajouter des étiquettes de page",
|
||||
"Add Stamps": "Ajouter des tampons",
|
||||
"Add Watermark": "Ajouter un filigrane",
|
||||
"Adjust Colors": "Ajuster les couleurs",
|
||||
"Alternate & Mix Pages": "Alterner et mélanger les pages",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Apparence",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Couleur de fond",
|
||||
"Bates Numbering": "Numérotation Bates",
|
||||
"BMP to PDF": "BMP vers PDF",
|
||||
"CBZ to PDF": "CBZ vers PDF",
|
||||
"Change language": "Changer de langue",
|
||||
"Change Permissions": "Modifier les autorisations",
|
||||
"Change Text Color": "Changer la couleur du texte",
|
||||
"Close search": "Fermer la recherche",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Combiner en une seule page",
|
||||
"Commercial License": "Licence commerciale",
|
||||
"Compare PDFs": "Comparer des PDF",
|
||||
"Compress PDF": "Compresser un PDF",
|
||||
"Contributing": "Contribuer",
|
||||
"Convert from PDF": "Convertir depuis PDF",
|
||||
"Convert to PDF": "Convertir vers PDF",
|
||||
"Create PDF Form": "Créer un formulaire PDF",
|
||||
"Crop PDF": "Rogner un PDF",
|
||||
"CSV to PDF": "CSV vers PDF",
|
||||
"Delete Pages": "Supprimer des pages",
|
||||
"Deskew PDF": "Redresser un PDF",
|
||||
"Digital Signature": "Signature numérique PDF",
|
||||
"Display detailed list": "Afficher la liste détaillée",
|
||||
"Divide Pages": "Diviser les pages",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Documentation de BentoPDF : la boîte à outils PDF gratuite, open source et axée sur la confidentialité",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Double licence : AGPL-3.0 et licence commerciale.",
|
||||
"Duplex Collate": "Assembler un scan recto/verso",
|
||||
"Edit & Annotate": "Modifier et annoter",
|
||||
"Edit Attachments": "Gérer les pièces jointes",
|
||||
"Edit Bookmarks": "Modifier les signets",
|
||||
"Edit Metadata": "Modifier les métadonnées",
|
||||
"Email to PDF": "Email vers PDF",
|
||||
"EPUB to PDF": "EPUB vers PDF",
|
||||
"Excel to PDF": "Excel vers PDF",
|
||||
"Extract Attachments": "Extraire les pièces jointes",
|
||||
"Extract Images": "Extraire les images",
|
||||
"Extract Pages": "Extraire des pages",
|
||||
"Extract Tables": "Extraire les tableaux PDF",
|
||||
"FB2 to PDF": "FB2 vers PDF",
|
||||
"Fix Page Size": "Uniformiser la taille des pages",
|
||||
"Flatten PDF": "Aplatir le PDF",
|
||||
"Font to Outline": "Polices en contours",
|
||||
"Getting Started": "Premiers pas",
|
||||
"Guide": "Guide",
|
||||
"Header & Footer": "En-tête et pied de page",
|
||||
"HEIC to PDF": "HEIC vers PDF",
|
||||
"Home": "Accueil",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Images vers PDF",
|
||||
"Invert Colors": "Inverser les couleurs",
|
||||
"JPG to PDF": "JPG vers PDF",
|
||||
"JSON to PDF": "JSON vers PDF",
|
||||
"Linearize PDF": "Optimiser pour le web",
|
||||
"Markdown to PDF": "Markdown vers PDF",
|
||||
"Menu": "Menu",
|
||||
"Merge PDF": "Fusionner des PDF",
|
||||
"MOBI to PDF": "MOBI vers PDF",
|
||||
"N-Up PDF": "PDF N-up",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Page suivante",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Aucun résultat pour",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODG vers PDF",
|
||||
"ODP to PDF": "ODP vers PDF",
|
||||
"ODS to PDF": "ODS vers PDF",
|
||||
"ODT to PDF": "ODT vers PDF",
|
||||
"On this page": "Sur cette page",
|
||||
"Optimize & Repair": "Optimiser et réparer",
|
||||
"Organize & Duplicate": "Dupliquer et organiser",
|
||||
"Organize & Manage": "Organiser et gérer",
|
||||
"Overview": "Vue d’ensemble",
|
||||
"Page Dimensions": "Dimensions des pages",
|
||||
"Page Numbers": "Numéros de page",
|
||||
"PDF Booklet": "Livret PDF",
|
||||
"PDF Editor": "Éditeur PDF",
|
||||
"PDF Form Filler": "Remplir un formulaire PDF",
|
||||
"PDF Multi Tool": "Outil PDF tout-en-un",
|
||||
"PDF OCG": "Calques PDF (OCG)",
|
||||
"PDF to BMP": "PDF vers BMP",
|
||||
"PDF to CBZ": "PDF vers CBZ",
|
||||
"PDF to CSV": "PDF vers CSV",
|
||||
"PDF to Excel": "PDF vers Excel",
|
||||
"PDF to Greyscale": "PDF en niveaux de gris",
|
||||
"PDF to JPG": "PDF vers JPG",
|
||||
"PDF to JSON": "PDF vers JSON",
|
||||
"PDF to Markdown": "PDF vers Markdown",
|
||||
"PDF to PDF/A": "PDF vers PDF/A",
|
||||
"PDF to PNG": "PDF vers PNG",
|
||||
"PDF to SVG": "PDF vers SVG",
|
||||
"PDF to Text": "PDF vers texte",
|
||||
"PDF to TIFF": "PDF vers TIFF",
|
||||
"PDF to WebP": "PDF vers WebP",
|
||||
"PDF to Word": "PDF vers Word",
|
||||
"PDF Workflow Builder": "Constructeur de workflow PDF",
|
||||
"PDFs to ZIP": "PDF vers ZIP",
|
||||
"PNG to PDF": "PNG vers PDF",
|
||||
"Posterize PDF": "Posteriser un PDF",
|
||||
"PowerPoint to PDF": "PowerPoint vers PDF",
|
||||
"Prepare PDF for AI": "Préparer le PDF pour l'IA",
|
||||
"Previous page": "Page précédente",
|
||||
"Protect PDF": "Protéger un PDF",
|
||||
"PSD to PDF": "PSD vers PDF",
|
||||
"PUB to PDF": "PUB vers PDF",
|
||||
"Rasterize PDF": "Rastériser le PDF",
|
||||
"Remove Annotations": "Supprimer les annotations",
|
||||
"Remove Blank Pages": "Supprimer les pages blanches",
|
||||
"Remove Metadata": "Supprimer les métadonnées",
|
||||
"Remove Restrictions": "Supprimer les restrictions",
|
||||
"Repair PDF": "Réparer un PDF",
|
||||
"Reset search": "Réinitialiser la recherche",
|
||||
"Return to top": "Retour en haut",
|
||||
"Reverse Pages": "Inverser l’ordre des pages",
|
||||
"Rotate by Custom Degrees": "Rotation par angle personnalisé",
|
||||
"Rotate PDF": "Faire pivoter un PDF",
|
||||
"RTF to PDF": "RTF vers PDF",
|
||||
"Sanitize PDF": "Nettoyer un PDF",
|
||||
"Scanner Effect": "Effet scanner",
|
||||
"Search": "Rechercher",
|
||||
"Secure PDF": "Sécuriser un PDF",
|
||||
"Self-Hosting": "Auto-hébergement",
|
||||
"Self-Hosting Guide": "Guide d’auto-hébergement",
|
||||
"Sign PDF": "Signer un PDF",
|
||||
"Skip to content": "Aller au contenu",
|
||||
"Split PDF": "Scinder un PDF",
|
||||
"SVG to PDF": "SVG vers PDF",
|
||||
"Switch to dark theme": "Passer au thème sombre",
|
||||
"Switch to light theme": "Passer au thème clair",
|
||||
"Table of Contents": "Table des matières",
|
||||
"Text to PDF": "Texte vers PDF",
|
||||
"TIFF to PDF": "TIFF vers PDF",
|
||||
"to close": "pour fermer",
|
||||
"to navigate": "pour naviguer",
|
||||
"to select": "pour sélectionner",
|
||||
"Tools": "Outils",
|
||||
"Tools Reference": "Référence des outils",
|
||||
"Unlock PDF": "Déverrouiller un PDF",
|
||||
"Validate Signature": "Valider la signature PDF",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Afficher les métadonnées",
|
||||
"VSD to PDF": "VSD vers PDF",
|
||||
"WebP to PDF": "WebP vers PDF",
|
||||
"Word to PDF": "Word vers PDF",
|
||||
"WPD to PDF": "WPD vers PDF",
|
||||
"WPS to PDF": "WPS vers PDF",
|
||||
"XML to PDF": "XML vers PDF",
|
||||
"XPS to PDF": "XPS vers PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Tambah Lampiran",
|
||||
"Add Blank Page": "Tambah Halaman Kosong",
|
||||
"Add Page Labels": "Tambahkan label halaman",
|
||||
"Add Stamps": "Tambah Stempel",
|
||||
"Add Watermark": "Tambah Watermark",
|
||||
"Adjust Colors": "Sesuaikan Warna",
|
||||
"Alternate & Mix Pages": "Selang-seling & Campur Halaman",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Tampilan",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Warna Latar Belakang",
|
||||
"Bates Numbering": "Penomoran Bates",
|
||||
"BMP to PDF": "BMP ke PDF",
|
||||
"CBZ to PDF": "CBZ ke PDF",
|
||||
"Change language": "Ubah bahasa",
|
||||
"Change Permissions": "Ubah Izin",
|
||||
"Change Text Color": "Ubah Warna Teks",
|
||||
"Close search": "Tutup pencarian",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Gabung ke Halaman Tunggal",
|
||||
"Commercial License": "Lisensi Komersial",
|
||||
"Compare PDFs": "Bandingkan PDF",
|
||||
"Compress PDF": "Kompres PDF",
|
||||
"Contributing": "Berkontribusi",
|
||||
"Convert from PDF": "Konversi dari PDF",
|
||||
"Convert to PDF": "Konversi ke PDF",
|
||||
"Create PDF Form": "Buat Formulir PDF",
|
||||
"Crop PDF": "Potong PDF",
|
||||
"CSV to PDF": "CSV ke PDF",
|
||||
"Delete Pages": "Hapus Halaman",
|
||||
"Deskew PDF": "Luruskan PDF",
|
||||
"Digital Signature": "Tanda Tangan Digital",
|
||||
"Display detailed list": "Tampilkan daftar terperinci",
|
||||
"Divide Pages": "Bagi Halaman",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Dokumentasi BentoPDF - Toolkit PDF gratis, sumber terbuka, dan mengutamakan privasi",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Berlisensi ganda di bawah AGPL-3.0 dan Lisensi Komersial.",
|
||||
"Duplex Collate": "Susun Ulang Pindaian Dupleks",
|
||||
"Edit & Annotate": "Edit & Anotasi",
|
||||
"Edit Attachments": "Edit Lampiran",
|
||||
"Edit Bookmarks": "Edit Bookmark",
|
||||
"Edit Metadata": "Edit Metadata",
|
||||
"Email to PDF": "Email ke PDF",
|
||||
"EPUB to PDF": "EPUB ke PDF",
|
||||
"Excel to PDF": "Excel ke PDF",
|
||||
"Extract Attachments": "Ekstrak Lampiran",
|
||||
"Extract Images": "Ekstrak Gambar",
|
||||
"Extract Pages": "Ekstrak Halaman",
|
||||
"Extract Tables": "Ekstrak Tabel PDF",
|
||||
"FB2 to PDF": "FB2 ke PDF",
|
||||
"Fix Page Size": "Perbaiki Ukuran Halaman",
|
||||
"Flatten PDF": "Ratakan PDF",
|
||||
"Font to Outline": "Font ke Garis Tepi",
|
||||
"Getting Started": "Memulai",
|
||||
"Guide": "Panduan",
|
||||
"Header & Footer": "Kepala & Kaki Halaman",
|
||||
"HEIC to PDF": "HEIC ke PDF",
|
||||
"Home": "Beranda",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Gambar ke PDF",
|
||||
"Invert Colors": "Balik Warna",
|
||||
"JPG to PDF": "JPG ke PDF",
|
||||
"JSON to PDF": "JSON ke PDF",
|
||||
"Linearize PDF": "Linierisasi PDF",
|
||||
"Markdown to PDF": "Markdown ke PDF",
|
||||
"Menu": "Menu",
|
||||
"Merge PDF": "Gabung PDF",
|
||||
"MOBI to PDF": "MOBI ke PDF",
|
||||
"N-Up PDF": "N-Up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Halaman berikutnya",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Tidak ada hasil untuk",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODG ke PDF",
|
||||
"ODP to PDF": "ODP ke PDF",
|
||||
"ODS to PDF": "ODS ke PDF",
|
||||
"ODT to PDF": "ODT ke PDF",
|
||||
"On this page": "Di halaman ini",
|
||||
"Optimize & Repair": "Optimalkan & Perbaiki",
|
||||
"Organize & Duplicate": "Duplikat & Atur",
|
||||
"Organize & Manage": "Atur & Kelola",
|
||||
"Overview": "Ringkasan",
|
||||
"Page Dimensions": "Dimensi Halaman",
|
||||
"Page Numbers": "Nomor Halaman",
|
||||
"PDF Booklet": "Buku Kecil PDF",
|
||||
"PDF Editor": "Editor PDF",
|
||||
"PDF Form Filler": "Pengisi Formulir PDF",
|
||||
"PDF Multi Tool": "Alat Multi PDF",
|
||||
"PDF OCG": "Lapisan PDF (OCG)",
|
||||
"PDF to BMP": "PDF ke BMP",
|
||||
"PDF to CBZ": "PDF ke CBZ",
|
||||
"PDF to CSV": "PDF ke CSV",
|
||||
"PDF to Excel": "PDF ke Excel",
|
||||
"PDF to Greyscale": "PDF ke Skala Abu-abu",
|
||||
"PDF to JPG": "PDF ke JPG",
|
||||
"PDF to JSON": "PDF ke JSON",
|
||||
"PDF to Markdown": "PDF ke Markdown",
|
||||
"PDF to PDF/A": "PDF ke PDF/A",
|
||||
"PDF to PNG": "PDF ke PNG",
|
||||
"PDF to SVG": "PDF ke SVG",
|
||||
"PDF to Text": "PDF ke Teks",
|
||||
"PDF to TIFF": "PDF ke TIFF",
|
||||
"PDF to WebP": "PDF ke WebP",
|
||||
"PDF to Word": "PDF ke Word",
|
||||
"PDF Workflow Builder": "Pembangun Alur Kerja PDF",
|
||||
"PDFs to ZIP": "PDF ke ZIP",
|
||||
"PNG to PDF": "PNG ke PDF",
|
||||
"Posterize PDF": "Posterisasi PDF",
|
||||
"PowerPoint to PDF": "PowerPoint ke PDF",
|
||||
"Prepare PDF for AI": "Siapkan PDF untuk AI",
|
||||
"Previous page": "Halaman sebelumnya",
|
||||
"Protect PDF": "Lindungi PDF",
|
||||
"PSD to PDF": "PSD ke PDF",
|
||||
"PUB to PDF": "PUB ke PDF",
|
||||
"Rasterize PDF": "Rasterisasi PDF",
|
||||
"Remove Annotations": "Hapus Anotasi",
|
||||
"Remove Blank Pages": "Hapus Halaman Kosong",
|
||||
"Remove Metadata": "Hapus Metadata",
|
||||
"Remove Restrictions": "Hapus Pembatasan",
|
||||
"Repair PDF": "Perbaiki PDF",
|
||||
"Reset search": "Atur ulang pencarian",
|
||||
"Return to top": "Kembali ke atas",
|
||||
"Reverse Pages": "Balik Halaman",
|
||||
"Rotate by Custom Degrees": "Putar dengan Derajat Kustom",
|
||||
"Rotate PDF": "Putar PDF",
|
||||
"RTF to PDF": "RTF ke PDF",
|
||||
"Sanitize PDF": "Sanitasi PDF",
|
||||
"Scanner Effect": "Efek Pemindai",
|
||||
"Search": "Cari",
|
||||
"Secure PDF": "Amankan PDF",
|
||||
"Self-Hosting": "Hosting Mandiri",
|
||||
"Self-Hosting Guide": "Panduan Hosting Mandiri",
|
||||
"Sign PDF": "Tanda Tangan PDF",
|
||||
"Skip to content": "Lewati ke konten",
|
||||
"Split PDF": "Pisah PDF",
|
||||
"SVG to PDF": "SVG ke PDF",
|
||||
"Switch to dark theme": "Beralih ke tema gelap",
|
||||
"Switch to light theme": "Beralih ke tema terang",
|
||||
"Table of Contents": "Daftar Isi",
|
||||
"Text to PDF": "Teks ke PDF",
|
||||
"TIFF to PDF": "TIFF ke PDF",
|
||||
"to close": "untuk menutup",
|
||||
"to navigate": "untuk bernavigasi",
|
||||
"to select": "untuk memilih",
|
||||
"Tools": "Alat",
|
||||
"Tools Reference": "Referensi Alat",
|
||||
"Unlock PDF": "Buka Kunci PDF",
|
||||
"Validate Signature": "Validasi Tanda Tangan PDF",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Lihat Metadata",
|
||||
"VSD to PDF": "VSD ke PDF",
|
||||
"WebP to PDF": "WebP ke PDF",
|
||||
"Word to PDF": "Word ke PDF",
|
||||
"WPD to PDF": "WPD ke PDF",
|
||||
"WPS to PDF": "WPS ke PDF",
|
||||
"XML to PDF": "XML ke PDF",
|
||||
"XPS to PDF": "XPS ke PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Aggiungi allegati",
|
||||
"Add Blank Page": "Aggiungi pagina vuota",
|
||||
"Add Page Labels": "Aggiungi etichette di pagina",
|
||||
"Add Stamps": "Aggiungi timbri",
|
||||
"Add Watermark": "Aggiungi filigrana",
|
||||
"Adjust Colors": "Regola colori",
|
||||
"Alternate & Mix Pages": "Alterna e mescola pagine",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Aspetto",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Colore di sfondo",
|
||||
"Bates Numbering": "Numerazione Bates",
|
||||
"BMP to PDF": "BMP in PDF",
|
||||
"CBZ to PDF": "CBZ in PDF",
|
||||
"Change language": "Cambia lingua",
|
||||
"Change Permissions": "Modifica permessi",
|
||||
"Change Text Color": "Cambia colore testo",
|
||||
"Close search": "Chiudi la ricerca",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Combina in una pagina",
|
||||
"Commercial License": "Licenza commerciale",
|
||||
"Compare PDFs": "Confronta PDF",
|
||||
"Compress PDF": "Comprimi PDF",
|
||||
"Contributing": "Contribuire",
|
||||
"Convert from PDF": "Converti da PDF",
|
||||
"Convert to PDF": "Converti in PDF",
|
||||
"Create PDF Form": "Crea modulo PDF",
|
||||
"Crop PDF": "Ritaglia PDF",
|
||||
"CSV to PDF": "CSV in PDF",
|
||||
"Delete Pages": "Elimina pagine",
|
||||
"Deskew PDF": "Raddrizza PDF",
|
||||
"Digital Signature": "Firma digitale",
|
||||
"Display detailed list": "Mostra elenco dettagliato",
|
||||
"Divide Pages": "Dividi pagine",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Documentazione di BentoPDF: il toolkit PDF gratuito, open source e attento alla privacy",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Doppia licenza: AGPL-3.0 e Licenza commerciale.",
|
||||
"Duplex Collate": "Fascicola scansione fronte/retro",
|
||||
"Edit & Annotate": "Modifica e annota",
|
||||
"Edit Attachments": "Modifica allegati",
|
||||
"Edit Bookmarks": "Modifica segnalibri",
|
||||
"Edit Metadata": "Modifica metadati",
|
||||
"Email to PDF": "Email in PDF",
|
||||
"EPUB to PDF": "EPUB in PDF",
|
||||
"Excel to PDF": "Excel in PDF",
|
||||
"Extract Attachments": "Estrai allegati",
|
||||
"Extract Images": "Estrai immagini",
|
||||
"Extract Pages": "Estrai pagine",
|
||||
"Extract Tables": "Estrai tabelle",
|
||||
"FB2 to PDF": "FB2 in PDF",
|
||||
"Fix Page Size": "Correggi dimensione pagina",
|
||||
"Flatten PDF": "Appiattisci PDF",
|
||||
"Font to Outline": "Font in contorni",
|
||||
"Getting Started": "Per iniziare",
|
||||
"Guide": "Guida",
|
||||
"Header & Footer": "Intestazione e piè di pagina",
|
||||
"HEIC to PDF": "HEIC in PDF",
|
||||
"Home": "Home",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Immagini in PDF",
|
||||
"Invert Colors": "Inverti colori",
|
||||
"JPG to PDF": "JPG in PDF",
|
||||
"JSON to PDF": "JSON in PDF",
|
||||
"Linearize PDF": "Linearizza PDF",
|
||||
"Markdown to PDF": "Markdown in PDF",
|
||||
"Menu": "Menu",
|
||||
"Merge PDF": "Unisci PDF",
|
||||
"MOBI to PDF": "MOBI in PDF",
|
||||
"N-Up PDF": "N-Up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Pagina successiva",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Nessun risultato per",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODG in PDF",
|
||||
"ODP to PDF": "ODP in PDF",
|
||||
"ODS to PDF": "ODS in PDF",
|
||||
"ODT to PDF": "ODT in PDF",
|
||||
"On this page": "In questa pagina",
|
||||
"Optimize & Repair": "Ottimizza e ripara",
|
||||
"Organize & Duplicate": "Organizza e duplica",
|
||||
"Organize & Manage": "Organizza e gestisci",
|
||||
"Overview": "Panoramica",
|
||||
"Page Dimensions": "Dimensioni pagina",
|
||||
"Page Numbers": "Numeri di pagina",
|
||||
"PDF Booklet": "Opuscolo PDF",
|
||||
"PDF Editor": "Editor PDF",
|
||||
"PDF Form Filler": "Compilatore moduli PDF",
|
||||
"PDF Multi Tool": "Strumento PDF multifunzione",
|
||||
"PDF OCG": "Livelli PDF (OCG)",
|
||||
"PDF to BMP": "PDF in BMP",
|
||||
"PDF to CBZ": "PDF in CBZ",
|
||||
"PDF to CSV": "PDF in CSV",
|
||||
"PDF to Excel": "PDF in Excel",
|
||||
"PDF to Greyscale": "PDF in scala di grigi",
|
||||
"PDF to JPG": "PDF in JPG",
|
||||
"PDF to JSON": "PDF in JSON",
|
||||
"PDF to Markdown": "PDF in Markdown",
|
||||
"PDF to PDF/A": "PDF in PDF/A",
|
||||
"PDF to PNG": "PDF in PNG",
|
||||
"PDF to SVG": "PDF in SVG",
|
||||
"PDF to Text": "PDF in Testo",
|
||||
"PDF to TIFF": "PDF in TIFF",
|
||||
"PDF to WebP": "PDF in WebP",
|
||||
"PDF to Word": "PDF in Word",
|
||||
"PDF Workflow Builder": "Costruttore di workflow PDF",
|
||||
"PDFs to ZIP": "PDF in ZIP",
|
||||
"PNG to PDF": "PNG in PDF",
|
||||
"Posterize PDF": "Posterizza PDF",
|
||||
"PowerPoint to PDF": "PowerPoint in PDF",
|
||||
"Prepare PDF for AI": "Prepara PDF per IA",
|
||||
"Previous page": "Pagina precedente",
|
||||
"Protect PDF": "Proteggi PDF",
|
||||
"PSD to PDF": "PSD in PDF",
|
||||
"PUB to PDF": "PUB in PDF",
|
||||
"Rasterize PDF": "Rasterizza PDF",
|
||||
"Remove Annotations": "Rimuovi annotazioni",
|
||||
"Remove Blank Pages": "Rimuovi pagine vuote",
|
||||
"Remove Metadata": "Rimuovi metadati",
|
||||
"Remove Restrictions": "Rimuovi restrizioni",
|
||||
"Repair PDF": "Ripara PDF",
|
||||
"Reset search": "Reimposta la ricerca",
|
||||
"Return to top": "Torna su",
|
||||
"Reverse Pages": "Inverti pagine",
|
||||
"Rotate by Custom Degrees": "Ruota di gradi personalizzati",
|
||||
"Rotate PDF": "Ruota PDF",
|
||||
"RTF to PDF": "RTF in PDF",
|
||||
"Sanitize PDF": "Sanitizza PDF",
|
||||
"Scanner Effect": "Effetto scanner",
|
||||
"Search": "Cerca",
|
||||
"Secure PDF": "Proteggi PDF",
|
||||
"Self-Hosting": "Self-hosting",
|
||||
"Self-Hosting Guide": "Guida al self-hosting",
|
||||
"Sign PDF": "Firma PDF",
|
||||
"Skip to content": "Vai al contenuto",
|
||||
"Split PDF": "Dividi PDF",
|
||||
"SVG to PDF": "SVG in PDF",
|
||||
"Switch to dark theme": "Passa al tema scuro",
|
||||
"Switch to light theme": "Passa al tema chiaro",
|
||||
"Table of Contents": "Indice",
|
||||
"Text to PDF": "Testo in PDF",
|
||||
"TIFF to PDF": "TIFF in PDF",
|
||||
"to close": "per chiudere",
|
||||
"to navigate": "per navigare",
|
||||
"to select": "per selezionare",
|
||||
"Tools": "Strumenti",
|
||||
"Tools Reference": "Riferimento degli strumenti",
|
||||
"Unlock PDF": "Sblocca PDF",
|
||||
"Validate Signature": "Convalida firma",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Visualizza metadati",
|
||||
"VSD to PDF": "VSD in PDF",
|
||||
"WebP to PDF": "WebP in PDF",
|
||||
"Word to PDF": "Word in PDF",
|
||||
"WPD to PDF": "WPD in PDF",
|
||||
"WPS to PDF": "WPS in PDF",
|
||||
"XML to PDF": "XML in PDF",
|
||||
"XPS to PDF": "XPS in PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "添付ファイルの追加",
|
||||
"Add Blank Page": "空白ページの追加",
|
||||
"Add Page Labels": "ページラベルの追加",
|
||||
"Add Stamps": "スタンプの追加",
|
||||
"Add Watermark": "透かしの追加",
|
||||
"Adjust Colors": "色の調整",
|
||||
"Alternate & Mix Pages": "ページの交互結合と混合",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "外観",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "背景色",
|
||||
"Bates Numbering": "通し番号付け",
|
||||
"BMP to PDF": "BMPからPDF",
|
||||
"CBZ to PDF": "CBZからPDF",
|
||||
"Change language": "言語を変更",
|
||||
"Change Permissions": "権限の変更",
|
||||
"Change Text Color": "テキストの色の変更",
|
||||
"Close search": "検索を閉じる",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "単一ページに結合",
|
||||
"Commercial License": "商用ライセンス",
|
||||
"Compare PDFs": "PDFの比較",
|
||||
"Compress PDF": "PDFの圧縮",
|
||||
"Contributing": "コントリビュート",
|
||||
"Convert from PDF": "PDFから変換",
|
||||
"Convert to PDF": "PDFに変換",
|
||||
"Create PDF Form": "PDFフォームの作成",
|
||||
"Crop PDF": "PDFのトリミング",
|
||||
"CSV to PDF": "CSVからPDF",
|
||||
"Delete Pages": "ページの削除",
|
||||
"Deskew PDF": "PDFの傾き補正",
|
||||
"Digital Signature": "PDFへの電子署名",
|
||||
"Display detailed list": "詳細リストを表示",
|
||||
"Divide Pages": "ページの分割",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "BentoPDFのドキュメント: 無料でオープンソース、プライバシーを第一に考えたPDFツールキット",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "AGPL-3.0と商用ライセンスのデュアルライセンスです。",
|
||||
"Duplex Collate": "両面スキャンの並べ替え",
|
||||
"Edit & Annotate": "編集と注釈",
|
||||
"Edit Attachments": "添付ファイルの編集",
|
||||
"Edit Bookmarks": "しおりの編集",
|
||||
"Edit Metadata": "メタデータの編集",
|
||||
"Email to PDF": "電子メールからPDF",
|
||||
"EPUB to PDF": "EPUBからPDF",
|
||||
"Excel to PDF": "ExcelからPDF",
|
||||
"Extract Attachments": "添付ファイルの抽出",
|
||||
"Extract Images": "画像の抽出",
|
||||
"Extract Pages": "ページの抽出",
|
||||
"Extract Tables": "PDFテーブルの抽出",
|
||||
"FB2 to PDF": "FB2からPDF",
|
||||
"Fix Page Size": "ページサイズを固定",
|
||||
"Flatten PDF": "PDFの平坦化",
|
||||
"Font to Outline": "フォントをアウトライン化",
|
||||
"Getting Started": "はじめに",
|
||||
"Guide": "ガイド",
|
||||
"Header & Footer": "ヘッダーとフッター",
|
||||
"HEIC to PDF": "HEICからPDF",
|
||||
"Home": "ホーム",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "画像からPDF",
|
||||
"Invert Colors": "色の反転",
|
||||
"JPG to PDF": "JPGからPDF",
|
||||
"JSON to PDF": "JSONからPDF",
|
||||
"Linearize PDF": "PDFの線形化",
|
||||
"Markdown to PDF": "マークダウンからPDF",
|
||||
"Menu": "メニュー",
|
||||
"Merge PDF": "PDFの結合",
|
||||
"MOBI to PDF": "MOBIからPDF",
|
||||
"N-Up PDF": "N-Up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "次のページ",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "次の検索結果はありません:",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODGからPDF",
|
||||
"ODP to PDF": "ODPからPDF",
|
||||
"ODS to PDF": "ODSからPDF",
|
||||
"ODT to PDF": "ODTからPDF",
|
||||
"On this page": "このページの内容",
|
||||
"Optimize & Repair": "最適化と修復",
|
||||
"Organize & Duplicate": "複製と整理",
|
||||
"Organize & Manage": "整理と管理",
|
||||
"Overview": "概要",
|
||||
"Page Dimensions": "ページ寸法",
|
||||
"Page Numbers": "ページ番号",
|
||||
"PDF Booklet": "PDFブックレット",
|
||||
"PDF Editor": "PDFエディター",
|
||||
"PDF Form Filler": "PDFフォーム入力機能",
|
||||
"PDF Multi Tool": "PDFマルチツール",
|
||||
"PDF OCG": "PDF OCG",
|
||||
"PDF to BMP": "PDFからBMP",
|
||||
"PDF to CBZ": "PDFからCBZ",
|
||||
"PDF to CSV": "PDFからCSV",
|
||||
"PDF to Excel": "PDFからExcel",
|
||||
"PDF to Greyscale": "PDFのグレースケール化",
|
||||
"PDF to JPG": "PDFからJPG",
|
||||
"PDF to JSON": "PDFからJSON",
|
||||
"PDF to Markdown": "PDFからマークダウン",
|
||||
"PDF to PDF/A": "PDFからPDF/A",
|
||||
"PDF to PNG": "PDFからPNG",
|
||||
"PDF to SVG": "PDFからSVG",
|
||||
"PDF to Text": "PDFからテキスト",
|
||||
"PDF to TIFF": "PDFからTIFF",
|
||||
"PDF to WebP": "PDFからWebP",
|
||||
"PDF to Word": "PDFからWord",
|
||||
"PDF Workflow Builder": "PDFワークフロービルダー",
|
||||
"PDFs to ZIP": "PDFからZIP",
|
||||
"PNG to PDF": "PNGからPDF",
|
||||
"Posterize PDF": "PDFのポスタライズ",
|
||||
"PowerPoint to PDF": "PowerPointからPDF",
|
||||
"Prepare PDF for AI": "AI用のPDFを準備する",
|
||||
"Previous page": "前のページ",
|
||||
"Protect PDF": "PDFの保護",
|
||||
"PSD to PDF": "PSDからPDF",
|
||||
"PUB to PDF": "PUBからPDF",
|
||||
"Rasterize PDF": "PDFの画像化",
|
||||
"Remove Annotations": "注釈の削除",
|
||||
"Remove Blank Pages": "空白ページの削除",
|
||||
"Remove Metadata": "メタデータの削除",
|
||||
"Remove Restrictions": "制限の解除",
|
||||
"Repair PDF": "PDFの修復",
|
||||
"Reset search": "検索をリセット",
|
||||
"Return to top": "トップに戻る",
|
||||
"Reverse Pages": "ページの反転",
|
||||
"Rotate by Custom Degrees": "任意の角度で回転",
|
||||
"Rotate PDF": "PDFの回転",
|
||||
"RTF to PDF": "RTFからPDF",
|
||||
"Sanitize PDF": "PDFをサニタイズ",
|
||||
"Scanner Effect": "スキャナー効果",
|
||||
"Search": "検索",
|
||||
"Secure PDF": "PDFのセキュリティ",
|
||||
"Self-Hosting": "セルフホスティング",
|
||||
"Self-Hosting Guide": "セルフホスティングガイド",
|
||||
"Sign PDF": "PDFへ署名",
|
||||
"Skip to content": "コンテンツへスキップ",
|
||||
"Split PDF": "PDFの分割",
|
||||
"SVG to PDF": "SVGからPDF",
|
||||
"Switch to dark theme": "ダークテーマに切り替え",
|
||||
"Switch to light theme": "ライトテーマに切り替え",
|
||||
"Table of Contents": "目次",
|
||||
"Text to PDF": "テキストからPDF",
|
||||
"TIFF to PDF": "TIFFからPDF",
|
||||
"to close": "閉じる",
|
||||
"to navigate": "移動",
|
||||
"to select": "選択",
|
||||
"Tools": "ツール",
|
||||
"Tools Reference": "ツールリファレンス",
|
||||
"Unlock PDF": "PDFのロック解除",
|
||||
"Validate Signature": "PDF署名を検証する",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "メタデータの表示",
|
||||
"VSD to PDF": "VSDからPDF",
|
||||
"WebP to PDF": "WebPからPDF",
|
||||
"Word to PDF": "WordからPDF",
|
||||
"WPD to PDF": "WPDからPDF",
|
||||
"WPS to PDF": "WPSからPDF",
|
||||
"XML to PDF": "XMLからPDF",
|
||||
"XPS to PDF": "XPSからPDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "첨부파일 추가",
|
||||
"Add Blank Page": "빈 페이지 추가",
|
||||
"Add Page Labels": "페이지 레이블 추가",
|
||||
"Add Stamps": "도장 추가",
|
||||
"Add Watermark": "워터마크 추가",
|
||||
"Adjust Colors": "색상 조정",
|
||||
"Alternate & Mix Pages": "페이지 교차 병합",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "모양",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "배경색 변경",
|
||||
"Bates Numbering": "베이츠 번호 매기기",
|
||||
"BMP to PDF": "BMP를 PDF로",
|
||||
"CBZ to PDF": "CBZ를 PDF로",
|
||||
"Change language": "언어 변경",
|
||||
"Change Permissions": "권한 변경",
|
||||
"Change Text Color": "텍스트 색상 변경",
|
||||
"Close search": "검색 닫기",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "한 페이지로 합치기",
|
||||
"Commercial License": "상용 라이선스",
|
||||
"Compare PDFs": "PDF 비교",
|
||||
"Compress PDF": "PDF 압축",
|
||||
"Contributing": "기여하기",
|
||||
"Convert from PDF": "PDF에서 변환",
|
||||
"Convert to PDF": "PDF로 변환",
|
||||
"Create PDF Form": "PDF 양식 만들기",
|
||||
"Crop PDF": "PDF 자르기",
|
||||
"CSV to PDF": "CSV를 PDF로",
|
||||
"Delete Pages": "페이지 삭제",
|
||||
"Deskew PDF": "PDF 기울기 보정",
|
||||
"Digital Signature": "PDF 디지털 서명",
|
||||
"Display detailed list": "자세한 목록 표시",
|
||||
"Divide Pages": "페이지 분할",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "BentoPDF 문서 - 무료 오픈소스, 개인정보 보호 중심 PDF 도구 모음",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "AGPL-3.0과 상용 라이선스의 이중 라이선스로 제공됩니다.",
|
||||
"Duplex Collate": "양면 스캔 정렬",
|
||||
"Edit & Annotate": "편집 및 주석",
|
||||
"Edit Attachments": "첨부파일 편집",
|
||||
"Edit Bookmarks": "북마크 편집",
|
||||
"Edit Metadata": "메타데이터 편집",
|
||||
"Email to PDF": "이메일을 PDF로",
|
||||
"EPUB to PDF": "EPUB를 PDF로",
|
||||
"Excel to PDF": "Excel을 PDF로",
|
||||
"Extract Attachments": "첨부파일 추출",
|
||||
"Extract Images": "이미지 추출",
|
||||
"Extract Pages": "페이지 추출",
|
||||
"Extract Tables": "PDF 표 추출",
|
||||
"FB2 to PDF": "FB2를 PDF로",
|
||||
"Fix Page Size": "페이지 크기 통일",
|
||||
"Flatten PDF": "PDF 평탄화",
|
||||
"Font to Outline": "폰트 윤곽선 변환",
|
||||
"Getting Started": "시작하기",
|
||||
"Guide": "가이드",
|
||||
"Header & Footer": "머리글 및 바닥글",
|
||||
"HEIC to PDF": "HEIC를 PDF로",
|
||||
"Home": "홈",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "이미지를 PDF로",
|
||||
"Invert Colors": "색상 반전",
|
||||
"JPG to PDF": "JPG를 PDF로",
|
||||
"JSON to PDF": "JSON을 PDF로",
|
||||
"Linearize PDF": "PDF 선형화",
|
||||
"Markdown to PDF": "Markdown을 PDF로",
|
||||
"Menu": "메뉴",
|
||||
"Merge PDF": "PDF 병합",
|
||||
"MOBI to PDF": "MOBI를 PDF로",
|
||||
"N-Up PDF": "N-Up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "다음 페이지",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "검색 결과 없음:",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODG를 PDF로",
|
||||
"ODP to PDF": "ODP를 PDF로",
|
||||
"ODS to PDF": "ODS를 PDF로",
|
||||
"ODT to PDF": "ODT를 PDF로",
|
||||
"On this page": "이 페이지의 내용",
|
||||
"Optimize & Repair": "최적화 및 복구",
|
||||
"Organize & Duplicate": "복제 및 정리",
|
||||
"Organize & Manage": "정리 및 관리",
|
||||
"Overview": "개요",
|
||||
"Page Dimensions": "페이지 크기 정보",
|
||||
"Page Numbers": "페이지 번호",
|
||||
"PDF Booklet": "PDF 소책자",
|
||||
"PDF Editor": "PDF 편집기",
|
||||
"PDF Form Filler": "PDF 양식 작성",
|
||||
"PDF Multi Tool": "PDF 멀티 도구",
|
||||
"PDF OCG": "PDF OCG",
|
||||
"PDF to BMP": "PDF를 BMP로",
|
||||
"PDF to CBZ": "PDF를 CBZ로",
|
||||
"PDF to CSV": "PDF를 CSV로",
|
||||
"PDF to Excel": "PDF를 Excel로",
|
||||
"PDF to Greyscale": "PDF 흑백 변환",
|
||||
"PDF to JPG": "PDF를 JPG로",
|
||||
"PDF to JSON": "PDF를 JSON으로",
|
||||
"PDF to Markdown": "PDF를 Markdown으로",
|
||||
"PDF to PDF/A": "PDF를 PDF/A로",
|
||||
"PDF to PNG": "PDF를 PNG로",
|
||||
"PDF to SVG": "PDF를 SVG로",
|
||||
"PDF to Text": "PDF를 텍스트로",
|
||||
"PDF to TIFF": "PDF를 TIFF로",
|
||||
"PDF to WebP": "PDF를 WebP로",
|
||||
"PDF to Word": "PDF를 Word로",
|
||||
"PDF Workflow Builder": "PDF 워크플로우 빌더",
|
||||
"PDFs to ZIP": "PDF를 ZIP으로",
|
||||
"PNG to PDF": "PNG를 PDF로",
|
||||
"Posterize PDF": "PDF 포스터화",
|
||||
"PowerPoint to PDF": "PowerPoint를 PDF로",
|
||||
"Prepare PDF for AI": "AI용 PDF 준비",
|
||||
"Previous page": "이전 페이지",
|
||||
"Protect PDF": "PDF 보호",
|
||||
"PSD to PDF": "PSD를 PDF로",
|
||||
"PUB to PDF": "PUB를 PDF로",
|
||||
"Rasterize PDF": "PDF 래스터화",
|
||||
"Remove Annotations": "주석 제거",
|
||||
"Remove Blank Pages": "빈 페이지 제거",
|
||||
"Remove Metadata": "메타데이터 제거",
|
||||
"Remove Restrictions": "제한 해제",
|
||||
"Repair PDF": "PDF 복구",
|
||||
"Reset search": "검색 초기화",
|
||||
"Return to top": "맨 위로",
|
||||
"Reverse Pages": "페이지 역순 정렬",
|
||||
"Rotate by Custom Degrees": "사용자 지정 각도 회전",
|
||||
"Rotate PDF": "PDF 회전",
|
||||
"RTF to PDF": "RTF를 PDF로",
|
||||
"Sanitize PDF": "PDF 정리",
|
||||
"Scanner Effect": "스캔 효과",
|
||||
"Search": "검색",
|
||||
"Secure PDF": "PDF 보안",
|
||||
"Self-Hosting": "자체 호스팅",
|
||||
"Self-Hosting Guide": "자체 호스팅 가이드",
|
||||
"Sign PDF": "PDF 서명",
|
||||
"Skip to content": "본문으로 건너뛰기",
|
||||
"Split PDF": "PDF 분할",
|
||||
"SVG to PDF": "SVG를 PDF로",
|
||||
"Switch to dark theme": "다크 테마로 전환",
|
||||
"Switch to light theme": "라이트 테마로 전환",
|
||||
"Table of Contents": "목차 생성",
|
||||
"Text to PDF": "텍스트를 PDF로",
|
||||
"TIFF to PDF": "TIFF를 PDF로",
|
||||
"to close": "닫기",
|
||||
"to navigate": "이동",
|
||||
"to select": "선택",
|
||||
"Tools": "도구",
|
||||
"Tools Reference": "도구 참조",
|
||||
"Unlock PDF": "PDF 잠금 해제",
|
||||
"Validate Signature": "PDF 서명 검증",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "메타데이터 보기",
|
||||
"VSD to PDF": "VSD를 PDF로",
|
||||
"WebP to PDF": "WebP를 PDF로",
|
||||
"Word to PDF": "Word를 PDF로",
|
||||
"WPD to PDF": "WPD를 PDF로",
|
||||
"WPS to PDF": "WPS를 PDF로",
|
||||
"XML to PDF": "XML을 PDF로",
|
||||
"XPS to PDF": "XPS를 PDF로"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Bijlagen toevoegen",
|
||||
"Add Blank Page": "Blanco pagina toevoegen",
|
||||
"Add Page Labels": "Paginalabels toevoegen",
|
||||
"Add Stamps": "Stempels toevoegen",
|
||||
"Add Watermark": "Watermerk toevoegen",
|
||||
"Adjust Colors": "Kleuren aanpassen",
|
||||
"Alternate & Mix Pages": "Pagina's afwisselen en mixen",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Weergave",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Achtergrondkleur",
|
||||
"Bates Numbering": "Bates-nummering",
|
||||
"BMP to PDF": "BMP naar PDF",
|
||||
"CBZ to PDF": "CBZ naar PDF",
|
||||
"Change language": "Taal wijzigen",
|
||||
"Change Permissions": "Rechten aanpassen",
|
||||
"Change Text Color": "Tekstkleur",
|
||||
"Close search": "Zoeken sluiten",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Combineren tot één pagina",
|
||||
"Commercial License": "Commerciële licentie",
|
||||
"Compare PDFs": "PDF's vergelijken",
|
||||
"Compress PDF": "PDF comprimeren",
|
||||
"Contributing": "Bijdragen",
|
||||
"Convert from PDF": "Converteren van PDF",
|
||||
"Convert to PDF": "Converteren naar PDF",
|
||||
"Create PDF Form": "PDF-formulier aanmaken",
|
||||
"Crop PDF": "PDF bijsnijden",
|
||||
"CSV to PDF": "CSV naar PDF",
|
||||
"Delete Pages": "Pagina's verwijderen",
|
||||
"Deskew PDF": "PDF rechttrekken",
|
||||
"Digital Signature": "Digitale handtekening",
|
||||
"Display detailed list": "Gedetailleerde lijst tonen",
|
||||
"Divide Pages": "Pagina's opdelen",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Documentatie voor BentoPDF - De gratis, open-source PDF-toolkit met privacy voorop",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Dubbel gelicentieerd onder AGPL-3.0 en een commerciële licentie.",
|
||||
"Duplex Collate": "Dubbelzijdige scan sorteren",
|
||||
"Edit & Annotate": "Bewerken & Annoteren",
|
||||
"Edit Attachments": "Bijlagen bewerken",
|
||||
"Edit Bookmarks": "Bladwijzers bewerken",
|
||||
"Edit Metadata": "Metadata bewerken",
|
||||
"Email to PDF": "E-mail naar PDF",
|
||||
"EPUB to PDF": "EPUB naar PDF",
|
||||
"Excel to PDF": "Excel naar PDF",
|
||||
"Extract Attachments": "Bijlagen extraheren",
|
||||
"Extract Images": "Afbeeldingen extraheren",
|
||||
"Extract Pages": "Pagina's extraheren",
|
||||
"Extract Tables": "PDF-tabellen Extraheren",
|
||||
"FB2 to PDF": "FB2 naar PDF",
|
||||
"Fix Page Size": "Paginagrootte corrigeren",
|
||||
"Flatten PDF": "PDF platmaken",
|
||||
"Font to Outline": "Lettertype naar contouren",
|
||||
"Getting Started": "Aan de slag",
|
||||
"Guide": "Gids",
|
||||
"Header & Footer": "Koptekst en voettekst",
|
||||
"HEIC to PDF": "HEIC naar PDF",
|
||||
"Home": "Startpagina",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Afbeelding naar PDF",
|
||||
"Invert Colors": "Kleuren omkeren",
|
||||
"JPG to PDF": "JPG naar PDF",
|
||||
"JSON to PDF": "JSON naar PDF",
|
||||
"Linearize PDF": "PDF lineariseren",
|
||||
"Markdown to PDF": "Markdown naar PDF",
|
||||
"Menu": "Menu",
|
||||
"Merge PDF": "PDF samenvoegen",
|
||||
"MOBI to PDF": "MOBI naar PDF",
|
||||
"N-Up PDF": "N-up PDF",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Volgende pagina",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Geen resultaten voor",
|
||||
"OCR PDF": "PDF OCR",
|
||||
"ODG to PDF": "ODG naar PDF",
|
||||
"ODP to PDF": "ODP naar PDF",
|
||||
"ODS to PDF": "ODS naar PDF",
|
||||
"ODT to PDF": "ODT naar PDF",
|
||||
"On this page": "Op deze pagina",
|
||||
"Optimize & Repair": "Optimaliseren & Repareren",
|
||||
"Organize & Duplicate": "Dupliceren & Organiseren",
|
||||
"Organize & Manage": "Organiseren & Beheren",
|
||||
"Overview": "Overzicht",
|
||||
"Page Dimensions": "Paginaafmetingen",
|
||||
"Page Numbers": "Paginanummers",
|
||||
"PDF Booklet": "PDF-boekje",
|
||||
"PDF Editor": "PDF bewerken",
|
||||
"PDF Form Filler": "PDF-formulier invullen",
|
||||
"PDF Multi Tool": "PDF-multitool",
|
||||
"PDF OCG": "PDF-lagen (OCG)",
|
||||
"PDF to BMP": "PDF naar BMP",
|
||||
"PDF to CBZ": "PDF naar CBZ",
|
||||
"PDF to CSV": "PDF naar CSV",
|
||||
"PDF to Excel": "PDF naar Excel",
|
||||
"PDF to Greyscale": "PDF naar grijswaarden",
|
||||
"PDF to JPG": "PDF naar JPG",
|
||||
"PDF to JSON": "PDF naar JSON",
|
||||
"PDF to Markdown": "PDF naar Markdown",
|
||||
"PDF to PDF/A": "PDF naar PDF/A",
|
||||
"PDF to PNG": "PDF naar PNG",
|
||||
"PDF to SVG": "PDF naar SVG",
|
||||
"PDF to Text": "PDF naar tekst",
|
||||
"PDF to TIFF": "PDF naar TIFF",
|
||||
"PDF to WebP": "PDF naar WebP",
|
||||
"PDF to Word": "PDF naar Word",
|
||||
"PDF Workflow Builder": "PDF-workflowbouwer",
|
||||
"PDFs to ZIP": "PDF's naar ZIP",
|
||||
"PNG to PDF": "PNG naar PDF",
|
||||
"Posterize PDF": "PDF-poster",
|
||||
"PowerPoint to PDF": "PowerPoint naar PDF",
|
||||
"Prepare PDF for AI": "PDF voorbereiden voor AI",
|
||||
"Previous page": "Vorige pagina",
|
||||
"Protect PDF": "PDF beveiligen",
|
||||
"PSD to PDF": "PSD naar PDF",
|
||||
"PUB to PDF": "PUB naar PDF",
|
||||
"Rasterize PDF": "PDF rasteren",
|
||||
"Remove Annotations": "Annotaties verwijderen",
|
||||
"Remove Blank Pages": "Blanco pagina's verwijderen",
|
||||
"Remove Metadata": "Metadata verwijderen",
|
||||
"Remove Restrictions": "Beperkingen verwijderen",
|
||||
"Repair PDF": "PDF repareren",
|
||||
"Reset search": "Zoekopdracht wissen",
|
||||
"Return to top": "Terug naar boven",
|
||||
"Reverse Pages": "Paginavolgorde omkeren",
|
||||
"Rotate by Custom Degrees": "Roteren met aangepaste hoek",
|
||||
"Rotate PDF": "PDF roteren",
|
||||
"RTF to PDF": "RTF naar PDF",
|
||||
"Sanitize PDF": "PDF opschonen",
|
||||
"Scanner Effect": "Scannereffect",
|
||||
"Search": "Zoeken",
|
||||
"Secure PDF": "PDF Beveiligen",
|
||||
"Self-Hosting": "Zelf hosten",
|
||||
"Self-Hosting Guide": "Gids voor zelf hosten",
|
||||
"Sign PDF": "PDF ondertekenen",
|
||||
"Skip to content": "Naar inhoud springen",
|
||||
"Split PDF": "PDF splitsen",
|
||||
"SVG to PDF": "SVG naar PDF",
|
||||
"Switch to dark theme": "Overschakelen naar donker thema",
|
||||
"Switch to light theme": "Overschakelen naar licht thema",
|
||||
"Table of Contents": "Inhoudsopgave",
|
||||
"Text to PDF": "Tekst naar PDF",
|
||||
"TIFF to PDF": "TIFF naar PDF",
|
||||
"to close": "om te sluiten",
|
||||
"to navigate": "om te navigeren",
|
||||
"to select": "om te selecteren",
|
||||
"Tools": "Tools",
|
||||
"Tools Reference": "Toolsoverzicht",
|
||||
"Unlock PDF": "PDF ontgrendelen",
|
||||
"Validate Signature": "PDF-handtekening Valideren",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Metadata weergeven",
|
||||
"VSD to PDF": "VSD naar PDF",
|
||||
"WebP to PDF": "WebP naar PDF",
|
||||
"Word to PDF": "Word naar PDF",
|
||||
"WPD to PDF": "WPD naar PDF",
|
||||
"WPS to PDF": "WPS naar PDF",
|
||||
"XML to PDF": "XML naar PDF",
|
||||
"XPS to PDF": "XPS naar PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Dodaj załączniki",
|
||||
"Add Blank Page": "Dodaj pustą stronę",
|
||||
"Add Page Labels": "Dodaj etykiety stron",
|
||||
"Add Stamps": "Dodaj pieczątki",
|
||||
"Add Watermark": "Dodaj znak wodny",
|
||||
"Adjust Colors": "Dostosuj kolory",
|
||||
"Alternate & Mix Pages": "Naprzemienne i mieszane strony",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Wygląd",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Kolor tła",
|
||||
"Bates Numbering": "Numeracja Batesa",
|
||||
"BMP to PDF": "BMP na PDF",
|
||||
"CBZ to PDF": "CBZ na PDF",
|
||||
"Change language": "Zmień język",
|
||||
"Change Permissions": "Zmień uprawnienia",
|
||||
"Change Text Color": "Zmień kolor tekstu",
|
||||
"Close search": "Zamknij wyszukiwanie",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Połącz w jedną stronę",
|
||||
"Commercial License": "Licencja komercyjna",
|
||||
"Compare PDFs": "Porównaj PDF",
|
||||
"Compress PDF": "Kompresuj PDF",
|
||||
"Contributing": "Współtworzenie",
|
||||
"Convert from PDF": "Konwertuj z PDF",
|
||||
"Convert to PDF": "Konwertuj do PDF",
|
||||
"Create PDF Form": "Utwórz formularz PDF",
|
||||
"Crop PDF": "Przytnij PDF",
|
||||
"CSV to PDF": "CSV na PDF",
|
||||
"Delete Pages": "Usuń strony",
|
||||
"Deskew PDF": "Wyprostuj PDF",
|
||||
"Digital Signature": "Podpis cyfrowy",
|
||||
"Display detailed list": "Wyświetl szczegółową listę",
|
||||
"Divide Pages": "Podziel strony",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Dokumentacja BentoPDF: darmowy zestaw narzędzi PDF o otwartym kodzie źródłowym, stworzony z myślą o prywatności",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Podwójna licencja: AGPL-3.0 i licencja komercyjna.",
|
||||
"Duplex Collate": "Sortowanie skanu dwustronnego",
|
||||
"Edit & Annotate": "Edycja i adnotacje",
|
||||
"Edit Attachments": "Edytuj załączniki",
|
||||
"Edit Bookmarks": "Edytuj zakładki",
|
||||
"Edit Metadata": "Edytuj metadane",
|
||||
"Email to PDF": "E-mail na PDF",
|
||||
"EPUB to PDF": "EPUB na PDF",
|
||||
"Excel to PDF": "Excel na PDF",
|
||||
"Extract Attachments": "Wyodrębnij załączniki",
|
||||
"Extract Images": "Wyodrębnij obrazy",
|
||||
"Extract Pages": "Wyodrębnij strony",
|
||||
"Extract Tables": "Wyodrębnij tabele",
|
||||
"FB2 to PDF": "FB2 na PDF",
|
||||
"Fix Page Size": "Napraw rozmiar strony",
|
||||
"Flatten PDF": "Spłaszcz PDF",
|
||||
"Font to Outline": "Czcionka na krzywe",
|
||||
"Getting Started": "Pierwsze kroki",
|
||||
"Guide": "Przewodnik",
|
||||
"Header & Footer": "Nagłówek i stopka",
|
||||
"HEIC to PDF": "HEIC na PDF",
|
||||
"Home": "Strona główna",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Obrazy do PDF",
|
||||
"Invert Colors": "Odwróć kolory",
|
||||
"JPG to PDF": "JPG na PDF",
|
||||
"JSON to PDF": "JSON na PDF",
|
||||
"Linearize PDF": "Linearyzuj PDF",
|
||||
"Markdown to PDF": "Markdown na PDF",
|
||||
"Menu": "Menu",
|
||||
"Merge PDF": "Połącz PDF",
|
||||
"MOBI to PDF": "MOBI na PDF",
|
||||
"N-Up PDF": "PDF N-Up",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Następna strona",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Brak wyników dla",
|
||||
"OCR PDF": "OCR PDF",
|
||||
"ODG to PDF": "ODG na PDF",
|
||||
"ODP to PDF": "ODP na PDF",
|
||||
"ODS to PDF": "ODS na PDF",
|
||||
"ODT to PDF": "ODT na PDF",
|
||||
"On this page": "Na tej stronie",
|
||||
"Optimize & Repair": "Optymalizacja i naprawa",
|
||||
"Organize & Duplicate": "Organizacja i duplikowanie",
|
||||
"Organize & Manage": "Organizacja i zarządzanie",
|
||||
"Overview": "Przegląd",
|
||||
"Page Dimensions": "Wymiary strony",
|
||||
"Page Numbers": "Numery stron",
|
||||
"PDF Booklet": "Broszura PDF",
|
||||
"PDF Editor": "Edytor PDF",
|
||||
"PDF Form Filler": "Wypełnianie formularzy PDF",
|
||||
"PDF Multi Tool": "Wielofunkcyjne narzędzie PDF",
|
||||
"PDF OCG": "Warstwy PDF (OCG)",
|
||||
"PDF to BMP": "PDF na BMP",
|
||||
"PDF to CBZ": "PDF na CBZ",
|
||||
"PDF to CSV": "PDF na CSV",
|
||||
"PDF to Excel": "PDF na Excel",
|
||||
"PDF to Greyscale": "PDF na odcienie szarości",
|
||||
"PDF to JPG": "PDF na JPG",
|
||||
"PDF to JSON": "PDF na JSON",
|
||||
"PDF to Markdown": "PDF na Markdown",
|
||||
"PDF to PDF/A": "PDF na PDF/A",
|
||||
"PDF to PNG": "PDF na PNG",
|
||||
"PDF to SVG": "PDF na SVG",
|
||||
"PDF to Text": "PDF na tekst",
|
||||
"PDF to TIFF": "PDF na TIFF",
|
||||
"PDF to WebP": "PDF na WebP",
|
||||
"PDF to Word": "PDF na Word",
|
||||
"PDF Workflow Builder": "Kreator przepływów PDF",
|
||||
"PDFs to ZIP": "PDF na ZIP",
|
||||
"PNG to PDF": "PNG na PDF",
|
||||
"Posterize PDF": "Plakatowanie PDF",
|
||||
"PowerPoint to PDF": "PowerPoint na PDF",
|
||||
"Prepare PDF for AI": "Przygotuj PDF dla AI",
|
||||
"Previous page": "Poprzednia strona",
|
||||
"Protect PDF": "Zabezpiecz PDF",
|
||||
"PSD to PDF": "PSD na PDF",
|
||||
"PUB to PDF": "PUB na PDF",
|
||||
"Rasterize PDF": "Rasteryzuj PDF",
|
||||
"Remove Annotations": "Usuń adnotacje",
|
||||
"Remove Blank Pages": "Usuń puste strony",
|
||||
"Remove Metadata": "Usuń metadane",
|
||||
"Remove Restrictions": "Usuń restrykcje",
|
||||
"Repair PDF": "Napraw PDF",
|
||||
"Reset search": "Wyczyść wyszukiwanie",
|
||||
"Return to top": "Wróć na górę",
|
||||
"Reverse Pages": "Odwróć strony",
|
||||
"Rotate by Custom Degrees": "Obróć o niestandardowy kąt",
|
||||
"Rotate PDF": "Obróć PDF",
|
||||
"RTF to PDF": "RTF na PDF",
|
||||
"Sanitize PDF": "Sanityzuj PDF",
|
||||
"Scanner Effect": "Efekt skanera",
|
||||
"Search": "Szukaj",
|
||||
"Secure PDF": "Zabezpiecz PDF",
|
||||
"Self-Hosting": "Własny hosting",
|
||||
"Self-Hosting Guide": "Przewodnik po własnym hostingu",
|
||||
"Sign PDF": "Podpisz PDF",
|
||||
"Skip to content": "Przejdź do treści",
|
||||
"Split PDF": "Podziel PDF",
|
||||
"SVG to PDF": "SVG na PDF",
|
||||
"Switch to dark theme": "Przełącz na ciemny motyw",
|
||||
"Switch to light theme": "Przełącz na jasny motyw",
|
||||
"Table of Contents": "Spis treści",
|
||||
"Text to PDF": "Tekst na PDF",
|
||||
"TIFF to PDF": "TIFF na PDF",
|
||||
"to close": "aby zamknąć",
|
||||
"to navigate": "aby nawigować",
|
||||
"to select": "aby wybrać",
|
||||
"Tools": "Narzędzia",
|
||||
"Tools Reference": "Opis narzędzi",
|
||||
"Unlock PDF": "Odblokuj PDF",
|
||||
"Validate Signature": "Zweryfikuj podpis",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Wyświetl metadane",
|
||||
"VSD to PDF": "VSD na PDF",
|
||||
"WebP to PDF": "WebP na PDF",
|
||||
"Word to PDF": "Word na PDF",
|
||||
"WPD to PDF": "WPD na PDF",
|
||||
"WPS to PDF": "WPS na PDF",
|
||||
"XML to PDF": "XML na PDF",
|
||||
"XPS to PDF": "XPS na PDF"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"Add Attachments": "Adicionar anexos",
|
||||
"Add Blank Page": "Adicionar página em branco",
|
||||
"Add Page Labels": "Adicionar rótulos de página",
|
||||
"Add Stamps": "Adicionar carimbos",
|
||||
"Add Watermark": "Adicionar marca d'água",
|
||||
"Adjust Colors": "Ajustar cores",
|
||||
"Alternate & Mix Pages": "Alternar e misturar páginas",
|
||||
"Apache": "Apache",
|
||||
"Appearance": "Aparência",
|
||||
"AWS S3 + CloudFront": "AWS S3 + CloudFront",
|
||||
"Background Color": "Cor de fundo",
|
||||
"Bates Numbering": "Numeração Bates",
|
||||
"BMP to PDF": "BMP para PDF",
|
||||
"CBZ to PDF": "CBZ para PDF",
|
||||
"Change language": "Mudar idioma",
|
||||
"Change Permissions": "Alterar permissões",
|
||||
"Change Text Color": "Alterar cor do texto",
|
||||
"Close search": "Fechar busca",
|
||||
"Cloudflare Pages": "Cloudflare Pages",
|
||||
"Combine to Single Page": "Combinar em página única",
|
||||
"Commercial License": "Licença comercial",
|
||||
"Compare PDFs": "Comparar PDFs",
|
||||
"Compress PDF": "Comprimir PDF",
|
||||
"Contributing": "Contribuir",
|
||||
"Convert from PDF": "Converter de PDF",
|
||||
"Convert to PDF": "Converter para PDF",
|
||||
"Create PDF Form": "Criar formulário PDF",
|
||||
"Crop PDF": "Cortar PDF",
|
||||
"CSV to PDF": "CSV para PDF",
|
||||
"Delete Pages": "Excluir páginas",
|
||||
"Deskew PDF": "Desinclinar PDF",
|
||||
"Digital Signature": "Assinatura Digital",
|
||||
"Display detailed list": "Exibir lista detalhada",
|
||||
"Divide Pages": "Dividir páginas",
|
||||
"Docker": "Docker",
|
||||
"Documentation for BentoPDF - The free, open-source, privacy-first PDF toolkit": "Documentação do BentoPDF: o kit de ferramentas PDF gratuito, de código aberto e focado em privacidade",
|
||||
"Dual-licensed under AGPL-3.0 and Commercial License.": "Licenciado em dupla licença: AGPL-3.0 e Licença Comercial.",
|
||||
"Duplex Collate": "Intercalar digitalização frente e verso",
|
||||
"Edit & Annotate": "Editar e Anotar",
|
||||
"Edit Attachments": "Editar anexos",
|
||||
"Edit Bookmarks": "Editar marcadores",
|
||||
"Edit Metadata": "Editar metadados",
|
||||
"Email to PDF": "E-mail para PDF",
|
||||
"EPUB to PDF": "EPUB para PDF",
|
||||
"Excel to PDF": "Excel para PDF",
|
||||
"Extract Attachments": "Extrair anexos",
|
||||
"Extract Images": "Extrair imagens",
|
||||
"Extract Pages": "Extrair páginas",
|
||||
"Extract Tables": "Extrair Tabelas",
|
||||
"FB2 to PDF": "FB2 para PDF",
|
||||
"Fix Page Size": "Ajustar tamanho da página",
|
||||
"Flatten PDF": "Achatar PDF",
|
||||
"Font to Outline": "Fonte para contorno",
|
||||
"Getting Started": "Primeiros Passos",
|
||||
"Guide": "Guia",
|
||||
"Header & Footer": "Cabeçalho e rodapé",
|
||||
"HEIC to PDF": "HEIC para PDF",
|
||||
"Home": "Início",
|
||||
"Hostinger": "Hostinger",
|
||||
"Images to PDF": "Imagens para PDF",
|
||||
"Invert Colors": "Inverter cores",
|
||||
"JPG to PDF": "JPG para PDF",
|
||||
"JSON to PDF": "JSON para PDF",
|
||||
"Linearize PDF": "Linearizar PDF",
|
||||
"Markdown to PDF": "Markdown para PDF",
|
||||
"Menu": "Menu",
|
||||
"Merge PDF": "Mesclar PDF",
|
||||
"MOBI to PDF": "MOBI para PDF",
|
||||
"N-Up PDF": "PDF N-Up",
|
||||
"Netlify": "Netlify",
|
||||
"Next page": "Próxima página",
|
||||
"Nginx": "Nginx",
|
||||
"No results for": "Nenhum resultado para",
|
||||
"OCR PDF": "OCR em PDF",
|
||||
"ODG to PDF": "ODG para PDF",
|
||||
"ODP to PDF": "ODP para PDF",
|
||||
"ODS to PDF": "ODS para PDF",
|
||||
"ODT to PDF": "ODT para PDF",
|
||||
"On this page": "Nesta página",
|
||||
"Optimize & Repair": "Otimizar e Reparar",
|
||||
"Organize & Duplicate": "Duplicar e Organizar",
|
||||
"Organize & Manage": "Organizar e Gerenciar",
|
||||
"Overview": "Visão geral",
|
||||
"Page Dimensions": "Dimensões da página",
|
||||
"Page Numbers": "Números de página",
|
||||
"PDF Booklet": "Livreto PDF",
|
||||
"PDF Editor": "Editor de PDF",
|
||||
"PDF Form Filler": "Preenchimento de formulário PDF",
|
||||
"PDF Multi Tool": "Multiferramenta PDF",
|
||||
"PDF OCG": "Camadas de PDF (OCG)",
|
||||
"PDF to BMP": "PDF para BMP",
|
||||
"PDF to CBZ": "PDF para CBZ",
|
||||
"PDF to CSV": "PDF para CSV",
|
||||
"PDF to Excel": "PDF para Excel",
|
||||
"PDF to Greyscale": "PDF para tons de cinza",
|
||||
"PDF to JPG": "PDF para JPG",
|
||||
"PDF to JSON": "PDF para JSON",
|
||||
"PDF to Markdown": "PDF para Markdown",
|
||||
"PDF to PDF/A": "PDF para PDF/A",
|
||||
"PDF to PNG": "PDF para PNG",
|
||||
"PDF to SVG": "PDF para SVG",
|
||||
"PDF to Text": "PDF para texto",
|
||||
"PDF to TIFF": "PDF para TIFF",
|
||||
"PDF to WebP": "PDF para WebP",
|
||||
"PDF to Word": "PDF para Word",
|
||||
"PDF Workflow Builder": "Construtor de fluxo de trabalho PDF",
|
||||
"PDFs to ZIP": "PDFs para ZIP",
|
||||
"PNG to PDF": "PNG para PDF",
|
||||
"Posterize PDF": "Posterizar PDF",
|
||||
"PowerPoint to PDF": "PowerPoint para PDF",
|
||||
"Prepare PDF for AI": "Preparar PDF para IA",
|
||||
"Previous page": "Página anterior",
|
||||
"Protect PDF": "Proteger PDF",
|
||||
"PSD to PDF": "PSD para PDF",
|
||||
"PUB to PDF": "PUB para PDF",
|
||||
"Rasterize PDF": "Rasterizar PDF",
|
||||
"Remove Annotations": "Remover anotações",
|
||||
"Remove Blank Pages": "Remover páginas em branco",
|
||||
"Remove Metadata": "Remover metadados",
|
||||
"Remove Restrictions": "Remover restrições",
|
||||
"Repair PDF": "Reparar PDF",
|
||||
"Reset search": "Limpar busca",
|
||||
"Return to top": "Voltar ao topo",
|
||||
"Reverse Pages": "Inverter ordem das páginas",
|
||||
"Rotate by Custom Degrees": "Girar em graus personalizados",
|
||||
"Rotate PDF": "Girar PDF",
|
||||
"RTF to PDF": "RTF para PDF",
|
||||
"Sanitize PDF": "Sanitizar PDF",
|
||||
"Scanner Effect": "Efeito scanner",
|
||||
"Search": "Buscar",
|
||||
"Secure PDF": "Proteger PDF",
|
||||
"Self-Hosting": "Hospedagem Própria",
|
||||
"Self-Hosting Guide": "Guia de Hospedagem Própria",
|
||||
"Sign PDF": "Assinar PDF",
|
||||
"Skip to content": "Pular para o conteúdo",
|
||||
"Split PDF": "Dividir PDF",
|
||||
"SVG to PDF": "SVG para PDF",
|
||||
"Switch to dark theme": "Mudar para o tema escuro",
|
||||
"Switch to light theme": "Mudar para o tema claro",
|
||||
"Table of Contents": "Sumário",
|
||||
"Text to PDF": "Texto para PDF",
|
||||
"TIFF to PDF": "TIFF para PDF",
|
||||
"to close": "para fechar",
|
||||
"to navigate": "para navegar",
|
||||
"to select": "para selecionar",
|
||||
"Tools": "Ferramentas",
|
||||
"Tools Reference": "Referência de Ferramentas",
|
||||
"Unlock PDF": "Desbloquear PDF",
|
||||
"Validate Signature": "Validar Assinatura",
|
||||
"Vercel": "Vercel",
|
||||
"View Metadata": "Ver metadados",
|
||||
"VSD to PDF": "VSD para PDF",
|
||||
"WebP to PDF": "WebP para PDF",
|
||||
"Word to PDF": "Word para PDF",
|
||||
"WPD to PDF": "WPD para PDF",
|
||||
"WPS to PDF": "WPS para PDF",
|
||||
"XML to PDF": "XML para PDF",
|
||||
"XPS to PDF": "XPS para PDF"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user