@awacloud/pdf 0.0.0-stage → 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 (345) hide show
  1. package/CHANGELOG.md +609 -0
  2. package/LICENSE +661 -0
  3. package/NOTICE +77 -0
  4. package/README.md +363 -2
  5. package/dist/build/index.js +21 -0
  6. package/dist/build/pdf-full-rw.js +10972 -0
  7. package/dist/build/pdf-full-rw.meta.json +105 -0
  8. package/dist/build/pdf-full-rw.min.js +53 -0
  9. package/dist/build/pdf-full.js +6078 -0
  10. package/dist/build/pdf-full.meta.json +90 -0
  11. package/dist/build/pdf-full.min.js +32 -0
  12. package/dist/build/pdf-large-rw.js +10367 -0
  13. package/dist/build/pdf-large-rw.meta.json +99 -0
  14. package/dist/build/pdf-large-rw.min.js +53 -0
  15. package/dist/build/pdf-large.js +5473 -0
  16. package/dist/build/pdf-large.meta.json +84 -0
  17. package/dist/build/pdf-large.min.js +32 -0
  18. package/dist/build/pdf-legacy-rw.js +12402 -0
  19. package/dist/build/pdf-legacy-rw.meta.json +110 -0
  20. package/dist/build/pdf-legacy-rw.min.js +53 -0
  21. package/dist/build/pdf-legacy.js +7508 -0
  22. package/dist/build/pdf-legacy.meta.json +95 -0
  23. package/dist/build/pdf-legacy.min.js +32 -0
  24. package/dist/build/pdf-rw.js +7578 -0
  25. package/dist/build/pdf-rw.meta.json +77 -0
  26. package/dist/build/pdf-rw.min.js +53 -0
  27. package/dist/build/pdf.js +2684 -0
  28. package/dist/build/pdf.meta.json +62 -0
  29. package/dist/build/pdf.min.js +32 -0
  30. package/dist/standalone/pdf-full-rw.js +16798 -0
  31. package/dist/standalone/pdf-full-rw.meta.json +78 -0
  32. package/dist/standalone/pdf-full-rw.min.js +56 -0
  33. package/dist/standalone/pdf-full.js +11904 -0
  34. package/dist/standalone/pdf-full.meta.json +63 -0
  35. package/dist/standalone/pdf-full.min.js +35 -0
  36. package/dist/standalone/pdf-large-rw.js +16193 -0
  37. package/dist/standalone/pdf-large-rw.meta.json +72 -0
  38. package/dist/standalone/pdf-large-rw.min.js +56 -0
  39. package/dist/standalone/pdf-large.js +11299 -0
  40. package/dist/standalone/pdf-large.meta.json +57 -0
  41. package/dist/standalone/pdf-large.min.js +35 -0
  42. package/dist/standalone/pdf-legacy-rw.js +18228 -0
  43. package/dist/standalone/pdf-legacy-rw.meta.json +83 -0
  44. package/dist/standalone/pdf-legacy-rw.min.js +56 -0
  45. package/dist/standalone/pdf-legacy.js +13334 -0
  46. package/dist/standalone/pdf-legacy.meta.json +68 -0
  47. package/dist/standalone/pdf-legacy.min.js +35 -0
  48. package/dist/standalone/pdf-rw.js +13404 -0
  49. package/dist/standalone/pdf-rw.meta.json +50 -0
  50. package/dist/standalone/pdf-rw.min.js +56 -0
  51. package/dist/standalone/pdf.js +8510 -0
  52. package/dist/standalone/pdf.meta.json +35 -0
  53. package/dist/standalone/pdf.min.js +35 -0
  54. package/docs/README.md +53 -0
  55. package/docs/api/README.md +38 -0
  56. package/docs/api/_shared/README.md +91 -0
  57. package/docs/api/action/README.md +29 -0
  58. package/docs/api/action/action.md +81 -0
  59. package/docs/api/action/goTo.md +66 -0
  60. package/docs/api/action/launch.md +58 -0
  61. package/docs/api/action/named.md +55 -0
  62. package/docs/api/action/uri.md +54 -0
  63. package/docs/api/annot/README.md +53 -0
  64. package/docs/api/annot/annot.md +114 -0
  65. package/docs/api/annot/fileAttach.md +53 -0
  66. package/docs/api/annot/freeText.md +68 -0
  67. package/docs/api/annot/ink.md +69 -0
  68. package/docs/api/annot/link.md +74 -0
  69. package/docs/api/annot/markup.md +83 -0
  70. package/docs/api/annot/popup.md +52 -0
  71. package/docs/api/annot/projection.md +56 -0
  72. package/docs/api/annot/redact.md +67 -0
  73. package/docs/api/annot/square.md +87 -0
  74. package/docs/api/annot/stamp.md +54 -0
  75. package/docs/api/annot/text.md +69 -0
  76. package/docs/api/annot/widget.md +69 -0
  77. package/docs/api/associatedFiles/README.md +9 -0
  78. package/docs/api/associatedFiles/associatedFiles.md +78 -0
  79. package/docs/api/bundles/README.md +68 -0
  80. package/docs/api/bundles/dist-matrix.md +165 -0
  81. package/docs/api/bundles/pdf-full.md +148 -0
  82. package/docs/api/bundles/pdf-large.md +144 -0
  83. package/docs/api/bundles/pdf-legacy.md +169 -0
  84. package/docs/api/content/README.md +29 -0
  85. package/docs/api/content/color.md +99 -0
  86. package/docs/api/content/graphics.md +114 -0
  87. package/docs/api/content/images.md +124 -0
  88. package/docs/api/content/ops.md +100 -0
  89. package/docs/api/content/stream.md +107 -0
  90. package/docs/api/content/text.md +98 -0
  91. package/docs/api/crypto/README.md +29 -0
  92. package/docs/api/crypto/aesGcm.md +72 -0
  93. package/docs/api/crypto/permissions.md +79 -0
  94. package/docs/api/crypto/security.md +98 -0
  95. package/docs/api/crypto/standardV4.md +104 -0
  96. package/docs/api/crypto/standardV5.md +84 -0
  97. package/docs/api/crypto/standardV6.md +93 -0
  98. package/docs/api/destination/README.md +9 -0
  99. package/docs/api/destination/destination.md +79 -0
  100. package/docs/api/document/README.md +29 -0
  101. package/docs/api/document/builder.md +281 -0
  102. package/docs/api/document/catalog.md +98 -0
  103. package/docs/api/document/document.md +187 -0
  104. package/docs/api/document/encryptedWriter.md +149 -0
  105. package/docs/api/document/incrementalWriter.md +148 -0
  106. package/docs/api/document/page.md +99 -0
  107. package/docs/api/document/pages.md +82 -0
  108. package/docs/api/document/resources.md +102 -0
  109. package/docs/api/document/writer.md +157 -0
  110. package/docs/api/document/xrefStreamWriter.md +122 -0
  111. package/docs/api/embedded/README.md +13 -0
  112. package/docs/api/embedded/collection.md +80 -0
  113. package/docs/api/embedded/embeddedFile.md +86 -0
  114. package/docs/api/embedded/fileSpec.md +87 -0
  115. package/docs/api/errors.md +110 -0
  116. package/docs/api/extra/3d-richmedia.md +76 -0
  117. package/docs/api/extra/README.md +99 -0
  118. package/docs/api/extra/annot-extended.md +71 -0
  119. package/docs/api/extra/associated-files.md +70 -0
  120. package/docs/api/extra/ccitt-fax-decoder.md +74 -0
  121. package/docs/api/extra/color-spaces-extended.md +72 -0
  122. package/docs/api/extra/content-ops-extended.md +82 -0
  123. package/docs/api/extra/document-parts.md +69 -0
  124. package/docs/api/extra/embedded-files-portfolio.md +87 -0
  125. package/docs/api/extra/font-cid-typed.md +77 -0
  126. package/docs/api/extra/font-color-tagging.md +76 -0
  127. package/docs/api/extra/form-actions-extended.md +75 -0
  128. package/docs/api/extra/info-dict-deprecated.md +72 -0
  129. package/docs/api/extra/jbig2-read.md +80 -0
  130. package/docs/api/extra/legacy-deprecated-annots.md +89 -0
  131. package/docs/api/extra/legacy-deprecated-filters.md +78 -0
  132. package/docs/api/extra/legacy-rc4-read.md +74 -0
  133. package/docs/api/extra/legacy-xfa-read.md +65 -0
  134. package/docs/api/extra/linearization-write.md +71 -0
  135. package/docs/api/extra/misc.md +93 -0
  136. package/docs/api/extra/optional-content-extended.md +83 -0
  137. package/docs/api/extra/pdf-a-output-intent.md +65 -0
  138. package/docs/api/extra/pdf-sandbox.md +76 -0
  139. package/docs/api/extra/pdf-ua-tagged.md +63 -0
  140. package/docs/api/extra/pdf-x-prepress.md +65 -0
  141. package/docs/api/extra/redaction-iso32005.md +65 -0
  142. package/docs/api/extra/shading-typed.md +73 -0
  143. package/docs/api/extra/sig-aes-gcm.md +69 -0
  144. package/docs/api/extra/sig-pades.md +103 -0
  145. package/docs/api/extra/tagged-pdf-typed.md +78 -0
  146. package/docs/api/extra/transparency-typed.md +74 -0
  147. package/docs/api/extra/well-tagged-pdf.md +61 -0
  148. package/docs/api/extra/xmp-extended.md +65 -0
  149. package/docs/api/font/README.md +25 -0
  150. package/docs/api/font/embed.md +157 -0
  151. package/docs/api/font/encoding.md +95 -0
  152. package/docs/api/font/font.md +97 -0
  153. package/docs/api/font/type3.md +89 -0
  154. package/docs/api/form/README.md +35 -0
  155. package/docs/api/form/acroform.md +88 -0
  156. package/docs/api/form/appearance.md +87 -0
  157. package/docs/api/form/button.md +97 -0
  158. package/docs/api/form/choice.md +96 -0
  159. package/docs/api/form/fieldTree.md +93 -0
  160. package/docs/api/form/signature.md +90 -0
  161. package/docs/api/form/text.md +88 -0
  162. package/docs/api/linearization/README.md +11 -0
  163. package/docs/api/linearization/linearization.md +81 -0
  164. package/docs/api/main.md +116 -0
  165. package/docs/api/metadata/README.md +10 -0
  166. package/docs/api/metadata/info.md +70 -0
  167. package/docs/api/metadata/xmp.md +62 -0
  168. package/docs/api/ocg/README.md +23 -0
  169. package/docs/api/ocg/config.md +95 -0
  170. package/docs/api/ocg/ocg.md +77 -0
  171. package/docs/api/outline/README.md +11 -0
  172. package/docs/api/outline/outline.md +107 -0
  173. package/docs/api/pdf.md +152 -0
  174. package/docs/api/prepress/README.md +10 -0
  175. package/docs/api/prepress/outputIntent.md +79 -0
  176. package/docs/api/prepress/pageBoundary.md +75 -0
  177. package/docs/api/sig/README.md +32 -0
  178. package/docs/api/sig/byteRange.md +120 -0
  179. package/docs/api/sig/certChain.md +84 -0
  180. package/docs/api/sig/dss.md +111 -0
  181. package/docs/api/sig/oids.md +76 -0
  182. package/docs/api/sig/sha1.md +72 -0
  183. package/docs/api/sig/sign.md +317 -0
  184. package/docs/api/sig/signature.md +178 -0
  185. package/docs/api/sig/timestamp.md +84 -0
  186. package/docs/api/syntax/README.md +29 -0
  187. package/docs/api/syntax/crossRefStream.md +115 -0
  188. package/docs/api/syntax/filters/README.md +50 -0
  189. package/docs/api/syntax/filters/ascii85.md +76 -0
  190. package/docs/api/syntax/filters/asciiHex.md +73 -0
  191. package/docs/api/syntax/filters/dispatch.md +125 -0
  192. package/docs/api/syntax/filters/flate.md +134 -0
  193. package/docs/api/syntax/filters/runLength.md +78 -0
  194. package/docs/api/syntax/objStream.md +88 -0
  195. package/docs/api/syntax/parser-obj.md +97 -0
  196. package/docs/api/syntax/parser.md +151 -0
  197. package/docs/api/syntax/serializer.md +109 -0
  198. package/docs/api/syntax/tokenizer.md +104 -0
  199. package/docs/api/syntax/trailer.md +85 -0
  200. package/docs/api/syntax/xref.md +139 -0
  201. package/docs/api/tagged/README.md +25 -0
  202. package/docs/api/tagged/classMap.md +67 -0
  203. package/docs/api/tagged/markedContent.md +62 -0
  204. package/docs/api/tagged/parentTree.md +67 -0
  205. package/docs/api/tagged/roleMap.md +67 -0
  206. package/docs/api/tagged/structElement.md +76 -0
  207. package/docs/api/tagged/structTree.md +75 -0
  208. package/docs/guide/coverage.md +113 -0
  209. package/docs/guide/crypto.md +121 -0
  210. package/docs/guide/extending.md +76 -0
  211. package/docs/guide/getting-started.md +75 -0
  212. package/docs/guide/legacy-1.7.md +42 -0
  213. package/docs/guide/pades-integration.md +579 -0
  214. package/docs/guide/read-pdf.md +89 -0
  215. package/package.json +97 -4
  216. package/src/_shared/index.js +179 -0
  217. package/src/action/action.js +119 -0
  218. package/src/action/goTo.js +89 -0
  219. package/src/action/launch.js +61 -0
  220. package/src/action/named.js +54 -0
  221. package/src/action/uri.js +51 -0
  222. package/src/annot/annot.js +212 -0
  223. package/src/annot/fileAttach.js +55 -0
  224. package/src/annot/freeText.js +82 -0
  225. package/src/annot/ink.js +77 -0
  226. package/src/annot/link.js +77 -0
  227. package/src/annot/markup.js +91 -0
  228. package/src/annot/popup.js +53 -0
  229. package/src/annot/projection.js +52 -0
  230. package/src/annot/redact.js +87 -0
  231. package/src/annot/square.js +132 -0
  232. package/src/annot/stamp.js +48 -0
  233. package/src/annot/text.js +54 -0
  234. package/src/annot/widget.js +61 -0
  235. package/src/associatedFiles/associatedFiles.js +86 -0
  236. package/src/bundles/pdf-full.js +91 -0
  237. package/src/bundles/pdf-large.js +81 -0
  238. package/src/bundles/pdf-legacy.js +107 -0
  239. package/src/content/color.js +114 -0
  240. package/src/content/graphics.js +192 -0
  241. package/src/content/images.js +160 -0
  242. package/src/content/ops.js +137 -0
  243. package/src/content/stream.js +154 -0
  244. package/src/content/text.js +125 -0
  245. package/src/crypto/aesGcm.js +123 -0
  246. package/src/crypto/permissions.js +112 -0
  247. package/src/crypto/security.js +327 -0
  248. package/src/crypto/standardV4.js +443 -0
  249. package/src/crypto/standardV5.js +306 -0
  250. package/src/crypto/standardV6.js +334 -0
  251. package/src/destination/destination.js +183 -0
  252. package/src/document/builder.js +618 -0
  253. package/src/document/catalog.js +100 -0
  254. package/src/document/document.js +472 -0
  255. package/src/document/encryptedWriter.js +554 -0
  256. package/src/document/incrementalWriter.js +514 -0
  257. package/src/document/page.js +131 -0
  258. package/src/document/pages.js +103 -0
  259. package/src/document/resources.js +146 -0
  260. package/src/document/writer.js +211 -0
  261. package/src/document/xrefStreamWriter.js +353 -0
  262. package/src/embedded/collection.js +102 -0
  263. package/src/embedded/embeddedFile.js +99 -0
  264. package/src/embedded/fileSpec.js +137 -0
  265. package/src/errors.js +78 -0
  266. package/src/extra/3d-richmedia.js +171 -0
  267. package/src/extra/annot-extended.js +200 -0
  268. package/src/extra/associated-files.js +131 -0
  269. package/src/extra/ccitt-fax-decoder.js +776 -0
  270. package/src/extra/color-spaces-extended.js +196 -0
  271. package/src/extra/content-ops-extended.js +153 -0
  272. package/src/extra/document-parts.js +149 -0
  273. package/src/extra/embedded-files-portfolio.js +234 -0
  274. package/src/extra/font-cid-typed.js +185 -0
  275. package/src/extra/font-color-tagging.js +144 -0
  276. package/src/extra/form-actions-extended.js +196 -0
  277. package/src/extra/info-dict-deprecated.js +137 -0
  278. package/src/extra/jbig2-read.js +169 -0
  279. package/src/extra/legacy-deprecated-annots.js +198 -0
  280. package/src/extra/legacy-deprecated-filters.js +167 -0
  281. package/src/extra/legacy-rc4-read.js +235 -0
  282. package/src/extra/legacy-xfa-read.js +104 -0
  283. package/src/extra/linearization-write.js +97 -0
  284. package/src/extra/misc.js +217 -0
  285. package/src/extra/optional-content-extended.js +142 -0
  286. package/src/extra/pdf-a-output-intent.js +112 -0
  287. package/src/extra/pdf-sandbox.js +88 -0
  288. package/src/extra/pdf-ua-tagged.js +116 -0
  289. package/src/extra/pdf-x-prepress.js +114 -0
  290. package/src/extra/redaction-iso32005.js +136 -0
  291. package/src/extra/shading-typed.js +222 -0
  292. package/src/extra/sig-aes-gcm.js +135 -0
  293. package/src/extra/sig-pades.js +242 -0
  294. package/src/extra/tagged-pdf-typed.js +203 -0
  295. package/src/extra/transparency-typed.js +135 -0
  296. package/src/extra/well-tagged-pdf.js +138 -0
  297. package/src/extra/xmp-extended.js +190 -0
  298. package/src/font/embed.js +480 -0
  299. package/src/font/encoding.js +92 -0
  300. package/src/font/font.js +101 -0
  301. package/src/font/type3.js +75 -0
  302. package/src/form/acroform.js +94 -0
  303. package/src/form/appearance.js +90 -0
  304. package/src/form/button.js +105 -0
  305. package/src/form/choice.js +152 -0
  306. package/src/form/fieldTree.js +120 -0
  307. package/src/form/signature.js +100 -0
  308. package/src/form/text.js +101 -0
  309. package/src/linearization/linearization.js +107 -0
  310. package/src/main.js +411 -0
  311. package/src/metadata/info.js +87 -0
  312. package/src/metadata/xmp.js +62 -0
  313. package/src/ocg/config.js +156 -0
  314. package/src/ocg/ocg.js +124 -0
  315. package/src/outline/outline.js +157 -0
  316. package/src/pdf.js +133 -0
  317. package/src/prepress/outputIntent.js +118 -0
  318. package/src/prepress/pageBoundary.js +108 -0
  319. package/src/sig/byteRange.js +306 -0
  320. package/src/sig/certChain.js +247 -0
  321. package/src/sig/dss.js +317 -0
  322. package/src/sig/oids.js +157 -0
  323. package/src/sig/sha1.js +142 -0
  324. package/src/sig/sign.js +1899 -0
  325. package/src/sig/signature.js +1441 -0
  326. package/src/sig/timestamp.js +236 -0
  327. package/src/syntax/crossRefStream.js +133 -0
  328. package/src/syntax/filters/ascii85.js +122 -0
  329. package/src/syntax/filters/asciiHex.js +83 -0
  330. package/src/syntax/filters/dispatch.js +176 -0
  331. package/src/syntax/filters/flate.js +316 -0
  332. package/src/syntax/filters/runLength.js +96 -0
  333. package/src/syntax/objStream.js +99 -0
  334. package/src/syntax/parser-obj.js +52 -0
  335. package/src/syntax/parser.js +321 -0
  336. package/src/syntax/serializer.js +221 -0
  337. package/src/syntax/tokenizer.js +290 -0
  338. package/src/syntax/trailer.js +76 -0
  339. package/src/syntax/xref.js +341 -0
  340. package/src/tagged/classMap.js +81 -0
  341. package/src/tagged/markedContent.js +123 -0
  342. package/src/tagged/parentTree.js +126 -0
  343. package/src/tagged/roleMap.js +107 -0
  344. package/src/tagged/structElement.js +138 -0
  345. package/src/tagged/structTree.js +94 -0
@@ -0,0 +1,52 @@
1
+ // Copyright (c) 2026 AwaCloud SAS
2
+ // Author: Matthieu Bouilloux
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ // Dual-licensed; see the NOTICE file for licensing and any additional terms.
5
+
6
+ /**
7
+ * @fileoverview PDF typed-object constructors and reflection helpers.
8
+ *
9
+ * Extracted from `parser.js` so the parser orchestrator stays under the
10
+ * 300-LOC cap. The `obj` factory is the canonical way to build typed
11
+ * objects literally — both internal use (parser construction) and
12
+ * external use (tests, content-stream serializers in L1+).
13
+ *
14
+ * @module pdf/syntax/parser-obj
15
+ */
16
+
17
+ /**
18
+ * Module factory — worker-safe, self-contained.
19
+ */
20
+ export const pdfParserObj = {
21
+ name: 'pdfParserObj',
22
+ dependencies: [],
23
+ factory() {
24
+ const obj = {
25
+ nul: () => ({ type: 'null' }),
26
+ bool: (v) => ({ type: 'bool', value: !!v }),
27
+ int: (v) => ({ type: 'int', value: v | 0 }),
28
+ real: (v) => ({ type: 'real', value: +v }),
29
+ name: (s) => ({ type: 'name', value: String(s) }),
30
+ string: (bytes, syntax) => ({
31
+ type: 'string',
32
+ value: bytes,
33
+ syntax: syntax === 'hex' ? 'hex' : 'lit'
34
+ }),
35
+ array: (items) => ({ type: 'array', items: items || [] }),
36
+ dict: (entries) => ({ type: 'dict', entries: entries || {} }),
37
+ ref: (num, gen) => ({ type: 'ref', num: num | 0, gen: (gen | 0) || 0 }),
38
+ stream: (dict, raw) => ({ type: 'stream', dict, raw })
39
+ };
40
+
41
+ function getEntry(dict, key) {
42
+ if (!dict || dict.type !== 'dict') return undefined;
43
+ return dict.entries[key];
44
+ }
45
+
46
+ function isType(v, kind) {
47
+ return !!(v && v.type === kind);
48
+ }
49
+
50
+ return { obj, getEntry, isType };
51
+ }
52
+ };
@@ -0,0 +1,321 @@
1
+ // Copyright (c) 2026 AwaCloud SAS
2
+ // Author: Matthieu Bouilloux
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ // Dual-licensed; see the NOTICE file for licensing and any additional terms.
5
+
6
+ /**
7
+ * @fileoverview PDF object parser — turns a tokenizer stream into a tree
8
+ * of typed PDF objects per ISO 32000-2:2020 §7.3 (Objects).
9
+ *
10
+ * Object types produced:
11
+ *
12
+ * | type | shape |
13
+ * |-----------|--------------------------------------------------------|
14
+ * | `null` | `{ type: 'null' }` |
15
+ * | `bool` | `{ type: 'bool', value: boolean }` |
16
+ * | `int` | `{ type: 'int', value: number }` |
17
+ * | `real` | `{ type: 'real', value: number }` |
18
+ * | `name` | `{ type: 'name', value: string }` |
19
+ * | `string` | `{ type: 'string', value: Uint8Array, syntax: 'lit'\|'hex' }` |
20
+ * | `array` | `{ type: 'array', items: object[] }` |
21
+ * | `dict` | `{ type: 'dict', entries: { [name: string]: object } }` |
22
+ * | `ref` | `{ type: 'ref', num: int, gen: int }` |
23
+ * | `stream` | `{ type: 'stream', dict, raw: Uint8Array }` |
24
+ *
25
+ * Indirect object definitions (`<num> <gen> obj ... endobj`) are parsed
26
+ * by `parseIndirect()` and return `{ num, gen, value }`.
27
+ *
28
+ * Companion files:
29
+ * - `parser-obj.js` — `obj.*` constructors and `getEntry`, `isType`.
30
+ *
31
+ * @module pdf/syntax/parser
32
+ */
33
+
34
+ /**
35
+ * Module factory — worker-safe, self-contained.
36
+ *
37
+ * Receives `pdfErrors` and `pdfTokenizer` via DI ; carries its own
38
+ * stream-length and `endstream` helpers in the body so the factory is
39
+ * self-sufficient.
40
+ *
41
+ * Re-exports the injected `pdfTokenizer`'s `tokenize` alongside the
42
+ * parser's own API: `pdfContentStream` declares `pdfParser` as its
43
+ * `tokenize` source, so the returned module must expose it for the
44
+ * public module graph to resolve a working `parseContentStream`.
45
+ */
46
+ import { pdfErrors } from '../errors.js';
47
+ import { pdfParserObj } from './parser-obj.js';
48
+ import { pdfTokenizer } from './tokenizer.js';
49
+
50
+ export const pdfParser = {
51
+ name: 'pdfParser',
52
+ dependencies: ['pdfErrors', 'pdfParserObj', 'pdfTokenizer'],
53
+ deps: [pdfErrors, pdfParserObj, pdfTokenizer],
54
+ factory(errors, parserObj, tokenizerMod) {
55
+ const { ParseError } = errors;
56
+ const { obj, getEntry, isType } = parserObj;
57
+ const tokenize = tokenizerMod.tokenize;
58
+ const LF = 0x0A, CR = 0x0D;
59
+
60
+ // --- stream-length and `endstream` helpers ---
61
+ function resolveStreamLength(streamDict, resolveRef) {
62
+ const e = streamDict.entries && streamDict.entries.Length;
63
+ if (!e) return -1;
64
+ if (e.type === 'int') return e.value;
65
+ if (e.type === 'ref' && typeof resolveRef === 'function') {
66
+ const r = resolveRef(e);
67
+ if (r && r.type === 'int') return r.value;
68
+ }
69
+ return -1;
70
+ }
71
+ function findEndstream(bytes, from, quota) {
72
+ const needle = [0x65,0x6E,0x64,0x73,0x74,0x72,0x65,0x61,0x6D]; // endstream
73
+ const lim = Math.min(bytes.length, from + (quota | 0));
74
+ outer: for (let i = from; i + needle.length <= lim; i++) {
75
+ for (let k = 0; k < needle.length; k++) {
76
+ if (bytes[i + k] !== needle[k]) continue outer;
77
+ }
78
+ // Walk back over trailing EOL whitespace before `endstream`.
79
+ let end = i;
80
+ if (end > from && bytes[end - 1] === LF) end--;
81
+ if (end > from && bytes[end - 1] === CR) end--;
82
+ return end;
83
+ }
84
+ return -1;
85
+ }
86
+ function skipEolWs(tok) {
87
+ const b = tok.bytes;
88
+ let p = tok.pos();
89
+ while (p < b.length) {
90
+ const c = b[p];
91
+ if (c === 0x20 || c === 0x09 || c === LF || c === CR) p++;
92
+ else break;
93
+ }
94
+ tok.seek(p);
95
+ }
96
+
97
+ // --- parserLimits (mutable per-instance state) ---
98
+ const parserLimits = {
99
+ maxDepth: 200,
100
+ maxArrayLen: 1_000_000,
101
+ maxStreamBytes: 256 * 1024 * 1024
102
+ };
103
+ function setParserLimits(partial) {
104
+ if (partial && typeof partial === 'object') {
105
+ if (Number.isInteger(partial.maxDepth) && partial.maxDepth > 0)
106
+ parserLimits.maxDepth = partial.maxDepth;
107
+ if (Number.isInteger(partial.maxArrayLen) && partial.maxArrayLen > 0)
108
+ parserLimits.maxArrayLen = partial.maxArrayLen;
109
+ if (Number.isInteger(partial.maxStreamBytes)
110
+ && partial.maxStreamBytes > 0)
111
+ parserLimits.maxStreamBytes = partial.maxStreamBytes;
112
+ }
113
+ return parserLimits;
114
+ }
115
+
116
+ function parseObject(tok) {
117
+ const t = tok.next();
118
+ if (!t) {
119
+ throw new ParseError('pdf/parser/eof', 'unexpected end of input');
120
+ }
121
+ return continueParse(tok, t, 0);
122
+ }
123
+
124
+ function continueParse(tok, t, depth) {
125
+ if (depth > parserLimits.maxDepth) {
126
+ throw new ParseError('pdf/parser/depth-exceeded',
127
+ 'parser depth limit exceeded',
128
+ { context: { depth, limit: parserLimits.maxDepth,
129
+ offset: t && t.offset } });
130
+ }
131
+ switch (t.kind) {
132
+ case 'kw':
133
+ if (t.value === 'null') return { type: 'null' };
134
+ if (t.value === 'true') return { type: 'bool', value: true };
135
+ if (t.value === 'false') return { type: 'bool', value: false };
136
+ throw new ParseError('pdf/parser/unexpected-keyword',
137
+ `unexpected keyword "${t.value}"`,
138
+ { context: { offset: t.offset, keyword: t.value } });
139
+ case 'name':
140
+ return { type: 'name', value: t.value };
141
+ case 'string':
142
+ return { type: 'string', value: t.value, syntax: 'lit' };
143
+ case 'hex':
144
+ return { type: 'string', value: t.value, syntax: 'hex' };
145
+ case 'int':
146
+ return tryReadRef(tok, t);
147
+ case 'real':
148
+ return { type: 'real', value: t.value };
149
+ case 'open_arr':
150
+ return readArray(tok, t.offset, depth);
151
+ case 'open_dict':
152
+ return readDict(tok, t.offset, depth);
153
+ case 'close_arr':
154
+ case 'close_dict':
155
+ throw new ParseError('pdf/parser/unbalanced',
156
+ `unexpected ${t.kind}`,
157
+ { context: { offset: t.offset } });
158
+ default:
159
+ throw new ParseError('pdf/parser/unexpected-token',
160
+ `unexpected token of kind "${t.kind}"`,
161
+ { context: { offset: t.offset, kind: t.kind } });
162
+ }
163
+ }
164
+
165
+ function tryReadRef(tok, intTok) {
166
+ const at = tok.pos();
167
+ const p1 = tok.peek();
168
+ if (!p1 || p1.kind !== 'int') {
169
+ return { type: 'int', value: intTok.value };
170
+ }
171
+ tok.next();
172
+ const p2 = tok.peek();
173
+ if (p2 && p2.kind === 'kw' && p2.value === 'R') {
174
+ tok.next();
175
+ return { type: 'ref', num: intTok.value | 0, gen: p1.value | 0 };
176
+ }
177
+ tok.seek(at);
178
+ return { type: 'int', value: intTok.value };
179
+ }
180
+
181
+ function readArray(tok, startOff, depth) {
182
+ const items = [];
183
+ for (;;) {
184
+ const p = tok.peek();
185
+ if (!p) {
186
+ throw new ParseError('pdf/parser/unterminated-array',
187
+ 'array not closed',
188
+ { context: { offset: startOff } });
189
+ }
190
+ if (p.kind === 'close_arr') { tok.next(); return { type: 'array', items }; }
191
+ if (items.length >= parserLimits.maxArrayLen) {
192
+ throw new ParseError('pdf/parser/array-too-long',
193
+ 'array exceeds parserLimits.maxArrayLen',
194
+ { context: { length: items.length,
195
+ limit: parserLimits.maxArrayLen,
196
+ offset: startOff } });
197
+ }
198
+ const sub = tok.next();
199
+ items.push(continueParse(tok, sub, (depth | 0) + 1));
200
+ }
201
+ }
202
+
203
+ function readDict(tok, startOff, depth) {
204
+ const entries = {};
205
+ for (;;) {
206
+ const p = tok.peek();
207
+ if (!p) {
208
+ throw new ParseError('pdf/parser/unterminated-dict',
209
+ 'dict not closed',
210
+ { context: { offset: startOff } });
211
+ }
212
+ if (p.kind === 'close_dict') { tok.next(); return { type: 'dict', entries }; }
213
+ if (p.kind !== 'name') {
214
+ throw new ParseError('pdf/parser/dict-key-not-name',
215
+ 'expected /Name as dict key',
216
+ { context: { offset: p.offset, kind: p.kind } });
217
+ }
218
+ const keyTok = tok.next();
219
+ const sub = tok.next();
220
+ if (!sub) {
221
+ throw new ParseError('pdf/parser/eof', 'unexpected end of input');
222
+ }
223
+ const value = continueParse(tok, sub, (depth | 0) + 1);
224
+ entries[keyTok.value] = value;
225
+ }
226
+ }
227
+
228
+ function parseIndirect(tok, resolveRef) {
229
+ const t1 = tok.next();
230
+ if (!t1 || t1.kind !== 'int') {
231
+ throw new ParseError('pdf/parser/indirect-bad-num',
232
+ 'expected object number',
233
+ { context: { offset: t1 ? t1.offset : tok.pos() } });
234
+ }
235
+ const t2 = tok.next();
236
+ if (!t2 || t2.kind !== 'int') {
237
+ throw new ParseError('pdf/parser/indirect-bad-gen',
238
+ 'expected generation number',
239
+ { context: { offset: t2 ? t2.offset : tok.pos() } });
240
+ }
241
+ const t3 = tok.next();
242
+ if (!t3 || t3.kind !== 'kw' || t3.value !== 'obj') {
243
+ throw new ParseError('pdf/parser/indirect-missing-obj',
244
+ 'expected obj keyword',
245
+ { context: { offset: t3 ? t3.offset : tok.pos() } });
246
+ }
247
+
248
+ const value = parseObject(tok);
249
+ let body = value;
250
+
251
+ const after = tok.peek();
252
+ if (value.type === 'dict' && after && after.kind === 'kw' && after.value === 'stream') {
253
+ tok.next();
254
+ const bytes = tok.bytes;
255
+ let p = tok.pos();
256
+ if (p < bytes.length && bytes[p] === CR) p++;
257
+ if (p < bytes.length && bytes[p] === LF) p++;
258
+ tok.seek(p);
259
+
260
+ const dataStart = tok.pos();
261
+ const length = resolveStreamLength(value, resolveRef);
262
+ let dataEnd;
263
+ if (Number.isFinite(length) && length >= 0
264
+ && dataStart + length <= bytes.length) {
265
+ dataEnd = dataStart + length;
266
+ } else {
267
+ dataEnd = findEndstream(bytes, dataStart,
268
+ parserLimits.maxStreamBytes);
269
+ if (dataEnd < 0) {
270
+ throw new ParseError('pdf/parser/stream/no-endstream',
271
+ 'cannot locate endstream within quota',
272
+ { context: { offset: dataStart,
273
+ num: t1.value, gen: t2.value,
274
+ quota: parserLimits.maxStreamBytes } });
275
+ }
276
+ }
277
+ const raw = bytes.subarray(dataStart, dataEnd);
278
+ tok.seek(dataEnd);
279
+ skipEolWs(tok);
280
+ const endTok = tok.next();
281
+ if (!endTok || endTok.kind !== 'kw' || endTok.value !== 'endstream') {
282
+ throw new ParseError('pdf/parser/stream/expected-endstream',
283
+ 'expected endstream',
284
+ { context: { offset: endTok ? endTok.offset : tok.pos() } });
285
+ }
286
+ body = { type: 'stream', dict: value, raw };
287
+ }
288
+
289
+ const endObj = tok.next();
290
+ if (!endObj || endObj.kind !== 'kw' || endObj.value !== 'endobj') {
291
+ throw new ParseError('pdf/parser/indirect-missing-endobj',
292
+ 'expected endobj',
293
+ { context: { offset: endObj ? endObj.offset : tok.pos(),
294
+ num: t1.value, gen: t2.value } });
295
+ }
296
+ return { num: t1.value, gen: t2.value, value: body };
297
+ }
298
+
299
+ function parseFromBytes(bytes) {
300
+ const tok = tokenize(bytes);
301
+ return parseObject(tok);
302
+ }
303
+ function parseIndirectFromBytes(bytes, at, resolveRef) {
304
+ const tok = tokenize(bytes, { start: at | 0 });
305
+ return parseIndirect(tok, resolveRef);
306
+ }
307
+
308
+ return {
309
+ tokenize,
310
+ parseObject,
311
+ parseIndirect,
312
+ parseFromBytes,
313
+ parseIndirectFromBytes,
314
+ parserLimits,
315
+ setParserLimits,
316
+ obj,
317
+ getEntry,
318
+ isType
319
+ };
320
+ }
321
+ };
@@ -0,0 +1,221 @@
1
+ // Copyright (c) 2026 AwaCloud SAS
2
+ // Author: Matthieu Bouilloux
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ // Dual-licensed; see the NOTICE file for licensing and any additional terms.
5
+
6
+ /**
7
+ * @fileoverview Serialize typed PDF objects back to bytes per
8
+ * ISO 32000-2:2020 §7.3 / §7.5.
9
+ *
10
+ * Public API (via factory):
11
+ *
12
+ * - `serializeObject(obj)` → `Uint8Array`
13
+ * - `serializeIndirect(num, gen, obj)` → `Uint8Array`
14
+ * - `formatReal(n)` → `string`
15
+ *
16
+ * @module pdf/syntax/serializer
17
+ */
18
+
19
+ /**
20
+ * Module factory — worker-safe, self-contained.
21
+ */
22
+ import { pdfErrors } from '../errors.js';
23
+
24
+ export const pdfSerializer = {
25
+ name: 'pdfSerializer',
26
+ dependencies: ['pdfErrors'],
27
+ deps: [pdfErrors],
28
+ factory(errors) {
29
+ const { RenderError } = errors;
30
+ const te = new TextEncoder();
31
+
32
+ function serializeObject(obj) {
33
+ if (!obj || typeof obj.type !== 'string') {
34
+ throw new RenderError('pdf/serializer/bad-input',
35
+ 'serializeObject expects a typed object',
36
+ { context: { typeof: typeof obj } });
37
+ }
38
+ return concat(emitObject(obj));
39
+ }
40
+
41
+ function serializeIndirect(num, gen, body) {
42
+ if (!Number.isFinite(num) || num < 0) {
43
+ throw new RenderError('pdf/serializer/bad-num',
44
+ 'object number must be a non-negative integer',
45
+ { context: { num } });
46
+ }
47
+ if (!Number.isFinite(gen) || gen < 0) {
48
+ throw new RenderError('pdf/serializer/bad-gen',
49
+ 'generation number must be a non-negative integer',
50
+ { context: { gen } });
51
+ }
52
+ const head = te.encode(`${num | 0} ${gen | 0} obj\n`);
53
+ const tail = te.encode('\nendobj\n');
54
+
55
+ if (body && body.type === 'stream') {
56
+ const raw = body.raw instanceof Uint8Array ? body.raw : new Uint8Array(0);
57
+ const dict = body.dict || { type: 'dict', entries: {} };
58
+ const updated = {
59
+ type: 'dict',
60
+ entries: {
61
+ ...dict.entries,
62
+ Length: { type: 'int', value: raw.length }
63
+ }
64
+ };
65
+ const dictBytes = concat(emitObject(updated));
66
+ const streamHead = te.encode('\nstream\n');
67
+ const streamTail = te.encode('\nendstream');
68
+ return concatU8([head, dictBytes, streamHead, raw, streamTail, tail]);
69
+ }
70
+ return concatU8([head, concat(emitObject(body)), tail]);
71
+ }
72
+
73
+ function formatReal(n) {
74
+ if (!Number.isFinite(n)) {
75
+ throw new RenderError('pdf/serializer/bad-real',
76
+ 'real number must be finite', { context: { value: n } });
77
+ }
78
+ if (Number.isInteger(n)) {
79
+ return `${n}`;
80
+ }
81
+ let s = n.toFixed(5);
82
+ if (s.indexOf('.') >= 0) {
83
+ s = s.replace(/0+$/, '');
84
+ if (s.endsWith('.')) s = s.slice(0, -1);
85
+ }
86
+ if (s === '-0') s = '0';
87
+ return s;
88
+ }
89
+
90
+ function emitObject(o) {
91
+ switch (o.type) {
92
+ case 'null': return ['null'];
93
+ case 'bool': return [o.value ? 'true' : 'false'];
94
+ case 'int': return [`${o.value | 0}`];
95
+ case 'real': return [formatReal(o.value)];
96
+ case 'name': return [emitName(o.value)];
97
+ case 'string':return [emitString(o)];
98
+ case 'array': return emitArray(o);
99
+ case 'dict': return emitDict(o);
100
+ case 'ref': return [`${o.num | 0} ${o.gen | 0} R`];
101
+ case 'stream':
102
+ throw new RenderError('pdf/serializer/inline-stream',
103
+ 'stream objects must be serialized via serializeIndirect');
104
+ default:
105
+ throw new RenderError('pdf/serializer/unknown-type',
106
+ `unknown object type "${o.type}"`,
107
+ { context: { type: o.type } });
108
+ }
109
+ }
110
+
111
+ function emitName(value) {
112
+ let out = '/';
113
+ for (let i = 0; i < value.length; i++) {
114
+ const cp = value.codePointAt(i);
115
+ if (cp > 0xFFFF) i++;
116
+ if (cp < 0x21 || cp > 0x7E
117
+ || cp === 0x23 || cp === 0x2F
118
+ || cp === 0x28 || cp === 0x29 || cp === 0x3C || cp === 0x3E
119
+ || cp === 0x5B || cp === 0x5D || cp === 0x7B || cp === 0x7D
120
+ || cp === 0x25) {
121
+ const enc = te.encode(String.fromCodePoint(cp));
122
+ for (const b of enc) out += '#' + b.toString(16).padStart(2, '0').toUpperCase();
123
+ } else {
124
+ out += String.fromCharCode(cp);
125
+ }
126
+ }
127
+ return out;
128
+ }
129
+
130
+ function emitString(o) {
131
+ const bytes = o.value;
132
+ if (!(bytes instanceof Uint8Array)) {
133
+ throw new RenderError('pdf/serializer/bad-string',
134
+ 'string value must be Uint8Array',
135
+ { context: { typeof: typeof bytes } });
136
+ }
137
+ if (o.syntax === 'hex' || shouldUseHex(bytes)) return emitHexString(bytes);
138
+ return emitLiteralString(bytes);
139
+ }
140
+
141
+ function shouldUseHex(bytes) {
142
+ let bad = 0;
143
+ for (let i = 0; i < bytes.length; i++) {
144
+ const b = bytes[i];
145
+ if (b < 0x09 || (b > 0x0D && b < 0x20) || b >= 0x7F) bad++;
146
+ if (bad > bytes.length / 4) return true;
147
+ }
148
+ return false;
149
+ }
150
+
151
+ function emitLiteralString(bytes) {
152
+ const out = ['('];
153
+ for (let i = 0; i < bytes.length; i++) {
154
+ const b = bytes[i];
155
+ if (b === 0x5C) out.push('\\\\');
156
+ else if (b === 0x28) out.push('\\(');
157
+ else if (b === 0x29) out.push('\\)');
158
+ else if (b === 0x0A) out.push('\\n');
159
+ else if (b === 0x0D) out.push('\\r');
160
+ else if (b === 0x09) out.push('\\t');
161
+ else if (b === 0x08) out.push('\\b');
162
+ else if (b === 0x0C) out.push('\\f');
163
+ else if (b < 0x20 || b > 0x7E) {
164
+ out.push('\\' + b.toString(8).padStart(3, '0'));
165
+ } else out.push(String.fromCharCode(b));
166
+ }
167
+ out.push(')');
168
+ return out.join('');
169
+ }
170
+
171
+ function emitHexString(bytes) {
172
+ const H = '0123456789ABCDEF';
173
+ let s = '<';
174
+ for (let i = 0; i < bytes.length; i++) {
175
+ s += H[bytes[i] >> 4] + H[bytes[i] & 0xF];
176
+ }
177
+ return s + '>';
178
+ }
179
+
180
+ function emitArray(o) {
181
+ const parts = ['['];
182
+ let first = true;
183
+ for (const it of o.items) {
184
+ if (!first) parts.push(' ');
185
+ first = false;
186
+ for (const p of emitObject(it)) parts.push(p);
187
+ }
188
+ parts.push(']');
189
+ return parts;
190
+ }
191
+
192
+ function emitDict(o) {
193
+ const parts = ['<<'];
194
+ for (const k of Object.keys(o.entries)) {
195
+ parts.push(' ');
196
+ parts.push(emitName(k));
197
+ parts.push(' ');
198
+ for (const p of emitObject(o.entries[k])) parts.push(p);
199
+ }
200
+ parts.push(' >>');
201
+ return parts;
202
+ }
203
+
204
+ function concat(parts) {
205
+ let s = '';
206
+ for (const p of parts) s += p;
207
+ return te.encode(s);
208
+ }
209
+
210
+ function concatU8(arrays) {
211
+ let n = 0;
212
+ for (const a of arrays) n += a.length;
213
+ const out = new Uint8Array(n);
214
+ let o = 0;
215
+ for (const a of arrays) { out.set(a, o); o += a.length; }
216
+ return out;
217
+ }
218
+
219
+ return { serializeObject, serializeIndirect, formatReal };
220
+ }
221
+ };