UNPKG

@cantoo/pdf-lib

Version:

Create and modify PDF files with JavaScript

218 lines (185 loc) 6.5 kB
import PDFRef from '../objects/PDFRef'; import CharCodes from '../syntax/CharCodes'; import { copyStringIntoBuffer, padStart } from '../../utils'; export interface Entry { ref: PDFRef; offset: number; deleted: boolean; } /** * Entries should be added using the [[addEntry]] and [[addDeletedEntry]] * methods. */ class PDFCrossRefSection { static create = () => new PDFCrossRefSection({ ref: PDFRef.of(0, 65535), offset: 0, deleted: true, }); static createEmpty = () => new PDFCrossRefSection(); private subsections: Entry[][]; private chunkIdx: number; private chunkLength: number; private constructor(firstEntry: Entry | void) { this.subsections = firstEntry ? [[firstEntry]] : []; this.chunkIdx = 0; this.chunkLength = firstEntry ? 1 : 0; } addEntry(ref: PDFRef, offset: number): void { this.append({ ref, offset, deleted: false }); } addDeletedEntry(ref: PDFRef, nextFreeObjectNumber: number): void { // fix the first entry if required if (!this.subsections.length) { this.subsections = [ [ { ref: PDFRef.of(0, 65535), offset: ref.objectNumber, deleted: true, }, ], ]; this.chunkIdx = 0; this.chunkLength = 1; } else if (!this.subsections[0][0].offset) { this.subsections[0][0].offset = ref.objectNumber; } this.append({ ref, offset: nextFreeObjectNumber, deleted: true }); } toString(): string { let section = 'xref\n'; for ( let rangeIdx = 0, rangeLen = this.subsections.length; rangeIdx < rangeLen; rangeIdx++ ) { const range = this.subsections[rangeIdx]; section += `${range[0].ref.objectNumber} ${range.length}\n`; for ( let entryIdx = 0, entryLen = range.length; entryIdx < entryLen; entryIdx++ ) { const entry = range[entryIdx]; section += padStart(String(entry.offset), 10, '0'); section += ' '; section += padStart(String(entry.ref.generationNumber), 5, '0'); section += ' '; section += entry.deleted ? 'f' : 'n'; section += ' \n'; } } return section; } sizeInBytes(): number { let size = 5; for (let idx = 0, len = this.subsections.length; idx < len; idx++) { const subsection = this.subsections[idx]; const subsectionLength = subsection.length; const [firstEntry] = subsection; size += 2; size += String(firstEntry.ref.objectNumber).length; size += String(subsectionLength).length; size += 20 * subsectionLength; } return size; } copyBytesInto(buffer: Uint8Array, offset: number): number { const initialOffset = offset; buffer[offset++] = CharCodes.x; buffer[offset++] = CharCodes.r; buffer[offset++] = CharCodes.e; buffer[offset++] = CharCodes.f; buffer[offset++] = CharCodes.Newline; offset += this.copySubsectionsIntoBuffer(this.subsections, buffer, offset); return offset - initialOffset; } private copySubsectionsIntoBuffer( subsections: Entry[][], buffer: Uint8Array, offset: number, ): number { const initialOffset = offset; const length = subsections.length; for (let idx = 0; idx < length; idx++) { const subsection = this.subsections[idx]; const firstObjectNumber = String(subsection[0].ref.objectNumber); offset += copyStringIntoBuffer(firstObjectNumber, buffer, offset); buffer[offset++] = CharCodes.Space; const rangeLength = String(subsection.length); offset += copyStringIntoBuffer(rangeLength, buffer, offset); buffer[offset++] = CharCodes.Newline; offset += this.copyEntriesIntoBuffer(subsection, buffer, offset); } return offset - initialOffset; } private copyEntriesIntoBuffer( entries: Entry[], buffer: Uint8Array, offset: number, ): number { const length = entries.length; for (let idx = 0; idx < length; idx++) { const entry = entries[idx]; const entryOffset = padStart(String(entry.offset), 10, '0'); offset += copyStringIntoBuffer(entryOffset, buffer, offset); buffer[offset++] = CharCodes.Space; const entryGen = padStart(String(entry.ref.generationNumber), 5, '0'); offset += copyStringIntoBuffer(entryGen, buffer, offset); buffer[offset++] = CharCodes.Space; buffer[offset++] = entry.deleted ? CharCodes.f : CharCodes.n; buffer[offset++] = CharCodes.Space; buffer[offset++] = CharCodes.Newline; } return 20 * length; } private append(currEntry: Entry): void { if (this.chunkLength === 0) { this.subsections.push([currEntry]); this.chunkIdx = 0; this.chunkLength = 1; return; } const chunk = this.subsections[this.chunkIdx]; const prevEntry = chunk[this.chunkLength - 1]; if (currEntry.ref.objectNumber - prevEntry.ref.objectNumber !== 1) { // the current chunk is not the right chunk, find the right one, or create a new one for (let c = 0; c < this.subsections.length; c++) { const first = this.subsections[c][0]; const last = this.subsections[c][this.subsections[c].length - 1]; if (first.ref.objectNumber > currEntry.ref.objectNumber) { // goes before this subsection, or at the start of it if (first.ref.objectNumber - currEntry.ref.objectNumber === 1) { // first element of subsection this.subsections[c].unshift(currEntry); if (c === this.chunkIdx) this.chunkLength += 1; return; } else { // create subsection this.subsections.splice(c, 0, [currEntry]); this.chunkIdx++; return; } } else if (last.ref.objectNumber > currEntry.ref.objectNumber) { // goes in this subsection, find its place.. const cep = this.subsections[c].findIndex( (ee) => ee.ref.objectNumber > currEntry.ref.objectNumber, ); this.subsections[c].splice(cep, 0, currEntry); if (c === this.chunkIdx) this.chunkLength += 1; } // bigger, keep looking } // if got to here, then a new subsection is required this.subsections.push([currEntry]); this.chunkIdx += 1; this.chunkLength = 1; } else { chunk.push(currEntry); this.chunkLength += 1; } } } export default PDFCrossRefSection;