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,38 @@
1
+ import { ByteArray } from '../../types'
2
+ import { bytesToString } from '../../utils/bytesToString'
3
+ import { stringToBytes } from '../../utils/stringToBytes'
4
+ import { PdfStringToken } from '../tokens/string-token'
5
+ import { PdfObject } from './pdf-object'
6
+
7
+ export class PdfString extends PdfObject {
8
+ /**
9
+ * The raw bytes of the PDF string.
10
+ */
11
+ private _raw: ByteArray
12
+
13
+ constructor(raw: ByteArray | string) {
14
+ super()
15
+ this._raw = typeof raw === 'string' ? stringToBytes(raw) : raw
16
+ }
17
+
18
+ get raw(): ByteArray {
19
+ return this._raw
20
+ }
21
+
22
+ set raw(raw: ByteArray) {
23
+ this.setModified()
24
+ this._raw = raw
25
+ }
26
+
27
+ get value(): string {
28
+ return bytesToString(this.raw)
29
+ }
30
+
31
+ protected tokenize() {
32
+ return [new PdfStringToken(this.raw)]
33
+ }
34
+
35
+ clone(): this {
36
+ return new PdfString(new Uint8Array(this.raw)) as this
37
+ }
38
+ }
@@ -0,0 +1,57 @@
1
+ import { Ref } from '../ref'
2
+ import { PdfByteOffsetToken } from '../tokens/byte-offset-token'
3
+ import { PdfToken } from '../tokens/token'
4
+ import { PdfTrailerToken } from '../tokens/trailer-token'
5
+ import { PdfWhitespaceToken } from '../tokens/whitespace-token'
6
+ import { PdfArray } from './pdf-array'
7
+ import { PdfDictionary } from './pdf-dictionary'
8
+ import { PdfHexadecimal } from './pdf-hexadecimal'
9
+ import { PdfNumber } from './pdf-number'
10
+ import { PdfObject } from './pdf-object'
11
+ import { PdfObjectReference } from './pdf-object-reference'
12
+
13
+ export type PdfTrailerEntries = {
14
+ Size: PdfNumber
15
+ Root?: PdfObjectReference
16
+ Info?: PdfObjectReference
17
+ Prev?: PdfNumber
18
+ XRefStm?: PdfNumber
19
+ Encrypt?: PdfObjectReference
20
+ ID?: PdfArray<PdfHexadecimal>
21
+ }
22
+
23
+ export type PdfTrailerDictionary = PdfDictionary<PdfTrailerEntries>
24
+
25
+ export class PdfTrailer extends PdfObject {
26
+ dict: PdfTrailerDictionary
27
+ offset: Ref<number> = new Ref(0)
28
+
29
+ constructor(entries: PdfTrailerEntries | PdfDictionary<PdfTrailerEntries>) {
30
+ super()
31
+ this.dict =
32
+ entries instanceof PdfDictionary
33
+ ? entries
34
+ : new PdfDictionary(entries)
35
+ }
36
+
37
+ protected tokenize(): PdfToken[] {
38
+ return [
39
+ new PdfByteOffsetToken(this.offset),
40
+ new PdfTrailerToken(),
41
+ ...(this.dict.preTokens ? [] : [PdfWhitespaceToken.NEWLINE]),
42
+ ...this.dict.toTokens(),
43
+ ]
44
+ }
45
+
46
+ clone(): this {
47
+ return new PdfTrailer(this.dict.clone()) as this
48
+ }
49
+
50
+ isModified(): boolean {
51
+ return (
52
+ super.isModified() ||
53
+ this.dict.isModified() ||
54
+ this.offset.isModified
55
+ )
56
+ }
57
+ }
@@ -0,0 +1,264 @@
1
+ import { Ref } from '../ref'
2
+ import { PdfByteOffsetToken } from '../tokens/byte-offset-token'
3
+ import { PdfWhitespaceToken } from '../tokens/whitespace-token'
4
+ import { PdfXRefTableEntryToken } from '../tokens/xref-table-entry-token'
5
+ import { PdfXRefTableSectionStartToken } from '../tokens/xref-table-section-start-token'
6
+ import { PdfXRefTableStartToken } from '../tokens/xref-table-start-token'
7
+ import { PdfIndirectObject } from './pdf-indirect-object'
8
+ import { PdfNumber } from './pdf-number'
9
+ import { PdfObject } from './pdf-object'
10
+
11
+ export class PdfXRefTableEntry extends PdfObject {
12
+ objectNumber: PdfNumber
13
+ generationNumber: PdfNumber
14
+ byteOffset: PdfNumber
15
+ inUse: boolean
16
+
17
+ constructor(options: {
18
+ byteOffset: number | PdfNumber | Ref<number>
19
+ objectNumber: number | PdfNumber
20
+ generationNumber: number | PdfNumber
21
+ inUse: boolean
22
+ }) {
23
+ super()
24
+
25
+ this.byteOffset =
26
+ options.byteOffset instanceof PdfNumber
27
+ ? options.byteOffset
28
+ : new PdfNumber({
29
+ value: options.byteOffset,
30
+ padTo: 10,
31
+ })
32
+ this.byteOffset.isByteOffset = true
33
+
34
+ this.objectNumber = new PdfNumber(options.objectNumber)
35
+ this.generationNumber =
36
+ options.generationNumber instanceof PdfNumber
37
+ ? options.generationNumber
38
+ : new PdfNumber({
39
+ value: options.generationNumber,
40
+ padTo: 5,
41
+ })
42
+ this.inUse = options.inUse
43
+ }
44
+
45
+ protected tokenize() {
46
+ return [
47
+ new PdfXRefTableEntryToken(
48
+ this.byteOffset.toToken(),
49
+ this.generationNumber.toToken(),
50
+ this.objectNumber.toToken(),
51
+ this.inUse,
52
+ ),
53
+ ]
54
+ }
55
+
56
+ clone(): this {
57
+ return new PdfXRefTableEntry({
58
+ byteOffset: this.byteOffset.clone(),
59
+ objectNumber: this.objectNumber.clone(),
60
+ generationNumber: this.generationNumber.clone(),
61
+ inUse: this.inUse,
62
+ }) as this
63
+ }
64
+
65
+ isModified(): boolean {
66
+ return (
67
+ super.isModified() ||
68
+ this.byteOffset.isModified() ||
69
+ this.objectNumber.isModified() ||
70
+ this.generationNumber.isModified()
71
+ )
72
+ }
73
+ }
74
+
75
+ export class PdfXRefTableSectionHeader extends PdfObject {
76
+ startObjectNumber: PdfNumber
77
+ entryCount: PdfNumber
78
+
79
+ constructor(options: {
80
+ startObjectNumber: number | PdfNumber
81
+ entryCount: number | PdfNumber
82
+ }) {
83
+ super()
84
+ this.startObjectNumber = new PdfNumber(options.startObjectNumber)
85
+ this.entryCount = new PdfNumber(options.entryCount)
86
+ }
87
+
88
+ protected tokenize() {
89
+ return [
90
+ new PdfXRefTableSectionStartToken(
91
+ this.startObjectNumber.value,
92
+ this.entryCount.value,
93
+ ),
94
+ ]
95
+ }
96
+
97
+ clone(): this {
98
+ return new PdfXRefTableSectionHeader({
99
+ startObjectNumber: this.startObjectNumber.clone(),
100
+ entryCount: this.entryCount.clone(),
101
+ }) as this
102
+ }
103
+ }
104
+
105
+ export class PdfXRefTable extends PdfObject {
106
+ sections: PdfXRefTableSectionHeader[]
107
+ entries: PdfXRefTableEntry[]
108
+ offset: Ref<number> = new Ref(0)
109
+
110
+ constructor(options?: {
111
+ sections?: PdfXRefTableSectionHeader[]
112
+ entries?: PdfXRefTableEntry[]
113
+ offset?: Ref<number> | number
114
+ }) {
115
+ super()
116
+ this.sections = options?.sections ?? []
117
+ this.entries = options?.entries ?? []
118
+ if (options?.offset) {
119
+ this.offset.update(options.offset)
120
+ }
121
+ }
122
+
123
+ isModified(): boolean {
124
+ return (
125
+ super.isModified() ||
126
+ this.sections.some((section) => section.isModified()) ||
127
+ this.entries.some((entry) => entry.isModified()) ||
128
+ this.offset.isModified
129
+ )
130
+ }
131
+
132
+ addEntryForObject(obj: PdfIndirectObject): void {
133
+ const foundEntry = this.entries.find(
134
+ (entry) => entry.objectNumber.value === obj.objectNumber,
135
+ )
136
+
137
+ if (foundEntry) {
138
+ return
139
+ }
140
+
141
+ const newEntry = new PdfXRefTableEntry({
142
+ objectNumber: obj.objectNumber,
143
+ generationNumber: obj.generationNumber,
144
+ byteOffset: obj.offset,
145
+ inUse: true,
146
+ })
147
+
148
+ this.entries.push(newEntry)
149
+ }
150
+
151
+ getEntry(objectNumber: number): PdfXRefTableEntry | undefined {
152
+ return this.entries.find(
153
+ (entry) => entry.objectNumber.value === objectNumber,
154
+ )
155
+ }
156
+
157
+ get lastSection(): PdfXRefTableSectionHeader | null {
158
+ if (this.sections.length === 0) {
159
+ return null
160
+ }
161
+ return this.sections[this.sections.length - 1]
162
+ }
163
+
164
+ protected tokenize() {
165
+ const innerTokens = this.sortEntriesIntoSections().flatMap(
166
+ ({ section, entries }) => [
167
+ ...section.toTokens(),
168
+ ...entries.flatMap((entry) => entry.toTokens()),
169
+ ],
170
+ )
171
+
172
+ for (let i = 0; i < innerTokens.length; i++) {
173
+ const previousToken = innerTokens[i - 1]
174
+ const currentToken = innerTokens[i]
175
+
176
+ // Only insert newline if previous token is not whitespace AND current token is not whitespace
177
+ const previousIsWhitespace =
178
+ previousToken instanceof PdfWhitespaceToken
179
+ const currentIsWhitespace =
180
+ currentToken instanceof PdfWhitespaceToken
181
+
182
+ if (!previousIsWhitespace && !currentIsWhitespace) {
183
+ innerTokens.splice(i, 0, PdfWhitespaceToken.NEWLINE)
184
+ i++ // Skip the newly inserted token
185
+ }
186
+ }
187
+
188
+ return [
189
+ new PdfByteOffsetToken(this.offset),
190
+ new PdfXRefTableStartToken(),
191
+ ...innerTokens,
192
+ ]
193
+ }
194
+
195
+ private sortEntriesIntoSections(): {
196
+ section: PdfXRefTableSectionHeader
197
+ entries: PdfXRefTableEntry[]
198
+ }[] {
199
+ const sections: {
200
+ section: PdfXRefTableSectionHeader
201
+ entries: PdfXRefTableEntry[]
202
+ }[] = []
203
+
204
+ let currentSection: PdfXRefTableSectionHeader | null = null
205
+ let currentEntries: PdfXRefTableEntry[] = []
206
+
207
+ const sortedEntries = this.entries.sort(
208
+ (a, b) => a.objectNumber.value - b.objectNumber.value,
209
+ )
210
+
211
+ for (let i = 0; i < sortedEntries.length; i++) {
212
+ const last = i > 0 ? sortedEntries[i - 1] : null
213
+ const entry = sortedEntries[i]
214
+
215
+ if (
216
+ !last ||
217
+ entry.objectNumber.value !== last.objectNumber.value + 1
218
+ ) {
219
+ if (currentSection) {
220
+ sections.push({
221
+ section: currentSection,
222
+ entries: currentEntries,
223
+ })
224
+ }
225
+
226
+ currentSection =
227
+ this.sections.find(
228
+ (x) =>
229
+ x.startObjectNumber.value ===
230
+ entry.objectNumber.value,
231
+ ) ??
232
+ new PdfXRefTableSectionHeader({
233
+ startObjectNumber: entry.objectNumber,
234
+ entryCount: 1,
235
+ })
236
+
237
+ currentEntries = [entry]
238
+ } else {
239
+ currentEntries.push(entry)
240
+ }
241
+
242
+ if (currentSection) {
243
+ currentSection.entryCount.value = currentEntries.length
244
+ }
245
+ }
246
+
247
+ if (currentSection) {
248
+ sections.push({
249
+ section: currentSection,
250
+ entries: currentEntries,
251
+ })
252
+ }
253
+
254
+ return sections
255
+ }
256
+
257
+ clone(): this {
258
+ return new PdfXRefTable({
259
+ sections: this.sections.map((s) => s.clone()),
260
+ entries: this.entries.map((e) => e.clone()),
261
+ offset: new Ref(this.offset.resolve()),
262
+ }) as this
263
+ }
264
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Abstract base class for parsers that transform input items to output items.
3
+ * Provides a common interface for feeding input and generating output.
4
+ *
5
+ * @typeParam I - The input item type
6
+ * @typeParam O - The output item type
7
+ */
8
+ export abstract class Parser<I, O> {
9
+ /**
10
+ * Feeds input items to the parser.
11
+ *
12
+ * @param input - Input items to process
13
+ */
14
+ abstract feed(...input: I[]): void
15
+
16
+ /**
17
+ * Generates output items from the fed input.
18
+ *
19
+ * @returns A generator yielding output items
20
+ */
21
+ abstract nextItems(): Generator<O>
22
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Callback function type for ref update notifications.
3
+ *
4
+ * @typeParam T - The value type of the ref
5
+ */
6
+ export type RefUpdateCallback<T> = (oldValue: T, newValue: T) => void
7
+
8
+ /**
9
+ * A mutable reference wrapper that supports value updates and change notifications.
10
+ * Can hold a direct value or reference another Ref, forming reference chains.
11
+ *
12
+ * @typeParam T - The value type being referenced
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const ref = new Ref(42)
17
+ * ref.onUpdate((old, newVal) => console.log(`Changed from ${old} to ${newVal}`))
18
+ * ref.update(100) // Logs: Changed from 42 to 100
19
+ * ```
20
+ */
21
+ export class Ref<T> {
22
+ /** The current value or a reference to another Ref */
23
+ value: T | Ref<T>
24
+ /** Registered callbacks for update notifications */
25
+ callbacks: Array<RefUpdateCallback<T>> = []
26
+ isModified: boolean = false
27
+
28
+ /**
29
+ * Creates a new Ref with an initial value.
30
+ *
31
+ * @param value - The initial value or another Ref to chain
32
+ * @throws Error if attempting to create a self-referencing Ref
33
+ */
34
+ constructor(value: T | Ref<T>) {
35
+ if (value === this) {
36
+ throw new Error('Cannot create Ref to itself')
37
+ }
38
+ this.value = value
39
+ }
40
+
41
+ /**
42
+ * Updates the reference to a new value or another Ref.
43
+ * Notifies all registered callbacks of the change.
44
+ *
45
+ * @param newValue - The new value or Ref to point to
46
+ */
47
+ update(newValue: T | Ref<T>) {
48
+ if (newValue === this) {
49
+ return
50
+ }
51
+
52
+ const resolvedNewValue =
53
+ newValue instanceof Ref ? newValue.resolve() : newValue
54
+
55
+ const oldValue = this.resolve()
56
+ if (oldValue !== resolvedNewValue) {
57
+ this.isModified = true
58
+ }
59
+
60
+ this.value = newValue
61
+ this.callbacks.forEach((cb) => cb(oldValue, this.resolve()))
62
+ }
63
+
64
+ /**
65
+ * Resolves the reference chain to get the final value.
66
+ *
67
+ * @returns The resolved value of type T
68
+ */
69
+ resolve(): T {
70
+ if (this.value instanceof Ref) {
71
+ return this.value.resolve()
72
+ }
73
+ return this.value
74
+ }
75
+
76
+ /**
77
+ * Compares this Ref's resolved value with another value or Ref.
78
+ *
79
+ * @param other - The value or Ref to compare against
80
+ * @returns True if the resolved values are equal
81
+ */
82
+ equals(other?: Ref<T> | T): boolean {
83
+ if (other === undefined) {
84
+ return false
85
+ }
86
+
87
+ if (other instanceof Ref) {
88
+ return this.resolve() === other.resolve()
89
+ }
90
+
91
+ return this.resolve() === other
92
+ }
93
+
94
+ /**
95
+ * Registers a callback to be notified when the value changes.
96
+ *
97
+ * @param callback - The function to call on value updates
98
+ */
99
+ onUpdate(callback: RefUpdateCallback<T>): void {
100
+ this.callbacks.push(callback)
101
+ }
102
+ }
@@ -0,0 +1,68 @@
1
+ import { ByteArray } from '../types'
2
+ import { Parser } from './parser'
3
+ import { PdfByteOffsetToken } from './tokens/byte-offset-token'
4
+ import { PdfToken } from './tokens/token'
5
+
6
+ /**
7
+ * Serializes PDF tokens into a byte stream.
8
+ * Handles byte offset calculation and token serialization.
9
+ */
10
+ export class PdfTokenSerializer extends Parser<PdfToken, number> {
11
+ /** Current byte offset in the output stream */
12
+ offset: number = 0
13
+ private buffer: PdfToken[] = []
14
+
15
+ /**
16
+ * Feeds tokens into the serializer buffer.
17
+ *
18
+ * @param input - PDF tokens to serialize
19
+ */
20
+ feed(...input: PdfToken[]): void {
21
+ this.buffer.push(...input)
22
+ }
23
+
24
+ /**
25
+ * Generates bytes from the buffered tokens.
26
+ * Updates byte offset tokens as they are encountered.
27
+ *
28
+ * @returns A generator yielding individual bytes
29
+ */
30
+ *nextItems(): Generator<number> {
31
+ while (this.buffer.length) {
32
+ const obj = this.buffer.shift()!
33
+
34
+ if (obj instanceof PdfByteOffsetToken) {
35
+ obj.update(this.offset)
36
+ continue
37
+ }
38
+
39
+ const bytes = obj.toBytes()
40
+ yield* bytes
41
+ this.offset += bytes.length
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Pre-calculates byte offsets for all byte offset tokens in the buffer.
47
+ * Does not consume the buffer.
48
+ */
49
+ calculateOffsets(): void {
50
+ let currentOffset = 0
51
+
52
+ for (const token of this.buffer) {
53
+ if (token instanceof PdfByteOffsetToken) {
54
+ token.update(currentOffset)
55
+ }
56
+ currentOffset += token.toBytes().length
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Serializes all buffered tokens to a byte array.
62
+ *
63
+ * @returns The serialized PDF as a Uint8Array
64
+ */
65
+ toBytes(): ByteArray {
66
+ return new Uint8Array(Array.from(this.nextItems()))
67
+ }
68
+ }
@@ -0,0 +1,20 @@
1
+ import { PdfObject } from '../objects/pdf-object'
2
+ import { ByteArray } from '../../types'
3
+ import { IterableReadableStream } from '../../utils/IterableReadableStream'
4
+ import { pdfDecoderAsync } from '../generators'
5
+
6
+ export class PdfObjectStream extends IterableReadableStream<PdfObject> {
7
+ constructor(input: AsyncIterable<ByteArray> | Iterable<ByteArray>) {
8
+ super({
9
+ async start(controller) {
10
+ const decoder = pdfDecoderAsync(input)
11
+
12
+ for await (const obj of decoder) {
13
+ controller.enqueue(obj)
14
+ }
15
+
16
+ controller.close()
17
+ },
18
+ })
19
+ }
20
+ }