@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,554 @@
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 Encrypted Document writer (V=4/5/6 handlers, AES-GCM).
8
+ *
9
+ * Wraps `pdfWriter.writeDocument` with an `encrypt` path:
10
+ *
11
+ * - V=4, R=4 — PDF 1.6 Standard Security Handler. method:
12
+ * 'AESV2' (AES-128 CBC) or 'V2' (RC4-128). Per-object key
13
+ * derivation (MD5 of FEK || objId || gen [+ 'sAlT' for AESV2]).
14
+ * - V=5, R=5 — Adobe Extension Level 3 (AES-256-CBC, SHA-256, no
15
+ * hardening loop).
16
+ * - V=5, R=6 — ISO 32000-2:2020 (AES-256-CBC, Algorithm 2.B
17
+ * hardening, SHA-256/384/512). With method 'AESV4' an ISO/TS
18
+ * 32003 AES-256-GCM crypt filter is emitted (IV || ct || tag).
19
+ *
20
+ * Strategy:
21
+ *
22
+ * 1. Derive a 32-byte file encryption key (FEK) from `randomBytes`
23
+ * (caller-supplied for determinism in tests).
24
+ * 2. Build `/O`, `/U`, `/OE`, `/UE` via the per-revision helpers
25
+ * exposed by `pdfStandardV5` / `pdfStandardV6` (`buildUUE`,
26
+ * `buildOOE`).
27
+ * 3. Build `/Perms` (Algorithm 10) carrying `P` + `EncryptMetadata`.
28
+ * 4. Walk the indirect graph, encrypt every `string` (literal or
29
+ * hex) and every `stream` `.raw` payload in place — each with a
30
+ * fresh 16-byte IV — producing a new graph.
31
+ * 5. Append an `/Encrypt` indirect (last object number) to the graph.
32
+ * 6. Call `pdfWriter.writeDocument({ indirects, root, info, id })`
33
+ * with `id` matching the one baked into the FEK derivation when
34
+ * relevant (V=5 ignores /ID in key derivation per the spec).
35
+ *
36
+ * @module pdf/document/encryptedWriter
37
+ */
38
+
39
+ /**
40
+ * Module factory — worker-safe, self-contained.
41
+ */
42
+ import { pdfErrors } from '../errors.js';
43
+ import { pdfWriter } from './writer.js';
44
+ import { pdfStandardV5 } from '../crypto/standardV5.js';
45
+ import { pdfStandardV6 } from '../crypto/standardV6.js';
46
+ import { pdfStandardV4 } from '../crypto/standardV4.js';
47
+ import { pdfAesGcm } from '../crypto/aesGcm.js';
48
+
49
+ export const pdfEncryptedWriter = {
50
+ name: 'pdfEncryptedWriter',
51
+ dependencies: [
52
+ 'pdfErrors',
53
+ 'pdfWriter',
54
+ 'pdfStandardV5',
55
+ 'pdfStandardV6',
56
+ 'pdfStandardV4',
57
+ 'pdfAesGcm'
58
+ ],
59
+ deps: [pdfErrors, pdfWriter, pdfStandardV5, pdfStandardV6, pdfStandardV4, pdfAesGcm],
60
+ factory(errors, writerMod, v5Mod, v6Mod, v4Mod, gcmMod) {
61
+ const { EncryptionError, RenderError } = errors;
62
+ const writeDocument = writerMod && writerMod.writeDocument;
63
+
64
+ if (typeof writeDocument !== 'function') {
65
+ throw new EncryptionError('pdf/crypto/enc-writer/missing-writer',
66
+ 'pdfEncryptedWriter requires pdfWriter.writeDocument');
67
+ }
68
+
69
+ // Cryptographic randomness only: `enc.randomBytes`, else
70
+ // `globalThis.crypto.getRandomValues` (read per call), else a typed refusal.
71
+ function resolveRandomBytes(enc) {
72
+ if (enc.randomBytes) return enc.randomBytes;
73
+ if (globalThis.crypto
74
+ && typeof globalThis.crypto.getRandomValues === 'function') {
75
+ return (n) => globalThis.crypto.getRandomValues(new Uint8Array(n));
76
+ }
77
+ throw new EncryptionError('pdf/crypto/enc-writer/no-random',
78
+ 'no cryptographic random source: pass opts.encrypt.randomBytes '
79
+ + '(crypto.getRandomValues is unavailable)');
80
+ }
81
+
82
+ function selectHandler(version, revision) {
83
+ if (version === 5 && revision === 5) return v5Mod;
84
+ if (version === 5 && revision === 6) return v6Mod;
85
+ if (version === 4 && revision === 4) {
86
+ if (!v4Mod) {
87
+ throw new EncryptionError('pdf/crypto/enc-writer/missing-v4',
88
+ 'V=4 requires pdfStandardV4');
89
+ }
90
+ return v4Mod;
91
+ }
92
+ throw new EncryptionError('pdf/crypto/enc-writer/unsupported-version',
93
+ 'encrypted write supports V=4 R=4, V=5 R=5, or V=5 R=6',
94
+ { context: { version, revision } });
95
+ }
96
+
97
+ function bytesToObj(bytes) {
98
+ // Produce a typed PDF string carrying raw bytes; the
99
+ // serializer will pick a representation (hex preferred
100
+ // when bytes are non-printable, which is the case for
101
+ // AES-256 ciphertext).
102
+ return { type: 'string', value: bytes, syntax: 'hex' };
103
+ }
104
+
105
+ function intObj(n) { return { type: 'int', value: n | 0 }; }
106
+ function nameObj(s) { return { type: 'name', value: s }; }
107
+ function boolObj(b) { return { type: 'bool', value: !!b }; }
108
+
109
+ // Detect /Type /EmbeddedFile streams — used by the EFF dispatch
110
+ // path so we can route through `encryptEmbeddedFile` with the
111
+ // /EFF-resolved method instead of `encryptStream` with /StmF.
112
+ // Mirrors `pdfSecurity.isEmbeddedFileStream` (read side,
113
+ // Follow-up J).
114
+ function isEmbeddedFileStream(value) {
115
+ if (!value || value.type !== 'stream') return false;
116
+ const d = value.dict;
117
+ if (!d || d.type !== 'dict' || !d.entries) return false;
118
+ const t = d.entries.Type;
119
+ if (!t) return false;
120
+ if (t.type === 'name') return t.value === 'EmbeddedFile';
121
+ if (typeof t === 'string') return t === 'EmbeddedFile';
122
+ return false;
123
+ }
124
+
125
+ function encryptObjectInPlace(value, encryptOne, encryptEmbedded) {
126
+ // Recursively walk a typed object graph, encrypting
127
+ // strings and stream payloads with `encryptOne(bytes)`.
128
+ // When `encryptEmbedded` is provided AND a stream is
129
+ // detected as /Type /EmbeddedFile, the stream payload is
130
+ // routed via `encryptEmbedded(bytes)` instead — used by
131
+ // the EFF write dispatch (Follow-up O, ISO 32000-1 §7.6.5).
132
+ if (!value || typeof value !== 'object') return value;
133
+ switch (value.type) {
134
+ case 'string': {
135
+ const ct = encryptOne(value.value);
136
+ return { type: 'string', value: ct, syntax: 'hex' };
137
+ }
138
+ case 'array': {
139
+ const items = new Array(value.items.length);
140
+ for (let i = 0; i < value.items.length; i++) {
141
+ items[i] = encryptObjectInPlace(
142
+ value.items[i], encryptOne, encryptEmbedded);
143
+ }
144
+ return { type: 'array', items };
145
+ }
146
+ case 'dict': {
147
+ const entries = {};
148
+ for (const k of Object.keys(value.entries)) {
149
+ entries[k] = encryptObjectInPlace(
150
+ value.entries[k], encryptOne, encryptEmbedded);
151
+ }
152
+ return { type: 'dict', entries };
153
+ }
154
+ case 'stream': {
155
+ const dict = encryptObjectInPlace(
156
+ value.dict, encryptOne, encryptEmbedded);
157
+ const useEff = encryptEmbedded
158
+ && isEmbeddedFileStream(value);
159
+ const enc = useEff ? encryptEmbedded : encryptOne;
160
+ const raw = value.raw instanceof Uint8Array
161
+ ? enc(value.raw)
162
+ : value.raw;
163
+ return { type: 'stream', dict, raw };
164
+ }
165
+ default:
166
+ return value;
167
+ }
168
+ }
169
+
170
+ function buildEncryptDict(meta) {
171
+ const e = {
172
+ Filter: nameObj('Standard'),
173
+ V: intObj(meta.V),
174
+ R: intObj(meta.R),
175
+ Length: intObj(meta.keyBits),
176
+ P: intObj(meta.P),
177
+ O: bytesToObj(meta.O),
178
+ U: bytesToObj(meta.U),
179
+ EncryptMetadata: boolObj(meta.encryptMetadata !== false)
180
+ };
181
+ if (meta.OE) e.OE = bytesToObj(meta.OE);
182
+ if (meta.UE) e.UE = bytesToObj(meta.UE);
183
+ if (meta.Perms) e.Perms = bytesToObj(meta.Perms);
184
+
185
+ // /CF — crypt filters. /StdCF is the default for /StmF and
186
+ // /StrF. When `effCfm` is distinct from `cfm`, an
187
+ // additional `/StdEFF` crypt filter is emitted and /EFF
188
+ // names it; otherwise /EFF falls back to /StdCF.
189
+ //
190
+ // CFM values:
191
+ // - V=4 + 'AESV2' → CFM=AESV2 (AES-128 CBC), Length=16
192
+ // - V=4 + 'V2' → CFM=V2 (RC4-128), Length=16
193
+ // - V=5 + 'AESV3' → CFM=AESV3 (AES-256 CBC), Length=32
194
+ // - V=5 + 'AESV4' → CFM=AESV4 (AES-256 GCM), Length=32
195
+ const cfm = meta.cfm;
196
+ const cfLength = meta.cfLength;
197
+ const cfEntries = {
198
+ StdCF: {
199
+ type: 'dict',
200
+ entries: {
201
+ CFM: nameObj(cfm),
202
+ AuthEvent: nameObj('DocOpen'),
203
+ Length: intObj(cfLength)
204
+ }
205
+ }
206
+ };
207
+ const effCfm = meta.effCfm;
208
+ const useDistinctEff = effCfm && effCfm !== cfm;
209
+ if (useDistinctEff) {
210
+ cfEntries.StdEFF = {
211
+ type: 'dict',
212
+ entries: {
213
+ CFM: nameObj(effCfm),
214
+ AuthEvent: nameObj('DocOpen'),
215
+ Length: intObj(meta.effCfLength | 0 || cfLength)
216
+ }
217
+ };
218
+ }
219
+ e.CF = { type: 'dict', entries: cfEntries };
220
+ e.StmF = nameObj('StdCF');
221
+ e.StrF = nameObj('StdCF');
222
+ // /EFF (ISO 32000-1 §7.6.5) names the crypt filter used for
223
+ // /Type /EmbeddedFile streams. When unspecified, readers
224
+ // fall back to /StmF — we keep the field omitted in that
225
+ // case to match the prior behaviour.
226
+ if (useDistinctEff) {
227
+ e.EFF = nameObj('StdEFF');
228
+ }
229
+ return { type: 'dict', entries: e };
230
+ }
231
+
232
+ function nextObjNum(indirects) {
233
+ let max = 0;
234
+ for (const it of indirects) {
235
+ if (it && Number.isFinite(it.num) && it.num > max) max = it.num;
236
+ }
237
+ return max + 1;
238
+ }
239
+
240
+ /**
241
+ * writeEncryptedDocument(opts) — produce a /Encrypt-protected
242
+ * PDF.
243
+ *
244
+ * opts mirrors `writeDocument` plus:
245
+ *
246
+ * encrypt: {
247
+ * version: 5,
248
+ * revision: 5 | 6,
249
+ * keyBits: 256,
250
+ * ownerPassword: string | Uint8Array,
251
+ * userPassword: string | Uint8Array,
252
+ * permissions: number, // /P bitmask (Table 24)
253
+ * encryptMetadata?: boolean,
254
+ * randomBytes?: (n) => Uint8Array
255
+ * // default `globalThis.crypto.getRandomValues`; when
256
+ * // neither exists the call throws
257
+ * // `pdf/crypto/enc-writer/no-random`
258
+ * }
259
+ *
260
+ * Returns { bytes, encryptObjNum, fek, id }.
261
+ */
262
+ function writeEncryptedDocument(opts) {
263
+ if (!opts || typeof opts !== 'object') {
264
+ throw new RenderError('pdf/crypto/enc-writer/bad-input',
265
+ 'writeEncryptedDocument requires opts');
266
+ }
267
+ const enc = opts.encrypt;
268
+ if (!enc || typeof enc !== 'object') {
269
+ throw new RenderError('pdf/crypto/enc-writer/no-encrypt',
270
+ 'opts.encrypt is required');
271
+ }
272
+ const version = enc.version | 0;
273
+ const revision = enc.revision | 0;
274
+ const handler = selectHandler(version, revision);
275
+ const rand = resolveRandomBytes(enc);
276
+ const encryptMetadata = enc.encryptMetadata !== false;
277
+ const P = enc.permissions | 0;
278
+
279
+ // Resolve method + key size per (version, revision).
280
+ // Default method:
281
+ // - V=4 R=4 → 'AESV2' (modern V=4 default)
282
+ // - V=5 R=5/6 → 'AESV3' (CBC) ; opt-in 'AESV4' (GCM)
283
+ let method = enc.method;
284
+ if (!method) method = (version === 4) ? 'AESV2' : 'AESV3';
285
+
286
+ // Validate method against (version, revision).
287
+ if (version === 4 && method !== 'AESV2' && method !== 'V2') {
288
+ throw new RenderError('pdf/crypto/enc-writer/bad-method',
289
+ 'V=4 method must be AESV2 or V2',
290
+ { context: { method } });
291
+ }
292
+ if (version === 5 && method !== 'AESV3' && method !== 'AESV4') {
293
+ throw new RenderError('pdf/crypto/enc-writer/bad-method',
294
+ 'V=5 method must be AESV3 or AESV4',
295
+ { context: { method } });
296
+ }
297
+ if (method === 'AESV4' && !gcmMod) {
298
+ throw new EncryptionError('pdf/crypto/enc-writer/missing-gcm',
299
+ 'AESV4 (GCM) requires pdfAesGcm');
300
+ }
301
+
302
+ // Optional /EFF method — distinct cipher for /Type
303
+ // /EmbeddedFile streams (ISO 32000-1 §7.6.5, Follow-up O).
304
+ // When set, the writer dispatches embedded-file streams
305
+ // through `handler.encryptEmbeddedFile` (V=4) or the GCM
306
+ // wrapper (V=5/AESV4) with the EFF method, while regular
307
+ // streams stay on /StmF. Validated against (V, R) using the
308
+ // same rules as `method`.
309
+ const effMethod = enc.effMethod || null;
310
+ if (effMethod) {
311
+ if (version === 4
312
+ && effMethod !== 'AESV2' && effMethod !== 'V2') {
313
+ throw new RenderError('pdf/crypto/enc-writer/bad-eff-method',
314
+ 'V=4 effMethod must be AESV2 or V2',
315
+ { context: { effMethod } });
316
+ }
317
+ if (version === 5
318
+ && effMethod !== 'AESV3' && effMethod !== 'AESV4') {
319
+ throw new RenderError('pdf/crypto/enc-writer/bad-eff-method',
320
+ 'V=5 effMethod must be AESV3 or AESV4',
321
+ { context: { effMethod } });
322
+ }
323
+ if (effMethod === 'AESV4' && !gcmMod) {
324
+ throw new EncryptionError('pdf/crypto/enc-writer/missing-gcm',
325
+ 'AESV4 (GCM) effMethod requires pdfAesGcm');
326
+ }
327
+ }
328
+
329
+ const fekBytes = (version === 4) ? 16 : 32;
330
+ const keyBits = enc.keyBits | 0 || (fekBytes * 8);
331
+
332
+ // id is required for V=4 key derivation (and PDF mandates
333
+ // /ID when /Encrypt is present). Produce it now.
334
+ let id = opts.id;
335
+ if (!id) {
336
+ const a = rand(16);
337
+ const b = rand(16);
338
+ id = [a, b];
339
+ }
340
+ const idFirst = id[0] instanceof Uint8Array ? id[0] : new Uint8Array(id[0]);
341
+
342
+ // Step 1 — fresh FEK + build /O, /U (+ /OE, /UE, /Perms for V=5).
343
+ let fek, O, U, OE, UE, Perms;
344
+ if (version === 4) {
345
+ const ou = handler.buildOU(
346
+ enc.ownerPassword, enc.userPassword, P, idFirst, encryptMetadata);
347
+ O = ou.O;
348
+ U = ou.U;
349
+ fek = ou.fek;
350
+ OE = null; UE = null; Perms = null;
351
+ } else {
352
+ fek = rand(32);
353
+ const uValSalt = rand(8);
354
+ const uKeySalt = rand(8);
355
+ const oValSalt = rand(8);
356
+ const oKeySalt = rand(8);
357
+ const u = handler.buildUUE(enc.userPassword, fek, uValSalt, uKeySalt);
358
+ U = u.U; UE = u.UE;
359
+ const o = handler.buildOOE(enc.ownerPassword, fek, U, oValSalt, oKeySalt);
360
+ O = o.O; OE = o.OE;
361
+ Perms = handler.buildPerms(P, fek, encryptMetadata, rand);
362
+ }
363
+
364
+ // Step 2 — encrypt strings/streams in the graph.
365
+ // Per-object encryption requires (objNum, gen) for V=4.
366
+ // Two factories produced per object — one for /StmF (regular
367
+ // streams + strings), one for /EFF (embedded files) when a
368
+ // distinct effMethod is configured.
369
+ function makeEncryptForMethod(activeMethod) {
370
+ const typedEncrypt = {
371
+ V: version, R: revision, method: activeMethod
372
+ };
373
+ return function makeEnc(objNum, gen) {
374
+ if (activeMethod === 'AESV4') {
375
+ return function encryptOneGcm(plain) {
376
+ const ivProvider = () => rand(12);
377
+ return gcmMod.encryptObjectGcm(
378
+ fek, plain, ivProvider);
379
+ };
380
+ }
381
+ if (version === 4) {
382
+ const isEmbedded = activeMethod === effMethod
383
+ && effMethod !== null
384
+ && typeof handler.encryptEmbeddedFile === 'function';
385
+ return function encryptOneV4(plain) {
386
+ const iv = (activeMethod === 'AESV2')
387
+ ? rand(16) : null;
388
+ return isEmbedded
389
+ ? handler.encryptEmbeddedFile(
390
+ typedEncrypt, fek, objNum, gen, plain, iv)
391
+ : handler.encryptStream(
392
+ typedEncrypt, fek, objNum, gen, plain, iv);
393
+ };
394
+ }
395
+ return function encryptOneV5(plain) {
396
+ const iv = rand(16);
397
+ return handler.encryptStream(
398
+ typedEncrypt, fek, objNum, gen, plain, iv);
399
+ };
400
+ };
401
+ }
402
+ const makeStmEnc = makeEncryptForMethod(method);
403
+ const makeEffEnc = effMethod
404
+ ? makeEncryptForMethod(effMethod)
405
+ : null;
406
+
407
+ const inIndirects = opts.indirects || [];
408
+ const outIndirects = new Array(inIndirects.length);
409
+ for (let i = 0; i < inIndirects.length; i++) {
410
+ const it = inIndirects[i];
411
+ const encStm = makeStmEnc(it.num | 0, it.gen | 0);
412
+ const encEff = makeEffEnc
413
+ ? makeEffEnc(it.num | 0, it.gen | 0)
414
+ : null;
415
+ outIndirects[i] = {
416
+ num: it.num,
417
+ gen: it.gen,
418
+ value: encryptObjectInPlace(it.value, encStm, encEff)
419
+ };
420
+ }
421
+
422
+ // Step 3 — append /Encrypt indirect.
423
+ const encNum = nextObjNum(outIndirects);
424
+ const cfLength = (version === 4) ? 16 : 32;
425
+ outIndirects.push({
426
+ num: encNum,
427
+ gen: 0,
428
+ value: buildEncryptDict({
429
+ V: version, R: revision, keyBits, P,
430
+ O, U, OE, UE, Perms, encryptMetadata,
431
+ cfm: method, cfLength,
432
+ effCfm: effMethod || null,
433
+ effCfLength: effMethod ? cfLength : 0
434
+ })
435
+ });
436
+
437
+ // Step 4 — emit.
438
+ const bytes = writeEncryptedTrailer({
439
+ indirects: outIndirects,
440
+ root: opts.root,
441
+ info: opts.info,
442
+ id,
443
+ version: opts.version,
444
+ encryptRef: { num: encNum, gen: 0 }
445
+ });
446
+
447
+ return {
448
+ bytes, encryptObjNum: encNum, fek, id,
449
+ O, U, OE, UE, Perms,
450
+ version, revision, method, effMethod
451
+ };
452
+ }
453
+
454
+ // The base writeDocument does not know about /Encrypt in the
455
+ // trailer. We inline the same logic but inject the /Encrypt
456
+ // reference into the trailer dict. Implementation mirrors
457
+ // `pdfWriter.writeDocument` and is kept minimal — when the
458
+ // base writer learns /Encrypt natively this can be removed.
459
+ const te = new TextEncoder();
460
+ function concat(arrays) {
461
+ let n = 0;
462
+ for (const a of arrays) n += a.length;
463
+ const out = new Uint8Array(n);
464
+ let o = 0;
465
+ for (const a of arrays) { out.set(a, o); o += a.length; }
466
+ return out;
467
+ }
468
+ function writeEncryptedTrailer(opts) {
469
+ // Trick: emit the body via the base writer (header + objects +
470
+ // xref + trailer), then re-emit only the trailer to include
471
+ // /Encrypt. The base writer is stable and well-tested, so we
472
+ // instead rebuild a parallel path here. Keep it tight.
473
+ //
474
+ // Per the base writer we need: header → objects → xref →
475
+ // trailer (with /Encrypt added) → startxref → %%EOF.
476
+ // The writer module doesn't export its serializer. We must
477
+ // re-route through writeDocument with a placeholder, then
478
+ // patch the trailer.
479
+ //
480
+ // Simpler: re-implement here using the serializer we get
481
+ // from writerMod — but writerMod only exposes writeDocument
482
+ // / assembleIndirects. Strategy: call writeDocument, then
483
+ // patch the trailer dict to insert /Encrypt N 0 R.
484
+
485
+ const baseBytes = writeDocument({
486
+ indirects: opts.indirects,
487
+ root: opts.root,
488
+ info: opts.info,
489
+ id: opts.id,
490
+ version: opts.version
491
+ });
492
+
493
+ // Locate "trailer\n<< /Size ..." and inject " /Encrypt N 0 R"
494
+ // just before the closing " >>". Operates on bytes — the
495
+ // base writer guarantees ASCII for the trailer dict.
496
+ const trailerKey = te.encode('trailer\n<<');
497
+ let pos = -1;
498
+ for (let i = baseBytes.length - 256; i < baseBytes.length - trailerKey.length; i++) {
499
+ if (i < 0) continue;
500
+ let match = true;
501
+ for (let k = 0; k < trailerKey.length; k++) {
502
+ if (baseBytes[i + k] !== trailerKey[k]) { match = false; break; }
503
+ }
504
+ if (match) { pos = i; break;}
505
+ }
506
+ // Fallback — scan whole file (small enough).
507
+ if (pos < 0) {
508
+ outer: for (let i = 0; i < baseBytes.length - trailerKey.length; i++) {
509
+ for (let k = 0; k < trailerKey.length; k++) {
510
+ if (baseBytes[i + k] !== trailerKey[k]) continue outer;
511
+ }
512
+ pos = i; break;
513
+ }
514
+ }
515
+ if (pos < 0) {
516
+ throw new RenderError('pdf/crypto/enc-writer/no-trailer',
517
+ 'base writer output did not contain a trailer');
518
+ }
519
+ // Find ' >>\n' after pos.
520
+ const close = te.encode(' >>\n');
521
+ let cposEnd = -1;
522
+ for (let i = pos; i < baseBytes.length - close.length; i++) {
523
+ let match = true;
524
+ for (let k = 0; k < close.length; k++) {
525
+ if (baseBytes[i + k] !== close[k]) { match = false; break; }
526
+ }
527
+ if (match) { cposEnd = i; break; }
528
+ }
529
+ if (cposEnd < 0) {
530
+ throw new RenderError('pdf/crypto/enc-writer/no-trailer-close',
531
+ 'base writer output did not contain a closing trailer dict');
532
+ }
533
+ const inject = te.encode(
534
+ ` /Encrypt ${opts.encryptRef.num} ${opts.encryptRef.gen | 0} R`);
535
+
536
+ // The trailer offset hasn't changed (objects + xref happen
537
+ // before trailer), but the startxref pointer encoded
538
+ // inside the trailer block also hasn't moved. We must,
539
+ // however, ensure that startxref still points to the same
540
+ // xref offset — which is true because we only inject
541
+ // bytes *after* the trailer offset in the file (within
542
+ // the trailer dict itself, which lives at the end).
543
+ //
544
+ // But xref offsets inside the table are NOT affected
545
+ // (they refer to object positions, all before the
546
+ // trailer). So we can splice safely.
547
+ const head = baseBytes.subarray(0, cposEnd);
548
+ const tail = baseBytes.subarray(cposEnd);
549
+ return concat([head, inject, tail]);
550
+ }
551
+
552
+ return { writeEncryptedDocument };
553
+ }
554
+ };