@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,156 @@
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 Optional Content Configuration dictionary typing per
8
+ * ISO 32000-2:2020 §8.11.4.
9
+ *
10
+ * `/OCProperties./D` (default config) and entries of `/OCProperties./Configs`
11
+ * are dictionaries describing the visibility state of a set of OCGs. The
12
+ * L3 typing exposes `name`, `creator`, `baseState`, `on`, `off`, `intent`,
13
+ * `as`, `order`, `listMode`, `rbGroups`, `locked`.
14
+ *
15
+ * Unknown entries are preserved in `_extras`.
16
+ *
17
+ * @module pdf/ocg/config
18
+ */
19
+
20
+ /**
21
+ * Module factory.
22
+ */
23
+ import { pdfErrors } from '../errors.js';
24
+ import { pdfParser } from '../syntax/parser.js';
25
+
26
+ export const pdfOCConfig = {
27
+ name: 'pdfOCConfig',
28
+ dependencies: ['pdfErrors', 'pdfParser'],
29
+ deps: [pdfErrors, pdfParser],
30
+ factory(errors, parser) {
31
+ const { ParseError } = errors;
32
+ const { isType } = parser;
33
+
34
+ const KNOWN = new Set([
35
+ 'Name', 'Creator', 'BaseState', 'ON', 'OFF',
36
+ 'Intent', 'AS', 'Order', 'ListMode', 'RBGroups', 'Locked'
37
+ ]);
38
+ const BASE_STATES = new Set(['ON', 'OFF', 'Unchanged']);
39
+
40
+ function refsOf(node, label) {
41
+ if (node.type !== 'array') {
42
+ throw new ParseError('pdf/ocg/config/bad-refs',
43
+ '/' + label + ' must be an array of refs',
44
+ { context: { type: node.type } });
45
+ }
46
+ return node.items.filter((r) => r && r.type === 'ref');
47
+ }
48
+
49
+ function typeUsageApp(d) {
50
+ if (!isType(d, 'dict')) {
51
+ throw new ParseError('pdf/ocg/config/as/not-dict',
52
+ '/AS entry must be a dictionary',
53
+ { context: { type: d && d.type } });
54
+ }
55
+ const e = d.entries;
56
+ const out = { raw: d };
57
+ if (e.Event && e.Event.type === 'name') out.event = e.Event.value;
58
+ if (e.OCGs && e.OCGs.type === 'array') {
59
+ out.ocgs = e.OCGs.items.filter((r) => r.type === 'ref');
60
+ }
61
+ if (e.Category && e.Category.type === 'array') {
62
+ out.category = e.Category.items
63
+ .filter((c) => c.type === 'name').map((c) => c.value);
64
+ }
65
+ return out;
66
+ }
67
+
68
+ function typeOCConfig(dict) {
69
+ if (!isType(dict, 'dict')) {
70
+ throw new ParseError('pdf/ocg/config/not-dict',
71
+ 'OCConfig must be a dictionary',
72
+ { context: { type: dict && dict.type } });
73
+ }
74
+ const e = dict.entries;
75
+ const out = { raw: dict, _extras: {} };
76
+
77
+ if (e.Name) {
78
+ if (e.Name.type !== 'string') {
79
+ throw new ParseError('pdf/ocg/config/bad-name',
80
+ '/Name must be a string', { context: { type: e.Name.type } });
81
+ }
82
+ out.name = e.Name.value;
83
+ }
84
+ if (e.Creator) {
85
+ if (e.Creator.type !== 'string') {
86
+ throw new ParseError('pdf/ocg/config/bad-creator',
87
+ '/Creator must be a string', { context: { type: e.Creator.type } });
88
+ }
89
+ out.creator = e.Creator.value;
90
+ }
91
+ if (e.BaseState) {
92
+ if (e.BaseState.type !== 'name' || !BASE_STATES.has(e.BaseState.value)) {
93
+ throw new ParseError('pdf/ocg/config/bad-base-state',
94
+ '/BaseState must be ON|OFF|Unchanged',
95
+ { context: { actual: e.BaseState.value } });
96
+ }
97
+ out.baseState = e.BaseState.value;
98
+ }
99
+ if (e.ON) out.on = refsOf(e.ON, 'ON');
100
+ if (e.OFF) out.off = refsOf(e.OFF, 'OFF');
101
+ if (e.Locked) out.locked = refsOf(e.Locked, 'Locked');
102
+
103
+ if (e.Intent) {
104
+ if (e.Intent.type === 'name') out.intent = [e.Intent.value];
105
+ else if (e.Intent.type === 'array') {
106
+ out.intent = e.Intent.items
107
+ .filter((x) => x && x.type === 'name')
108
+ .map((x) => x.value);
109
+ } else {
110
+ throw new ParseError('pdf/ocg/config/bad-intent',
111
+ '/Intent must be name or array of names',
112
+ { context: { type: e.Intent.type } });
113
+ }
114
+ }
115
+ if (e.AS) {
116
+ if (e.AS.type !== 'array') {
117
+ throw new ParseError('pdf/ocg/config/bad-as',
118
+ '/AS must be an array', { context: { type: e.AS.type } });
119
+ }
120
+ out.as = e.AS.items.map(typeUsageApp);
121
+ }
122
+ if (e.Order) {
123
+ if (e.Order.type !== 'array') {
124
+ throw new ParseError('pdf/ocg/config/bad-order',
125
+ '/Order must be an array', { context: { type: e.Order.type } });
126
+ }
127
+ out.order = e.Order.items;
128
+ }
129
+ if (e.ListMode) {
130
+ if (e.ListMode.type !== 'name') {
131
+ throw new ParseError('pdf/ocg/config/bad-list-mode',
132
+ '/ListMode must be a name',
133
+ { context: { type: e.ListMode.type } });
134
+ }
135
+ out.listMode = e.ListMode.value;
136
+ }
137
+ if (e.RBGroups) {
138
+ if (e.RBGroups.type !== 'array') {
139
+ throw new ParseError('pdf/ocg/config/bad-rb',
140
+ '/RBGroups must be an array',
141
+ { context: { type: e.RBGroups.type } });
142
+ }
143
+ out.rbGroups = e.RBGroups.items
144
+ .filter((it) => it && it.type === 'array')
145
+ .map((it) => it.items.filter((r) => r.type === 'ref'));
146
+ }
147
+
148
+ for (const k of Object.keys(e)) {
149
+ if (!KNOWN.has(k)) out._extras[k] = e[k];
150
+ }
151
+ return out;
152
+ }
153
+
154
+ return { typeOCConfig, typeUsageApp };
155
+ }
156
+ };
package/src/ocg/ocg.js ADDED
@@ -0,0 +1,124 @@
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 Optional Content Group (OCG) typing per ISO 32000-2:2020
8
+ * §8.11.2.
9
+ *
10
+ * An OCG is a dictionary `/Type /OCG` referenced by content streams,
11
+ * annotations and form XObjects whose visibility can be toggled at view
12
+ * time. The L3 typing decodes `/Name`, `/Intent`, and the `/Usage`
13
+ * sub-dictionary (CreatorInfo, Language, Export, Zoom, Print, View,
14
+ * User, PageElement plus state convenience entries).
15
+ *
16
+ * Unknown entries are preserved in `_extras`.
17
+ *
18
+ * @module pdf/ocg/ocg
19
+ */
20
+
21
+ /**
22
+ * Module factory.
23
+ */
24
+ import { pdfErrors } from '../errors.js';
25
+ import { pdfParser } from '../syntax/parser.js';
26
+
27
+ export const pdfOCG = {
28
+ name: 'pdfOCG',
29
+ dependencies: ['pdfErrors', 'pdfParser'],
30
+ deps: [pdfErrors, pdfParser],
31
+ factory(errors, parser) {
32
+ const { ParseError } = errors;
33
+ const { isType } = parser;
34
+
35
+ const KNOWN_TOP = new Set(['Type', 'Name', 'Intent', 'Usage']);
36
+ const KNOWN_USAGE = new Set([
37
+ 'CreatorInfo', 'Language', 'Export', 'Zoom', 'Print', 'View',
38
+ 'User', 'PageElement', 'ViewState', 'PrintState', 'ExportState'
39
+ ]);
40
+
41
+ function lower(s) { return s.charAt(0).toLowerCase() + s.slice(1); }
42
+
43
+ function typeUsage(dict) {
44
+ if (!isType(dict, 'dict')) {
45
+ throw new ParseError('pdf/ocg/usage/not-dict',
46
+ 'Usage must be a dictionary',
47
+ { context: { type: dict && dict.type } });
48
+ }
49
+ const e = dict.entries;
50
+ const out = { raw: dict, _extras: {} };
51
+
52
+ for (const k of KNOWN_USAGE) {
53
+ if (e[k] !== undefined) {
54
+ const v = e[k];
55
+ if (k === 'PrintState' || k === 'ViewState' || k === 'ExportState') {
56
+ if (v.type !== 'name') {
57
+ throw new ParseError('pdf/ocg/usage/bad-state',
58
+ '/' + k + ' must be a name',
59
+ { context: { type: v.type } });
60
+ }
61
+ out[lower(k)] = v.value;
62
+ } else {
63
+ out[lower(k)] = v;
64
+ }
65
+ }
66
+ }
67
+ for (const k of Object.keys(e)) {
68
+ if (!KNOWN_USAGE.has(k)) out._extras[k] = e[k];
69
+ }
70
+ return out;
71
+ }
72
+
73
+ function typeOCG(dict) {
74
+ if (!isType(dict, 'dict')) {
75
+ throw new ParseError('pdf/ocg/not-dict',
76
+ 'OCG must be a dictionary',
77
+ { context: { type: dict && dict.type } });
78
+ }
79
+ const e = dict.entries;
80
+ if (e.Type && (e.Type.type !== 'name' || e.Type.value !== 'OCG')) {
81
+ throw new ParseError('pdf/ocg/bad-type',
82
+ '/Type entry must be /OCG',
83
+ { context: { actual: e.Type.value } });
84
+ }
85
+ if (!e.Name || e.Name.type !== 'string') {
86
+ throw new ParseError('pdf/ocg/missing-name',
87
+ 'OCG is missing required /Name string',
88
+ { context: { type: e.Name && e.Name.type } });
89
+ }
90
+
91
+ const out = {
92
+ name: e.Name.value,
93
+ raw: dict,
94
+ _extras: {}
95
+ };
96
+
97
+ if (e.Intent) {
98
+ if (e.Intent.type === 'name') {
99
+ out.intent = [e.Intent.value];
100
+ } else if (e.Intent.type === 'array') {
101
+ out.intent = e.Intent.items
102
+ .filter((it) => it && it.type === 'name')
103
+ .map((it) => it.value);
104
+ }
105
+ }
106
+
107
+ if (e.Usage) {
108
+ if (e.Usage.type !== 'dict') {
109
+ throw new ParseError('pdf/ocg/bad-usage',
110
+ '/Usage must be a dictionary',
111
+ { context: { type: e.Usage.type } });
112
+ }
113
+ out.usage = typeUsage(e.Usage);
114
+ }
115
+
116
+ for (const k of Object.keys(e)) {
117
+ if (!KNOWN_TOP.has(k)) out._extras[k] = e[k];
118
+ }
119
+ return out;
120
+ }
121
+
122
+ return { typeOCG, typeUsage };
123
+ }
124
+ };
@@ -0,0 +1,157 @@
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 Outline (bookmarks) tree walker per ISO 32000-2:2020
8
+ * §12.3.3.
9
+ *
10
+ * The outline is a doubly-linked tree of `/Type /Outlines` (root) and
11
+ * outline items. Each item carries `/Title`, `/Parent`, `/Prev`, `/Next`,
12
+ * `/First`, `/Last`, `/Count`, `/A` (action), `/Dest`, `/C` (color, 3
13
+ * numbers), `/F` (style flags). `/SE` (structure element) is deprecated.
14
+ *
15
+ * `walkOutline(rootDict, resolveRef)` returns a flat depth-first list of
16
+ * typed records:
17
+ * `{ ref, title, dest?, action?, count?, parent, prev, next, first,
18
+ * last, color?, F?, _extras }`
19
+ *
20
+ * The caller resolves indirect refs; the walker enforces cycle
21
+ * detection and maxDepth, similarly to pageTree.
22
+ *
23
+ * @module pdf/outline/outline
24
+ */
25
+
26
+ /**
27
+ * Module factory.
28
+ */
29
+ import { pdfErrors } from '../errors.js';
30
+ import { pdfParser } from '../syntax/parser.js';
31
+
32
+ export const pdfOutline = {
33
+ name: 'pdfOutline',
34
+ dependencies: ['pdfErrors', 'pdfParser'],
35
+ deps: [pdfErrors, pdfParser],
36
+ factory(errors, parser) {
37
+ const { ParseError } = errors;
38
+ const { isType } = parser;
39
+
40
+ const KNOWN = new Set([
41
+ 'Title', 'Parent', 'Prev', 'Next', 'First', 'Last',
42
+ 'Count', 'A', 'Dest', 'SE', 'C', 'F', 'Type'
43
+ ]);
44
+
45
+ function typeOutlineItem(dict) {
46
+ if (!isType(dict, 'dict')) {
47
+ throw new ParseError('pdf/outline/item/not-dict',
48
+ 'outline item must be a dictionary',
49
+ { context: { type: dict && dict.type } });
50
+ }
51
+ const e = dict.entries;
52
+ if (!e.Title || e.Title.type !== 'string') {
53
+ throw new ParseError('pdf/outline/missing-title',
54
+ 'outline item missing /Title string',
55
+ { context: { type: e.Title && e.Title.type } });
56
+ }
57
+ const out = { title: e.Title.value, raw: dict, _extras: {} };
58
+ if (e.Parent && e.Parent.type === 'ref') out.parent = e.Parent;
59
+ if (e.Prev && e.Prev.type === 'ref') out.prev = e.Prev;
60
+ if (e.Next && e.Next.type === 'ref') out.next = e.Next;
61
+ if (e.First && e.First.type === 'ref') out.first = e.First;
62
+ if (e.Last && e.Last.type === 'ref') out.last = e.Last;
63
+ if (e.Count && (e.Count.type === 'int' || e.Count.type === 'real')) {
64
+ out.count = e.Count.value | 0;
65
+ }
66
+ if (e.A) out.action = e.A;
67
+ if (e.Dest) out.dest = e.Dest;
68
+ if (e.C && e.C.type === 'array') {
69
+ const c = e.C.items.filter((x) => x.type === 'int' || x.type === 'real')
70
+ .map((x) => x.value);
71
+ if (c.length === 3) out.color = c;
72
+ }
73
+ if (e.F && (e.F.type === 'int' || e.F.type === 'real')) {
74
+ out.F = e.F.value | 0;
75
+ }
76
+ for (const k of Object.keys(e)) {
77
+ if (!KNOWN.has(k)) out._extras[k] = e[k];
78
+ }
79
+ return out;
80
+ }
81
+
82
+ function walkOutline(rootDict, resolveRef, opts) {
83
+ if (!isType(rootDict, 'dict')) {
84
+ throw new ParseError('pdf/outline/not-dict',
85
+ 'outline root must be a dictionary',
86
+ { context: { type: rootDict && rootDict.type } });
87
+ }
88
+ const maxDepth = (opts && opts.maxDepth) || 64;
89
+ const maxItems = (opts && opts.maxItems) || 100000;
90
+ const out = [];
91
+ const visited = new Set();
92
+ const first = rootDict.entries.First;
93
+ if (!first) return out;
94
+ if (first.type !== 'ref') {
95
+ throw new ParseError('pdf/outline/bad-first',
96
+ '/First must be an indirect reference',
97
+ { context: { type: first.type } });
98
+ }
99
+ if (typeof resolveRef !== 'function') {
100
+ throw new ParseError('pdf/outline/no-resolver',
101
+ 'walkOutline requires a resolveRef function');
102
+ }
103
+
104
+ function walkSiblings(ref, depth) {
105
+ if (depth > maxDepth) {
106
+ throw new ParseError('pdf/outline/max-depth',
107
+ 'outline depth limit exceeded',
108
+ { context: { depth, maxDepth } });
109
+ }
110
+ let cur = ref;
111
+ let guard = 0;
112
+ while (cur) {
113
+ if (cur.type !== 'ref') {
114
+ throw new ParseError('pdf/outline/non-ref-sibling',
115
+ 'outline sibling must be an indirect ref',
116
+ { context: { kind: cur.type } });
117
+ }
118
+ const key = cur.num + ':' + cur.gen;
119
+ if (visited.has(key)) {
120
+ throw new ParseError('pdf/outline/cycle',
121
+ 'cycle detected in outline siblings',
122
+ { context: { ref: cur } });
123
+ }
124
+ visited.add(key);
125
+
126
+ const node = resolveRef(cur);
127
+ if (!isType(node, 'dict')) {
128
+ throw new ParseError('pdf/outline/not-dict',
129
+ 'outline item must be a dictionary',
130
+ { context: { ref: cur } });
131
+ }
132
+ const rec = typeOutlineItem(node);
133
+ rec.ref = { num: cur.num, gen: cur.gen };
134
+ out.push(rec);
135
+ if (out.length > maxItems) {
136
+ throw new ParseError('pdf/outline/too-many',
137
+ 'outline item limit exceeded',
138
+ { context: { maxItems } });
139
+ }
140
+
141
+ if (rec.first) walkSiblings(rec.first, depth + 1);
142
+
143
+ cur = rec.next || null;
144
+ if (++guard > maxItems) {
145
+ throw new ParseError('pdf/outline/runaway',
146
+ 'outline sibling chain exceeded item cap');
147
+ }
148
+ }
149
+ }
150
+
151
+ walkSiblings(first, 0);
152
+ return out;
153
+ }
154
+
155
+ return { walkOutline, typeOutlineItem };
156
+ }
157
+ };
package/src/pdf.js ADDED
@@ -0,0 +1,133 @@
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 Top-level `@awacloud/pdf` orchestrator — public entry-point
8
+ * factory that binds the syntax + document layer modules into an
9
+ * ergonomic read/write API exposing `.use(...)` extensions.
10
+ *
11
+ * Strict factory-only descriptor — same pattern as
12
+ * `@awacloud/ooxml/src/docx/docx.js`. All dependencies are declared in
13
+ * `dependencies[]` and received as parameters ; no top-level `import`,
14
+ * no internal bootstrap.
15
+ *
16
+ * Resolution path : register the four arrays of `src/main.js` into a
17
+ * `@awacloud/fw` `ModuleRuntime` and call `runtime.resolve('pdf')`. Tests
18
+ * materialise via the helper `tests/_helpers/build.js` `bootstrapPdf()`.
19
+ *
20
+ * Extension hook — `.use(extension)` is idempotent on `extension.name`.
21
+ *
22
+ * @module pdf/pdf
23
+ */
24
+
25
+ import { pdfErrors } from './errors.js';
26
+ import { pdfShared } from './_shared/index.js';
27
+ import { pdfTokenizer } from './syntax/tokenizer.js';
28
+ import { pdfParserObj } from './syntax/parser-obj.js';
29
+ import { pdfParser } from './syntax/parser.js';
30
+ import { pdfXref } from './syntax/xref.js';
31
+ import { pdfTrailer } from './syntax/trailer.js';
32
+ import { pdfSerializer } from './syntax/serializer.js';
33
+ import { pdfCatalog } from './document/catalog.js';
34
+ import { pdfPages } from './document/pages.js';
35
+ import { pdfPage } from './document/page.js';
36
+ import { pdfDocument } from './document/document.js';
37
+ import { pdfWriter } from './document/writer.js';
38
+
39
+ export const pdf = {
40
+ name: 'pdf',
41
+ dependencies: [
42
+ 'pdfErrors', 'pdfShared',
43
+ 'pdfTokenizer', 'pdfParserObj', 'pdfParser',
44
+ 'pdfXref', 'pdfTrailer', 'pdfSerializer',
45
+ 'pdfCatalog', 'pdfPages', 'pdfPage',
46
+ 'pdfDocument', 'pdfWriter'
47
+ ],
48
+ deps: [pdfErrors, pdfShared, pdfTokenizer, pdfParserObj, pdfParser, pdfXref, pdfTrailer, pdfSerializer, pdfCatalog, pdfPages, pdfPage, pdfDocument, pdfWriter],
49
+
50
+ factory(errors, shared, tokenizer, parserObj, parser,
51
+ xref, trailer, serializer,
52
+ catalog, pages, page,
53
+ docMod, writerMod) {
54
+ const { ContractError } = errors;
55
+
56
+ const readDocument = docMod.readDocument;
57
+ const readHeader = docMod.readHeader;
58
+ const writeDocument = writerMod.writeDocument;
59
+ const assembleIndirects = writerMod.assembleIndirects;
60
+
61
+ const usedExtensions = new Set();
62
+ const api = {
63
+ read(bytes, opts) { return readDocument(bytes, opts); },
64
+ header(bytes) { return readHeader(bytes); },
65
+ /**
66
+ * Serialize a document model back to bytes.
67
+ *
68
+ * For a `read()` Document, `opts.strict === true` (strictly `true`,
69
+ * a truthy non-boolean is NOT strict) throws
70
+ * `pdf/writer/unresolvable-objects` instead of silently dropping
71
+ * an in-use xref entry that cannot be resolved. In lenient mode
72
+ * the dropped entries are listed on the result's non-enumerable
73
+ * `skippedObjects` property and handed once to `opts.onSkipped`
74
+ * (a copy of the list) when it is non-empty. A WriteModel carries
75
+ * no resolvable xref, so `opts` is ignored for it.
76
+ *
77
+ * @param {object} model A `read()` Document or a raw WriteModel.
78
+ * @param {{ strict?: boolean, onSkipped?: (skipped: Array<{num:number, gen:number, code:string}>) => void }} [opts]
79
+ * @returns {Uint8Array} bytes; non-enumerable `skippedObjects` (always an
80
+ * array, `[]` for a WriteModel or a clean Document).
81
+ */
82
+ write(model, opts) {
83
+ const o = opts && typeof opts === 'object' ? opts : {};
84
+ let skipped = [];
85
+ let bytes;
86
+ if (model && model._raw && typeof model._raw.resolve === 'function') {
87
+ const indirects = assembleIndirects(model, o.strict === true ? { strict: true } : undefined);
88
+ skipped = indirects.skippedObjects || [];
89
+ bytes = writeDocument({
90
+ indirects,
91
+ root: model.catalog.pages
92
+ ? { num: model.trailer.root.num, gen: model.trailer.root.gen }
93
+ : model.trailer.root,
94
+ info: model.trailer.info,
95
+ id: model.trailer.id,
96
+ version: '2.0'
97
+ });
98
+ } else {
99
+ bytes = writeDocument(model);
100
+ }
101
+ if (skipped.length > 0 && typeof o.onSkipped === 'function') o.onSkipped(skipped.slice());
102
+ Object.defineProperty(bytes, 'skippedObjects', {
103
+ value: skipped, enumerable: false, writable: true, configurable: true
104
+ });
105
+ return bytes;
106
+ },
107
+ use(ext) {
108
+ if (!ext || typeof ext.name !== 'string' || typeof ext.register !== 'function') {
109
+ throw new ContractError('pdf/use/bad-extension',
110
+ '.use() requires { name: string, register: function }',
111
+ { context: { keys: ext && Object.keys(ext) } });
112
+ }
113
+ if (usedExtensions.has(ext.name)) return api;
114
+ usedExtensions.add(ext.name);
115
+ const next = ext.register(api, { usedExtensions });
116
+ if (next && typeof next === 'object') {
117
+ for (const k of Object.keys(next)) {
118
+ if (k !== 'use') api[k] = next[k];
119
+ }
120
+ }
121
+ return api;
122
+ },
123
+ usedExtension(name) { return usedExtensions.has(name); }
124
+ };
125
+
126
+ // Touch unused but declared deps so the runtime warns on mis-wiring.
127
+ void shared; void tokenizer; void parserObj; void parser;
128
+ void xref; void trailer; void serializer;
129
+ void catalog; void pages; void page;
130
+
131
+ return api;
132
+ }
133
+ };