• 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

94.81
/src/core/PDFObjectCopier.ts
1
import { isPDFInstance, PDFClasses } from '../api/objects';
54✔
2
import PDFArray from './objects/PDFArray';
3
import PDFDict from './objects/PDFDict';
4
import PDFName from './objects/PDFName';
54✔
5
import PDFObject from './objects/PDFObject';
6
import PDFRef from './objects/PDFRef';
7
import PDFStream from './objects/PDFStream';
8
import PDFContext from './PDFContext';
9
import PDFPageLeaf from './structures/PDFPageLeaf';
54✔
10

11
/**
12
 * PDFObjectCopier copies PDFObjects from a src context to a dest context.
13
 * The primary use case for this is to copy pages between PDFs.
14
 *
15
 * _Copying_ an object with a PDFObjectCopier is different from _cloning_ an
16
 * object with its [[PDFObject.clone]] method:
17
 *
18
 * ```
19
 *   const src: PDFContext = ...
20
 *   const dest: PDFContext = ...
21
 *   const originalObject: PDFObject = ...
22
 *   const copiedObject = PDFObjectCopier.for(src, dest).copy(originalObject);
23
 *   const clonedObject = originalObject.clone();
24
 * ```
25
 *
26
 * Copying an object is equivalent to cloning it and then copying over any other
27
 * objects that it references. Note that only dictionaries, arrays, and streams
28
 * (or structures build from them) can contain indirect references to other
29
 * objects. Copying a PDFObject that is not a dictionary, array, or stream is
30
 * supported, but is equivalent to cloning it.
31
 */
32
class PDFObjectCopier {
33
  static for = (src: PDFContext, dest: PDFContext) =>
54✔
34
    new PDFObjectCopier(src, dest);
9✔
35

36
  private readonly src: PDFContext;
37
  private readonly dest: PDFContext;
38
  private readonly traversedObjects = new Map<PDFObject, PDFObject>();
9✔
39

40
  private constructor(src: PDFContext, dest: PDFContext) {
41
    this.src = src;
9✔
42
    this.dest = dest;
9✔
43
  }
44

45
  // prettier-ignore
46
  copy = <T extends PDFObject>(object: T): T => (
9✔
47
      isPDFInstance(object, PDFClasses.PDFPageLeaf) ? this.copyPDFPage(object as any as PDFPageLeaf)
1,390✔
48
    : isPDFInstance(object, PDFClasses.PDFDict)     ? this.copyPDFDict(object as any as PDFDict)
1,387✔
49
    : isPDFInstance(object, PDFClasses.PDFArray)    ? this.copyPDFArray(object as any as PDFArray)
1,326✔
50
    : isPDFInstance(object, PDFClasses.PDFStream)   ? this.copyPDFStream(object as any as PDFStream)
1,269✔
51
    : isPDFInstance(object, PDFClasses.PDFRef)      ? this.copyPDFIndirectObject(object as any as PDFRef)
1,240✔
52
    : object.clone()
53
  ) as T;
54

55
  private copyPDFPage = (originalPage: PDFPageLeaf): PDFPageLeaf => {
9✔
56
    const clonedPage = originalPage.clone();
3✔
57

58
    // Move any entries that the originalPage is inheriting from its parent
59
    // tree nodes directly into originalPage so they are preserved during
60
    // the copy.
61
    const { InheritableEntries } = PDFPageLeaf;
3✔
62
    for (let idx = 0, len = InheritableEntries.length; idx < len; idx++) {
3✔
63
      const key = PDFName.of(InheritableEntries[idx]);
12✔
64
      const value = clonedPage.getInheritableAttribute(key)!;
12✔
65
      if (!clonedPage.get(key) && value) clonedPage.set(key, value);
12✔
66
    }
67

68
    // Remove the parent reference to prevent the whole donor document's page
69
    // tree from being copied when we only need a single page.
70
    clonedPage.delete(PDFName.of('Parent'));
3✔
71

72
    return this.copyPDFDict(clonedPage) as PDFPageLeaf;
3✔
73
  };
74

75
  private copyPDFDict = (originalDict: PDFDict): PDFDict => {
9✔
76
    if (this.traversedObjects.has(originalDict)) {
64✔
77
      return this.traversedObjects.get(originalDict) as PDFDict;
1✔
78
    }
79

80
    const clonedDict = originalDict.clone(this.dest);
63✔
81
    this.traversedObjects.set(originalDict, clonedDict);
63✔
82

83
    const entries = originalDict.entries();
63✔
84

85
    for (let idx = 0, len = entries.length; idx < len; idx++) {
63✔
86
      const [key, value] = entries[idx];
333✔
87
      clonedDict.set(key, this.copy(value));
333✔
88
    }
89

90
    return clonedDict;
63✔
91
  };
92

93
  private copyPDFArray = (originalArray: PDFArray): PDFArray => {
9✔
94
    if (this.traversedObjects.has(originalArray)) {
57!
95
      return this.traversedObjects.get(originalArray) as PDFArray;
×
96
    }
97

98
    const clonedArray = originalArray.clone(this.dest);
57✔
99
    this.traversedObjects.set(originalArray, clonedArray);
57✔
100

101
    for (let idx = 0, len = originalArray.size(); idx < len; idx++) {
57✔
102
      const value = originalArray.get(idx);
819✔
103
      clonedArray.set(idx, this.copy(value));
819✔
104
    }
105

106
    return clonedArray;
57✔
107
  };
108

109
  private copyPDFStream = (originalStream: PDFStream): PDFStream => {
9✔
110
    if (this.traversedObjects.has(originalStream)) {
29!
111
      return this.traversedObjects.get(originalStream) as PDFStream;
×
112
    }
113

114
    const clonedStream = originalStream.clone(this.dest);
29✔
115
    this.traversedObjects.set(originalStream, clonedStream);
29✔
116

117
    const entries = originalStream.dict.entries();
29✔
118
    for (let idx = 0, len = entries.length; idx < len; idx++) {
29✔
119
      const [key, value] = entries[idx];
107✔
120
      clonedStream.dict.set(key, this.copy(value));
107✔
121
    }
122

123
    return clonedStream;
29✔
124
  };
125

126
  private copyPDFIndirectObject = (ref: PDFRef): PDFRef => {
9✔
127
    const alreadyMapped = this.traversedObjects.has(ref);
125✔
128

129
    if (!alreadyMapped) {
125✔
130
      const newRef = this.dest.nextRef();
112✔
131
      this.traversedObjects.set(ref, newRef);
112✔
132

133
      const dereferencedValue = this.src.lookup(ref);
112✔
134
      if (dereferencedValue) {
112✔
135
        const cloned = this.copy(dereferencedValue);
111✔
136
        this.dest.assign(newRef, cloned);
111✔
137
      }
138
    }
139

140
    return this.traversedObjects.get(ref) as PDFRef;
125✔
141
  };
142
}
143

144
export default PDFObjectCopier;
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