@kitsy/pdf 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +16 -0
  4. package/README.md +229 -2
  5. package/THIRD_PARTY_NOTICES.md +48 -0
  6. package/dist/chunk-2K5X7L5J.js +43 -0
  7. package/dist/chunk-2K5X7L5J.js.map +1 -0
  8. package/dist/chunk-4EAYBJ6G.js +362 -0
  9. package/dist/chunk-4EAYBJ6G.js.map +1 -0
  10. package/dist/chunk-4LBJTDV5.js +165 -0
  11. package/dist/chunk-4LBJTDV5.js.map +1 -0
  12. package/dist/chunk-4USCGC24.js +304 -0
  13. package/dist/chunk-4USCGC24.js.map +1 -0
  14. package/dist/chunk-5OIMR74G.js +273 -0
  15. package/dist/chunk-5OIMR74G.js.map +1 -0
  16. package/dist/chunk-6NFNAKWA.js +257 -0
  17. package/dist/chunk-6NFNAKWA.js.map +1 -0
  18. package/dist/chunk-7MTEMQLL.js +47 -0
  19. package/dist/chunk-7MTEMQLL.js.map +1 -0
  20. package/dist/chunk-7V4SWTUK.js +83 -0
  21. package/dist/chunk-7V4SWTUK.js.map +1 -0
  22. package/dist/chunk-7XH7MFZ4.js +295 -0
  23. package/dist/chunk-7XH7MFZ4.js.map +1 -0
  24. package/dist/chunk-BK3MYSUS.js +61 -0
  25. package/dist/chunk-BK3MYSUS.js.map +1 -0
  26. package/dist/chunk-BLHNKR3S.js +35 -0
  27. package/dist/chunk-BLHNKR3S.js.map +1 -0
  28. package/dist/chunk-CCJ43SIX.js +342 -0
  29. package/dist/chunk-CCJ43SIX.js.map +1 -0
  30. package/dist/chunk-CNEDWATL.js +210 -0
  31. package/dist/chunk-CNEDWATL.js.map +1 -0
  32. package/dist/chunk-CYTWDJE2.js +152 -0
  33. package/dist/chunk-CYTWDJE2.js.map +1 -0
  34. package/dist/chunk-DKVOICYZ.js +45 -0
  35. package/dist/chunk-DKVOICYZ.js.map +1 -0
  36. package/dist/chunk-DPEJU4P6.js +79 -0
  37. package/dist/chunk-DPEJU4P6.js.map +1 -0
  38. package/dist/chunk-DQPABZTU.js +22 -0
  39. package/dist/chunk-DQPABZTU.js.map +1 -0
  40. package/dist/chunk-EKHEY3LI.js +46 -0
  41. package/dist/chunk-EKHEY3LI.js.map +1 -0
  42. package/dist/chunk-G467LFKC.js +191 -0
  43. package/dist/chunk-G467LFKC.js.map +1 -0
  44. package/dist/chunk-GLYWSVSB.js +250 -0
  45. package/dist/chunk-GLYWSVSB.js.map +1 -0
  46. package/dist/chunk-GXBIMUO7.js +135 -0
  47. package/dist/chunk-GXBIMUO7.js.map +1 -0
  48. package/dist/chunk-H6EERRCR.js +180 -0
  49. package/dist/chunk-H6EERRCR.js.map +1 -0
  50. package/dist/chunk-HDTM3LXD.js +35 -0
  51. package/dist/chunk-HDTM3LXD.js.map +1 -0
  52. package/dist/chunk-IGMX56C3.js +180 -0
  53. package/dist/chunk-IGMX56C3.js.map +1 -0
  54. package/dist/chunk-J4IRICCJ.js +74 -0
  55. package/dist/chunk-J4IRICCJ.js.map +1 -0
  56. package/dist/chunk-JXKQ7S6W.js +331 -0
  57. package/dist/chunk-JXKQ7S6W.js.map +1 -0
  58. package/dist/chunk-KY6DXS6S.js +34 -0
  59. package/dist/chunk-KY6DXS6S.js.map +1 -0
  60. package/dist/chunk-NOFGCYGS.js +22 -0
  61. package/dist/chunk-NOFGCYGS.js.map +1 -0
  62. package/dist/chunk-OSHUJRKD.js +131 -0
  63. package/dist/chunk-OSHUJRKD.js.map +1 -0
  64. package/dist/chunk-PMH7OWPF.js +40 -0
  65. package/dist/chunk-PMH7OWPF.js.map +1 -0
  66. package/dist/chunk-PRBKSX3A.js +299 -0
  67. package/dist/chunk-PRBKSX3A.js.map +1 -0
  68. package/dist/chunk-SVPUX7AM.js +48 -0
  69. package/dist/chunk-SVPUX7AM.js.map +1 -0
  70. package/dist/chunk-TR6HJ4NO.js +659 -0
  71. package/dist/chunk-TR6HJ4NO.js.map +1 -0
  72. package/dist/chunk-TV3E4YUF.js +69 -0
  73. package/dist/chunk-TV3E4YUF.js.map +1 -0
  74. package/dist/chunk-U2ROTOPF.js +256 -0
  75. package/dist/chunk-U2ROTOPF.js.map +1 -0
  76. package/dist/chunk-VCTN77K2.js +112 -0
  77. package/dist/chunk-VCTN77K2.js.map +1 -0
  78. package/dist/chunk-WEOPDD3P.js +120 -0
  79. package/dist/chunk-WEOPDD3P.js.map +1 -0
  80. package/dist/chunk-ZBZ7OC6I.js +228 -0
  81. package/dist/chunk-ZBZ7OC6I.js.map +1 -0
  82. package/dist/config-CJYo46X9.d.ts +19 -0
  83. package/dist/index.d.ts +487 -0
  84. package/dist/index.js +23 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/react/certificate-sign.d.ts +5 -0
  87. package/dist/react/certificate-sign.js +14 -0
  88. package/dist/react/certificate-sign.js.map +1 -0
  89. package/dist/react/compress.d.ts +5 -0
  90. package/dist/react/compress.js +8 -0
  91. package/dist/react/compress.js.map +1 -0
  92. package/dist/react/fill-form.d.ts +5 -0
  93. package/dist/react/fill-form.js +8 -0
  94. package/dist/react/fill-form.js.map +1 -0
  95. package/dist/react/images-to-pdf.d.ts +5 -0
  96. package/dist/react/images-to-pdf.js +7 -0
  97. package/dist/react/images-to-pdf.js.map +1 -0
  98. package/dist/react/merge.d.ts +5 -0
  99. package/dist/react/merge.js +8 -0
  100. package/dist/react/merge.js.map +1 -0
  101. package/dist/react/metadata.d.ts +5 -0
  102. package/dist/react/metadata.js +8 -0
  103. package/dist/react/metadata.js.map +1 -0
  104. package/dist/react/organize.d.ts +5 -0
  105. package/dist/react/organize.js +12 -0
  106. package/dist/react/organize.js.map +1 -0
  107. package/dist/react/pdf-to-images.d.ts +5 -0
  108. package/dist/react/pdf-to-images.js +13 -0
  109. package/dist/react/pdf-to-images.js.map +1 -0
  110. package/dist/react/protect.d.ts +5 -0
  111. package/dist/react/protect.js +8 -0
  112. package/dist/react/protect.js.map +1 -0
  113. package/dist/react/sign.d.ts +5 -0
  114. package/dist/react/sign.js +13 -0
  115. package/dist/react/sign.js.map +1 -0
  116. package/dist/react/split.d.ts +5 -0
  117. package/dist/react/split.js +11 -0
  118. package/dist/react/split.js.map +1 -0
  119. package/dist/react/verify-signatures.d.ts +5 -0
  120. package/dist/react/verify-signatures.js +8 -0
  121. package/dist/react/verify-signatures.js.map +1 -0
  122. package/dist/react/watermark.d.ts +5 -0
  123. package/dist/react/watermark.js +10 -0
  124. package/dist/react/watermark.js.map +1 -0
  125. package/dist/react.d.ts +40 -0
  126. package/dist/react.js +40 -0
  127. package/dist/react.js.map +1 -0
  128. package/dist/styles.css +2 -0
  129. package/dist/vite.d.ts +5 -0
  130. package/dist/vite.js +9 -0
  131. package/dist/vite.js.map +1 -0
  132. package/package.json +163 -4
@@ -0,0 +1,331 @@
1
+ import { mergePdfs } from './chunk-HDTM3LXD.js';
2
+ import { PdfToolPage, PdfToolColumns, SummaryList, SideTips, FileDropzone, iconButtonClass, inputClass, fieldLabelClass, StatusBanner, primaryButtonClass, secondaryButtonClass, BackToPdfTools } from './chunk-H6EERRCR.js';
3
+ import { formatBytes, isPdfFile, toBaseName, downloadBlob, getPdfPageCount } from './chunk-EKHEY3LI.js';
4
+ import { useState, useEffect, useMemo } from 'react';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+
7
+ var MAX_FILES = 20;
8
+ var MAX_TOTAL_BYTES = 200 * 1024 * 1024;
9
+ var nextEntryId = 0;
10
+ function MergePdf() {
11
+ const [entries, setEntries] = useState([]);
12
+ const [outputName, setOutputName] = useState("merged");
13
+ const [isMerging, setIsMerging] = useState(false);
14
+ const [status, setStatus] = useState({
15
+ tone: "info",
16
+ text: "Add PDF files, arrange them in order, and merge into one document."
17
+ });
18
+ useEffect(() => {
19
+ document.title = "Merge PDF | Apps";
20
+ }, []);
21
+ const totalBytes = useMemo(
22
+ () => entries.reduce((sum, entry) => sum + entry.file.size, 0),
23
+ [entries]
24
+ );
25
+ const totalPages = useMemo(
26
+ () => entries.reduce((sum, entry) => sum + (entry.pageCount || 0), 0),
27
+ [entries]
28
+ );
29
+ const readyEntries = useMemo(
30
+ () => entries.filter((entry) => entry.state === "ready"),
31
+ [entries]
32
+ );
33
+ const updateEntry = (id, patch) => {
34
+ setEntries(
35
+ (current) => current.map((entry) => entry.id === id ? { ...entry, ...patch } : entry)
36
+ );
37
+ };
38
+ const inspectEntry = async (entry) => {
39
+ try {
40
+ const bytes = await entry.file.arrayBuffer();
41
+ const pageCount = await getPdfPageCount(bytes);
42
+ updateEntry(entry.id, { bytes, pageCount, state: "ready", error: "" });
43
+ } catch (error) {
44
+ updateEntry(entry.id, {
45
+ state: "error",
46
+ error: error?.message || "Could not read this PDF."
47
+ });
48
+ }
49
+ };
50
+ const addFiles = (nextFiles) => {
51
+ if (!nextFiles.length) return;
52
+ const pdfOnly = nextFiles.filter(isPdfFile);
53
+ if (!pdfOnly.length) {
54
+ setStatus({ tone: "error", text: "Only PDF files are supported." });
55
+ return;
56
+ }
57
+ if (entries.length + pdfOnly.length > MAX_FILES) {
58
+ setStatus({
59
+ tone: "error",
60
+ text: `Please merge up to ${MAX_FILES} PDF files at a time.`
61
+ });
62
+ return;
63
+ }
64
+ const incomingBytes = pdfOnly.reduce((sum, file) => sum + file.size, 0);
65
+ if (totalBytes + incomingBytes > MAX_TOTAL_BYTES) {
66
+ setStatus({
67
+ tone: "error",
68
+ text: `Total size must stay under ${formatBytes(MAX_TOTAL_BYTES)}.`
69
+ });
70
+ return;
71
+ }
72
+ const newEntries = pdfOnly.map((file) => ({
73
+ id: `pdf-${++nextEntryId}`,
74
+ file,
75
+ bytes: null,
76
+ pageCount: 0,
77
+ pages: "",
78
+ state: "loading",
79
+ error: ""
80
+ }));
81
+ setEntries((current) => [...current, ...newEntries]);
82
+ newEntries.forEach(inspectEntry);
83
+ const skipped = nextFiles.length - pdfOnly.length;
84
+ setStatus({
85
+ tone: "success",
86
+ text: `${pdfOnly.length} PDF file${pdfOnly.length > 1 ? "s" : ""} added.${skipped ? ` ${skipped} non-PDF file${skipped > 1 ? "s" : ""} skipped.` : ""}`
87
+ });
88
+ };
89
+ const moveEntry = (index, offset) => {
90
+ setEntries((current) => {
91
+ const target = index + offset;
92
+ if (target < 0 || target >= current.length) return current;
93
+ const next = [...current];
94
+ [next[index], next[target]] = [next[target], next[index]];
95
+ return next;
96
+ });
97
+ };
98
+ const removeEntry = (id) => {
99
+ setEntries((current) => current.filter((entry) => entry.id !== id));
100
+ };
101
+ const clearSelection = () => {
102
+ setEntries([]);
103
+ setStatus({
104
+ tone: "info",
105
+ text: "Selection cleared. Add PDF files to continue."
106
+ });
107
+ };
108
+ const handleMerge = async (event) => {
109
+ event.preventDefault();
110
+ if (!readyEntries.length) {
111
+ setStatus({
112
+ tone: "error",
113
+ text: "Please add at least one readable PDF before merging."
114
+ });
115
+ return;
116
+ }
117
+ if (entries.some((entry) => entry.state === "loading")) {
118
+ setStatus({ tone: "info", text: "Still reading files. Please try again in a moment." });
119
+ return;
120
+ }
121
+ setIsMerging(true);
122
+ setStatus({ tone: "info", text: "Merging your PDF files..." });
123
+ try {
124
+ const baseName = toBaseName(outputName, "merged");
125
+ const mergedBytes = await mergePdfs(
126
+ readyEntries.map((entry) => ({
127
+ name: entry.file.name,
128
+ bytes: entry.bytes,
129
+ pages: entry.pages
130
+ })),
131
+ { title: baseName }
132
+ );
133
+ const blob = new Blob([mergedBytes], { type: "application/pdf" });
134
+ downloadBlob(blob, `${baseName}.pdf`);
135
+ const skipped = entries.length - readyEntries.length;
136
+ setStatus({
137
+ tone: "success",
138
+ text: `Merged ${readyEntries.length} file${readyEntries.length > 1 ? "s" : ""} (${formatBytes(
139
+ blob.size
140
+ )}). Your download has started.${skipped ? ` ${skipped} unreadable file${skipped > 1 ? "s were" : " was"} skipped.` : ""}`
141
+ });
142
+ } catch (error) {
143
+ setStatus({
144
+ tone: "error",
145
+ text: `Merge failed: ${error?.message || "Please try again."}`
146
+ });
147
+ } finally {
148
+ setIsMerging(false);
149
+ }
150
+ };
151
+ return /* @__PURE__ */ jsx(
152
+ PdfToolPage,
153
+ {
154
+ title: "Merge PDF",
155
+ description: "Combine multiple PDFs into one file. Reorder files, pick page ranges, and download. Everything runs in your browser; files never leave your device.",
156
+ stats: [
157
+ { value: MAX_FILES, label: "Max Files" },
158
+ { value: "PDF", label: "Output" }
159
+ ],
160
+ children: /* @__PURE__ */ jsx(
161
+ PdfToolColumns,
162
+ {
163
+ main: /* @__PURE__ */ jsxs("form", { onSubmit: handleMerge, children: [
164
+ /* @__PURE__ */ jsx(
165
+ FileDropzone,
166
+ {
167
+ accept: ".pdf,application/pdf",
168
+ multiple: true,
169
+ disabled: isMerging,
170
+ onFiles: addFiles,
171
+ title: "Drag and drop PDF files here",
172
+ hint: "or click to browse. Add more any time; they are appended to the list."
173
+ }
174
+ ),
175
+ entries.length ? /* @__PURE__ */ jsx("ol", { className: "mt-5 space-y-3", children: entries.map((entry, index) => /* @__PURE__ */ jsxs(
176
+ "li",
177
+ {
178
+ className: `rounded-2xl border p-3 sm:p-4 ${entry.state === "error" ? "border-rose-200 bg-rose-50/70 dark:border-rose-500/30 dark:bg-rose-500/10" : "border-slate-200 bg-white/90 dark:border-slate-700 dark:bg-slate-900/80"}`,
179
+ children: [
180
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
181
+ /* @__PURE__ */ jsx("span", { className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-xs font-semibold text-slate-600 dark:bg-slate-800 dark:text-slate-300", children: index + 1 }),
182
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
183
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-slate-900 dark:text-slate-100", children: entry.file.name }),
184
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-slate-500 dark:text-slate-400", children: [
185
+ formatBytes(entry.file.size),
186
+ entry.state === "ready" ? ` \xB7 ${entry.pageCount} page${entry.pageCount === 1 ? "" : "s"}` : null,
187
+ entry.state === "loading" ? " \xB7 reading..." : null
188
+ ] })
189
+ ] }),
190
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
191
+ /* @__PURE__ */ jsx(
192
+ "button",
193
+ {
194
+ type: "button",
195
+ onClick: () => moveEntry(index, -1),
196
+ disabled: isMerging || index === 0,
197
+ className: iconButtonClass,
198
+ "aria-label": `Move ${entry.file.name} up`,
199
+ children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-up" })
200
+ }
201
+ ),
202
+ /* @__PURE__ */ jsx(
203
+ "button",
204
+ {
205
+ type: "button",
206
+ onClick: () => moveEntry(index, 1),
207
+ disabled: isMerging || index === entries.length - 1,
208
+ className: iconButtonClass,
209
+ "aria-label": `Move ${entry.file.name} down`,
210
+ children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-down" })
211
+ }
212
+ ),
213
+ /* @__PURE__ */ jsx(
214
+ "button",
215
+ {
216
+ type: "button",
217
+ onClick: () => removeEntry(entry.id),
218
+ disabled: isMerging,
219
+ className: iconButtonClass,
220
+ "aria-label": `Remove ${entry.file.name}`,
221
+ children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-xmark" })
222
+ }
223
+ )
224
+ ] })
225
+ ] }),
226
+ entry.state === "error" ? /* @__PURE__ */ jsxs("p", { className: "mt-2 text-xs text-rose-700 dark:text-rose-300", children: [
227
+ entry.error,
228
+ " This file will be skipped."
229
+ ] }) : null,
230
+ entry.state === "ready" && entry.pageCount > 1 ? /* @__PURE__ */ jsxs("label", { className: "mt-3 flex flex-wrap items-center gap-2 text-xs text-slate-600 dark:text-slate-300", children: [
231
+ /* @__PURE__ */ jsx("span", { className: "font-semibold uppercase tracking-[0.12em]", children: "Pages" }),
232
+ /* @__PURE__ */ jsx(
233
+ "input",
234
+ {
235
+ type: "text",
236
+ value: entry.pages,
237
+ onChange: (event) => updateEntry(entry.id, { pages: event.target.value }),
238
+ disabled: isMerging,
239
+ placeholder: `All pages (e.g. 1-${entry.pageCount}, or ${entry.pageCount}-1 to reverse)`,
240
+ className: `flex-1 ${inputClass}`
241
+ }
242
+ )
243
+ ] }) : null
244
+ ]
245
+ },
246
+ entry.id
247
+ )) }) : null,
248
+ /* @__PURE__ */ jsxs("label", { className: `mt-5 ${fieldLabelClass}`, children: [
249
+ "Output file name",
250
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center rounded-xl border border-slate-300 bg-white focus-within:border-[#1F8CFF] dark:border-slate-700 dark:bg-slate-950", children: [
251
+ /* @__PURE__ */ jsx(
252
+ "input",
253
+ {
254
+ type: "text",
255
+ value: outputName,
256
+ onChange: (event) => setOutputName(event.target.value),
257
+ disabled: isMerging,
258
+ className: "min-w-0 flex-1 rounded-xl bg-transparent px-3 py-2 text-sm normal-case tracking-normal text-slate-900 outline-none dark:text-slate-100"
259
+ }
260
+ ),
261
+ /* @__PURE__ */ jsx("span", { className: "px-3 text-sm normal-case tracking-normal text-slate-500", children: ".pdf" })
262
+ ] })
263
+ ] }),
264
+ /* @__PURE__ */ jsx(StatusBanner, { status }),
265
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 flex flex-wrap gap-3", children: [
266
+ /* @__PURE__ */ jsxs(
267
+ "button",
268
+ {
269
+ type: "submit",
270
+ disabled: isMerging || !readyEntries.length,
271
+ className: primaryButtonClass,
272
+ children: [
273
+ /* @__PURE__ */ jsx("i", { className: `fa-solid ${isMerging ? "fa-spinner fa-spin" : "fa-object-group"}` }),
274
+ isMerging ? "Merging..." : "Merge PDFs"
275
+ ]
276
+ }
277
+ ),
278
+ /* @__PURE__ */ jsxs(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ onClick: clearSelection,
283
+ disabled: isMerging || !entries.length,
284
+ className: secondaryButtonClass,
285
+ children: [
286
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-trash" }),
287
+ "Clear"
288
+ ]
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsx(BackToPdfTools, {})
292
+ ] })
293
+ ] }),
294
+ side: /* @__PURE__ */ jsxs(Fragment, { children: [
295
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "Summary" }),
296
+ /* @__PURE__ */ jsx(
297
+ SummaryList,
298
+ {
299
+ items: [
300
+ { label: "Files", value: entries.length },
301
+ { label: "Total pages", value: totalPages },
302
+ { label: "Total size", value: formatBytes(totalBytes) }
303
+ ]
304
+ }
305
+ ),
306
+ /* @__PURE__ */ jsx(
307
+ SideTips,
308
+ {
309
+ tips: [
310
+ "Files are merged top to bottom. Use the arrows to reorder.",
311
+ /* @__PURE__ */ jsxs(Fragment, { children: [
312
+ "Page ranges are 1-based: ",
313
+ /* @__PURE__ */ jsx("code", { children: "1-3, 5, 8-" }),
314
+ ". ",
315
+ /* @__PURE__ */ jsx("code", { children: "5-1" }),
316
+ " reverses."
317
+ ] }),
318
+ "Password-protected PDFs are skipped."
319
+ ]
320
+ }
321
+ )
322
+ ] })
323
+ }
324
+ )
325
+ }
326
+ );
327
+ }
328
+
329
+ export { MergePdf };
330
+ //# sourceMappingURL=chunk-JXKQ7S6W.js.map
331
+ //# sourceMappingURL=chunk-JXKQ7S6W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/merge/MergePdf.tsx"],"names":[],"mappings":"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","file":"chunk-JXKQ7S6W.js","sourcesContent":["import React, { useEffect, useMemo, useState } from \"react\";\nimport { downloadBlob, formatBytes, toBaseName } from \"../shared/files\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SideTips,\n StatusBanner,\n SummaryList,\n fieldLabelClass,\n iconButtonClass,\n inputClass,\n primaryButtonClass,\n secondaryButtonClass,\n} from \"../shared/ToolLayout\";\nimport { getPdfPageCount, isPdfFile, mergePdfs } from \"./mergePdfCore\";\n\nconst MAX_FILES = 20;\nconst MAX_TOTAL_BYTES = 200 * 1024 * 1024;\n\nlet nextEntryId = 0;\n\nexport default function MergePdf() {\n const [entries, setEntries] = useState([]);\n const [outputName, setOutputName] = useState(\"merged\");\n const [isMerging, setIsMerging] = useState(false);\n const [status, setStatus] = useState({\n tone: \"info\",\n text: \"Add PDF files, arrange them in order, and merge into one document.\",\n });\n\n useEffect(() => {\n document.title = \"Merge PDF | Apps\";\n }, []);\n\n const totalBytes = useMemo(\n () => entries.reduce((sum, entry) => sum + entry.file.size, 0),\n [entries]\n );\n\n const totalPages = useMemo(\n () => entries.reduce((sum, entry) => sum + (entry.pageCount || 0), 0),\n [entries]\n );\n\n const readyEntries = useMemo(\n () => entries.filter((entry) => entry.state === \"ready\"),\n [entries]\n );\n\n const updateEntry = (id, patch) => {\n setEntries((current) =>\n current.map((entry) => (entry.id === id ? { ...entry, ...patch } : entry))\n );\n };\n\n const inspectEntry = async (entry) => {\n try {\n const bytes = await entry.file.arrayBuffer();\n const pageCount = await getPdfPageCount(bytes);\n updateEntry(entry.id, { bytes, pageCount, state: \"ready\", error: \"\" });\n } catch (error) {\n updateEntry(entry.id, {\n state: \"error\",\n error: error?.message || \"Could not read this PDF.\",\n });\n }\n };\n\n const addFiles = (nextFiles) => {\n if (!nextFiles.length) return;\n\n const pdfOnly = nextFiles.filter(isPdfFile);\n if (!pdfOnly.length) {\n setStatus({ tone: \"error\", text: \"Only PDF files are supported.\" });\n return;\n }\n\n if (entries.length + pdfOnly.length > MAX_FILES) {\n setStatus({\n tone: \"error\",\n text: `Please merge up to ${MAX_FILES} PDF files at a time.`,\n });\n return;\n }\n\n const incomingBytes = pdfOnly.reduce((sum, file) => sum + file.size, 0);\n if (totalBytes + incomingBytes > MAX_TOTAL_BYTES) {\n setStatus({\n tone: \"error\",\n text: `Total size must stay under ${formatBytes(MAX_TOTAL_BYTES)}.`,\n });\n return;\n }\n\n const newEntries = pdfOnly.map((file) => ({\n id: `pdf-${++nextEntryId}`,\n file,\n bytes: null,\n pageCount: 0,\n pages: \"\",\n state: \"loading\",\n error: \"\",\n }));\n\n setEntries((current) => [...current, ...newEntries]);\n newEntries.forEach(inspectEntry);\n\n const skipped = nextFiles.length - pdfOnly.length;\n setStatus({\n tone: \"success\",\n text: `${pdfOnly.length} PDF file${pdfOnly.length > 1 ? \"s\" : \"\"} added.${\n skipped ? ` ${skipped} non-PDF file${skipped > 1 ? \"s\" : \"\"} skipped.` : \"\"\n }`,\n });\n };\n\n const moveEntry = (index, offset) => {\n setEntries((current) => {\n const target = index + offset;\n if (target < 0 || target >= current.length) return current;\n const next = [...current];\n [next[index], next[target]] = [next[target], next[index]];\n return next;\n });\n };\n\n const removeEntry = (id) => {\n setEntries((current) => current.filter((entry) => entry.id !== id));\n };\n\n const clearSelection = () => {\n setEntries([]);\n setStatus({\n tone: \"info\",\n text: \"Selection cleared. Add PDF files to continue.\",\n });\n };\n\n const handleMerge = async (event) => {\n event.preventDefault();\n if (!readyEntries.length) {\n setStatus({\n tone: \"error\",\n text: \"Please add at least one readable PDF before merging.\",\n });\n return;\n }\n\n if (entries.some((entry) => entry.state === \"loading\")) {\n setStatus({ tone: \"info\", text: \"Still reading files. Please try again in a moment.\" });\n return;\n }\n\n setIsMerging(true);\n setStatus({ tone: \"info\", text: \"Merging your PDF files...\" });\n\n try {\n const baseName = toBaseName(outputName, \"merged\");\n const mergedBytes = await mergePdfs(\n readyEntries.map((entry) => ({\n name: entry.file.name,\n bytes: entry.bytes,\n pages: entry.pages,\n })),\n { title: baseName }\n );\n\n const blob = new Blob([mergedBytes as BlobPart], { type: \"application/pdf\" });\n downloadBlob(blob, `${baseName}.pdf`);\n\n const skipped = entries.length - readyEntries.length;\n setStatus({\n tone: \"success\",\n text: `Merged ${readyEntries.length} file${readyEntries.length > 1 ? \"s\" : \"\"} (${formatBytes(\n blob.size\n )}). Your download has started.${\n skipped ? ` ${skipped} unreadable file${skipped > 1 ? \"s were\" : \" was\"} skipped.` : \"\"\n }`,\n });\n } catch (error) {\n setStatus({\n tone: \"error\",\n text: `Merge failed: ${error?.message || \"Please try again.\"}`,\n });\n } finally {\n setIsMerging(false);\n }\n };\n\n return (\n <PdfToolPage\n title=\"Merge PDF\"\n description=\"Combine multiple PDFs into one file. Reorder files, pick page ranges, and download. Everything runs in your browser; files never leave your device.\"\n stats={[\n { value: MAX_FILES, label: \"Max Files\" },\n { value: \"PDF\", label: \"Output\" },\n ]}\n >\n <PdfToolColumns\n main={\n <form onSubmit={handleMerge}>\n <FileDropzone\n accept=\".pdf,application/pdf\"\n multiple\n disabled={isMerging}\n onFiles={addFiles}\n title=\"Drag and drop PDF files here\"\n hint=\"or click to browse. Add more any time; they are appended to the list.\"\n />\n\n {entries.length ? (\n <ol className=\"mt-5 space-y-3\">\n {entries.map((entry, index) => (\n <li\n key={entry.id}\n className={`rounded-2xl border p-3 sm:p-4 ${\n entry.state === \"error\"\n ? \"border-rose-200 bg-rose-50/70 dark:border-rose-500/30 dark:bg-rose-500/10\"\n : \"border-slate-200 bg-white/90 dark:border-slate-700 dark:bg-slate-900/80\"\n }`}\n >\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-xs font-semibold text-slate-600 dark:bg-slate-800 dark:text-slate-300\">\n {index + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-slate-900 dark:text-slate-100\">\n {entry.file.name}\n </p>\n <p className=\"text-xs text-slate-500 dark:text-slate-400\">\n {formatBytes(entry.file.size)}\n {entry.state === \"ready\"\n ? ` · ${entry.pageCount} page${entry.pageCount === 1 ? \"\" : \"s\"}`\n : null}\n {entry.state === \"loading\" ? \" · reading...\" : null}\n </p>\n </div>\n <div className=\"flex items-center gap-1.5\">\n <button\n type=\"button\"\n onClick={() => moveEntry(index, -1)}\n disabled={isMerging || index === 0}\n className={iconButtonClass}\n aria-label={`Move ${entry.file.name} up`}\n >\n <i className=\"fa-solid fa-arrow-up\" />\n </button>\n <button\n type=\"button\"\n onClick={() => moveEntry(index, 1)}\n disabled={isMerging || index === entries.length - 1}\n className={iconButtonClass}\n aria-label={`Move ${entry.file.name} down`}\n >\n <i className=\"fa-solid fa-arrow-down\" />\n </button>\n <button\n type=\"button\"\n onClick={() => removeEntry(entry.id)}\n disabled={isMerging}\n className={iconButtonClass}\n aria-label={`Remove ${entry.file.name}`}\n >\n <i className=\"fa-solid fa-xmark\" />\n </button>\n </div>\n </div>\n\n {entry.state === \"error\" ? (\n <p className=\"mt-2 text-xs text-rose-700 dark:text-rose-300\">\n {entry.error} This file will be skipped.\n </p>\n ) : null}\n\n {entry.state === \"ready\" && entry.pageCount > 1 ? (\n <label className=\"mt-3 flex flex-wrap items-center gap-2 text-xs text-slate-600 dark:text-slate-300\">\n <span className=\"font-semibold uppercase tracking-[0.12em]\">Pages</span>\n <input\n type=\"text\"\n value={entry.pages}\n onChange={(event) => updateEntry(entry.id, { pages: event.target.value })}\n disabled={isMerging}\n placeholder={`All pages (e.g. 1-${entry.pageCount}, or ${entry.pageCount}-1 to reverse)`}\n className={`flex-1 ${inputClass}`}\n />\n </label>\n ) : null}\n </li>\n ))}\n </ol>\n ) : null}\n\n <label className={`mt-5 ${fieldLabelClass}`}>\n Output file name\n <div className=\"mt-2 flex items-center rounded-xl border border-slate-300 bg-white focus-within:border-[#1F8CFF] dark:border-slate-700 dark:bg-slate-950\">\n <input\n type=\"text\"\n value={outputName}\n onChange={(event) => setOutputName(event.target.value)}\n disabled={isMerging}\n className=\"min-w-0 flex-1 rounded-xl bg-transparent px-3 py-2 text-sm normal-case tracking-normal text-slate-900 outline-none dark:text-slate-100\"\n />\n <span className=\"px-3 text-sm normal-case tracking-normal text-slate-500\">.pdf</span>\n </div>\n </label>\n\n <StatusBanner status={status} />\n\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <button\n type=\"submit\"\n disabled={isMerging || !readyEntries.length}\n className={primaryButtonClass}\n >\n <i className={`fa-solid ${isMerging ? \"fa-spinner fa-spin\" : \"fa-object-group\"}`} />\n {isMerging ? \"Merging...\" : \"Merge PDFs\"}\n </button>\n\n <button\n type=\"button\"\n onClick={clearSelection}\n disabled={isMerging || !entries.length}\n className={secondaryButtonClass}\n >\n <i className=\"fa-solid fa-trash\" />\n Clear\n </button>\n\n <BackToPdfTools />\n </div>\n </form>\n }\n side={\n <>\n <h2 className=\"text-base font-semibold text-slate-900 dark:text-slate-100\">Summary</h2>\n <SummaryList\n items={[\n { label: \"Files\", value: entries.length },\n { label: \"Total pages\", value: totalPages },\n { label: \"Total size\", value: formatBytes(totalBytes) },\n ]}\n />\n <SideTips\n tips={[\n \"Files are merged top to bottom. Use the arrows to reorder.\",\n <>\n Page ranges are 1-based: <code>1-3, 5, 8-</code>. <code>5-1</code> reverses.\n </>,\n \"Password-protected PDFs are skipped.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}
@@ -0,0 +1,34 @@
1
+ // src/sign/signatureImage.ts
2
+ function whiteToTransparent(data, threshold = 225) {
3
+ for (let i = 0; i < data.length; i += 4) {
4
+ const lightest = Math.min(data[i], data[i + 1], data[i + 2]);
5
+ if (lightest >= threshold) {
6
+ data[i + 3] = 0;
7
+ } else if (lightest > threshold - 40) {
8
+ data[i + 3] = Math.min(data[i + 3], Math.round((threshold - lightest) / 40 * 255));
9
+ }
10
+ }
11
+ return data;
12
+ }
13
+ function opaqueBounds(data, width, height, alphaThreshold = 8) {
14
+ let minX = width;
15
+ let minY = height;
16
+ let maxX = -1;
17
+ let maxY = -1;
18
+ for (let y = 0; y < height; y += 1) {
19
+ for (let x = 0; x < width; x += 1) {
20
+ if (data[(y * width + x) * 4 + 3] > alphaThreshold) {
21
+ if (x < minX) minX = x;
22
+ if (x > maxX) maxX = x;
23
+ if (y < minY) minY = y;
24
+ if (y > maxY) maxY = y;
25
+ }
26
+ }
27
+ }
28
+ if (maxX < 0) return null;
29
+ return { x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1 };
30
+ }
31
+
32
+ export { opaqueBounds, whiteToTransparent };
33
+ //# sourceMappingURL=chunk-KY6DXS6S.js.map
34
+ //# sourceMappingURL=chunk-KY6DXS6S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sign/signatureImage.ts"],"names":[],"mappings":";AAGO,SAAS,kBAAA,CAAmB,IAAA,EAAyB,SAAA,GAAY,GAAA,EAAK;AAC3E,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,MAAA,EAAQ,KAAK,CAAA,EAAG;AACvC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA,EAAG,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA,EAAG,IAAA,CAAK,CAAA,GAAI,CAAC,CAAC,CAAA;AAC3D,IAAA,IAAI,YAAY,SAAA,EAAW;AACzB,MAAA,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA,GAAI,CAAA;AAAA,IAChB,CAAA,MAAA,IAAW,QAAA,GAAW,SAAA,GAAY,EAAA,EAAI;AAEpC,MAAA,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA,GAAI,IAAA,CAAK,IAAI,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA,EAAG,KAAK,KAAA,CAAA,CAAQ,SAAA,GAAY,QAAA,IAAY,EAAA,GAAM,GAAG,CAAC,CAAA;AAAA,IACrF;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,YAAA,CACd,IAAA,EACA,KAAA,EACA,MAAA,EACA,iBAAiB,CAAA,EACjB;AACA,EAAA,IAAI,IAAA,GAAO,KAAA;AACX,EAAA,IAAI,IAAA,GAAO,MAAA;AACX,EAAA,IAAI,IAAA,GAAO,EAAA;AACX,EAAA,IAAI,IAAA,GAAO,EAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,KAAK,CAAA,EAAG;AAClC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,KAAK,CAAA,EAAG;AACjC,MAAA,IAAI,MAAM,CAAA,GAAI,KAAA,GAAQ,KAAK,CAAA,GAAI,CAAC,IAAI,cAAA,EAAgB;AAClD,QAAA,IAAI,CAAA,GAAI,MAAM,IAAA,GAAO,CAAA;AACrB,QAAA,IAAI,CAAA,GAAI,MAAM,IAAA,GAAO,CAAA;AACrB,QAAA,IAAI,CAAA,GAAI,MAAM,IAAA,GAAO,CAAA;AACrB,QAAA,IAAI,CAAA,GAAI,MAAM,IAAA,GAAO,CAAA;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACA,EAAA,IAAI,IAAA,GAAO,GAAG,OAAO,IAAA;AACrB,EAAA,OAAO,EAAE,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,KAAA,EAAO,IAAA,GAAO,IAAA,GAAO,CAAA,EAAG,MAAA,EAAQ,IAAA,GAAO,IAAA,GAAO,CAAA,EAAE;AAC7E","file":"chunk-KY6DXS6S.js","sourcesContent":["// Pixel helpers for signature images (RGBA data, as in ImageData.data).\n\n/** Make near-white pixels transparent, e.g. for a photographed signature. */\nexport function whiteToTransparent(data: Uint8ClampedArray, threshold = 225) {\n for (let i = 0; i < data.length; i += 4) {\n const lightest = Math.min(data[i], data[i + 1], data[i + 2]);\n if (lightest >= threshold) {\n data[i + 3] = 0;\n } else if (lightest > threshold - 40) {\n // Soften the edge so strokes don't look cut out.\n data[i + 3] = Math.min(data[i + 3], Math.round(((threshold - lightest) / 40) * 255));\n }\n }\n return data;\n}\n\n/** Bounding box of pixels with alpha above the threshold, or null if empty. */\nexport function opaqueBounds(\n data: Uint8ClampedArray,\n width: number,\n height: number,\n alphaThreshold = 8\n) {\n let minX = width;\n let minY = height;\n let maxX = -1;\n let maxY = -1;\n for (let y = 0; y < height; y += 1) {\n for (let x = 0; x < width; x += 1) {\n if (data[(y * width + x) * 4 + 3] > alphaThreshold) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n }\n }\n if (maxX < 0) return null;\n return { x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1 };\n}\n"]}
@@ -0,0 +1,22 @@
1
+ // src/toimages/pdfToImagesCore.ts
2
+ var IMAGE_FORMATS = {
3
+ png: { label: "PNG (sharp, larger)", mime: "image/png", extension: "png" },
4
+ jpeg: { label: "JPG (smaller)", mime: "image/jpeg", extension: "jpg" }
5
+ };
6
+ var DPI_OPTIONS = [72, 150, 220, 300];
7
+ function dpiToScale(dpi) {
8
+ return Math.max(0.1, Number(dpi) || 72) / 72;
9
+ }
10
+ function imageFileName(baseName, pageNumber, pageCount, format) {
11
+ const digits = String(Math.max(pageCount, 1)).length;
12
+ const page = String(pageNumber).padStart(digits, "0");
13
+ return `${baseName}-page-${page}.${IMAGE_FORMATS[format].extension}`;
14
+ }
15
+ function renderedSize(widthPoints, heightPoints, dpi) {
16
+ const scale = dpiToScale(dpi);
17
+ return { width: Math.floor(widthPoints * scale), height: Math.floor(heightPoints * scale) };
18
+ }
19
+
20
+ export { DPI_OPTIONS, IMAGE_FORMATS, dpiToScale, imageFileName, renderedSize };
21
+ //# sourceMappingURL=chunk-NOFGCYGS.js.map
22
+ //# sourceMappingURL=chunk-NOFGCYGS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/toimages/pdfToImagesCore.ts"],"names":[],"mappings":";AAEO,IAAM,aAAA,GAAyF;AAAA,EACpG,KAAK,EAAE,KAAA,EAAO,uBAAuB,IAAA,EAAM,WAAA,EAAa,WAAW,KAAA,EAAM;AAAA,EACzE,MAAM,EAAE,KAAA,EAAO,iBAAiB,IAAA,EAAM,YAAA,EAAc,WAAW,KAAA;AACjE;AAEO,IAAM,WAAA,GAAc,CAAC,EAAA,EAAI,GAAA,EAAK,KAAK,GAAG;AAGtC,SAAS,WAAW,GAAA,EAAa;AACtC,EAAA,OAAO,KAAK,GAAA,CAAI,GAAA,EAAK,OAAO,GAAG,CAAA,IAAK,EAAE,CAAA,GAAI,EAAA;AAC5C;AAGO,SAAS,aAAA,CAAc,QAAA,EAAkB,UAAA,EAAoB,SAAA,EAAmB,MAAA,EAAqB;AAC1G,EAAA,MAAM,SAAS,MAAA,CAAO,IAAA,CAAK,IAAI,SAAA,EAAW,CAAC,CAAC,CAAA,CAAE,MAAA;AAC9C,EAAA,MAAM,OAAO,MAAA,CAAO,UAAU,CAAA,CAAE,QAAA,CAAS,QAAQ,GAAG,CAAA;AACpD,EAAA,OAAO,CAAA,EAAG,QAAQ,CAAA,MAAA,EAAS,IAAI,IAAI,aAAA,CAAc,MAAM,EAAE,SAAS,CAAA,CAAA;AACpE;AAGO,SAAS,YAAA,CAAa,WAAA,EAAqB,YAAA,EAAsB,GAAA,EAAa;AACnF,EAAA,MAAM,KAAA,GAAQ,WAAW,GAAG,CAAA;AAC5B,EAAA,OAAO,EAAE,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,WAAA,GAAc,KAAK,CAAA,EAAG,MAAA,EAAQ,IAAA,CAAK,KAAA,CAAM,YAAA,GAAe,KAAK,CAAA,EAAE;AAC5F","file":"chunk-NOFGCYGS.js","sourcesContent":["export type ImageFormat = \"png\" | \"jpeg\";\n\nexport const IMAGE_FORMATS: Record<ImageFormat, { label: string; mime: string; extension: string }> = {\n png: { label: \"PNG (sharp, larger)\", mime: \"image/png\", extension: \"png\" },\n jpeg: { label: \"JPG (smaller)\", mime: \"image/jpeg\", extension: \"jpg\" },\n};\n\nexport const DPI_OPTIONS = [72, 150, 220, 300];\n\n/** PDF user space is 72 points per inch. */\nexport function dpiToScale(dpi: number) {\n return Math.max(0.1, Number(dpi) || 72) / 72;\n}\n\n/** \"report-page-07.png\": zero-padded so files sort in page order. */\nexport function imageFileName(baseName: string, pageNumber: number, pageCount: number, format: ImageFormat) {\n const digits = String(Math.max(pageCount, 1)).length;\n const page = String(pageNumber).padStart(digits, \"0\");\n return `${baseName}-page-${page}.${IMAGE_FORMATS[format].extension}`;\n}\n\n/** Pixel size of a page rendered at the given DPI, before any browser limits. */\nexport function renderedSize(widthPoints: number, heightPoints: number, dpi: number) {\n const scale = dpiToScale(dpi);\n return { width: Math.floor(widthPoints * scale), height: Math.floor(heightPoints * scale) };\n}\n"]}
@@ -0,0 +1,131 @@
1
+ import { requireAsset } from './chunk-DQPABZTU.js';
2
+ import { PDFDocument } from 'pdf-lib';
3
+ import createModule from '@neslinesli93/qpdf-wasm';
4
+
5
+ var creationQueue = Promise.resolve();
6
+ async function createCaptured(factory) {
7
+ const turn = creationQueue.then(() => createPatched(factory));
8
+ creationQueue = turn.catch(() => void 0);
9
+ return turn;
10
+ }
11
+ async function createPatched(factory) {
12
+ let sink = null;
13
+ const original = { log: console.log, error: console.error, warn: console.warn };
14
+ const forward = (kind, fallback) => (...args) => {
15
+ if (sink) sink[kind].push(args.map(String).join(" "));
16
+ else fallback(...args);
17
+ };
18
+ console.log = forward("out", original.log);
19
+ console.error = forward("err", original.error);
20
+ console.warn = forward("err", original.warn);
21
+ let qpdf;
22
+ try {
23
+ qpdf = await factory({});
24
+ } finally {
25
+ console.log = original.log;
26
+ console.error = original.error;
27
+ console.warn = original.warn;
28
+ }
29
+ function run(args) {
30
+ sink = { out: [], err: [] };
31
+ let code;
32
+ try {
33
+ code = qpdf.callMain(args);
34
+ } catch (error) {
35
+ code = typeof error?.status === "number" ? error.status : 2;
36
+ }
37
+ const result = { code, stdout: sink.out.join("\n"), stderr: sink.err.join("\n") };
38
+ sink = null;
39
+ return result;
40
+ }
41
+ return { qpdf, run };
42
+ }
43
+ var PasswordError = class extends Error {
44
+ constructor(message = "That password is not correct.") {
45
+ super(message);
46
+ this.name = "PasswordError";
47
+ }
48
+ };
49
+ var INPUT = "/input.pdf";
50
+ var OUTPUT = "/output.pdf";
51
+ var succeeded = (code) => code === 0 || code === 3;
52
+ function cleanError(stderr) {
53
+ const line = stderr.split("\n").map((text) => text.trim()).find(Boolean) || "";
54
+ return line.replace(/^[^:]*:\s*/, "").replace(/^\/input\.pdf:\s*/, "").trim();
55
+ }
56
+ function randomPassword(length = 32) {
57
+ const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
58
+ const values = new Uint32Array(length);
59
+ globalThis.crypto.getRandomValues(values);
60
+ return Array.from(values, (value) => alphabet[value % alphabet.length]).join("");
61
+ }
62
+ async function isEncrypted(bytes) {
63
+ try {
64
+ const doc = await PDFDocument.load(bytes, { ignoreEncryption: true, updateMetadata: false });
65
+ return doc.isEncrypted;
66
+ } catch {
67
+ throw new Error("This file is not a readable PDF.");
68
+ }
69
+ }
70
+ function toBytes(bytes) {
71
+ return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
72
+ }
73
+ async function protectPdf(bytes, options, factory) {
74
+ const userPassword = options.userPassword ?? "";
75
+ const print = options.print ?? "full";
76
+ const extract = options.extract ?? true;
77
+ const modify = options.modify ?? true;
78
+ const restricted = print !== "full" || !extract || !modify;
79
+ if (!userPassword && !restricted) {
80
+ throw new Error("Set a password to open the file, or turn off at least one permission.");
81
+ }
82
+ if (await isEncrypted(bytes)) {
83
+ throw new Error("This PDF is already protected. Unlock it first, then protect it again.");
84
+ }
85
+ const ownerPassword = options.ownerPassword || randomPassword();
86
+ if (userPassword && ownerPassword === userPassword && restricted) {
87
+ throw new Error("Use a different owner password, or the restrictions can be lifted with the open password.");
88
+ }
89
+ const { qpdf, run } = await createCaptured(factory);
90
+ qpdf.FS.writeFile(INPUT, toBytes(bytes));
91
+ const result = run([
92
+ "--encrypt",
93
+ `--user-password=${userPassword}`,
94
+ `--owner-password=${ownerPassword}`,
95
+ "--bits=256",
96
+ `--print=${print}`,
97
+ `--extract=${extract ? "y" : "n"}`,
98
+ `--modify=${modify ? "all" : "none"}`,
99
+ "--",
100
+ INPUT,
101
+ OUTPUT
102
+ ]);
103
+ if (!succeeded(result.code)) {
104
+ throw new Error(cleanError(result.stderr) || "Could not protect this PDF.");
105
+ }
106
+ return { bytes: qpdf.FS.readFile(OUTPUT), ownerPassword };
107
+ }
108
+ async function unlockPdf(bytes, password, factory) {
109
+ if (!await isEncrypted(bytes)) {
110
+ throw new Error("This PDF is not password protected.");
111
+ }
112
+ const { qpdf, run } = await createCaptured(factory);
113
+ qpdf.FS.writeFile(INPUT, toBytes(bytes));
114
+ const result = run([`--password=${password}`, "--decrypt", INPUT, OUTPUT]);
115
+ if (!succeeded(result.code)) {
116
+ const message = cleanError(result.stderr);
117
+ if (/invalid password/i.test(message)) {
118
+ throw new PasswordError(password ? "That password is not correct." : "This PDF needs a password to open.");
119
+ }
120
+ throw new Error(message || "Could not unlock this PDF.");
121
+ }
122
+ return qpdf.FS.readFile(OUTPUT);
123
+ }
124
+ var browserQpdf = (options) => {
125
+ const wasmUrl = requireAsset("qpdfWasmUrl", "Protect / Unlock PDF");
126
+ return createModule({ ...options, locateFile: () => wasmUrl });
127
+ };
128
+
129
+ export { PasswordError, browserQpdf, isEncrypted, protectPdf, randomPassword, unlockPdf };
130
+ //# sourceMappingURL=chunk-OSHUJRKD.js.map
131
+ //# sourceMappingURL=chunk-OSHUJRKD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protect/protectPdfCore.ts","../src/protect/browserQpdf.ts"],"names":[],"mappings":"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","file":"chunk-OSHUJRKD.js","sourcesContent":["import { PDFDocument } from \"pdf-lib\";\n\n/** The parts of an Emscripten qpdf instance we use. */\nexport type QpdfInstance = {\n callMain(args: string[]): number;\n FS: {\n writeFile(path: string, data: Uint8Array): void;\n readFile(path: string): Uint8Array;\n unlink(path: string): void;\n };\n};\n\n/** Create a fresh qpdf module (browser: fetches the .wasm; tests: from disk). */\nexport type QpdfFactory = (options: Record<string, unknown>) => Promise<QpdfInstance>;\n\ntype Sink = { out: string[]; err: string[] } | null;\n\n/** Module creations run one at a time, so console patches never overlap. */\nlet creationQueue: Promise<unknown> = Promise.resolve();\n\n/**\n * qpdf binds console.log / console.error once, when the module is created,\n * and ignores Emscripten's print hooks. Install forwarding functions just\n * while it is created so its output can be captured per run.\n */\nasync function createCaptured(factory: QpdfFactory) {\n const turn = creationQueue.then(() => createPatched(factory));\n creationQueue = turn.catch(() => undefined);\n return turn;\n}\n\nasync function createPatched(factory: QpdfFactory) {\n let sink: Sink = null;\n const original = { log: console.log, error: console.error, warn: console.warn };\n const forward = (kind: \"out\" | \"err\", fallback: (...args: unknown[]) => void) =>\n (...args: unknown[]) => {\n if (sink) sink[kind].push(args.map(String).join(\" \"));\n else fallback(...args);\n };\n console.log = forward(\"out\", original.log);\n console.error = forward(\"err\", original.error);\n console.warn = forward(\"err\", original.warn);\n let qpdf: QpdfInstance;\n try {\n qpdf = await factory({});\n } finally {\n console.log = original.log;\n console.error = original.error;\n console.warn = original.warn;\n }\n\n function run(args: string[]) {\n sink = { out: [], err: [] };\n let code: number;\n try {\n code = qpdf.callMain(args);\n } catch (error) {\n code = typeof error?.status === \"number\" ? error.status : 2;\n }\n const result = { code, stdout: sink.out.join(\"\\n\"), stderr: sink.err.join(\"\\n\") };\n sink = null;\n return result;\n }\n\n return { qpdf, run };\n}\n\nexport class PasswordError extends Error {\n constructor(message = \"That password is not correct.\") {\n super(message);\n this.name = \"PasswordError\";\n }\n}\n\nexport type PrintPermission = \"full\" | \"low\" | \"none\";\n\nexport type ProtectOptions = {\n /** Needed to open the file. Empty means anyone can open it (restrictions only). */\n userPassword?: string;\n /** Needed to change permissions. Generated when left empty. */\n ownerPassword?: string;\n print?: PrintPermission;\n /** Copying text and images. */\n extract?: boolean;\n /** Any editing: content, pages, annotations, forms. */\n modify?: boolean;\n};\n\nconst INPUT = \"/input.pdf\";\nconst OUTPUT = \"/output.pdf\";\n\n/** qpdf exit codes: 0 ok, 3 ok with warnings, 2 error. */\nconst succeeded = (code: number) => code === 0 || code === 3;\n\n/** \"this.program: /input.pdf: invalid password\" -> \"invalid password\" */\nfunction cleanError(stderr: string) {\n const line = stderr.split(\"\\n\").map((text) => text.trim()).find(Boolean) || \"\";\n return line.replace(/^[^:]*:\\s*/, \"\").replace(/^\\/input\\.pdf:\\s*/, \"\").trim();\n}\n\nexport function randomPassword(length = 32) {\n const alphabet = \"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789\";\n const values = new Uint32Array(length);\n globalThis.crypto.getRandomValues(values);\n return Array.from(values, (value) => alphabet[value % alphabet.length]).join(\"\");\n}\n\nexport async function isEncrypted(bytes: ArrayBuffer | Uint8Array) {\n try {\n const doc = await PDFDocument.load(bytes, { ignoreEncryption: true, updateMetadata: false });\n return doc.isEncrypted;\n } catch {\n throw new Error(\"This file is not a readable PDF.\");\n }\n}\n\nfunction toBytes(bytes: ArrayBuffer | Uint8Array) {\n return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n}\n\n/** Encrypt with AES-256. Returns the bytes and the owner password used. */\nexport async function protectPdf(\n bytes: ArrayBuffer | Uint8Array,\n options: ProtectOptions,\n factory: QpdfFactory\n) {\n const userPassword = options.userPassword ?? \"\";\n const print = options.print ?? \"full\";\n const extract = options.extract ?? true;\n const modify = options.modify ?? true;\n const restricted = print !== \"full\" || !extract || !modify;\n if (!userPassword && !restricted) {\n throw new Error(\"Set a password to open the file, or turn off at least one permission.\");\n }\n if (await isEncrypted(bytes)) {\n throw new Error(\"This PDF is already protected. Unlock it first, then protect it again.\");\n }\n\n // An empty owner password would let anyone open the file without a password.\n const ownerPassword = options.ownerPassword || randomPassword();\n if (userPassword && ownerPassword === userPassword && restricted) {\n throw new Error(\"Use a different owner password, or the restrictions can be lifted with the open password.\");\n }\n\n const { qpdf, run } = await createCaptured(factory);\n qpdf.FS.writeFile(INPUT, toBytes(bytes));\n const result = run([\n \"--encrypt\",\n `--user-password=${userPassword}`,\n `--owner-password=${ownerPassword}`,\n \"--bits=256\",\n `--print=${print}`,\n `--extract=${extract ? \"y\" : \"n\"}`,\n `--modify=${modify ? \"all\" : \"none\"}`,\n \"--\",\n INPUT,\n OUTPUT,\n ]);\n if (!succeeded(result.code)) {\n throw new Error(cleanError(result.stderr) || \"Could not protect this PDF.\");\n }\n return { bytes: qpdf.FS.readFile(OUTPUT), ownerPassword };\n}\n\n/**\n * Remove encryption. Tries without a password first, which works for files\n * that only restrict printing or editing. Throws PasswordError when a\n * password is needed or the one given is wrong.\n */\nexport async function unlockPdf(\n bytes: ArrayBuffer | Uint8Array,\n password: string,\n factory: QpdfFactory\n) {\n if (!(await isEncrypted(bytes))) {\n throw new Error(\"This PDF is not password protected.\");\n }\n\n const { qpdf, run } = await createCaptured(factory);\n qpdf.FS.writeFile(INPUT, toBytes(bytes));\n const result = run([`--password=${password}`, \"--decrypt\", INPUT, OUTPUT]);\n if (!succeeded(result.code)) {\n const message = cleanError(result.stderr);\n if (/invalid password/i.test(message)) {\n throw new PasswordError(password ? \"That password is not correct.\" : \"This PDF needs a password to open.\");\n }\n throw new Error(message || \"Could not unlock this PDF.\");\n }\n return qpdf.FS.readFile(OUTPUT);\n}\n","// The Emscripten factory, pointed at the .wasm URL the host app configured.\n// Fetched only when Protect / Unlock runs.\nimport createModule from \"@neslinesli93/qpdf-wasm\";\nimport { requireAsset } from \"../config\";\nimport type { QpdfFactory } from \"./protectPdfCore\";\n\nexport const browserQpdf: QpdfFactory = (options) => {\n const wasmUrl = requireAsset(\"qpdfWasmUrl\", \"Protect / Unlock PDF\");\n return (createModule as unknown as QpdfFactory)({ ...options, locateFile: () => wasmUrl });\n};\n"]}