import LandingAIADE from "landingai-ade";
import fs from "fs";
import path from "path";
import { createCanvas, loadImage } from "canvas";
import { pdf } from "pdf-to-img";
// Image types the script crops, besides PDFs (convert other images, such as TIFF or WebP, to PNG first)
const IMAGE_TYPES = new Set([".png", ".jpg", ".jpeg"]);
async function saveBlocksAsImages(
parseResponse: LandingAIADE.V2ParseResponse,
documentPath: string,
outputBaseDir: string = "blocks"
) {
// Office documents and spreadsheets have no page image to crop from
const fileType = path.extname(documentPath).toLowerCase();
if (fileType !== ".pdf" && !IMAGE_TYPES.has(fileType)) {
console.log(`No blocks to save: the script crops PDF, PNG and JPEG files, not ${path.basename(documentPath)}`);
return null;
}
// Create timestamped output directory
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, -5);
const documentName = path.basename(documentPath, path.extname(documentPath));
const outputDir = path.join(outputBaseDir, `${documentName}_${timestamp}`);
async function savePageBlocks(imageSource: Buffer | string, page: LandingAIADE.V2ParsePage) {
// Load the page image
const image = await loadImage(imageSource);
// Create page-specific directory (page numbers are 1-indexed)
const pageDir = path.join(outputDir, `page_${page.grounding!.page}`);
fs.mkdirSync(pageDir, { recursive: true });
for (const block of page.children ?? []) {
const box = block.grounding!.box;
// Convert normalized coordinates to pixel coordinates
const x1 = Math.floor(box.xmin * image.width);
const y1 = Math.floor(box.ymin * image.height);
const x2 = Math.floor(box.xmax * image.width);
const y2 = Math.floor(box.ymax * image.height);
const width = x2 - x1;
const height = y2 - y1;
if (width <= 0 || height <= 0) continue; // Nothing to crop
// Draw the cropped region on its own canvas
const canvas = createCanvas(width, height);
canvas.getContext("2d").drawImage(image, x1, y1, width, height, 0, 0, width, height);
// Save the crop, named by block ID (for example, text-0.png or table-0.png)
const outputPath = path.join(pageDir, `${block.id}.png`);
fs.writeFileSync(outputPath, canvas.toBuffer("image/png"));
console.log(`Saved block: ${outputPath}`);
}
}
// Skip pages that failed to parse
const pages = (parseResponse.structure?.children ?? []).filter(
(node) => node.type === "page" && node.status !== "failed",
);
if (pages.length === 0) {
console.log("No blocks to save: the document has no successfully parsed pages");
return null;
}
if (fileType === ".pdf") {
const pagesByNumber = new Map(pages.map((page) => [page.grounding!.page, page] as const));
// pdf-to-img renders the pages in order; grounding.page is 1-indexed
const document = await pdf(documentPath, { scale: 2.0 });
let pageNumber = 1;
for await (const pageImage of document) {
const page = pagesByNumber.get(pageNumber);
if (page) await savePageBlocks(pageImage, page);
pageNumber++;
}
} else {
// Boxes match the upright image: rotate photos that carry EXIF orientation before running the script
await savePageBlocks(documentPath, pages[0]);
}
console.log(`\nAll blocks saved to: ${outputDir}`);
return outputDir;
}
// Initialize client (uses the API key from the VISION_AGENT_API_KEY environment variable)
const client = new LandingAIADE();
async function extractBlocks() {
// Replace with your file path
const documentPath = "/path/to/file/document";
// Parse the document
console.log("Parsing document...");
const parseResponse = await client.v2.parse({
document: fs.createReadStream(documentPath),
model: "dpt-3-pro-latest",
});
console.log("Parsing complete!");
// Save each block as a separate image
await saveBlocksAsImages(parseResponse, documentPath);
}
extractBlocks();