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,248 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { stringToBytes } from '../../src/utils/stringToBytes'
3
+ import { bytesToString } from '../../src/utils/bytesToString'
4
+ import { hexToBytes } from '../../src/utils/hexToBytes'
5
+ import { bytesToHex } from '../../src/utils/bytesToHex'
6
+ import { padBytes } from '../../src/utils/padBytes'
7
+ import { concatUint8Arrays } from '../../src/utils/concatUint8Arrays'
8
+ import { hexBytesToBytes } from '../../src/utils/hexBytesToBytes'
9
+ import { bytesToHexBytes } from '../../src/utils/bytesToHexBytes'
10
+ import { stringToHexBytes } from '../../src/utils/stringToHexBytes'
11
+ import { hexBytesToString } from '../../src/utils/hexBytesToString'
12
+ import { assert, assertIfDefined } from '../../src/utils/assert'
13
+
14
+ describe('Utility Functions', () => {
15
+ describe('stringToBytes', () => {
16
+ it('should convert a string to bytes', () => {
17
+ const result = stringToBytes('Hello')
18
+ expect(result).toEqual(new Uint8Array([72, 101, 108, 108, 111]))
19
+ })
20
+
21
+ it('should return the same array if already a ByteArray', () => {
22
+ const input = new Uint8Array([1, 2, 3])
23
+ const result = stringToBytes(input)
24
+ expect(result).toBe(input)
25
+ })
26
+
27
+ it('should handle empty string', () => {
28
+ const result = stringToBytes('')
29
+ expect(result.length).toBe(0)
30
+ })
31
+
32
+ it('should handle unicode characters', () => {
33
+ const result = stringToBytes('café')
34
+ // 'café' encodes to: 99, 97, 102, 195, 169 in UTF-8
35
+ expect(result.length).toBe(5)
36
+ })
37
+ })
38
+
39
+ describe('bytesToString', () => {
40
+ it('should convert bytes to a string', () => {
41
+ const bytes = new Uint8Array([72, 101, 108, 108, 111])
42
+ const result = bytesToString(bytes)
43
+ expect(result).toBe('Hello')
44
+ })
45
+
46
+ it('should handle empty array', () => {
47
+ const bytes = new Uint8Array([])
48
+ const result = bytesToString(bytes)
49
+ expect(result).toBe('')
50
+ })
51
+
52
+ it('should handle UTF-8 encoded bytes', () => {
53
+ const bytes = new Uint8Array([99, 97, 102, 195, 169]) // 'café' in UTF-8
54
+ const result = bytesToString(bytes)
55
+ expect(result).toBe('café')
56
+ })
57
+ })
58
+
59
+ describe('hexToBytes', () => {
60
+ it('should convert hex string to bytes', () => {
61
+ const result = hexToBytes('48656c6c6f') // 'Hello' in hex
62
+ expect(result).toEqual(new Uint8Array([72, 101, 108, 108, 111]))
63
+ })
64
+
65
+ it('should handle uppercase hex', () => {
66
+ const result = hexToBytes('DEADBEEF')
67
+ expect(result).toEqual(new Uint8Array([222, 173, 190, 239]))
68
+ })
69
+
70
+ it('should handle empty string', () => {
71
+ const result = hexToBytes('')
72
+ expect(result.length).toBe(0)
73
+ })
74
+ })
75
+
76
+ describe('bytesToHex', () => {
77
+ it('should convert bytes to hex string', () => {
78
+ const bytes = new Uint8Array([72, 101, 108, 108, 111])
79
+ const result = bytesToHex(bytes)
80
+ expect(result).toBe('48656C6C6F')
81
+ })
82
+
83
+ it('should handle empty array', () => {
84
+ const bytes = new Uint8Array([])
85
+ const result = bytesToHex(bytes)
86
+ expect(result).toBe('')
87
+ })
88
+
89
+ it('should handle bytes with leading zeros', () => {
90
+ const bytes = new Uint8Array([0, 15, 16])
91
+ const result = bytesToHex(bytes)
92
+ expect(result).toBe('000F10')
93
+ })
94
+ })
95
+
96
+ describe('padBytes', () => {
97
+ it('should pad bytes to the specified length', () => {
98
+ const bytes = new Uint8Array([1, 2, 3])
99
+ const result = padBytes(bytes, 5)
100
+ expect(result.length).toBe(5)
101
+ expect(result).toEqual(new Uint8Array([1, 2, 3, 0, 0]))
102
+ })
103
+
104
+ it('should throw error if bytes are longer than target length', () => {
105
+ const bytes = new Uint8Array([1, 2, 3, 4, 5])
106
+ expect(() => padBytes(bytes, 3)).toThrow(
107
+ 'Cannot pad bytes: current length 5 is greater than or equal to target length 3.',
108
+ )
109
+ })
110
+
111
+ it('should handle same length (no padding needed)', () => {
112
+ const bytes = new Uint8Array([1, 2, 3])
113
+ // Same length doesn't throw - it just returns the same content in a new array
114
+ const result = padBytes(bytes, 3)
115
+ expect(result).toEqual(new Uint8Array([1, 2, 3]))
116
+ })
117
+
118
+ it('should handle empty array', () => {
119
+ const bytes = new Uint8Array([])
120
+ const result = padBytes(bytes, 3)
121
+ expect(result).toEqual(new Uint8Array([0, 0, 0]))
122
+ })
123
+ })
124
+
125
+ describe('concatUint8Arrays', () => {
126
+ it('should concatenate multiple arrays', () => {
127
+ const arr1 = new Uint8Array([1, 2])
128
+ const arr2 = new Uint8Array([3, 4])
129
+ const arr3 = new Uint8Array([5])
130
+ const result = concatUint8Arrays(arr1, arr2, arr3)
131
+ expect(result).toEqual(new Uint8Array([1, 2, 3, 4, 5]))
132
+ })
133
+
134
+ it('should handle empty arrays', () => {
135
+ const arr1 = new Uint8Array([1, 2])
136
+ const arr2 = new Uint8Array([])
137
+ const arr3 = new Uint8Array([3])
138
+ const result = concatUint8Arrays(arr1, arr2, arr3)
139
+ expect(result).toEqual(new Uint8Array([1, 2, 3]))
140
+ })
141
+
142
+ it('should handle single array', () => {
143
+ const arr = new Uint8Array([1, 2, 3])
144
+ const result = concatUint8Arrays(arr)
145
+ expect(result).toEqual(new Uint8Array([1, 2, 3]))
146
+ })
147
+
148
+ it('should handle no arrays', () => {
149
+ const result = concatUint8Arrays()
150
+ expect(result).toEqual(new Uint8Array([]))
151
+ })
152
+ })
153
+
154
+ describe('hexBytesToBytes', () => {
155
+ it('should convert hex bytes to bytes', () => {
156
+ const hexBytes = stringToBytes('48656C6C6F') // 'Hello' in hex
157
+ const result = hexBytesToBytes(hexBytes)
158
+ expect(result).toEqual(new Uint8Array([72, 101, 108, 108, 111]))
159
+ })
160
+
161
+ it('should handle lowercase hex', () => {
162
+ const hexBytes = stringToBytes('deadbeef')
163
+ const result = hexBytesToBytes(hexBytes)
164
+ expect(result).toEqual(new Uint8Array([222, 173, 190, 239]))
165
+ })
166
+ })
167
+
168
+ describe('bytesToHexBytes', () => {
169
+ it('should convert bytes to hex bytes', () => {
170
+ const bytes = new Uint8Array([72, 101, 108, 108, 111])
171
+ const result = bytesToHexBytes(bytes)
172
+ expect(bytesToString(result)).toBe('48656C6C6F')
173
+ })
174
+
175
+ it('should handle bytes with values requiring leading zeros', () => {
176
+ const bytes = new Uint8Array([0, 15])
177
+ const result = bytesToHexBytes(bytes)
178
+ expect(bytesToString(result)).toBe('000F')
179
+ })
180
+ })
181
+
182
+ describe('stringToHexBytes', () => {
183
+ it('should convert hex string with angle brackets to bytes', () => {
184
+ const result = stringToHexBytes('<deadbeef>')
185
+ expect(result).toEqual(new Uint8Array([222, 173, 190, 239]))
186
+ })
187
+
188
+ it('should handle hex string without angle brackets', () => {
189
+ const result = stringToHexBytes('deadbeef')
190
+ expect(result).toEqual(new Uint8Array([222, 173, 190, 239]))
191
+ })
192
+ })
193
+
194
+ describe('hexBytesToString', () => {
195
+ it('should convert bytes to hex string', () => {
196
+ const bytes = new Uint8Array([222, 173, 190, 239])
197
+ const result = hexBytesToString(bytes)
198
+ expect(result).toBe('deadbeef')
199
+ })
200
+
201
+ it('should handle bytes with values requiring leading zeros', () => {
202
+ const bytes = new Uint8Array([0, 15])
203
+ const result = hexBytesToString(bytes)
204
+ expect(result).toBe('000f')
205
+ })
206
+ })
207
+
208
+ describe('assert', () => {
209
+ it('should not throw for truthy values', () => {
210
+ expect(() => assert(true)).not.toThrow()
211
+ expect(() => assert(1)).not.toThrow()
212
+ expect(() => assert('hello')).not.toThrow()
213
+ expect(() => assert({})).not.toThrow()
214
+ })
215
+
216
+ it('should throw for falsy values', () => {
217
+ expect(() => assert(false)).toThrow()
218
+ expect(() => assert(0)).toThrow()
219
+ expect(() => assert('')).toThrow()
220
+ expect(() => assert(null)).toThrow()
221
+ expect(() => assert(undefined)).toThrow()
222
+ })
223
+
224
+ it('should throw with custom message', () => {
225
+ expect(() => assert(false, 'Custom error')).toThrow('Custom error')
226
+ })
227
+ })
228
+
229
+ describe('assertIfDefined', () => {
230
+ it('should not throw if value is undefined', () => {
231
+ expect(() => assertIfDefined(undefined, false)).not.toThrow()
232
+ })
233
+
234
+ it('should not throw if value is null', () => {
235
+ expect(() => assertIfDefined(null, false)).not.toThrow()
236
+ })
237
+
238
+ it('should not throw if condition is truthy for defined value', () => {
239
+ expect(() => assertIfDefined('defined', true)).not.toThrow()
240
+ })
241
+
242
+ it('should throw if condition is falsy for defined value', () => {
243
+ expect(() =>
244
+ assertIfDefined('defined', false, 'Condition failed'),
245
+ ).toThrow('Condition failed')
246
+ })
247
+ })
248
+ })
@@ -0,0 +1,72 @@
1
+ import { assert, describe, expect, it } from 'vitest'
2
+ import { PdfXrefLookup } from '../../src/pdf/pdf-xref-lookup.js'
3
+ import { PdfObject } from '../../src/core/objects/pdf-object'
4
+ import { PdfIndirectObject } from '../../src/core/objects/pdf-indirect-object.js'
5
+ import {
6
+ PdfXRefStream,
7
+ PdfXRefStreamCompressedEntry,
8
+ PdfXRefStreamStandardEntry,
9
+ } from '../../src/core/objects/pdf-stream'
10
+ import {
11
+ PdfXRefTable,
12
+ PdfXRefTableEntry,
13
+ } from '../../src/core/objects/pdf-xref-table.js'
14
+ import { PdfTrailer } from '../../src/core/objects/pdf-trailer'
15
+ import { PdfNumber } from '../../src/core/objects/pdf-number'
16
+ import { PdfStartXRef } from '../../src/core/objects/pdf-start-xref'
17
+ import { PdfComment } from '../../src/core/objects/pdf-comment'
18
+
19
+ describe('PDF Xref Lookup', () => {
20
+ const xrefStream = new PdfIndirectObject({
21
+ content: PdfXRefStream.fromEntries([
22
+ new PdfXRefTableEntry({
23
+ objectNumber: 1,
24
+ byteOffset: 200,
25
+ generationNumber: 0,
26
+ inUse: true,
27
+ }),
28
+ new PdfXRefTableEntry({
29
+ objectNumber: 2,
30
+ byteOffset: 300,
31
+ generationNumber: 0,
32
+ inUse: true,
33
+ }),
34
+ new PdfXRefStreamCompressedEntry({
35
+ objectNumber: 3,
36
+ objectStreamNumber: 5,
37
+ index: 0,
38
+ }),
39
+ ]),
40
+ offset: 100,
41
+ })
42
+
43
+ const xrefTable = new PdfXRefTable()
44
+ const objects: PdfObject[] = [
45
+ xrefStream,
46
+ xrefTable,
47
+ new PdfTrailer({
48
+ Size: new PdfNumber(1),
49
+ Prev: new PdfNumber(xrefStream.offset),
50
+ }),
51
+ new PdfStartXRef(xrefTable.offset),
52
+ PdfComment.EOF,
53
+ ]
54
+ const xrefLookup = PdfXrefLookup.fromObjects(objects)
55
+
56
+ it('should chain multiple xref table or stream lookups', () => {
57
+ expect(xrefLookup.object).toBeInstanceOf(PdfXRefTable)
58
+ expect(xrefLookup.object).toEqual(xrefTable)
59
+ expect(xrefLookup.prev?.object).toEqual(xrefStream)
60
+
61
+ const entry1 = xrefLookup.getObject(2)!
62
+ assert(entry1 instanceof PdfXRefStreamStandardEntry)
63
+ expect(entry1.byteOffset.value).toBe(300)
64
+ })
65
+
66
+ it('should find compressed entries in xref streams', () => {
67
+ const entry = xrefLookup.getObject(3)!
68
+ assert(entry instanceof PdfXRefStreamCompressedEntry)
69
+ expect(entry.objectNumber.value).toBe(3)
70
+ expect(entry.index.value).toBe(0)
71
+ })
72
+ })
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "include": ["src/**/*", "examples/**/*", "test/**/*", "test-fixtures"]
4
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.prod.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "include": ["src/**/*.ts"],
7
+ "exclude": ["src/**/*.test.ts", "test-fixtures", "test/**/*"]
8
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "readme": "none",
3
+ "entryPointStrategy": "expand",
4
+ "entryPoints": ["src/**/*.ts"],
5
+ "exclude": [
6
+ "**/node_modules/**",
7
+ "**/test/**",
8
+ "**/scripts/**",
9
+ "**/dist/**",
10
+ "**/*.spec.ts",
11
+ "**/*.test.ts"
12
+ ],
13
+ "tsconfig": "tsconfig.prod.json"
14
+ }
@@ -0,0 +1,43 @@
1
+ import { defineConfig } from 'vitest/config'
2
+ import { playwright } from '@vitest/browser-playwright'
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ isolate: true,
7
+ projects: [
8
+ {
9
+ test: {
10
+ name: 'node',
11
+ environment: 'node',
12
+ include: ['**/*.node.(test|spec).ts'],
13
+ exclude: ['node_modules'],
14
+ },
15
+ },
16
+ {
17
+ test: {
18
+ name: 'browser',
19
+ include: ['**/*.(test|spec).ts'],
20
+ exclude: ['node_modules', '**/*.node.(test|spec).ts'],
21
+ browser: {
22
+ // Disable CORS to test the timestamp request example
23
+ provider: playwright({
24
+ contextOptions: {
25
+ extraHTTPHeaders: {
26
+ 'Content-Type':
27
+ 'application/timestamp-query',
28
+ },
29
+ },
30
+ launchOptions: {
31
+ args: ['--disable-web-security'],
32
+ },
33
+ }),
34
+ enabled: true,
35
+ headless: true,
36
+ screenshotFailures: false,
37
+ instances: [{ browser: 'chromium' }],
38
+ },
39
+ },
40
+ },
41
+ ],
42
+ },
43
+ })
@@ -0,0 +1,2 @@
1
+ packages:
2
+ - 'packages/*'
package/renovate.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended",
5
+ ":preserveSemverRanges",
6
+ ":rebaseStalePrs",
7
+ ":disableDigestUpdates",
8
+ "schedule:weekly",
9
+ "group:recommended",
10
+ "group:monorepos",
11
+ "workarounds:all"
12
+ ],
13
+ "labels": ["deps"],
14
+ "dependencyDashboard": true,
15
+ "ignorePaths": ["**/node_modules/**"],
16
+ "timezone": "Europe/London",
17
+ "schedule": ["after 2am every weekday", "before 5am every weekday"],
18
+ "packageRules": [
19
+ {
20
+ "minimumReleaseAge": "3 days",
21
+ "matchPackageNames": ["/npm/"]
22
+ },
23
+ {
24
+ "groupName": "patch updates",
25
+ "matchUpdateTypes": ["patch"],
26
+ "matchPackageNames": ["*"]
27
+ },
28
+ {
29
+ "groupName": "Typedoc",
30
+ "matchUpdateTypes": ["patch", "minor", "major"],
31
+ "matchPackageNames": ["/typedoc/"]
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,30 @@
1
+ import fs from 'fs/promises'
2
+
3
+ let markdown = `# PDF-Lite Examples
4
+
5
+ This directory contains example scripts demonstrating how to use the PDF-Lite library.
6
+
7
+ `
8
+
9
+ const examples = await fs.readdir(import.meta.dirname + '/../examples')
10
+ for (const example of examples) {
11
+ if (example.endsWith('.ts')) {
12
+ const exampleSrc = await fs.readFile(
13
+ import.meta.dirname + `/../examples/${example}`,
14
+ 'utf-8',
15
+ )
16
+ const exampleTitleStart = exampleSrc.indexOf('//')
17
+ const exampleTitleEnd = exampleSrc.indexOf('\n', exampleTitleStart)
18
+ const exampleTitle =
19
+ exampleTitleStart !== -1
20
+ ? exampleSrc
21
+ .substring(exampleTitleStart + 2, exampleTitleEnd)
22
+ .trim()
23
+ : 'Untitled Example'
24
+ const exampleContent = exampleSrc.substring(exampleTitleEnd + 1).trim()
25
+
26
+ markdown += `## ${exampleTitle}\n\n\`\`\`typescript\n${exampleContent}\n\`\`\`\n`
27
+ }
28
+ }
29
+
30
+ await fs.writeFile(import.meta.dirname + '/../EXAMPLES.md', markdown, 'utf-8')
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # Usage: ./bump-version.sh <new-version>
3
+ # Example: ./bump-version.sh patch
4
+ # Example: ./bump-version.sh 1.2.3
5
+ # Example: ./bump-version.sh major
6
+
7
+ set -e
8
+
9
+ if [ -n "$1" ]; then
10
+ # Bump version in root package.json
11
+ pnpm version --no-git-tag-version $@
12
+
13
+ # Bump version in all workspaces
14
+ pnpm -r exec pnpm version --no-git-tag-version $@
15
+
16
+ # Get the new version from root package.json
17
+ NEW_VERSION=$(node -p "require('./package.json').version")
18
+
19
+ # Make sure all packages pdf-lite* depend on latest versions of other pdf-lite* packages
20
+
21
+ echo "Updating all pdf-lite* dependencies to version $NEW_VERSION"
22
+
23
+ find . -name 'package.json' -not -path '*/node_modules/*' | while read -r pkg_file; do
24
+ # Only update if there are pdf-lite* dependencies
25
+ if grep -q '"pdf-lite' "$pkg_file"; then
26
+ tmp_file=$(mktemp)
27
+ node -e "
28
+ const fs = require('fs');
29
+ const pkg = JSON.parse(fs.readFileSync('$pkg_file', 'utf8'));
30
+ const newVersion = '$NEW_VERSION';
31
+ ['dependencies', 'devDependencies'].forEach(depType => {
32
+ if (pkg[depType]) {
33
+ Object.keys(pkg[depType]).forEach(dep => {
34
+ if (dep.startsWith('pdf-lite')) {
35
+ const initialChar = pkg[depType][dep][0];
36
+ if (['^', '~', '>', '<', '='].includes(initialChar)) {
37
+ pkg[depType][dep] = initialChar + newVersion;
38
+ } else {
39
+ pkg[depType][dep] = newVersion;
40
+ }
41
+ }
42
+ });
43
+ }
44
+ });
45
+ fs.writeFileSync('$tmp_file', JSON.stringify(pkg, null, 2) + '\n');
46
+ "
47
+ mv "$tmp_file" "$pkg_file"
48
+ echo "Updated $pkg_file"
49
+ fi
50
+ done
51
+
52
+ echo "Version bump completed successfully to $NEW_VERSION"
53
+ fi
54
+
55
+ # Update lockfile
56
+ pnpm install --lockfile-only
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ NEW_VERSION=$1
6
+
7
+ if [ -z "$NEW_VERSION" ]; then
8
+ echo "Usage: $0 <new-version>"
9
+ exit 1
10
+ fi
11
+
12
+ echo "Generating HTML docs for version: $NEW_VERSION"
13
+
14
+ pnpm exec typedoc \
15
+ --options typedoc.json \
16
+ --out docs-html/$NEW_VERSION \
17
+ --projectDocuments 'README.md' \
18
+ --projectDocuments 'CONTRIBUTING.md' \
19
+ --projectDocuments 'EXAMPLES.md' \
20
+ --githubPages \
21
+ --includeVersion
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ URL="https://jacobshirley.github.io/pdf-lite"
6
+
7
+ # Get latest version from package.json
8
+ MAJOR_VERSION=$(node -p "require('./package.json').version.split('.')[0]")
9
+ echo "Generating docs for version: $MAJOR_VERSION"
10
+
11
+ # Update absolute doc links to versioned path
12
+ find . -type f -name '*.md' -exec sed -i "s|$URL/v[0-9a-zA-Z._-]*|$URL/v$MAJOR_VERSION|g" {} +
13
+
14
+ # Generate markdown docs
15
+ pnpm exec typedoc --options typedoc.json --plugin typedoc-plugin-markdown --readme none && pnpm format
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ # Usage: ./prepare-release.sh
3
+
4
+ set -e
5
+
6
+ force_args=()
7
+ if [[ " $* " == *" --force=true"* ]]; then
8
+ force_args+=("--force")
9
+ fi
10
+
11
+ # Stage all
12
+ git add --all
13
+
14
+ # Get current version
15
+ CURRENT_VERSION=$(node -p "require('./package.json').version")
16
+
17
+ # Output the current version to use in other steps
18
+ echo "$CURRENT_VERSION" > .release-version
19
+
20
+ # Create a new branch for the release
21
+ git checkout -b "release/v$CURRENT_VERSION"
22
+
23
+ # Commit the changes
24
+ git commit -m "release(v$CURRENT_VERSION): prepare"
25
+
26
+ # Push the branch to the remote repository
27
+ git push origin "release/v$CURRENT_VERSION" -u "${force_args[@]}"
28
+
29
+ if [[ " $* " == *" --create-pr "* ]]; then
30
+ # Create a pull request using GitHub CLI
31
+ PR_TITLE="chore: prerelease $CURRENT_VERSION"
32
+ gh pr create --fill --head "release/v$CURRENT_VERSION" --base master --body "Automated prerelease branch for version $CURRENT_VERSION" || true # Ignore errors if PR already exists
33
+ fi
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "resolveJsonModule": true,
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "isolatedModules": true,
11
+ "esModuleInterop": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "strict": true,
14
+ "skipLibCheck": true,
15
+ "lib": ["DOM", "ESNext"],
16
+ "allowJs": true,
17
+ "incremental": true,
18
+ "allowArbitraryExtensions": true,
19
+ "erasableSyntaxOnly": true
20
+ },
21
+ "include": ["examples/**/*", "scripts/**/*"]
22
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "sourceMap": false,
6
+ "composite": false,
7
+ "incremental": false,
8
+ "declarationMap": false
9
+ },
10
+ "include": ["src/**/*.ts"],
11
+ "exclude": ["test/**/*.test.ts"]
12
+ }
package/typedoc.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://typedoc.org/schema.json",
3
+ "entryPoints": ["packages/*"],
4
+ "exclude": [
5
+ "**/node_modules/**",
6
+ "**/test/**",
7
+ "**/scripts/**",
8
+ "**/dist/**",
9
+ "**/*.spec.ts",
10
+ "**/*.test.ts"
11
+ ],
12
+ "name": "PKI-Lite",
13
+ "entryPointStrategy": "packages",
14
+ "disableSources": true,
15
+ "alwaysCreateEntryPointModule": false,
16
+ "cleanOutputDir": true,
17
+ "pretty": true,
18
+ "packageOptions": {
19
+ "includeVersion": false,
20
+ "exclude": [
21
+ "**/node_modules/**",
22
+ "**/test/**",
23
+ "**/scripts/**",
24
+ "**/dist/**",
25
+ "**/*.spec.ts",
26
+ "**/*.test.ts"
27
+ ],
28
+ "entryPoints": ["src/index.ts"]
29
+ },
30
+ "searchInDocuments": true,
31
+ "searchGroupBoosts": {
32
+ "Classes": 3
33
+ }
34
+ }