import { PDFDict, PDFDocument, PDFName } from "pdf-lib"; export interface PdfInspection { ok: boolean; pages: number; text: string; textChars: number; fonts: { total: number; embedded: number; names: string[]; unembedded: string[] }; errors: string[]; title?: string; } export async function inspectPdf(buf: Buffer): Promise { const errors: string[] = []; let pages = 0; const fonts = { total: 0, embedded: 0, names: [] as string[], unembedded: [] as string[] }; let title: string | undefined; try { const doc = await PDFDocument.load(buf, { ignoreEncryption: true, updateMetadata: false }); pages = doc.getPageCount(); title = doc.getTitle(); const seen = new Set(); for (const page of doc.getPages()) { const res = page.node.Resources(); const fontDict = res?.lookup(PDFName.of("Font")); if (!(fontDict instanceof PDFDict)) continue; for (const [, ref] of fontDict.entries()) { const font = doc.context.lookup(ref); if (!(font instanceof PDFDict)) continue; const base = font.lookup(PDFName.of("BaseFont"))?.toString() ?? "?"; if (seen.has(base)) continue; seen.add(base); let desc = font.lookup(PDFName.of("FontDescriptor")); if (!(desc instanceof PDFDict)) { const descendants = font.lookup(PDFName.of("DescendantFonts")) as unknown as | { get?: (i: number) => unknown } | undefined; const first = descendants && typeof descendants.get === "function" ? descendants.get(0) : undefined; const df = first ? doc.context.lookup(first as never) : undefined; if (df instanceof PDFDict) desc = df.lookup(PDFName.of("FontDescriptor")); } const d = desc instanceof PDFDict ? desc : undefined; const embedded = !!( d && (d.has(PDFName.of("FontFile")) || d.has(PDFName.of("FontFile2")) || d.has(PDFName.of("FontFile3"))) ); const subtype = font.lookup(PDFName.of("Subtype"))?.toString(); const isType3 = subtype === "/Type3"; fonts.total++; fonts.names.push(base); if (embedded || isType3) fonts.embedded++; else fonts.unembedded.push(base); } } } catch (e) { errors.push(`pdf-lib parse failed: ${(e as Error).message}`); } let text = ""; try { const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs"); const task = pdfjs.getDocument({ data: new Uint8Array(buf), useSystemFonts: true, disableFontFace: true, verbosity: 0, }); const pdf = await task.promise; pages = pages || pdf.numPages; const parts: string[] = []; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const content = await page.getTextContent(); parts.push(content.items.map((it) => ("str" in it ? it.str : "")).join(" ")); } text = parts.join("\n"); await task.destroy(); } catch (e) { errors.push(`pdfjs text extraction failed: ${(e as Error).message}`); } const textChars = text.replace(/\s+/g, "").length; if (pages === 0) errors.push("no pages"); if (textChars < 500) errors.push(`too little selectable text (${textChars} chars)`); return { ok: errors.length === 0, pages, text, textChars, fonts, errors, title }; }