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,221 @@
1
+ import { Parser } from './parser'
2
+
3
+ /**
4
+ * Error thrown when the parser needs more input to continue parsing.
5
+ */
6
+ export class NoMoreTokensError extends Error {}
7
+
8
+ /**
9
+ * Error thrown when the end of file has been reached and no more input is available.
10
+ */
11
+ export class EofReachedError extends Error {}
12
+
13
+ /**
14
+ * Abstract base class for incremental parsers that can process input as it becomes available.
15
+ * Supports buffering, lookahead, and backtracking for complex parsing scenarios.
16
+ *
17
+ * @typeParam I - The input item type
18
+ * @typeParam O - The output item type
19
+ */
20
+ export abstract class IncrementalParser<I, O> extends Parser<I, O> {
21
+ /** Current position in the input stream */
22
+ protected inputOffset: number = 0
23
+ /** Number of outputs generated */
24
+ protected outputOffset: number = 0
25
+
26
+ /** Buffer holding input items */
27
+ protected buffer: I[] = []
28
+ /** Current position in the buffer */
29
+ protected bufferIndex: number = 0
30
+
31
+ /** Whether end of file has been signaled */
32
+ eof: boolean = false
33
+
34
+ /**
35
+ * Feeds input items into the parser buffer.
36
+ *
37
+ * @param input - Input items to add to the buffer
38
+ */
39
+ feed(...input: (I | I[])[]): void {
40
+ for (const item of input) {
41
+ if (Array.isArray(item)) {
42
+ for (const subItem of item) {
43
+ this.buffer.push(subItem)
44
+ }
45
+
46
+ continue
47
+ }
48
+
49
+ this.buffer.push(item)
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Checks if end of file has been reached and buffer is exhausted.
55
+ *
56
+ * @returns True if no more input is available
57
+ */
58
+ protected atEof(): boolean {
59
+ return this.eof && this.bufferIndex >= this.buffer.length
60
+ }
61
+
62
+ /**
63
+ * Peeks at an item in the buffer without consuming it.
64
+ *
65
+ * @param ahead - Number of positions to look ahead (default: 0)
66
+ * @returns The item at the peek position, or null if at EOF
67
+ * @throws NoMoreTokensError if more input is needed and EOF not signaled
68
+ */
69
+ protected peek(ahead: number = 0): I | null {
70
+ const index = this.bufferIndex + ahead
71
+ if (index >= this.buffer.length) {
72
+ if (!this.eof) {
73
+ throw new NoMoreTokensError('No more items available')
74
+ }
75
+ return null
76
+ }
77
+ return this.buffer[index]
78
+ }
79
+
80
+ /**
81
+ * Consumes and returns the next item from the buffer.
82
+ *
83
+ * @returns The next item
84
+ * @throws NoMoreTokensError if more input is needed and EOF not signaled
85
+ * @throws EofReachedError if at EOF and no more items available
86
+ */
87
+ protected next(): I {
88
+ if (this.bufferIndex >= this.buffer.length) {
89
+ if (!this.eof) {
90
+ throw new NoMoreTokensError('No more items available')
91
+ }
92
+ throw new EofReachedError('End of file reached')
93
+ }
94
+ this.inputOffset++
95
+ return this.buffer[this.bufferIndex++]
96
+ }
97
+
98
+ private expectValue<T extends I>(
99
+ value: I,
100
+ itemType: (new (...args: any[]) => T) | T,
101
+ ): T {
102
+ if (typeof itemType === 'function') {
103
+ if (!(value instanceof itemType)) {
104
+ throw new Error(
105
+ `[offset: ${this.inputOffset}] Expected ${itemType.name} but got ${value}`,
106
+ )
107
+ }
108
+ return value as T
109
+ }
110
+
111
+ if (value !== itemType) {
112
+ const type =
113
+ typeof value === 'object'
114
+ ? (value?.constructor.name ?? value)
115
+ : value
116
+
117
+ throw new Error(
118
+ `[offset: ${this.inputOffset}] Expected ${itemType} but got ${type}`,
119
+ )
120
+ }
121
+ return value as T
122
+ }
123
+
124
+ /**
125
+ * Consumes and validates the next item against an expected type or value.
126
+ *
127
+ * @typeParam T - The expected item type
128
+ * @param itemType - Constructor or value to match against
129
+ * @returns The consumed item cast to the expected type
130
+ * @throws Error if the item doesn't match the expected type/value
131
+ */
132
+ protected expect<T extends I>(
133
+ itemType: (new (...args: any[]) => T) | T,
134
+ ): T {
135
+ const item = this.next()
136
+ return this.expectValue(item, itemType)
137
+ }
138
+
139
+ /**
140
+ * Tries multiple parsing functions and returns the first successful result.
141
+ * Automatically backtracks on failure.
142
+ *
143
+ * @param fn - Array of parsing functions to try
144
+ * @returns The result from the first successful parsing function
145
+ * @throws NoMoreTokensError if any function needs more input
146
+ * @throws Error if all parsing functions fail
147
+ */
148
+ protected oneOf(...fn: (() => O)[]): O {
149
+ for (const f of fn) {
150
+ const savedOffset = this.inputOffset
151
+ const savedIndex = this.bufferIndex
152
+
153
+ try {
154
+ return f.bind(this)()
155
+ } catch (err) {
156
+ if (err instanceof NoMoreTokensError) {
157
+ throw err
158
+ }
159
+ this.inputOffset = savedOffset
160
+ this.bufferIndex = savedIndex
161
+ }
162
+ }
163
+
164
+ throw new Error('No parsing function succeeded')
165
+ }
166
+
167
+ /**
168
+ * Compacts the buffer by removing consumed items
169
+ */
170
+ protected compact(): void {
171
+ if (this.bufferIndex > 0) {
172
+ this.buffer = this.buffer.slice(this.bufferIndex)
173
+ this.bufferIndex = 0
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Override to customize when to compact the buffer
179
+ * By default, compacts when more than 1000 items have been consumed
180
+ *
181
+ * @returns boolean indicating whether to compact the buffer
182
+ */
183
+ protected canCompact(): boolean {
184
+ return this.bufferIndex > 1000
185
+ }
186
+
187
+ /**
188
+ * Generates parsed output items from the buffer.
189
+ * Handles backtracking when more input is needed.
190
+ *
191
+ * @returns A generator yielding parsed output items
192
+ */
193
+ *nextItems(): Generator<O> {
194
+ while (!this.atEof()) {
195
+ const savedIndex = this.bufferIndex
196
+
197
+ try {
198
+ yield this.parse()
199
+ this.outputOffset++
200
+
201
+ if (this.canCompact()) {
202
+ this.compact()
203
+ }
204
+ } catch (err) {
205
+ if (!(err instanceof NoMoreTokensError)) {
206
+ throw err
207
+ }
208
+ this.bufferIndex = savedIndex
209
+ break
210
+ }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Abstract method to parse the next output item from the buffer.
216
+ * Subclasses must implement this to define parsing logic.
217
+ *
218
+ * @returns The parsed output item
219
+ */
220
+ protected abstract parse(): O
221
+ }
@@ -0,0 +1,2 @@
1
+ export * from './decoder.js'
2
+ export * from './tokeniser.js'
@@ -0,0 +1,54 @@
1
+ import { PdfEndArrayToken } from '../tokens/end-array-token'
2
+ import { PdfStartArrayToken } from '../tokens/start-array-token'
3
+ import { PdfWhitespaceToken } from '../tokens/whitespace-token'
4
+ import { PdfObject } from './pdf-object'
5
+
6
+ export class PdfArray<T extends PdfObject = PdfObject> extends PdfObject {
7
+ items: T[]
8
+ innerTokens: PdfWhitespaceToken[] = []
9
+
10
+ constructor(items: T[] = []) {
11
+ super()
12
+ this.items = items
13
+ }
14
+
15
+ get length(): number {
16
+ return this.items.length
17
+ }
18
+
19
+ push(item: T) {
20
+ this.items.push(item)
21
+ }
22
+
23
+ protected tokenize() {
24
+ const items = this.items.flatMap((item, index) => {
25
+ const tokens = item.toTokens()
26
+
27
+ const preTokens = item.preTokens
28
+ ? []
29
+ : index === 0
30
+ ? [PdfWhitespaceToken.SPACE]
31
+ : []
32
+ const postTokens = item.postTokens ? [] : [PdfWhitespaceToken.SPACE]
33
+
34
+ return [...preTokens, ...tokens, ...postTokens]
35
+ })
36
+
37
+ return [
38
+ new PdfStartArrayToken(),
39
+ ...this.innerTokens,
40
+ ...items,
41
+ new PdfEndArrayToken(),
42
+ ]
43
+ }
44
+
45
+ clone(): this {
46
+ return new PdfArray(this.items.map((x) => x.clone())) as this
47
+ }
48
+
49
+ isModified(): boolean {
50
+ return (
51
+ super.isModified() || this.items.some((item) => item.isModified())
52
+ )
53
+ }
54
+ }
@@ -0,0 +1,19 @@
1
+ import { PdfBooleanToken } from '../tokens/boolean-token'
2
+ import { PdfObject } from './pdf-object'
3
+
4
+ export class PdfBoolean extends PdfObject {
5
+ value: boolean
6
+
7
+ constructor(value: boolean) {
8
+ super()
9
+ this.value = value
10
+ }
11
+
12
+ protected tokenize() {
13
+ return [new PdfBooleanToken(this.value)]
14
+ }
15
+
16
+ clone(): this {
17
+ return new PdfBoolean(this.value) as this
18
+ }
19
+ }
@@ -0,0 +1,50 @@
1
+ import { ByteArray } from '../../types'
2
+ import { stringToBytes } from '../../utils/stringToBytes'
3
+ import { PdfCommentToken } from '../tokens/comment-token'
4
+ import { PdfWhitespaceToken } from '../tokens/whitespace-token'
5
+ import { PdfObject } from './pdf-object'
6
+
7
+ export class PdfComment extends PdfObject {
8
+ static EOF = new PdfComment('%EOF')
9
+
10
+ /**
11
+ * The comment value as raw bytes.
12
+ */
13
+ raw: ByteArray
14
+
15
+ constructor(raw: string | ByteArray) {
16
+ super()
17
+ this.raw = typeof raw === 'string' ? stringToBytes(raw) : raw
18
+ }
19
+
20
+ isEof(): boolean {
21
+ return this.equals(PdfComment.EOF)
22
+ }
23
+
24
+ asString() {
25
+ return new TextDecoder().decode(this.raw)
26
+ }
27
+
28
+ isVersionComment(): boolean {
29
+ const str = this.asString().toUpperCase()
30
+ return str.startsWith('PDF-')
31
+ }
32
+
33
+ isEOFComment() {
34
+ return this.equals(PdfComment.EOF)
35
+ }
36
+
37
+ static versionComment(version: string): PdfComment {
38
+ const comment = new PdfComment(`PDF-${version}`)
39
+ comment.postTokens = [PdfWhitespaceToken.NEWLINE]
40
+ return comment
41
+ }
42
+
43
+ protected tokenize() {
44
+ return [new PdfCommentToken(this.raw)]
45
+ }
46
+
47
+ clone(): this {
48
+ return new PdfComment(new Uint8Array(this.raw)) as this
49
+ }
50
+ }
@@ -0,0 +1,74 @@
1
+ import { PdfString } from './pdf-string'
2
+
3
+ export class PdfDate extends PdfString {
4
+ constructor(date: Date) {
5
+ const pad = (n: number, width: number = 2): string =>
6
+ n.toString().padStart(width, '0')
7
+
8
+ const year = date.getFullYear()
9
+ const month = pad(date.getMonth() + 1)
10
+ const day = pad(date.getDate())
11
+ const hours = pad(date.getHours())
12
+ const minutes = pad(date.getMinutes())
13
+ const seconds = pad(date.getSeconds())
14
+
15
+ const tzOffsetMinutes = -date.getTimezoneOffset() // local to UTC
16
+ const sign = tzOffsetMinutes >= 0 ? '+' : '-'
17
+ const offsetHours = pad(Math.floor(Math.abs(tzOffsetMinutes) / 60))
18
+ const offsetMins = pad(Math.abs(tzOffsetMinutes) % 60)
19
+
20
+ const dateString = `D:${year}${month}${day}${hours}${minutes}${seconds}${sign}${offsetHours}'${offsetMins}'`
21
+ super(dateString)
22
+ }
23
+
24
+ get date(): Date {
25
+ const str = new TextDecoder().decode(this.raw)
26
+ const match =
27
+ /^D:(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})([+\-Z])?(\d{2})?'?(\d{2})?'?/.exec(
28
+ str,
29
+ )
30
+ if (!match) {
31
+ throw new Error(`Invalid PDF date string: ${str}`)
32
+ }
33
+
34
+ const [
35
+ ,
36
+ year,
37
+ month,
38
+ day,
39
+ hours,
40
+ minutes,
41
+ seconds,
42
+ tzSign,
43
+ tzHours,
44
+ tzMinutes,
45
+ ] = match
46
+
47
+ let date = new Date(
48
+ Date.UTC(
49
+ parseInt(year),
50
+ parseInt(month) - 1,
51
+ parseInt(day),
52
+ parseInt(hours),
53
+ parseInt(minutes),
54
+ parseInt(seconds),
55
+ ),
56
+ )
57
+
58
+ if (tzSign && tzSign !== 'Z') {
59
+ const offsetHours = parseInt(tzHours)
60
+ const offsetMinutes = parseInt(tzMinutes)
61
+ const totalOffsetMinutes =
62
+ offsetHours * 60 + (isNaN(offsetMinutes) ? 0 : offsetMinutes)
63
+ const offsetMillis = totalOffsetMinutes * 60 * 1000
64
+
65
+ if (tzSign === '+') {
66
+ date = new Date(date.getTime() - offsetMillis)
67
+ } else if (tzSign === '-') {
68
+ date = new Date(date.getTime() + offsetMillis)
69
+ }
70
+ }
71
+
72
+ return date
73
+ }
74
+ }
@@ -0,0 +1,171 @@
1
+ import { PdfEndDictionaryToken } from '../tokens/end-dictionary-token'
2
+ import { PdfStartDictionaryToken } from '../tokens/start-dictionary-token'
3
+ import { PdfToken } from '../tokens/token'
4
+ import { PdfWhitespaceToken } from '../tokens/whitespace-token'
5
+ import { PdfName } from './pdf-name'
6
+ import { PdfObject } from './pdf-object'
7
+
8
+ export type PdfDictionaryEntry = PdfObject | undefined
9
+ export type PdfDictionaryEntries = Record<string, PdfDictionaryEntry>
10
+ export type PdfDictionaryMap = Map<PdfName, PdfObject | undefined>
11
+
12
+ export class PdfDictionary<
13
+ T extends PdfDictionaryEntries = PdfDictionaryEntries,
14
+ > extends PdfObject {
15
+ #entries: PdfDictionaryMap
16
+ innerTokens: PdfToken[] = []
17
+
18
+ constructor(entries?: T | PdfDictionaryMap) {
19
+ super()
20
+
21
+ this.#entries = entries instanceof Map ? entries : new Map<any, any>()
22
+ if (entries) {
23
+ for (const [key, value] of Object.entries(entries)) {
24
+ this.#entries.set(new PdfName(key), value)
25
+ }
26
+ }
27
+ }
28
+
29
+ get<K extends Extract<keyof T, string>>(
30
+ key: PdfName<K> | K,
31
+ ): T[K] | undefined {
32
+ if (key instanceof PdfName) {
33
+ return this.#entries.get(key) as T[K] | undefined
34
+ } else {
35
+ for (const [k, v] of this.#entries) {
36
+ if (k.value === key) {
37
+ return v as T[K] | undefined
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ set<K extends Extract<keyof T, string>>(key: PdfName<K> | K, value: T[K]) {
44
+ const currentValue = this.get(key)
45
+ if (currentValue !== value && !currentValue?.equals(value)) {
46
+ this.modified = true
47
+ }
48
+
49
+ if (key instanceof PdfName) {
50
+ this.#entries.set(key, value)
51
+ return
52
+ } else {
53
+ for (const k of this.#entries.keys()) {
54
+ if (k.value === key) {
55
+ this.#entries.set(k, value)
56
+ return
57
+ }
58
+ }
59
+ }
60
+ this.#entries.set(new PdfName(key), value)
61
+ }
62
+
63
+ delete<K extends Extract<keyof T, string>>(key: PdfName<K> | K) {
64
+ if (this.has(key)) {
65
+ this.modified = true
66
+ }
67
+
68
+ if (key instanceof PdfName) {
69
+ this.#entries.delete(key)
70
+ return
71
+ } else {
72
+ for (const k of this.#entries.keys()) {
73
+ if (k.value === key) {
74
+ this.#entries.delete(k)
75
+ return
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ has<K extends Extract<keyof T, string>>(key: PdfName<K> | K): boolean {
82
+ if (key instanceof PdfName) {
83
+ return this.#entries.has(key)
84
+ } else {
85
+ for (const k of this.#entries.keys()) {
86
+ if (k.value === key) {
87
+ return true
88
+ }
89
+ }
90
+ }
91
+ return false
92
+ }
93
+
94
+ get values(): {
95
+ readonly [K in Extract<keyof T, string>]: T[K]
96
+ } {
97
+ const obj: Partial<T> = {}
98
+ for (const [key, value] of this.#entries) {
99
+ obj[key.value as Extract<keyof T, string>] = value as T[Extract<
100
+ keyof T,
101
+ string
102
+ >]
103
+ }
104
+ return obj as T
105
+ }
106
+
107
+ protected tokenize() {
108
+ let index = 0
109
+
110
+ const entries = Array.from(this.#entries.entries()).flatMap(
111
+ ([key, value]) => {
112
+ if (value === undefined) {
113
+ return []
114
+ }
115
+
116
+ const preTokens = key.preTokens
117
+ ? []
118
+ : index === 0
119
+ ? [PdfWhitespaceToken.SPACE]
120
+ : []
121
+
122
+ const centralTokens =
123
+ key.postTokens || value.preTokens
124
+ ? []
125
+ : [PdfWhitespaceToken.SPACE]
126
+
127
+ const postTokens = value.postTokens
128
+ ? []
129
+ : [PdfWhitespaceToken.SPACE]
130
+
131
+ index++
132
+ return [
133
+ ...preTokens,
134
+ ...key.toTokens(),
135
+ ...centralTokens,
136
+ ...value.toTokens(),
137
+ ...postTokens,
138
+ ]
139
+ },
140
+ )
141
+
142
+ return [
143
+ new PdfStartDictionaryToken(),
144
+ ...this.innerTokens,
145
+ ...entries,
146
+ new PdfEndDictionaryToken(),
147
+ ]
148
+ }
149
+
150
+ copyFrom(other: PdfDictionary<any>) {
151
+ for (const [key, value] of other.#entries) {
152
+ this.#entries.set(key, value)
153
+ }
154
+ this.modified = true
155
+ }
156
+
157
+ clone(): this {
158
+ const clonedEntries = new Map<PdfName, PdfObject | undefined>()
159
+ for (const [key, value] of this.#entries) {
160
+ clonedEntries.set(key.clone(), value ? value.clone() : undefined)
161
+ }
162
+ return new PdfDictionary(clonedEntries) as this
163
+ }
164
+
165
+ isModified(): boolean {
166
+ return (
167
+ super.isModified() ||
168
+ Array.from(this.#entries.values()).some((v) => v?.isModified())
169
+ )
170
+ }
171
+ }
@@ -0,0 +1,54 @@
1
+ import { ByteArray } from '../../types'
2
+ import { bytesToHexBytes } from '../../utils/bytesToHexBytes'
3
+ import { bytesToString } from '../../utils/bytesToString'
4
+ import { hexBytesToBytes } from '../../utils/hexBytesToBytes'
5
+ import { stringToBytes } from '../../utils/stringToBytes'
6
+ import { PdfHexadecimalToken } from '../tokens/hexadecimal-token'
7
+ import { PdfObject } from './pdf-object'
8
+
9
+ export class PdfHexadecimal extends PdfObject {
10
+ /**
11
+ * The raw byte value represented by this hexadecimal object.
12
+ * NB: This is the hexadecimal representation, not the actual byte values.
13
+ */
14
+ raw: ByteArray
15
+
16
+ constructor(value: string | ByteArray, format: 'hex' | 'bytes' = 'hex') {
17
+ super()
18
+
19
+ let bytes: ByteArray
20
+ if (format === 'bytes') {
21
+ bytes = bytesToHexBytes(
22
+ value instanceof Uint8Array ? value : stringToBytes(value),
23
+ )
24
+ } else {
25
+ bytes = value instanceof Uint8Array ? value : stringToBytes(value)
26
+ }
27
+
28
+ this.raw = bytes
29
+ }
30
+
31
+ static toHexadecimal(data: string | ByteArray): PdfHexadecimal {
32
+ return new PdfHexadecimal(data, 'bytes')
33
+ }
34
+
35
+ get bytes(): ByteArray {
36
+ return hexBytesToBytes(this.raw)
37
+ }
38
+
39
+ toHexBytes(): ByteArray {
40
+ return bytesToHexBytes(this.raw)
41
+ }
42
+
43
+ toHexString(): string {
44
+ return bytesToString(this.toHexBytes())
45
+ }
46
+
47
+ protected tokenize() {
48
+ return [new PdfHexadecimalToken(this.raw)]
49
+ }
50
+
51
+ clone(): this {
52
+ return new PdfHexadecimal(new Uint8Array(this.raw)) as this
53
+ }
54
+ }