@kitsy/pdf 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE +201 -0
- package/NOTICE +16 -0
- package/README.md +229 -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-4USCGC24.js +304 -0
- package/dist/chunk-4USCGC24.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-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-U2ROTOPF.js +256 -0
- package/dist/chunk-U2ROTOPF.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 +487 -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 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/ToolLayout.tsx"],"names":[],"mappings":"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","file":"chunk-H6EERRCR.js","sourcesContent":["import React, { createContext, useContext, useRef, useState, type ReactNode } from \"react\";\n\nexport type PdfToolsLinkProps = { href: string; className: string; children: ReactNode };\n\nexport type PdfToolsOptions = {\n /** Link shown at the bottom of each tool, e.g. back to your tool list. Omit to hide it. */\n backLink?: { href: string; label?: string } | null;\n /** Render links with your router (defaults to a plain <a>). */\n renderLink?: (props: PdfToolsLinkProps) => ReactNode;\n /** Rendered above the tool's title card, e.g. your app's navigation. */\n header?: ReactNode;\n /** Extra classes for the outer page wrapper (spacing, background). */\n className?: string;\n};\n\nconst PdfToolsContext = createContext<PdfToolsOptions>({});\n\n/** Optional: configures navigation and page chrome for every tool inside it. */\nexport function PdfToolsProvider({ children, ...options }: PdfToolsOptions & { children: ReactNode }) {\n return <PdfToolsContext.Provider value={options}>{children}</PdfToolsContext.Provider>;\n}\n\nconst pageShellClass =\n \"relative overflow-hidden bg-[radial-gradient(circle_at_top_left,_#e0f2fe_0%,_#f8fafc_42%,_#eef2ff_100%)] dark:bg-none dark:bg-slate-950\";\nconst pageContainerClass = \"relative mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-10\";\nconst glassPanelClass =\n \"rounded-3xl border border-slate-200/80 bg-white/80 shadow-lg shadow-slate-200/60 backdrop-blur dark:border-white/8 dark:bg-slate-950/72 dark:shadow-none\";\n\nexport const primaryButtonClass =\n \"inline-flex items-center gap-2 rounded-xl border border-[#1F8CFF] bg-[#1F8CFF] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-[#1579E6] disabled:cursor-not-allowed disabled:opacity-60\";\n\nexport const secondaryButtonClass =\n \"inline-flex items-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800\";\n\nexport const iconButtonClass =\n \"inline-flex h-8 w-8 items-center justify-center rounded-lg border border-slate-200 bg-white text-xs text-slate-600 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-40 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800\";\n\nexport const inputClass =\n \"w-full min-w-0 rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-sm normal-case tracking-normal text-slate-900 outline-none focus:border-[#1F8CFF] disabled:opacity-60 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100\";\n\nexport const fieldLabelClass =\n \"block text-xs font-semibold uppercase tracking-[0.12em] text-slate-600 dark:text-slate-300\";\n\nexport const cardClass =\n \"rounded-2xl border border-slate-200 bg-white/90 p-3 sm:p-4 dark:border-slate-700 dark:bg-slate-900/80\";\n\nexport function PdfToolPage({ title, description, stats = [], children }) {\n const { header, className = \"py-8 sm:py-12\" } = useContext(PdfToolsContext);\n return (\n <div className={`kitsy-pdf ${pageShellClass} ${className}`}>\n <div className={pageContainerClass}>\n {header}\n\n <section className=\"relative overflow-hidden rounded-3xl border border-slate-200/70 bg-gradient-to-br from-slate-900 via-slate-800 to-rose-900 p-8 text-white shadow-2xl shadow-slate-300/40 sm:p-10\">\n <div className=\"absolute -right-20 -top-16 h-52 w-52 rounded-full bg-white/10 blur-3xl\" />\n <div className=\"absolute -left-16 bottom-0 h-36 w-36 rounded-full bg-rose-300/20 blur-3xl\" />\n\n <div className=\"relative flex flex-wrap items-end justify-between gap-6\">\n <div className=\"max-w-2xl\">\n <p className=\"text-xs font-semibold uppercase tracking-[0.28em] text-rose-200/90\">\n PDF Utility\n </p>\n <h1 className=\"mt-3 text-3xl font-semibold leading-tight sm:text-4xl\">{title}</h1>\n <p className=\"mt-4 text-base text-slate-200\">{description}</p>\n </div>\n\n {stats.length ? (\n <div className=\"grid grid-cols-2 gap-3\">\n {stats.map((stat) => (\n <div\n key={stat.label}\n className=\"rounded-2xl border border-white/15 bg-white/10 px-4 py-3 text-center backdrop-blur\"\n >\n <p className=\"text-2xl font-semibold\">{stat.value}</p>\n <p className=\"mt-1 text-xs uppercase tracking-[0.2em] text-slate-200\">\n {stat.label}\n </p>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n </section>\n\n {children}\n </div>\n </div>\n );\n}\n\n/** Main form panel plus a 20rem side panel, matching the other app pages. */\nexport function PdfToolColumns({ main, side }) {\n return (\n <div className=\"mt-8 grid gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <section className={`${glassPanelClass} p-5 sm:p-6`}>{main}</section>\n <section className={`${glassPanelClass} p-5 sm:p-6`}>{side}</section>\n </div>\n );\n}\n\nexport function SideTips({ tips }) {\n return (\n <div className=\"mt-4 rounded-xl border border-dashed border-slate-300 bg-slate-50 p-4 text-xs leading-relaxed text-slate-600 dark:border-slate-700 dark:bg-slate-900/70 dark:text-slate-300\">\n <p className=\"font-semibold text-slate-800 dark:text-slate-200\">Tips</p>\n <ul className=\"mt-2 list-disc space-y-1 pl-4\">\n {tips.map((tip, index) => (\n <li key={index}>{tip}</li>\n ))}\n </ul>\n </div>\n );\n}\n\nexport function SummaryList({ items }) {\n return (\n <dl className=\"mt-4 space-y-2 text-sm\">\n {items.map((item) => (\n <div\n key={item.label}\n className=\"flex justify-between gap-3 rounded-xl border border-slate-200 bg-white/90 px-3 py-2 dark:border-slate-700 dark:bg-slate-900/80\"\n >\n <dt className=\"text-slate-600 dark:text-slate-300\">{item.label}</dt>\n <dd className=\"truncate font-semibold text-slate-900 dark:text-slate-100\">{item.value}</dd>\n </div>\n ))}\n </dl>\n );\n}\n\nexport function StatusBanner({ status }) {\n let toneClass =\n \"border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-700 dark:bg-slate-900/80 dark:text-slate-300\";\n if (status.tone === \"error\") {\n toneClass =\n \"border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-300\";\n } else if (status.tone === \"success\") {\n toneClass =\n \"border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-300\";\n }\n\n return (\n <div role=\"status\" className={`mt-4 rounded-xl border px-4 py-3 text-sm ${toneClass}`}>\n {status.text}\n </div>\n );\n}\n\nexport function FileDropzone({\n accept,\n multiple = false,\n disabled = false,\n onFiles,\n title,\n hint,\n icon = \"fa-solid fa-file-pdf\",\n}) {\n const inputRef = useRef(null);\n const [isDropActive, setIsDropActive] = useState(false);\n\n const handleDrop = (event) => {\n event.preventDefault();\n setIsDropActive(false);\n if (!disabled) onFiles(Array.from(event.dataTransfer.files || []));\n };\n\n const handleDragOver = (event) => {\n event.preventDefault();\n setIsDropActive(true);\n };\n\n const handleDragLeave = (event) => {\n event.preventDefault();\n setIsDropActive(false);\n };\n\n return (\n <>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n onChange={(event) => {\n onFiles(Array.from(event.target.files || []));\n event.target.value = \"\";\n }}\n disabled={disabled}\n className=\"hidden\"\n />\n\n <button\n type=\"button\"\n onClick={() => inputRef.current?.click()}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n disabled={disabled}\n className={`group w-full rounded-2xl border-2 border-dashed p-8 text-left transition ${\n isDropActive\n ? \"border-[#1F8CFF] bg-blue-50 dark:bg-blue-500/10\"\n : \"border-slate-300 bg-white hover:border-[#1F8CFF] hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900/70 dark:hover:border-[#1F8CFF] dark:hover:bg-slate-800\"\n }`}\n >\n <div className=\"flex flex-col items-center text-center\">\n <span className=\"inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-[#1F8CFF]/10 text-[#1F8CFF]\">\n <i className={`${icon} text-xl`} />\n </span>\n <p className=\"mt-4 text-lg font-semibold text-slate-900 dark:text-slate-100\">{title}</p>\n <p className=\"mt-2 text-sm text-slate-600 dark:text-slate-300\">{hint}</p>\n </div>\n </button>\n </>\n );\n}\n\nexport function BackToPdfTools() {\n const { backLink, renderLink } = useContext(PdfToolsContext);\n if (!backLink) return null;\n const children = (\n <>\n <i className=\"fa-solid fa-arrow-left\" />\n {backLink.label || \"Back to PDF Tools\"}\n </>\n );\n return renderLink ? (\n <>{renderLink({ href: backLink.href, className: secondaryButtonClass, children })}</>\n ) : (\n <a href={backLink.href} className={secondaryButtonClass}>\n {children}\n </a>\n );\n}\n\nexport function SelectedPdfCard({ file, pageCount, onRemove, disabled, detail = undefined, signed = false }) {\n const card = (\n <div className={`mt-5 flex items-center gap-3 ${cardClass}`}>\n <span className=\"inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-rose-500/10 text-rose-600 dark:text-rose-300\">\n <i className=\"fa-solid fa-file-pdf\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-slate-900 dark:text-slate-100\">{file.name}</p>\n <p className=\"text-xs text-slate-500 dark:text-slate-400\">\n {detail ?? (pageCount ? `${pageCount} page${pageCount === 1 ? \"\" : \"s\"}` : \"reading...\")}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onRemove}\n disabled={disabled}\n className={iconButtonClass}\n aria-label={`Remove ${file.name}`}\n >\n <i className=\"fa-solid fa-xmark\" />\n </button>\n </div>\n );\n if (!signed) return card;\n return (\n <>\n {card}\n <p className=\"mt-2 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100\">\n <i className=\"fa-solid fa-triangle-exclamation mr-1\" />\n This PDF is digitally signed. Saving a changed copy will invalidate its signature; the original file is not changed.\n </p>\n </>\n );\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { producerName } from './chunk-DQPABZTU.js';
|
|
2
|
+
import { parsePageSelection } from './chunk-BLHNKR3S.js';
|
|
3
|
+
import { loadPdf } from './chunk-EKHEY3LI.js';
|
|
4
|
+
import { PDFDocument } from 'pdf-lib';
|
|
5
|
+
|
|
6
|
+
async function mergePdfs(sources, options = {}) {
|
|
7
|
+
if (!sources.length) {
|
|
8
|
+
throw new Error("Add at least one PDF to merge.");
|
|
9
|
+
}
|
|
10
|
+
const merged = await PDFDocument.create();
|
|
11
|
+
for (const source of sources) {
|
|
12
|
+
let doc;
|
|
13
|
+
try {
|
|
14
|
+
doc = await loadPdf(source.bytes);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw new Error(`${source.name}: ${error.message}`);
|
|
17
|
+
}
|
|
18
|
+
let indices;
|
|
19
|
+
try {
|
|
20
|
+
indices = parsePageSelection(source.pages, doc.getPageCount());
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new Error(`${source.name}: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
const pages = await merged.copyPages(doc, indices);
|
|
25
|
+
pages.forEach((page) => merged.addPage(page));
|
|
26
|
+
}
|
|
27
|
+
if (options.title) merged.setTitle(options.title);
|
|
28
|
+
merged.setProducer(producerName());
|
|
29
|
+
merged.setCreationDate(/* @__PURE__ */ new Date());
|
|
30
|
+
return merged.save();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { mergePdfs };
|
|
34
|
+
//# sourceMappingURL=chunk-HDTM3LXD.js.map
|
|
35
|
+
//# sourceMappingURL=chunk-HDTM3LXD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/merge/mergePdfCore.ts"],"names":[],"mappings":";;;;;AAmBA,eAAsB,SAAA,CAAU,OAAA,EAA2B,OAAA,GAA2B,EAAC,EAAG;AACxF,EAAA,IAAI,CAAC,QAAQ,MAAA,EAAQ;AACnB,IAAA,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAAA,EAClD;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,MAAA,EAAO;AAExC,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI;AACF,MAAA,GAAA,GAAM,MAAM,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAA;AAAA,IAClC,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,IAAI,MAAM,CAAA,EAAG,MAAA,CAAO,IAAI,CAAA,EAAA,EAAM,KAAA,CAAgB,OAAO,CAAA,CAAE,CAAA;AAAA,IAC/D;AAEA,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,kBAAA,CAAmB,MAAA,CAAO,KAAA,EAAO,GAAA,CAAI,cAAc,CAAA;AAAA,IAC/D,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,IAAI,MAAM,CAAA,EAAG,MAAA,CAAO,IAAI,CAAA,EAAA,EAAM,KAAA,CAAgB,OAAO,CAAA,CAAE,CAAA;AAAA,IAC/D;AAEA,IAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,SAAA,CAAU,KAAK,OAAO,CAAA;AACjD,IAAA,KAAA,CAAM,QAAQ,CAAC,IAAA,KAAS,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,EAC9C;AAEA,EAAA,IAAI,OAAA,CAAQ,KAAA,EAAO,MAAA,CAAO,QAAA,CAAS,QAAQ,KAAK,CAAA;AAChD,EAAA,MAAA,CAAO,WAAA,CAAY,cAAc,CAAA;AACjC,EAAA,MAAA,CAAO,eAAA,iBAAgB,IAAI,IAAA,EAAM,CAAA;AAEjC,EAAA,OAAO,OAAO,IAAA,EAAK;AACrB","file":"chunk-HDTM3LXD.js","sourcesContent":["import { PDFDocument } from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\nimport { parsePageSelection } from \"../shared/pageRanges\";\nimport { producerName } from \"../config\";\n\nexport { isPdfFile, getPdfPageCount, loadPdf } from \"../shared/files\";\nexport { parsePageSelection } from \"../shared/pageRanges\";\n\nexport type MergePdfSource = {\n name: string;\n bytes: ArrayBuffer | Uint8Array;\n /** Optional 1-based page selection, e.g. \"1-3, 5, 8-\". Empty means all pages. */\n pages?: string;\n};\n\nexport type MergePdfOptions = {\n title?: string;\n};\n\nexport async function mergePdfs(sources: MergePdfSource[], options: MergePdfOptions = {}) {\n if (!sources.length) {\n throw new Error(\"Add at least one PDF to merge.\");\n }\n\n const merged = await PDFDocument.create();\n\n for (const source of sources) {\n let doc: PDFDocument;\n try {\n doc = await loadPdf(source.bytes);\n } catch (error) {\n throw new Error(`${source.name}: ${(error as Error).message}`);\n }\n\n let indices: number[];\n try {\n indices = parsePageSelection(source.pages, doc.getPageCount());\n } catch (error) {\n throw new Error(`${source.name}: ${(error as Error).message}`);\n }\n\n const pages = await merged.copyPages(doc, indices);\n pages.forEach((page) => merged.addPage(page));\n }\n\n if (options.title) merged.setTitle(options.title);\n merged.setProducer(producerName());\n merged.setCreationDate(new Date());\n\n return merged.save();\n}\n"]}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { verifyPdfSignatures } from './chunk-5OIMR74G.js';
|
|
2
|
+
import { MAX_SINGLE_PDF_BYTES } from './chunk-BK3MYSUS.js';
|
|
3
|
+
import { PdfToolPage, PdfToolColumns, SideTips, SelectedPdfCard, FileDropzone, StatusBanner, BackToPdfTools, cardClass, SummaryList } from './chunk-H6EERRCR.js';
|
|
4
|
+
import { isPdfFile } from './chunk-EKHEY3LI.js';
|
|
5
|
+
import { useRef, useState, useEffect, useCallback } from 'react';
|
|
6
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
var formatDay = (date) => date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
|
|
9
|
+
function verdict(check) {
|
|
10
|
+
if (check.intact && check.coversWholeFile) {
|
|
11
|
+
return { tone: "good", icon: "fa-circle-check", title: "Signature is valid", detail: "The document has not changed since it was signed." };
|
|
12
|
+
}
|
|
13
|
+
if (check.intact) {
|
|
14
|
+
return {
|
|
15
|
+
tone: "warn",
|
|
16
|
+
icon: "fa-triangle-exclamation",
|
|
17
|
+
title: "Valid, but the file was changed later",
|
|
18
|
+
detail: "The signed version is intact, but content was added after signing (for example a later signature, comments, or edits)."
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (check.intact === false) {
|
|
22
|
+
return { tone: "bad", icon: "fa-circle-xmark", title: "Signature is invalid", detail: check.problem || "The document was changed after it was signed." };
|
|
23
|
+
}
|
|
24
|
+
return { tone: "warn", icon: "fa-circle-question", title: "Could not be checked", detail: check.problem || "This signature could not be verified." };
|
|
25
|
+
}
|
|
26
|
+
var TONES = {
|
|
27
|
+
good: "border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-100",
|
|
28
|
+
warn: "border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100",
|
|
29
|
+
bad: "border-rose-200 bg-rose-50 text-rose-900 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-100"
|
|
30
|
+
};
|
|
31
|
+
function SignatureCard({ check, index }) {
|
|
32
|
+
const result = verdict(check);
|
|
33
|
+
const signer = check.signer;
|
|
34
|
+
return /* @__PURE__ */ jsxs("section", { className: `mt-5 ${cardClass}`, "data-testid": "signature-result", children: [
|
|
35
|
+
/* @__PURE__ */ jsxs("div", { className: `flex items-start gap-3 rounded-xl border px-4 py-3 ${TONES[result.tone]}`, children: [
|
|
36
|
+
/* @__PURE__ */ jsx("i", { className: `fa-solid ${result.icon} mt-0.5` }),
|
|
37
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
38
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: result.title }),
|
|
39
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs", children: result.detail })
|
|
40
|
+
] })
|
|
41
|
+
] }),
|
|
42
|
+
/* @__PURE__ */ jsxs("h3", { className: "mt-4 text-sm font-semibold text-slate-900 dark:text-slate-100", children: [
|
|
43
|
+
"Signature ",
|
|
44
|
+
index + 1,
|
|
45
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-2 font-normal text-slate-500 dark:text-slate-400", children: [
|
|
46
|
+
"(",
|
|
47
|
+
check.fieldName,
|
|
48
|
+
")"
|
|
49
|
+
] })
|
|
50
|
+
] }),
|
|
51
|
+
/* @__PURE__ */ jsx(
|
|
52
|
+
SummaryList,
|
|
53
|
+
{
|
|
54
|
+
items: [
|
|
55
|
+
{ label: "Signed by", value: signer?.commonName || "-" },
|
|
56
|
+
{ label: "Organisation", value: signer?.organization || "-" },
|
|
57
|
+
{ label: "Email", value: signer?.email || "-" },
|
|
58
|
+
{ label: "Certificate issued by", value: signer ? signer.issuerCommonName || signer.issuerOrganization || "-" : "-" },
|
|
59
|
+
{ label: "Certificate valid", value: signer ? `${formatDay(signer.notBefore)} \u2013 ${formatDay(signer.notAfter)}` : "-" },
|
|
60
|
+
{ label: "Signed on", value: check.signingTime ? check.signingTime.toLocaleString() : "-" },
|
|
61
|
+
{ label: "Reason", value: check.reason || "-" },
|
|
62
|
+
{ label: "Location", value: check.location || "-" },
|
|
63
|
+
{ label: "Format", value: check.subFilter || "-" }
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
),
|
|
67
|
+
!check.coversWholeFile ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-amber-700 dark:text-amber-300", children: "Signed on, reason, and location are read from the file's latest version, which was changed after signing, so they may not be what the signer wrote." }) : null,
|
|
68
|
+
signer?.selfSigned ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-amber-700 dark:text-amber-300", children: "The certificate is self-signed, so anyone could have made it." }) : null,
|
|
69
|
+
check.certificateValidAtSigning === false ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-rose-700 dark:text-rose-300", children: "The certificate was not valid at the claimed signing time." }) : null
|
|
70
|
+
] });
|
|
71
|
+
}
|
|
72
|
+
function VerifyPdfSignatures() {
|
|
73
|
+
const requestRef = useRef(0);
|
|
74
|
+
const [file, setFile] = useState(null);
|
|
75
|
+
const [checks, setChecks] = useState(null);
|
|
76
|
+
const [isReading, setIsReading] = useState(false);
|
|
77
|
+
const [status, setStatus] = useState({ tone: "info", text: "Add a signed PDF to check its digital signatures." });
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
document.title = "Verify PDF Signatures | Apps";
|
|
80
|
+
}, []);
|
|
81
|
+
const clear = () => {
|
|
82
|
+
requestRef.current += 1;
|
|
83
|
+
setFile(null);
|
|
84
|
+
setChecks(null);
|
|
85
|
+
setIsReading(false);
|
|
86
|
+
setStatus({ tone: "info", text: "Add a signed PDF to check its digital signatures." });
|
|
87
|
+
};
|
|
88
|
+
const selectFiles = useCallback(async (files) => {
|
|
89
|
+
const next = (files || []).find(isPdfFile);
|
|
90
|
+
if (!next) {
|
|
91
|
+
if (files?.length) setStatus({ tone: "error", text: "Only PDF files are supported." });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (next.size > MAX_SINGLE_PDF_BYTES) {
|
|
95
|
+
setStatus({ tone: "error", text: "This PDF is larger than 200 MB." });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const request = ++requestRef.current;
|
|
99
|
+
setFile(next);
|
|
100
|
+
setChecks(null);
|
|
101
|
+
setIsReading(true);
|
|
102
|
+
setStatus({ tone: "info", text: "Checking signatures..." });
|
|
103
|
+
try {
|
|
104
|
+
const result = await verifyPdfSignatures(await next.arrayBuffer());
|
|
105
|
+
if (request !== requestRef.current) return;
|
|
106
|
+
setChecks(result);
|
|
107
|
+
const valid = result.filter((check) => check.intact && check.coversWholeFile).length;
|
|
108
|
+
setStatus(
|
|
109
|
+
result.length ? {
|
|
110
|
+
tone: valid === result.length ? "success" : "error",
|
|
111
|
+
text: `${result.length} signature${result.length === 1 ? "" : "s"} found, ${valid} fully valid.`
|
|
112
|
+
} : { tone: "info", text: `${next.name} has no digital signatures.` }
|
|
113
|
+
);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (request !== requestRef.current) return;
|
|
116
|
+
setFile(null);
|
|
117
|
+
setStatus({ tone: "error", text: error?.message || "Could not read this PDF." });
|
|
118
|
+
} finally {
|
|
119
|
+
if (request === requestRef.current) setIsReading(false);
|
|
120
|
+
}
|
|
121
|
+
}, []);
|
|
122
|
+
return /* @__PURE__ */ jsx(
|
|
123
|
+
PdfToolPage,
|
|
124
|
+
{
|
|
125
|
+
title: "Verify PDF Signatures",
|
|
126
|
+
description: "Check the digital signatures in a PDF: who signed it, when, and whether it has changed since. Runs in your browser; files never leave your device.",
|
|
127
|
+
stats: [
|
|
128
|
+
{ value: checks ? checks.length : "-", label: "Signatures" },
|
|
129
|
+
{ value: checks ? checks.filter((check) => check.intact && check.coversWholeFile).length : "-", label: "Valid" }
|
|
130
|
+
],
|
|
131
|
+
children: /* @__PURE__ */ jsx(
|
|
132
|
+
PdfToolColumns,
|
|
133
|
+
{
|
|
134
|
+
main: /* @__PURE__ */ jsxs("div", { children: [
|
|
135
|
+
file ? /* @__PURE__ */ jsx(
|
|
136
|
+
SelectedPdfCard,
|
|
137
|
+
{
|
|
138
|
+
file,
|
|
139
|
+
pageCount: 0,
|
|
140
|
+
detail: isReading ? "checking..." : `${checks?.length ?? 0} signature${checks?.length === 1 ? "" : "s"}`,
|
|
141
|
+
onRemove: clear,
|
|
142
|
+
disabled: isReading
|
|
143
|
+
}
|
|
144
|
+
) : /* @__PURE__ */ jsx(
|
|
145
|
+
FileDropzone,
|
|
146
|
+
{
|
|
147
|
+
accept: ".pdf,application/pdf",
|
|
148
|
+
disabled: isReading,
|
|
149
|
+
onFiles: selectFiles,
|
|
150
|
+
title: "Drag and drop a signed PDF here",
|
|
151
|
+
hint: "or click to browse from your device"
|
|
152
|
+
}
|
|
153
|
+
),
|
|
154
|
+
/* @__PURE__ */ jsx(StatusBanner, { status }),
|
|
155
|
+
(checks || []).map((check, index) => /* @__PURE__ */ jsx(SignatureCard, { check, index }, `${check.fieldName}-${index}`)),
|
|
156
|
+
/* @__PURE__ */ jsx("div", { className: "mt-5 flex flex-wrap gap-3", children: /* @__PURE__ */ jsx(BackToPdfTools, {}) })
|
|
157
|
+
] }),
|
|
158
|
+
side: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
159
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "What is checked" }),
|
|
160
|
+
/* @__PURE__ */ jsx(
|
|
161
|
+
SideTips,
|
|
162
|
+
{
|
|
163
|
+
tips: [
|
|
164
|
+
"Integrity: the signed bytes still match the signature.",
|
|
165
|
+
"Coverage: whether anything was added to the file after signing.",
|
|
166
|
+
"Signer details come from the certificate inside the signature.",
|
|
167
|
+
"Not checked: whether the certificate is trusted, revoked, or issued by a licensed authority. Use Adobe Reader for a trust check."
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
] })
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { VerifyPdfSignatures };
|
|
179
|
+
//# sourceMappingURL=chunk-IGMX56C3.js.map
|
|
180
|
+
//# sourceMappingURL=chunk-IGMX56C3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/certsign/VerifyPdfSignatures.tsx"],"names":[],"mappings":"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","file":"chunk-IGMX56C3.js","sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { isPdfFile } from \"../shared/files\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SelectedPdfCard,\n SideTips,\n StatusBanner,\n SummaryList,\n cardClass,\n} from \"../shared/ToolLayout\";\nimport { MAX_SINGLE_PDF_BYTES } from \"../shared/useSinglePdf\";\nimport { verifyPdfSignatures, type SignatureCheck } from \"./verifyCore\";\n\nconst formatDay = (date: Date) =>\n date.toLocaleDateString(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" });\n\nfunction verdict(check: SignatureCheck) {\n if (check.intact && check.coversWholeFile) {\n return { tone: \"good\", icon: \"fa-circle-check\", title: \"Signature is valid\", detail: \"The document has not changed since it was signed.\" };\n }\n if (check.intact) {\n return {\n tone: \"warn\",\n icon: \"fa-triangle-exclamation\",\n title: \"Valid, but the file was changed later\",\n detail: \"The signed version is intact, but content was added after signing (for example a later signature, comments, or edits).\",\n };\n }\n if (check.intact === false) {\n return { tone: \"bad\", icon: \"fa-circle-xmark\", title: \"Signature is invalid\", detail: check.problem || \"The document was changed after it was signed.\" };\n }\n return { tone: \"warn\", icon: \"fa-circle-question\", title: \"Could not be checked\", detail: check.problem || \"This signature could not be verified.\" };\n}\n\nconst TONES = {\n good: \"border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-100\",\n warn: \"border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100\",\n bad: \"border-rose-200 bg-rose-50 text-rose-900 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-100\",\n};\n\nfunction SignatureCard({ check, index }: { check: SignatureCheck; index: number }) {\n const result = verdict(check);\n const signer = check.signer;\n return (\n <section className={`mt-5 ${cardClass}`} data-testid=\"signature-result\">\n <div className={`flex items-start gap-3 rounded-xl border px-4 py-3 ${TONES[result.tone]}`}>\n <i className={`fa-solid ${result.icon} mt-0.5`} />\n <div>\n <p className=\"text-sm font-semibold\">{result.title}</p>\n <p className=\"mt-0.5 text-xs\">{result.detail}</p>\n </div>\n </div>\n <h3 className=\"mt-4 text-sm font-semibold text-slate-900 dark:text-slate-100\">\n Signature {index + 1}\n <span className=\"ml-2 font-normal text-slate-500 dark:text-slate-400\">({check.fieldName})</span>\n </h3>\n <SummaryList\n items={[\n { label: \"Signed by\", value: signer?.commonName || \"-\" },\n { label: \"Organisation\", value: signer?.organization || \"-\" },\n { label: \"Email\", value: signer?.email || \"-\" },\n { label: \"Certificate issued by\", value: signer ? signer.issuerCommonName || signer.issuerOrganization || \"-\" : \"-\" },\n { label: \"Certificate valid\", value: signer ? `${formatDay(signer.notBefore)} – ${formatDay(signer.notAfter)}` : \"-\" },\n { label: \"Signed on\", value: check.signingTime ? check.signingTime.toLocaleString() : \"-\" },\n { label: \"Reason\", value: check.reason || \"-\" },\n { label: \"Location\", value: check.location || \"-\" },\n { label: \"Format\", value: check.subFilter || \"-\" },\n ]}\n />\n {!check.coversWholeFile ? (\n <p className=\"mt-2 text-xs text-amber-700 dark:text-amber-300\">\n Signed on, reason, and location are read from the file's latest version, which was changed after signing, so\n they may not be what the signer wrote.\n </p>\n ) : null}\n {signer?.selfSigned ? (\n <p className=\"mt-2 text-xs text-amber-700 dark:text-amber-300\">The certificate is self-signed, so anyone could have made it.</p>\n ) : null}\n {check.certificateValidAtSigning === false ? (\n <p className=\"mt-2 text-xs text-rose-700 dark:text-rose-300\">The certificate was not valid at the claimed signing time.</p>\n ) : null}\n </section>\n );\n}\n\nexport default function VerifyPdfSignatures() {\n const requestRef = useRef(0);\n const [file, setFile] = useState(null);\n const [checks, setChecks] = useState<SignatureCheck[] | null>(null);\n const [isReading, setIsReading] = useState(false);\n const [status, setStatus] = useState({ tone: \"info\", text: \"Add a signed PDF to check its digital signatures.\" });\n\n useEffect(() => {\n document.title = \"Verify PDF Signatures | Apps\";\n }, []);\n\n const clear = () => {\n requestRef.current += 1;\n setFile(null);\n setChecks(null);\n setIsReading(false);\n setStatus({ tone: \"info\", text: \"Add a signed PDF to check its digital signatures.\" });\n };\n\n // Own loader: encrypted PDFs can still be verified, so this skips useSinglePdf.\n const selectFiles = useCallback(async (files) => {\n const next = (files || []).find(isPdfFile);\n if (!next) {\n if (files?.length) setStatus({ tone: \"error\", text: \"Only PDF files are supported.\" });\n return;\n }\n if (next.size > MAX_SINGLE_PDF_BYTES) {\n setStatus({ tone: \"error\", text: \"This PDF is larger than 200 MB.\" });\n return;\n }\n const request = ++requestRef.current;\n setFile(next);\n setChecks(null);\n setIsReading(true);\n setStatus({ tone: \"info\", text: \"Checking signatures...\" });\n try {\n const result = await verifyPdfSignatures(await next.arrayBuffer());\n if (request !== requestRef.current) return;\n setChecks(result);\n const valid = result.filter((check) => check.intact && check.coversWholeFile).length;\n setStatus(\n result.length\n ? {\n tone: valid === result.length ? \"success\" : \"error\",\n text: `${result.length} signature${result.length === 1 ? \"\" : \"s\"} found, ${valid} fully valid.`,\n }\n : { tone: \"info\", text: `${next.name} has no digital signatures.` }\n );\n } catch (error) {\n if (request !== requestRef.current) return;\n setFile(null);\n setStatus({ tone: \"error\", text: error?.message || \"Could not read this PDF.\" });\n } finally {\n if (request === requestRef.current) setIsReading(false);\n }\n }, []);\n\n return (\n <PdfToolPage\n title=\"Verify PDF Signatures\"\n description=\"Check the digital signatures in a PDF: who signed it, when, and whether it has changed since. Runs in your browser; files never leave your device.\"\n stats={[\n { value: checks ? checks.length : \"-\", label: \"Signatures\" },\n { value: checks ? checks.filter((check) => check.intact && check.coversWholeFile).length : \"-\", label: \"Valid\" },\n ]}\n >\n <PdfToolColumns\n main={\n <div>\n {file ? (\n <SelectedPdfCard\n file={file}\n pageCount={0}\n detail={isReading ? \"checking...\" : `${checks?.length ?? 0} signature${checks?.length === 1 ? \"\" : \"s\"}`}\n onRemove={clear}\n disabled={isReading}\n />\n ) : (\n <FileDropzone\n accept=\".pdf,application/pdf\"\n disabled={isReading}\n onFiles={selectFiles}\n title=\"Drag and drop a signed PDF here\"\n hint=\"or click to browse from your device\"\n />\n )}\n <StatusBanner status={status} />\n {(checks || []).map((check, index) => (\n <SignatureCard key={`${check.fieldName}-${index}`} check={check} index={index} />\n ))}\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <BackToPdfTools />\n </div>\n </div>\n }\n side={\n <>\n <h2 className=\"text-base font-semibold text-slate-900 dark:text-slate-100\">What is checked</h2>\n <SideTips\n tips={[\n \"Integrity: the signed bytes still match the signature.\",\n \"Coverage: whether anything was added to the file after signing.\",\n \"Signer details come from the certificate inside the signature.\",\n \"Not checked: whether the certificate is trusted, revoked, or issued by a licensed authority. Use Adobe Reader for a trust check.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { openRenderDocument, renderThumbnail } from './chunk-DPEJU4P6.js';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
function useRenderDocument(bytes) {
|
|
5
|
+
const [doc, setDoc] = useState(null);
|
|
6
|
+
const [error, setError] = useState("");
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!bytes) {
|
|
9
|
+
setDoc(null);
|
|
10
|
+
setError("");
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
let cancelled = false;
|
|
14
|
+
let opened = null;
|
|
15
|
+
setDoc(null);
|
|
16
|
+
setError("");
|
|
17
|
+
openRenderDocument(bytes).then((result) => {
|
|
18
|
+
opened = result;
|
|
19
|
+
if (cancelled) {
|
|
20
|
+
result.destroy();
|
|
21
|
+
} else {
|
|
22
|
+
setDoc(result);
|
|
23
|
+
}
|
|
24
|
+
}).catch((reason) => {
|
|
25
|
+
if (!cancelled) setError(reason?.message || "Could not render this PDF.");
|
|
26
|
+
});
|
|
27
|
+
return () => {
|
|
28
|
+
cancelled = true;
|
|
29
|
+
opened?.destroy();
|
|
30
|
+
};
|
|
31
|
+
}, [bytes]);
|
|
32
|
+
return { doc, error };
|
|
33
|
+
}
|
|
34
|
+
function useThumbnails(doc, width = 160) {
|
|
35
|
+
const [thumbnails, setThumbnails] = useState([]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!doc) {
|
|
38
|
+
setThumbnails([]);
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
let cancelled = false;
|
|
42
|
+
const urls = [];
|
|
43
|
+
setThumbnails(Array.from({ length: doc.numPages }, () => null));
|
|
44
|
+
(async () => {
|
|
45
|
+
for (let pageNumber = 1; pageNumber <= doc.numPages; pageNumber += 1) {
|
|
46
|
+
if (cancelled) return;
|
|
47
|
+
try {
|
|
48
|
+
const thumb = await renderThumbnail(doc, pageNumber, width);
|
|
49
|
+
if (cancelled) {
|
|
50
|
+
URL.revokeObjectURL(thumb.url);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
urls.push(thumb.url);
|
|
54
|
+
setThumbnails((current) => {
|
|
55
|
+
const next = [...current];
|
|
56
|
+
next[pageNumber - 1] = thumb;
|
|
57
|
+
return next;
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
if (cancelled) return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
return () => {
|
|
65
|
+
cancelled = true;
|
|
66
|
+
urls.forEach((url) => URL.revokeObjectURL(url));
|
|
67
|
+
};
|
|
68
|
+
}, [doc, width]);
|
|
69
|
+
return thumbnails;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { useRenderDocument, useThumbnails };
|
|
73
|
+
//# sourceMappingURL=chunk-J4IRICCJ.js.map
|
|
74
|
+
//# sourceMappingURL=chunk-J4IRICCJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/useRenderDocument.ts"],"names":[],"mappings":"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","file":"chunk-J4IRICCJ.js","sourcesContent":["import { useEffect, useState } from \"react\";\nimport { openRenderDocument, renderThumbnail } from \"./pdfRender\";\n\n/** Open bytes in pdf.js; destroys the previous document when bytes change. */\nexport function useRenderDocument(bytes) {\n const [doc, setDoc] = useState(null);\n const [error, setError] = useState(\"\");\n\n useEffect(() => {\n if (!bytes) {\n setDoc(null);\n setError(\"\");\n return undefined;\n }\n\n let cancelled = false;\n let opened = null;\n setDoc(null);\n setError(\"\");\n openRenderDocument(bytes)\n .then((result) => {\n opened = result;\n if (cancelled) {\n result.destroy();\n } else {\n setDoc(result);\n }\n })\n .catch((reason) => {\n if (!cancelled) setError(reason?.message || \"Could not render this PDF.\");\n });\n\n return () => {\n cancelled = true;\n opened?.destroy();\n };\n }, [bytes]);\n\n return { doc, error };\n}\n\n/**\n * Render page thumbnails one after another (to keep memory and the worker\n * calm), exposing each as soon as it is ready. Index i is page i + 1.\n */\nexport function useThumbnails(doc, width = 160) {\n const [thumbnails, setThumbnails] = useState([]);\n\n useEffect(() => {\n if (!doc) {\n setThumbnails([]);\n return undefined;\n }\n\n let cancelled = false;\n const urls = [];\n setThumbnails(Array.from({ length: doc.numPages }, () => null));\n\n (async () => {\n for (let pageNumber = 1; pageNumber <= doc.numPages; pageNumber += 1) {\n if (cancelled) return;\n try {\n const thumb = await renderThumbnail(doc, pageNumber, width);\n if (cancelled) {\n URL.revokeObjectURL(thumb.url);\n return;\n }\n urls.push(thumb.url);\n setThumbnails((current) => {\n const next = [...current];\n next[pageNumber - 1] = thumb;\n return next;\n });\n } catch {\n if (cancelled) return;\n }\n }\n })();\n\n return () => {\n cancelled = true;\n urls.forEach((url) => URL.revokeObjectURL(url));\n };\n }, [doc, width]);\n\n return thumbnails;\n}\n"]}
|