mindee
Version:
Mindee Client Library for Node.js
133 lines (132 loc) • 5.66 kB
JavaScript
import { loadOptionalDependency } from "../dependency/index.js";
import { MindeeImageError } from "../errors/index.js";
import { getMinMaxX, getMinMaxY } from "../geometry/index.js";
import { adjustForRotation } from "../geometry/polygonUtils.js";
import { ExtractedImage } from "../image/extractedImage.js";
import { ExtractedImages } from "../image/extractedImages.js";
import { logger } from "../logger.js";
import { createPdfFromInputSource } from "../pdf/pdfOperation.js";
import { rasterizePage } from "../pdf/pdfUtils.js";
let pdfLib = null;
async function getPdfLib() {
if (!pdfLib) {
const pdfLibImport = await loadOptionalDependency("@cantoo/pdf-lib", "Image Extraction");
pdfLib = pdfLibImport.default || pdfLibImport;
}
return pdfLib;
}
/**
* Extracts elements from a PDF document based on a list of bounding boxes.
* @param inputSource The input source to extract from.
* @param polygonsPerPage List of polygons to extract from per page.
* @param quality JPEG quality of extracted images.
*/
export async function extractImagesFromPolygon(inputSource, polygonsPerPage, quality) {
const allExtractedImages = new ExtractedImages();
const pdfDoc = await createPdfFromInputSource(inputSource);
for (const [pageId, polygons] of polygonsPerPage) {
logger.debug(`Extracting images from page ${pageId}`);
const pdfPage = pdfDoc.getPage(pageId);
const extractions = await extractFromPage(pdfPage, polygons, true, quality);
const extractedImages = extractions.map((buffer, elementId) => new ExtractedImage(buffer, inputSource.filename + `_page-${pageId}-item-${elementId}.jpg`, pageId, elementId));
allExtractedImages.push(...extractedImages);
}
return allExtractedImages;
}
/**
* Extracts elements from a page based off of a list of bounding boxes.
*
* @param pdfPage PDF Page to extract from.
* @param polygons List of coordinates to pull the elements from.
* @param asImage Whether to return the extracted elements as images.
* @param quality JPEG quality of extracted images, given as number between 0 and 1.
*/
export async function extractFromPage(pdfPage, polygons, asImage = false, quality) {
const pdfLib = await getPdfLib();
const { width, height } = pdfPage.getSize();
const extractedElements = [];
if (quality && (quality < 0)) {
throw new MindeeImageError("Quality must be a number between 0 and 1");
}
if (quality && quality > 1) {
logger.warn("Quality is greater than 1, this operation will apply a manual upscale on the output." +
" Use only if you know what you are doing.");
}
const qualityScale = quality ?? 1;
const orientation = pdfPage.getRotation().angle;
const sourceDoc = pdfPage.doc;
const pageIndex = sourceDoc.getPages().indexOf(pdfPage);
for (const origPolygon of polygons) {
logger.debug(`Extracting image with polygon: ${origPolygon.toString()}`);
const tempPdf = await pdfLib.PDFDocument.create();
const [copiedPage] = await tempPdf.copyPages(sourceDoc, [pageIndex]);
const polygon = adjustForRotation(origPolygon, orientation);
const newWidth = width * (getMinMaxX(polygon).max - getMinMaxX(polygon).min);
const newHeight = height * (getMinMaxY(polygon).max - getMinMaxY(polygon).min);
const cropped = await tempPdf.embedPage(copiedPage, {
left: getMinMaxX(polygon).min * width,
right: getMinMaxX(polygon).max * width,
top: height - (getMinMaxY(polygon).min * height),
bottom: height - (getMinMaxY(polygon).max * height),
});
let finalWidth;
let finalHeight;
if (orientation === 90 || orientation === 270) {
finalWidth = newHeight * qualityScale;
finalHeight = newWidth * qualityScale;
}
else {
finalWidth = newWidth * qualityScale;
finalHeight = newHeight * qualityScale;
}
const samplePage = tempPdf.addPage([finalWidth, finalHeight]);
samplePage.drawRectangle({
x: 0,
y: 0,
width: finalWidth,
height: finalHeight,
color: pdfLib.rgb(1, 1, 1),
});
if (orientation === 0) {
samplePage.drawPage(cropped, {
width: newWidth * qualityScale,
height: newHeight * qualityScale,
});
}
else if (orientation === 90) {
samplePage.drawPage(cropped, {
x: 0,
y: finalHeight,
width: newWidth * qualityScale,
height: newHeight * qualityScale,
rotate: pdfLib.degrees(270),
});
}
else if (orientation === 180) {
samplePage.drawPage(cropped, {
x: finalWidth,
y: finalHeight,
width: newWidth * qualityScale,
height: newHeight * qualityScale,
rotate: pdfLib.degrees(180),
});
}
else if (orientation === 270) {
samplePage.drawPage(cropped, {
x: finalWidth,
y: 0,
width: newWidth * qualityScale,
height: newHeight * qualityScale,
rotate: pdfLib.degrees(90),
});
}
const pdfBuffer = Buffer.from(await tempPdf.save());
if (asImage) {
extractedElements.push(await rasterizePage(pdfBuffer, 0, 100));
}
else {
extractedElements.push(pdfBuffer);
}
}
return extractedElements;
}