import { PDFDocument, degrees } from 'pdf-lib'; import type { PdfFile, PageRef, SplitResult, Range } from './pdfTypes'; function createId() { return Math.random().toString(36).slice(2); } export async function loadPdfFromFile(file: File): Promise { const arrayBuffer = await file.arrayBuffer(); const doc = await PDFDocument.load(arrayBuffer); return { id: createId(), name: file.name, doc, pageCount: doc.getPageCount(), arrayBuffer, }; } export async function mergePdfFiles( basePdf: PdfFile, newPdf: PdfFile, insertAt: number ): Promise { const baseDoc = basePdf.doc ?? await PDFDocument.load(basePdf.arrayBuffer); const newDoc = newPdf.doc ?? await PDFDocument.load(newPdf.arrayBuffer); const mergedDoc = await PDFDocument.create(); const basePageCount = baseDoc.getPageCount(); const newPageCount = newDoc.getPageCount(); const clampedInsertAt = Math.min(Math.max(insertAt, 0), basePageCount); const basePages = await mergedDoc.copyPages( baseDoc, Array.from({ length: basePageCount }, (_, i) => i) ); const newPages = await mergedDoc.copyPages( newDoc, Array.from({ length: newPageCount }, (_, i) => i) ); for (let i = 0; i < clampedInsertAt; i += 1) { mergedDoc.addPage(basePages[i]); } for (let i = 0; i < newPages.length; i += 1) { mergedDoc.addPage(newPages[i]); } for (let i = clampedInsertAt; i < basePages.length; i += 1) { mergedDoc.addPage(basePages[i]); } const bytes = await mergedDoc.save(); const buffer = new ArrayBuffer(bytes.byteLength); new Uint8Array(buffer).set(bytes); const baseName = basePdf.name.replace(/\.pdf$/i, ''); const newName = newPdf.name.replace(/\.pdf$/i, ''); return { id: createId(), name: `${baseName}_plus_${newName}.pdf`, arrayBuffer: buffer, pageCount: mergedDoc.getPageCount(), doc: mergedDoc, }; } export async function splitIntoSinglePages( pdf: PdfFile ): Promise { const { doc, name } = pdf; const title = doc.getTitle(); const author = doc.getAuthor(); const subject = doc.getSubject(); const keywords = doc.getKeywords(); const producer = doc.getProducer(); const creator = doc.getCreator(); const creationDate = doc.getCreationDate(); const modificationDate = doc.getModificationDate(); const results: SplitResult[] = []; for (let i = 0; i < doc.getPageCount(); i++) { const newDoc = await PDFDocument.create(); const [copiedPage] = await newDoc.copyPages(doc, [i]); newDoc.addPage(copiedPage); if (title) newDoc.setTitle(title); if (author) newDoc.setAuthor(author); if (subject) newDoc.setSubject(subject); if (keywords) newDoc.setKeywords([keywords]); if (producer) newDoc.setProducer(producer); if (creator) newDoc.setCreator(creator); if (creationDate) newDoc.setCreationDate(creationDate); if (modificationDate) newDoc.setModificationDate(modificationDate); const bytes = await newDoc.save(); const blob = new Blob([bytes], { type: 'application/pdf' }); const base = name.replace(/\.pdf$/i, ''); const filename = `${base}_page_${String(i + 1).padStart(3, '0')}.pdf`; results.push({ pageIndex: i, blob, filename, }); } return results; } export async function extractRange( pdf: PdfFile, range: Range ): Promise { const { doc } = pdf; const pageCount = doc.getPageCount(); const fromIndex = Math.max(0, range.from - 1); const toIndex = Math.min(pageCount - 1, range.to - 1); if (fromIndex > toIndex) { throw new Error('Invalid range: from > to'); } const newDoc = await PDFDocument.create(); const indices: number[] = []; for (let i = fromIndex; i <= toIndex; i++) indices.push(i); const copiedPages = await newDoc.copyPages(doc, indices); copiedPages.forEach((p) => newDoc.addPage(p)); const bytes = await newDoc.save(); return new Blob([bytes], { type: 'application/pdf' }); } export async function mergePdfs(pdfs: PdfFile[]): Promise { const newDoc = await PDFDocument.create(); for (const pdf of pdfs) { const pageCount = pdf.doc.getPageCount(); const indices = Array.from({ length: pageCount }, (_, i) => i); const copiedPages = await newDoc.copyPages(pdf.doc, indices); copiedPages.forEach((p) => newDoc.addPage(p)); } const bytes = await newDoc.save(); return new Blob([bytes], { type: 'application/pdf' }); } export async function exportPages( pdf: PdfFile, pages: PageRef[] ): Promise { const { doc } = pdf; const pageCount = doc.getPageCount(); if (pages.length === 0) { throw new Error('Pages must contain at least one page'); } if ( pages.some( (page) => page.sourcePageIndex < 0 || page.sourcePageIndex >= pageCount ) ) { throw new Error('Pages contain invalid source page indices'); } const newDoc = await PDFDocument.create(); const indices = pages.map((page) => page.sourcePageIndex); const copiedPages = await newDoc.copyPages(doc, indices); copiedPages.forEach((page, idx) => { const angle = pages[idx].rotation; if (typeof angle === 'number' && angle % 360 !== 0) { page.setRotation(degrees(angle)); } newDoc.addPage(page); }); const bytes = await newDoc.save(); return new Blob([bytes], { type: 'application/pdf' }); } export async function exportReordered( pdf: PdfFile, order: number[], rotations?: Record ): Promise { return exportPages( pdf, order.map((sourcePageIndex) => ({ id: String(sourcePageIndex), sourcePageIndex, rotation: rotations?.[sourcePageIndex] ?? 0, })) ); }