• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

adnsistemas / pdf-lib / #18

24 Mar 2026 08:15PM UTC coverage: 74.286% (+0.3%) from 74.001%
#18

push

David N. Abdala
Documentation change

2569 of 3981 branches covered (64.53%)

Branch coverage included in aggregate %.

7372 of 9401 relevant lines covered (78.42%)

297170.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.12
/src/core/structures/PDFPageTree.ts
1
import PDFArray from '../objects/PDFArray';
54✔
2
import PDFDict, { DictMap } from '../objects/PDFDict';
54✔
3
import PDFName from '../objects/PDFName';
54✔
4
import PDFNumber from '../objects/PDFNumber';
54✔
5
import PDFRef from '../objects/PDFRef';
6
import PDFContext from '../PDFContext';
7
import PDFPageLeaf from './PDFPageLeaf';
8
import { InvalidTargetIndexError, CorruptPageTreeError } from '../errors';
54✔
9
import { isPDFInstance, PDFClasses } from '../../api/objects';
54✔
10

11
export type TreeNode = PDFPageTree | PDFPageLeaf;
12

13
class PDFPageTree extends PDFDict {
14
  static className = () => PDFClasses.PDFPageTree;
6,558✔
15
  myClass(): PDFClasses {
16
    return PDFClasses.PDFPageTree;
6,712✔
17
  }
18

19
  static withContext = (context: PDFContext, parent?: PDFRef) => {
54✔
20
    const dict = new Map();
180✔
21
    dict.set(PDFName.of('Type'), PDFName.of('Pages'));
180✔
22
    dict.set(PDFName.of('Kids'), context.obj([]));
180✔
23
    dict.set(PDFName.of('Count'), context.obj(0));
180✔
24
    if (parent) dict.set(PDFName.of('Parent'), parent);
180✔
25
    return new PDFPageTree(dict, context);
180✔
26
  };
27

28
  static fromMapWithContext = (map: DictMap, context: PDFContext) =>
54✔
29
    new PDFPageTree(map, context);
338✔
30

31
  Parent(): PDFPageTree | undefined {
32
    return this.lookup(PDFName.of('Parent')) as PDFPageTree | undefined;
838✔
33
  }
34

35
  Kids(): PDFArray {
36
    return this.lookup(PDFName.of('Kids'), PDFArray);
848✔
37
  }
38

39
  Count(): PDFNumber {
40
    return this.lookup(PDFName.of('Count'), PDFNumber);
652✔
41
  }
42

43
  pushTreeNode(treeRef: PDFRef): void {
44
    const Kids = this.Kids();
80✔
45
    Kids.push(treeRef);
80✔
46
  }
47

48
  pushLeafNode(leafRef: PDFRef): void {
49
    const Kids = this.Kids();
145✔
50
    this.insertLeafKid(Kids.size(), leafRef);
145✔
51
  }
52

53
  /**
54
   * Inserts the given ref as a leaf node of this page tree at the specified
55
   * index (zero-based). Also increments the `Count` of each page tree in the
56
   * hierarchy to accomodate the new page.
57
   *
58
   * Returns the ref of the PDFPageTree node into which `leafRef` was inserted,
59
   * or `undefined` if it was inserted into the root node (the PDFPageTree upon
60
   * which the method was first called).
61
   */
62
  insertLeafNode(leafRef: PDFRef, targetIndex: number): PDFRef | undefined {
63
    const Kids = this.Kids();
62✔
64
    const Count = this.Count().asNumber();
62✔
65

66
    if (targetIndex > Count) {
62✔
67
      throw new InvalidTargetIndexError(targetIndex, Count);
1✔
68
    }
69

70
    let leafsRemainingUntilTarget = targetIndex;
61✔
71
    for (let idx = 0, len = Kids.size(); idx < len; idx++) {
61✔
72
      if (leafsRemainingUntilTarget === 0) {
49✔
73
        // Insert page and return
74
        this.insertLeafKid(idx, leafRef);
6✔
75
        return undefined;
6✔
76
      }
77

78
      const kidRef = Kids.get(idx) as PDFRef;
43✔
79
      const kid = this.context.lookup(kidRef);
43✔
80

81
      if (isPDFInstance(kid, PDFClasses.PDFPageTree)) {
43✔
82
        if (
5✔
83
          (kid as PDFPageTree).Count().asNumber() > leafsRemainingUntilTarget
84
        ) {
85
          // Dig in
86
          return (
4✔
87
            (kid as PDFPageTree).insertLeafNode(
7✔
88
              leafRef,
89
              leafsRemainingUntilTarget,
90
            ) || kidRef
91
          );
92
        } else {
93
          // Move on
94
          leafsRemainingUntilTarget -= (kid as PDFPageTree).Count().asNumber();
1✔
95
        }
96
      }
97

98
      if (isPDFInstance(kid, PDFClasses.PDFPageLeaf)) {
39✔
99
        // Move on
100
        leafsRemainingUntilTarget -= 1;
38✔
101
      }
102
    }
103

104
    if (leafsRemainingUntilTarget === 0) {
51✔
105
      // Insert page at the end and return
106
      this.insertLeafKid(Kids.size(), leafRef);
51✔
107
      return undefined;
51✔
108
    }
109

110
    // Should never get here if `targetIndex` is valid
111
    throw new CorruptPageTreeError(targetIndex, 'insertLeafNode');
×
112
  }
113

114
  /**
115
   * Removes the leaf node at the specified index (zero-based) from this page
116
   * tree. Also decrements the `Count` of each page tree in the hierarchy to
117
   * account for the removed page.
118
   *
119
   * If `prune` is true, then intermediate tree nodes will be removed from the
120
   * tree if they contain 0 children after the leaf node is removed.
121
   */
122
  removeLeafNode(targetIndex: number, prune = true): void {
28✔
123
    const Kids = this.Kids();
53✔
124
    const Count = this.Count().asNumber();
53✔
125

126
    if (targetIndex >= Count) {
53✔
127
      throw new InvalidTargetIndexError(targetIndex, Count);
6✔
128
    }
129

130
    let leafsRemainingUntilTarget = targetIndex;
47✔
131
    for (let idx = 0, len = Kids.size(); idx < len; idx++) {
47✔
132
      const kidRef = Kids.get(idx) as PDFRef;
77✔
133
      const kid = this.context.lookup(kidRef);
77✔
134

135
      if (isPDFInstance(kid, PDFClasses.PDFPageTree)) {
77✔
136
        if (
35✔
137
          (kid as PDFPageTree).Count().asNumber() > leafsRemainingUntilTarget
138
        ) {
139
          // Dig in
140
          (kid as PDFPageTree).removeLeafNode(leafsRemainingUntilTarget, prune);
24✔
141
          if (prune && (kid as PDFPageTree).Kids().size() === 0)
24✔
142
            Kids.remove(idx);
2✔
143
          return;
24✔
144
        } else {
145
          // Move on
146
          leafsRemainingUntilTarget -= (kid as PDFPageTree).Count().asNumber();
11✔
147
        }
148
      }
149

150
      if (isPDFInstance(kid, PDFClasses.PDFPageLeaf)) {
53✔
151
        if (leafsRemainingUntilTarget === 0) {
42✔
152
          // Remove page and return
153
          this.removeKid(idx);
21✔
154
          return;
21✔
155
        } else {
156
          // Move on
157
          leafsRemainingUntilTarget -= 1;
21✔
158
        }
159
      }
160
    }
161

162
    // Should never get here if `targetIndex` is valid
163
    throw new CorruptPageTreeError(targetIndex, 'removeLeafNode');
2✔
164
  }
165

166
  ascend(visitor: (node: PDFPageTree) => any): void {
167
    visitor(this);
836✔
168
    const Parent = this.Parent();
836✔
169
    if (Parent) Parent.ascend(visitor);
836✔
170
  }
171

172
  /** Performs a Post-Order traversal of this page tree */
173
  traverse(visitor: (node: TreeNode, ref: PDFRef) => any): void {
174
    const Kids = this.Kids();
212✔
175
    for (let idx = 0, len = Kids.size(); idx < len; idx++) {
212✔
176
      const kidRef = Kids.get(idx) as PDFRef;
602✔
177
      const kid = this.context.lookup(kidRef) as TreeNode;
602✔
178
      if (isPDFInstance(kid, PDFClasses.PDFPageTree))
602✔
179
        (kid as PDFPageTree).traverse(visitor);
84✔
180
      visitor(kid, kidRef);
602✔
181
    }
182
  }
183

184
  private insertLeafKid(kidIdx: number, leafRef: PDFRef): void {
185
    const Kids = this.Kids();
202✔
186

187
    this.ascend((node) => {
202✔
188
      const newCount = node.Count().asNumber() + 1;
400✔
189
      node.set(PDFName.of('Count'), PDFNumber.of(newCount));
400✔
190
    });
191

192
    Kids.insert(kidIdx, leafRef);
202✔
193
  }
194

195
  private removeKid(kidIdx: number): void {
196
    const Kids = this.Kids();
21✔
197

198
    const kid = Kids.lookup(kidIdx);
21✔
199
    if (isPDFInstance(kid, PDFClasses.PDFPageLeaf)) {
21✔
200
      this.ascend((node) => {
21✔
201
        const newCount = node.Count().asNumber() - 1;
45✔
202
        node.set(PDFName.of('Count'), PDFNumber.of(newCount));
45✔
203
      });
204
    }
205

206
    Kids.remove(kidIdx);
21✔
207
  }
208
}
209

210
export default PDFPageTree;
54✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc