101 lines
3.4 KiB
JavaScript
101 lines
3.4 KiB
JavaScript
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
|
|
import { parentPort, workerData } from "node:worker_threads";
|
|
|
|
const { bytes, requestedPages, maxCharacters, maxPages } = workerData;
|
|
|
|
function isPasswordError(error) {
|
|
return Boolean(error && typeof error === "object" && error.name === "PasswordException");
|
|
}
|
|
|
|
async function extract() {
|
|
let loadingTask;
|
|
try {
|
|
loadingTask = getDocument({
|
|
data: bytes,
|
|
standardFontDataUrl: new URL("../../node_modules/pdfjs-dist/standard_fonts/", import.meta.url).href,
|
|
isEvalSupported: false,
|
|
disableFontFace: true,
|
|
stopAtErrors: true,
|
|
});
|
|
const document = await loadingTask.promise;
|
|
if (document.numPages === 0) {
|
|
return { status: "no_pages", pageCount: 0, pages: [], text: "This PDF has no pages.", truncated: false };
|
|
}
|
|
|
|
const pages = requestedPages?.length
|
|
? [...new Set(requestedPages)].slice(0, maxPages)
|
|
: Array.from({ length: Math.min(document.numPages, maxPages) }, (_, index) => index + 1);
|
|
const invalidPage = pages.find((page) => page > document.numPages);
|
|
if (invalidPage) {
|
|
return {
|
|
status: "error",
|
|
pageCount: document.numPages,
|
|
pages: [],
|
|
text: `Requested page ${invalidPage}, but this PDF has ${document.numPages} pages.`,
|
|
truncated: false,
|
|
};
|
|
}
|
|
|
|
const chunks = [];
|
|
let characterCount = 0;
|
|
let foundText = false;
|
|
let truncated = false;
|
|
const append = (text) => {
|
|
const remaining = maxCharacters - characterCount;
|
|
if (remaining <= 0) {
|
|
truncated = true;
|
|
return;
|
|
}
|
|
const part = text.slice(0, remaining);
|
|
chunks.push(part);
|
|
characterCount += part.length;
|
|
if (part.length < text.length) truncated = true;
|
|
};
|
|
|
|
for (const pageNumber of pages) {
|
|
append(`${chunks.length ? "\n\n" : ""}--- Page ${pageNumber} ---\n`);
|
|
if (truncated) break;
|
|
const page = await document.getPage(pageNumber);
|
|
const reader = page.streamTextContent().getReader();
|
|
try {
|
|
while (!truncated) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
for (const item of value.items) {
|
|
if (!("str" in item) || !item.str) continue;
|
|
foundText = true;
|
|
append(item.str + (item.hasEOL ? "\n" : " "));
|
|
if (truncated) break;
|
|
}
|
|
}
|
|
} finally {
|
|
if (truncated) await reader.cancel().catch(() => undefined);
|
|
reader.releaseLock();
|
|
}
|
|
if (truncated) break;
|
|
}
|
|
|
|
const text = chunks.join("").trimEnd();
|
|
if (!foundText) {
|
|
return {
|
|
status: "no_text",
|
|
pageCount: document.numPages,
|
|
pages,
|
|
text: "No extractable text was found in the selected pages.",
|
|
truncated: false,
|
|
};
|
|
}
|
|
return { status: "ok", pageCount: document.numPages, pages, text, truncated };
|
|
} catch (error) {
|
|
if (isPasswordError(error)) {
|
|
return { status: "encrypted", pageCount: null, pages: [], text: "This PDF is encrypted and requires a password.", truncated: false };
|
|
}
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
return { status: "error", pageCount: null, pages: [], text: `PDF reading failed: ${message}`, truncated: false };
|
|
} finally {
|
|
await loadingTask?.destroy().catch(() => undefined);
|
|
}
|
|
}
|
|
|
|
parentPort?.postMessage(await extract());
|