import { renderToBuffer } from "@react-pdf/renderer"; import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs"; import type { ReactElement } from "react"; export interface TextBox { str: string; x0: number; x1: number; /** Distance from the top edge of the page, in points. */ top: number; bottom: number; } export interface PageText { width: number; height: number; items: TextBox[]; } export interface RenderedPdf { pages: PageText[]; /** Raw PDF bytes as a latin1 string, for grepping object dictionaries. */ raw: string; bytes: Uint8Array; } /** Renders a react-pdf element under Node and returns the text boxes pdf.js finds on each page. */ export async function renderPdfText(element: ReactElement): Promise { // The renderToBuffer type wants a element; any element that renders one works. const buf = await renderToBuffer(element as Parameters[0]); const raw = buf.toString("latin1"); const bytes = new Uint8Array(buf); const task = getDocument({ data: new Uint8Array(buf), useSystemFonts: false, disableFontFace: true, }); const doc = await task.promise; const pages: PageText[] = []; for (let n = 1; n <= doc.numPages; n++) { const page = await doc.getPage(n); const [, , width, height] = page.view; const content = await page.getTextContent(); const items: TextBox[] = []; for (const item of content.items) { if (!("str" in item) || item.str.trim() === "") continue; const x = item.transform[4]; const f = item.transform[5]; items.push({ str: item.str, x0: x, x1: x + item.width, top: height - f - item.height, bottom: height - f, }); } pages.push({ width, height, items }); } await task.destroy(); return { pages, raw, bytes }; } /** Pairs of items on one page whose boxes overlap (horizontal tolerance 0.5pt, vertical overlap must exceed 2pt). */ export function findOverlaps(page: PageText): Array<[TextBox, TextBox]> { const out: Array<[TextBox, TextBox]> = []; const its = page.items; for (let i = 0; i < its.length; i++) { for (let j = i + 1; j < its.length; j++) { const a = its[i]; const b = its[j]; const h = Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0); const v = Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top); if (h > 0.5 && v > 2) out.push([a, b]); } } return out; } export const describeBox = (t: TextBox) => `"${t.str}" [x ${t.x0.toFixed(1)}..${t.x1.toFixed(1)}, y ${t.top.toFixed(1)}..${t.bottom.toFixed(1)}]`;