token-goat 2.9.13 → 2.9.15
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/README.md +28 -1
- package/dist/token-goat-chunk-2X2EBBC6.mjs +277 -0
- package/dist/token-goat-chunk-3BTK54F3.mjs +1733 -0
- package/dist/token-goat-chunk-3NSDDTGL.mjs +34 -0
- package/dist/token-goat-chunk-4EXFN2AW.mjs +29 -0
- package/dist/token-goat-chunk-5V7DAC7V.mjs +123 -0
- package/dist/token-goat-chunk-6DLVZDB6.mjs +34 -0
- package/dist/token-goat-chunk-7ZYK25AO.mjs +24 -0
- package/dist/token-goat-chunk-A4JYKD5H.mjs +144 -0
- package/dist/token-goat-chunk-AH6QILZM.mjs +26 -0
- package/dist/token-goat-chunk-ASYEPR3S.mjs +212 -0
- package/dist/{token-goat-chunk-RDITECDL.mjs → token-goat-chunk-ATIFTMRC.mjs} +31 -13
- package/dist/{token-goat-chunk-ZZI3IDQZ.mjs → token-goat-chunk-BL5LNGBG.mjs} +3935 -11949
- package/dist/{token-goat-chunk-4NXUKV7D.mjs → token-goat-chunk-C5JIO6HK.mjs} +8 -4
- package/dist/{token-goat-chunk-FZU7GMUS.mjs → token-goat-chunk-DQ4J5AFF.mjs} +50 -18
- package/dist/token-goat-chunk-ERTXEKB6.mjs +228 -0
- package/dist/{token-goat-chunk-6B44WLIF.mjs → token-goat-chunk-GIIHUSZX.mjs} +142 -21
- package/dist/token-goat-chunk-GMOUBOX4.mjs +386 -0
- package/dist/{token-goat-chunk-3ESRORNM.mjs → token-goat-chunk-GMQQA7E4.mjs} +12576 -12326
- package/dist/token-goat-chunk-IVUQLQWN.mjs +2046 -0
- package/dist/token-goat-chunk-K7F2BFIK.mjs +2430 -0
- package/dist/{token-goat-chunk-U7X6LQD2.mjs → token-goat-chunk-LCZBPOIN.mjs} +10197 -9717
- package/dist/token-goat-chunk-LMFO66YD.mjs +331 -0
- package/dist/token-goat-chunk-LT7JRU6K.mjs +22 -0
- package/dist/token-goat-chunk-MZDIJJ3R.mjs +420 -0
- package/dist/token-goat-chunk-NDPO7GAH.mjs +177 -0
- package/dist/token-goat-chunk-NDRP4KJQ.mjs +4371 -0
- package/dist/token-goat-chunk-NEI4NC54.mjs +424 -0
- package/dist/token-goat-chunk-NU7TLMQK.mjs +585 -0
- package/dist/token-goat-chunk-OSUFN2FV.mjs +326 -0
- package/dist/token-goat-chunk-OUGNPMDA.mjs +959 -0
- package/dist/token-goat-chunk-PM76YS22.mjs +1341 -0
- package/dist/token-goat-chunk-S4XRY446.mjs +2637 -0
- package/dist/{token-goat-chunk-YOA4N6WA.mjs → token-goat-chunk-SFAS46RE.mjs} +5 -3
- package/dist/{token-goat-chunk-QWSUZWFP.mjs → token-goat-chunk-SZWYESBS.mjs} +793 -650
- package/dist/{token-goat-chunk-B3CTCQTH.mjs → token-goat-chunk-XEPXYDPI.mjs} +3 -2
- package/dist/token-goat-chunk-XTQAOTSO.mjs +89 -0
- package/dist/token-goat-chunk-Y4AFKTHK.mjs +22 -0
- package/dist/token-goat-chunk-YKG35VHC.mjs +228 -0
- package/dist/{token-goat-chunk-JOXLE672.mjs → token-goat-chunk-YQ7WI2CO.mjs} +990 -106
- package/dist/{token-goat-chunk-2WC4ZUXN.mjs → token-goat-chunk-YZX7EFG4.mjs} +1 -1
- package/dist/token-goat-chunk-Z6UXPYJA.mjs +62 -0
- package/dist/token-goat-hook.mjs +16 -8
- package/dist/token-goat.core.mjs +27 -10
- package/docs/cli.md +9 -6
- package/package.json +4 -2
- package/dist/token-goat-chunk-FQCNJV4V.mjs +0 -693
- package/dist/token-goat-chunk-JVNPCQB7.mjs +0 -31
- package/dist/token-goat-chunk-P2PU4CR5.mjs +0 -26
- package/dist/token-goat-chunk-QKXBGBQR.mjs +0 -3653
- package/dist/token-goat-chunk-UM47DRD3.mjs +0 -242
- package/dist/token-goat-chunk-UMXJN7DI.mjs +0 -5521
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
createLazyModuleLoader
|
|
5
|
+
} from "./token-goat-chunk-AH6QILZM.mjs";
|
|
6
|
+
import {
|
|
7
|
+
DocumentRefusedError,
|
|
8
|
+
MAX_DOCUMENT_WORK_MILLIS
|
|
9
|
+
} from "./token-goat-chunk-Y4AFKTHK.mjs";
|
|
10
|
+
import {
|
|
11
|
+
compileGuardedRegex
|
|
12
|
+
} from "./token-goat-chunk-GMOUBOX4.mjs";
|
|
13
|
+
import {
|
|
14
|
+
init_define_import_meta_env
|
|
15
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
16
|
+
|
|
17
|
+
// src/pdf_extract.ts
|
|
18
|
+
init_define_import_meta_env();
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
var loadPdfjs = createLazyModuleLoader(async () => {
|
|
21
|
+
const mod = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
22
|
+
if (typeof import.meta.resolve === "function") {
|
|
23
|
+
try {
|
|
24
|
+
mod.GlobalWorkerOptions.workerSrc = await import.meta.resolve("pdfjs-dist/legacy/build/pdf.worker.mjs");
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return mod;
|
|
29
|
+
}, "pdf-extract disabled (pdfjs-dist unavailable)");
|
|
30
|
+
var MAX_PDF_TEXT_BYTES = 8 * 1024 * 1024;
|
|
31
|
+
var MAX_PDF_INPUT_BYTES = 50 * 1024 * 1024;
|
|
32
|
+
var MAX_PDF_TEXT_ITEMS = 2e5;
|
|
33
|
+
var MAX_OUTLINE_ENTRIES = 2e4;
|
|
34
|
+
var MAX_OUTLINE_TITLE_CHARS = 500;
|
|
35
|
+
var MAX_LOCATE_MATCHES = 1e3;
|
|
36
|
+
var MAX_LOCATE_CONTEXT_CHARS = 4e3;
|
|
37
|
+
var MAX_PDF_WORK_MILLIS = MAX_DOCUMENT_WORK_MILLIS;
|
|
38
|
+
var PdfRefusedError = class extends DocumentRefusedError {
|
|
39
|
+
};
|
|
40
|
+
var PdfTooLargeError = class extends PdfRefusedError {
|
|
41
|
+
constructor(message) {
|
|
42
|
+
super(message, "PdfTooLargeError");
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var PdfTookTooLongError = class extends PdfRefusedError {
|
|
46
|
+
constructor(message) {
|
|
47
|
+
super(message, "PdfTookTooLongError", true);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function pdfWorkDeadline() {
|
|
51
|
+
return Date.now() + MAX_PDF_WORK_MILLIS;
|
|
52
|
+
}
|
|
53
|
+
function pdfWorkTookTooLong() {
|
|
54
|
+
return new PdfTookTooLongError(`reading this PDF's text passed the ${MAX_PDF_WORK_MILLIS}ms limit. Narrow the read with --pages, or use a smaller document.`);
|
|
55
|
+
}
|
|
56
|
+
function assertPdfIsARegularFileWithinBounds(stat, file) {
|
|
57
|
+
if (!stat.isFile()) {
|
|
58
|
+
throw new PdfTooLargeError(`${file} is not a regular file, so its size cannot be checked before reading it.`);
|
|
59
|
+
}
|
|
60
|
+
if (stat.size > MAX_PDF_INPUT_BYTES) {
|
|
61
|
+
throw new PdfTooLargeError(`${file} is ${stat.size} bytes, past the ${MAX_PDF_INPUT_BYTES}-byte limit for a PDF. Split it, or extract from a smaller copy.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function pdfTextBudgetExceeded() {
|
|
65
|
+
return new PdfTooLargeError(`this PDF's text passes the ${MAX_PDF_TEXT_BYTES}-byte extraction limit. Narrow the read with --pages, or use a smaller document.`);
|
|
66
|
+
}
|
|
67
|
+
function pdfPageTooDenseToRead() {
|
|
68
|
+
return new PdfTooLargeError(`a page of this PDF holds more than ${MAX_PDF_TEXT_ITEMS} separate pieces of text, past what extraction will hold whatever they weigh. Use a smaller document.`);
|
|
69
|
+
}
|
|
70
|
+
function assertPdfTextWithinBounds(text) {
|
|
71
|
+
if (Buffer.byteLength(text, "utf8") > MAX_PDF_TEXT_BYTES) throw pdfTextBudgetExceeded();
|
|
72
|
+
}
|
|
73
|
+
async function readPdfFileWithinBounds(file) {
|
|
74
|
+
const handle = await fs.promises.open(file, "r");
|
|
75
|
+
try {
|
|
76
|
+
const stat = await handle.stat();
|
|
77
|
+
assertPdfIsARegularFileWithinBounds(stat, file);
|
|
78
|
+
return await readAllWithinReportedSize(handle, stat.size, file);
|
|
79
|
+
} finally {
|
|
80
|
+
await handle.close();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function readAllWithinReportedSize(handle, reportedSize, file) {
|
|
84
|
+
const buffer = Buffer.alloc(reportedSize + 1);
|
|
85
|
+
let read = 0;
|
|
86
|
+
for (; ; ) {
|
|
87
|
+
const { bytesRead } = await handle.read(buffer, read, buffer.length - read, read);
|
|
88
|
+
if (bytesRead === 0) break;
|
|
89
|
+
read += bytesRead;
|
|
90
|
+
if (read === buffer.length) {
|
|
91
|
+
throw new PdfTooLargeError(`${file} grew past the ${reportedSize} bytes it reported while it was being read. Extract from a copy that is not being written to.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return read === reportedSize ? new Uint8Array(buffer.buffer, buffer.byteOffset, read) : new Uint8Array(buffer.subarray(0, read));
|
|
95
|
+
}
|
|
96
|
+
async function* pageTextItems(page, deadline) {
|
|
97
|
+
const keep = (items) => items.filter((item) => item !== null && typeof item === "object" && "str" in item);
|
|
98
|
+
if (typeof page.streamTextContent !== "function") {
|
|
99
|
+
const content = await raceDeadline(page.getTextContent(), deadline);
|
|
100
|
+
yield keep(content.items);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const reader = page.streamTextContent().getReader();
|
|
104
|
+
for (; ; ) {
|
|
105
|
+
const { done, value } = await raceDeadline(reader.read(), deadline);
|
|
106
|
+
if (done) return;
|
|
107
|
+
yield keep(value?.items ?? []);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function getPageWithinDeadline(doc, pageNum, deadline) {
|
|
111
|
+
if (Date.now() > deadline) throw pdfWorkTookTooLong();
|
|
112
|
+
return raceDeadline(doc.getPage(pageNum), deadline);
|
|
113
|
+
}
|
|
114
|
+
async function readPageTextItems(page, budget, deadline) {
|
|
115
|
+
let items = [];
|
|
116
|
+
let spent = 0;
|
|
117
|
+
let count = 0;
|
|
118
|
+
let over = null;
|
|
119
|
+
for await (const chunk of pageTextItems(page, deadline)) {
|
|
120
|
+
if (Date.now() > deadline) throw pdfWorkTookTooLong();
|
|
121
|
+
if (over) continue;
|
|
122
|
+
for (const item of chunk) spent += item.str.length + 1;
|
|
123
|
+
count += chunk.length;
|
|
124
|
+
if (spent > budget || count > MAX_PDF_TEXT_ITEMS) {
|
|
125
|
+
over = count > MAX_PDF_TEXT_ITEMS ? "items" : "bytes";
|
|
126
|
+
items = [];
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
for (const item of chunk) items.push(item);
|
|
130
|
+
}
|
|
131
|
+
if (over) throw over === "items" ? pdfPageTooDenseToRead() : pdfTextBudgetExceeded();
|
|
132
|
+
return items;
|
|
133
|
+
}
|
|
134
|
+
function parsePageRange(spec, pageCount) {
|
|
135
|
+
if (!spec) return null;
|
|
136
|
+
const m = /^(\d+)(?:-(\d+))?$/.exec(spec.trim());
|
|
137
|
+
if (!m) throw new Error(`invalid --pages spec: ${spec} (expected "N" or "N-M")`);
|
|
138
|
+
const start = parseInt(m[1], 10);
|
|
139
|
+
const end = m[2] ? parseInt(m[2], 10) : start;
|
|
140
|
+
if (start < 1 || end < start) throw new Error(`invalid --pages spec: ${spec}`);
|
|
141
|
+
if (start > pageCount) throw new Error(`invalid --pages spec: ${spec} (page ${start} is past end of document with ${pageCount} pages)`);
|
|
142
|
+
return { start, end: Math.min(end, pageCount) };
|
|
143
|
+
}
|
|
144
|
+
var Y_EPSILON = 2;
|
|
145
|
+
function reconstructLayout(items, deadline) {
|
|
146
|
+
const rows = [];
|
|
147
|
+
const bucketOf = (y) => Math.floor(y / Y_EPSILON);
|
|
148
|
+
const byBucket = /* @__PURE__ */ new Map();
|
|
149
|
+
const bucketOfRow = [];
|
|
150
|
+
const place = (bucket, row) => {
|
|
151
|
+
const list = byBucket.get(bucket);
|
|
152
|
+
if (list) list.push(row);
|
|
153
|
+
else byBucket.set(bucket, [row]);
|
|
154
|
+
bucketOfRow[row] = bucket;
|
|
155
|
+
};
|
|
156
|
+
let checked = 0;
|
|
157
|
+
for (const item of items) {
|
|
158
|
+
if ((checked++ & 1023) === 0 && Date.now() > deadline) throw pdfWorkTookTooLong();
|
|
159
|
+
const y = item.transform[5];
|
|
160
|
+
if (!Number.isFinite(y)) {
|
|
161
|
+
rows.push([item]);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const home = bucketOf(y);
|
|
165
|
+
let found = -1;
|
|
166
|
+
for (const bucket of [home - 1, home, home + 1]) {
|
|
167
|
+
for (const row of byBucket.get(bucket) ?? []) {
|
|
168
|
+
const last = rows[row][rows[row].length - 1];
|
|
169
|
+
if (Math.abs(last.transform[5] - y) < Y_EPSILON && (found === -1 || row < found)) found = row;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (found === -1) {
|
|
173
|
+
rows.push([item]);
|
|
174
|
+
place(home, rows.length - 1);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
;
|
|
178
|
+
rows[found].push(item);
|
|
179
|
+
const was = bucketOfRow[found];
|
|
180
|
+
if (was !== home) {
|
|
181
|
+
const list = byBucket.get(was);
|
|
182
|
+
const at = list.indexOf(found);
|
|
183
|
+
if (at >= 0) list.splice(at, 1);
|
|
184
|
+
place(home, found);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
rows.sort((a, b) => {
|
|
188
|
+
const ya = a[0].transform[5];
|
|
189
|
+
const yb = b[0].transform[5];
|
|
190
|
+
const finiteA = Number.isFinite(ya);
|
|
191
|
+
const finiteB = Number.isFinite(yb);
|
|
192
|
+
if (finiteA && finiteB) return yb - ya;
|
|
193
|
+
if (finiteA) return -1;
|
|
194
|
+
if (finiteB) return 1;
|
|
195
|
+
return 0;
|
|
196
|
+
});
|
|
197
|
+
const lines = [];
|
|
198
|
+
for (const row of rows) {
|
|
199
|
+
row.sort((a, b) => a.transform[4] - b.transform[4]);
|
|
200
|
+
let line = "";
|
|
201
|
+
let prevEndX = null;
|
|
202
|
+
for (const item of row) {
|
|
203
|
+
const x = item.transform[4];
|
|
204
|
+
if (prevEndX !== null) {
|
|
205
|
+
const gap = x - prevEndX;
|
|
206
|
+
line += gap > 20 ? " " : gap > 4 ? " " : "";
|
|
207
|
+
}
|
|
208
|
+
line += item.str;
|
|
209
|
+
prevEndX = x + (item.width ?? 0);
|
|
210
|
+
}
|
|
211
|
+
lines.push(line);
|
|
212
|
+
}
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
}
|
|
215
|
+
async function withPdfDocument(pdfjs, data, fn) {
|
|
216
|
+
const deadline = pdfWorkDeadline();
|
|
217
|
+
const loadingTask = pdfjs.getDocument({ data, useWorkerFetch: false, disableFontFace: true, verbosity: 0 });
|
|
218
|
+
try {
|
|
219
|
+
return await fn(await raceDeadline(loadingTask.promise, deadline), deadline);
|
|
220
|
+
} finally {
|
|
221
|
+
await raceDeadline(loadingTask.destroy(), Date.now() + PDF_TEARDOWN_MILLIS).catch(() => void 0);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
var PDF_TEARDOWN_MILLIS = 5e3;
|
|
225
|
+
async function raceDeadline(work, deadline) {
|
|
226
|
+
let timer;
|
|
227
|
+
try {
|
|
228
|
+
return await Promise.race([
|
|
229
|
+
work,
|
|
230
|
+
new Promise((_, reject) => {
|
|
231
|
+
timer = setTimeout(() => reject(pdfWorkTookTooLong()), Math.max(0, deadline - Date.now()));
|
|
232
|
+
timer.unref();
|
|
233
|
+
})
|
|
234
|
+
]);
|
|
235
|
+
} finally {
|
|
236
|
+
if (timer) clearTimeout(timer);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function withCrashGuard(fn, wrapCrash) {
|
|
240
|
+
return new Promise((resolve, reject) => {
|
|
241
|
+
let settled = false;
|
|
242
|
+
const cleanup = () => {
|
|
243
|
+
process.removeListener("uncaughtException", onCrash);
|
|
244
|
+
process.removeListener("unhandledRejection", onCrash);
|
|
245
|
+
};
|
|
246
|
+
const onCrash = (err) => {
|
|
247
|
+
if (settled) return;
|
|
248
|
+
settled = true;
|
|
249
|
+
cleanup();
|
|
250
|
+
reject(wrapCrash(err));
|
|
251
|
+
};
|
|
252
|
+
process.on("uncaughtException", onCrash);
|
|
253
|
+
process.on("unhandledRejection", onCrash);
|
|
254
|
+
fn().then(
|
|
255
|
+
(result) => {
|
|
256
|
+
if (settled) return;
|
|
257
|
+
settled = true;
|
|
258
|
+
cleanup();
|
|
259
|
+
resolve(result);
|
|
260
|
+
},
|
|
261
|
+
(err) => {
|
|
262
|
+
if (settled) return;
|
|
263
|
+
settled = true;
|
|
264
|
+
cleanup();
|
|
265
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
var MAX_OUTLINE_DEPTH = 500;
|
|
271
|
+
async function extractPdfText(data, pagesSpec, layout = false) {
|
|
272
|
+
const pdfjs = await loadPdfjs();
|
|
273
|
+
if (!pdfjs) throw new Error("pdfjs-dist is not installed; run `npm install pdfjs-dist` to enable pdf-extract");
|
|
274
|
+
return withPdfDocument(pdfjs, data, async (doc, deadline) => {
|
|
275
|
+
const range = parsePageRange(pagesSpec, doc.numPages);
|
|
276
|
+
const start = range ? range.start : 1;
|
|
277
|
+
const end = range ? range.end : doc.numPages;
|
|
278
|
+
const pages = [];
|
|
279
|
+
let spent = 0;
|
|
280
|
+
for (let i = start; i <= end; i++) {
|
|
281
|
+
const page = await getPageWithinDeadline(doc, i, deadline);
|
|
282
|
+
const textItems = await readPageTextItems(page, MAX_PDF_TEXT_BYTES - spent, deadline);
|
|
283
|
+
const pageText = layout ? reconstructLayout(textItems, deadline) : textItems.map((item) => item.str).join(" ");
|
|
284
|
+
spent += pageText.length;
|
|
285
|
+
pages.push(pageText.trim());
|
|
286
|
+
}
|
|
287
|
+
const text = pages.join("\n\n");
|
|
288
|
+
assertPdfTextWithinBounds(text);
|
|
289
|
+
return { text, pageCount: doc.numPages, pagesExtracted: end - start + 1 };
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
async function locatePdfPages(data, pattern, opts) {
|
|
293
|
+
const guarded = compileGuardedRegex(pattern, opts.ignoreCase === true ? "i" : "");
|
|
294
|
+
if (!guarded.ok) throw new Error(`invalid regex pattern: ${pattern} (${guarded.reason})`);
|
|
295
|
+
const re = guarded.re;
|
|
296
|
+
const pdfjs = await loadPdfjs();
|
|
297
|
+
if (!pdfjs) throw new Error("pdfjs-dist is not installed; run `npm install pdfjs-dist` to enable pdf-extract");
|
|
298
|
+
const maxMatches = Math.min(opts.maxMatches ?? 50, MAX_LOCATE_MATCHES);
|
|
299
|
+
const context = Math.min(opts.context ?? 80, MAX_LOCATE_CONTEXT_CHARS);
|
|
300
|
+
return withPdfDocument(pdfjs, data, async (doc, deadline) => {
|
|
301
|
+
const range = parsePageRange(opts.pages, doc.numPages);
|
|
302
|
+
const start = range ? range.start : 1;
|
|
303
|
+
const end = range ? range.end : doc.numPages;
|
|
304
|
+
const matches = [];
|
|
305
|
+
let i = start;
|
|
306
|
+
for (; i <= end && matches.length < maxMatches; i++) {
|
|
307
|
+
const page = await getPageWithinDeadline(doc, i, deadline);
|
|
308
|
+
const textItems = await readPageTextItems(page, MAX_PDF_TEXT_BYTES, deadline);
|
|
309
|
+
const pageText = textItems.map((item) => item.str).join(" ");
|
|
310
|
+
const m = re.exec(pageText);
|
|
311
|
+
if (m === null) continue;
|
|
312
|
+
matches.push({ page: i, snippet: locateSnippet(pageText, m.index, m[0].length, context) });
|
|
313
|
+
}
|
|
314
|
+
const truncated = matches.length >= maxMatches && i <= end;
|
|
315
|
+
return { matches, truncated };
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function locateSnippet(text, index, matchLen, context) {
|
|
319
|
+
const kept = Math.min(matchLen, context);
|
|
320
|
+
const pad = Math.max(0, context - kept);
|
|
321
|
+
const from = Math.max(0, index - Math.floor(pad / 2));
|
|
322
|
+
const to = Math.min(text.length, index + kept + Math.ceil(pad / 2));
|
|
323
|
+
const snippet = text.slice(from, to).replace(/\s+/g, " ").trim();
|
|
324
|
+
return matchLen > kept ? `${snippet}...` : snippet;
|
|
325
|
+
}
|
|
326
|
+
async function resolveDestPage(doc, dest, deadline) {
|
|
327
|
+
let explicitDest = dest;
|
|
328
|
+
if (typeof explicitDest === "string") {
|
|
329
|
+
explicitDest = await raceDeadline(doc.getDestination(explicitDest), deadline);
|
|
330
|
+
}
|
|
331
|
+
if (!Array.isArray(explicitDest) || explicitDest.length === 0) return null;
|
|
332
|
+
try {
|
|
333
|
+
const pageIndex = await raceDeadline(doc.getPageIndex(explicitDest[0]), deadline);
|
|
334
|
+
return pageIndex + 1;
|
|
335
|
+
} catch (err) {
|
|
336
|
+
if (err instanceof PdfTookTooLongError) throw err;
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function extractPdfOutline(data) {
|
|
341
|
+
const pdfjs = await loadPdfjs();
|
|
342
|
+
if (!pdfjs) throw new Error("pdfjs-dist is not installed; run `npm install pdfjs-dist` to enable pdf-outline");
|
|
343
|
+
return withCrashGuard(
|
|
344
|
+
() => withPdfDocument(pdfjs, data, async (doc, deadline) => {
|
|
345
|
+
const outline = await raceDeadline(doc.getOutline(), deadline);
|
|
346
|
+
if (!outline) return [];
|
|
347
|
+
const entries = [];
|
|
348
|
+
async function walk(items, level) {
|
|
349
|
+
if (level >= MAX_OUTLINE_DEPTH) return;
|
|
350
|
+
for (const item of items) {
|
|
351
|
+
if (entries.length >= MAX_OUTLINE_ENTRIES) return;
|
|
352
|
+
if (Date.now() > deadline) throw pdfWorkTookTooLong();
|
|
353
|
+
const page = await resolveDestPage(doc, item.dest, deadline);
|
|
354
|
+
const title = item.title.trim();
|
|
355
|
+
entries.push({ level, title: title.length > MAX_OUTLINE_TITLE_CHARS ? `${title.slice(0, MAX_OUTLINE_TITLE_CHARS)}...` : title, page });
|
|
356
|
+
if (item.items.length > 0) await walk(item.items, level + 1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
await walk(outline, 0);
|
|
360
|
+
return entries;
|
|
361
|
+
}),
|
|
362
|
+
(err) => new Error(`PDF outline is too deeply nested to read safely (pdfjs-dist crashed marshaling it): ${err instanceof Error ? err.message : String(err)}`)
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
async function pageHasText(doc, pageNum, deadline) {
|
|
366
|
+
const page = await getPageWithinDeadline(doc, pageNum, deadline);
|
|
367
|
+
let found = false;
|
|
368
|
+
for await (const chunk of pageTextItems(page, deadline)) {
|
|
369
|
+
if (Date.now() > deadline) throw pdfWorkTookTooLong();
|
|
370
|
+
found ||= chunk.some((item) => item.str.trim().length > 0);
|
|
371
|
+
}
|
|
372
|
+
return found;
|
|
373
|
+
}
|
|
374
|
+
async function extractPdfMeta(data) {
|
|
375
|
+
const pdfjs = await loadPdfjs();
|
|
376
|
+
if (!pdfjs) throw new Error("pdfjs-dist is not installed; run `npm install pdfjs-dist` to enable pdf-meta");
|
|
377
|
+
return withPdfDocument(pdfjs, data, async (doc, deadline) => {
|
|
378
|
+
const { info } = await raceDeadline(doc.getMetadata(), deadline);
|
|
379
|
+
const infoDict = info;
|
|
380
|
+
const sampleNums = Array.from(new Set([1, Math.ceil(doc.numPages / 2), doc.numPages].filter((n) => n >= 1 && n <= doc.numPages)));
|
|
381
|
+
let hasTextLayer = false;
|
|
382
|
+
for (const n of sampleNums) {
|
|
383
|
+
if (await pageHasText(doc, n, deadline)) {
|
|
384
|
+
hasTextLayer = true;
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
pageCount: doc.numPages,
|
|
390
|
+
title: typeof infoDict["Title"] === "string" && infoDict["Title"].trim().length > 0 ? infoDict["Title"] : null,
|
|
391
|
+
author: typeof infoDict["Author"] === "string" && infoDict["Author"].trim().length > 0 ? infoDict["Author"] : null,
|
|
392
|
+
hasTextLayer
|
|
393
|
+
};
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export {
|
|
398
|
+
MAX_PDF_TEXT_BYTES,
|
|
399
|
+
MAX_PDF_INPUT_BYTES,
|
|
400
|
+
MAX_PDF_TEXT_ITEMS,
|
|
401
|
+
MAX_OUTLINE_ENTRIES,
|
|
402
|
+
MAX_OUTLINE_TITLE_CHARS,
|
|
403
|
+
MAX_LOCATE_MATCHES,
|
|
404
|
+
MAX_LOCATE_CONTEXT_CHARS,
|
|
405
|
+
MAX_PDF_WORK_MILLIS,
|
|
406
|
+
PdfRefusedError,
|
|
407
|
+
PdfTooLargeError,
|
|
408
|
+
PdfTookTooLongError,
|
|
409
|
+
pdfWorkDeadline,
|
|
410
|
+
assertPdfIsARegularFileWithinBounds,
|
|
411
|
+
assertPdfTextWithinBounds,
|
|
412
|
+
readPdfFileWithinBounds,
|
|
413
|
+
readAllWithinReportedSize,
|
|
414
|
+
readPageTextItems,
|
|
415
|
+
parsePageRange,
|
|
416
|
+
reconstructLayout,
|
|
417
|
+
withPdfDocument,
|
|
418
|
+
PDF_TEARDOWN_MILLIS,
|
|
419
|
+
extractPdfText,
|
|
420
|
+
locatePdfPages,
|
|
421
|
+
resolveDestPage,
|
|
422
|
+
extractPdfOutline,
|
|
423
|
+
extractPdfMeta
|
|
424
|
+
};
|