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,25 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ /**
4
+ * Pads a byte array to a specified length with trailing zeros.
5
+ *
6
+ * @param bytes - The byte array to pad.
7
+ * @param length - The target length for the padded array.
8
+ * @returns A new byte array padded to the specified length.
9
+ * @throws Error if the input array is already longer than the target length.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * padBytes(new Uint8Array([1, 2]), 4) // Returns Uint8Array([1, 2, 0, 0])
14
+ * ```
15
+ */
16
+ export function padBytes(bytes: ByteArray, length: number): ByteArray {
17
+ if (bytes.length > length) {
18
+ throw new Error(
19
+ `Cannot pad bytes: current length ${bytes.length} is greater than or equal to target length ${length}.`,
20
+ )
21
+ }
22
+ const padded = new Uint8Array(length)
23
+ padded.set(bytes)
24
+ return padded
25
+ }
@@ -0,0 +1,332 @@
1
+ import { PdfDictionary } from '../core/objects/pdf-dictionary.js'
2
+ import { PdfNumber } from '../core/objects/pdf-number.js'
3
+ import { PdfObject } from '../core/objects/pdf-object.js'
4
+ import { ByteArray, DecodeParms } from '../types.js'
5
+
6
+ /**
7
+ * Handles PNG and TIFF predictor encoding and decoding for PDF streams.
8
+ * Predictors are used to improve compression efficiency by transforming
9
+ * image data before or after compression.
10
+ */
11
+ export class Predictor {
12
+ /**
13
+ * Decodes data that was encoded with a predictor.
14
+ *
15
+ * @param data - The encoded data to decode.
16
+ * @param params - Optional decode parameters including Predictor, Columns, Colors, and BitsPerComponent.
17
+ * @returns The decoded byte array.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const decoded = Predictor.decode(encodedData, { Predictor: 12, Columns: 100 })
22
+ * ```
23
+ */
24
+ static decode(data: ByteArray, params: DecodeParms = {}): ByteArray {
25
+ const predictor = params.Predictor ?? 1
26
+ const columns = params.Columns ?? 1
27
+ const colors = params.Colors ?? 1
28
+ const bpc = params.BitsPerComponent ?? 8
29
+
30
+ const bpp = Math.ceil((colors * bpc) / 8)
31
+
32
+ if (predictor === 2) {
33
+ return this.tiffDecode(data, columns, bpp)
34
+ } else if (predictor >= 10 && predictor <= 15) {
35
+ return this.pngDecode(data, columns, bpp)
36
+ }
37
+
38
+ return data
39
+ }
40
+
41
+ /**
42
+ * Encodes data using a predictor algorithm.
43
+ *
44
+ * @param data - The data to encode.
45
+ * @param params - Optional encode parameters including Predictor, Columns, Colors, and BitsPerComponent.
46
+ * @param PdfStreamFilterType - The PNG filter type to use for encoding.
47
+ * @returns The encoded byte array.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const encoded = Predictor.encode(rawData, { Predictor: 12, Columns: 100 }, 1)
52
+ * ```
53
+ */
54
+ static encode(
55
+ data: ByteArray,
56
+ params: DecodeParms = {},
57
+ PdfStreamFilterType: number = 0,
58
+ ): ByteArray {
59
+ const predictor = params.Predictor ?? 1
60
+ const columns = params.Columns ?? 1
61
+ const colors = params.Colors ?? 1
62
+ const bpc = params.BitsPerComponent ?? 8
63
+
64
+ const bpp = Math.ceil((colors * bpc) / 8)
65
+
66
+ if (predictor === 2) {
67
+ return this.tiffEncode(data, columns, bpp)
68
+ } else if (predictor >= 10 && predictor <= 15) {
69
+ return this.pngEncode(data, columns, bpp, PdfStreamFilterType)
70
+ }
71
+
72
+ return data
73
+ }
74
+
75
+ /**
76
+ * Decodes TIFF predictor encoded data.
77
+ *
78
+ * @param data - The TIFF encoded data.
79
+ * @param columns - The number of columns in the image.
80
+ * @param bpp - Bytes per pixel.
81
+ * @returns The decoded byte array.
82
+ */
83
+ static tiffDecode(
84
+ data: ByteArray,
85
+ columns: number,
86
+ bpp: number,
87
+ ): ByteArray {
88
+ const rowLength = columns * bpp
89
+ const output = new Uint8Array(data.length)
90
+
91
+ for (let i = 0; i < data.length; i += rowLength) {
92
+ for (let j = 0; j < rowLength; j++) {
93
+ output[i + j] =
94
+ j < bpp
95
+ ? data[i + j]
96
+ : (data[i + j] + output[i + j - bpp]) & 0xff
97
+ }
98
+ }
99
+
100
+ return output
101
+ }
102
+
103
+ /**
104
+ * Encodes data using TIFF predictor.
105
+ *
106
+ * @param data - The data to encode.
107
+ * @param columns - The number of columns in the image.
108
+ * @param bpp - Bytes per pixel.
109
+ * @returns The TIFF encoded byte array.
110
+ */
111
+ static tiffEncode(
112
+ data: ByteArray,
113
+ columns: number,
114
+ bpp: number,
115
+ ): ByteArray {
116
+ const rowLength = columns * bpp
117
+ const output = new Uint8Array(data.length)
118
+
119
+ for (let i = 0; i < data.length; i += rowLength) {
120
+ for (let j = 0; j < rowLength; j++) {
121
+ output[i + j] =
122
+ j < bpp
123
+ ? data[i + j]
124
+ : (data[i + j] - data[i + j - bpp] + 256) & 0xff
125
+ }
126
+ }
127
+
128
+ return output
129
+ }
130
+
131
+ /**
132
+ * Decodes PNG predictor encoded data.
133
+ *
134
+ * @param data - The PNG encoded data.
135
+ * @param columns - The number of columns in the image.
136
+ * @param bpp - Bytes per pixel.
137
+ * @returns The decoded byte array.
138
+ * @throws Error if an unsupported PNG filter type is encountered.
139
+ */
140
+ static pngDecode(data: ByteArray, columns: number, bpp: number): ByteArray {
141
+ const rowLength = columns * bpp
142
+ const output: number[] = []
143
+
144
+ for (let i = 0; i < data.length; ) {
145
+ const filter = data[i]
146
+ const row = data.slice(i + 1, i + 1 + rowLength)
147
+ const prior = output.slice(output.length - rowLength, output.length)
148
+
149
+ const decodedRow = new Uint8Array(rowLength)
150
+ for (let j = 0; j < rowLength; j++) {
151
+ const left = j >= bpp ? decodedRow[j - bpp] : 0
152
+ const up = prior[j] ?? 0
153
+ const upperLeft = j >= bpp ? (prior[j - bpp] ?? 0) : 0
154
+
155
+ switch (filter) {
156
+ case 0:
157
+ decodedRow[j] = row[j]
158
+ break
159
+ case 1:
160
+ decodedRow[j] = (row[j] + left) & 0xff
161
+ break
162
+ case 2:
163
+ decodedRow[j] = (row[j] + up) & 0xff
164
+ break
165
+ case 3:
166
+ decodedRow[j] =
167
+ (row[j] + Math.floor((left + up) / 2)) & 0xff
168
+ break
169
+ case 4:
170
+ decodedRow[j] =
171
+ (row[j] +
172
+ this.paethPredictor(left, up, upperLeft)) &
173
+ 0xff
174
+ break
175
+ default:
176
+ throw new Error(`Unsupported PNG filter: ${filter}`)
177
+ }
178
+ }
179
+
180
+ output.push(...decodedRow)
181
+ i += 1 + rowLength
182
+ }
183
+
184
+ return new Uint8Array(output)
185
+ }
186
+
187
+ /**
188
+ * Encodes data using PNG predictor.
189
+ *
190
+ * @param data - The data to encode.
191
+ * @param columns - The number of columns in the image.
192
+ * @param bpp - Bytes per pixel.
193
+ * @param PdfStreamFilterType - The PNG filter type (0-4) to use.
194
+ * @returns The PNG encoded byte array.
195
+ * @throws Error if an unsupported PNG filter type is specified.
196
+ */
197
+ static pngEncode(
198
+ data: ByteArray,
199
+ columns: number,
200
+ bpp: number,
201
+ PdfStreamFilterType: number,
202
+ ): ByteArray {
203
+ const rowLength = columns * bpp
204
+ const output: number[] = []
205
+ let prior = new Uint8Array(rowLength)
206
+
207
+ for (let i = 0; i < data.length; i += rowLength) {
208
+ const row = data.slice(i, i + rowLength)
209
+ const encodedRow = new Uint8Array(rowLength)
210
+
211
+ for (let j = 0; j < rowLength; j++) {
212
+ const left = j >= bpp ? row[j - bpp] : 0
213
+ const up = prior[j] ?? 0
214
+ const upperLeft = j >= bpp ? (prior[j - bpp] ?? 0) : 0
215
+
216
+ switch (PdfStreamFilterType) {
217
+ case 0:
218
+ encodedRow[j] = row[j]
219
+ break
220
+ case 1:
221
+ encodedRow[j] = (row[j] - left + 256) & 0xff
222
+ break
223
+ case 2:
224
+ encodedRow[j] = (row[j] - up + 256) & 0xff
225
+ break
226
+ case 3:
227
+ encodedRow[j] =
228
+ (row[j] - Math.floor((left + up) / 2) + 256) & 0xff
229
+ break
230
+ case 4:
231
+ encodedRow[j] =
232
+ (row[j] -
233
+ this.paethPredictor(left, up, upperLeft) +
234
+ 256) &
235
+ 0xff
236
+ break
237
+ default:
238
+ throw new Error(
239
+ `Unsupported PNG filter: ${PdfStreamFilterType}`,
240
+ )
241
+ }
242
+ }
243
+
244
+ output.push(PdfStreamFilterType, ...encodedRow)
245
+ prior = row
246
+ }
247
+
248
+ return new Uint8Array(output)
249
+ }
250
+
251
+ /**
252
+ * Implements the Paeth predictor algorithm used in PNG filtering.
253
+ *
254
+ * @param a - The left pixel value.
255
+ * @param b - The above pixel value.
256
+ * @param c - The upper-left pixel value.
257
+ * @returns The predicted pixel value.
258
+ */
259
+ private static paethPredictor(a: number, b: number, c: number): number {
260
+ const p = a + b - c
261
+ const pa = Math.abs(p - a)
262
+ const pb = Math.abs(p - b)
263
+ const pc = Math.abs(p - c)
264
+
265
+ if (pa <= pb && pa <= pc) return a
266
+ if (pb <= pc) return b
267
+ return c
268
+ }
269
+
270
+ /**
271
+ * Extracts decode parameters from a PDF dictionary.
272
+ *
273
+ * @param decodeParms - Optional PDF dictionary containing decode parameters.
274
+ * @returns The decode parameters object or null if not applicable.
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * const params = Predictor.getDecodeParms(dictionary)
279
+ * if (params) {
280
+ * console.log(params.Predictor)
281
+ * }
282
+ * ```
283
+ */
284
+ static getDecodeParms(decodeParms?: PdfDictionary): DecodeParms | null {
285
+ if (!decodeParms) {
286
+ return null
287
+ }
288
+
289
+ const predictor = decodeParms.get('Predictor')?.as(PdfNumber)?.value
290
+
291
+ if (predictor === undefined) {
292
+ return null
293
+ }
294
+
295
+ if (predictor <= 1 || predictor > 15) {
296
+ return null
297
+ }
298
+
299
+ const BitsPerComponent = decodeParms
300
+ .get('BitsPerComponent')
301
+ ?.as(PdfNumber)
302
+ const Columns = decodeParms.get('Columns')?.as(PdfNumber)
303
+ const Colors = decodeParms.get('Colors')?.as(PdfNumber)
304
+
305
+ return {
306
+ BitsPerComponent: BitsPerComponent?.value,
307
+ Columns: Columns?.value,
308
+ Predictor: predictor,
309
+ Colors: Colors?.value,
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Checks if the decode parameters can be handled by this predictor.
315
+ *
316
+ * @param decodeParms - Optional PDF dictionary containing decode parameters.
317
+ * @returns True if the parameters can be handled, false otherwise.
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * if (Predictor.canHandleDecodeParms(dictionary)) {
322
+ * // Process with predictor
323
+ * }
324
+ * ```
325
+ */
326
+ static canHandleDecodeParms(decodeParms?: PdfDictionary): boolean {
327
+ if (Predictor.getDecodeParms(decodeParms) === null) {
328
+ return false
329
+ }
330
+ return true
331
+ }
332
+ }
@@ -0,0 +1,56 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ /**
4
+ * Replaces occurrences of a search buffer with a replacement buffer within a target buffer.
5
+ *
6
+ * @typeParam T - The type of the target buffer, extending ByteArray.
7
+ * @param searchBuffer - The byte pattern to search for.
8
+ * @param replaceBuffer - The byte pattern to replace with.
9
+ * @param targetBuffer - The buffer to search within.
10
+ * @param multiple - Whether to replace all occurrences or just the first. Defaults to false.
11
+ * @returns A new byte array with the replacements made.
12
+ * @throws Error if the search buffer is not found in the target buffer.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const result = replaceInBuffer(
17
+ * new Uint8Array([1, 2]),
18
+ * new Uint8Array([3, 4, 5]),
19
+ * new Uint8Array([0, 1, 2, 6])
20
+ * ) // Returns Uint8Array([0, 3, 4, 5, 6])
21
+ * ```
22
+ */
23
+ export function replaceInBuffer<T extends ByteArray>(
24
+ searchBuffer: ByteArray,
25
+ replaceBuffer: ByteArray,
26
+ targetBuffer: T,
27
+ multiple: boolean = false,
28
+ ): ByteArray {
29
+ const searchLength = searchBuffer.length
30
+ const replaceLength = replaceBuffer.length
31
+ const targetLength = targetBuffer.length
32
+ const result = new Uint8Array(targetLength - searchLength + replaceLength)
33
+ let found = false
34
+ let offset = 0
35
+ for (let i = 0; i < targetLength; i++) {
36
+ if (
37
+ (multiple || !found) &&
38
+ i <= targetLength - searchLength &&
39
+ targetBuffer
40
+ .subarray(i, i + searchLength)
41
+ .every((value, index) => value === searchBuffer[index])
42
+ ) {
43
+ result.set(replaceBuffer, offset)
44
+ offset += replaceLength
45
+ i += searchLength - 1 // Skip the length of the search buffer
46
+ found = true
47
+ } else {
48
+ result[offset++] = targetBuffer[i]
49
+ }
50
+ }
51
+
52
+ if (!found) {
53
+ throw new Error(`Search buffer not found in target buffer`)
54
+ }
55
+ return result
56
+ }
@@ -0,0 +1,22 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ /**
4
+ * Converts a string or byte array to a byte array using UTF-8 encoding.
5
+ * If the input is already a byte array, it is returned as-is.
6
+ *
7
+ * @param str - The string or byte array to convert.
8
+ * @returns The input as a byte array.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * stringToBytes('Hello') // Returns Uint8Array([72, 101, 108, 108, 111])
13
+ * ```
14
+ */
15
+ export function stringToBytes(str: string | ByteArray): ByteArray {
16
+ if (typeof str === 'string') {
17
+ const encoder = new TextEncoder()
18
+ return encoder.encode(str) as ByteArray
19
+ } else {
20
+ return str
21
+ }
22
+ }
@@ -0,0 +1,23 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ /**
4
+ * Converts a hexadecimal string (optionally with angle brackets) to a byte array.
5
+ * Strips any surrounding angle brackets before conversion.
6
+ *
7
+ * @param str - The hexadecimal string to convert, optionally wrapped in angle brackets.
8
+ * @returns A byte array with the decoded values.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * stringToHexBytes('<FF00>') // Returns Uint8Array([255, 0])
13
+ * stringToHexBytes('FF00') // Also returns Uint8Array([255, 0])
14
+ * ```
15
+ */
16
+ export function stringToHexBytes(str: string): ByteArray {
17
+ const hex = str.replace(/<|>/g, '')
18
+ const bytes = new Uint8Array(hex.length / 2)
19
+ for (let i = 0; i < hex.length; i += 2) {
20
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16)
21
+ }
22
+ return bytes
23
+ }
@@ -0,0 +1,123 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ /**
4
+ * Unescapes a PDF literal string by processing escape sequences.
5
+ * Handles escape sequences for special characters including newlines, tabs,
6
+ * backslashes, parentheses, and octal character codes.
7
+ *
8
+ * @param input - The escaped byte array to unescape.
9
+ * @returns A new byte array with escape sequences converted to their literal values.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // Unescapes '\n' to a literal newline
14
+ * unescapeString(new Uint8Array([0x5c, 0x6e])) // Returns Uint8Array([0x0a])
15
+ * ```
16
+ */
17
+ export function unescapeString(input: ByteArray): ByteArray {
18
+ const buffer = input
19
+ const bytes: number[] = []
20
+ let offset = 0
21
+ let nesting = 1
22
+ let inEscape = false
23
+
24
+ const ByteMap = {
25
+ LEFT_PARENTHESIS: 0x28,
26
+ RIGHT_PARENTHESIS: 0x29,
27
+ BACKSLASH: 0x5c,
28
+ n: 0x6e,
29
+ r: 0x72,
30
+ t: 0x74,
31
+ b: 0x62,
32
+ f: 0x66,
33
+ }
34
+
35
+ function isOctet(byte: number): boolean {
36
+ return byte >= 0x30 && byte <= 0x37 // 0-7
37
+ }
38
+
39
+ while (inEscape || nesting > 0) {
40
+ if (offset >= buffer.length) {
41
+ break
42
+ }
43
+
44
+ if (buffer[offset] === ByteMap.LEFT_PARENTHESIS) {
45
+ nesting++
46
+ } else if (buffer[offset] === ByteMap.RIGHT_PARENTHESIS) {
47
+ nesting--
48
+ if (nesting === 0) {
49
+ offset++
50
+ break
51
+ }
52
+ } else if (buffer[offset] === ByteMap.BACKSLASH || inEscape) {
53
+ inEscape = true
54
+ const next = buffer[offset + 1]
55
+
56
+ if (next === undefined) {
57
+ break
58
+ }
59
+
60
+ switch (next) {
61
+ case ByteMap.n:
62
+ bytes.push(0x0a)
63
+ break // \n
64
+ case ByteMap.r:
65
+ bytes.push(0x0d)
66
+ break // \r
67
+ case ByteMap.t:
68
+ bytes.push(0x09)
69
+ break // \t
70
+ case ByteMap.b:
71
+ bytes.push(0x08)
72
+ break // \b
73
+ case ByteMap.f:
74
+ bytes.push(0x0c)
75
+ break // \f
76
+ case ByteMap.LEFT_PARENTHESIS:
77
+ bytes.push(ByteMap.LEFT_PARENTHESIS)
78
+ break // \(
79
+ case ByteMap.RIGHT_PARENTHESIS:
80
+ bytes.push(ByteMap.RIGHT_PARENTHESIS)
81
+ break // \)
82
+ case ByteMap.BACKSLASH:
83
+ bytes.push(ByteMap.BACKSLASH)
84
+ break // \\
85
+ case 0x0a:
86
+ case 0x0d:
87
+ // Ignore line breaks in the string after a backslash
88
+ break
89
+ default:
90
+ if (isOctet(next)) {
91
+ let octal = String.fromCharCode(next)
92
+ // Octal: up to 3 digits
93
+ const next2 = buffer[offset + 2]
94
+ if (next2 !== undefined && isOctet(next2)) {
95
+ octal += String.fromCharCode(next2)
96
+ const next3 = buffer[offset + 3]
97
+ if (next3 !== undefined && isOctet(next3)) {
98
+ octal += String.fromCharCode(next3)
99
+ }
100
+ }
101
+
102
+ bytes.push(parseInt(octal, 8))
103
+ offset += octal.length + 1 // Adjust offset for the number of octal digits
104
+ inEscape = false
105
+ continue
106
+ } else {
107
+ // If it's not a valid escape sequence, just add the next byte
108
+ bytes.push(next)
109
+ }
110
+ break
111
+ }
112
+
113
+ offset += 2 // Skip the escape character and the next character
114
+ inEscape = false
115
+ continue
116
+ }
117
+
118
+ bytes.push(buffer[offset])
119
+ offset++
120
+ }
121
+
122
+ return new Uint8Array(bytes)
123
+ }