@kitsy/pdf 0.0.0-stage → 0.2.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.
- package/CHANGELOG.md +32 -0
- package/LICENSE +201 -0
- package/NOTICE +16 -0
- package/README.md +256 -2
- package/THIRD_PARTY_NOTICES.md +48 -0
- package/dist/chunk-2K5X7L5J.js +43 -0
- package/dist/chunk-2K5X7L5J.js.map +1 -0
- package/dist/chunk-4EAYBJ6G.js +362 -0
- package/dist/chunk-4EAYBJ6G.js.map +1 -0
- package/dist/chunk-4LBJTDV5.js +165 -0
- package/dist/chunk-4LBJTDV5.js.map +1 -0
- package/dist/chunk-4ZEZ2GLD.js +353 -0
- package/dist/chunk-4ZEZ2GLD.js.map +1 -0
- package/dist/chunk-5OIMR74G.js +273 -0
- package/dist/chunk-5OIMR74G.js.map +1 -0
- package/dist/chunk-6NFNAKWA.js +257 -0
- package/dist/chunk-6NFNAKWA.js.map +1 -0
- package/dist/chunk-7MTEMQLL.js +47 -0
- package/dist/chunk-7MTEMQLL.js.map +1 -0
- package/dist/chunk-7V4SWTUK.js +83 -0
- package/dist/chunk-7V4SWTUK.js.map +1 -0
- package/dist/chunk-7XH7MFZ4.js +295 -0
- package/dist/chunk-7XH7MFZ4.js.map +1 -0
- package/dist/chunk-BK3MYSUS.js +61 -0
- package/dist/chunk-BK3MYSUS.js.map +1 -0
- package/dist/chunk-BLHNKR3S.js +35 -0
- package/dist/chunk-BLHNKR3S.js.map +1 -0
- package/dist/chunk-CCJ43SIX.js +342 -0
- package/dist/chunk-CCJ43SIX.js.map +1 -0
- package/dist/chunk-CNEDWATL.js +210 -0
- package/dist/chunk-CNEDWATL.js.map +1 -0
- package/dist/chunk-CYTWDJE2.js +152 -0
- package/dist/chunk-CYTWDJE2.js.map +1 -0
- package/dist/chunk-DKVOICYZ.js +45 -0
- package/dist/chunk-DKVOICYZ.js.map +1 -0
- package/dist/chunk-DPEJU4P6.js +79 -0
- package/dist/chunk-DPEJU4P6.js.map +1 -0
- package/dist/chunk-DQPABZTU.js +22 -0
- package/dist/chunk-DQPABZTU.js.map +1 -0
- package/dist/chunk-EKHEY3LI.js +46 -0
- package/dist/chunk-EKHEY3LI.js.map +1 -0
- package/dist/chunk-FIEYAKPI.js +421 -0
- package/dist/chunk-FIEYAKPI.js.map +1 -0
- package/dist/chunk-G467LFKC.js +191 -0
- package/dist/chunk-G467LFKC.js.map +1 -0
- package/dist/chunk-GLYWSVSB.js +250 -0
- package/dist/chunk-GLYWSVSB.js.map +1 -0
- package/dist/chunk-GXBIMUO7.js +135 -0
- package/dist/chunk-GXBIMUO7.js.map +1 -0
- package/dist/chunk-H6EERRCR.js +180 -0
- package/dist/chunk-H6EERRCR.js.map +1 -0
- package/dist/chunk-HDTM3LXD.js +35 -0
- package/dist/chunk-HDTM3LXD.js.map +1 -0
- package/dist/chunk-IGMX56C3.js +180 -0
- package/dist/chunk-IGMX56C3.js.map +1 -0
- package/dist/chunk-J4IRICCJ.js +74 -0
- package/dist/chunk-J4IRICCJ.js.map +1 -0
- package/dist/chunk-JXKQ7S6W.js +331 -0
- package/dist/chunk-JXKQ7S6W.js.map +1 -0
- package/dist/chunk-KY6DXS6S.js +34 -0
- package/dist/chunk-KY6DXS6S.js.map +1 -0
- package/dist/chunk-NOFGCYGS.js +22 -0
- package/dist/chunk-NOFGCYGS.js.map +1 -0
- package/dist/chunk-OSHUJRKD.js +131 -0
- package/dist/chunk-OSHUJRKD.js.map +1 -0
- package/dist/chunk-PMH7OWPF.js +40 -0
- package/dist/chunk-PMH7OWPF.js.map +1 -0
- package/dist/chunk-PRBKSX3A.js +299 -0
- package/dist/chunk-PRBKSX3A.js.map +1 -0
- package/dist/chunk-SVPUX7AM.js +48 -0
- package/dist/chunk-SVPUX7AM.js.map +1 -0
- package/dist/chunk-TR6HJ4NO.js +659 -0
- package/dist/chunk-TR6HJ4NO.js.map +1 -0
- package/dist/chunk-TV3E4YUF.js +69 -0
- package/dist/chunk-TV3E4YUF.js.map +1 -0
- package/dist/chunk-VCTN77K2.js +112 -0
- package/dist/chunk-VCTN77K2.js.map +1 -0
- package/dist/chunk-WEOPDD3P.js +120 -0
- package/dist/chunk-WEOPDD3P.js.map +1 -0
- package/dist/chunk-ZBZ7OC6I.js +228 -0
- package/dist/chunk-ZBZ7OC6I.js.map +1 -0
- package/dist/config-CJYo46X9.d.ts +19 -0
- package/dist/index.d.ts +517 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/react/certificate-sign.d.ts +5 -0
- package/dist/react/certificate-sign.js +14 -0
- package/dist/react/certificate-sign.js.map +1 -0
- package/dist/react/compress.d.ts +5 -0
- package/dist/react/compress.js +8 -0
- package/dist/react/compress.js.map +1 -0
- package/dist/react/fill-form.d.ts +5 -0
- package/dist/react/fill-form.js +8 -0
- package/dist/react/fill-form.js.map +1 -0
- package/dist/react/images-to-pdf.d.ts +5 -0
- package/dist/react/images-to-pdf.js +7 -0
- package/dist/react/images-to-pdf.js.map +1 -0
- package/dist/react/merge.d.ts +5 -0
- package/dist/react/merge.js +8 -0
- package/dist/react/merge.js.map +1 -0
- package/dist/react/metadata.d.ts +5 -0
- package/dist/react/metadata.js +8 -0
- package/dist/react/metadata.js.map +1 -0
- package/dist/react/organize.d.ts +5 -0
- package/dist/react/organize.js +12 -0
- package/dist/react/organize.js.map +1 -0
- package/dist/react/pdf-to-images.d.ts +5 -0
- package/dist/react/pdf-to-images.js +13 -0
- package/dist/react/pdf-to-images.js.map +1 -0
- package/dist/react/protect.d.ts +5 -0
- package/dist/react/protect.js +8 -0
- package/dist/react/protect.js.map +1 -0
- package/dist/react/sign.d.ts +5 -0
- package/dist/react/sign.js +13 -0
- package/dist/react/sign.js.map +1 -0
- package/dist/react/split.d.ts +5 -0
- package/dist/react/split.js +11 -0
- package/dist/react/split.js.map +1 -0
- package/dist/react/verify-signatures.d.ts +5 -0
- package/dist/react/verify-signatures.js +8 -0
- package/dist/react/verify-signatures.js.map +1 -0
- package/dist/react/watermark.d.ts +5 -0
- package/dist/react/watermark.js +10 -0
- package/dist/react/watermark.js.map +1 -0
- package/dist/react.d.ts +40 -0
- package/dist/react.js +40 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +9 -0
- package/dist/vite.js.map +1 -0
- package/package.json +163 -4
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { METADATA_FIELDS, readPdfMetadata, writePdfMetadata } from './chunk-DKVOICYZ.js';
|
|
2
|
+
import { useSinglePdf } from './chunk-BK3MYSUS.js';
|
|
3
|
+
import { PdfToolPage, PdfToolColumns, SummaryList, SideTips, SelectedPdfCard, FileDropzone, fieldLabelClass, inputClass, StatusBanner, primaryButtonClass, secondaryButtonClass, BackToPdfTools } from './chunk-H6EERRCR.js';
|
|
4
|
+
import { downloadBlob, toBaseName, formatBytes } from './chunk-EKHEY3LI.js';
|
|
5
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
6
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
var FIELD_LABELS = {
|
|
9
|
+
title: "Title",
|
|
10
|
+
author: "Author",
|
|
11
|
+
subject: "Subject",
|
|
12
|
+
keywords: "Keywords",
|
|
13
|
+
creator: "Creator (app that made it)",
|
|
14
|
+
producer: "Producer (PDF library)"
|
|
15
|
+
};
|
|
16
|
+
var EMPTY_FIELDS = Object.fromEntries(METADATA_FIELDS.map((field) => [field, ""]));
|
|
17
|
+
function formatDate(date) {
|
|
18
|
+
return date ? date.toLocaleString() : "Not set";
|
|
19
|
+
}
|
|
20
|
+
function PdfMetadata() {
|
|
21
|
+
const [fields, setFields] = useState(EMPTY_FIELDS);
|
|
22
|
+
const [info, setInfo] = useState(null);
|
|
23
|
+
const [stripXmp, setStripXmp] = useState(true);
|
|
24
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
25
|
+
const [status, setStatus] = useState({
|
|
26
|
+
tone: "info",
|
|
27
|
+
text: "Add a PDF to view and edit its title, author, subject, and keywords."
|
|
28
|
+
});
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
document.title = "Edit PDF Metadata | Apps";
|
|
31
|
+
}, []);
|
|
32
|
+
const onError = useCallback((text) => setStatus({ tone: "error", text }), []);
|
|
33
|
+
const onLoaded = useCallback(async ({ file, bytes }) => {
|
|
34
|
+
try {
|
|
35
|
+
const meta = await readPdfMetadata(bytes);
|
|
36
|
+
setFields(Object.fromEntries(METADATA_FIELDS.map((field) => [field, meta[field]])));
|
|
37
|
+
setInfo(meta);
|
|
38
|
+
setStatus({ tone: "success", text: `${file.name} loaded. Edit the fields and save.` });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
setStatus({ tone: "error", text: error?.message || "Could not read this PDF." });
|
|
41
|
+
}
|
|
42
|
+
}, []);
|
|
43
|
+
const pdf = useSinglePdf({ onError, onLoaded });
|
|
44
|
+
const clearFile = () => {
|
|
45
|
+
pdf.clear();
|
|
46
|
+
setInfo(null);
|
|
47
|
+
setFields(EMPTY_FIELDS);
|
|
48
|
+
};
|
|
49
|
+
const handleSave = async (event) => {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
if (!pdf.bytes) return;
|
|
52
|
+
setIsSaving(true);
|
|
53
|
+
setStatus({ tone: "info", text: "Saving metadata..." });
|
|
54
|
+
try {
|
|
55
|
+
const bytes = await writePdfMetadata(pdf.bytes, fields, { stripXmp });
|
|
56
|
+
const blob = new Blob([bytes], { type: "application/pdf" });
|
|
57
|
+
downloadBlob(blob, `${toBaseName(pdf.file.name, "document")}.pdf`);
|
|
58
|
+
setStatus({
|
|
59
|
+
tone: "success",
|
|
60
|
+
text: `Saved (${formatBytes(blob.size)}). Your download has started.`
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
setStatus({ tone: "error", text: `Save failed: ${error?.message || "Please try again."}` });
|
|
64
|
+
} finally {
|
|
65
|
+
setIsSaving(false);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const busy = isSaving || pdf.isReading;
|
|
69
|
+
const ready = Boolean(pdf.bytes && info);
|
|
70
|
+
return /* @__PURE__ */ jsx(
|
|
71
|
+
PdfToolPage,
|
|
72
|
+
{
|
|
73
|
+
title: "Edit PDF Metadata",
|
|
74
|
+
description: "See and change the hidden document properties that viewers, search engines, and file managers show. Runs in your browser; files never leave your device.",
|
|
75
|
+
stats: [
|
|
76
|
+
{ value: pdf.pageCount || "-", label: "Pages" },
|
|
77
|
+
{ value: "PDF", label: "Output" }
|
|
78
|
+
],
|
|
79
|
+
children: /* @__PURE__ */ jsx(
|
|
80
|
+
PdfToolColumns,
|
|
81
|
+
{
|
|
82
|
+
main: /* @__PURE__ */ jsxs("form", { onSubmit: handleSave, children: [
|
|
83
|
+
pdf.file ? /* @__PURE__ */ jsx(
|
|
84
|
+
SelectedPdfCard,
|
|
85
|
+
{
|
|
86
|
+
signed: pdf.signed,
|
|
87
|
+
file: pdf.file,
|
|
88
|
+
pageCount: pdf.pageCount,
|
|
89
|
+
onRemove: clearFile,
|
|
90
|
+
disabled: isSaving
|
|
91
|
+
}
|
|
92
|
+
) : /* @__PURE__ */ jsx(
|
|
93
|
+
FileDropzone,
|
|
94
|
+
{
|
|
95
|
+
accept: ".pdf,application/pdf",
|
|
96
|
+
disabled: busy,
|
|
97
|
+
onFiles: pdf.selectFiles,
|
|
98
|
+
title: "Drag and drop a PDF here",
|
|
99
|
+
hint: "or click to browse from your device"
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
/* @__PURE__ */ jsxs("fieldset", { className: "mt-5 grid gap-4 sm:grid-cols-2", disabled: busy || !ready, children: [
|
|
103
|
+
METADATA_FIELDS.map((field) => /* @__PURE__ */ jsxs(
|
|
104
|
+
"label",
|
|
105
|
+
{
|
|
106
|
+
className: `${field === "title" || field === "keywords" ? "sm:col-span-2" : ""} ${fieldLabelClass}`,
|
|
107
|
+
children: [
|
|
108
|
+
FIELD_LABELS[field],
|
|
109
|
+
/* @__PURE__ */ jsx(
|
|
110
|
+
"input",
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
value: fields[field],
|
|
114
|
+
onChange: (event) => setFields((current) => ({ ...current, [field]: event.target.value })),
|
|
115
|
+
placeholder: field === "keywords" ? "Comma separated" : "",
|
|
116
|
+
className: `mt-2 ${inputClass}`
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
field
|
|
122
|
+
)),
|
|
123
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-start gap-3 text-sm text-slate-700 sm:col-span-2 dark:text-slate-300", children: [
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
"input",
|
|
126
|
+
{
|
|
127
|
+
type: "checkbox",
|
|
128
|
+
checked: stripXmp,
|
|
129
|
+
onChange: (event) => setStripXmp(event.target.checked),
|
|
130
|
+
className: "mt-0.5 h-4 w-4 accent-[#1F8CFF]"
|
|
131
|
+
}
|
|
132
|
+
),
|
|
133
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
134
|
+
"Remove the XMP copy of the metadata",
|
|
135
|
+
/* @__PURE__ */ jsx("span", { className: "block text-xs text-slate-500 dark:text-slate-400", children: info?.hasXmp ? "This file has one. Some apps read it instead of the fields above, so removing it avoids showing old values." : "This file has none." })
|
|
136
|
+
] })
|
|
137
|
+
] })
|
|
138
|
+
] }),
|
|
139
|
+
/* @__PURE__ */ jsx(StatusBanner, { status }),
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-5 flex flex-wrap gap-3", children: [
|
|
141
|
+
/* @__PURE__ */ jsxs("button", { type: "submit", disabled: busy || !ready, className: primaryButtonClass, children: [
|
|
142
|
+
/* @__PURE__ */ jsx("i", { className: `fa-solid ${isSaving ? "fa-spinner fa-spin" : "fa-floppy-disk"}` }),
|
|
143
|
+
isSaving ? "Saving..." : "Save PDF"
|
|
144
|
+
] }),
|
|
145
|
+
/* @__PURE__ */ jsxs(
|
|
146
|
+
"button",
|
|
147
|
+
{
|
|
148
|
+
type: "button",
|
|
149
|
+
onClick: () => setFields(EMPTY_FIELDS),
|
|
150
|
+
disabled: busy || !ready,
|
|
151
|
+
className: secondaryButtonClass,
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ jsx("i", { className: "fa-solid fa-eraser" }),
|
|
154
|
+
"Clear all fields"
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
),
|
|
158
|
+
/* @__PURE__ */ jsx(BackToPdfTools, {})
|
|
159
|
+
] })
|
|
160
|
+
] }),
|
|
161
|
+
side: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
162
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "File info" }),
|
|
163
|
+
/* @__PURE__ */ jsx(
|
|
164
|
+
SummaryList,
|
|
165
|
+
{
|
|
166
|
+
items: [
|
|
167
|
+
{ label: "Pages", value: info?.pageCount ?? "-" },
|
|
168
|
+
{ label: "Created", value: info ? formatDate(info.creationDate) : "-" },
|
|
169
|
+
{ label: "Modified", value: info ? formatDate(info.modificationDate) : "-" }
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
),
|
|
173
|
+
/* @__PURE__ */ jsx(
|
|
174
|
+
SideTips,
|
|
175
|
+
{
|
|
176
|
+
tips: [
|
|
177
|
+
"Clear all fields before sharing a file to remove names and app details.",
|
|
178
|
+
"Saving updates the modified date. Page content is not changed."
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
] })
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export { PdfMetadata };
|
|
190
|
+
//# sourceMappingURL=chunk-G467LFKC.js.map
|
|
191
|
+
//# sourceMappingURL=chunk-G467LFKC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/metadata/PdfMetadata.tsx"],"names":[],"mappings":";;;;;;;AAmBA,IAAM,YAAA,GAAe;AAAA,EACnB,KAAA,EAAO,OAAA;AAAA,EACP,MAAA,EAAQ,QAAA;AAAA,EACR,OAAA,EAAS,SAAA;AAAA,EACT,QAAA,EAAU,UAAA;AAAA,EACV,OAAA,EAAS,4BAAA;AAAA,EACT,QAAA,EAAU;AACZ,CAAA;AAEA,IAAM,YAAA,GAAe,MAAA,CAAO,WAAA,CAAY,eAAA,CAAgB,GAAA,CAAI,CAAC,KAAA,KAAU,CAAC,KAAA,EAAO,EAAE,CAAC,CAAC,CAAA;AAEnF,SAAS,WAAW,IAAA,EAAM;AACxB,EAAA,OAAO,IAAA,GAAO,IAAA,CAAK,cAAA,EAAe,GAAI,SAAA;AACxC;AAEe,SAAR,WAAA,GAA+B;AACpC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,YAAY,CAAA;AACjD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,IAAI,CAAA;AACrC,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,IAAI,CAAA;AAC7C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAC9C,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,QAAA,CAAS;AAAA,IACnC,IAAA,EAAM,MAAA;AAAA,IACN,IAAA,EAAM;AAAA,GACP,CAAA;AAED,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,QAAA,CAAS,KAAA,GAAQ,0BAAA;AAAA,EACnB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,CAAC,IAAA,KAAS,SAAA,CAAU,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,CAAA,EAAG,EAAE,CAAA;AAC5E,EAAA,MAAM,WAAW,WAAA,CAAY,OAAO,EAAE,IAAA,EAAM,OAAM,KAAM;AACtD,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,eAAA,CAAgB,KAAK,CAAA;AACxC,MAAA,SAAA,CAAU,MAAA,CAAO,WAAA,CAAY,eAAA,CAAgB,GAAA,CAAI,CAAC,KAAA,KAAU,CAAC,KAAA,EAAO,IAAA,CAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AAClF,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA,SAAA,CAAU,EAAE,MAAM,SAAA,EAAW,IAAA,EAAM,GAAG,IAAA,CAAK,IAAI,sCAAsC,CAAA;AAAA,IACvF,SAAS,KAAA,EAAO;AACd,MAAA,SAAA,CAAU,EAAE,IAAA,EAAM,OAAA,EAAS,MAAM,KAAA,EAAO,OAAA,IAAW,4BAA4B,CAAA;AAAA,IACjF;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AACL,EAAA,MAAM,GAAA,GAAM,YAAA,CAAa,EAAE,OAAA,EAAS,UAAU,CAAA;AAE9C,EAAA,MAAM,YAAY,MAAM;AACtB,IAAA,GAAA,CAAI,KAAA,EAAM;AACV,IAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,IAAA,SAAA,CAAU,YAAY,CAAA;AAAA,EACxB,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,OAAO,KAAA,KAAU;AAClC,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,IAAI,CAAC,IAAI,KAAA,EAAO;AAEhB,IAAA,WAAA,CAAY,IAAI,CAAA;AAChB,IAAA,SAAA,CAAU,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,sBAAsB,CAAA;AACtD,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,MAAM,gBAAA,CAAiB,GAAA,CAAI,OAAO,MAAA,EAAQ,EAAE,UAAU,CAAA;AACpE,MAAA,MAAM,IAAA,GAAO,IAAI,IAAA,CAAK,CAAC,KAAiB,CAAA,EAAG,EAAE,IAAA,EAAM,iBAAA,EAAmB,CAAA;AACtE,MAAA,YAAA,CAAa,IAAA,EAAM,GAAG,UAAA,CAAW,GAAA,CAAI,KAAK,IAAA,EAAM,UAAU,CAAC,CAAA,IAAA,CAAM,CAAA;AACjE,MAAA,SAAA,CAAU;AAAA,QACR,IAAA,EAAM,SAAA;AAAA,QACN,IAAA,EAAM,CAAA,OAAA,EAAU,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC,CAAA,6BAAA;AAAA,OACvC,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,SAAA,CAAU,EAAE,MAAM,OAAA,EAAS,IAAA,EAAM,gBAAgB,KAAA,EAAO,OAAA,IAAW,mBAAmB,CAAA,CAAA,EAAI,CAAA;AAAA,IAC5F,CAAA,SAAE;AACA,MAAA,WAAA,CAAY,KAAK,CAAA;AAAA,IACnB;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,IAAA,GAAO,YAAY,GAAA,CAAI,SAAA;AAC7B,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,KAAA,IAAS,IAAI,CAAA;AAEvC,EAAA,uBACE,GAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAM,mBAAA;AAAA,MACN,WAAA,EAAY,0JAAA;AAAA,MACZ,KAAA,EAAO;AAAA,QACL,EAAE,KAAA,EAAO,GAAA,CAAI,SAAA,IAAa,GAAA,EAAK,OAAO,OAAA,EAAQ;AAAA,QAC9C,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,QAAA;AAAS,OAClC;AAAA,MAEA,QAAA,kBAAA,GAAA;AAAA,QAAC,cAAA;AAAA,QAAA;AAAA,UACC,IAAA,kBACE,IAAA,CAAC,MAAA,EAAA,EAAK,QAAA,EAAU,UAAA,EACb,QAAA,EAAA;AAAA,YAAA,GAAA,CAAI,IAAA,mBACH,GAAA;AAAA,cAAC,eAAA;AAAA,cAAA;AAAA,gBACC,QAAQ,GAAA,CAAI,MAAA;AAAA,gBACZ,MAAM,GAAA,CAAI,IAAA;AAAA,gBACV,WAAW,GAAA,CAAI,SAAA;AAAA,gBACf,QAAA,EAAU,SAAA;AAAA,gBACV,QAAA,EAAU;AAAA;AAAA,aACZ,mBAEA,GAAA;AAAA,cAAC,YAAA;AAAA,cAAA;AAAA,gBACC,MAAA,EAAO,sBAAA;AAAA,gBACP,QAAA,EAAU,IAAA;AAAA,gBACV,SAAS,GAAA,CAAI,WAAA;AAAA,gBACb,KAAA,EAAM,0BAAA;AAAA,gBACN,IAAA,EAAK;AAAA;AAAA,aACP;AAAA,iCAGD,UAAA,EAAA,EAAS,SAAA,EAAU,kCAAiC,QAAA,EAAU,IAAA,IAAQ,CAAC,KAAA,EACrE,QAAA,EAAA;AAAA,cAAA,eAAA,CAAgB,GAAA,CAAI,CAAC,KAAA,qBACpB,IAAA;AAAA,gBAAC,OAAA;AAAA,gBAAA;AAAA,kBAEC,SAAA,EAAW,GAAG,KAAA,KAAU,OAAA,IAAW,UAAU,UAAA,GAAa,eAAA,GAAkB,EAAE,CAAA,CAAA,EAAI,eAAe,CAAA,CAAA;AAAA,kBAEhG,QAAA,EAAA;AAAA,oBAAA,YAAA,CAAa,KAAK,CAAA;AAAA,oCACnB,GAAA;AAAA,sBAAC,OAAA;AAAA,sBAAA;AAAA,wBACC,IAAA,EAAK,MAAA;AAAA,wBACL,KAAA,EAAO,OAAO,KAAK,CAAA;AAAA,wBACnB,QAAA,EAAU,CAAC,KAAA,KAAU,SAAA,CAAU,CAAC,OAAA,MAAa,EAAE,GAAG,OAAA,EAAS,CAAC,KAAK,GAAG,KAAA,CAAM,MAAA,CAAO,OAAM,CAAE,CAAA;AAAA,wBACzF,WAAA,EAAa,KAAA,KAAU,UAAA,GAAa,iBAAA,GAAoB,EAAA;AAAA,wBACxD,SAAA,EAAW,QAAQ,UAAU,CAAA;AAAA;AAAA;AAC/B;AAAA,iBAAA;AAAA,gBAVK;AAAA,eAYR,CAAA;AAAA,8BAED,IAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,iFAAA,EACf,QAAA,EAAA;AAAA,gCAAA,GAAA;AAAA,kBAAC,OAAA;AAAA,kBAAA;AAAA,oBACC,IAAA,EAAK,UAAA;AAAA,oBACL,OAAA,EAAS,QAAA;AAAA,oBACT,UAAU,CAAC,KAAA,KAAU,WAAA,CAAY,KAAA,CAAM,OAAO,OAAO,CAAA;AAAA,oBACrD,SAAA,EAAU;AAAA;AAAA,iBACZ;AAAA,qCACC,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,kBAAA,qCAAA;AAAA,sCAEH,MAAA,EAAA,EAAK,SAAA,EAAU,oDACb,QAAA,EAAA,IAAA,EAAM,MAAA,GACH,gHACA,qBAAA,EACN;AAAA,iBAAA,EACF;AAAA,eAAA,EACF;AAAA,aAAA,EACF,CAAA;AAAA,4BAEA,GAAA,CAAC,gBAAa,MAAA,EAAgB,CAAA;AAAA,4BAE9B,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,8BAAA,IAAA,CAAC,QAAA,EAAA,EAAO,MAAK,QAAA,EAAS,QAAA,EAAU,QAAQ,CAAC,KAAA,EAAO,WAAW,kBAAA,EACzD,QAAA,EAAA;AAAA,gCAAA,GAAA,CAAC,OAAE,SAAA,EAAW,CAAA,SAAA,EAAY,QAAA,GAAW,oBAAA,GAAuB,gBAAgB,CAAA,CAAA,EAAI,CAAA;AAAA,gBAC/E,WAAW,WAAA,GAAc;AAAA,eAAA,EAC5B,CAAA;AAAA,8BACA,IAAA;AAAA,gBAAC,QAAA;AAAA,gBAAA;AAAA,kBACC,IAAA,EAAK,QAAA;AAAA,kBACL,OAAA,EAAS,MAAM,SAAA,CAAU,YAAY,CAAA;AAAA,kBACrC,QAAA,EAAU,QAAQ,CAAC,KAAA;AAAA,kBACnB,SAAA,EAAW,oBAAA;AAAA,kBAEX,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,GAAA,EAAA,EAAE,WAAU,oBAAA,EAAqB,CAAA;AAAA,oBAAE;AAAA;AAAA;AAAA,eAEtC;AAAA,kCACC,cAAA,EAAA,EAAe;AAAA,aAAA,EAClB;AAAA,WAAA,EACF,CAAA;AAAA,UAEF,sBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,4DAAA,EAA6D,QAAA,EAAA,WAAA,EAAS,CAAA;AAAA,4BACpF,GAAA;AAAA,cAAC,WAAA;AAAA,cAAA;AAAA,gBACC,KAAA,EAAO;AAAA,kBACL,EAAE,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,IAAA,EAAM,aAAa,GAAA,EAAI;AAAA,kBAChD,EAAE,OAAO,SAAA,EAAW,KAAA,EAAO,OAAO,UAAA,CAAW,IAAA,CAAK,YAAY,CAAA,GAAI,GAAA,EAAI;AAAA,kBACtE,EAAE,OAAO,UAAA,EAAY,KAAA,EAAO,OAAO,UAAA,CAAW,IAAA,CAAK,gBAAgB,CAAA,GAAI,GAAA;AAAI;AAC7E;AAAA,aACF;AAAA,4BACA,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAM;AAAA,kBACJ,yEAAA;AAAA,kBACA;AAAA;AACF;AAAA;AACF,WAAA,EACF;AAAA;AAAA;AAEJ;AAAA,GACF;AAEJ","file":"chunk-G467LFKC.js","sourcesContent":["import React, { useCallback, useEffect, useState } from \"react\";\nimport { downloadBlob, formatBytes, toBaseName } from \"../shared/files\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SelectedPdfCard,\n SideTips,\n StatusBanner,\n SummaryList,\n fieldLabelClass,\n inputClass,\n primaryButtonClass,\n secondaryButtonClass,\n} from \"../shared/ToolLayout\";\nimport { useSinglePdf } from \"../shared/useSinglePdf\";\nimport { METADATA_FIELDS, readPdfMetadata, writePdfMetadata } from \"./pdfMetadataCore\";\n\nconst FIELD_LABELS = {\n title: \"Title\",\n author: \"Author\",\n subject: \"Subject\",\n keywords: \"Keywords\",\n creator: \"Creator (app that made it)\",\n producer: \"Producer (PDF library)\",\n};\n\nconst EMPTY_FIELDS = Object.fromEntries(METADATA_FIELDS.map((field) => [field, \"\"]));\n\nfunction formatDate(date) {\n return date ? date.toLocaleString() : \"Not set\";\n}\n\nexport default function PdfMetadata() {\n const [fields, setFields] = useState(EMPTY_FIELDS);\n const [info, setInfo] = useState(null);\n const [stripXmp, setStripXmp] = useState(true);\n const [isSaving, setIsSaving] = useState(false);\n const [status, setStatus] = useState({\n tone: \"info\",\n text: \"Add a PDF to view and edit its title, author, subject, and keywords.\",\n });\n\n useEffect(() => {\n document.title = \"Edit PDF Metadata | Apps\";\n }, []);\n\n const onError = useCallback((text) => setStatus({ tone: \"error\", text }), []);\n const onLoaded = useCallback(async ({ file, bytes }) => {\n try {\n const meta = await readPdfMetadata(bytes);\n setFields(Object.fromEntries(METADATA_FIELDS.map((field) => [field, meta[field]])));\n setInfo(meta);\n setStatus({ tone: \"success\", text: `${file.name} loaded. Edit the fields and save.` });\n } catch (error) {\n setStatus({ tone: \"error\", text: error?.message || \"Could not read this PDF.\" });\n }\n }, []);\n const pdf = useSinglePdf({ onError, onLoaded });\n\n const clearFile = () => {\n pdf.clear();\n setInfo(null);\n setFields(EMPTY_FIELDS);\n };\n\n const handleSave = async (event) => {\n event.preventDefault();\n if (!pdf.bytes) return;\n\n setIsSaving(true);\n setStatus({ tone: \"info\", text: \"Saving metadata...\" });\n try {\n const bytes = await writePdfMetadata(pdf.bytes, fields, { stripXmp });\n const blob = new Blob([bytes as BlobPart], { type: \"application/pdf\" });\n downloadBlob(blob, `${toBaseName(pdf.file.name, \"document\")}.pdf`);\n setStatus({\n tone: \"success\",\n text: `Saved (${formatBytes(blob.size)}). Your download has started.`,\n });\n } catch (error) {\n setStatus({ tone: \"error\", text: `Save failed: ${error?.message || \"Please try again.\"}` });\n } finally {\n setIsSaving(false);\n }\n };\n\n const busy = isSaving || pdf.isReading;\n const ready = Boolean(pdf.bytes && info);\n\n return (\n <PdfToolPage\n title=\"Edit PDF Metadata\"\n description=\"See and change the hidden document properties that viewers, search engines, and file managers show. Runs in your browser; files never leave your device.\"\n stats={[\n { value: pdf.pageCount || \"-\", label: \"Pages\" },\n { value: \"PDF\", label: \"Output\" },\n ]}\n >\n <PdfToolColumns\n main={\n <form onSubmit={handleSave}>\n {pdf.file ? (\n <SelectedPdfCard\n signed={pdf.signed}\n file={pdf.file}\n pageCount={pdf.pageCount}\n onRemove={clearFile}\n disabled={isSaving}\n />\n ) : (\n <FileDropzone\n accept=\".pdf,application/pdf\"\n disabled={busy}\n onFiles={pdf.selectFiles}\n title=\"Drag and drop a PDF here\"\n hint=\"or click to browse from your device\"\n />\n )}\n\n <fieldset className=\"mt-5 grid gap-4 sm:grid-cols-2\" disabled={busy || !ready}>\n {METADATA_FIELDS.map((field) => (\n <label\n key={field}\n className={`${field === \"title\" || field === \"keywords\" ? \"sm:col-span-2\" : \"\"} ${fieldLabelClass}`}\n >\n {FIELD_LABELS[field]}\n <input\n type=\"text\"\n value={fields[field]}\n onChange={(event) => setFields((current) => ({ ...current, [field]: event.target.value }))}\n placeholder={field === \"keywords\" ? \"Comma separated\" : \"\"}\n className={`mt-2 ${inputClass}`}\n />\n </label>\n ))}\n\n <label className=\"flex items-start gap-3 text-sm text-slate-700 sm:col-span-2 dark:text-slate-300\">\n <input\n type=\"checkbox\"\n checked={stripXmp}\n onChange={(event) => setStripXmp(event.target.checked)}\n className=\"mt-0.5 h-4 w-4 accent-[#1F8CFF]\"\n />\n <span>\n Remove the XMP copy of the metadata\n <span className=\"block text-xs text-slate-500 dark:text-slate-400\">\n {info?.hasXmp\n ? \"This file has one. Some apps read it instead of the fields above, so removing it avoids showing old values.\"\n : \"This file has none.\"}\n </span>\n </span>\n </label>\n </fieldset>\n\n <StatusBanner status={status} />\n\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <button type=\"submit\" disabled={busy || !ready} className={primaryButtonClass}>\n <i className={`fa-solid ${isSaving ? \"fa-spinner fa-spin\" : \"fa-floppy-disk\"}`} />\n {isSaving ? \"Saving...\" : \"Save PDF\"}\n </button>\n <button\n type=\"button\"\n onClick={() => setFields(EMPTY_FIELDS)}\n disabled={busy || !ready}\n className={secondaryButtonClass}\n >\n <i className=\"fa-solid fa-eraser\" />\n Clear all fields\n </button>\n <BackToPdfTools />\n </div>\n </form>\n }\n side={\n <>\n <h2 className=\"text-base font-semibold text-slate-900 dark:text-slate-100\">File info</h2>\n <SummaryList\n items={[\n { label: \"Pages\", value: info?.pageCount ?? \"-\" },\n { label: \"Created\", value: info ? formatDate(info.creationDate) : \"-\" },\n { label: \"Modified\", value: info ? formatDate(info.modificationDate) : \"-\" },\n ]}\n />\n <SideTips\n tips={[\n \"Clear all fields before sharing a file to remove names and app details.\",\n \"Saving updates the modified date. Page content is not changed.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { planSplit, splitPdf } from './chunk-TV3E4YUF.js';
|
|
2
|
+
import { createZip } from './chunk-7V4SWTUK.js';
|
|
3
|
+
import { useSinglePdf } from './chunk-BK3MYSUS.js';
|
|
4
|
+
import { PdfToolPage, PdfToolColumns, cardClass, SummaryList, SideTips, SelectedPdfCard, FileDropzone, fieldLabelClass, inputClass, StatusBanner, primaryButtonClass, BackToPdfTools } from './chunk-H6EERRCR.js';
|
|
5
|
+
import { toBaseName, downloadBlob, formatBytes } from './chunk-EKHEY3LI.js';
|
|
6
|
+
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
7
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
var MODES = [
|
|
10
|
+
{
|
|
11
|
+
id: "ranges",
|
|
12
|
+
label: "By ranges",
|
|
13
|
+
hint: "One PDF per range."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "every",
|
|
17
|
+
label: "Every N pages",
|
|
18
|
+
hint: "Equal chunks, e.g. every page."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "extract",
|
|
22
|
+
label: "Extract pages",
|
|
23
|
+
hint: "Selected pages into one PDF."
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
function describePages(indices) {
|
|
27
|
+
const first = indices[0] + 1;
|
|
28
|
+
const last = indices[indices.length - 1] + 1;
|
|
29
|
+
if (indices.length === 1) return `Page ${first}`;
|
|
30
|
+
const contiguous = indices.every((value, i) => i === 0 || value === indices[i - 1] + 1);
|
|
31
|
+
return contiguous ? `Pages ${first}-${last}` : `${indices.length} pages`;
|
|
32
|
+
}
|
|
33
|
+
function SplitPdf() {
|
|
34
|
+
const [mode, setMode] = useState("ranges");
|
|
35
|
+
const [ranges, setRanges] = useState("");
|
|
36
|
+
const [every, setEvery] = useState("1");
|
|
37
|
+
const [outputName, setOutputName] = useState("");
|
|
38
|
+
const [isSplitting, setIsSplitting] = useState(false);
|
|
39
|
+
const [status, setStatus] = useState({
|
|
40
|
+
tone: "info",
|
|
41
|
+
text: "Add a PDF, choose how to split it, and download the parts."
|
|
42
|
+
});
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
document.title = "Split PDF | Apps";
|
|
45
|
+
}, []);
|
|
46
|
+
const onError = useCallback((text) => setStatus({ tone: "error", text }), []);
|
|
47
|
+
const onLoaded = useCallback(({ file, pageCount }) => {
|
|
48
|
+
setOutputName(toBaseName(file.name, "document"));
|
|
49
|
+
setRanges("");
|
|
50
|
+
setStatus({
|
|
51
|
+
tone: "success",
|
|
52
|
+
text: `${file.name} loaded with ${pageCount} page${pageCount === 1 ? "" : "s"}.`
|
|
53
|
+
});
|
|
54
|
+
}, []);
|
|
55
|
+
const pdf = useSinglePdf({ onError, onLoaded });
|
|
56
|
+
const plan = useMemo(() => {
|
|
57
|
+
if (!pdf.pageCount) return { groups: [], error: "" };
|
|
58
|
+
try {
|
|
59
|
+
return { groups: planSplit(pdf.pageCount, { mode, ranges, every: Number(every) }), error: "" };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return { groups: [], error: error.message };
|
|
62
|
+
}
|
|
63
|
+
}, [pdf.pageCount, mode, ranges, every]);
|
|
64
|
+
const handleSplit = async (event) => {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
if (!pdf.bytes || !plan.groups.length) return;
|
|
67
|
+
setIsSplitting(true);
|
|
68
|
+
setStatus({ tone: "info", text: "Splitting your PDF..." });
|
|
69
|
+
try {
|
|
70
|
+
const baseName = toBaseName(outputName, "document");
|
|
71
|
+
const outputs = await splitPdf(pdf.bytes, {
|
|
72
|
+
mode,
|
|
73
|
+
ranges,
|
|
74
|
+
every: Number(every),
|
|
75
|
+
baseName
|
|
76
|
+
});
|
|
77
|
+
let blob;
|
|
78
|
+
if (outputs.length === 1) {
|
|
79
|
+
blob = new Blob([outputs[0].bytes], { type: "application/pdf" });
|
|
80
|
+
downloadBlob(blob, outputs[0].name);
|
|
81
|
+
} else {
|
|
82
|
+
blob = new Blob([createZip(outputs)], { type: "application/zip" });
|
|
83
|
+
downloadBlob(blob, `${baseName}-split.zip`);
|
|
84
|
+
}
|
|
85
|
+
setStatus({
|
|
86
|
+
tone: "success",
|
|
87
|
+
text: outputs.length === 1 ? `Created ${outputs[0].name} (${formatBytes(blob.size)}). Your download has started.` : `Created ${outputs.length} PDFs in a ZIP (${formatBytes(blob.size)}). Your download has started.`
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
setStatus({ tone: "error", text: `Split failed: ${error?.message || "Please try again."}` });
|
|
91
|
+
} finally {
|
|
92
|
+
setIsSplitting(false);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const busy = isSplitting || pdf.isReading;
|
|
96
|
+
const rangePlaceholder = pdf.pageCount > 1 ? `e.g. 1-${Math.ceil(pdf.pageCount / 2)}, ${Math.ceil(pdf.pageCount / 2) + 1}-` : "e.g. 1";
|
|
97
|
+
return /* @__PURE__ */ jsx(
|
|
98
|
+
PdfToolPage,
|
|
99
|
+
{
|
|
100
|
+
title: "Split PDF",
|
|
101
|
+
description: "Break a PDF into parts by page ranges or fixed chunks, or pull out just the pages you need. Runs in your browser; files never leave your device.",
|
|
102
|
+
stats: [
|
|
103
|
+
{ value: pdf.pageCount || "-", label: "Pages" },
|
|
104
|
+
{ value: plan.groups.length || "-", label: "Outputs" }
|
|
105
|
+
],
|
|
106
|
+
children: /* @__PURE__ */ jsx(
|
|
107
|
+
PdfToolColumns,
|
|
108
|
+
{
|
|
109
|
+
main: /* @__PURE__ */ jsxs("form", { onSubmit: handleSplit, children: [
|
|
110
|
+
pdf.file ? /* @__PURE__ */ jsx(
|
|
111
|
+
SelectedPdfCard,
|
|
112
|
+
{
|
|
113
|
+
signed: pdf.signed,
|
|
114
|
+
file: pdf.file,
|
|
115
|
+
pageCount: pdf.pageCount,
|
|
116
|
+
onRemove: pdf.clear,
|
|
117
|
+
disabled: isSplitting
|
|
118
|
+
}
|
|
119
|
+
) : /* @__PURE__ */ jsx(
|
|
120
|
+
FileDropzone,
|
|
121
|
+
{
|
|
122
|
+
accept: ".pdf,application/pdf",
|
|
123
|
+
disabled: busy,
|
|
124
|
+
onFiles: pdf.selectFiles,
|
|
125
|
+
title: "Drag and drop a PDF here",
|
|
126
|
+
hint: "or click to browse from your device"
|
|
127
|
+
}
|
|
128
|
+
),
|
|
129
|
+
/* @__PURE__ */ jsxs("fieldset", { className: "mt-5", disabled: busy || !pdf.pageCount, children: [
|
|
130
|
+
/* @__PURE__ */ jsx("legend", { className: fieldLabelClass, children: "Split mode" }),
|
|
131
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 grid gap-2 sm:grid-cols-3", children: MODES.map((option) => /* @__PURE__ */ jsxs(
|
|
132
|
+
"label",
|
|
133
|
+
{
|
|
134
|
+
className: `cursor-pointer rounded-xl border p-3 text-sm transition ${mode === option.id ? "border-[#1F8CFF] bg-blue-50 dark:bg-blue-500/10" : "border-slate-200 bg-white hover:border-slate-300 dark:border-slate-700 dark:bg-slate-900"}`,
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ jsx(
|
|
137
|
+
"input",
|
|
138
|
+
{
|
|
139
|
+
type: "radio",
|
|
140
|
+
name: "split-mode",
|
|
141
|
+
value: option.id,
|
|
142
|
+
checked: mode === option.id,
|
|
143
|
+
onChange: () => setMode(option.id),
|
|
144
|
+
className: "sr-only"
|
|
145
|
+
}
|
|
146
|
+
),
|
|
147
|
+
/* @__PURE__ */ jsx("span", { className: "block font-semibold text-slate-900 dark:text-slate-100", children: option.label }),
|
|
148
|
+
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-xs text-slate-600 dark:text-slate-300", children: option.hint })
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
option.id
|
|
152
|
+
)) }),
|
|
153
|
+
mode === "every" ? /* @__PURE__ */ jsxs("label", { className: `mt-4 ${fieldLabelClass}`, children: [
|
|
154
|
+
"Pages per file",
|
|
155
|
+
/* @__PURE__ */ jsx(
|
|
156
|
+
"input",
|
|
157
|
+
{
|
|
158
|
+
type: "number",
|
|
159
|
+
min: 1,
|
|
160
|
+
max: pdf.pageCount || void 0,
|
|
161
|
+
value: every,
|
|
162
|
+
onChange: (event) => setEvery(event.target.value),
|
|
163
|
+
className: `mt-2 ${inputClass}`
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
] }) : /* @__PURE__ */ jsxs("label", { className: `mt-4 ${fieldLabelClass}`, children: [
|
|
167
|
+
mode === "extract" ? "Pages to extract" : "Page ranges",
|
|
168
|
+
/* @__PURE__ */ jsx(
|
|
169
|
+
"input",
|
|
170
|
+
{
|
|
171
|
+
type: "text",
|
|
172
|
+
value: ranges,
|
|
173
|
+
onChange: (event) => setRanges(event.target.value),
|
|
174
|
+
placeholder: rangePlaceholder,
|
|
175
|
+
className: `mt-2 ${inputClass}`
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
] }),
|
|
179
|
+
/* @__PURE__ */ jsxs("label", { className: `mt-4 ${fieldLabelClass}`, children: [
|
|
180
|
+
"File name prefix",
|
|
181
|
+
/* @__PURE__ */ jsx(
|
|
182
|
+
"input",
|
|
183
|
+
{
|
|
184
|
+
type: "text",
|
|
185
|
+
value: outputName,
|
|
186
|
+
onChange: (event) => setOutputName(event.target.value),
|
|
187
|
+
placeholder: "document",
|
|
188
|
+
className: `mt-2 ${inputClass}`
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
] })
|
|
192
|
+
] }),
|
|
193
|
+
pdf.pageCount && plan.error && (ranges.trim() || mode === "every") ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-xs text-rose-700 dark:text-rose-300", children: plan.error }) : null,
|
|
194
|
+
/* @__PURE__ */ jsx(StatusBanner, { status }),
|
|
195
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-5 flex flex-wrap gap-3", children: [
|
|
196
|
+
/* @__PURE__ */ jsxs(
|
|
197
|
+
"button",
|
|
198
|
+
{
|
|
199
|
+
type: "submit",
|
|
200
|
+
disabled: busy || !plan.groups.length,
|
|
201
|
+
className: primaryButtonClass,
|
|
202
|
+
children: [
|
|
203
|
+
/* @__PURE__ */ jsx("i", { className: `fa-solid ${isSplitting ? "fa-spinner fa-spin" : "fa-scissors"}` }),
|
|
204
|
+
isSplitting ? "Splitting..." : "Split PDF"
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
),
|
|
208
|
+
/* @__PURE__ */ jsx(BackToPdfTools, {})
|
|
209
|
+
] })
|
|
210
|
+
] }),
|
|
211
|
+
side: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
212
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "Output" }),
|
|
213
|
+
plan.groups.length ? /* @__PURE__ */ jsx("ol", { className: "mt-4 max-h-80 space-y-2 overflow-y-auto pr-1", children: plan.groups.map((indices, index) => /* @__PURE__ */ jsxs("li", { className: `${cardClass} !p-2.5 text-sm`, children: [
|
|
214
|
+
/* @__PURE__ */ jsxs("p", { className: "font-medium text-slate-900 dark:text-slate-100", children: [
|
|
215
|
+
"File ",
|
|
216
|
+
index + 1
|
|
217
|
+
] }),
|
|
218
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500 dark:text-slate-400", children: describePages(indices) })
|
|
219
|
+
] }, index)) }) : /* @__PURE__ */ jsx(
|
|
220
|
+
SummaryList,
|
|
221
|
+
{
|
|
222
|
+
items: [{ label: "Files to create", value: pdf.pageCount ? 0 : "-" }]
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
/* @__PURE__ */ jsx(
|
|
226
|
+
SideTips,
|
|
227
|
+
{
|
|
228
|
+
tips: [
|
|
229
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
230
|
+
"Ranges: ",
|
|
231
|
+
/* @__PURE__ */ jsx("code", { children: "1-3, 4-6" }),
|
|
232
|
+
" makes two files. ",
|
|
233
|
+
/* @__PURE__ */ jsx("code", { children: "8-" }),
|
|
234
|
+
" means 8 to the end."
|
|
235
|
+
] }),
|
|
236
|
+
"More than one output downloads as a ZIP.",
|
|
237
|
+
"Extract keeps pages in the order you type them."
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
] })
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export { SplitPdf };
|
|
249
|
+
//# sourceMappingURL=chunk-GLYWSVSB.js.map
|
|
250
|
+
//# sourceMappingURL=chunk-GLYWSVSB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/split/SplitPdf.tsx"],"names":[],"mappings":"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","file":"chunk-GLYWSVSB.js","sourcesContent":["import React, { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { downloadBlob, formatBytes, toBaseName } from \"../shared/files\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SelectedPdfCard,\n SideTips,\n StatusBanner,\n SummaryList,\n cardClass,\n fieldLabelClass,\n inputClass,\n primaryButtonClass,\n} from \"../shared/ToolLayout\";\nimport { useSinglePdf } from \"../shared/useSinglePdf\";\nimport { createZip } from \"../shared/zip\";\nimport { planSplit, splitPdf, type SplitMode } from \"./splitPdfCore\";\n\nconst MODES = [\n {\n id: \"ranges\",\n label: \"By ranges\",\n hint: \"One PDF per range.\",\n },\n {\n id: \"every\",\n label: \"Every N pages\",\n hint: \"Equal chunks, e.g. every page.\",\n },\n {\n id: \"extract\",\n label: \"Extract pages\",\n hint: \"Selected pages into one PDF.\",\n },\n];\n\nfunction describePages(indices) {\n const first = indices[0] + 1;\n const last = indices[indices.length - 1] + 1;\n if (indices.length === 1) return `Page ${first}`;\n const contiguous = indices.every((value, i) => i === 0 || value === indices[i - 1] + 1);\n return contiguous ? `Pages ${first}-${last}` : `${indices.length} pages`;\n}\n\nexport default function SplitPdf() {\n const [mode, setMode] = useState<SplitMode>(\"ranges\");\n const [ranges, setRanges] = useState(\"\");\n const [every, setEvery] = useState(\"1\");\n const [outputName, setOutputName] = useState(\"\");\n const [isSplitting, setIsSplitting] = useState(false);\n const [status, setStatus] = useState({\n tone: \"info\",\n text: \"Add a PDF, choose how to split it, and download the parts.\",\n });\n\n useEffect(() => {\n document.title = \"Split PDF | Apps\";\n }, []);\n\n const onError = useCallback((text) => setStatus({ tone: \"error\", text }), []);\n const onLoaded = useCallback(({ file, pageCount }) => {\n setOutputName(toBaseName(file.name, \"document\"));\n setRanges(\"\");\n setStatus({\n tone: \"success\",\n text: `${file.name} loaded with ${pageCount} page${pageCount === 1 ? \"\" : \"s\"}.`,\n });\n }, []);\n const pdf = useSinglePdf({ onError, onLoaded });\n\n const plan = useMemo(() => {\n if (!pdf.pageCount) return { groups: [], error: \"\" };\n try {\n return { groups: planSplit(pdf.pageCount, { mode, ranges, every: Number(every) }), error: \"\" };\n } catch (error) {\n return { groups: [], error: error.message };\n }\n }, [pdf.pageCount, mode, ranges, every]);\n\n const handleSplit = async (event) => {\n event.preventDefault();\n if (!pdf.bytes || !plan.groups.length) return;\n\n setIsSplitting(true);\n setStatus({ tone: \"info\", text: \"Splitting your PDF...\" });\n\n try {\n const baseName = toBaseName(outputName, \"document\");\n const outputs = await splitPdf(pdf.bytes, {\n mode,\n ranges,\n every: Number(every),\n baseName,\n });\n\n let blob;\n if (outputs.length === 1) {\n blob = new Blob([outputs[0].bytes as BlobPart], { type: \"application/pdf\" });\n downloadBlob(blob, outputs[0].name);\n } else {\n blob = new Blob([createZip(outputs) as BlobPart], { type: \"application/zip\" });\n downloadBlob(blob, `${baseName}-split.zip`);\n }\n\n setStatus({\n tone: \"success\",\n text:\n outputs.length === 1\n ? `Created ${outputs[0].name} (${formatBytes(blob.size)}). Your download has started.`\n : `Created ${outputs.length} PDFs in a ZIP (${formatBytes(blob.size)}). Your download has started.`,\n });\n } catch (error) {\n setStatus({ tone: \"error\", text: `Split failed: ${error?.message || \"Please try again.\"}` });\n } finally {\n setIsSplitting(false);\n }\n };\n\n const busy = isSplitting || pdf.isReading;\n const rangePlaceholder =\n pdf.pageCount > 1 ? `e.g. 1-${Math.ceil(pdf.pageCount / 2)}, ${Math.ceil(pdf.pageCount / 2) + 1}-` : \"e.g. 1\";\n\n return (\n <PdfToolPage\n title=\"Split PDF\"\n description=\"Break a PDF into parts by page ranges or fixed chunks, or pull out just the pages you need. Runs in your browser; files never leave your device.\"\n stats={[\n { value: pdf.pageCount || \"-\", label: \"Pages\" },\n { value: plan.groups.length || \"-\", label: \"Outputs\" },\n ]}\n >\n <PdfToolColumns\n main={\n <form onSubmit={handleSplit}>\n {pdf.file ? (\n <SelectedPdfCard\n signed={pdf.signed}\n file={pdf.file}\n pageCount={pdf.pageCount}\n onRemove={pdf.clear}\n disabled={isSplitting}\n />\n ) : (\n <FileDropzone\n accept=\".pdf,application/pdf\"\n disabled={busy}\n onFiles={pdf.selectFiles}\n title=\"Drag and drop a PDF here\"\n hint=\"or click to browse from your device\"\n />\n )}\n\n <fieldset className=\"mt-5\" disabled={busy || !pdf.pageCount}>\n <legend className={fieldLabelClass}>Split mode</legend>\n <div className=\"mt-2 grid gap-2 sm:grid-cols-3\">\n {MODES.map((option) => (\n <label\n key={option.id}\n className={`cursor-pointer rounded-xl border p-3 text-sm transition ${\n mode === option.id\n ? \"border-[#1F8CFF] bg-blue-50 dark:bg-blue-500/10\"\n : \"border-slate-200 bg-white hover:border-slate-300 dark:border-slate-700 dark:bg-slate-900\"\n }`}\n >\n <input\n type=\"radio\"\n name=\"split-mode\"\n value={option.id}\n checked={mode === option.id}\n onChange={() => setMode(option.id as SplitMode)}\n className=\"sr-only\"\n />\n <span className=\"block font-semibold text-slate-900 dark:text-slate-100\">\n {option.label}\n </span>\n <span className=\"mt-1 block text-xs text-slate-600 dark:text-slate-300\">\n {option.hint}\n </span>\n </label>\n ))}\n </div>\n\n {mode === \"every\" ? (\n <label className={`mt-4 ${fieldLabelClass}`}>\n Pages per file\n <input\n type=\"number\"\n min={1}\n max={pdf.pageCount || undefined}\n value={every}\n onChange={(event) => setEvery(event.target.value)}\n className={`mt-2 ${inputClass}`}\n />\n </label>\n ) : (\n <label className={`mt-4 ${fieldLabelClass}`}>\n {mode === \"extract\" ? \"Pages to extract\" : \"Page ranges\"}\n <input\n type=\"text\"\n value={ranges}\n onChange={(event) => setRanges(event.target.value)}\n placeholder={rangePlaceholder}\n className={`mt-2 ${inputClass}`}\n />\n </label>\n )}\n\n <label className={`mt-4 ${fieldLabelClass}`}>\n File name prefix\n <input\n type=\"text\"\n value={outputName}\n onChange={(event) => setOutputName(event.target.value)}\n placeholder=\"document\"\n className={`mt-2 ${inputClass}`}\n />\n </label>\n </fieldset>\n\n {pdf.pageCount && plan.error && (ranges.trim() || mode === \"every\") ? (\n <p className=\"mt-3 text-xs text-rose-700 dark:text-rose-300\">{plan.error}</p>\n ) : null}\n\n <StatusBanner status={status} />\n\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <button\n type=\"submit\"\n disabled={busy || !plan.groups.length}\n className={primaryButtonClass}\n >\n <i className={`fa-solid ${isSplitting ? \"fa-spinner fa-spin\" : \"fa-scissors\"}`} />\n {isSplitting ? \"Splitting...\" : \"Split PDF\"}\n </button>\n <BackToPdfTools />\n </div>\n </form>\n }\n side={\n <>\n <h2 className=\"text-base font-semibold text-slate-900 dark:text-slate-100\">Output</h2>\n {plan.groups.length ? (\n <ol className=\"mt-4 max-h-80 space-y-2 overflow-y-auto pr-1\">\n {plan.groups.map((indices, index) => (\n <li key={index} className={`${cardClass} !p-2.5 text-sm`}>\n <p className=\"font-medium text-slate-900 dark:text-slate-100\">\n File {index + 1}\n </p>\n <p className=\"text-xs text-slate-500 dark:text-slate-400\">\n {describePages(indices)}\n </p>\n </li>\n ))}\n </ol>\n ) : (\n <SummaryList\n items={[{ label: \"Files to create\", value: pdf.pageCount ? 0 : \"-\" }]}\n />\n )}\n <SideTips\n tips={[\n <>\n Ranges: <code>1-3, 4-6</code> makes two files. <code>8-</code> means 8 to the end.\n </>,\n \"More than one output downloads as a ZIP.\",\n \"Extract keeps pages in the order you type them.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { normalizeRotation } from './chunk-PMH7OWPF.js';
|
|
2
|
+
import { loadPdf } from './chunk-EKHEY3LI.js';
|
|
3
|
+
import { degrees, PDFDict, PDFNumber, PDFArray, PDFRef, PDFName } from 'pdf-lib';
|
|
4
|
+
|
|
5
|
+
async function organizePdf(bytes, edits) {
|
|
6
|
+
if (!edits.length) {
|
|
7
|
+
throw new Error("Keep at least one page.");
|
|
8
|
+
}
|
|
9
|
+
const doc = await loadPdf(bytes);
|
|
10
|
+
const pages = doc.getPages();
|
|
11
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12
|
+
for (const edit of edits) {
|
|
13
|
+
if (!Number.isInteger(edit.source) || edit.source < 0 || edit.source >= pages.length) {
|
|
14
|
+
throw new Error(`Page ${edit.source + 1} does not exist.`);
|
|
15
|
+
}
|
|
16
|
+
if (seen.has(edit.source)) {
|
|
17
|
+
throw new Error(`Page ${edit.source + 1} is listed twice.`);
|
|
18
|
+
}
|
|
19
|
+
seen.add(edit.source);
|
|
20
|
+
}
|
|
21
|
+
const removed = pages.filter((_, index) => !seen.has(index));
|
|
22
|
+
for (let index = pages.length - 1; index >= 0; index -= 1) {
|
|
23
|
+
doc.removePage(index);
|
|
24
|
+
}
|
|
25
|
+
for (const edit of edits) {
|
|
26
|
+
const page = pages[edit.source];
|
|
27
|
+
const turn = normalizeRotation(edit.rotation || 0);
|
|
28
|
+
if (turn) {
|
|
29
|
+
page.setRotation(degrees(normalizeRotation(page.getRotation().angle + turn)));
|
|
30
|
+
}
|
|
31
|
+
doc.addPage(page);
|
|
32
|
+
}
|
|
33
|
+
pruneDanglingReferences(doc, removed);
|
|
34
|
+
return doc.save();
|
|
35
|
+
}
|
|
36
|
+
var N = (name) => PDFName.of(name);
|
|
37
|
+
function destinationPage(item) {
|
|
38
|
+
let dest = item.lookup(N("Dest"));
|
|
39
|
+
const action = item.lookup(N("A"));
|
|
40
|
+
if (!dest && action instanceof PDFDict && action.lookup(N("S")) === N("GoTo")) {
|
|
41
|
+
dest = action.lookup(N("D"));
|
|
42
|
+
}
|
|
43
|
+
if (dest instanceof PDFArray && dest.size() > 0) {
|
|
44
|
+
const target = dest.get(0);
|
|
45
|
+
return target instanceof PDFRef ? target.toString() : null;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function pruneOutlineLevel(doc, parent, removed) {
|
|
50
|
+
const kept = [];
|
|
51
|
+
let ref = parent.get(N("First"));
|
|
52
|
+
const visited = /* @__PURE__ */ new Set();
|
|
53
|
+
while (ref instanceof PDFRef && !visited.has(ref.toString())) {
|
|
54
|
+
visited.add(ref.toString());
|
|
55
|
+
const item = doc.context.lookup(ref);
|
|
56
|
+
if (!(item instanceof PDFDict)) break;
|
|
57
|
+
const next = item.get(N("Next"));
|
|
58
|
+
const childCount = pruneOutlineLevel(doc, item, removed);
|
|
59
|
+
const hasChildren = item.get(N("First")) instanceof PDFRef;
|
|
60
|
+
const target = destinationPage(item);
|
|
61
|
+
if (target && removed.has(target)) {
|
|
62
|
+
if (hasChildren) {
|
|
63
|
+
item.delete(N("Dest"));
|
|
64
|
+
item.delete(N("A"));
|
|
65
|
+
kept.push({ ref, item });
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
kept.push({ ref, item });
|
|
69
|
+
}
|
|
70
|
+
const previous = item.lookup(N("Count"));
|
|
71
|
+
if (hasChildren) {
|
|
72
|
+
const open = !(previous instanceof PDFNumber) || previous.asNumber() >= 0;
|
|
73
|
+
item.set(N("Count"), PDFNumber.of(open ? childCount : -childCount));
|
|
74
|
+
} else {
|
|
75
|
+
item.delete(N("Count"));
|
|
76
|
+
}
|
|
77
|
+
ref = next;
|
|
78
|
+
}
|
|
79
|
+
kept.forEach(({ item }, index) => {
|
|
80
|
+
if (index > 0) item.set(N("Prev"), kept[index - 1].ref);
|
|
81
|
+
else item.delete(N("Prev"));
|
|
82
|
+
if (index < kept.length - 1) item.set(N("Next"), kept[index + 1].ref);
|
|
83
|
+
else item.delete(N("Next"));
|
|
84
|
+
});
|
|
85
|
+
if (kept.length) {
|
|
86
|
+
parent.set(N("First"), kept[0].ref);
|
|
87
|
+
parent.set(N("Last"), kept[kept.length - 1].ref);
|
|
88
|
+
} else {
|
|
89
|
+
parent.delete(N("First"));
|
|
90
|
+
parent.delete(N("Last"));
|
|
91
|
+
}
|
|
92
|
+
return kept.reduce((sum, { item }) => {
|
|
93
|
+
const count = item.lookup(N("Count"));
|
|
94
|
+
return sum + 1 + (count instanceof PDFNumber && count.asNumber() > 0 ? count.asNumber() : 0);
|
|
95
|
+
}, 0);
|
|
96
|
+
}
|
|
97
|
+
function pruneField(doc, ref, removedWidgets) {
|
|
98
|
+
if (!(ref instanceof PDFRef)) return true;
|
|
99
|
+
const field = doc.context.lookup(ref);
|
|
100
|
+
if (!(field instanceof PDFDict)) return true;
|
|
101
|
+
const kids = field.lookup(N("Kids"));
|
|
102
|
+
if (kids instanceof PDFArray) {
|
|
103
|
+
const keptKids = kids.asArray().filter((kid) => pruneField(doc, kid, removedWidgets));
|
|
104
|
+
if (!keptKids.length) return false;
|
|
105
|
+
field.set(N("Kids"), doc.context.obj(keptKids));
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return !removedWidgets.has(ref.toString());
|
|
109
|
+
}
|
|
110
|
+
function pruneDanglingReferences(doc, removedPages) {
|
|
111
|
+
if (!removedPages.length) return;
|
|
112
|
+
const removedRefs = new Set(removedPages.map((page) => page.ref.toString()));
|
|
113
|
+
const outlines = doc.catalog.lookup(N("Outlines"));
|
|
114
|
+
if (outlines instanceof PDFDict) {
|
|
115
|
+
const visible = pruneOutlineLevel(doc, outlines, removedRefs);
|
|
116
|
+
if (visible) outlines.set(N("Count"), PDFNumber.of(visible));
|
|
117
|
+
else outlines.delete(N("Count"));
|
|
118
|
+
}
|
|
119
|
+
const removedWidgets = /* @__PURE__ */ new Set();
|
|
120
|
+
for (const page of removedPages) {
|
|
121
|
+
const annots = page.node.lookup(N("Annots"));
|
|
122
|
+
if (annots instanceof PDFArray) {
|
|
123
|
+
annots.asArray().forEach((annot) => annot instanceof PDFRef && removedWidgets.add(annot.toString()));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const acroForm = doc.catalog.lookup(N("AcroForm"));
|
|
127
|
+
const fields = acroForm instanceof PDFDict ? acroForm.lookup(N("Fields")) : null;
|
|
128
|
+
if (removedWidgets.size && acroForm instanceof PDFDict && fields instanceof PDFArray) {
|
|
129
|
+
acroForm.set(N("Fields"), doc.context.obj(fields.asArray().filter((field) => pruneField(doc, field, removedWidgets))));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { organizePdf };
|
|
134
|
+
//# sourceMappingURL=chunk-GXBIMUO7.js.map
|
|
135
|
+
//# sourceMappingURL=chunk-GXBIMUO7.js.map
|