@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.
Files changed (132) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +16 -0
  4. package/README.md +256 -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-4ZEZ2GLD.js +353 -0
  13. package/dist/chunk-4ZEZ2GLD.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-FIEYAKPI.js +421 -0
  43. package/dist/chunk-FIEYAKPI.js.map +1 -0
  44. package/dist/chunk-G467LFKC.js +191 -0
  45. package/dist/chunk-G467LFKC.js.map +1 -0
  46. package/dist/chunk-GLYWSVSB.js +250 -0
  47. package/dist/chunk-GLYWSVSB.js.map +1 -0
  48. package/dist/chunk-GXBIMUO7.js +135 -0
  49. package/dist/chunk-GXBIMUO7.js.map +1 -0
  50. package/dist/chunk-H6EERRCR.js +180 -0
  51. package/dist/chunk-H6EERRCR.js.map +1 -0
  52. package/dist/chunk-HDTM3LXD.js +35 -0
  53. package/dist/chunk-HDTM3LXD.js.map +1 -0
  54. package/dist/chunk-IGMX56C3.js +180 -0
  55. package/dist/chunk-IGMX56C3.js.map +1 -0
  56. package/dist/chunk-J4IRICCJ.js +74 -0
  57. package/dist/chunk-J4IRICCJ.js.map +1 -0
  58. package/dist/chunk-JXKQ7S6W.js +331 -0
  59. package/dist/chunk-JXKQ7S6W.js.map +1 -0
  60. package/dist/chunk-KY6DXS6S.js +34 -0
  61. package/dist/chunk-KY6DXS6S.js.map +1 -0
  62. package/dist/chunk-NOFGCYGS.js +22 -0
  63. package/dist/chunk-NOFGCYGS.js.map +1 -0
  64. package/dist/chunk-OSHUJRKD.js +131 -0
  65. package/dist/chunk-OSHUJRKD.js.map +1 -0
  66. package/dist/chunk-PMH7OWPF.js +40 -0
  67. package/dist/chunk-PMH7OWPF.js.map +1 -0
  68. package/dist/chunk-PRBKSX3A.js +299 -0
  69. package/dist/chunk-PRBKSX3A.js.map +1 -0
  70. package/dist/chunk-SVPUX7AM.js +48 -0
  71. package/dist/chunk-SVPUX7AM.js.map +1 -0
  72. package/dist/chunk-TR6HJ4NO.js +659 -0
  73. package/dist/chunk-TR6HJ4NO.js.map +1 -0
  74. package/dist/chunk-TV3E4YUF.js +69 -0
  75. package/dist/chunk-TV3E4YUF.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 +517 -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,152 @@
1
+ import { loadPdf } from './chunk-EKHEY3LI.js';
2
+ import { PDFName, PDFArray, PDFRef, PDFTextField, PDFRadioGroup, PDFDropdown, PDFOptionList, PDFCheckBox, PDFSignature } from 'pdf-lib';
3
+
4
+ function kindOf(field) {
5
+ if (field instanceof PDFTextField) return "text";
6
+ if (field instanceof PDFCheckBox) return "checkbox";
7
+ if (field instanceof PDFRadioGroup) return "radio";
8
+ if (field instanceof PDFDropdown) return "dropdown";
9
+ if (field instanceof PDFOptionList) return "list";
10
+ if (field instanceof PDFSignature) return "signature";
11
+ return "button";
12
+ }
13
+ function widgetPages(doc) {
14
+ const pages = /* @__PURE__ */ new Map();
15
+ doc.getPages().forEach((page, index) => {
16
+ const annots = page.node.lookup(PDFName.of("Annots"));
17
+ if (annots instanceof PDFArray) {
18
+ annots.asArray().forEach((annot) => {
19
+ if (annot instanceof PDFRef) pages.set(annot.toString(), index + 1);
20
+ });
21
+ }
22
+ });
23
+ return pages;
24
+ }
25
+ function firstWidgetPage(doc, field, pages) {
26
+ for (const widget of field.acroField.getWidgets()) {
27
+ const ref = doc.context.getObjectRef(widget.dict);
28
+ const page = ref && pages.get(ref.toString());
29
+ if (page) return page;
30
+ }
31
+ return null;
32
+ }
33
+ function currentValue(field) {
34
+ if (field instanceof PDFTextField) return field.getText() ?? "";
35
+ if (field instanceof PDFCheckBox) return field.isChecked();
36
+ if (field instanceof PDFRadioGroup) return field.getSelected() ?? "";
37
+ if (field instanceof PDFDropdown) return field.getSelected()[0] ?? "";
38
+ if (field instanceof PDFOptionList) return field.getSelected();
39
+ return "";
40
+ }
41
+ function sameValue(a, b) {
42
+ if (Array.isArray(a) || Array.isArray(b)) {
43
+ const left = [a].flat().map(String);
44
+ const right = [b].flat().map(String);
45
+ return left.length === right.length && left.every((item, index) => item === right[index]);
46
+ }
47
+ return a === b;
48
+ }
49
+ function describe(doc, field, pages) {
50
+ const info = {
51
+ name: field.getName(),
52
+ kind: kindOf(field),
53
+ readOnly: field.isReadOnly(),
54
+ required: field.isRequired(),
55
+ page: firstWidgetPage(doc, field, pages),
56
+ value: currentValue(field)
57
+ };
58
+ if (field instanceof PDFTextField) {
59
+ info.multiline = field.isMultiline();
60
+ info.maxLength = field.getMaxLength();
61
+ } else if (field instanceof PDFRadioGroup) {
62
+ info.options = field.getOptions();
63
+ } else if (field instanceof PDFDropdown) {
64
+ info.options = field.getOptions();
65
+ info.editable = field.isEditable();
66
+ } else if (field instanceof PDFOptionList) {
67
+ info.options = field.getOptions();
68
+ info.multiSelect = field.isMultiselect();
69
+ }
70
+ return info;
71
+ }
72
+ async function readFormFields(bytes) {
73
+ const doc = await loadPdf(bytes, { updateMetadata: false });
74
+ const form = doc.getForm();
75
+ const pages = widgetPages(doc);
76
+ const hasXfa = form.acroForm.dict.has(PDFName.of("XFA"));
77
+ return {
78
+ fields: form.getFields().map((field) => describe(doc, field, pages)),
79
+ hasXfa
80
+ };
81
+ }
82
+ function applyValue(field, value) {
83
+ if (field instanceof PDFTextField) {
84
+ const text = String(value ?? "");
85
+ const max = field.getMaxLength();
86
+ if (max !== void 0 && text.length > max) {
87
+ throw new Error(`at most ${max} characters are allowed.`);
88
+ }
89
+ field.setText(text || void 0);
90
+ } else if (field instanceof PDFCheckBox) {
91
+ if (value) field.check();
92
+ else field.uncheck();
93
+ } else if (field instanceof PDFRadioGroup) {
94
+ if (value) field.select(String(value));
95
+ else field.clear();
96
+ } else if (field instanceof PDFDropdown) {
97
+ if (!value) field.clear();
98
+ else if (field.getOptions().includes(String(value)) || field.isEditable()) field.select(String(value));
99
+ else throw new Error(`"${value}" is not one of its options.`);
100
+ } else if (field instanceof PDFOptionList) {
101
+ const selected = (Array.isArray(value) ? value : [value]).map(String).filter(Boolean);
102
+ const options = field.getOptions();
103
+ const unknown = selected.find((option) => !options.includes(option));
104
+ if (unknown) throw new Error(`"${unknown}" is not one of its options.`);
105
+ if (selected.length) field.select(selected);
106
+ else field.clear();
107
+ }
108
+ }
109
+ async function fillForm(bytes, values, { flatten = false } = {}) {
110
+ const doc = await loadPdf(bytes);
111
+ const form = doc.getForm();
112
+ for (const [name, value] of Object.entries(values)) {
113
+ const field = form.getFieldMaybe(name);
114
+ if (!field || field.isReadOnly()) continue;
115
+ const kind = kindOf(field);
116
+ if (kind === "button" || kind === "signature") continue;
117
+ if (sameValue(currentValue(field), value)) continue;
118
+ try {
119
+ applyValue(field, value);
120
+ } catch (error) {
121
+ throw new Error(`Field "${name}": ${error.message}`);
122
+ }
123
+ }
124
+ form.deleteXFA();
125
+ try {
126
+ if (flatten) form.flatten();
127
+ else form.updateFieldAppearances();
128
+ } catch (error) {
129
+ if (/cannot encode/i.test(error.message || "")) {
130
+ throw new Error(
131
+ "Some answers contain characters the standard PDF font cannot draw. Use basic Latin letters, numbers, and punctuation."
132
+ );
133
+ }
134
+ throw error;
135
+ }
136
+ if (flatten) dropDeletedAnnotations(doc);
137
+ return doc.save();
138
+ }
139
+ function dropDeletedAnnotations(doc) {
140
+ for (const page of doc.getPages()) {
141
+ const annots = page.node.lookup(PDFName.of("Annots"));
142
+ if (!(annots instanceof PDFArray)) continue;
143
+ const kept = annots.asArray().filter((annot) => !(annot instanceof PDFRef) || doc.context.lookup(annot) !== void 0);
144
+ if (kept.length === annots.size()) continue;
145
+ if (kept.length) page.node.set(PDFName.of("Annots"), doc.context.obj(kept));
146
+ else page.node.delete(PDFName.of("Annots"));
147
+ }
148
+ }
149
+
150
+ export { fillForm, readFormFields };
151
+ //# sourceMappingURL=chunk-CYTWDJE2.js.map
152
+ //# sourceMappingURL=chunk-CYTWDJE2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/forms/fillFormCore.ts"],"names":[],"mappings":"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","file":"chunk-CYTWDJE2.js","sourcesContent":["import {\n PDFArray,\n PDFButton,\n PDFCheckBox,\n PDFDocument,\n PDFDropdown,\n PDFField,\n PDFName,\n PDFOptionList,\n PDFRadioGroup,\n PDFRef,\n PDFSignature,\n PDFTextField,\n} from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\n\nexport type FieldKind = \"text\" | \"checkbox\" | \"radio\" | \"dropdown\" | \"list\" | \"button\" | \"signature\";\n\nexport type FormFieldInfo = {\n name: string;\n kind: FieldKind;\n /** text: string; checkbox: boolean; radio/dropdown: string; list: string[] */\n value: string | boolean | string[];\n options?: string[];\n multiline?: boolean;\n maxLength?: number;\n multiSelect?: boolean;\n /** Dropdowns that accept a typed value as well as the listed options. */\n editable?: boolean;\n readOnly: boolean;\n required: boolean;\n /** 1-based page of the field's first widget, or null if not on a page. */\n page: number | null;\n};\n\nexport type FormValues = Record<string, string | boolean | string[]>;\n\nfunction kindOf(field: PDFField): FieldKind {\n if (field instanceof PDFTextField) return \"text\";\n if (field instanceof PDFCheckBox) return \"checkbox\";\n if (field instanceof PDFRadioGroup) return \"radio\";\n if (field instanceof PDFDropdown) return \"dropdown\";\n if (field instanceof PDFOptionList) return \"list\";\n if (field instanceof PDFSignature) return \"signature\";\n return \"button\";\n}\n\n/** Map each annotation ref to its 1-based page number. */\nfunction widgetPages(doc: PDFDocument) {\n const pages = new Map<string, number>();\n doc.getPages().forEach((page, index) => {\n const annots = page.node.lookup(PDFName.of(\"Annots\"));\n if (annots instanceof PDFArray) {\n annots.asArray().forEach((annot) => {\n if (annot instanceof PDFRef) pages.set(annot.toString(), index + 1);\n });\n }\n });\n return pages;\n}\n\nfunction firstWidgetPage(doc: PDFDocument, field: PDFField, pages: Map<string, number>) {\n for (const widget of field.acroField.getWidgets()) {\n const ref = doc.context.getObjectRef(widget.dict);\n const page = ref && pages.get(ref.toString());\n if (page) return page;\n }\n return null;\n}\n\nfunction currentValue(field: PDFField): string | boolean | string[] {\n if (field instanceof PDFTextField) return field.getText() ?? \"\";\n if (field instanceof PDFCheckBox) return field.isChecked();\n if (field instanceof PDFRadioGroup) return field.getSelected() ?? \"\";\n if (field instanceof PDFDropdown) return field.getSelected()[0] ?? \"\";\n if (field instanceof PDFOptionList) return field.getSelected();\n return \"\";\n}\n\nfunction sameValue(a: string | boolean | string[], b: string | boolean | string[]) {\n if (Array.isArray(a) || Array.isArray(b)) {\n const left = [a].flat().map(String);\n const right = [b].flat().map(String);\n return left.length === right.length && left.every((item, index) => item === right[index]);\n }\n return a === b;\n}\n\nfunction describe(doc: PDFDocument, field: PDFField, pages: Map<string, number>): FormFieldInfo {\n const info: FormFieldInfo = {\n name: field.getName(),\n kind: kindOf(field),\n readOnly: field.isReadOnly(),\n required: field.isRequired(),\n page: firstWidgetPage(doc, field, pages),\n value: currentValue(field),\n };\n\n if (field instanceof PDFTextField) {\n info.multiline = field.isMultiline();\n info.maxLength = field.getMaxLength();\n } else if (field instanceof PDFRadioGroup) {\n info.options = field.getOptions();\n } else if (field instanceof PDFDropdown) {\n info.options = field.getOptions();\n info.editable = field.isEditable();\n } else if (field instanceof PDFOptionList) {\n info.options = field.getOptions();\n info.multiSelect = field.isMultiselect();\n }\n return info;\n}\n\nexport async function readFormFields(bytes: ArrayBuffer | Uint8Array) {\n const doc = await loadPdf(bytes, { updateMetadata: false });\n const form = doc.getForm();\n const pages = widgetPages(doc);\n const hasXfa = form.acroForm.dict.has(PDFName.of(\"XFA\"));\n return {\n fields: form.getFields().map((field) => describe(doc, field, pages)),\n hasXfa,\n };\n}\n\nfunction applyValue(field: PDFField, value: string | boolean | string[]) {\n if (field instanceof PDFTextField) {\n const text = String(value ?? \"\");\n const max = field.getMaxLength();\n if (max !== undefined && text.length > max) {\n throw new Error(`at most ${max} characters are allowed.`);\n }\n field.setText(text || undefined);\n } else if (field instanceof PDFCheckBox) {\n if (value) field.check();\n else field.uncheck();\n } else if (field instanceof PDFRadioGroup) {\n if (value) field.select(String(value));\n else field.clear();\n } else if (field instanceof PDFDropdown) {\n if (!value) field.clear();\n else if (field.getOptions().includes(String(value)) || field.isEditable()) field.select(String(value));\n else throw new Error(`\"${value}\" is not one of its options.`);\n } else if (field instanceof PDFOptionList) {\n const selected = (Array.isArray(value) ? value : [value]).map(String).filter(Boolean);\n const options = field.getOptions();\n const unknown = selected.find((option) => !options.includes(option));\n if (unknown) throw new Error(`\"${unknown}\" is not one of its options.`);\n if (selected.length) field.select(selected);\n else field.clear();\n }\n}\n\n/**\n * Fill form fields by name. Read-only, button, and signature fields are left\n * alone. With flatten, answers are drawn into the pages and the form removed.\n */\nexport async function fillForm(\n bytes: ArrayBuffer | Uint8Array,\n values: FormValues,\n { flatten = false }: { flatten?: boolean } = {}\n) {\n const doc = await loadPdf(bytes);\n const form = doc.getForm();\n\n for (const [name, value] of Object.entries(values)) {\n const field = form.getFieldMaybe(name);\n if (!field || field.isReadOnly()) continue;\n const kind = kindOf(field);\n if (kind === \"button\" || kind === \"signature\") continue;\n // Untouched fields keep their existing appearance (font, non-Latin text).\n if (sameValue(currentValue(field), value)) continue;\n try {\n applyValue(field, value);\n } catch (error) {\n throw new Error(`Field \"${name}\": ${(error as Error).message}`);\n }\n }\n\n // XFA forms carry a second copy of the form that some viewers prefer; it\n // would hide the values set here.\n form.deleteXFA();\n\n try {\n if (flatten) form.flatten();\n else form.updateFieldAppearances();\n } catch (error) {\n if (/cannot encode/i.test((error as Error).message || \"\")) {\n throw new Error(\n \"Some answers contain characters the standard PDF font cannot draw. Use basic Latin letters, numbers, and punctuation.\"\n );\n }\n throw error;\n }\n\n if (flatten) dropDeletedAnnotations(doc);\n return doc.save();\n}\n\n/**\n * pdf-lib's flatten() deletes widget objects but can leave their refs in each\n * page's /Annots, which would point at nothing in the saved file.\n */\nfunction dropDeletedAnnotations(doc: PDFDocument) {\n for (const page of doc.getPages()) {\n const annots = page.node.lookup(PDFName.of(\"Annots\"));\n if (!(annots instanceof PDFArray)) continue;\n const kept = annots\n .asArray()\n .filter((annot) => !(annot instanceof PDFRef) || doc.context.lookup(annot) !== undefined);\n if (kept.length === annots.size()) continue;\n if (kept.length) page.node.set(PDFName.of(\"Annots\"), doc.context.obj(kept));\n else page.node.delete(PDFName.of(\"Annots\"));\n }\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import { loadPdf } from './chunk-EKHEY3LI.js';
2
+ import { PDFName } from 'pdf-lib';
3
+
4
+ var METADATA_FIELDS = [
5
+ "title",
6
+ "author",
7
+ "subject",
8
+ "keywords",
9
+ "creator",
10
+ "producer"
11
+ ];
12
+ async function readPdfMetadata(bytes) {
13
+ const doc = await loadPdf(bytes, { updateMetadata: false });
14
+ return {
15
+ title: doc.getTitle() || "",
16
+ author: doc.getAuthor() || "",
17
+ subject: doc.getSubject() || "",
18
+ keywords: doc.getKeywords() || "",
19
+ creator: doc.getCreator() || "",
20
+ producer: doc.getProducer() || "",
21
+ pageCount: doc.getPageCount(),
22
+ creationDate: doc.getCreationDate() || null,
23
+ modificationDate: doc.getModificationDate() || null,
24
+ hasXmp: Boolean(doc.catalog.get(PDFName.of("Metadata")))
25
+ };
26
+ }
27
+ function splitKeywords(value) {
28
+ return (value || "").split(/[,;]/).map((keyword) => keyword.trim()).filter(Boolean);
29
+ }
30
+ async function writePdfMetadata(bytes, fields, { stripXmp = false, now = /* @__PURE__ */ new Date() } = {}) {
31
+ const doc = await loadPdf(bytes, { updateMetadata: false });
32
+ if (fields.title !== void 0) doc.setTitle(fields.title.trim());
33
+ if (fields.author !== void 0) doc.setAuthor(fields.author.trim());
34
+ if (fields.subject !== void 0) doc.setSubject(fields.subject.trim());
35
+ if (fields.keywords !== void 0) doc.setKeywords(splitKeywords(fields.keywords));
36
+ if (fields.creator !== void 0) doc.setCreator(fields.creator.trim());
37
+ if (fields.producer !== void 0) doc.setProducer(fields.producer.trim());
38
+ doc.setModificationDate(now);
39
+ if (stripXmp) doc.catalog.delete(PDFName.of("Metadata"));
40
+ return doc.save();
41
+ }
42
+
43
+ export { METADATA_FIELDS, readPdfMetadata, splitKeywords, writePdfMetadata };
44
+ //# sourceMappingURL=chunk-DKVOICYZ.js.map
45
+ //# sourceMappingURL=chunk-DKVOICYZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/metadata/pdfMetadataCore.ts"],"names":[],"mappings":";;;AAmBO,IAAM,eAAA,GAAkD;AAAA,EAC7D,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF;AAEA,eAAsB,gBAAgB,KAAA,EAAuD;AAE3F,EAAA,MAAM,MAAM,MAAM,OAAA,CAAQ,OAAO,EAAE,cAAA,EAAgB,OAAO,CAAA;AAC1D,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,GAAA,CAAI,QAAA,EAAS,IAAK,EAAA;AAAA,IACzB,MAAA,EAAQ,GAAA,CAAI,SAAA,EAAU,IAAK,EAAA;AAAA,IAC3B,OAAA,EAAS,GAAA,CAAI,UAAA,EAAW,IAAK,EAAA;AAAA,IAC7B,QAAA,EAAU,GAAA,CAAI,WAAA,EAAY,IAAK,EAAA;AAAA,IAC/B,OAAA,EAAS,GAAA,CAAI,UAAA,EAAW,IAAK,EAAA;AAAA,IAC7B,QAAA,EAAU,GAAA,CAAI,WAAA,EAAY,IAAK,EAAA;AAAA,IAC/B,SAAA,EAAW,IAAI,YAAA,EAAa;AAAA,IAC5B,YAAA,EAAc,GAAA,CAAI,eAAA,EAAgB,IAAK,IAAA;AAAA,IACvC,gBAAA,EAAkB,GAAA,CAAI,mBAAA,EAAoB,IAAK,IAAA;AAAA,IAC/C,MAAA,EAAQ,QAAQ,GAAA,CAAI,OAAA,CAAQ,IAAI,OAAA,CAAQ,EAAA,CAAG,UAAU,CAAC,CAAC;AAAA,GACzD;AACF;AAEO,SAAS,cAAc,KAAA,EAAe;AAC3C,EAAA,OAAA,CAAQ,KAAA,IAAS,EAAA,EACd,KAAA,CAAM,MAAM,CAAA,CACZ,GAAA,CAAI,CAAC,OAAA,KAAY,OAAA,CAAQ,IAAA,EAAM,CAAA,CAC/B,OAAO,OAAO,CAAA;AACnB;AAEA,eAAsB,gBAAA,CACpB,KAAA,EACA,MAAA,EACA,EAAE,QAAA,GAAW,KAAA,EAAO,GAAA,mBAAM,IAAI,IAAA,EAAK,EAAE,GAAwC,EAAC,EAC9E;AACA,EAAA,MAAM,MAAM,MAAM,OAAA,CAAQ,OAAO,EAAE,cAAA,EAAgB,OAAO,CAAA;AAE1D,EAAA,IAAI,MAAA,CAAO,UAAU,MAAA,EAAW,GAAA,CAAI,SAAS,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA;AAChE,EAAA,IAAI,MAAA,CAAO,WAAW,MAAA,EAAW,GAAA,CAAI,UAAU,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA;AACnE,EAAA,IAAI,MAAA,CAAO,YAAY,MAAA,EAAW,GAAA,CAAI,WAAW,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA;AACtE,EAAA,IAAI,MAAA,CAAO,aAAa,MAAA,EAAW,GAAA,CAAI,YAAY,aAAA,CAAc,MAAA,CAAO,QAAQ,CAAC,CAAA;AACjF,EAAA,IAAI,MAAA,CAAO,YAAY,MAAA,EAAW,GAAA,CAAI,WAAW,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA;AACtE,EAAA,IAAI,MAAA,CAAO,aAAa,MAAA,EAAW,GAAA,CAAI,YAAY,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA;AACzE,EAAA,GAAA,CAAI,oBAAoB,GAAG,CAAA;AAI3B,EAAA,IAAI,UAAU,GAAA,CAAI,OAAA,CAAQ,OAAO,OAAA,CAAQ,EAAA,CAAG,UAAU,CAAC,CAAA;AAEvD,EAAA,OAAO,IAAI,IAAA,EAAK;AAClB","file":"chunk-DKVOICYZ.js","sourcesContent":["import { PDFName } from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\n\nexport type PdfMetadataFields = {\n title: string;\n author: string;\n subject: string;\n keywords: string;\n creator: string;\n producer: string;\n};\n\nexport type PdfMetadata = PdfMetadataFields & {\n pageCount: number;\n creationDate: Date | null;\n modificationDate: Date | null;\n hasXmp: boolean;\n};\n\nexport const METADATA_FIELDS: Array<keyof PdfMetadataFields> = [\n \"title\",\n \"author\",\n \"subject\",\n \"keywords\",\n \"creator\",\n \"producer\",\n];\n\nexport async function readPdfMetadata(bytes: ArrayBuffer | Uint8Array): Promise<PdfMetadata> {\n // updateMetadata: false so we read what is in the file, not pdf-lib defaults.\n const doc = await loadPdf(bytes, { updateMetadata: false });\n return {\n title: doc.getTitle() || \"\",\n author: doc.getAuthor() || \"\",\n subject: doc.getSubject() || \"\",\n keywords: doc.getKeywords() || \"\",\n creator: doc.getCreator() || \"\",\n producer: doc.getProducer() || \"\",\n pageCount: doc.getPageCount(),\n creationDate: doc.getCreationDate() || null,\n modificationDate: doc.getModificationDate() || null,\n hasXmp: Boolean(doc.catalog.get(PDFName.of(\"Metadata\"))),\n };\n}\n\nexport function splitKeywords(value: string) {\n return (value || \"\")\n .split(/[,;]/)\n .map((keyword) => keyword.trim())\n .filter(Boolean);\n}\n\nexport async function writePdfMetadata(\n bytes: ArrayBuffer | Uint8Array,\n fields: Partial<PdfMetadataFields>,\n { stripXmp = false, now = new Date() }: { stripXmp?: boolean; now?: Date } = {}\n) {\n const doc = await loadPdf(bytes, { updateMetadata: false });\n\n if (fields.title !== undefined) doc.setTitle(fields.title.trim());\n if (fields.author !== undefined) doc.setAuthor(fields.author.trim());\n if (fields.subject !== undefined) doc.setSubject(fields.subject.trim());\n if (fields.keywords !== undefined) doc.setKeywords(splitKeywords(fields.keywords));\n if (fields.creator !== undefined) doc.setCreator(fields.creator.trim());\n if (fields.producer !== undefined) doc.setProducer(fields.producer.trim());\n doc.setModificationDate(now);\n\n // XMP is a second copy of the metadata that some apps prefer over the Info\n // dictionary. Dropping it keeps viewers from showing the old values.\n if (stripXmp) doc.catalog.delete(PDFName.of(\"Metadata\"));\n\n return doc.save();\n}\n"]}
@@ -0,0 +1,79 @@
1
+ import { requireAsset, getPdfConfig } from './chunk-DQPABZTU.js';
2
+
3
+ // src/shared/pdfRender.ts
4
+ var pdfjsPromise = null;
5
+ function loadPdfJs() {
6
+ if (!pdfjsPromise) {
7
+ const workerSrc = requireAsset("pdfjsWorkerSrc", "Rendering PDF pages");
8
+ pdfjsPromise = import('pdfjs-dist').then((pdfjs) => {
9
+ pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;
10
+ return pdfjs;
11
+ });
12
+ pdfjsPromise.catch(() => {
13
+ pdfjsPromise = null;
14
+ });
15
+ }
16
+ return pdfjsPromise;
17
+ }
18
+ async function openRenderDocument(bytes) {
19
+ const pdfjs = await loadPdfJs();
20
+ const data = bytes instanceof Uint8Array ? bytes.slice() : new Uint8Array(bytes.slice(0));
21
+ try {
22
+ const base = getPdfConfig().pdfjsAssetsBase?.replace(/\/+$/, "");
23
+ return await pdfjs.getDocument({
24
+ data,
25
+ isEvalSupported: false,
26
+ ...base ? { cMapUrl: `${base}/cmaps/`, cMapPacked: true, standardFontDataUrl: `${base}/standard_fonts/` } : {}
27
+ }).promise;
28
+ } catch (error) {
29
+ if (error?.name === "PasswordException") {
30
+ throw new Error("This PDF is password protected. Remove the password with Protect / Unlock PDF first.");
31
+ }
32
+ throw new Error("This file is not a readable PDF.");
33
+ }
34
+ }
35
+ async function renderPageToCanvas(doc, pageNumber, { scale = 1, background = "#ffffff", maxPixels = 16e6 } = {}) {
36
+ const page = await doc.getPage(pageNumber);
37
+ const base = page.getViewport({ scale: 1 });
38
+ const pixelLimit = Math.sqrt(maxPixels / (base.width * base.height));
39
+ const appliedScale = Math.min(scale, pixelLimit);
40
+ const viewport = page.getViewport({ scale: appliedScale });
41
+ const canvas = document.createElement("canvas");
42
+ canvas.width = Math.max(1, Math.floor(viewport.width));
43
+ canvas.height = Math.max(1, Math.floor(viewport.height));
44
+ const context = canvas.getContext("2d");
45
+ context.fillStyle = background;
46
+ context.fillRect(0, 0, canvas.width, canvas.height);
47
+ await page.render({ canvasContext: context, viewport }).promise;
48
+ page.cleanup();
49
+ return {
50
+ canvas,
51
+ width: base.width,
52
+ height: base.height,
53
+ scale: appliedScale,
54
+ reduced: appliedScale < scale
55
+ };
56
+ }
57
+ function canvasToBlob(canvas, type = "image/png", quality) {
58
+ return new Promise((resolve, reject) => {
59
+ canvas.toBlob(
60
+ (blob) => blob ? resolve(blob) : reject(new Error("Could not encode the image.")),
61
+ type,
62
+ quality
63
+ );
64
+ });
65
+ }
66
+ async function renderThumbnail(doc, pageNumber, targetWidth = 180) {
67
+ const page = await doc.getPage(pageNumber);
68
+ const base = page.getViewport({ scale: 1 });
69
+ const ratio = typeof window !== "undefined" ? Math.min(2, window.devicePixelRatio || 1) : 1;
70
+ const { canvas, width, height } = await renderPageToCanvas(doc, pageNumber, {
71
+ scale: targetWidth / base.width * ratio
72
+ });
73
+ const blob = await canvasToBlob(canvas, "image/jpeg", 0.8);
74
+ return { url: URL.createObjectURL(blob), width, height };
75
+ }
76
+
77
+ export { canvasToBlob, loadPdfJs, openRenderDocument, renderPageToCanvas, renderThumbnail };
78
+ //# sourceMappingURL=chunk-DPEJU4P6.js.map
79
+ //# sourceMappingURL=chunk-DPEJU4P6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/pdfRender.ts"],"names":[],"mappings":"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","file":"chunk-DPEJU4P6.js","sourcesContent":["// Browser-only page rendering via pdf.js. Loaded lazily so tools that only\n// edit PDFs (pdf-lib) never pay for it.\n\nimport { getPdfConfig, requireAsset } from \"../config\";\n\nlet pdfjsPromise = null;\n\nexport function loadPdfJs() {\n if (!pdfjsPromise) {\n const workerSrc = requireAsset(\"pdfjsWorkerSrc\", \"Rendering PDF pages\");\n pdfjsPromise = import(\"pdfjs-dist\").then((pdfjs) => {\n pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;\n return pdfjs;\n });\n pdfjsPromise.catch(() => {\n pdfjsPromise = null;\n });\n }\n return pdfjsPromise;\n}\n\n/**\n * Open a PDF for rendering. pdf.js transfers the buffer it is given to its\n * worker (leaving it empty), so it always gets a copy.\n */\nexport async function openRenderDocument(bytes: ArrayBuffer | Uint8Array) {\n const pdfjs = await loadPdfJs();\n const data = bytes instanceof Uint8Array ? bytes.slice() : new Uint8Array(bytes.slice(0));\n try {\n // CMaps and standard fonts are optional and served by the host app.\n const base = getPdfConfig().pdfjsAssetsBase?.replace(/\\/+$/, \"\");\n return await pdfjs.getDocument({\n data,\n isEvalSupported: false,\n ...(base\n ? { cMapUrl: `${base}/cmaps/`, cMapPacked: true, standardFontDataUrl: `${base}/standard_fonts/` }\n : {}),\n }).promise;\n } catch (error) {\n if (error?.name === \"PasswordException\") {\n throw new Error(\"This PDF is password protected. Remove the password with Protect / Unlock PDF first.\");\n }\n throw new Error(\"This file is not a readable PDF.\");\n }\n}\n\n/**\n * Render one page (1-based) as it appears in a viewer: CropBox and /Rotate\n * applied. Returns the canvas and its CSS-pixel size at scale 1.\n */\nexport async function renderPageToCanvas(\n doc,\n pageNumber: number,\n { scale = 1, background = \"#ffffff\", maxPixels = 16_000_000 } = {}\n) {\n const page = await doc.getPage(pageNumber);\n const base = page.getViewport({ scale: 1 });\n const pixelLimit = Math.sqrt(maxPixels / (base.width * base.height));\n const appliedScale = Math.min(scale, pixelLimit);\n const viewport = page.getViewport({ scale: appliedScale });\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.floor(viewport.width));\n canvas.height = Math.max(1, Math.floor(viewport.height));\n const context = canvas.getContext(\"2d\");\n context.fillStyle = background;\n context.fillRect(0, 0, canvas.width, canvas.height);\n\n await page.render({ canvasContext: context, viewport }).promise;\n page.cleanup();\n\n return {\n canvas,\n width: base.width,\n height: base.height,\n scale: appliedScale,\n reduced: appliedScale < scale,\n };\n}\n\nexport function canvasToBlob(canvas: HTMLCanvasElement, type = \"image/png\", quality?: number) {\n return new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n (blob) => (blob ? resolve(blob) : reject(new Error(\"Could not encode the image.\"))),\n type,\n quality\n );\n });\n}\n\n/** Render a small thumbnail as an object URL. Caller revokes it. */\nexport async function renderThumbnail(doc, pageNumber: number, targetWidth = 180) {\n const page = await doc.getPage(pageNumber);\n const base = page.getViewport({ scale: 1 });\n const ratio = typeof window !== \"undefined\" ? Math.min(2, window.devicePixelRatio || 1) : 1;\n const { canvas, width, height } = await renderPageToCanvas(doc, pageNumber, {\n scale: (targetWidth / base.width) * ratio,\n });\n const blob = await canvasToBlob(canvas, \"image/jpeg\", 0.8);\n return { url: URL.createObjectURL(blob), width, height };\n}\n"]}
@@ -0,0 +1,22 @@
1
+ // src/config.ts
2
+ var current = {};
3
+ function configurePdf(next) {
4
+ current = { ...current, ...next };
5
+ }
6
+ function getPdfConfig() {
7
+ return current;
8
+ }
9
+ function producerName() {
10
+ return current.producer || "@kitsy/pdf";
11
+ }
12
+ function requireAsset(key, purpose) {
13
+ const value = current[key];
14
+ if (!value) {
15
+ throw new Error(`@kitsy/pdf: ${purpose} needs configurePdf({ ${key} }) to be called first.`);
16
+ }
17
+ return value;
18
+ }
19
+
20
+ export { configurePdf, getPdfConfig, producerName, requireAsset };
21
+ //# sourceMappingURL=chunk-DQPABZTU.js.map
22
+ //# sourceMappingURL=chunk-DQPABZTU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts"],"names":[],"mappings":";AAkBA,IAAI,UAAqB,EAAC;AAGnB,SAAS,aAAa,IAAA,EAAiB;AAC5C,EAAA,OAAA,GAAU,EAAE,GAAG,OAAA,EAAS,GAAG,IAAA,EAAK;AAClC;AAEO,SAAS,YAAA,GAAoC;AAClD,EAAA,OAAO,OAAA;AACT;AAEO,SAAS,YAAA,GAAe;AAC7B,EAAA,OAAO,QAAQ,QAAA,IAAY,YAAA;AAC7B;AAEO,SAAS,YAAA,CAAa,KAAuC,OAAA,EAAiB;AACnF,EAAA,MAAM,KAAA,GAAQ,QAAQ,GAAG,CAAA;AACzB,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,OAAO,CAAA,sBAAA,EAAyB,GAAG,CAAA,uBAAA,CAAyB,CAAA;AAAA,EAC7F;AACA,EAAA,OAAO,KAAA;AACT","file":"chunk-DQPABZTU.js","sourcesContent":["// Host-app settings. Bundlers resolve asset URLs differently, so the host app\n// supplies them once at start-up.\n\nexport type PdfConfig = {\n /** Written to the Producer field of PDFs that tools create from scratch. Default \"@kitsy/pdf\". */\n producer?: string;\n /** URL of pdfjs-dist/build/pdf.worker.min.mjs. Needed for page previews and PDF to Images. */\n pdfjsWorkerSrc?: string;\n /**\n * Folder that holds pdf.js `cmaps/` and `standard_fonts/` (copied from\n * pdfjs-dist). Optional: without it, PDFs with CJK text or unembedded\n * standard fonts may render with substitute glyphs.\n */\n pdfjsAssetsBase?: string;\n /** URL of @neslinesli93/qpdf-wasm/dist/qpdf.wasm. Needed for Protect / Unlock. */\n qpdfWasmUrl?: string;\n};\n\nlet current: PdfConfig = {};\n\n/** Set asset URLs and options. Later calls merge into earlier ones. */\nexport function configurePdf(next: PdfConfig) {\n current = { ...current, ...next };\n}\n\nexport function getPdfConfig(): Readonly<PdfConfig> {\n return current;\n}\n\nexport function producerName() {\n return current.producer || \"@kitsy/pdf\";\n}\n\nexport function requireAsset(key: \"pdfjsWorkerSrc\" | \"qpdfWasmUrl\", purpose: string) {\n const value = current[key];\n if (!value) {\n throw new Error(`@kitsy/pdf: ${purpose} needs configurePdf({ ${key} }) to be called first.`);\n }\n return value;\n}\n"]}
@@ -0,0 +1,46 @@
1
+ import { PDFDocument } from 'pdf-lib';
2
+
3
+ // src/shared/files.ts
4
+ function isPdfFile(file) {
5
+ return file?.type === "application/pdf" || (file?.name || "").toLowerCase().endsWith(".pdf");
6
+ }
7
+ function formatBytes(bytes = 0) {
8
+ if (!bytes) return "0 B";
9
+ const units = ["B", "KB", "MB", "GB"];
10
+ const value = Math.floor(Math.log(bytes) / Math.log(1024));
11
+ const normalized = bytes / 1024 ** value;
12
+ return `${normalized.toFixed(normalized >= 10 ? 0 : 1)} ${units[value]}`;
13
+ }
14
+ function toBaseName(value, fallback) {
15
+ const base = (value || "").trim().replace(/\.(pdf|zip)$/i, "").replace(/[\\/:*?"<>|]+/g, "-");
16
+ return base || fallback;
17
+ }
18
+ function downloadBlob(blob, fileName) {
19
+ const url = URL.createObjectURL(blob);
20
+ const link = document.createElement("a");
21
+ link.href = url;
22
+ link.download = fileName;
23
+ document.body.appendChild(link);
24
+ link.click();
25
+ document.body.removeChild(link);
26
+ URL.revokeObjectURL(url);
27
+ }
28
+ async function loadPdf(bytes, options = {}) {
29
+ try {
30
+ return await PDFDocument.load(bytes, options);
31
+ } catch (error) {
32
+ const message = error instanceof Error ? error.message : "";
33
+ if (/encrypt/i.test(message)) {
34
+ throw new Error("This PDF is password protected. Remove the password with Protect / Unlock PDF first.");
35
+ }
36
+ throw new Error("This file is not a readable PDF.");
37
+ }
38
+ }
39
+ async function getPdfPageCount(bytes) {
40
+ const doc = await loadPdf(bytes);
41
+ return doc.getPageCount();
42
+ }
43
+
44
+ export { downloadBlob, formatBytes, getPdfPageCount, isPdfFile, loadPdf, toBaseName };
45
+ //# sourceMappingURL=chunk-EKHEY3LI.js.map
46
+ //# sourceMappingURL=chunk-EKHEY3LI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/files.ts"],"names":[],"mappings":";;;AAEO,SAAS,UAAU,IAAA,EAAwC;AAChE,EAAA,OACE,IAAA,EAAM,SAAS,iBAAA,IAAA,CACd,IAAA,EAAM,QAAQ,EAAA,EAAI,WAAA,EAAY,CAAE,QAAA,CAAS,MAAM,CAAA;AAEpD;AAEO,SAAS,WAAA,CAAY,QAAQ,CAAA,EAAG;AACrC,EAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAA,EAAK,IAAA,EAAM,MAAM,IAAI,CAAA;AACpC,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,IAAI,CAAC,CAAA;AACzD,EAAA,MAAM,UAAA,GAAa,QAAQ,IAAA,IAAQ,KAAA;AACnC,EAAA,OAAO,CAAA,EAAG,UAAA,CAAW,OAAA,CAAQ,UAAA,IAAc,EAAA,GAAK,CAAA,GAAI,CAAC,CAAC,CAAA,CAAA,EAAI,KAAA,CAAM,KAAK,CAAC,CAAA,CAAA;AACxE;AAGO,SAAS,UAAA,CAAW,OAAe,QAAA,EAAkB;AAC1D,EAAA,MAAM,IAAA,GAAA,CAAQ,KAAA,IAAS,EAAA,EACpB,IAAA,EAAK,CACL,OAAA,CAAQ,eAAA,EAAiB,EAAE,CAAA,CAC3B,OAAA,CAAQ,gBAAA,EAAkB,GAAG,CAAA;AAChC,EAAA,OAAO,IAAA,IAAQ,QAAA;AACjB;AAEO,SAAS,YAAA,CAAa,MAAY,QAAA,EAAkB;AACzD,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACvC,EAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AACZ,EAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,EAAA,QAAA,CAAS,IAAA,CAAK,YAAY,IAAI,CAAA;AAC9B,EAAA,IAAA,CAAK,KAAA,EAAM;AACX,EAAA,QAAA,CAAS,IAAA,CAAK,YAAY,IAAI,CAAA;AAC9B,EAAA,GAAA,CAAI,gBAAgB,GAAG,CAAA;AACzB;AAEA,eAAsB,OAAA,CACpB,KAAA,EACA,OAAA,GAAwC,EAAC,EACzC;AACA,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,WAAA,CAAY,IAAA,CAAK,KAAA,EAAO,OAAO,CAAA;AAAA,EAC9C,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,OAAA,GAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,EAAA;AACzD,IAAA,IAAI,UAAA,CAAW,IAAA,CAAK,OAAO,CAAA,EAAG;AAC5B,MAAA,MAAM,IAAI,MAAM,sFAAsF,CAAA;AAAA,IACxG;AACA,IAAA,MAAM,IAAI,MAAM,kCAAkC,CAAA;AAAA,EACpD;AACF;AAEA,eAAsB,gBAAgB,KAAA,EAAiC;AACrE,EAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,KAAK,CAAA;AAC/B,EAAA,OAAO,IAAI,YAAA,EAAa;AAC1B","file":"chunk-EKHEY3LI.js","sourcesContent":["import { PDFDocument } from \"pdf-lib\";\n\nexport function isPdfFile(file: { name?: string; type?: string }) {\n return (\n file?.type === \"application/pdf\" ||\n (file?.name || \"\").toLowerCase().endsWith(\".pdf\")\n );\n}\n\nexport function formatBytes(bytes = 0) {\n if (!bytes) return \"0 B\";\n const units = [\"B\", \"KB\", \"MB\", \"GB\"];\n const value = Math.floor(Math.log(bytes) / Math.log(1024));\n const normalized = bytes / 1024 ** value;\n return `${normalized.toFixed(normalized >= 10 ? 0 : 1)} ${units[value]}`;\n}\n\n/** Strip a known extension and characters that are invalid in file names. */\nexport function toBaseName(value: string, fallback: string) {\n const base = (value || \"\")\n .trim()\n .replace(/\\.(pdf|zip)$/i, \"\")\n .replace(/[\\\\/:*?\"<>|]+/g, \"-\");\n return base || fallback;\n}\n\nexport function downloadBlob(blob: Blob, fileName: string) {\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = fileName;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n\nexport async function loadPdf(\n bytes: ArrayBuffer | Uint8Array,\n options: { updateMetadata?: boolean } = {}\n) {\n try {\n return await PDFDocument.load(bytes, options);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"\";\n if (/encrypt/i.test(message)) {\n throw new Error(\"This PDF is password protected. Remove the password with Protect / Unlock PDF first.\");\n }\n throw new Error(\"This file is not a readable PDF.\");\n }\n}\n\nexport async function getPdfPageCount(bytes: ArrayBuffer | Uint8Array) {\n const doc = await loadPdf(bytes);\n return doc.getPageCount();\n}\n"]}