SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
2.5 KB · 58 lines typescript
Raw Blame History
1/**2 * PDF text extraction (SPEC §1): auction houses that publish prices-realised lists as PDFs.3 * pdfjs-dist is loaded lazily so connectors that never touch PDFs pay nothing.4 */5export interface PdfPage {6  page: number;7  /** lines reconstructed from text items grouped by y coordinate */8  lines: string[];9}1011export async function extractPdfPages(data: Uint8Array, opts: { maxPages?: number } = {}): Promise<PdfPage[]> {12  const pdfjs = (await import('pdfjs-dist/legacy/build/pdf.mjs')) as typeof import('pdfjs-dist/legacy/build/pdf.mjs');13  const doc = await pdfjs.getDocument({ data, useSystemFonts: true, disableFontFace: true, isEvalSupported: false }).promise;14  const pages: PdfPage[] = [];15  const n = Math.min(doc.numPages, opts.maxPages ?? doc.numPages);16  for (let i = 1; i <= n; i++) {17    const page = await doc.getPage(i);18    const content = await page.getTextContent();19    const rows = new Map<number, Array<{ x: number; s: string }>>();20    for (const it of content.items as Array<{ str?: string; transform?: number[] }>) {21      if (!it.str || !it.transform) continue;22      const y = Math.round(it.transform[5]! / 2) * 2; // 2pt buckets23      const x = it.transform[4]!;24      const row = rows.get(y) ?? [];25      row.push({ x, s: it.str });26      rows.set(y, row);27    }28    const lines = [...rows.entries()]29      .sort((a, b) => b[0] - a[0])30      .map(([, cells]) => cells.sort((a, b) => a.x - b.x).map((c) => c.s).join(' ').replace(/\s+/g, ' ').trim())31      .filter(Boolean);32    pages.push({ page: i, lines });33  }34  await doc.destroy();35  return pages;36}3738export async function extractPdfText(data: Uint8Array, opts: { maxPages?: number } = {}): Promise<string> {39  const pages = await extractPdfPages(data, opts);40  return pages.map((p) => p.lines.join('\n')).join('\n\f\n');41}4243/**44 * Parse "lot number … price" rows from prices-realised text. Returns null-safe rows; the connector45 * still decides currency and date. Handles "123  1,250", "Lot 123: $1,250", "123 ........ 1.250,00".46 */47export function parsePricesRealised(lines: string[]): Array<{ lot: string; priceText: string; line: string }> {48  const out: Array<{ lot: string; priceText: string; line: string }> = [];49  const re = /^(?:lot\s*)?(\d{1,6}[A-Za-z]?)\b[\s.:\-–]*.*?([$€£¥]?\s?\d{1,3}(?:[.,\s]\d{3})*(?:[.,]\d{2})?)\s*$/i;50  for (const line of lines) {51    const m = line.match(re);52    if (!m) continue;53    if (m[1] === m[2]) continue;54    out.push({ lot: m[1]!, priceText: m[2]!.trim(), line });55  }56  return out;57}58