@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,103 @@
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-tree walker per ISO 32000-2:2020 §7.7.3.
8
+ *
9
+ * The page tree is a balanced tree of `/Type /Pages` nodes whose leaves
10
+ * are `/Type /Page` dicts. Internal nodes carry `/Kids` (an array of
11
+ * refs) and `/Count` (number of leaves in the subtree). The catalog
12
+ * holds a reference to the root.
13
+ *
14
+ * This module walks an already-resolved tree (the caller passes a
15
+ * `resolveRef(ref) → obj` function) and produces an ordered list of
16
+ * page refs.
17
+ *
18
+ * @module pdf/document/pages
19
+ */
20
+
21
+ /**
22
+ * Module factory — worker-safe, self-contained.
23
+ */
24
+ import { pdfErrors } from '../errors.js';
25
+ import { pdfParser } from '../syntax/parser.js';
26
+
27
+ export const pdfPages = {
28
+ name: 'pdfPages',
29
+ dependencies: ['pdfErrors', 'pdfParser'],
30
+ deps: [pdfErrors, pdfParser],
31
+ factory(errors, parserMod) {
32
+ const { ParseError } = errors;
33
+ const isType = (parserMod && parserMod.isType)
34
+ || ((v, kind) => !!(v && v.type === kind));
35
+
36
+ function walkPageTree(rootRef, resolveRef, opts) {
37
+ const maxDepth = (opts && opts.maxDepth) || 64;
38
+ const maxPages = (opts && opts.maxPages) || 200000;
39
+ const out = [];
40
+ const visited = new Set();
41
+ walk(rootRef, 0);
42
+ return out;
43
+
44
+ function walk(ref, depth) {
45
+ if (depth > maxDepth) {
46
+ throw new ParseError('pdf/pages/max-depth',
47
+ 'page tree depth limit exceeded',
48
+ { context: { depth, maxDepth } });
49
+ }
50
+ const key = ref.num + ':' + ref.gen;
51
+ if (visited.has(key)) {
52
+ throw new ParseError('pdf/pages/cycle',
53
+ 'cycle detected in page tree',
54
+ { context: { ref } });
55
+ }
56
+ visited.add(key);
57
+
58
+ const node = resolveRef({ type: 'ref', num: ref.num, gen: ref.gen });
59
+ if (!isType(node, 'dict')) {
60
+ throw new ParseError('pdf/pages/not-dict',
61
+ 'page tree node is not a dict',
62
+ { context: { ref } });
63
+ }
64
+ const t = node.entries.Type;
65
+ const isPage = (t && t.type === 'name' && t.value === 'Page')
66
+ || (!node.entries.Kids && !t);
67
+ if (isPage) {
68
+ out.push({ num: ref.num, gen: ref.gen });
69
+ if (out.length > maxPages) {
70
+ throw new ParseError('pdf/pages/too-many',
71
+ 'page count limit exceeded',
72
+ { context: { maxPages } });
73
+ }
74
+ return;
75
+ }
76
+
77
+ const kids = node.entries.Kids;
78
+ if (!kids || kids.type !== 'array') {
79
+ throw new ParseError('pdf/pages/missing-kids',
80
+ 'intermediate page tree node missing /Kids array',
81
+ { context: { ref, hasKids: !!kids } });
82
+ }
83
+ for (const k of kids.items) {
84
+ if (k.type !== 'ref') {
85
+ throw new ParseError('pdf/pages/non-ref-kid',
86
+ 'page tree /Kids entries must be indirect refs',
87
+ { context: { kind: k.type } });
88
+ }
89
+ walk({ num: k.num, gen: k.gen }, depth + 1);
90
+ }
91
+ }
92
+ }
93
+
94
+ function readPageCount(node) {
95
+ if (!isType(node, 'dict')) return null;
96
+ const c = node.entries.Count;
97
+ if (!c || (c.type !== 'int' && c.type !== 'real')) return null;
98
+ return c.value | 0;
99
+ }
100
+
101
+ return { walkPageTree, readPageCount };
102
+ }
103
+ };
@@ -0,0 +1,146 @@
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 Resource dictionary resolution per ISO 32000-2:2020
8
+ * §7.8.3.
9
+ *
10
+ * A Page (or Form XObject) carries a `/Resources` dict with six
11
+ * named sub-dicts that map local names (used in content streams) to
12
+ * indirect references:
13
+ *
14
+ * | Sub-dict | Maps to |
15
+ * |---------------|----------------------------------|
16
+ * | `/Font` | Font dicts |
17
+ * | `/XObject` | Image / Form XObjects |
18
+ * | `/ColorSpace` | ColorSpace arrays / refs |
19
+ * | `/ExtGState` | ExtGState dicts |
20
+ * | `/Pattern` | Pattern dicts |
21
+ * | `/Shading` | Shading dicts |
22
+ *
23
+ * A `/ProcSet` array exists for legacy PDFs and is read but never
24
+ * required at L2 (the spec deprecates it in PDF 2.0).
25
+ *
26
+ * Resources are **inheritable**: a Page that lacks `/Resources` of its
27
+ * own inherits from its `/Parent` chain in the page tree (§7.7.3.4).
28
+ *
29
+ * @module pdf/document/resources
30
+ */
31
+
32
+ /**
33
+ * Module factory — worker-safe, self-contained.
34
+ */
35
+ import { pdfErrors } from '../errors.js';
36
+ import { pdfParser } from '../syntax/parser.js';
37
+
38
+ export const pdfResources = {
39
+ name: 'pdfResources',
40
+ dependencies: ['pdfErrors', 'pdfParser'],
41
+ deps: [pdfErrors, pdfParser],
42
+ factory(errors, parserObjMod) {
43
+ const { ParseError } = errors;
44
+ const isType = (parserObjMod && parserObjMod.isType)
45
+ || ((v, kind) => !!(v && v.type === kind));
46
+
47
+ const CATEGORIES = ['Font', 'XObject', 'ColorSpace', 'ExtGState', 'Pattern', 'Shading'];
48
+
49
+ /**
50
+ * Type a `/Resources` dictionary into its six category maps.
51
+ *
52
+ * A category value (`/Font`, `/ExtGState`, …) may itself be an
53
+ * indirect reference (ISO 32000-2 §7.3.10 — any object may be
54
+ * indirect); when `resolveRef` is supplied such a reference is
55
+ * resolved through it (the document's own resolver, which reaches
56
+ * objects inside object streams). Without a resolver a referenced
57
+ * category is rejected as before (`pdf/resources/bad-subdict`).
58
+ *
59
+ * @param {object|null} dict Typed `/Resources` dict, or `null`.
60
+ * @param {(ref: object) => object} [resolveRef] Indirect-ref resolver.
61
+ * @returns {object} The typed `Resources` shape.
62
+ */
63
+ function typeResources(dict, resolveRef) {
64
+ const out = {
65
+ Font: {}, XObject: {}, ColorSpace: {},
66
+ ExtGState: {}, Pattern: {}, Shading: {},
67
+ ProcSet: null,
68
+ raw: dict || null
69
+ };
70
+ if (!dict) return out;
71
+ if (!isType(dict, 'dict')) {
72
+ throw new ParseError('pdf/resources/not-dict',
73
+ '/Resources must be a dictionary',
74
+ { context: { type: dict.type } });
75
+ }
76
+ for (const cat of CATEGORIES) {
77
+ let sub = dict.entries[cat];
78
+ if (!sub) continue;
79
+ if (sub.type === 'ref' && typeof resolveRef === 'function') {
80
+ sub = resolveRef(sub);
81
+ if (!sub || sub.type === 'null') continue;
82
+ }
83
+ if (sub.type !== 'dict') {
84
+ throw new ParseError('pdf/resources/bad-subdict',
85
+ `/${cat} entry must be a dictionary`,
86
+ { context: { category: cat, type: sub.type } });
87
+ }
88
+ out[cat] = { ...sub.entries };
89
+ }
90
+ if (dict.entries.ProcSet && dict.entries.ProcSet.type === 'array') {
91
+ out.ProcSet = dict.entries.ProcSet.items
92
+ .filter(it => it.type === 'name')
93
+ .map(it => it.value);
94
+ }
95
+ return out;
96
+ }
97
+
98
+ function resolvePageResources(pageDict, resolveRef) {
99
+ let node = pageDict;
100
+ const visited = new Set();
101
+ for (let safety = 0; safety < 64; safety++) {
102
+ if (!isType(node, 'dict')) break;
103
+ if (node.entries.Resources) {
104
+ const r = node.entries.Resources;
105
+ if (r.type === 'ref') return typeResources(resolveRef(r), resolveRef);
106
+ if (r.type === 'dict') return typeResources(r, resolveRef);
107
+ throw new ParseError('pdf/resources/bad-shape',
108
+ '/Resources must be a dict or indirect ref',
109
+ { context: { type: r.type } });
110
+ }
111
+ const parent = node.entries.Parent;
112
+ if (!parent || parent.type !== 'ref') break;
113
+ const key = parent.num + ':' + parent.gen;
114
+ if (visited.has(key)) {
115
+ throw new ParseError('pdf/resources/cycle',
116
+ 'cycle in page-tree parent chain',
117
+ { context: { ref: parent } });
118
+ }
119
+ visited.add(key);
120
+ node = resolveRef(parent);
121
+ }
122
+ return typeResources(null);
123
+ }
124
+
125
+ function lookupResource(resources, category, name, resolveRef) {
126
+ const cat = resources[category];
127
+ if (!cat) {
128
+ throw new ParseError('pdf/resources/unknown-category',
129
+ `unknown resource category "${category}"`,
130
+ { context: { category } });
131
+ }
132
+ const entry = cat[name];
133
+ if (!entry) {
134
+ throw new ParseError('pdf/resources/missing-name',
135
+ `resource /${category} /${name} not found`,
136
+ { context: { category, name, available: Object.keys(cat) } });
137
+ }
138
+ if (entry.type === 'ref' && typeof resolveRef === 'function') {
139
+ return resolveRef(entry);
140
+ }
141
+ return entry;
142
+ }
143
+
144
+ return { typeResources, resolvePageResources, lookupResource };
145
+ }
146
+ };
@@ -0,0 +1,211 @@
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 Document writer — turn a model into PDF bytes (`%PDF-2.0` unless `opts.version` says otherwise).
8
+ *
9
+ * Strategy:
10
+ *
11
+ * 1. Emit the header (`%PDF-<version>\n`) and the binary comment line (bytes `0x25 0xE2 0xE3 0xCF 0xD3 0x0A`).
12
+ * 2. For each indirect object, serialize via `serializeIndirect` and
13
+ * record its byte offset.
14
+ * 3. Emit a classical xref table (`xref` keyword + subsection
15
+ * `0 N` + 20-byte entries — object 0 is the head of the free list
16
+ * with `0000000000 65535 f `).
17
+ * 4. Emit the trailer dict + `startxref` pointer + `%%EOF`.
18
+ *
19
+ * @module pdf/document/writer
20
+ */
21
+
22
+ /**
23
+ * Module factory — worker-safe, self-contained.
24
+ */
25
+ import { pdfErrors } from '../errors.js';
26
+ import { pdfSerializer } from '../syntax/serializer.js';
27
+
28
+ export const pdfWriter = {
29
+ name: 'pdfWriter',
30
+ dependencies: ['pdfErrors', 'pdfSerializer'],
31
+ deps: [pdfErrors, pdfSerializer],
32
+ factory(errors, serializerMod) {
33
+ const { RenderError } = errors;
34
+ const serializeIndirect = serializerMod.serializeIndirect;
35
+
36
+ const te = new TextEncoder();
37
+
38
+ function pad10(n) { return String(n).padStart(10, '0'); }
39
+
40
+ function hexLit(bytes) {
41
+ const H = '0123456789ABCDEF';
42
+ let s = '<';
43
+ for (let i = 0; i < bytes.length; i++) {
44
+ s += H[bytes[i] >> 4] + H[bytes[i] & 0xF];
45
+ }
46
+ return s + '>';
47
+ }
48
+
49
+ function concat(arrays) {
50
+ let n = 0;
51
+ for (const a of arrays) n += a.length;
52
+ const out = new Uint8Array(n);
53
+ let o = 0;
54
+ for (const a of arrays) { out.set(a, o); o += a.length; }
55
+ return out;
56
+ }
57
+
58
+ function validateIndirects(list) {
59
+ let prev = -1;
60
+ for (const it of list) {
61
+ if (!it || !Number.isFinite(it.num) || it.num < 1) {
62
+ throw new RenderError('pdf/writer/bad-indirect',
63
+ 'each indirect requires num >= 1',
64
+ { context: { item: it } });
65
+ }
66
+ if (it.num === prev) {
67
+ throw new RenderError('pdf/writer/duplicate-num',
68
+ 'duplicate object number',
69
+ { context: { num: it.num } });
70
+ }
71
+ prev = it.num;
72
+ }
73
+ }
74
+
75
+ function buildXref(maxNum, offsets) {
76
+ let s = `xref\n0 ${maxNum + 1}\n`;
77
+ s += '0000000000 65535 f \n';
78
+ for (let n = 1; n <= maxNum; n++) {
79
+ if (offsets.has(n)) {
80
+ s += pad10(offsets.get(n)) + ' 00000 n \n';
81
+ } else {
82
+ s += '0000000000 00000 f \n';
83
+ }
84
+ }
85
+ return te.encode(s);
86
+ }
87
+
88
+ function buildTrailer(opts) {
89
+ const parts = [`trailer\n<< /Size ${opts.size}`];
90
+ parts.push(` /Root ${opts.root.num} ${opts.root.gen | 0} R`);
91
+ if (opts.info) parts.push(` /Info ${opts.info.num} ${opts.info.gen | 0} R`);
92
+ if (opts.id) {
93
+ parts.push(' /ID [');
94
+ parts.push(hexLit(opts.id[0]));
95
+ parts.push(hexLit(opts.id[1]));
96
+ parts.push(']');
97
+ }
98
+ parts.push(' >>\n');
99
+ parts.push(`startxref\n${opts.xrefOffset}\n%%EOF\n`);
100
+ return te.encode(parts.join(''));
101
+ }
102
+
103
+ function writeDocument(opts) {
104
+ if (!opts || !Array.isArray(opts.indirects)) {
105
+ throw new RenderError('pdf/writer/bad-input',
106
+ 'writeDocument expects { indirects: [...] }');
107
+ }
108
+ if (!opts.root || !Number.isFinite(opts.root.num)) {
109
+ throw new RenderError('pdf/writer/no-root',
110
+ 'writeDocument requires opts.root');
111
+ }
112
+ const version = opts.version || '2.0';
113
+ if (!/^\d\.\d$/.test(version)) {
114
+ throw new RenderError('pdf/writer/bad-version',
115
+ 'version must look like "x.y"',
116
+ { context: { version } });
117
+ }
118
+ const indirects = opts.indirects.slice().sort((a, b) => a.num - b.num);
119
+ validateIndirects(indirects);
120
+
121
+ const parts = [];
122
+ let cursor = 0;
123
+ const offsets = new Map();
124
+
125
+ function push(u8) {
126
+ parts.push(u8);
127
+ cursor += u8.length;
128
+ }
129
+
130
+ push(te.encode(`%PDF-${version}\n`));
131
+ push(new Uint8Array([0x25, 0xE2, 0xE3, 0xCF, 0xD3, 0x0A]));
132
+
133
+ const maxNum = indirects.length > 0 ? indirects[indirects.length - 1].num : 0;
134
+ for (const { num, gen, value } of indirects) {
135
+ offsets.set(num, cursor);
136
+ push(serializeIndirect(num, gen, value));
137
+ }
138
+
139
+ const xrefOffset = cursor;
140
+ push(buildXref(maxNum, offsets));
141
+
142
+ const size = maxNum + 1;
143
+ push(buildTrailer({
144
+ size,
145
+ root: opts.root,
146
+ info: opts.info,
147
+ id: opts.id,
148
+ xrefOffset
149
+ }));
150
+
151
+ return concat(parts);
152
+ }
153
+
154
+ /**
155
+ * Snapshot a `pdf.read()` model into an ordered `{ num, gen, value }`
156
+ * list ready for `writeDocument`. Forces full resolution first; an
157
+ * in-use xref entry that cannot be resolved is NOT written (its
158
+ * indirect never reaches the cache), and the loss is reported rather
159
+ * than silent.
160
+ *
161
+ * Lenient by default: the list is still returned, with the skipped
162
+ * entries on its non-enumerable `skippedObjects` property
163
+ * (`Array<{ num, gen, code }>`, empty when nothing was skipped;
164
+ * `code` is the caught error's code when it is a typed pdf error,
165
+ * else `'unknown'`). With `opts.strict === true` the call throws a
166
+ * `RenderError` coded `pdf/writer/unresolvable-objects` instead,
167
+ * carrying the same list in `context.objects`.
168
+ *
169
+ * @param {object} model A model produced by `pdf.read()`.
170
+ * @param {{ strict?: boolean }} [opts]
171
+ * @returns {Array<{num:number, gen:number, value:object}>}
172
+ */
173
+ function assembleIndirects(model, opts) {
174
+ if (!model || !model._raw || typeof model._raw.resolve !== 'function') {
175
+ throw new RenderError('pdf/writer/bad-model',
176
+ 'assembleIndirects requires a model produced by pdf.read()');
177
+ }
178
+ const skipped = [];
179
+ for (const k of Object.keys(model.xref.entries)) {
180
+ const e = model.xref.entries[k];
181
+ if (e.free) continue;
182
+ try {
183
+ model._raw.resolve({ type: 'ref', num: Number(k), gen: e.gen });
184
+ } catch (err) {
185
+ skipped.push({
186
+ num: Number(k),
187
+ gen: e.gen,
188
+ code: errors.isPdfError(err) ? err.code : 'unknown'
189
+ });
190
+ }
191
+ }
192
+ if (skipped.length > 0 && opts && opts.strict === true) {
193
+ throw new RenderError('pdf/writer/unresolvable-objects',
194
+ `${skipped.length} object${skipped.length === 1 ? '' : 's'} could not be resolved and would be dropped`,
195
+ { context: { objects: skipped } });
196
+ }
197
+ const out = [];
198
+ for (const [key, rec] of model._raw.indirects.entries()) {
199
+ const [n, g] = key.split(':').map(Number);
200
+ out.push({ num: n, gen: g, value: rec.value });
201
+ }
202
+ out.sort((a, b) => a.num - b.num);
203
+ Object.defineProperty(out, 'skippedObjects', {
204
+ value: skipped, enumerable: false, writable: true, configurable: true
205
+ });
206
+ return out;
207
+ }
208
+
209
+ return { writeDocument, assembleIndirects };
210
+ }
211
+ };