pdf-to-png-converter
Version:
Node.js utility to convert PDF file/buffer pages to PNG files/buffers with no native dependencies.
127 lines (126 loc) • 6.12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.pdfToPng = pdfToPng;
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const const_1 = require("./const");
const node_canvas_factory_1 = require("./node.canvas.factory");
const propsToPdfDocInitParams_1 = require("./propsToPdfDocInitParams");
/**
* Converts a PDF file to PNG images, one per page.
*
* @param pdfFile - The PDF file to convert. Can be a file path (string) or an ArrayBufferLike.
* @param props - Optional configuration options for the conversion process.
* @returns A promise that resolves to an array of `PngPageOutput` objects, each representing a PNG image of a PDF page.
*
* @remarks
* - If `pdfFile` is a string, it is treated as a file path and read from disk.
* - The `props.pagesToProcess` option allows specifying which pages to convert (1-based indices).
* - The `props.viewportScale` option controls the rendering scale of the PDF pages.
* - The `props.outputFileMaskFunc` option allows customizing the output file name for each page.
* - If `props.outputFolder` is provided, the PNG files are saved to the specified folder.
* - The function processes pages in parallel for efficiency.
* - All resources are cleaned up after processing.
*
* @throws Will throw if the PDF file cannot be read or processed.
*/
async function pdfToPng(pdfFile, props) {
// Read the PDF file and initialize the PDF document
const isString = typeof pdfFile == 'string';
const pdfFileBuffer = isString
? await (async () => {
const buffer = await node_fs_1.promises.readFile(pdfFile);
// Ensure we always return an ArrayBuffer
if (buffer instanceof ArrayBuffer) {
return buffer;
}
else if (Buffer.isBuffer(buffer)) {
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
}
else {
throw new Error('Unsupported buffer type');
}
})()
: pdfFile;
const pdfDocument = await getPdfDocument(pdfFileBuffer, props);
// Get the pages to process based on the provided options, invalid pages will be filtered out
const pagesToProcess = props?.pagesToProcess ?? Array.from({ length: pdfDocument.numPages }, (_, index) => index + 1);
const validPagesToProcess = pagesToProcess.filter((pageNumber) => pageNumber <= pdfDocument.numPages && pageNumber >= 1);
// Process each page in parallel
const pngPagesOutput = [];
try {
const pageViewportScale = props?.viewportScale !== undefined ? props.viewportScale : const_1.PDF_TO_PNG_OPTIONS_DEFAULTS.viewportScale;
const defaultMask = isString ? (0, node_path_1.parse)(pdfFile).name : const_1.PDF_TO_PNG_OPTIONS_DEFAULTS.outputFileMask;
const pngPageOutputs = await Promise.all(validPagesToProcess.map((pageNumber) => {
const pageName = props?.outputFileMaskFunc?.(pageNumber) ?? `${defaultMask}_page_${pageNumber}.png`;
return processPdfPage(pdfDocument, pageName, pageNumber, pageViewportScale);
}));
pngPagesOutput.push(...pngPageOutputs);
}
finally {
await pdfDocument.cleanup();
}
// Save the PNG files to the output folder
if (props?.outputFolder !== undefined) {
const outputFolder = (0, node_path_1.join)(process.cwd(), props.outputFolder);
await node_fs_1.promises.mkdir(outputFolder, { recursive: true });
await Promise.all(pngPagesOutput.map(async (pngPageOutput) => {
pngPageOutput.path = (0, node_path_1.join)(outputFolder, pngPageOutput.name);
await node_fs_1.promises.writeFile(pngPageOutput.path, pngPageOutput.content);
}));
}
return pngPagesOutput;
}
/**
* Loads a PDF document from a given ArrayBuffer and returns a PDF.js document proxy.
*
* @param pdfFileBuffer - The buffer containing the PDF file data.
* @param props - Optional configuration options for PDF loading.
* @returns A promise that resolves to a PDFDocumentProxy representing the loaded PDF.
*
* @remarks
* This function dynamically imports the PDF.js library and initializes the document
* using the provided buffer and options. The options are converted to PDF.js-compatible
* initialization parameters via `propsToPdfDocInitParams`.
*
* @throws Will throw if the PDF cannot be loaded or parsed.
*/
async function getPdfDocument(pdfFileBuffer, props) {
const { getDocument } = await import('pdfjs-dist/legacy/build/pdf.mjs');
const documentInitParameters = (0, propsToPdfDocInitParams_1.propsToPdfDocInitParams)(props);
return await getDocument({
...documentInitParameters,
data: new Uint8Array(pdfFileBuffer),
}).promise;
}
/**
* Renders a specific page of a PDF document to a PNG image buffer.
*
* @param pdf - The PDF.js document proxy representing the loaded PDF.
* @param pageName - The name to associate with the rendered page.
* @param pageNumber - The 1-based index of the page to render.
* @param pageViewportScale - The scale factor to apply to the page viewport for rendering.
* @returns A promise that resolves to a `PngPageOutput` object containing the rendered PNG buffer and page metadata.
*/
async function processPdfPage(pdf, pageName, pageNumber, pageViewportScale) {
const page = await pdf.getPage(pageNumber);
const viewport = page.getViewport({ scale: pageViewportScale });
const canvasFactory = new node_canvas_factory_1.NodeCanvasFactory();
const { canvas, context } = canvasFactory.create(viewport.width, viewport.height);
try {
await page.render({ canvasContext: context, viewport }).promise;
const pngPageOutput = {
pageNumber,
name: pageName,
content: canvas.toBuffer('image/png'),
path: '',
width: viewport.width,
height: viewport.height,
};
return pngPageOutput;
}
finally {
page.cleanup();
canvasFactory.destroy({ canvas, context });
}
}