@pdfme/converter
Version:
TypeScript base PDF generator and React base UI. Open source, developed by the community, and completely free to use under the MIT license!
111 lines (94 loc) • 3.6 kB
text/typescript
import { PDFDocument } from '@pdfme/pdf-lib';
import { mm2pt } from '@pdfme/common';
import type { ImageType } from './types.js';
interface Img2PdfOptions {
scale?: number;
imageType?: ImageType;
size?: { height: number; width: number }; // in millimeters
margin?: [number, number, number, number]; // in millimeters [top, right, bottom, left]
}
function detectImageType(buffer: ArrayBuffer): 'jpeg' | 'png' | 'unknown' {
const bytes = new Uint8Array(buffer);
if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8) {
return 'jpeg';
}
if (
bytes.length >= 8 &&
bytes[0] === 0x89 &&
bytes[1] === 0x50 &&
bytes[2] === 0x4e &&
bytes[3] === 0x47 &&
bytes[4] === 0x0d &&
bytes[5] === 0x0a &&
bytes[6] === 0x1a &&
bytes[7] === 0x0a
) {
return 'png';
}
return 'unknown';
}
export async function img2pdf(
imgs: ArrayBuffer[],
options: Img2PdfOptions = {},
): Promise<ArrayBuffer> {
try {
const { scale = 1, size, margin = [0, 0, 0, 0] } = options;
if (!Array.isArray(imgs) || imgs.length === 0) {
throw new Error('Input must be a non-empty array of image buffers');
}
const doc = await PDFDocument.create();
for (const img of imgs) {
try {
let image;
const type = detectImageType(img);
if (type === 'jpeg') {
image = await doc.embedJpg(img);
} else if (type === 'png') {
image = await doc.embedPng(img);
} else {
try {
image = await doc.embedJpg(img);
} catch {
image = await doc.embedPng(img);
}
}
const page = doc.addPage();
const { width: imgWidth, height: imgHeight } = image.scale(scale);
// Set page size based on size option or image dimensions
const pageWidth = size ? mm2pt(size.width) : imgWidth;
const pageHeight = size ? mm2pt(size.height) : imgHeight;
page.setSize(pageWidth, pageHeight);
// Convert margins from mm to points
const [topMargin, rightMargin, bottomMargin, leftMargin] = margin.map(mm2pt);
// Calculate available space for the image after applying margins
const availableWidth = pageWidth - leftMargin - rightMargin;
const availableHeight = pageHeight - topMargin - bottomMargin;
// Calculate scaling to fit image within available space while maintaining aspect ratio
const widthRatio = availableWidth / imgWidth;
const heightRatio = availableHeight / imgHeight;
const ratio = Math.min(widthRatio, heightRatio, 1); // Don't upscale images
// Calculate final image dimensions and position
const finalWidth = imgWidth * ratio;
const finalHeight = imgHeight * ratio;
const x = leftMargin + (availableWidth - finalWidth) / 2; // Center horizontally
const y = bottomMargin + (availableHeight - finalHeight) / 2; // Center vertically
page.drawImage(image, {
x,
y,
width: finalWidth,
height: finalHeight,
});
} catch (error) {
throw new Error(`Failed to process image: ${(error as Error).message}`);
}
}
const pdfUint8Array = await doc.save();
// Create a new ArrayBuffer from the Uint8Array to ensure we return only ArrayBuffer
const buffer = new ArrayBuffer(pdfUint8Array.byteLength);
const view = new Uint8Array(buffer);
view.set(pdfUint8Array);
return buffer;
} catch (error) {
throw new Error(`[@pdfme/converter] img2pdf failed: ${(error as Error).message}`);
}
}