-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathpdf.js
More file actions
128 lines (113 loc) · 4.7 KB
/
Copy pathpdf.js
File metadata and controls
128 lines (113 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { sanitizePdfFilename } from "../../shared/file.js";
import { createPdfError, loadPdfSource, requirePdfDocument } from "../../shared/pdf.js";
function rangeError(code) {
const error = new Error(code);
error.code = code;
return error;
}
function validatePage(page, pageCount) {
if (!Number.isSafeInteger(page) || page < 1) throw rangeError("PAGE_RANGE_INVALID");
if (page > pageCount) throw rangeError("PAGE_OUT_OF_RANGE");
}
export function parsePageSelection(value, pageCount) {
const input = String(value || "").trim();
if (!input) throw rangeError("PAGE_RANGE_REQUIRED");
const pages = [];
for (const rawToken of input.split(",")) {
const token = rawToken.trim();
if (!token) throw rangeError("PAGE_RANGE_INVALID");
const single = token.match(/^(\d+)$/);
if (single) {
const page = Number(single[1]);
validatePage(page, pageCount);
pages.push(page - 1);
continue;
}
const range = token.match(/^(\d+)\s*-\s*(\d+)$/);
if (!range) throw rangeError("PAGE_RANGE_INVALID");
const start = Number(range[1]);
const end = Number(range[2]);
validatePage(start, pageCount);
validatePage(end, pageCount);
if (start > end) throw rangeError("PAGE_RANGE_REVERSED");
for (let page = start; page <= end; page += 1) pages.push(page - 1);
}
return pages;
}
export function createPageGroups({ mode, selection, interval, pageCount }) {
if (!Number.isSafeInteger(pageCount) || pageCount < 1) throw rangeError("PDF_HAS_NO_PAGES");
if (mode === "extract") return [parsePageSelection(selection, pageCount)];
if (mode === "every") return Array.from({ length: pageCount }, (_, index) => [index]);
if (mode === "interval") {
const size = Number(interval);
if (!Number.isSafeInteger(size) || size < 1) throw rangeError("INTERVAL_INVALID");
const groups = [];
for (let start = 0; start < pageCount; start += size) {
groups.push(Array.from({ length: Math.min(size, pageCount - start) }, (_, offset) => start + offset));
}
return groups;
}
throw rangeError("SPLIT_MODE_INVALID");
}
function filenameBase(value, fallback) {
return sanitizePdfFilename(value, fallback).replace(/\.pdf$/i, "");
}
export function createSplitNames({ mode, groups, baseName, sourceName, pageCount }) {
const sourceFallback = String(sourceName || "split-document").replace(/\.pdf$/i, "") || "split-document";
const base = filenameBase(baseName, sourceFallback);
if (mode === "extract") return { archive: null, entries: [`${base}.pdf`] };
const width = Math.max(3, String(pageCount).length);
const pad = (page) => String(page).padStart(width, "0");
const entries = groups.map((group) => {
const first = group[0] + 1;
const last = group[group.length - 1] + 1;
return mode === "every"
? `${base}_${pad(first)}.pdf`
: `${base}_pages_${pad(first)}-${pad(last)}.pdf`;
});
return { archive: `${base}_split.zip`, entries };
}
async function createPdfBytes(source, pages, PDFDocument) {
try {
const output = await PDFDocument.create();
const copiedPages = await output.copyPages(source, pages);
copiedPages.forEach((page) => output.addPage(page));
return await output.save();
} catch (error) {
throw createPdfError("PDF_GENERATION_FAILED", undefined, error);
}
}
export async function splitPdfFile({
file, mode, selection, interval, baseName, PDFDocument, JSZip,
onPhase = () => {}, onProgress = () => {},
}) {
requirePdfDocument(PDFDocument);
onPhase("reading");
const source = await loadPdfSource(file, PDFDocument);
const pageCount = source.getPageCount();
const groups = createPageGroups({ mode, selection, interval, pageCount });
const names = createSplitNames({ mode, groups, baseName, sourceName: file.name, pageCount });
onPhase("preparing");
if (mode === "extract") {
const bytes = await createPdfBytes(source, groups[0], PDFDocument);
onProgress(1, 1);
return {
kind: "pdf", filename: names.entries[0], entries: names.entries,
blob: new Blob([bytes], { type: "application/pdf" }), pageCount: groups[0].length,
};
}
if (typeof JSZip !== "function") throw createPdfError("ARCHIVE_LIBRARY_UNAVAILABLE");
const archive = new JSZip();
for (let index = 0; index < groups.length; index += 1) {
const bytes = await createPdfBytes(source, groups[index], PDFDocument);
archive.file(names.entries[index], bytes);
onProgress(index + 1, groups.length);
}
onPhase("archive");
try {
const blob = await archive.generateAsync({ type: "blob", compression: "STORE", streamFiles: true });
return { kind: "zip", filename: names.archive, entries: names.entries, blob, pageCount };
} catch (error) {
throw createPdfError("ARCHIVE_GENERATION_FAILED", undefined, error);
}
}