@cantoo/pdf-lib
Version:
Create and modify PDF files with JavaScript
175 lines • 7.93 kB
JavaScript
import PDFName from '../objects/PDFName.js';
import PDFRef from '../objects/PDFRef.js';
import PDFFlateStream from './PDFFlateStream.js';
import { bytesFor, Cache, reverseArray, sizeInBytes, sum } from '../../utils/index.js';
export var EntryType;
(function (EntryType) {
EntryType[EntryType["Deleted"] = 0] = "Deleted";
EntryType[EntryType["Uncompressed"] = 1] = "Uncompressed";
EntryType[EntryType["Compressed"] = 2] = "Compressed";
})(EntryType || (EntryType = {}));
/**
* Entries should be added using the [[addDeletedEntry]],
* [[addUncompressedEntry]], and [[addCompressedEntry]] methods
* **in order of ascending object number**.
*/
class PDFCrossRefStream extends PDFFlateStream {
constructor(dict, entries, encode = true) {
super(dict, encode);
// Returns an array of integer pairs for each subsection of the cross ref
// section, where each integer pair represents:
// firstObjectNumber(OfSection), length(OfSection)
this.computeIndex = () => {
const subsections = [];
let subsectionLength = 0;
for (let idx = 0, len = this.entries.length; idx < len; idx++) {
const currEntry = this.entries[idx];
const prevEntry = this.entries[idx - 1];
if (idx === 0) {
subsections.push(currEntry.ref.objectNumber);
}
else if (currEntry.ref.objectNumber - prevEntry.ref.objectNumber > 1) {
subsections.push(subsectionLength);
subsections.push(currEntry.ref.objectNumber);
subsectionLength = 0;
}
subsectionLength += 1;
}
subsections.push(subsectionLength);
return subsections;
};
this.computeEntryTuples = () => {
const entryTuples = new Array(this.entries.length);
for (let idx = 0, len = this.entries.length; idx < len; idx++) {
const entry = this.entries[idx];
if (entry.type === EntryType.Deleted) {
const { type, nextFreeObjectNumber, ref } = entry;
entryTuples[idx] = [type, nextFreeObjectNumber, ref.generationNumber];
}
if (entry.type === EntryType.Uncompressed) {
const { type, offset, ref } = entry;
entryTuples[idx] = [type, offset, ref.generationNumber];
}
if (entry.type === EntryType.Compressed) {
const { type, objectStreamRef, index } = entry;
entryTuples[idx] = [type, objectStreamRef.objectNumber, index];
}
}
return entryTuples;
};
this.computeMaxEntryByteWidths = () => {
const entryTuples = this.entryTuplesCache.access();
const widths = [0, 0, 0];
for (let idx = 0, len = entryTuples.length; idx < len; idx++) {
const [first, second, third] = entryTuples[idx];
const firstSize = sizeInBytes(first);
const secondSize = sizeInBytes(second);
const thirdSize = sizeInBytes(third);
if (firstSize > widths[0])
widths[0] = firstSize;
if (secondSize > widths[1])
widths[1] = secondSize;
if (thirdSize > widths[2])
widths[2] = thirdSize;
}
return widths;
};
this.entries = entries || [];
this.entryTuplesCache = Cache.populatedBy(this.computeEntryTuples);
this.maxByteWidthsCache = Cache.populatedBy(this.computeMaxEntryByteWidths);
this.indexCache = Cache.populatedBy(this.computeIndex);
dict.set(PDFName.of('Type'), PDFName.of('XRef'));
}
addDeletedEntry(ref, nextFreeObjectNumber) {
const type = EntryType.Deleted;
this.entries.push({ type, ref, nextFreeObjectNumber });
this.entryTuplesCache.invalidate();
this.maxByteWidthsCache.invalidate();
this.indexCache.invalidate();
this.contentsCache.invalidate();
}
addUncompressedEntry(ref, offset) {
const type = EntryType.Uncompressed;
this.entries.push({ type, ref, offset });
this.entryTuplesCache.invalidate();
this.maxByteWidthsCache.invalidate();
this.indexCache.invalidate();
this.contentsCache.invalidate();
}
addCompressedEntry(ref, objectStreamRef, index) {
const type = EntryType.Compressed;
this.entries.push({ type, ref, objectStreamRef, index });
this.entryTuplesCache.invalidate();
this.maxByteWidthsCache.invalidate();
this.indexCache.invalidate();
this.contentsCache.invalidate();
}
clone(context) {
const { dict, entries, encode } = this;
return PDFCrossRefStream.of(dict.clone(context), entries.slice(), encode);
}
getContentsString() {
const entryTuples = this.entryTuplesCache.access();
const byteWidths = this.maxByteWidthsCache.access();
let value = '';
for (let entryIdx = 0, entriesLen = entryTuples.length; entryIdx < entriesLen; entryIdx++) {
const [first, second, third] = entryTuples[entryIdx];
const firstBytes = reverseArray(bytesFor(first));
const secondBytes = reverseArray(bytesFor(second));
const thirdBytes = reverseArray(bytesFor(third));
for (let idx = byteWidths[0] - 1; idx >= 0; idx--) {
value += (firstBytes[idx] || 0).toString(2);
}
for (let idx = byteWidths[1] - 1; idx >= 0; idx--) {
value += (secondBytes[idx] || 0).toString(2);
}
for (let idx = byteWidths[2] - 1; idx >= 0; idx--) {
value += (thirdBytes[idx] || 0).toString(2);
}
}
return value;
}
getUnencodedContents() {
const entryTuples = this.entryTuplesCache.access();
const byteWidths = this.maxByteWidthsCache.access();
const buffer = new Uint8Array(this.getUnencodedContentsSize());
let offset = 0;
for (let entryIdx = 0, entriesLen = entryTuples.length; entryIdx < entriesLen; entryIdx++) {
const [first, second, third] = entryTuples[entryIdx];
const firstBytes = reverseArray(bytesFor(first));
const secondBytes = reverseArray(bytesFor(second));
const thirdBytes = reverseArray(bytesFor(third));
for (let idx = byteWidths[0] - 1; idx >= 0; idx--) {
buffer[offset++] = firstBytes[idx] || 0;
}
for (let idx = byteWidths[1] - 1; idx >= 0; idx--) {
buffer[offset++] = secondBytes[idx] || 0;
}
for (let idx = byteWidths[2] - 1; idx >= 0; idx--) {
buffer[offset++] = thirdBytes[idx] || 0;
}
}
return buffer;
}
getUnencodedContentsSize() {
const byteWidths = this.maxByteWidthsCache.access();
const entryWidth = sum(byteWidths);
return entryWidth * this.entries.length;
}
updateDict() {
super.updateDict();
const byteWidths = this.maxByteWidthsCache.access();
const index = this.indexCache.access();
const { context } = this.dict;
this.dict.set(PDFName.of('W'), context.obj(byteWidths));
this.dict.set(PDFName.of('Index'), context.obj(index));
}
}
PDFCrossRefStream.create = (dict, encode = true) => {
const stream = new PDFCrossRefStream(dict, [], encode);
stream.addDeletedEntry(PDFRef.of(0, 65535), 0);
return stream;
};
PDFCrossRefStream.of = (dict, entries, encode = true) => new PDFCrossRefStream(dict, entries, encode);
export default PDFCrossRefStream;
//# sourceMappingURL=PDFCrossRefStream.js.map