zip download of single pdfs
This commit is contained in:
78
src/pdf/pdfZipService.ts
Normal file
78
src/pdf/pdfZipService.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
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>): 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<Blob> {
|
||||
if (results.length === 0) {
|
||||
throw new Error('Cannot create a ZIP archive without split results.');
|
||||
}
|
||||
|
||||
const usedNames = new Set<string>();
|
||||
const entries: Record<string, Uint8Array> = {};
|
||||
|
||||
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');
|
||||
}
|
||||
Reference in New Issue
Block a user