import { zipSync } from 'fflate'; import type { SplitResult } from './pdfTypes'; function bytesToBlob(bytes: Uint8Array, type: string): Blob { const buffer = new ArrayBuffer(bytes.byteLength); new Uint8Array(buffer).set(bytes); return new Blob([buffer], { type }); } function removeControlCharacters(value: string): string { return Array.from(value) .filter((character) => { const code = character.charCodeAt(0); return code > 31 && code !== 127; }) .join(''); } function safeZipEntryName(filename: string, fallback: string): string { const cleaned = removeControlCharacters(filename) .replace(/[\\/]+/g, '_') .trim(); return cleaned.length > 0 ? cleaned : fallback; } function uniqueZipEntryName(filename: string, usedNames: Set): string { if (!usedNames.has(filename)) { usedNames.add(filename); return filename; } const dotIndex = filename.lastIndexOf('.'); const hasExtension = dotIndex > 0; const base = hasExtension ? filename.slice(0, dotIndex) : filename; const extension = hasExtension ? filename.slice(dotIndex) : ''; let counter = 2; let candidate = `${base}_${counter}${extension}`; while (usedNames.has(candidate)) { counter += 1; candidate = `${base}_${counter}${extension}`; } usedNames.add(candidate); return candidate; } export function createSplitZipFilename(pdfName: string): string { const baseName = pdfName.replace(/\.pdf$/i, '').trim() || 'document'; return `${baseName}_split_pages.zip`; } export async function createSplitResultsZip( results: SplitResult[] ): Promise { if (results.length === 0) { throw new Error('Cannot create a ZIP archive without split results.'); } const usedNames = new Set(); const entries: Record = {}; for (const result of results) { const fallback = `page_${String(result.pageIndex + 1).padStart(3, '0')}.pdf`; const entryName = uniqueZipEntryName( safeZipEntryName(result.filename, fallback), usedNames ); const arrayBuffer = await result.blob.arrayBuffer(); entries[entryName] = new Uint8Array(arrayBuffer); } const zippedBytes = zipSync(entries, { level: 6 }); return bytesToBlob(zippedBytes, 'application/zip'); }