merge files inital commit

This commit is contained in:
2026-05-23 15:02:40 +02:00
parent 8929080dc7
commit 3a0a90bd4a
16 changed files with 1232 additions and 190 deletions

View File

@@ -0,0 +1,57 @@
import { PDFDocument } from 'pdf-lib';
import { describe, expect, it } from 'vitest';
import type { PdfFile } from './pdfTypes';
import { mergePdfFilesAtPosition } from './pdfService';
async function makePdf(name: string, pageCount: number): Promise<PdfFile> {
const doc = await PDFDocument.create();
for (let i = 0; i < pageCount; i += 1) {
doc.addPage([100, 100]);
}
const bytes = await doc.save();
const arrayBuffer = new ArrayBuffer(bytes.byteLength);
new Uint8Array(arrayBuffer).set(bytes);
return {
id: name,
name,
doc,
pageCount,
arrayBuffer,
};
}
describe('mergePdfFilesAtPosition', () => {
it('merges a queue without a current base PDF', async () => {
const first = await makePdf('first.pdf', 1);
const second = await makePdf('second.pdf', 2);
const merged = await mergePdfFilesAtPosition({
basePdf: null,
incomingPdfs: [first, second],
insertAt: 0,
name: 'merged.pdf',
});
expect(merged.name).toBe('merged.pdf');
expect(merged.pageCount).toBe(3);
expect(merged.doc.getPageCount()).toBe(3);
});
it('inserts queued PDFs into a current base PDF at the requested slot', async () => {
const base = await makePdf('base.pdf', 3);
const incoming = await makePdf('incoming.pdf', 2);
const merged = await mergePdfFilesAtPosition({
basePdf: base,
incomingPdfs: [incoming],
insertAt: 1,
name: 'base_plus_incoming.pdf',
});
expect(merged.name).toBe('base_plus_incoming.pdf');
expect(merged.pageCount).toBe(5);
expect(merged.doc.getPageCount()).toBe(5);
});
});

View File

@@ -77,6 +77,76 @@ export async function mergePdfFiles(
};
}
interface MergePdfFilesAtPositionOptions {
basePdf: PdfFile | null;
incomingPdfs: PdfFile[];
insertAt: number;
name: string;
}
export async function mergePdfFilesAtPosition({
basePdf,
incomingPdfs,
insertAt,
name,
}: MergePdfFilesAtPositionOptions): Promise<PdfFile> {
if (!basePdf && incomingPdfs.length === 0) {
throw new Error('At least one PDF is required for merging');
}
const mergedDoc = await PDFDocument.create();
const addAllPages = async (sourcePdf: PdfFile) => {
const sourceDoc =
sourcePdf.doc ?? (await PDFDocument.load(sourcePdf.arrayBuffer));
const pageCount = sourceDoc.getPageCount();
const pages = await mergedDoc.copyPages(
sourceDoc,
Array.from({ length: pageCount }, (_, i) => i)
);
pages.forEach((page) => mergedDoc.addPage(page));
};
if (!basePdf) {
for (const incomingPdf of incomingPdfs) {
await addAllPages(incomingPdf);
}
} else {
const baseDoc =
basePdf.doc ?? (await PDFDocument.load(basePdf.arrayBuffer));
const basePageCount = baseDoc.getPageCount();
const clampedInsertAt = Math.min(Math.max(insertAt, 0), basePageCount);
const basePages = await mergedDoc.copyPages(
baseDoc,
Array.from({ length: basePageCount }, (_, i) => i)
);
for (let i = 0; i < clampedInsertAt; i += 1) {
mergedDoc.addPage(basePages[i]);
}
for (const incomingPdf of incomingPdfs) {
await addAllPages(incomingPdf);
}
for (let i = clampedInsertAt; i < basePages.length; i += 1) {
mergedDoc.addPage(basePages[i]);
}
}
const bytes = await mergedDoc.save();
const buffer = pdfBytesToArrayBuffer(bytes);
return {
id: createId(),
name,
arrayBuffer: buffer,
pageCount: mergedDoc.getPageCount(),
doc: mergedDoc,
};
}
export async function splitIntoSinglePages(
pdf: PdfFile
): Promise<SplitResult[]> {