UNPKG

pdf-lib

Version:

Create and modify PDF files with JavaScript

152 lines (129 loc) 4.53 kB
import PDFArray from 'src/core/objects/PDFArray'; import PDFDict, { DictMap } from 'src/core/objects/PDFDict'; import PDFName from 'src/core/objects/PDFName'; import PDFNumber from 'src/core/objects/PDFNumber'; import PDFRef from 'src/core/objects/PDFRef'; import PDFContext from 'src/core/PDFContext'; import PDFPageLeaf from 'src/core/structures/PDFPageLeaf'; export type TreeNode = PDFPageTree | PDFPageLeaf; class PDFPageTree extends PDFDict { static withContext = (context: PDFContext, parent?: PDFRef) => { const dict = new Map(); dict.set(PDFName.of('Type'), PDFName.of('Pages')); dict.set(PDFName.of('Kids'), context.obj([])); dict.set(PDFName.of('Count'), context.obj(0)); if (parent) dict.set(PDFName.of('Parent'), parent); return new PDFPageTree(dict, context); }; static fromMapWithContext = (map: DictMap, context: PDFContext) => new PDFPageTree(map, context); Parent(): PDFPageTree | undefined { return this.lookup(PDFName.of('Parent')) as PDFPageTree | undefined; } Kids(): PDFArray { return this.lookup(PDFName.of('Kids'), PDFArray); } Count(): PDFNumber { return this.lookup(PDFName.of('Count'), PDFNumber); } pushTreeNode(treeRef: PDFRef): void { const Kids = this.Kids(); Kids.push(treeRef); } pushLeafNode(leafRef: PDFRef): void { const Kids = this.Kids(); Kids.push(leafRef); this.ascend((node) => { const Count = node.Count(); node.set(PDFName.of('Count'), PDFNumber.of(Count.value() + 1)); }); } /** * Inserts the given ref as a leaf node of this page tree at the specified * index (zero-based). Also increments the `Count` of each page tree in the * hierarchy to accomodate the new page. * * Returns the ref of the PDFPageTree node into which `leafRef` was inserted, * or `undefined` if it was inserted into the root node (the PDFPageTree upon * which the method was first called). */ insertLeafNode(leafRef: PDFRef, index: number): PDFRef | undefined { const Kids = this.Kids(); const kidSize = Kids.size(); let kidIdx = 0; let currIndex = 0; while (currIndex < index) { if (kidIdx >= kidSize) { throw new Error(`Index out of bounds: ${kidIdx}/${kidSize}`); } const kidRef = Kids.get(kidIdx++) as PDFRef; const kid = this.context.lookup(kidRef) as TreeNode; if (kid instanceof PDFPageTree) { const kidCount = kid.Count().value(); if (currIndex + kidCount > index) { return kid.insertLeafNode(leafRef, index - currIndex) || kidRef; } else { currIndex += kidCount; } } else { currIndex += 1; } } Kids.insert(kidIdx, leafRef); this.ascend((node) => { const Count = node.Count(); node.set(PDFName.of('Count'), PDFNumber.of(Count.value() + 1)); }); return undefined; } removeLeafNode(index: number): void { const Kids = this.Kids(); const kidSize = Kids.size(); let kidIdx = 0; let currIndex = 0; while (currIndex < index) { if (kidIdx >= kidSize - 1) { throw new Error(`Index out of bounds: ${kidIdx}/${kidSize - 1}`); } const kidRef = Kids.get(kidIdx++) as PDFRef; const kid = this.context.lookup(kidRef) as TreeNode; if (kid instanceof PDFPageTree) { const kidCount = kid.Count().value(); if (currIndex + kidCount > index) { kid.removeLeafNode(index - currIndex); return; } else { currIndex += kidCount; } } else { currIndex += 1; } } const target = Kids.lookup(kidIdx); if (target instanceof PDFPageTree) { target.removeLeafNode(0); } else { Kids.remove(kidIdx); this.ascend((node) => { const Count = node.Count(); node.set(PDFName.of('Count'), PDFNumber.of(Count.value() - 1)); }); } } ascend(visitor: (node: PDFPageTree) => any): void { visitor(this); const Parent = this.Parent(); if (Parent) Parent.ascend(visitor); } /** Performs a Post-Order traversal of this page tree */ traverse(visitor: (node: TreeNode, ref: PDFRef) => any): void { const Kids = this.Kids(); for (let idx = 0, len = Kids.size(); idx < len; idx++) { const kidRef = Kids.get(idx) as PDFRef; const kid = this.context.lookup(kidRef) as TreeNode; if (kid instanceof PDFPageTree) kid.traverse(visitor); visitor(kid, kidRef); } } } export default PDFPageTree;