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,687 @@
1
+ import { PdfToken } from '../tokens/token'
2
+ import { PdfEndStreamToken } from '../tokens/end-stream-token'
3
+ import { PdfStartStreamToken } from '../tokens/start-stream-token'
4
+ import { PdfStreamChunkToken } from '../tokens/stream-chunk-token'
5
+ import { PdfWhitespaceToken } from '../tokens/whitespace-token'
6
+ import { PdfDictionary } from './pdf-dictionary'
7
+ import { PdfObject } from './pdf-object'
8
+ import { PdfName } from './pdf-name'
9
+ import { PdfArray } from './pdf-array'
10
+ import { flate } from '../../filters/flate'
11
+ import { asciiHex } from '../../filters/asciihex'
12
+ import { ascii85 } from '../../filters/ascii85'
13
+ import { lzw } from '../../filters/lzw'
14
+ import { runLength } from '../../filters/runlength'
15
+ import { passthroughFilter } from '../../filters/pass-through'
16
+ import { ByteArray } from '../../types'
17
+ import { PdfNumber } from './pdf-number'
18
+ import { Predictor } from '../../utils/predictors'
19
+ import { bytesToPdfObjects } from '../generators'
20
+ import { PdfIndirectObject } from './pdf-indirect-object'
21
+ import { PdfXRefTableEntry } from './pdf-xref-table'
22
+ import { stringToBytes } from '../../utils/stringToBytes'
23
+ import { PdfFilter, PdfStreamFilterType } from '../../filters/types'
24
+
25
+ export class PdfStream<
26
+ T extends PdfDictionary = PdfDictionary,
27
+ > extends PdfObject {
28
+ header: T
29
+ original: ByteArray
30
+ preStreamDataTokens?: PdfToken[]
31
+ postStreamDataTokens?: PdfToken[]
32
+
33
+ constructor(
34
+ options:
35
+ | { header: T; original: ByteArray | string; isModified?: boolean }
36
+ | ByteArray
37
+ | string,
38
+ ) {
39
+ super()
40
+
41
+ if (typeof options === 'string' || options instanceof Uint8Array) {
42
+ options = { header: new PdfDictionary() as T, original: options }
43
+ }
44
+
45
+ this.header = options.header
46
+ this.modified = options.isModified ?? true
47
+ this.original =
48
+ typeof options.original === 'string'
49
+ ? stringToBytes(options.original)
50
+ : options.original
51
+ if (!this.header.get('Length')) {
52
+ this.header.set('Length', new PdfNumber(this.original.length))
53
+ }
54
+ }
55
+
56
+ get raw(): ByteArray {
57
+ const length = this.header.get('Length')?.as(PdfNumber)?.value
58
+ if (length === undefined) return this.original
59
+
60
+ return this.original.slice(0, length)
61
+ }
62
+
63
+ set raw(data: ByteArray) {
64
+ this.setModified()
65
+ this.original = data
66
+ this.header.set('Length', new PdfNumber(data.length))
67
+ }
68
+
69
+ get originalAsString(): string {
70
+ return new TextDecoder().decode(this.original)
71
+ }
72
+
73
+ get rawAsString(): string {
74
+ return new TextDecoder().decode(this.raw)
75
+ }
76
+
77
+ getFilters(): PdfStreamFilterType[] {
78
+ const filters = this.header.get('Filter') as
79
+ | PdfArray<PdfName<PdfStreamFilterType>>
80
+ | PdfName<PdfStreamFilterType>
81
+ | undefined
82
+ if (!filters) {
83
+ return []
84
+ }
85
+
86
+ if (filters instanceof PdfName) {
87
+ return [filters.value]
88
+ } else if (filters instanceof PdfArray) {
89
+ return filters.items.map((item) => item.value)
90
+ } else {
91
+ throw new Error('Invalid Filter entry in PDF stream')
92
+ }
93
+ }
94
+
95
+ addFilter(filterName: PdfStreamFilterType) {
96
+ const filters = this.header.get('Filter')
97
+
98
+ if (!filters) {
99
+ this.header.set('Filter', new PdfName(filterName))
100
+ } else if (filters instanceof PdfName) {
101
+ this.header.set(
102
+ 'Filter',
103
+ new PdfArray([new PdfName(filterName), filters]),
104
+ )
105
+ } else if (filters instanceof PdfArray) {
106
+ filters.items.unshift(new PdfName(filterName))
107
+ } else {
108
+ throw new Error('Invalid Filter entry in PDF stream')
109
+ }
110
+
111
+ const filter = PdfStream.getFilter(filterName)
112
+ this.raw = filter.encode(this.raw)
113
+ return this
114
+ }
115
+
116
+ setPredictor(predictorParams: {
117
+ Predictor?: number
118
+ Columns?: number
119
+ Colors?: number
120
+ BitsPerComponent?: number
121
+ }) {
122
+ let decodeParms = this.header.get('DecodeParms')?.as(PdfDictionary)
123
+
124
+ if (!decodeParms) {
125
+ decodeParms = new PdfDictionary()
126
+ this.header.set('DecodeParms', decodeParms)
127
+ } else if (decodeParms instanceof PdfDictionary) {
128
+ // already a dictionary
129
+ } else {
130
+ throw new Error('Invalid DecodeParms entry in PDF stream')
131
+ }
132
+
133
+ for (const [key, value] of Object.entries(predictorParams)) {
134
+ decodeParms.set(key, new PdfNumber(value))
135
+ }
136
+
137
+ this.raw = Predictor.encode(this.raw, predictorParams)
138
+
139
+ return this
140
+ }
141
+
142
+ removeFilter(filterName: PdfStreamFilterType) {
143
+ let filters = this.header.get('Filter')
144
+ if (!filters) {
145
+ return this
146
+ }
147
+
148
+ const decoded = this.decode()
149
+
150
+ if (filters instanceof PdfName) {
151
+ if (filters.value === filterName) {
152
+ this.header.delete('Filter')
153
+ }
154
+ } else if (filters instanceof PdfArray) {
155
+ filters.items = filters.items.filter(
156
+ (item) => item.value !== filterName,
157
+ )
158
+ if (filters.items.length === 0) {
159
+ this.header.delete('Filter')
160
+ } else if (filters.items.length === 1) {
161
+ this.header.set('Filter', filters.items[0])
162
+ }
163
+ } else {
164
+ throw new Error('Invalid Filter entry in PDF stream')
165
+ }
166
+
167
+ const finalFilters = this.getFilters()
168
+ const encoded = PdfStream.applyFilters(decoded, finalFilters)
169
+ this.raw = encoded
170
+ return this
171
+ }
172
+
173
+ removePredictor() {
174
+ const decoded = this.decode()
175
+ this.header.delete('DecodeParms')
176
+ this.raw = decoded
177
+ return this
178
+ }
179
+
180
+ removeAllFilters() {
181
+ const decoded = this.decode()
182
+ this.raw = decoded
183
+ this.header.delete('Filter')
184
+ return this
185
+ }
186
+
187
+ decode(): ByteArray {
188
+ let data: ByteArray = this.raw
189
+ const filters = this.getFilters()
190
+
191
+ for (const filterName of filters) {
192
+ const filter = PdfStream.getFilter(filterName)
193
+ data = filter.decode(data)
194
+ }
195
+
196
+ const predictorParams = Predictor.getDecodeParms(
197
+ this.header.get('DecodeParms')?.as(PdfDictionary),
198
+ )
199
+
200
+ if (predictorParams) {
201
+ data = Predictor.decode(data, predictorParams)
202
+ }
203
+
204
+ return data
205
+ }
206
+
207
+ parseAs<T extends PdfStream>(
208
+ Class: new (options: {
209
+ header: PdfDictionary
210
+ original: ByteArray
211
+ isModified?: boolean
212
+ }) => T,
213
+ ): T {
214
+ const instance = new Class({
215
+ header: this.header,
216
+ original: this.original,
217
+ isModified: this.isModified(),
218
+ })
219
+ instance.preTokens = this.preTokens
220
+ instance.postTokens = this.postTokens
221
+ instance.preStreamDataTokens = this.preStreamDataTokens
222
+ instance.postStreamDataTokens = this.postStreamDataTokens
223
+
224
+ return instance
225
+ }
226
+
227
+ protected tokenize() {
228
+ return [
229
+ ...this.header.toTokens(),
230
+ PdfStartStreamToken.withTrailingWhitespace(
231
+ this.preStreamDataTokens,
232
+ ),
233
+ new PdfStreamChunkToken(this.original),
234
+ new PdfEndStreamToken(),
235
+ ]
236
+ }
237
+
238
+ isType(name: string): boolean {
239
+ const type = this.header.get('Type')
240
+ return type instanceof PdfName && type.value === name
241
+ }
242
+
243
+ static getFilter(name: PdfStreamFilterType): PdfFilter {
244
+ const allFilters = PdfStream.getAllFilters()
245
+ if (!allFilters[name]) {
246
+ throw new Error(`Unsupported filter: ${name}`)
247
+ }
248
+
249
+ return allFilters[name]
250
+ }
251
+
252
+ static getAllFilters() {
253
+ return {
254
+ FlateDecode: flate(),
255
+ Fl: flate(),
256
+ ASCIIHexDecode: asciiHex(),
257
+ ASCII85Decode: ascii85(),
258
+ LZWDecode: lzw(),
259
+ RunLengthDecode: runLength(),
260
+ CCITTFaxDecode: passthroughFilter(),
261
+ DCTDecode: passthroughFilter(),
262
+ JPXDecode: passthroughFilter(),
263
+ Crypt: passthroughFilter(),
264
+ } as const satisfies {
265
+ [key in PdfStreamFilterType]: PdfFilter
266
+ }
267
+ }
268
+
269
+ static applyFilters(
270
+ data: ByteArray,
271
+ filters: PdfStreamFilterType[],
272
+ ): ByteArray {
273
+ let result = data
274
+ for (const filterName of filters) {
275
+ const filter = PdfStream.getFilter(filterName)
276
+ if (!filter) {
277
+ throw new Error(`Unsupported filter: ${filterName}`)
278
+ }
279
+ result = filter.encode(result)
280
+ }
281
+ return result
282
+ }
283
+
284
+ clone(): this {
285
+ return new PdfStream({
286
+ header: this.header.clone(),
287
+ original: new Uint8Array(this.original),
288
+ }) as this
289
+ }
290
+ }
291
+
292
+ export class PdfObjStream extends PdfStream {
293
+ constructor(options: {
294
+ header: PdfDictionary
295
+ original: ByteArray | string
296
+ isModified?: boolean
297
+ }) {
298
+ super(options)
299
+
300
+ if (!this.isType('ObjStm')) {
301
+ throw new Error('PDF Object Stream must be of type ObjStm')
302
+ }
303
+ }
304
+
305
+ static fromObjects(objects: Iterable<PdfIndirectObject>): PdfObjStream {
306
+ let header = ''
307
+ let objectData = ''
308
+
309
+ let offset = 0
310
+
311
+ for (const obj of objects) {
312
+ header += `${obj.objectNumber} ${offset} `
313
+ const objString = obj.content.toString()
314
+ objectData += objString + '\n'
315
+ offset += objString.length + 1
316
+ }
317
+
318
+ const headerDict = new PdfDictionary()
319
+ headerDict.set('Type', new PdfName('ObjStm'))
320
+ headerDict.set('N', new PdfNumber(header.length))
321
+ headerDict.set('First', new PdfNumber(offset))
322
+
323
+ objectData = `${header.trim()}\n${objectData.trim()}`
324
+
325
+ return new PdfObjStream({
326
+ header: headerDict,
327
+ original: objectData,
328
+ })
329
+ }
330
+
331
+ *getObjectStream(): Generator<PdfIndirectObject> {
332
+ const decodedData = this.decode()
333
+ const reader = bytesToPdfObjects([decodedData])
334
+ const numbers: PdfNumber[] = []
335
+ let i = 0
336
+
337
+ while (true) {
338
+ const { value: obj, done } = reader.next()
339
+ if (done) break
340
+
341
+ if (obj instanceof PdfDictionary) {
342
+ const objectNumber = numbers[i * 2].value
343
+ const generationNumber = 0
344
+
345
+ yield new PdfIndirectObject({
346
+ objectNumber,
347
+ generationNumber,
348
+ content: obj,
349
+ })
350
+
351
+ i++
352
+ } else if (obj instanceof PdfNumber) {
353
+ numbers.push(obj)
354
+ } else {
355
+ throw new Error('Invalid object in PDF Object Stream')
356
+ }
357
+ }
358
+ }
359
+
360
+ getObject(options: {
361
+ objectNumber: number
362
+ }): PdfIndirectObject | undefined {
363
+ for (const obj of this.getObjectStream()) {
364
+ if (obj.objectNumber === options.objectNumber) {
365
+ return obj
366
+ }
367
+ }
368
+ return undefined
369
+ }
370
+
371
+ getObjects(): PdfIndirectObject[] {
372
+ return Array.from(this.getObjectStream())
373
+ }
374
+
375
+ clone(): this {
376
+ return new PdfObjStream({
377
+ header: this.header.clone(),
378
+ original: new Uint8Array(this.original),
379
+ }) as this
380
+ }
381
+ }
382
+
383
+ export class PdfXRefStreamCompressedEntry {
384
+ objectNumber: PdfNumber
385
+ objectStreamNumber: PdfNumber
386
+ index: PdfNumber
387
+
388
+ constructor(options: {
389
+ objectNumber: number | PdfNumber
390
+ objectStreamNumber: number | PdfNumber
391
+ index: number | PdfNumber
392
+ }) {
393
+ this.objectNumber =
394
+ options.objectNumber instanceof PdfNumber
395
+ ? options.objectNumber
396
+ : new PdfNumber(options.objectNumber)
397
+ this.objectStreamNumber =
398
+ options.objectStreamNumber instanceof PdfNumber
399
+ ? options.objectStreamNumber
400
+ : new PdfNumber(options.objectStreamNumber)
401
+ this.index =
402
+ options.index instanceof PdfNumber
403
+ ? options.index
404
+ : new PdfNumber(options.index)
405
+ }
406
+ }
407
+
408
+ export type PdfXRefStreamStandardEntry = PdfXRefTableEntry
409
+ export const PdfXRefStreamStandardEntry = PdfXRefTableEntry
410
+
411
+ export type PdfXRefStreamEntry =
412
+ | PdfXRefStreamStandardEntry
413
+ | PdfXRefStreamCompressedEntry
414
+
415
+ export class PdfXRefStream extends PdfStream {
416
+ constructor(options?: {
417
+ header?: PdfDictionary
418
+ original?: ByteArray | string
419
+ isModified?: boolean
420
+ }) {
421
+ super({
422
+ header: options?.header ?? PdfXRefStream.createNewHeader(),
423
+ original: options?.original ?? new Uint8Array(),
424
+ isModified: options?.isModified,
425
+ })
426
+
427
+ if (!this.isType('XRef')) {
428
+ throw new Error('PDF XRef Stream must be of type XRef')
429
+ }
430
+ }
431
+
432
+ static createNewHeader(): PdfDictionary {
433
+ const headerDict = new PdfDictionary()
434
+ headerDict.set('Type', new PdfName('XRef'))
435
+ headerDict.set(
436
+ 'W',
437
+ new PdfArray([
438
+ new PdfNumber(1),
439
+ new PdfNumber(4),
440
+ new PdfNumber(2),
441
+ ]),
442
+ )
443
+ headerDict.set('Size', new PdfNumber(0))
444
+
445
+ return headerDict
446
+ }
447
+
448
+ static fromEntries(
449
+ entries: PdfXRefStreamEntry[],
450
+ headerDict: PdfDictionary = new PdfDictionary(),
451
+ ): PdfXRefStream {
452
+ headerDict.delete('DecodeParms')
453
+ headerDict.delete('Filter')
454
+ headerDict.set('Type', new PdfName('XRef'))
455
+ entries.sort((a, b) => a.objectNumber.value - b.objectNumber.value)
456
+
457
+ const W = this.calculateW(entries)
458
+ headerDict.set('W', new PdfArray(W.map((w) => new PdfNumber(w))))
459
+
460
+ // Build Index array - pairs of [start, count] for each contiguous range
461
+ const indexArray: number[] = []
462
+ if (entries.length > 0) {
463
+ let rangeStart = entries[0].objectNumber.value
464
+ let rangeCount = 1
465
+
466
+ for (let i = 1; i < entries.length; i++) {
467
+ const currentNum = entries[i].objectNumber.value
468
+ const prevNum = entries[i - 1].objectNumber.value
469
+
470
+ if (currentNum === prevNum + 1) {
471
+ // Contiguous - extend current range
472
+ rangeCount++
473
+ } else {
474
+ // Gap found - save current range and start new one
475
+ indexArray.push(rangeStart, rangeCount)
476
+ rangeStart = currentNum
477
+ rangeCount = 1
478
+ }
479
+ }
480
+
481
+ // Add final range
482
+ indexArray.push(rangeStart, rangeCount)
483
+ }
484
+
485
+ headerDict.set(
486
+ 'Index',
487
+ new PdfArray(indexArray.map((num) => new PdfNumber(num))),
488
+ )
489
+
490
+ const size =
491
+ entries.length === 0
492
+ ? 0
493
+ : Math.max(
494
+ ...entries.map((entry) => entry.objectNumber.value),
495
+ ) + 1
496
+ headerDict.set('Size', new PdfNumber(size))
497
+
498
+ // Encode entries to bytes
499
+ const entrySize = W.reduce((a, b) => a + b, 0)
500
+ const streamBytes = new Uint8Array(entries.length * entrySize)
501
+ let offset = 0
502
+
503
+ for (const entry of entries) {
504
+ const typeByte =
505
+ entry instanceof PdfXRefTableEntry ? (entry.inUse ? 1 : 0) : 2
506
+
507
+ let field1 = typeByte
508
+ let field2 = 0
509
+ let field3 = 0
510
+
511
+ if (entry instanceof PdfXRefTableEntry) {
512
+ field2 = entry.byteOffset.value
513
+ field3 = entry.generationNumber.value
514
+ } else if (entry instanceof PdfXRefStreamCompressedEntry) {
515
+ field2 = entry.objectStreamNumber.value
516
+ field3 = entry.index.value
517
+ }
518
+
519
+ for (let i = 0; i < W[0]; i++) {
520
+ streamBytes[offset++] = (field1 >> (8 * (W[0] - 1 - i))) & 0xff
521
+ }
522
+ for (let i = 0; i < W[1]; i++) {
523
+ streamBytes[offset++] = (field2 >> (8 * (W[1] - 1 - i))) & 0xff
524
+ }
525
+ for (let i = 0; i < W[2]; i++) {
526
+ streamBytes[offset++] = (field3 >> (8 * (W[2] - 1 - i))) & 0xff
527
+ }
528
+ }
529
+
530
+ const rawData = new Uint8Array(streamBytes)
531
+ headerDict.set('Length', new PdfNumber(rawData.length))
532
+
533
+ return new PdfXRefStream({
534
+ header: headerDict,
535
+ original: rawData,
536
+ })
537
+ }
538
+
539
+ addEntry(entry: PdfXRefStreamEntry) {
540
+ const entries = this.getEntries()
541
+ entries.push(entry)
542
+
543
+ const newXRefStream = PdfXRefStream.fromEntries(entries)
544
+
545
+ this.header = newXRefStream.header
546
+ this.raw = newXRefStream.raw
547
+
548
+ return this
549
+ }
550
+
551
+ private static calculateW(entries: PdfXRefStreamEntry[]): number[] {
552
+ let maxGeneration = 0
553
+
554
+ const maxOffset = Math.max(
555
+ ...entries.map((entry) => {
556
+ let value = 0
557
+
558
+ if (entry instanceof PdfXRefTableEntry) {
559
+ value = entry.byteOffset.value
560
+ maxGeneration = Math.max(
561
+ maxGeneration,
562
+ entry.generationNumber.value,
563
+ )
564
+ } else if (entry instanceof PdfXRefStreamCompressedEntry) {
565
+ value = entry.objectStreamNumber.value
566
+ }
567
+
568
+ return value
569
+ }),
570
+ )
571
+
572
+ const W1 = 1 // Type byte
573
+ const W2 =
574
+ maxOffset === -1 ? 0 : Math.ceil(Math.log2(maxOffset + 1) / 8) || 1 // Offset or next free
575
+ const W3 = Math.ceil(Math.log2(maxGeneration + 1) / 8) || 1 // Generation number or index
576
+
577
+ return [W1, W2, W3]
578
+ }
579
+
580
+ get prev(): PdfNumber | undefined {
581
+ const prev = this.header.get('Prev')
582
+ return prev instanceof PdfNumber ? prev : undefined
583
+ }
584
+
585
+ *getEntryStream(): Generator<PdfXRefStreamEntry> {
586
+ const data = this.decode()
587
+
588
+ const header = this.header
589
+ const W = header
590
+ .get('W')
591
+ ?.as(PdfArray<PdfNumber>)
592
+ ?.items?.map((v: any) => v.value)
593
+
594
+ if (!W) throw new Error('Missing W entry in XRef stream')
595
+
596
+ if (W.length !== 3) {
597
+ throw new Error(
598
+ 'Invalid W entry in XRef stream. Expected array of 3 numbers.',
599
+ )
600
+ }
601
+
602
+ const entrySize = W[0] + W[1] + W[2]
603
+
604
+ const Index = header
605
+ .get('Index')
606
+ ?.as(PdfArray<PdfNumber>)
607
+ ?.items?.map((v) => v.value)
608
+
609
+ const Size = header.get('Size')!.as(PdfNumber).value
610
+
611
+ const objectRanges: [number, number][] = Index
612
+ ? Array.from({ length: Index.length / 2 }, (_, i) => [
613
+ Index[i * 2],
614
+ Index[i * 2 + 1],
615
+ ])
616
+ : [[0, Size]]
617
+
618
+ const expectedLength =
619
+ objectRanges.reduce((sum, [, count]) => sum + count, 0) * entrySize
620
+ if (data.length < expectedLength) {
621
+ throw new Error(
622
+ `XRef stream too short: expected ${expectedLength} bytes, got ${data.length}`,
623
+ )
624
+ }
625
+
626
+ let offset = 0
627
+
628
+ for (const [startObj, count] of objectRanges) {
629
+ for (let i = 0; i < count; i++) {
630
+ const objectNumber = startObj + i
631
+
632
+ const type = this.readInt(data, offset, W[0])
633
+ offset += W[0]
634
+ const field2 = this.readInt(data, offset, W[1])
635
+ offset += W[1]
636
+ const field3 = this.readInt(data, offset, W[2])
637
+ offset += W[2]
638
+
639
+ let entry: PdfXRefStreamEntry
640
+ if (type === 0) {
641
+ entry = new PdfXRefTableEntry({
642
+ objectNumber,
643
+ generationNumber: field3,
644
+ byteOffset: field2,
645
+ inUse: false,
646
+ })
647
+ } else if (type === 1) {
648
+ entry = new PdfXRefTableEntry({
649
+ objectNumber,
650
+ generationNumber: field3,
651
+ byteOffset: field2,
652
+ inUse: true,
653
+ })
654
+ } else if (type === 2) {
655
+ entry = new PdfXRefStreamCompressedEntry({
656
+ objectNumber,
657
+ objectStreamNumber: field2,
658
+ index: field3,
659
+ })
660
+ } else {
661
+ throw new Error(`Unknown xref entry type: ${type}`)
662
+ }
663
+
664
+ yield entry
665
+ }
666
+ }
667
+ }
668
+
669
+ getEntries(): PdfXRefStreamEntry[] {
670
+ return Array.from(this.getEntryStream())
671
+ }
672
+
673
+ private readInt(data: ByteArray, offset: number, length: number): number {
674
+ let value = 0
675
+ for (let i = 0; i < length; i++) {
676
+ value = (value << 8) | data[offset + i]
677
+ }
678
+ return value
679
+ }
680
+
681
+ clone(): this {
682
+ return new PdfXRefStream({
683
+ header: this.header.clone(),
684
+ original: new Uint8Array(this.original),
685
+ }) as this
686
+ }
687
+ }