@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,514 @@
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 Incremental update writer.
8
+ *
9
+ * `appendIncremental(pdfBytes, updates)` produces a new PDF that
10
+ * carries the original bytes verbatim followed by an "incremental
11
+ * update" section per ISO 32000-2:2020 §7.5.6:
12
+ *
13
+ * originalBytes ‖ newObjects ‖ xref ‖ trailer (with /Prev) ‖ %%EOF
14
+ *
15
+ * Strategy:
16
+ *
17
+ * 1. Locate the previous `startxref` offset in `pdfBytes` so the new
18
+ * trailer can carry `/Prev N`.
19
+ * 2. Locate the previous trailer dict (via `parseTrailerDict`) to
20
+ * carry forward `/Root`, `/Info`, `/Size`, `/ID` unless overridden.
21
+ * 3. Emit each `update` indirect (same shape as `pdfWriter.writeDocument`
22
+ * items: `{ num, gen, value }`) starting at the end of the original
23
+ * bytes; record byte offsets.
24
+ * 4. Emit a fresh classical xref table covering only the updated
25
+ * object numbers (one subsection per contiguous run). Object 0 is
26
+ * only emitted in the section starting at 0; otherwise we emit only
27
+ * the modified runs (per §7.5.6 — incremental xref sections do not
28
+ * need to cover object 0).
29
+ * 5. Emit a trailer with `/Prev = previousXrefOffset`, updated
30
+ * `/Size = max(prev.Size, newMaxNum + 1)`, and carry-over
31
+ * `/Root` / `/Info` / `/ID` if any.
32
+ *
33
+ * The PDF reader (`pdfDocument.readDocument`) already follows the
34
+ * `/Prev` chain — the new entries win over older ones automatically.
35
+ *
36
+ * Section form: the form of the update follows the form of the
37
+ * base's NEWEST section — the one `startxref` designates.
38
+ *
39
+ * - Classical table there → steps 4-5 above, byte-identical to the
40
+ * writer's original, table-only output (only the newest trailer is typed).
41
+ * - `/Type /XRef` stream there → the update ends with an uncompressed
42
+ * cross-reference stream (`pdfXref.buildXrefStream`) carrying `/W`,
43
+ * `/Index`, `/Size`, `/Prev`, `/Root` (+ `/Info`, `/ID`). `/Root`,
44
+ * `/Info`, `/ID`, `/Size` default to the newest-first merge of every
45
+ * section's dict, as `readDocument` types it (a linearized file's
46
+ * main stream lacks `/Root`; the first-page one carries it).
47
+ * - Hybrid-reference base (a classical trailer with `/XRefStm`, anywhere
48
+ * in the chain) → refused, `pdf/incremental/hybrid-base` (two
49
+ * conforming readers may resolve different objects in such a file).
50
+ * - Neither form at `startxref` → refused,
51
+ * `pdf/incremental/unsupported-base`.
52
+ *
53
+ * Constraints / scope:
54
+ *
55
+ * - No ObjStm grouping (see Item #7b).
56
+ * - Does not patch existing objects in-place — only appends.
57
+ *
58
+ * @module pdf/document/incrementalWriter
59
+ */
60
+
61
+ /**
62
+ * Module factory — worker-safe, self-contained.
63
+ */
64
+ import { pdfErrors } from '../errors.js';
65
+ import { pdfSerializer } from '../syntax/serializer.js';
66
+ import { pdfTokenizer } from '../syntax/tokenizer.js';
67
+ import { pdfParser } from '../syntax/parser.js';
68
+ import { pdfXref } from '../syntax/xref.js';
69
+ import { pdfTrailer } from '../syntax/trailer.js';
70
+
71
+ export const pdfIncrementalWriter = {
72
+ name: 'pdfIncrementalWriter',
73
+ dependencies: [
74
+ 'pdfErrors',
75
+ 'pdfSerializer',
76
+ 'pdfTokenizer',
77
+ 'pdfParser',
78
+ 'pdfXref',
79
+ 'pdfTrailer'
80
+ ],
81
+ deps: [pdfErrors, pdfSerializer, pdfTokenizer, pdfParser, pdfXref, pdfTrailer],
82
+ factory(errors, serializerMod, tokenizerMod, parserMod, xrefMod, trailerMod) {
83
+ const { RenderError, ParseError } = errors;
84
+ const serializeIndirect = serializerMod.serializeIndirect;
85
+ const serializeObject = serializerMod.serializeObject;
86
+ const locateStartXref = xrefMod.locateStartXref;
87
+ const readStartXref = xrefMod.readStartXref;
88
+ const parseXrefTable = xrefMod.parseXrefTable;
89
+ const parseTrailerDict = xrefMod.parseTrailerDict;
90
+ const readXrefStreamDict = xrefMod.readXrefStreamDict;
91
+ const buildXrefStream = xrefMod.buildXrefStream;
92
+ const typeTrailer = trailerMod.typeTrailer;
93
+
94
+ // Trailer / xref-stream dict keys that describe ONE section only
95
+ // (§7.5.5, §7.5.8.2) — same set as pdfDocument's trailer merge.
96
+ const SECTION_LOCAL_KEYS = new Set([
97
+ 'Prev', 'XRefStm', 'Type', 'W', 'Index', 'Length',
98
+ 'Filter', 'DecodeParms', 'F', 'FFilter', 'FDecodeParms', 'DL'
99
+ ]);
100
+
101
+ const te = new TextEncoder();
102
+
103
+ function pad10(n) { return String(n).padStart(10, '0'); }
104
+
105
+ function hexLit(bytes) {
106
+ const H = '0123456789ABCDEF';
107
+ let s = '<';
108
+ for (let i = 0; i < bytes.length; i++) {
109
+ s += H[bytes[i] >> 4] + H[bytes[i] & 0xF];
110
+ }
111
+ return s + '>';
112
+ }
113
+
114
+ function concat(arrays) {
115
+ let n = 0;
116
+ for (const a of arrays) n += a.length;
117
+ const out = new Uint8Array(n);
118
+ let o = 0;
119
+ for (const a of arrays) { out.set(a, o); o += a.length; }
120
+ return out;
121
+ }
122
+
123
+ function buildXrefSections(offsets, gens) {
124
+ // offsets : Map<num, byteOffset>
125
+ // gens : Map<num, gen>
126
+ // Always start with the free-list head (object 0).
127
+ const nums = Array.from(offsets.keys()).sort((a, b) => a - b);
128
+ const sections = [];
129
+
130
+ // Object 0 subsection: emit just the head (1 entry).
131
+ sections.push({ first: 0, count: 1, entries: ['0000000000 65535 f \n'] });
132
+
133
+ // Contiguous runs of updated nums.
134
+ let i = 0;
135
+ while (i < nums.length) {
136
+ let j = i;
137
+ while (j + 1 < nums.length && nums[j + 1] === nums[j] + 1) j++;
138
+ const entries = [];
139
+ for (let k = i; k <= j; k++) {
140
+ const n = nums[k];
141
+ const g = gens.get(n) | 0;
142
+ entries.push(`${pad10(offsets.get(n))} ${String(g).padStart(5, '0')} n \n`);
143
+ }
144
+ sections.push({ first: nums[i], count: (j - i + 1), entries });
145
+ i = j + 1;
146
+ }
147
+ let s = 'xref\n';
148
+ for (const sec of sections) {
149
+ s += `${sec.first} ${sec.count}\n`;
150
+ for (const e of sec.entries) s += e;
151
+ }
152
+ return te.encode(s);
153
+ }
154
+
155
+ function buildTrailer(opts) {
156
+ const parts = [`trailer\n<< /Size ${opts.size}`];
157
+ parts.push(` /Root ${opts.root.num} ${opts.root.gen | 0} R`);
158
+ if (opts.info) parts.push(` /Info ${opts.info.num} ${opts.info.gen | 0} R`);
159
+ if (opts.id) {
160
+ parts.push(' /ID [');
161
+ parts.push(hexLit(opts.id[0]));
162
+ parts.push(hexLit(opts.id[1]));
163
+ parts.push(']');
164
+ }
165
+ // An update over an encrypted document repeats its /Encrypt
166
+ // (ISO 32000-2 §7.5.6). An indirect reference is the usual
167
+ // form; a direct dictionary is serialised like an object body
168
+ // (its strings are binary, so it is spliced in as bytes).
169
+ let directEncrypt = null;
170
+ if (opts.encrypt) {
171
+ if (Number.isInteger(opts.encrypt.num)) {
172
+ parts.push(` /Encrypt ${opts.encrypt.num} ${opts.encrypt.gen | 0} R`);
173
+ } else {
174
+ directEncrypt = serializeObject(opts.encrypt);
175
+ }
176
+ }
177
+ const tail = ` /Prev ${opts.prev} >>\n`
178
+ + `startxref\n${opts.xrefOffset}\n%%EOF\n`;
179
+ if (directEncrypt) {
180
+ parts.push(' /Encrypt ');
181
+ return concat([te.encode(parts.join('')), directEncrypt, te.encode(tail)]);
182
+ }
183
+ parts.push(tail);
184
+ return te.encode(parts.join(''));
185
+ }
186
+
187
+ /** Skip the PDF white-space run (§7.2.3) starting at `at`. */
188
+ function skipWhitespace(bytes, at) {
189
+ let p = at < 0 ? 0 : at;
190
+ while (p < bytes.length) {
191
+ const b = bytes[p];
192
+ if (b === 0x00 || b === 0x09 || b === 0x0A
193
+ || b === 0x0C || b === 0x0D || b === 0x20) { p++; continue; }
194
+ break;
195
+ }
196
+ return p;
197
+ }
198
+
199
+ /** True when the bytes at `at` (after white space) spell `xref`. */
200
+ function startsXrefTable(bytes, at) {
201
+ const p = skipWhitespace(bytes, at);
202
+ return bytes[p] === 0x78 && bytes[p + 1] === 0x72
203
+ && bytes[p + 2] === 0x65 && bytes[p + 3] === 0x66;
204
+ }
205
+
206
+ /**
207
+ * Walk the cross-reference chain from `at` (newest first) and
208
+ * return each section's form and trailer dict. Tolerant: the walk
209
+ * stops at the first section that does not parse, so an older
210
+ * damaged section never changes what the newest one yields.
211
+ */
212
+ function walkSections(pdfBytes, at) {
213
+ const out = [];
214
+ const seen = new Set();
215
+ let cursor = at;
216
+ for (let safety = 0; safety < 32 && cursor >= 0; safety++) {
217
+ if (seen.has(cursor)) break;
218
+ seen.add(cursor);
219
+ let kind, dict;
220
+ try {
221
+ if (startsXrefTable(pdfBytes, cursor)) {
222
+ const section = parseXrefTable(pdfBytes, cursor);
223
+ dict = parseTrailerDict(pdfBytes, section.end).dict;
224
+ kind = 'table';
225
+ } else {
226
+ dict = readXrefStreamDict(pdfBytes, cursor).dict;
227
+ kind = 'stream';
228
+ }
229
+ } catch (_) {
230
+ break;
231
+ }
232
+ out.push({ at: cursor, kind, dict });
233
+ const prev = dict && dict.type === 'dict' && dict.entries.Prev;
234
+ if (prev && prev.type === 'int' && prev.value >= 0
235
+ && prev.value !== cursor) {
236
+ cursor = prev.value;
237
+ } else break;
238
+ }
239
+ return out;
240
+ }
241
+
242
+ /**
243
+ * Newest-first merge of the section dicts (same rule as
244
+ * `pdfDocument.readDocument`): each document key comes
245
+ * from the newest section that carries it; section-local keys are
246
+ * never merged.
247
+ */
248
+ function mergeSectionDicts(sections) {
249
+ const entries = {};
250
+ for (const s of sections) {
251
+ if (!s.dict || s.dict.type !== 'dict') continue;
252
+ for (const k of Object.keys(s.dict.entries)) {
253
+ if (SECTION_LOCAL_KEYS.has(k)) continue;
254
+ if (!(k in entries)) entries[k] = s.dict.entries[k];
255
+ }
256
+ }
257
+ return { type: 'dict', entries };
258
+ }
259
+
260
+ /**
261
+ * Hybrid-reference file (§7.5.8.4): a classical trailer carrying
262
+ * `/XRefStm`. Refused: two conforming readers may resolve different objects in such a
263
+ * file, so an update over it could read differently per viewer.
264
+ */
265
+ function refuseHybrid(sections) {
266
+ for (const s of sections) {
267
+ const stm = s.kind === 'table' && s.dict && s.dict.type === 'dict'
268
+ && s.dict.entries.XRefStm;
269
+ if (stm) {
270
+ throw new RenderError('pdf/incremental/hybrid-base',
271
+ 'appendIncremental refuses a hybrid-reference base: the '
272
+ + `classical xref section at offset ${s.at} carries /XRefStm `
273
+ + '(a companion cross-reference stream); updating it could '
274
+ + 'resolve differently in table-only and stream-aware readers',
275
+ { context: { offset: s.at, xrefStm: stm.value } });
276
+ }
277
+ }
278
+ }
279
+
280
+ function readPrevTrailer(pdfBytes) {
281
+ // Mirror of pdfDocument.readDocument's xref-locating logic.
282
+ const sxAt = locateStartXref(pdfBytes);
283
+ if (sxAt < 0) {
284
+ throw new ParseError('pdf/incremental/no-startxref',
285
+ 'pdfBytes has no startxref — not a valid PDF');
286
+ }
287
+ const prevXrefOffset = readStartXref(pdfBytes, sxAt);
288
+ let prev = { xrefOffset: prevXrefOffset, trailer: null, form: 'table' };
289
+ if (startsXrefTable(pdfBytes, prevXrefOffset)) {
290
+ // Classical base: the newest trailer alone is typed, exactly
291
+ // as the original table-only writer did — this path's output
292
+ // is byte-identical.
293
+ try {
294
+ const section = parseXrefTable(pdfBytes, prevXrefOffset);
295
+ const { dict } = parseTrailerDict(pdfBytes, section.end);
296
+ prev.trailer = typeTrailer(dict);
297
+ } catch (_) {
298
+ // unreadable — leave trailer null (opts.root required).
299
+ }
300
+ refuseHybrid(walkSections(pdfBytes, prevXrefOffset));
301
+ return prev;
302
+ }
303
+ // Stream base: the newest section must be a
304
+ // /Type /XRef stream; anything else is not a base this writer
305
+ // can extend.
306
+ try {
307
+ readXrefStreamDict(pdfBytes, prevXrefOffset);
308
+ } catch (e) {
309
+ throw new ParseError('pdf/incremental/unsupported-base',
310
+ 'appendIncremental cannot extend this base: startxref '
311
+ + `designates offset ${prevXrefOffset}, which starts neither a `
312
+ + 'classical xref table nor a /Type /XRef cross-reference stream',
313
+ { context: { offset: prevXrefOffset, cause: e && e.code } });
314
+ }
315
+ const sections = walkSections(pdfBytes, prevXrefOffset);
316
+ refuseHybrid(sections);
317
+ prev.form = 'stream';
318
+ try {
319
+ prev.trailer = typeTrailer(mergeSectionDicts(sections));
320
+ } catch (_) {
321
+ // no /Root or /Size anywhere in the chain — opts.root required.
322
+ }
323
+ return prev;
324
+ }
325
+
326
+ function maxNumOf(updates) {
327
+ let m = 0;
328
+ for (const it of updates) {
329
+ if (it && Number.isFinite(it.num) && it.num > m) m = it.num;
330
+ }
331
+ return m;
332
+ }
333
+
334
+ /**
335
+ * readBaseTrailer(pdfBytes) — the trailer an update over `pdfBytes`
336
+ * starts from, without writing anything.
337
+ *
338
+ * Walks the whole cross-reference chain from `startxref` (newest
339
+ * first) and types the newest-first MERGE of every section's dict
340
+ * (the rule `pdfDocument.readDocument` applies) — for a
341
+ * classical base too, so a caller allocating object numbers sees
342
+ * the same `/Size` a reader sees. The base is vetted exactly as
343
+ * `appendIncremental` vets it: a hybrid-reference base throws
344
+ * `pdf/incremental/hybrid-base`, a `startxref` designating neither
345
+ * form throws `pdf/incremental/unsupported-base`.
346
+ *
347
+ * Returns `{ form: 'table' | 'stream', xrefOffset, trailer }` where
348
+ * `trailer` is the typed merged trailer (`{ size, root, info?, id?,
349
+ * … }`, see `pdfTrailer.typeTrailer`) or `null` when no section
350
+ * supplies a usable `/Size` + `/Root`.
351
+ */
352
+ function readBaseTrailer(pdfBytes) {
353
+ if (!(pdfBytes instanceof Uint8Array)) {
354
+ throw new RenderError('pdf/incremental/bad-input',
355
+ 'readBaseTrailer expects a Uint8Array');
356
+ }
357
+ const prev = readPrevTrailer(pdfBytes);
358
+ let trailer = null;
359
+ try {
360
+ trailer = typeTrailer(mergeSectionDicts(
361
+ walkSections(pdfBytes, prev.xrefOffset)));
362
+ } catch (_) {
363
+ // no /Root or /Size anywhere in the chain.
364
+ }
365
+ return { form: prev.form, xrefOffset: prev.xrefOffset, trailer };
366
+ }
367
+
368
+ /**
369
+ * appendIncremental(pdfBytes, opts) — append an incremental
370
+ * update.
371
+ *
372
+ * opts:
373
+ * updates : Array<{ num, gen?, value }> — required
374
+ * root : { num, gen } | undefined — defaults to prev /Root
375
+ * info : { num, gen } | undefined — defaults to prev /Info
376
+ * id : [Uint8Array, Uint8Array] — defaults to prev /ID
377
+ * size : number | undefined — defaults to max(prev.Size, newMax+1)
378
+ * encrypt : { num, gen } | undefined — the base's /Encrypt, repeated
379
+ * in the update's trailer (or cross-reference stream dict)
380
+ * right after /ID; required for a conforming update over
381
+ * an encrypted document (ISO 32000-2 §7.5.6). Never
382
+ * defaulted from the base; absent → output unchanged. A
383
+ * direct dictionary (typed `{ type: 'dict' }`) is written
384
+ * verbatim in a classical trailer; a cross-reference
385
+ * stream update refuses it (`pdf/xref/bad-stream-section`).
386
+ *
387
+ * Over a stream base the cross-reference stream object takes number
388
+ * max(size, prev.Size, newMax+1) and the written /Size is one more.
389
+ *
390
+ * Returns Uint8Array.
391
+ */
392
+ function appendIncremental(pdfBytes, opts) {
393
+ return appendSection(pdfBytes, opts).bytes;
394
+ }
395
+
396
+ /**
397
+ * appendIncrementalWithOffsets(pdfBytes, opts) — exactly
398
+ * `appendIncremental` (same options, same validation, byte-identical
399
+ * output), also reporting where each appended object landed.
400
+ *
401
+ * Returns `{ bytes, offsets, xrefOffset }`: `offsets` is a
402
+ * `Map<num, byteOffset>` giving the absolute offset of each update's
403
+ * `num gen obj` header in `bytes`; `xrefOffset` is where the update's
404
+ * cross-reference section (table or stream object) starts — the
405
+ * offset the new `startxref` records.
406
+ */
407
+ function appendIncrementalWithOffsets(pdfBytes, opts) {
408
+ return appendSection(pdfBytes, opts);
409
+ }
410
+
411
+ /** Shared body of `appendIncremental` / `appendIncrementalWithOffsets`. */
412
+ function appendSection(pdfBytes, opts) {
413
+ if (!(pdfBytes instanceof Uint8Array)) {
414
+ throw new RenderError('pdf/incremental/bad-input',
415
+ 'appendIncremental expects (Uint8Array, opts)');
416
+ }
417
+ if (!opts || !Array.isArray(opts.updates)) {
418
+ throw new RenderError('pdf/incremental/no-updates',
419
+ 'opts.updates must be an array');
420
+ }
421
+ const updates = opts.updates.slice().sort((a, b) => a.num - b.num);
422
+ for (const it of updates) {
423
+ if (!it || !Number.isFinite(it.num) || it.num < 1) {
424
+ throw new RenderError('pdf/incremental/bad-update',
425
+ 'each update needs num >= 1',
426
+ { context: { item: it } });
427
+ }
428
+ }
429
+
430
+ const prev = readPrevTrailer(pdfBytes);
431
+ const root = opts.root
432
+ || (prev.trailer && prev.trailer.root)
433
+ || null;
434
+ if (!root || !Number.isFinite(root.num)) {
435
+ throw new RenderError('pdf/incremental/no-root',
436
+ 'opts.root required when previous trailer cannot be read');
437
+ }
438
+ const info = opts.info
439
+ || (prev.trailer && prev.trailer.info)
440
+ || null;
441
+ const id = opts.id
442
+ || (prev.trailer && prev.trailer.id)
443
+ || null;
444
+ const prevSize = (prev.trailer && Number.isFinite(prev.trailer.size))
445
+ ? prev.trailer.size : 0;
446
+
447
+ // Emit body: starting cursor = end of original bytes.
448
+ // PDF spec recommends a leading newline before incremental
449
+ // section if the previous file did not end with one. We
450
+ // always insert one to be safe.
451
+ const parts = [pdfBytes];
452
+ let cursor = pdfBytes.length;
453
+ const offsets = new Map();
454
+ const gens = new Map();
455
+
456
+ // Ensure separator newline.
457
+ const sep = te.encode('\n');
458
+ parts.push(sep);
459
+ cursor += sep.length;
460
+
461
+ for (const { num, gen, value } of updates) {
462
+ offsets.set(num, cursor);
463
+ gens.set(num, gen | 0);
464
+ const bytes = serializeIndirect(num, gen | 0, value);
465
+ parts.push(bytes);
466
+ cursor += bytes.length;
467
+ }
468
+
469
+ const xrefOffset = cursor;
470
+ const newMax = maxNumOf(updates);
471
+ const size = (opts.size | 0) || Math.max(prevSize, newMax + 1);
472
+
473
+ if (prev.form === 'stream') {
474
+ // The base's newest section is a cross-reference
475
+ // stream, so the update is one too (§7.5.8) — its own
476
+ // object takes the first number past every other one.
477
+ const xrefNum = Math.max(size, prevSize, newMax + 1);
478
+ const entries = [];
479
+ for (const [num, offset] of offsets) {
480
+ entries.push({ num, offset, gen: gens.get(num) });
481
+ }
482
+ parts.push(buildXrefStream({
483
+ num: xrefNum,
484
+ offset: xrefOffset,
485
+ entries,
486
+ size: xrefNum + 1,
487
+ prev: prev.xrefOffset,
488
+ root,
489
+ info,
490
+ id,
491
+ encrypt: opts.encrypt
492
+ }));
493
+ parts.push(te.encode(`startxref\n${xrefOffset}\n%%EOF\n`));
494
+ return { bytes: concat(parts), offsets, xrefOffset };
495
+ }
496
+
497
+ parts.push(buildXrefSections(offsets, gens));
498
+
499
+ parts.push(buildTrailer({
500
+ size,
501
+ root,
502
+ info,
503
+ id,
504
+ encrypt: opts.encrypt,
505
+ prev: prev.xrefOffset,
506
+ xrefOffset
507
+ }));
508
+
509
+ return { bytes: concat(parts), offsets, xrefOffset };
510
+ }
511
+
512
+ return { appendIncremental, appendIncrementalWithOffsets, readBaseTrailer };
513
+ }
514
+ };
@@ -0,0 +1,131 @@
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 Page typing per ISO 32000-2:2020 §7.7.3.3.
8
+ *
9
+ * A Page dict (`/Type /Page`) carries `/Parent`, `/MediaBox`,
10
+ * `/CropBox`, `/Resources`, `/Contents`, `/Rotate`, optional
11
+ * `/Annots`, `/Group`, `/Thumb`, `/Tabs`, `/StructParents`, `/Metadata`,
12
+ * etc.
13
+ *
14
+ * `/Contents` may be a single stream ref, an array of stream refs, or
15
+ * — rarely — an inline stream. The typed record exposes it as an
16
+ * array of refs (possibly empty).
17
+ *
18
+ * `/Resources` is inheritable from the parent page tree node (§7.7.3.4)
19
+ * — resolution is left to the document layer.
20
+ *
21
+ * @module pdf/document/page
22
+ */
23
+
24
+ /**
25
+ * Module factory — worker-safe, self-contained.
26
+ */
27
+ import { pdfErrors } from '../errors.js';
28
+ import { pdfParser } from '../syntax/parser.js';
29
+
30
+ export const pdfPage = {
31
+ name: 'pdfPage',
32
+ dependencies: ['pdfErrors', 'pdfParser'],
33
+ deps: [pdfErrors, pdfParser],
34
+ factory(errors, parserMod) {
35
+ const { ParseError } = errors;
36
+ const isType = (parserMod && parserMod.isType)
37
+ || ((v, kind) => !!(v && v.type === kind));
38
+
39
+ const KNOWN = new Set([
40
+ 'Type', 'Parent', 'LastModified', 'Resources', 'MediaBox',
41
+ 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox', 'BoxColorInfo',
42
+ 'Contents', 'Rotate', 'Group', 'Thumb', 'B', 'Dur', 'Trans',
43
+ 'Annots', 'AA', 'Metadata', 'PieceInfo', 'StructParents',
44
+ 'ID', 'PZ', 'SeparationInfo', 'Tabs', 'TemplateInstantiated',
45
+ 'PresSteps', 'UserUnit', 'VP', 'AF', 'OutputIntents',
46
+ 'DPart', 'AssociatedFiles'
47
+ ]);
48
+
49
+ function toBox(v) {
50
+ if (!v || v.type !== 'array' || v.items.length !== 4) return null;
51
+ const r = new Array(4);
52
+ for (let i = 0; i < 4; i++) {
53
+ const it = v.items[i];
54
+ if (!it || (it.type !== 'int' && it.type !== 'real')) return null;
55
+ r[i] = it.value;
56
+ }
57
+ return r;
58
+ }
59
+
60
+ function toRotate(v) {
61
+ if (!v || (v.type !== 'int' && v.type !== 'real')) return 0;
62
+ const n = v.value | 0;
63
+ const m = ((n % 360) + 360) % 360;
64
+ if (m % 90 !== 0) return 0;
65
+ return m;
66
+ }
67
+
68
+ function toContentsRefs(v) {
69
+ if (!v) return [];
70
+ if (v.type === 'ref') return [{ num: v.num, gen: v.gen }];
71
+ if (v.type === 'array') {
72
+ const out = [];
73
+ for (const it of v.items) {
74
+ if (it.type === 'ref') out.push({ num: it.num, gen: it.gen });
75
+ }
76
+ return out;
77
+ }
78
+ return [];
79
+ }
80
+
81
+ function toAnnots(v) {
82
+ if (!v) return [];
83
+ if (v.type === 'ref') return [v];
84
+ if (v.type === 'array') return v.items.slice();
85
+ return [];
86
+ }
87
+
88
+ function typePage(dict) {
89
+ if (!isType(dict, 'dict')) {
90
+ throw new ParseError('pdf/page/not-dict',
91
+ 'Page must be a dictionary',
92
+ { context: { type: dict && dict.type } });
93
+ }
94
+ const e = dict.entries;
95
+ if (e.Type && (e.Type.type !== 'name' || e.Type.value !== 'Page')) {
96
+ throw new ParseError('pdf/page/bad-type',
97
+ '/Type must be /Page when present',
98
+ { context: { actual: e.Type.value } });
99
+ }
100
+
101
+ const out = {
102
+ parent: e.Parent && e.Parent.type === 'ref'
103
+ ? { num: e.Parent.num, gen: e.Parent.gen } : null,
104
+ mediaBox: toBox(e.MediaBox),
105
+ resources: e.Resources || null,
106
+ contents: toContentsRefs(e.Contents),
107
+ rotate: toRotate(e.Rotate),
108
+ annots: toAnnots(e.Annots),
109
+ raw: dict,
110
+ _extras: {}
111
+ };
112
+
113
+ if (e.CropBox) out.cropBox = toBox(e.CropBox);
114
+ if (e.BleedBox) out.bleedBox = toBox(e.BleedBox);
115
+ if (e.TrimBox) out.trimBox = toBox(e.TrimBox);
116
+ if (e.ArtBox) out.artBox = toBox(e.ArtBox);
117
+ if (e.UserUnit && e.UserUnit.type === 'real') out.userUnit = e.UserUnit.value;
118
+ if (e.UserUnit && e.UserUnit.type === 'int') out.userUnit = e.UserUnit.value;
119
+ if (e.Tabs && e.Tabs.type === 'name') out.tabs = e.Tabs.value;
120
+ if (e.Metadata) out.metadata = e.Metadata;
121
+ if (e.Group) out.group = e.Group;
122
+
123
+ for (const k of Object.keys(e)) {
124
+ if (!KNOWN.has(k)) out._extras[k] = e[k];
125
+ }
126
+ return out;
127
+ }
128
+
129
+ return { typePage };
130
+ }
131
+ };