2026-03-10 08:17:45 -04:00
|
|
|
import { fromBuffer } from "pdf2pic";
|
|
|
|
|
import sharp from "sharp";
|
|
|
|
|
|
|
|
|
|
export interface PageImage {
|
|
|
|
|
page: number;
|
|
|
|
|
buffer: Buffer;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function pdfToImages(pdfBuffer: Buffer): Promise<PageImage[]> {
|
|
|
|
|
const converter = fromBuffer(pdfBuffer, {
|
|
|
|
|
density: 200,
|
|
|
|
|
format: "jpeg",
|
2026-04-07 14:10:03 -04:00
|
|
|
width: 2200,
|
2026-03-10 08:17:45 -04:00
|
|
|
height: 2200,
|
|
|
|
|
quality: 90,
|
2026-04-07 14:10:03 -04:00
|
|
|
preserveAspectRatio: true,
|
2026-03-10 08:17:45 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const pageCount = await getPdfPageCount(pdfBuffer);
|
|
|
|
|
const images: PageImage[] = [];
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i <= pageCount; i++) {
|
|
|
|
|
const result = await converter(i, { responseType: "buffer" });
|
|
|
|
|
if (result.buffer) {
|
2026-04-07 14:10:03 -04:00
|
|
|
let buf = result.buffer as Buffer;
|
|
|
|
|
let meta = await sharp(buf).metadata();
|
|
|
|
|
|
|
|
|
|
// Scanners often store landscape content with a PDF /Rotate flag that
|
|
|
|
|
// pdf2pic may not apply. Detect and rotate to portrait orientation.
|
|
|
|
|
if ((meta.width || 0) > (meta.height || 0)) {
|
|
|
|
|
buf = await sharp(buf).rotate(90).jpeg({ quality: 90 }).toBuffer();
|
|
|
|
|
meta = await sharp(buf).metadata();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 08:17:45 -04:00
|
|
|
images.push({
|
|
|
|
|
page: i,
|
2026-04-07 14:10:03 -04:00
|
|
|
buffer: buf,
|
|
|
|
|
width: meta.width || 1600,
|
|
|
|
|
height: meta.height || 2200,
|
2026-03-10 08:17:45 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return images;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getPdfPageCount(pdfBuffer: Buffer): Promise<number> {
|
|
|
|
|
const text = pdfBuffer.toString("latin1");
|
|
|
|
|
const matches = text.match(/\/Type\s*\/Page(?!s)/g);
|
|
|
|
|
return matches ? matches.length : 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|