UNPKG

pdf-lib

Version:

Create and modify PDF files with JavaScript

157 lines (124 loc) 4.9 kB
import PDFCrossRefSection from 'src/core/document/PDFCrossRefSection'; import PDFHeader from 'src/core/document/PDFHeader'; import PDFTrailer from 'src/core/document/PDFTrailer'; import PDFTrailerDict from 'src/core/document/PDFTrailerDict'; import PDFDict from 'src/core/objects/PDFDict'; import PDFObject from 'src/core/objects/PDFObject'; import PDFRef from 'src/core/objects/PDFRef'; import PDFContext from 'src/core/PDFContext'; import PDFObjectStream from 'src/core/structures/PDFObjectStream'; import CharCodes from 'src/core/syntax/CharCodes'; import { copyStringIntoBuffer, waitForTick } from 'src/utils'; export interface SerializationInfo { size: number; header: PDFHeader; indirectObjects: [PDFRef, PDFObject][]; xref?: PDFCrossRefSection; trailerDict?: PDFTrailerDict; trailer: PDFTrailer; } class PDFWriter { static forContext = (context: PDFContext, objectsPerTick: number) => new PDFWriter(context, objectsPerTick); protected readonly context: PDFContext; protected readonly objectsPerTick: number; private parsedObjects = 0; protected constructor(context: PDFContext, objectsPerTick: number) { this.context = context; this.objectsPerTick = objectsPerTick; } async serializeToBuffer(): Promise<Uint8Array> { const { size, header, indirectObjects, xref, trailerDict, trailer, } = await this.computeBufferSize(); let offset = 0; const buffer = new Uint8Array(size); offset += header.copyBytesInto(buffer, offset); buffer[offset++] = CharCodes.Newline; buffer[offset++] = CharCodes.Newline; for (let idx = 0, len = indirectObjects.length; idx < len; idx++) { const [ref, object] = indirectObjects[idx]; const objectNumber = String(ref.objectNumber); offset += copyStringIntoBuffer(objectNumber, buffer, offset); buffer[offset++] = CharCodes.Space; const generationNumber = String(ref.generationNumber); offset += copyStringIntoBuffer(generationNumber, buffer, offset); buffer[offset++] = CharCodes.Space; buffer[offset++] = CharCodes.o; buffer[offset++] = CharCodes.b; buffer[offset++] = CharCodes.j; buffer[offset++] = CharCodes.Newline; offset += object.copyBytesInto(buffer, offset); buffer[offset++] = CharCodes.Newline; buffer[offset++] = CharCodes.e; buffer[offset++] = CharCodes.n; buffer[offset++] = CharCodes.d; buffer[offset++] = CharCodes.o; buffer[offset++] = CharCodes.b; buffer[offset++] = CharCodes.j; buffer[offset++] = CharCodes.Newline; buffer[offset++] = CharCodes.Newline; const n = object instanceof PDFObjectStream ? object.getObjectsCount() : 1; if (this.shouldWaitForTick(n)) await waitForTick(); } if (xref) { offset += xref.copyBytesInto(buffer, offset); buffer[offset++] = CharCodes.Newline; } if (trailerDict) { offset += trailerDict.copyBytesInto(buffer, offset); buffer[offset++] = CharCodes.Newline; buffer[offset++] = CharCodes.Newline; } offset += trailer.copyBytesInto(buffer, offset); return buffer; } protected computeIndirectObjectSize([ref, object]: [ PDFRef, PDFObject, ]): number { const refSize = ref.sizeInBytes() + 3; // 'R' -> 'obj\n' const objectSize = object.sizeInBytes() + 9; // '\nendobj\n\n' return refSize + objectSize; } protected createTrailerDict(): PDFDict { return this.context.obj({ Size: this.context.largestObjectNumber + 1, Root: this.context.trailerInfo.Root, Encrypt: this.context.trailerInfo.Encrypt, Info: this.context.trailerInfo.Info, ID: this.context.trailerInfo.ID, }); } protected async computeBufferSize(): Promise<SerializationInfo> { const header = PDFHeader.forVersion(1, 7); let size = header.sizeInBytes() + 2; const xref = PDFCrossRefSection.create(); const indirectObjects = this.context.enumerateIndirectObjects(); for (let idx = 0, len = indirectObjects.length; idx < len; idx++) { const indirectObject = indirectObjects[idx]; const [ref] = indirectObject; xref.addEntry(ref, size); size += this.computeIndirectObjectSize(indirectObject); if (this.shouldWaitForTick(1)) await waitForTick(); } const xrefOffset = size; size += xref.sizeInBytes() + 1; // '\n' const trailerDict = PDFTrailerDict.of(this.createTrailerDict()); size += trailerDict.sizeInBytes() + 2; // '\n\n' const trailer = PDFTrailer.forLastCrossRefSectionOffset(xrefOffset); size += trailer.sizeInBytes(); return { size, header, indirectObjects, xref, trailerDict, trailer }; } protected shouldWaitForTick = (n: number) => { this.parsedObjects += n; return this.parsedObjects % this.objectsPerTick === 0; }; } export default PDFWriter;