pdf-lite 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/.commitlintrc.cjs +25 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. package/.github/workflows/docs.yaml +93 -0
  5. package/.github/workflows/prepare-release.yaml +79 -0
  6. package/.github/workflows/release.yaml +80 -0
  7. package/.github/workflows/test.yaml +35 -0
  8. package/.husky/commit-msg +1 -0
  9. package/.husky/pre-commit +1 -0
  10. package/.prettierignore +4 -0
  11. package/.prettierrc +4 -0
  12. package/CONTRIBUTING.md +109 -0
  13. package/EXAMPLES.md +1515 -0
  14. package/LICENSE +21 -0
  15. package/README.md +285 -0
  16. package/examples/001-create-pdf.ts +112 -0
  17. package/examples/002-create-encrypted-pdf.ts +121 -0
  18. package/examples/003-sign-pdf.ts +347 -0
  19. package/examples/004-incremental-update.ts +206 -0
  20. package/examples/005-modify-acroform.ts +374 -0
  21. package/examples/006-tokeniser-example.ts +131 -0
  22. package/examples/007-decoder-example.ts +197 -0
  23. package/package.json +72 -0
  24. package/packages/pdf-lite/README.md +3 -0
  25. package/packages/pdf-lite/package.json +68 -0
  26. package/packages/pdf-lite/scripts/create-encryption-tests.sh +41 -0
  27. package/packages/pdf-lite/scripts/gen-signing-keys.sh +290 -0
  28. package/packages/pdf-lite/scripts/generate-all-signing-keys.sh +70 -0
  29. package/packages/pdf-lite/src/core/decoder.ts +454 -0
  30. package/packages/pdf-lite/src/core/generators.ts +128 -0
  31. package/packages/pdf-lite/src/core/incremental-parser.ts +221 -0
  32. package/packages/pdf-lite/src/core/index.ts +2 -0
  33. package/packages/pdf-lite/src/core/objects/pdf-array.ts +54 -0
  34. package/packages/pdf-lite/src/core/objects/pdf-boolean.ts +19 -0
  35. package/packages/pdf-lite/src/core/objects/pdf-comment.ts +50 -0
  36. package/packages/pdf-lite/src/core/objects/pdf-date.ts +74 -0
  37. package/packages/pdf-lite/src/core/objects/pdf-dictionary.ts +171 -0
  38. package/packages/pdf-lite/src/core/objects/pdf-hexadecimal.ts +54 -0
  39. package/packages/pdf-lite/src/core/objects/pdf-indirect-object.ts +137 -0
  40. package/packages/pdf-lite/src/core/objects/pdf-name.ts +19 -0
  41. package/packages/pdf-lite/src/core/objects/pdf-null.ts +15 -0
  42. package/packages/pdf-lite/src/core/objects/pdf-number.ts +98 -0
  43. package/packages/pdf-lite/src/core/objects/pdf-object-reference.ts +30 -0
  44. package/packages/pdf-lite/src/core/objects/pdf-object.ts +107 -0
  45. package/packages/pdf-lite/src/core/objects/pdf-start-xref.ts +39 -0
  46. package/packages/pdf-lite/src/core/objects/pdf-stream.ts +687 -0
  47. package/packages/pdf-lite/src/core/objects/pdf-string.ts +38 -0
  48. package/packages/pdf-lite/src/core/objects/pdf-trailer.ts +57 -0
  49. package/packages/pdf-lite/src/core/objects/pdf-xref-table.ts +264 -0
  50. package/packages/pdf-lite/src/core/parser.ts +22 -0
  51. package/packages/pdf-lite/src/core/ref.ts +102 -0
  52. package/packages/pdf-lite/src/core/serializer.ts +68 -0
  53. package/packages/pdf-lite/src/core/streams/object-stream.ts +20 -0
  54. package/packages/pdf-lite/src/core/tokeniser.ts +687 -0
  55. package/packages/pdf-lite/src/core/tokens/boolean-token.ts +20 -0
  56. package/packages/pdf-lite/src/core/tokens/byte-offset-token.ts +20 -0
  57. package/packages/pdf-lite/src/core/tokens/comment-token.ts +32 -0
  58. package/packages/pdf-lite/src/core/tokens/end-array-token.ts +10 -0
  59. package/packages/pdf-lite/src/core/tokens/end-dictionary-token.ts +10 -0
  60. package/packages/pdf-lite/src/core/tokens/end-object-token.ts +10 -0
  61. package/packages/pdf-lite/src/core/tokens/end-stream-token.ts +11 -0
  62. package/packages/pdf-lite/src/core/tokens/hexadecimal-token.ts +22 -0
  63. package/packages/pdf-lite/src/core/tokens/name-token.ts +19 -0
  64. package/packages/pdf-lite/src/core/tokens/null-token.ts +9 -0
  65. package/packages/pdf-lite/src/core/tokens/number-token.ts +164 -0
  66. package/packages/pdf-lite/src/core/tokens/object-reference-token.ts +24 -0
  67. package/packages/pdf-lite/src/core/tokens/start-array-token.ts +10 -0
  68. package/packages/pdf-lite/src/core/tokens/start-dictionary-token.ts +10 -0
  69. package/packages/pdf-lite/src/core/tokens/start-object-token.ts +28 -0
  70. package/packages/pdf-lite/src/core/tokens/start-stream-token.ts +52 -0
  71. package/packages/pdf-lite/src/core/tokens/start-xref-token.ts +10 -0
  72. package/packages/pdf-lite/src/core/tokens/stream-chunk-token.ts +8 -0
  73. package/packages/pdf-lite/src/core/tokens/string-token.ts +17 -0
  74. package/packages/pdf-lite/src/core/tokens/token.ts +43 -0
  75. package/packages/pdf-lite/src/core/tokens/trailer-token.ts +12 -0
  76. package/packages/pdf-lite/src/core/tokens/whitespace-token.ts +43 -0
  77. package/packages/pdf-lite/src/core/tokens/xref-table-entry-token.ts +65 -0
  78. package/packages/pdf-lite/src/core/tokens/xref-table-section-start-token.ts +31 -0
  79. package/packages/pdf-lite/src/core/tokens/xref-table-start-token.ts +13 -0
  80. package/packages/pdf-lite/src/crypto/ciphers/aes128.ts +63 -0
  81. package/packages/pdf-lite/src/crypto/ciphers/aes256.ts +50 -0
  82. package/packages/pdf-lite/src/crypto/ciphers/rc4.ts +82 -0
  83. package/packages/pdf-lite/src/crypto/constants.ts +10 -0
  84. package/packages/pdf-lite/src/crypto/key-derivation/key-derivation-aes256.ts +213 -0
  85. package/packages/pdf-lite/src/crypto/key-derivation/key-derivation.ts +122 -0
  86. package/packages/pdf-lite/src/crypto/key-gen/key-gen-aes256.ts +79 -0
  87. package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-128.ts +190 -0
  88. package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-40.ts +129 -0
  89. package/packages/pdf-lite/src/crypto/types.ts +6 -0
  90. package/packages/pdf-lite/src/crypto/utils.ts +81 -0
  91. package/packages/pdf-lite/src/filters/ascii85.ts +128 -0
  92. package/packages/pdf-lite/src/filters/asciihex.ts +55 -0
  93. package/packages/pdf-lite/src/filters/flate.ts +39 -0
  94. package/packages/pdf-lite/src/filters/lzw.ts +144 -0
  95. package/packages/pdf-lite/src/filters/pass-through.ts +37 -0
  96. package/packages/pdf-lite/src/filters/runlength.ts +92 -0
  97. package/packages/pdf-lite/src/filters/types.ts +21 -0
  98. package/packages/pdf-lite/src/index.ts +4 -0
  99. package/packages/pdf-lite/src/pdf/errors.ts +5 -0
  100. package/packages/pdf-lite/src/pdf/index.ts +4 -0
  101. package/packages/pdf-lite/src/pdf/pdf-document.ts +924 -0
  102. package/packages/pdf-lite/src/pdf/pdf-reader.ts +57 -0
  103. package/packages/pdf-lite/src/pdf/pdf-revision.ts +234 -0
  104. package/packages/pdf-lite/src/pdf/pdf-xref-lookup.ts +527 -0
  105. package/packages/pdf-lite/src/security/crypt-filters/aesv2.ts +58 -0
  106. package/packages/pdf-lite/src/security/crypt-filters/aesv3.ts +56 -0
  107. package/packages/pdf-lite/src/security/crypt-filters/base.ts +140 -0
  108. package/packages/pdf-lite/src/security/crypt-filters/identity.ts +40 -0
  109. package/packages/pdf-lite/src/security/crypt-filters/v2.ts +59 -0
  110. package/packages/pdf-lite/src/security/handlers/base.ts +625 -0
  111. package/packages/pdf-lite/src/security/handlers/pubSec.ts +413 -0
  112. package/packages/pdf-lite/src/security/handlers/utils.ts +304 -0
  113. package/packages/pdf-lite/src/security/handlers/v1.ts +225 -0
  114. package/packages/pdf-lite/src/security/handlers/v2.ts +128 -0
  115. package/packages/pdf-lite/src/security/handlers/v4.ts +379 -0
  116. package/packages/pdf-lite/src/security/handlers/v5.ts +298 -0
  117. package/packages/pdf-lite/src/security/types.ts +158 -0
  118. package/packages/pdf-lite/src/signing/document-security-store.ts +224 -0
  119. package/packages/pdf-lite/src/signing/index.ts +3 -0
  120. package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-detached.ts +154 -0
  121. package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-sha1.ts +161 -0
  122. package/packages/pdf-lite/src/signing/signatures/adbe-x509-rsa-sha1.ts +106 -0
  123. package/packages/pdf-lite/src/signing/signatures/base.ts +229 -0
  124. package/packages/pdf-lite/src/signing/signatures/etsi-cades-detached.ts +229 -0
  125. package/packages/pdf-lite/src/signing/signatures/etsi-rfc3161.ts +92 -0
  126. package/packages/pdf-lite/src/signing/signatures/index.ts +6 -0
  127. package/packages/pdf-lite/src/signing/signer.ts +120 -0
  128. package/packages/pdf-lite/src/signing/types.ts +86 -0
  129. package/packages/pdf-lite/src/signing/utils.ts +71 -0
  130. package/packages/pdf-lite/src/types.ts +44 -0
  131. package/packages/pdf-lite/src/utils/IterableReadableStream.ts +30 -0
  132. package/packages/pdf-lite/src/utils/algos.ts +446 -0
  133. package/packages/pdf-lite/src/utils/assert.ts +42 -0
  134. package/packages/pdf-lite/src/utils/bytesToHex.ts +18 -0
  135. package/packages/pdf-lite/src/utils/bytesToHexBytes.ts +27 -0
  136. package/packages/pdf-lite/src/utils/bytesToString.ts +17 -0
  137. package/packages/pdf-lite/src/utils/concatUint8Arrays.ts +26 -0
  138. package/packages/pdf-lite/src/utils/escapeString.ts +49 -0
  139. package/packages/pdf-lite/src/utils/hexBytesToBytes.ts +22 -0
  140. package/packages/pdf-lite/src/utils/hexBytesToString.ts +21 -0
  141. package/packages/pdf-lite/src/utils/hexToBytes.ts +18 -0
  142. package/packages/pdf-lite/src/utils/padBytes.ts +25 -0
  143. package/packages/pdf-lite/src/utils/predictors.ts +332 -0
  144. package/packages/pdf-lite/src/utils/replaceInBuffer.ts +56 -0
  145. package/packages/pdf-lite/src/utils/stringToBytes.ts +22 -0
  146. package/packages/pdf-lite/src/utils/stringToHexBytes.ts +23 -0
  147. package/packages/pdf-lite/src/utils/unescapeString.ts +123 -0
  148. package/packages/pdf-lite/test/acceptance/__snapshots__/versions.node.test.ts.snap +60766 -0
  149. package/packages/pdf-lite/test/acceptance/fixtures/1.3/basic.pdf +0 -0
  150. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-128.pdf +0 -0
  151. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-256.pdf +0 -0
  152. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-128.pdf +0 -0
  153. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-40.pdf +0 -0
  154. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic.pdf +0 -0
  155. package/packages/pdf-lite/test/acceptance/fixtures/1.5/basic.pdf +0 -0
  156. package/packages/pdf-lite/test/acceptance/fixtures/1.6/basic.pdf +0 -0
  157. package/packages/pdf-lite/test/acceptance/fixtures/1.7/basic.pdf +0 -0
  158. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-128.pdf +43 -0
  159. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-256.pdf +43 -0
  160. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-128.pdf +43 -0
  161. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-40.pdf +44 -0
  162. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic.pdf +79 -0
  163. package/packages/pdf-lite/test/acceptance/versions.node.test.ts +41 -0
  164. package/packages/pdf-lite/test/unit/__snapshots__/decoder.node.test.ts.snap +86947 -0
  165. package/packages/pdf-lite/test/unit/__snapshots__/tokeniser.node.test.ts.snap +131829 -0
  166. package/packages/pdf-lite/test/unit/ciphers.test.ts +61 -0
  167. package/packages/pdf-lite/test/unit/decoder.node.test.ts +21 -0
  168. package/packages/pdf-lite/test/unit/decoder.test.ts +567 -0
  169. package/packages/pdf-lite/test/unit/filters.test.ts +67 -0
  170. package/packages/pdf-lite/test/unit/fixtures/basic.pdf +0 -0
  171. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-128.pdf +0 -0
  172. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-256.pdf +0 -0
  173. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-128.pdf +0 -0
  174. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-40.pdf +43 -0
  175. package/packages/pdf-lite/test/unit/fixtures/protectedAdobeLivecycle.pdf +0 -0
  176. package/packages/pdf-lite/test/unit/fixtures/rsa-2048/index.ts +187 -0
  177. package/packages/pdf-lite/test/unit/fixtures/template.pdf +0 -0
  178. package/packages/pdf-lite/test/unit/incremental-update.test.ts +0 -0
  179. package/packages/pdf-lite/test/unit/objects.test.ts +0 -0
  180. package/packages/pdf-lite/test/unit/pdf-document-signing.test.ts +0 -0
  181. package/packages/pdf-lite/test/unit/pdf-revision.test.ts +195 -0
  182. package/packages/pdf-lite/test/unit/pdf.browser.test.ts +0 -0
  183. package/packages/pdf-lite/test/unit/predictors.test.ts +226 -0
  184. package/packages/pdf-lite/test/unit/ref.test.ts +158 -0
  185. package/packages/pdf-lite/test/unit/security-handlers.test.ts +645 -0
  186. package/packages/pdf-lite/test/unit/serializer.test.ts +81 -0
  187. package/packages/pdf-lite/test/unit/signature-objects.test.ts +814 -0
  188. package/packages/pdf-lite/test/unit/string-escaping.test.ts +84 -0
  189. package/packages/pdf-lite/test/unit/tokeniser.node.test.ts +38 -0
  190. package/packages/pdf-lite/test/unit/tokeniser.test.ts +1213 -0
  191. package/packages/pdf-lite/test/unit/utils.test.ts +248 -0
  192. package/packages/pdf-lite/test/unit/xref-lookup.test.ts +72 -0
  193. package/packages/pdf-lite/tsconfig.json +4 -0
  194. package/packages/pdf-lite/tsconfig.prod.json +8 -0
  195. package/packages/pdf-lite/typedoc.json +14 -0
  196. package/packages/pdf-lite/vitest.config.ts +43 -0
  197. package/pnpm-workspace.yaml +2 -0
  198. package/renovate.json +34 -0
  199. package/scripts/build-examples.ts +30 -0
  200. package/scripts/bump-version.sh +56 -0
  201. package/scripts/gen-html-docs.sh +21 -0
  202. package/scripts/gen-md-docs.sh +15 -0
  203. package/scripts/prepare-release.sh +33 -0
  204. package/tsconfig.json +22 -0
  205. package/tsconfig.prod.json +12 -0
  206. package/typedoc.json +34 -0
@@ -0,0 +1,527 @@
1
+ import { PdfObject } from '../core/objects/pdf-object'
2
+ import { PdfIndirectObject } from '../core/objects/pdf-indirect-object'
3
+ import { PdfXRefTable, PdfXRefTableEntry } from '../core/objects/pdf-xref-table'
4
+ import { PdfStartXRef } from '../core/objects/pdf-start-xref'
5
+ import { PdfTrailer, PdfTrailerEntries } from '../core/objects/pdf-trailer'
6
+ import { PdfNumber } from '../core/objects/pdf-number'
7
+ import { PdfComment } from '../core/objects/pdf-comment'
8
+ import {
9
+ PdfObjStream,
10
+ PdfStream,
11
+ PdfXRefStream,
12
+ PdfXRefStreamCompressedEntry,
13
+ PdfXRefStreamEntry,
14
+ } from '../core/objects/pdf-stream'
15
+ import { PdfDictionary } from '../core/objects/pdf-dictionary'
16
+ import { Ref } from '../core/ref'
17
+ import { PdfObjectReference } from '../core/objects/pdf-object-reference'
18
+
19
+ /**
20
+ * Manages cross-reference (xref) lookup for PDF objects.
21
+ * Handles both traditional xref tables and xref streams, including hybrid documents.
22
+ * Supports linking multiple revisions through the Prev chain.
23
+ */
24
+ export class PdfXrefLookup {
25
+ /** The underlying xref object (either a table or stream) */
26
+ object: PdfIndirectObject<PdfXRefStream> | PdfXRefTable
27
+ /** Map of object numbers to their xref entries */
28
+ entries: Map<number, PdfXRefStreamEntry>
29
+ /** Trailer dictionary containing document metadata references */
30
+ trailerDict: PdfDictionary<PdfTrailerEntries>
31
+ /** Reference to the previous xref lookup in the revision chain */
32
+ prev?: PdfXrefLookup
33
+ private type?: 'table' | 'stream'
34
+ private hybridXRefStream?: PdfIndirectObject<PdfXRefStream>
35
+
36
+ /**
37
+ * Creates a new xref lookup instance.
38
+ *
39
+ * @param options - Configuration options
40
+ * @param options.type - Type of xref ('table' or 'stream')
41
+ * @param options.object - Pre-existing xref table or stream object
42
+ * @param options.trailerDict - Pre-existing trailer dictionary
43
+ * @param options.prev - Previous xref lookup to link to
44
+ */
45
+ constructor(options?: {
46
+ type?: 'table' | 'stream'
47
+ object?: PdfIndirectObject<PdfStream> | PdfXRefTable
48
+ trailerDict?: PdfDictionary<PdfTrailerEntries>
49
+ prev?: PdfXrefLookup
50
+ }) {
51
+ this.type = options?.type
52
+ this.entries = new Map<number, PdfXRefStreamEntry>()
53
+ this.prev = options?.prev
54
+ this.trailerDict = options?.trailerDict ?? new PdfDictionary()
55
+
56
+ if (options?.object) {
57
+ if (options.object instanceof PdfXRefTable) {
58
+ this.object = options.object
59
+ } else if (options.object.content instanceof PdfXRefStream) {
60
+ this.object = options.object as PdfIndirectObject<PdfXRefStream>
61
+ } else if (options.object.content instanceof PdfStream) {
62
+ this.object = options.object as PdfIndirectObject<PdfXRefStream>
63
+ this.object.content =
64
+ options.object.content.parseAs(PdfXRefStream)
65
+ } else {
66
+ throw new Error(
67
+ 'Provided object is not a valid XRef table or stream',
68
+ )
69
+ }
70
+ } else {
71
+ this.object = this.update()
72
+ }
73
+ this.size++
74
+
75
+ if (this.object instanceof PdfIndirectObject)
76
+ this.object.orderIndex = PdfIndirectObject.MAX_ORDER_INDEX
77
+ }
78
+
79
+ /**
80
+ * Links this xref to a previous xref lookup.
81
+ * Copies missing trailer entries from the previous xref.
82
+ *
83
+ * @param xref - The previous xref lookup to link to
84
+ * @throws Error if trying to set self as previous (would create circular reference) or if offsets match (would create ambiguous or invalid xref chain)
85
+ */
86
+ setPrev(xref: PdfXrefLookup) {
87
+ if (xref === this) {
88
+ throw new Error('Cannot set XRef lookup as its own previous lookup')
89
+ }
90
+
91
+ if (xref.offset === this.offset) {
92
+ throw new Error(
93
+ 'Cannot set XRef lookup previous to another lookup with the same offset',
94
+ )
95
+ }
96
+
97
+ this.prev = xref
98
+ const prevDict = xref.trailerDict
99
+ const dict = this.trailerDict
100
+
101
+ !dict.has('Info') && dict.set('Info', prevDict.get('Info')?.clone())
102
+ !dict.has('Root') && dict.set('Root', prevDict.get('Root')?.clone())
103
+ !dict.has('Encrypt') &&
104
+ dict.set('Encrypt', prevDict.get('Encrypt')?.clone())
105
+ !dict.has('ID') && dict.set('ID', prevDict.get('ID')?.clone())
106
+ !dict.has('Prev') && dict.set('Prev', new PdfNumber(xref.offset))
107
+
108
+ const prev = dict.get('Prev')
109
+ if (prev) {
110
+ prev.isByteOffset = true
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Sets the byte offset of the xref object.
116
+ */
117
+ set offset(value: Ref<number>) {
118
+ this.object.offset = value
119
+ }
120
+
121
+ /**
122
+ * Gets the byte offset of the xref object.
123
+ */
124
+ get offset(): Ref<number> {
125
+ return this.object.offset
126
+ }
127
+
128
+ /**
129
+ * Creates xref lookups from an array of PDF objects.
130
+ * Parses both xref tables and xref streams, linking them via the Prev chain.
131
+ *
132
+ * @param objects - Array of PDF objects to parse
133
+ * @returns The most recent xref lookup with linked previous lookups
134
+ */
135
+ static fromObjects(objects: PdfObject[]): PdfXrefLookup {
136
+ const lookups: PdfXrefLookup[] = []
137
+
138
+ for (let i = 0; i < objects.length; i++) {
139
+ const obj = objects[i]
140
+ if (obj instanceof PdfXRefTable) {
141
+ const trailer = objects[i + 1]
142
+ if (!(trailer instanceof PdfTrailer)) {
143
+ throw new Error(
144
+ 'Expected PdfTrailer after PdfXRefTable in objects',
145
+ )
146
+ }
147
+ lookups.push(PdfXrefLookup.fromXrefTable(obj, trailer, objects))
148
+ } else if (
149
+ obj instanceof PdfIndirectObject &&
150
+ obj.content instanceof PdfStream &&
151
+ obj.content.isType('XRef')
152
+ ) {
153
+ lookups.push(PdfXrefLookup.fromXrefStream(obj))
154
+ }
155
+ }
156
+
157
+ if (!lookups.length) {
158
+ return new PdfXrefLookup()
159
+ }
160
+
161
+ const startXref = objects.findLast((x) => x instanceof PdfStartXRef)
162
+ if (!startXref) {
163
+ throw new Error('No PdfStartXRef found in provided objects')
164
+ }
165
+
166
+ let lookup = lookups.find((x) => x.offset.equals(startXref.offset.ref))
167
+ if (!lookup) {
168
+ return lookups[0]
169
+ }
170
+
171
+ const initialLookup = lookup
172
+ while (lookup) {
173
+ lookup.prev = lookups.find((x) =>
174
+ x.offset.equals(lookup?.trailerDict.get('Prev')?.value),
175
+ )
176
+
177
+ lookup = lookup.prev
178
+ }
179
+
180
+ return initialLookup
181
+ }
182
+
183
+ /**
184
+ * Creates an xref lookup from a traditional xref table and trailer.
185
+ * Handles hybrid xref documents with both table and stream entries.
186
+ *
187
+ * @param xrefTable - The xref table object
188
+ * @param trailer - The trailer object
189
+ * @param objects - Optional array of objects for resolving hybrid xref streams
190
+ * @returns A new PdfXrefLookup instance
191
+ */
192
+ static fromXrefTable(
193
+ xrefTable: PdfXRefTable,
194
+ trailer: PdfTrailer,
195
+ objects?: PdfObject[],
196
+ ): PdfXrefLookup {
197
+ const lookup = new PdfXrefLookup({ object: xrefTable })
198
+
199
+ lookup.trailerDict = trailer.dict
200
+
201
+ xrefTable.entries.forEach((entry) => {
202
+ lookup.entries.set(entry.objectNumber.value, entry)
203
+ })
204
+
205
+ // Handle hybrid xref: check for XRefStm in trailer
206
+ const xrefStmOffset = trailer.dict.get('XRefStm')
207
+ if (xrefStmOffset instanceof PdfNumber && objects) {
208
+ // Find the xref stream object at this offset
209
+ const xrefStreamObj = objects.find(
210
+ (obj) =>
211
+ obj instanceof PdfIndirectObject &&
212
+ obj.content instanceof PdfStream &&
213
+ obj.content.isType('XRef') &&
214
+ obj.offset.equals(xrefStmOffset.value),
215
+ ) as PdfIndirectObject<PdfStream> | undefined
216
+
217
+ if (xrefStreamObj) {
218
+ const stream = xrefStreamObj.content.parseAs(PdfXRefStream)
219
+ // Add entries from the xref stream (these are typically compressed objects)
220
+ for (const entry of stream.getEntryStream()) {
221
+ // Only add if not already present in the table
222
+ if (!lookup.entries.has(entry.objectNumber.value)) {
223
+ lookup.entries.set(entry.objectNumber.value, entry)
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ return lookup
230
+ }
231
+
232
+ /**
233
+ * Creates an xref lookup from an xref stream object.
234
+ *
235
+ * @param streamObject - The indirect object containing the xref stream
236
+ * @returns A new PdfXrefLookup instance
237
+ */
238
+ static fromXrefStream(
239
+ streamObject: PdfIndirectObject<PdfStream>,
240
+ ): PdfXrefLookup {
241
+ const stream = streamObject.content.parseAs(PdfXRefStream)
242
+ const lookup = new PdfXrefLookup({ object: streamObject })
243
+
244
+ for (const entry of stream.getEntryStream()) {
245
+ lookup.entries.set(entry.objectNumber.value, entry)
246
+ }
247
+
248
+ lookup.trailerDict = stream.header as PdfDictionary<PdfTrailerEntries>
249
+ return lookup
250
+ }
251
+
252
+ /**
253
+ * Gets the size of the xref table (highest object number + 1).
254
+ * Ensures size is at least as large as the previous revision.
255
+ */
256
+ get size(): number {
257
+ const trailerSize = this.trailerDict.get('Size')?.value ?? 0
258
+ const prevSize = this.prev?.size ?? 0
259
+
260
+ if (trailerSize < prevSize) {
261
+ this.trailerDict.set('Size', new PdfNumber(prevSize))
262
+ }
263
+
264
+ const size = Math.max(trailerSize, prevSize)
265
+ this.trailerDict.set('Size', new PdfNumber(size))
266
+
267
+ return size
268
+ }
269
+
270
+ /**
271
+ * Sets the size of the xref table.
272
+ */
273
+ set size(value: number) {
274
+ this.trailerDict.set('Size', new PdfNumber(value))
275
+ }
276
+
277
+ /**
278
+ * Gets all xref entries as an array.
279
+ */
280
+ get entriesValues(): PdfXRefStreamEntry[] {
281
+ return Array.from(this.entries.values())
282
+ }
283
+
284
+ /**
285
+ * Links xref entries to their corresponding indirect objects.
286
+ * Updates byte offset references to point to actual object offsets.
287
+ *
288
+ * @param objects - Array of indirect objects to link
289
+ */
290
+ linkIndirectObjects(objects: PdfIndirectObject[]): void {
291
+ for (const entry of this.entriesValues) {
292
+ if (entry instanceof PdfXRefStreamCompressedEntry) {
293
+ continue
294
+ }
295
+
296
+ if (!entry.inUse) {
297
+ continue
298
+ }
299
+
300
+ const [matchedObject] = objects.filter((obj) =>
301
+ obj.offset.equals(entry.byteOffset.value),
302
+ )
303
+
304
+ if (
305
+ !matchedObject ||
306
+ matchedObject.objectNumber !== entry.objectNumber.value
307
+ ) {
308
+ continue
309
+ }
310
+
311
+ entry.byteOffset.ref.update(matchedObject.offset)
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Links this xref to a previous xref lookup based on the Prev trailer entry.
317
+ *
318
+ * @param objects - Array of xref lookups to search for the previous one
319
+ */
320
+ linkPrev(objects: PdfXrefLookup[]): void {
321
+ const prevOffset = this.trailerDict.get('Prev')?.value
322
+ if (prevOffset === undefined) {
323
+ return
324
+ }
325
+
326
+ const prevLookup = objects.filter((obj) =>
327
+ obj.offset.equals(prevOffset),
328
+ )
329
+
330
+ if (prevLookup.length === 0) {
331
+ return
332
+ } else if (prevLookup.length > 1) {
333
+ return
334
+ }
335
+
336
+ if (prevLookup[0].offset.equals(0)) return
337
+
338
+ this.setPrev(prevLookup[0])
339
+ }
340
+
341
+ /**
342
+ * Updates the xref object with current entries.
343
+ * Handles both table and stream formats, including hybrid documents.
344
+ *
345
+ * @returns The updated xref object
346
+ */
347
+ update(): PdfIndirectObject<PdfXRefStream> | PdfXRefTable {
348
+ if (this.object instanceof PdfXRefTable) {
349
+ const tableEntries = this.entriesValues.filter(
350
+ (entry) => entry instanceof PdfXRefTableEntry,
351
+ )
352
+ const compressedEntries = this.entriesValues.filter(
353
+ (entry) => entry instanceof PdfXRefStreamCompressedEntry,
354
+ )
355
+
356
+ this.object.entries = tableEntries
357
+
358
+ // If there are compressed entries, create a hybrid xref with an XRefStm
359
+ if (compressedEntries.length > 0) {
360
+ this.hybridXRefStream = new PdfIndirectObject({
361
+ content: PdfXRefStream.fromEntries(compressedEntries),
362
+ })
363
+ // Store reference to the xref stream offset
364
+ this.trailerDict.set(
365
+ 'XRefStm',
366
+ new PdfNumber(this.hybridXRefStream.offset),
367
+ )
368
+ } else {
369
+ // Remove XRefStm if no compressed entries exist
370
+ this.trailerDict.delete('XRefStm')
371
+ this.hybridXRefStream = undefined
372
+ }
373
+
374
+ return this.object
375
+ } else if (this.object instanceof PdfIndirectObject) {
376
+ this.object.content = PdfXRefStream.fromEntries(
377
+ this.entriesValues,
378
+ this.trailerDict,
379
+ )
380
+ return this.object
381
+ }
382
+
383
+ let newObject: PdfIndirectObject<PdfXRefStream> | PdfXRefTable
384
+ if (this.type === 'table') {
385
+ newObject = new PdfXRefTable()
386
+ } else {
387
+ newObject = new PdfIndirectObject({
388
+ content: PdfXRefStream.fromEntries(
389
+ this.entriesValues,
390
+ this.trailerDict,
391
+ ),
392
+ })
393
+ }
394
+ this.object = newObject
395
+
396
+ return this.object
397
+ }
398
+
399
+ /**
400
+ * Adds an indirect object to the xref lookup.
401
+ * Assigns an object number if not already set.
402
+ *
403
+ * @param newObject - The indirect object to add
404
+ * @param options - Options for compressed objects
405
+ * @param options.parentObjectNumber - Object number of the containing object stream
406
+ * @param options.indexInStream - Index within the object stream
407
+ * @throws Error if trying to add compressed object with non-zero generation number
408
+ */
409
+ addObject(
410
+ newObject: PdfIndirectObject,
411
+ options?: {
412
+ parentObjectNumber?: number
413
+ indexInStream?: number
414
+ },
415
+ ): void {
416
+ if (!newObject.inPdf()) {
417
+ newObject.objectNumber = this.size++
418
+ }
419
+
420
+ if (
421
+ options?.indexInStream !== undefined &&
422
+ options?.parentObjectNumber !== undefined
423
+ ) {
424
+ if (newObject.generationNumber !== 0) {
425
+ throw new Error(
426
+ 'Object streams cannot contain objects with generation number other than 0',
427
+ )
428
+ }
429
+ this.entries.set(
430
+ newObject.objectNumber,
431
+ new PdfXRefStreamCompressedEntry({
432
+ objectNumber: newObject.objectNumber,
433
+ objectStreamNumber: options.parentObjectNumber,
434
+ index: options.indexInStream,
435
+ }),
436
+ )
437
+ } else {
438
+ this.entries.set(
439
+ newObject.objectNumber,
440
+ new PdfXRefTableEntry({
441
+ objectNumber: newObject.objectNumber,
442
+ generationNumber: newObject.generationNumber,
443
+ byteOffset: newObject.offset,
444
+ inUse: true,
445
+ }),
446
+ )
447
+ }
448
+
449
+ this.update()
450
+
451
+ if (newObject.content instanceof PdfObjStream) {
452
+ let index = 0
453
+ for (const child of newObject.content.getObjectStream()) {
454
+ this.addObject(child, {
455
+ parentObjectNumber: newObject.objectNumber,
456
+ indexInStream: index++,
457
+ })
458
+ }
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Removes an indirect object from the xref lookup.
464
+ * Also removes any trailer references to the object.
465
+ *
466
+ * @param object - The indirect object to remove
467
+ */
468
+ removeObject(object: PdfIndirectObject): void {
469
+ this.entries.delete(object.objectNumber)
470
+
471
+ const trailerValues = this.trailerDict.values
472
+ for (const entry of Object.keys(
473
+ trailerValues,
474
+ ) as (keyof PdfTrailerEntries)[]) {
475
+ const value = trailerValues[entry]
476
+ if (
477
+ value instanceof PdfObjectReference &&
478
+ value.equals(object.reference)
479
+ ) {
480
+ this.trailerDict.delete(entry)
481
+ }
482
+ }
483
+
484
+ this.update()
485
+ }
486
+
487
+ /**
488
+ * Gets an xref entry by object number.
489
+ * Falls back to the previous xref if not found in current entries.
490
+ *
491
+ * @param objectNumber - The object number to look up
492
+ * @returns The xref entry or undefined if not found
493
+ */
494
+ getObject(objectNumber: number): PdfXRefStreamEntry | undefined {
495
+ if (!this.entries.has(objectNumber) && this.prev) {
496
+ return this.prev.getObject(objectNumber)
497
+ }
498
+
499
+ return this.entries.get(objectNumber)
500
+ }
501
+
502
+ /**
503
+ * Generates the trailer section objects for this xref.
504
+ * Includes xref table/stream, trailer (if using table), startxref, and EOF.
505
+ *
506
+ * @returns Array of objects forming the trailer section
507
+ */
508
+ toTrailerSection(): PdfObject[] {
509
+ const objects: PdfObject[] = []
510
+
511
+ // If this is a hybrid xref table with compressed entries, add the xref stream first
512
+ if (this.object instanceof PdfXRefTable && this.hybridXRefStream) {
513
+ objects.push(this.hybridXRefStream)
514
+ }
515
+
516
+ objects.push(this.object)
517
+
518
+ if (this.object instanceof PdfXRefTable) {
519
+ objects.push(new PdfTrailer(this.trailerDict))
520
+ }
521
+
522
+ objects.push(new PdfStartXRef(this.object.offset))
523
+ objects.push(PdfComment.EOF)
524
+
525
+ return objects
526
+ }
527
+ }
@@ -0,0 +1,58 @@
1
+ import { PdfCryptFilter, CryptFilterOptions } from './base'
2
+ import { aes128 } from '../../crypto/ciphers/aes128'
3
+ import { Cipher } from '../../crypto/types'
4
+
5
+ /**
6
+ * AESV2 crypt filter using AES-128-CBC encryption.
7
+ * Implements 128-bit AES encryption in CBC mode for PDF content.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const filter = new AesV2CryptFilter({
12
+ * authEvent: 'DocOpen',
13
+ * securityHandler
14
+ * })
15
+ * const encrypted = await filter.encrypt(data, objectNumber, generationNumber)
16
+ * ```
17
+ */
18
+ export class AesV2CryptFilter extends PdfCryptFilter {
19
+ /**
20
+ * Creates a new AES-128 crypt filter.
21
+ *
22
+ * @param options - Configuration options with authentication event and security handler.
23
+ */
24
+ constructor(options: {
25
+ authEvent: CryptFilterOptions['authEvent']
26
+ securityHandler?: CryptFilterOptions['securityHandler']
27
+ }) {
28
+ super({ ...options, cfm: 'AESV2', length: 128 })
29
+ }
30
+
31
+ /**
32
+ * Gets an AES-128 cipher for encryption/decryption.
33
+ *
34
+ * @param objectNumber - The PDF object number for key derivation.
35
+ * @param generationNumber - The PDF generation number for key derivation.
36
+ * @returns An AES-128 cipher instance.
37
+ * @throws Error if security handler is not set.
38
+ */
39
+ async getCipher(
40
+ objectNumber?: number,
41
+ generationNumber?: number,
42
+ ): Promise<Cipher> {
43
+ const securityHandler = this.securityHandler
44
+ if (!securityHandler) {
45
+ throw new Error('Missing security handler for AESV2 crypt filter')
46
+ }
47
+
48
+ const objectKey = await securityHandler.computeObjectKey(
49
+ objectNumber,
50
+ generationNumber,
51
+ 'AES-128-CBC',
52
+ )
53
+
54
+ return aes128(
55
+ objectKey.slice(0, 16), // AES-128 needs a 16-byte key
56
+ )
57
+ }
58
+ }
@@ -0,0 +1,56 @@
1
+ import { aes256 } from '../../crypto/ciphers/aes256'
2
+ import { Cipher } from '../../crypto/types'
3
+ import { PdfCryptFilter, CryptFilterOptions } from './base'
4
+
5
+ /**
6
+ * AESV3 crypt filter using AES-256-CBC encryption.
7
+ * Implements 256-bit AES encryption in CBC mode for PDF content.
8
+ * This is the recommended encryption method for new documents.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const filter = new AesV3CryptFilter({
13
+ * authEvent: 'DocOpen',
14
+ * securityHandler
15
+ * })
16
+ * const encrypted = await filter.encrypt(data, objectNumber, generationNumber)
17
+ * ```
18
+ */
19
+ export class AesV3CryptFilter extends PdfCryptFilter {
20
+ /**
21
+ * Creates a new AES-256 crypt filter.
22
+ *
23
+ * @param options - Configuration options with authentication event and security handler.
24
+ */
25
+ constructor(options: {
26
+ authEvent: CryptFilterOptions['authEvent']
27
+ securityHandler?: CryptFilterOptions['securityHandler']
28
+ }) {
29
+ super({ ...options, cfm: 'AESV3', length: 256 })
30
+ }
31
+
32
+ /**
33
+ * Gets an AES-256 cipher for encryption/decryption.
34
+ *
35
+ * @param objectNumber - The PDF object number (unused for AESV3).
36
+ * @param generationNumber - The PDF generation number (unused for AESV3).
37
+ * @returns An AES-256 cipher instance.
38
+ * @throws Error if security handler is not set.
39
+ */
40
+ async getCipher(
41
+ objectNumber?: number,
42
+ generationNumber?: number,
43
+ ): Promise<Cipher> {
44
+ const securityHandler = this.securityHandler
45
+ if (!securityHandler) {
46
+ throw new Error('Missing security handler for AESV3 crypt filter')
47
+ }
48
+
49
+ const objectKey = await securityHandler.computeObjectKey(
50
+ objectNumber,
51
+ generationNumber,
52
+ )
53
+
54
+ return aes256(objectKey)
55
+ }
56
+ }