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.
- package/.commitlintrc.cjs +25 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/workflows/docs.yaml +93 -0
- package/.github/workflows/prepare-release.yaml +79 -0
- package/.github/workflows/release.yaml +80 -0
- package/.github/workflows/test.yaml +35 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.prettierignore +4 -0
- package/.prettierrc +4 -0
- package/CONTRIBUTING.md +109 -0
- package/EXAMPLES.md +1515 -0
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/examples/001-create-pdf.ts +112 -0
- package/examples/002-create-encrypted-pdf.ts +121 -0
- package/examples/003-sign-pdf.ts +347 -0
- package/examples/004-incremental-update.ts +206 -0
- package/examples/005-modify-acroform.ts +374 -0
- package/examples/006-tokeniser-example.ts +131 -0
- package/examples/007-decoder-example.ts +197 -0
- package/package.json +72 -0
- package/packages/pdf-lite/README.md +3 -0
- package/packages/pdf-lite/package.json +68 -0
- package/packages/pdf-lite/scripts/create-encryption-tests.sh +41 -0
- package/packages/pdf-lite/scripts/gen-signing-keys.sh +290 -0
- package/packages/pdf-lite/scripts/generate-all-signing-keys.sh +70 -0
- package/packages/pdf-lite/src/core/decoder.ts +454 -0
- package/packages/pdf-lite/src/core/generators.ts +128 -0
- package/packages/pdf-lite/src/core/incremental-parser.ts +221 -0
- package/packages/pdf-lite/src/core/index.ts +2 -0
- package/packages/pdf-lite/src/core/objects/pdf-array.ts +54 -0
- package/packages/pdf-lite/src/core/objects/pdf-boolean.ts +19 -0
- package/packages/pdf-lite/src/core/objects/pdf-comment.ts +50 -0
- package/packages/pdf-lite/src/core/objects/pdf-date.ts +74 -0
- package/packages/pdf-lite/src/core/objects/pdf-dictionary.ts +171 -0
- package/packages/pdf-lite/src/core/objects/pdf-hexadecimal.ts +54 -0
- package/packages/pdf-lite/src/core/objects/pdf-indirect-object.ts +137 -0
- package/packages/pdf-lite/src/core/objects/pdf-name.ts +19 -0
- package/packages/pdf-lite/src/core/objects/pdf-null.ts +15 -0
- package/packages/pdf-lite/src/core/objects/pdf-number.ts +98 -0
- package/packages/pdf-lite/src/core/objects/pdf-object-reference.ts +30 -0
- package/packages/pdf-lite/src/core/objects/pdf-object.ts +107 -0
- package/packages/pdf-lite/src/core/objects/pdf-start-xref.ts +39 -0
- package/packages/pdf-lite/src/core/objects/pdf-stream.ts +687 -0
- package/packages/pdf-lite/src/core/objects/pdf-string.ts +38 -0
- package/packages/pdf-lite/src/core/objects/pdf-trailer.ts +57 -0
- package/packages/pdf-lite/src/core/objects/pdf-xref-table.ts +264 -0
- package/packages/pdf-lite/src/core/parser.ts +22 -0
- package/packages/pdf-lite/src/core/ref.ts +102 -0
- package/packages/pdf-lite/src/core/serializer.ts +68 -0
- package/packages/pdf-lite/src/core/streams/object-stream.ts +20 -0
- package/packages/pdf-lite/src/core/tokeniser.ts +687 -0
- package/packages/pdf-lite/src/core/tokens/boolean-token.ts +20 -0
- package/packages/pdf-lite/src/core/tokens/byte-offset-token.ts +20 -0
- package/packages/pdf-lite/src/core/tokens/comment-token.ts +32 -0
- package/packages/pdf-lite/src/core/tokens/end-array-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/end-dictionary-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/end-object-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/end-stream-token.ts +11 -0
- package/packages/pdf-lite/src/core/tokens/hexadecimal-token.ts +22 -0
- package/packages/pdf-lite/src/core/tokens/name-token.ts +19 -0
- package/packages/pdf-lite/src/core/tokens/null-token.ts +9 -0
- package/packages/pdf-lite/src/core/tokens/number-token.ts +164 -0
- package/packages/pdf-lite/src/core/tokens/object-reference-token.ts +24 -0
- package/packages/pdf-lite/src/core/tokens/start-array-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/start-dictionary-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/start-object-token.ts +28 -0
- package/packages/pdf-lite/src/core/tokens/start-stream-token.ts +52 -0
- package/packages/pdf-lite/src/core/tokens/start-xref-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/stream-chunk-token.ts +8 -0
- package/packages/pdf-lite/src/core/tokens/string-token.ts +17 -0
- package/packages/pdf-lite/src/core/tokens/token.ts +43 -0
- package/packages/pdf-lite/src/core/tokens/trailer-token.ts +12 -0
- package/packages/pdf-lite/src/core/tokens/whitespace-token.ts +43 -0
- package/packages/pdf-lite/src/core/tokens/xref-table-entry-token.ts +65 -0
- package/packages/pdf-lite/src/core/tokens/xref-table-section-start-token.ts +31 -0
- package/packages/pdf-lite/src/core/tokens/xref-table-start-token.ts +13 -0
- package/packages/pdf-lite/src/crypto/ciphers/aes128.ts +63 -0
- package/packages/pdf-lite/src/crypto/ciphers/aes256.ts +50 -0
- package/packages/pdf-lite/src/crypto/ciphers/rc4.ts +82 -0
- package/packages/pdf-lite/src/crypto/constants.ts +10 -0
- package/packages/pdf-lite/src/crypto/key-derivation/key-derivation-aes256.ts +213 -0
- package/packages/pdf-lite/src/crypto/key-derivation/key-derivation.ts +122 -0
- package/packages/pdf-lite/src/crypto/key-gen/key-gen-aes256.ts +79 -0
- package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-128.ts +190 -0
- package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-40.ts +129 -0
- package/packages/pdf-lite/src/crypto/types.ts +6 -0
- package/packages/pdf-lite/src/crypto/utils.ts +81 -0
- package/packages/pdf-lite/src/filters/ascii85.ts +128 -0
- package/packages/pdf-lite/src/filters/asciihex.ts +55 -0
- package/packages/pdf-lite/src/filters/flate.ts +39 -0
- package/packages/pdf-lite/src/filters/lzw.ts +144 -0
- package/packages/pdf-lite/src/filters/pass-through.ts +37 -0
- package/packages/pdf-lite/src/filters/runlength.ts +92 -0
- package/packages/pdf-lite/src/filters/types.ts +21 -0
- package/packages/pdf-lite/src/index.ts +4 -0
- package/packages/pdf-lite/src/pdf/errors.ts +5 -0
- package/packages/pdf-lite/src/pdf/index.ts +4 -0
- package/packages/pdf-lite/src/pdf/pdf-document.ts +924 -0
- package/packages/pdf-lite/src/pdf/pdf-reader.ts +57 -0
- package/packages/pdf-lite/src/pdf/pdf-revision.ts +234 -0
- package/packages/pdf-lite/src/pdf/pdf-xref-lookup.ts +527 -0
- package/packages/pdf-lite/src/security/crypt-filters/aesv2.ts +58 -0
- package/packages/pdf-lite/src/security/crypt-filters/aesv3.ts +56 -0
- package/packages/pdf-lite/src/security/crypt-filters/base.ts +140 -0
- package/packages/pdf-lite/src/security/crypt-filters/identity.ts +40 -0
- package/packages/pdf-lite/src/security/crypt-filters/v2.ts +59 -0
- package/packages/pdf-lite/src/security/handlers/base.ts +625 -0
- package/packages/pdf-lite/src/security/handlers/pubSec.ts +413 -0
- package/packages/pdf-lite/src/security/handlers/utils.ts +304 -0
- package/packages/pdf-lite/src/security/handlers/v1.ts +225 -0
- package/packages/pdf-lite/src/security/handlers/v2.ts +128 -0
- package/packages/pdf-lite/src/security/handlers/v4.ts +379 -0
- package/packages/pdf-lite/src/security/handlers/v5.ts +298 -0
- package/packages/pdf-lite/src/security/types.ts +158 -0
- package/packages/pdf-lite/src/signing/document-security-store.ts +224 -0
- package/packages/pdf-lite/src/signing/index.ts +3 -0
- package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-detached.ts +154 -0
- package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-sha1.ts +161 -0
- package/packages/pdf-lite/src/signing/signatures/adbe-x509-rsa-sha1.ts +106 -0
- package/packages/pdf-lite/src/signing/signatures/base.ts +229 -0
- package/packages/pdf-lite/src/signing/signatures/etsi-cades-detached.ts +229 -0
- package/packages/pdf-lite/src/signing/signatures/etsi-rfc3161.ts +92 -0
- package/packages/pdf-lite/src/signing/signatures/index.ts +6 -0
- package/packages/pdf-lite/src/signing/signer.ts +120 -0
- package/packages/pdf-lite/src/signing/types.ts +86 -0
- package/packages/pdf-lite/src/signing/utils.ts +71 -0
- package/packages/pdf-lite/src/types.ts +44 -0
- package/packages/pdf-lite/src/utils/IterableReadableStream.ts +30 -0
- package/packages/pdf-lite/src/utils/algos.ts +446 -0
- package/packages/pdf-lite/src/utils/assert.ts +42 -0
- package/packages/pdf-lite/src/utils/bytesToHex.ts +18 -0
- package/packages/pdf-lite/src/utils/bytesToHexBytes.ts +27 -0
- package/packages/pdf-lite/src/utils/bytesToString.ts +17 -0
- package/packages/pdf-lite/src/utils/concatUint8Arrays.ts +26 -0
- package/packages/pdf-lite/src/utils/escapeString.ts +49 -0
- package/packages/pdf-lite/src/utils/hexBytesToBytes.ts +22 -0
- package/packages/pdf-lite/src/utils/hexBytesToString.ts +21 -0
- package/packages/pdf-lite/src/utils/hexToBytes.ts +18 -0
- package/packages/pdf-lite/src/utils/padBytes.ts +25 -0
- package/packages/pdf-lite/src/utils/predictors.ts +332 -0
- package/packages/pdf-lite/src/utils/replaceInBuffer.ts +56 -0
- package/packages/pdf-lite/src/utils/stringToBytes.ts +22 -0
- package/packages/pdf-lite/src/utils/stringToHexBytes.ts +23 -0
- package/packages/pdf-lite/src/utils/unescapeString.ts +123 -0
- package/packages/pdf-lite/test/acceptance/__snapshots__/versions.node.test.ts.snap +60766 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.3/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-128.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-256.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-128.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-40.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.5/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.6/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.7/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-128.pdf +43 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-256.pdf +43 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-128.pdf +43 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-40.pdf +44 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic.pdf +79 -0
- package/packages/pdf-lite/test/acceptance/versions.node.test.ts +41 -0
- package/packages/pdf-lite/test/unit/__snapshots__/decoder.node.test.ts.snap +86947 -0
- package/packages/pdf-lite/test/unit/__snapshots__/tokeniser.node.test.ts.snap +131829 -0
- package/packages/pdf-lite/test/unit/ciphers.test.ts +61 -0
- package/packages/pdf-lite/test/unit/decoder.node.test.ts +21 -0
- package/packages/pdf-lite/test/unit/decoder.test.ts +567 -0
- package/packages/pdf-lite/test/unit/filters.test.ts +67 -0
- package/packages/pdf-lite/test/unit/fixtures/basic.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-128.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-256.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-128.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-40.pdf +43 -0
- package/packages/pdf-lite/test/unit/fixtures/protectedAdobeLivecycle.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/rsa-2048/index.ts +187 -0
- package/packages/pdf-lite/test/unit/fixtures/template.pdf +0 -0
- package/packages/pdf-lite/test/unit/incremental-update.test.ts +0 -0
- package/packages/pdf-lite/test/unit/objects.test.ts +0 -0
- package/packages/pdf-lite/test/unit/pdf-document-signing.test.ts +0 -0
- package/packages/pdf-lite/test/unit/pdf-revision.test.ts +195 -0
- package/packages/pdf-lite/test/unit/pdf.browser.test.ts +0 -0
- package/packages/pdf-lite/test/unit/predictors.test.ts +226 -0
- package/packages/pdf-lite/test/unit/ref.test.ts +158 -0
- package/packages/pdf-lite/test/unit/security-handlers.test.ts +645 -0
- package/packages/pdf-lite/test/unit/serializer.test.ts +81 -0
- package/packages/pdf-lite/test/unit/signature-objects.test.ts +814 -0
- package/packages/pdf-lite/test/unit/string-escaping.test.ts +84 -0
- package/packages/pdf-lite/test/unit/tokeniser.node.test.ts +38 -0
- package/packages/pdf-lite/test/unit/tokeniser.test.ts +1213 -0
- package/packages/pdf-lite/test/unit/utils.test.ts +248 -0
- package/packages/pdf-lite/test/unit/xref-lookup.test.ts +72 -0
- package/packages/pdf-lite/tsconfig.json +4 -0
- package/packages/pdf-lite/tsconfig.prod.json +8 -0
- package/packages/pdf-lite/typedoc.json +14 -0
- package/packages/pdf-lite/vitest.config.ts +43 -0
- package/pnpm-workspace.yaml +2 -0
- package/renovate.json +34 -0
- package/scripts/build-examples.ts +30 -0
- package/scripts/bump-version.sh +56 -0
- package/scripts/gen-html-docs.sh +21 -0
- package/scripts/gen-md-docs.sh +15 -0
- package/scripts/prepare-release.sh +33 -0
- package/tsconfig.json +22 -0
- package/tsconfig.prod.json +12 -0
- 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,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
|
+
}
|