2026-04-16 00:51:44 -04:00
|
|
|
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
|
2026-03-10 08:17:45 -04:00
|
|
|
import sharp from "sharp";
|
|
|
|
|
|
2026-04-16 00:51:44 -04:00
|
|
|
const TARGET_DPI = 200;
|
|
|
|
|
const PDF_DEFAULT_DPI = 72;
|
|
|
|
|
const SCALE = TARGET_DPI / PDF_DEFAULT_DPI;
|
|
|
|
|
|
2026-03-10 08:17:45 -04:00
|
|
|
export interface PageImage {
|
|
|
|
|
page: number;
|
|
|
|
|
buffer: Buffer;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 00:51:44 -04:00
|
|
|
interface CanvasAndContext {
|
|
|
|
|
canvas: { toBuffer(mime: string): Buffer };
|
|
|
|
|
context: unknown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface CanvasFactory {
|
|
|
|
|
create(width: number, height: number): CanvasAndContext;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 08:17:45 -04:00
|
|
|
export async function pdfToImages(pdfBuffer: Buffer): Promise<PageImage[]> {
|
2026-04-16 00:51:44 -04:00
|
|
|
const data = new Uint8Array(pdfBuffer);
|
|
|
|
|
const loadingTask = getDocument({ data, useSystemFonts: true });
|
|
|
|
|
const pdfDocument = await loadingTask.promise;
|
|
|
|
|
const pageCount = pdfDocument.numPages;
|
2026-03-10 08:17:45 -04:00
|
|
|
const images: PageImage[] = [];
|
|
|
|
|
|
2026-04-16 00:51:44 -04:00
|
|
|
const canvasFactory = pdfDocument.canvasFactory as unknown as CanvasFactory;
|
|
|
|
|
|
|
|
|
|
console.log(`[pdf] Rendering ${pageCount} page(s) at ${TARGET_DPI} DPI (scale ${SCALE.toFixed(2)})`);
|
|
|
|
|
|
2026-03-10 08:17:45 -04:00
|
|
|
for (let i = 1; i <= pageCount; i++) {
|
2026-04-16 00:51:44 -04:00
|
|
|
const page = await pdfDocument.getPage(i);
|
|
|
|
|
const viewport = page.getViewport({ scale: SCALE });
|
|
|
|
|
|
|
|
|
|
const { canvas, context } = canvasFactory.create(
|
|
|
|
|
Math.floor(viewport.width),
|
|
|
|
|
Math.floor(viewport.height)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
await page.render({ canvas: canvas as any, viewport } as any).promise;
|
|
|
|
|
|
|
|
|
|
const pngBuffer = canvas.toBuffer("image/png");
|
|
|
|
|
page.cleanup();
|
|
|
|
|
|
|
|
|
|
let buf = await sharp(pngBuffer).jpeg({ quality: 90 }).toBuffer();
|
|
|
|
|
let meta = await sharp(buf).metadata();
|
|
|
|
|
const w = meta.width || 0;
|
|
|
|
|
const h = meta.height || 0;
|
|
|
|
|
|
|
|
|
|
// Duplex scanners flip the sheet between sides, so odd pages
|
|
|
|
|
// (back/response card) are 180-deg rotated relative to even pages
|
|
|
|
|
// (front/survey). Landscape pages need rotation to portrait;
|
|
|
|
|
// the direction depends on which side of the sheet was scanned.
|
|
|
|
|
const isBackSide = i % 2 === 1;
|
|
|
|
|
|
|
|
|
|
if (w > h) {
|
|
|
|
|
const angle = isBackSide ? -90 : 90;
|
|
|
|
|
buf = await sharp(buf).rotate(angle).jpeg({ quality: 90 }).toBuffer();
|
|
|
|
|
meta = await sharp(buf).metadata();
|
|
|
|
|
} else if (isBackSide) {
|
|
|
|
|
buf = await sharp(buf).rotate(180).jpeg({ quality: 90 }).toBuffer();
|
|
|
|
|
meta = await sharp(buf).metadata();
|
2026-03-10 08:17:45 -04:00
|
|
|
}
|
2026-04-16 00:51:44 -04:00
|
|
|
|
|
|
|
|
images.push({
|
|
|
|
|
page: i,
|
|
|
|
|
buffer: buf,
|
|
|
|
|
width: meta.width || Math.floor(viewport.width),
|
|
|
|
|
height: meta.height || Math.floor(viewport.height),
|
|
|
|
|
});
|
2026-03-10 08:17:45 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-16 00:51:44 -04:00
|
|
|
await pdfDocument.destroy();
|
2026-03-10 08:17:45 -04:00
|
|
|
return images;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function imageToBase64(buffer: Buffer): Promise<string> {
|
|
|
|
|
const processed = await sharp(buffer)
|
|
|
|
|
.resize(1200, undefined, { withoutEnlargement: true })
|
|
|
|
|
.jpeg({ quality: 85 })
|
|
|
|
|
.toBuffer();
|
|
|
|
|
return processed.toString("base64");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function processUploadedImage(buffer: Buffer): Promise<Buffer> {
|
|
|
|
|
return sharp(buffer)
|
|
|
|
|
.resize(1600, undefined, { withoutEnlargement: true })
|
|
|
|
|
.normalize()
|
|
|
|
|
.sharpen()
|
|
|
|
|
.jpeg({ quality: 90 })
|
|
|
|
|
.toBuffer();
|
|
|
|
|
}
|