From 393114e9be06c5954cc6d5a5255e1eac8c51d218 Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Tue, 7 Apr 2026 22:12:21 -0500 Subject: [PATCH] Fix PDF page count detection and add processing diagnostics The old getPdfPageCount used a fragile regex on raw PDF binary that could undercount pages, causing survey sides to be skipped entirely. Replaced with pdf-lib's PDFDocument.load() for reliable counting, with the old regex as a fallback. Added logging for page counts and pairing to diagnose missing backs. Made-with: Cursor --- package-lock.json | 43 +++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/lib/ocr.ts | 6 +++++- src/lib/pdf.ts | 17 ++++++++++++++--- 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 549251b..5285992 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "mailparser": "^3.9.6", "next": "16.1.6", "next-themes": "^0.4.6", + "pdf-lib": "^1.17.1", "pdf2pic": "^3.2.0", "pg": "^8.20.0", "prisma": "^7.4.2", @@ -2994,6 +2995,24 @@ "node": ">=8.0.0" } }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.10" + } + }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", @@ -10899,6 +10918,12 @@ "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", "license": "MIT" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11007,6 +11032,24 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "license": "MIT", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "node_modules/pdf-lib/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/pdf2pic": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/pdf2pic/-/pdf2pic-3.2.0.tgz", diff --git a/package.json b/package.json index 1a2c4be..e93c6ae 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "mailparser": "^3.9.6", "next": "16.1.6", "next-themes": "^0.4.6", + "pdf-lib": "^1.17.1", "pdf2pic": "^3.2.0", "pg": "^8.20.0", "prisma": "^7.4.2", diff --git a/src/lib/ocr.ts b/src/lib/ocr.ts index 6f2f70e..423dbe5 100644 --- a/src/lib/ocr.ts +++ b/src/lib/ocr.ts @@ -44,7 +44,9 @@ export async function processFile( const sourceKey = `sources/${jobId}/${fileName}`; await uploadBuffer(sourceKey, fileBuffer, isPdf ? "application/pdf" : "image/jpeg"); - // Pair pages: page 1 = response card (back), page 2 = survey (front), etc. + console.log(`[ocr] ${fileName}: ${pageImages.length} page(s) extracted, isPdf=${isPdf}`); + + // Pair pages: page 1 = response card, page 2 = survey, etc. const pairs: { response?: typeof pageImages[0]; survey?: typeof pageImages[0] }[] = []; for (let i = 0; i < pageImages.length; i += 2) { pairs.push({ @@ -59,6 +61,8 @@ export async function processFile( pairs.push({ response: pageImages[0] }); } + console.log(`[ocr] ${fileName}: ${pairs.length} card pair(s), pages per pair: ${pairs.map((p, i) => `pair ${i}: response=${p.response ? "yes" : "no"}, survey=${p.survey ? "yes" : "no"}`).join("; ")}`); + for (let pairIdx = 0; pairIdx < pairs.length; pairIdx++) { if (pairIdx > 0) await ocrDelay(); const pair = pairs[pairIdx]; diff --git a/src/lib/pdf.ts b/src/lib/pdf.ts index 0334555..d874fd0 100644 --- a/src/lib/pdf.ts +++ b/src/lib/pdf.ts @@ -1,4 +1,5 @@ import { fromBuffer } from "pdf2pic"; +import { PDFDocument } from "pdf-lib"; import sharp from "sharp"; export interface PageImage { @@ -57,9 +58,19 @@ export async function pdfToImages(pdfBuffer: Buffer): Promise { } async function getPdfPageCount(pdfBuffer: Buffer): Promise { - const text = pdfBuffer.toString("latin1"); - const matches = text.match(/\/Type\s*\/Page(?!s)/g); - return matches ? matches.length : 2; + try { + const doc = await PDFDocument.load(pdfBuffer, { ignoreEncryption: true }); + const count = doc.getPageCount(); + console.log(`[pdf] Page count (pdf-lib): ${count}`); + return count; + } catch (err) { + console.warn("[pdf] pdf-lib page count failed, falling back to regex:", err); + const text = pdfBuffer.toString("latin1"); + const matches = text.match(/\/Type\s*\/Page(?!s)/g); + const count = matches ? matches.length : 2; + console.log(`[pdf] Page count (regex fallback): ${count}`); + return count; + } } export async function imageToBase64(buffer: Buffer): Promise {