mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-07 00:28:39 +01:00
feat(multi-pdf): enhance PDF multi-tool functionality to allow drag n drop of PDF and images
fix(ocr): pin font CDN to rawcdn.githack.com with commit SHAs
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { preprocessImageFile } from './image-input-utils.js';
|
||||
|
||||
export interface EmbeddableImage {
|
||||
bytes: Uint8Array;
|
||||
format: 'jpg' | 'png';
|
||||
}
|
||||
|
||||
export async function normalizeImageToEmbeddable(
|
||||
file: File
|
||||
): Promise<EmbeddableImage> {
|
||||
const processed = await preprocessImageFile(file);
|
||||
const name = processed.name.toLowerCase();
|
||||
const isJpg =
|
||||
processed.type === 'image/jpeg' ||
|
||||
name.endsWith('.jpg') ||
|
||||
name.endsWith('.jpeg');
|
||||
const isPng = processed.type === 'image/png' || name.endsWith('.png');
|
||||
|
||||
if (isJpg) {
|
||||
return {
|
||||
bytes: new Uint8Array(await processed.arrayBuffer()),
|
||||
format: 'jpg',
|
||||
};
|
||||
}
|
||||
if (isPng) {
|
||||
return {
|
||||
bytes: new Uint8Array(await processed.arrayBuffer()),
|
||||
format: 'png',
|
||||
};
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(processed);
|
||||
try {
|
||||
const img = await new Promise<HTMLImageElement>((resolve, reject) => {
|
||||
const i = new Image();
|
||||
i.onload = () => resolve(i);
|
||||
i.onerror = () =>
|
||||
reject(new Error(`Failed to decode image: ${processed.name}`));
|
||||
i.src = url;
|
||||
});
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.naturalWidth;
|
||||
canvas.height = img.naturalHeight;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) throw new Error('Canvas context unavailable');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const blob = await new Promise<Blob>((resolve, reject) => {
|
||||
canvas.toBlob(
|
||||
(b) => (b ? resolve(b) : reject(new Error('Canvas toBlob failed'))),
|
||||
'image/png'
|
||||
);
|
||||
});
|
||||
return { bytes: new Uint8Array(await blob.arrayBuffer()), format: 'png' };
|
||||
} finally {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
export async function convertImagesToPdfFile(images: File[]): Promise<File> {
|
||||
const pdfDoc = await PDFLibDocument.create();
|
||||
for (const img of images) {
|
||||
const { bytes, format } = await normalizeImageToEmbeddable(img);
|
||||
const embedded =
|
||||
format === 'jpg'
|
||||
? await pdfDoc.embedJpg(bytes)
|
||||
: await pdfDoc.embedPng(bytes);
|
||||
const page = pdfDoc.addPage([embedded.width, embedded.height]);
|
||||
page.drawImage(embedded, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: embedded.width,
|
||||
height: embedded.height,
|
||||
});
|
||||
}
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
const filename =
|
||||
images.length === 1
|
||||
? images[0].name.replace(/\.[^.]+$/, '.pdf')
|
||||
: `images-${Date.now()}.pdf`;
|
||||
return new File([new Uint8Array(pdfBytes)], filename, {
|
||||
type: 'application/pdf',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { isValidImageFile } from './image-input-utils.js';
|
||||
|
||||
export interface PartitionedFiles {
|
||||
pdfFiles: File[];
|
||||
imageFiles: File[];
|
||||
skipped: string[];
|
||||
}
|
||||
|
||||
export function partitionIncomingFiles(rawFiles: File[]): PartitionedFiles {
|
||||
const pdfFiles: File[] = [];
|
||||
const imageFiles: File[] = [];
|
||||
const skipped: string[] = [];
|
||||
|
||||
for (const f of rawFiles) {
|
||||
if (f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf')) {
|
||||
pdfFiles.push(f);
|
||||
} else if (isValidImageFile(f)) {
|
||||
imageFiles.push(f);
|
||||
} else {
|
||||
skipped.push(f.name);
|
||||
}
|
||||
}
|
||||
|
||||
return { pdfFiles, imageFiles, skipped };
|
||||
}
|
||||
Reference in New Issue
Block a user