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,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
|
+
}
|