@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,480 @@
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 Adapter between PDF Font objects and `@awacloud/fonts/embed-pdf`.
8
+ *
9
+ * This is the only file in `@awacloud/pdf` that knows about
10
+ * `@awacloud/fonts/embed-pdf`. It takes a parsed font (the output of
11
+ * `fonts.read(bytes)`) and a set of code points to embed, and produces the
12
+ * PDF dicts needed for a simple (`/TrueType` + `/WinAnsiEncoding`) or a
13
+ * composite (`/Type0` + `/Identity-H` + `/CIDFontType2`) embedding.
14
+ *
15
+ * **Re-derived against the REAL `@awacloud/fonts` contract.**
16
+ * The `@awacloud/fonts/embed-pdf` `subsetForPdf(font, codePoints, opts?)` result
17
+ * carries exactly these eight keys:
18
+ *
19
+ * ```js
20
+ * {
21
+ * subsetBytes: Uint8Array, // packed TrueType SFNT
22
+ * gidMap: Map<oldGid, newGid>,
23
+ * glyphMap: Map<codePoint, newGid>,
24
+ * encoding: null, // Identity-H → cmap-driven
25
+ * widths: number[], // per NEW gid, in FONT UNITS
26
+ * toUnicodeCmap: string, // ready-made CMap stream text
27
+ * postScriptName: string, // 'ABCDEF+Family'
28
+ * fontDescriptor: object // POJO, carries FontFile2 bytes
29
+ * }
30
+ * ```
31
+ *
32
+ * It does NOT return `font`, `subtype`, `baseFont`, `firstChar`, `lastChar`
33
+ * nor `fontFile` — the keys the previous revision of this adapter read,
34
+ * which made every call on a real `Font` throw `fonts/fd-no-font`.
35
+ *
36
+ * Two contracts this adapter owns:
37
+ *
38
+ * 1. **Widths are rescaled here.** `subset.widths[newGid]` is in font units
39
+ * (`hmtx.advanceWidth`); PDF wants 1000/em. Every width this module
40
+ * emits is `round(w * 1000 / parsedFont.unitsPerEm)`.
41
+ * 2. **The descriptor leaves without its font program.** `FontFile2` /
42
+ * `FontFile3` is LIFTED out of the descriptor POJO into the returned
43
+ * `fontFile` (+ `fontFileKey`); the returned `descriptor` dict carries no
44
+ * font-program key. The consumer allocates the stream indirect
45
+ * (`/Length1` = `fontFile.length`) and injects the ref. Same for
46
+ * `toUnicodeStream`, which the consumer must also allocate as an indirect
47
+ * and reference (the serializer refuses an inline stream).
48
+ *
49
+ * 3. **`/ToUnicode` is keyed by character code, so its source is
50
+ * route-dependent.** `embedCid` uses `subset.toUnicodeCmap` as-is (under
51
+ * `Identity-H` the code IS the CID = the subset's new gid, exactly how
52
+ * the subsetter keys it); `embedSimple` builds its own byte-keyed map
53
+ * through `embedBuildToUnicode` (with `{ codeBytes: 1 }`: a one-byte
54
+ * `<00> <FF>` codespace), because a WinAnsi font's codes are bytes, not
55
+ * gids. Never both for one route.
56
+ *
57
+ * @module pdf/font/embed
58
+ */
59
+
60
+ /**
61
+ * Module factory — worker-safe, self-contained.
62
+ */
63
+ import { pdfErrors } from '../errors.js';
64
+ import { embedSubsetForPdf } from '@awacloud/fonts/embed-pdf/subsetForPdf.js';
65
+ import { embedFontDescriptor } from '@awacloud/fonts/embed-pdf/fontDescriptor.js';
66
+ import { embedCidSystemInfo } from '@awacloud/fonts/embed-pdf/cidSystemInfo.js';
67
+ import { embedToUnicodeBuilder } from '@awacloud/fonts/embed-pdf/toUnicodeBuilder.js';
68
+
69
+ export const pdfFontEmbed = {
70
+ name: 'pdfFontEmbed',
71
+ dependencies: [
72
+ 'pdfErrors',
73
+ 'embedSubsetForPdf', 'embedFontDescriptor',
74
+ 'embedCidSystemInfo', 'embedToUnicodeBuilder'
75
+ ],
76
+ deps: [pdfErrors, embedSubsetForPdf, embedFontDescriptor, embedCidSystemInfo, embedToUnicodeBuilder],
77
+ factory(errors, subsetMod, fontDescriptorMod, cidSystemInfoMod, toUnicodeBuilderMod) {
78
+ const { ContractError } = errors;
79
+ const fontsEmbedPdf = {
80
+ subsetForPdf: subsetMod.subsetForPdf,
81
+ buildFontDescriptor: fontDescriptorMod.buildFontDescriptor,
82
+ buildCidSystemInfo: cidSystemInfoMod.buildCidSystemInfo,
83
+ embedBuildToUnicode: toUnicodeBuilderMod.embedBuildToUnicode
84
+ };
85
+
86
+ // Minimal `obj.*` helpers — duplicated rather than depending on
87
+ // pdfParserObj to keep this adapter slim (it's only used from
88
+ // writers that already wired pdfParserObj independently).
89
+ const obj = {
90
+ name: (v) => ({ type: 'name', value: String(v) }),
91
+ int: (v) => ({ type: 'int', value: v | 0 }),
92
+ real: (v) => ({ type: 'real', value: +v }),
93
+ bool: (v) => ({ type: 'bool', value: !!v }),
94
+ string: (v) => ({ type: 'string', value: v }),
95
+ array: (items) => ({ type: 'array', items }),
96
+ dict: (entries) => ({ type: 'dict', entries }),
97
+ stream: (dict, raw) => ({ type: 'stream', dict, raw }),
98
+ nul: () => ({ type: 'null' })
99
+ };
100
+
101
+ // --- WinAnsiEncoding (CP1252), ISO 32000-2 Annex D.2 -------------
102
+ // Byte → code point for the 0x80..0x9F block; 0x20..0x7E and
103
+ // 0xA0..0xFF are Latin-1 identity. Built inside the factory so the
104
+ // descriptor stays capture-free (`fw/no-factory-capture`).
105
+ const WIN_ANSI_HIGH = [
106
+ 0x20AC, null, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
107
+ 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, null, 0x017D, null,
108
+ null, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
109
+ 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, null, 0x017E, 0x0178
110
+ ];
111
+ const WIN_ANSI_BYTE = new Map(); // codePoint → byte
112
+ for (let b = 0x20; b <= 0x7E; b++) WIN_ANSI_BYTE.set(b, b);
113
+ for (let i = 0; i < WIN_ANSI_HIGH.length; i++) {
114
+ if (WIN_ANSI_HIGH[i] != null) WIN_ANSI_BYTE.set(WIN_ANSI_HIGH[i], 0x80 + i);
115
+ }
116
+ for (let b = 0xA0; b <= 0xFF; b++) WIN_ANSI_BYTE.set(b, b);
117
+
118
+ function coerce(v) {
119
+ if (v && typeof v === 'object' && typeof v.type === 'string') return v;
120
+ if (typeof v === 'number') {
121
+ return Number.isInteger(v) ? obj.int(v) : obj.real(v);
122
+ }
123
+ if (typeof v === 'boolean') return obj.bool(v);
124
+ if (typeof v === 'string') {
125
+ if (/^[A-Za-z][\w.-]{0,63}$/.test(v)) return obj.name(v);
126
+ return obj.string(new TextEncoder().encode(v));
127
+ }
128
+ if (Array.isArray(v)) return obj.array(v.map(coerce));
129
+ return obj.nul();
130
+ }
131
+
132
+ /**
133
+ * Split a `buildFontDescriptor` POJO into the dict entries and the
134
+ * raw font program. `FontName` is forced to a PDF name: a subset
135
+ * name (`ABCDEF+Family`) is rejected by `coerce`'s name regex
136
+ * because of the `+`, and would otherwise serialize as a string.
137
+ */
138
+ function splitDescriptor(desc) {
139
+ const src = (desc && typeof desc === 'object') ? desc : {};
140
+ const entries = {};
141
+ let fontFile = null;
142
+ let fontFileKey = 'FontFile2';
143
+ for (const k of Object.keys(src)) {
144
+ if (k === 'FontFile' || k === 'FontFile2' || k === 'FontFile3') {
145
+ fontFileKey = k;
146
+ fontFile = src[k];
147
+ continue;
148
+ }
149
+ entries[k] = (k === 'FontName') ? obj.name(src[k]) : coerce(src[k]);
150
+ }
151
+ if (!entries.Type) entries.Type = obj.name('FontDescriptor');
152
+ return { descriptor: obj.dict(entries), fontFile, fontFileKey };
153
+ }
154
+
155
+ function wrapCidSysInfo(info) {
156
+ if (!info || typeof info !== 'object') return obj.dict({});
157
+ return obj.dict({
158
+ Registry: coerce(info.Registry || 'Adobe'),
159
+ Ordering: coerce(info.Ordering || 'Identity'),
160
+ Supplement: coerce(info.Supplement != null ? info.Supplement : 0)
161
+ });
162
+ }
163
+
164
+ function assertFont(parsedFont) {
165
+ if (!parsedFont
166
+ || typeof parsedFont !== 'object'
167
+ || !parsedFont.unicodeMap
168
+ || typeof parsedFont.glyphIndexForCodePoint !== 'function'
169
+ || typeof parsedFont.advanceWidth !== 'function'
170
+ || !Number.isFinite(parsedFont.unitsPerEm)) {
171
+ throw new ContractError('pdf/embed/bad-font',
172
+ 'embed requires a parsed Font (unicodeMap, glyphIndexForCodePoint, advanceWidth, unitsPerEm)',
173
+ { context: { keys: parsedFont && typeof parsedFont === 'object'
174
+ ? Object.keys(parsedFont) : typeof parsedFont } });
175
+ }
176
+ }
177
+
178
+ /** De-duplicate + sort, rejecting anything that is not a code point. */
179
+ function normaliseCodePoints(codePoints) {
180
+ if (codePoints == null || typeof codePoints[Symbol.iterator] !== 'function') {
181
+ throw new ContractError('pdf/embed/bad-codepoints',
182
+ 'codePoints must be an iterable of integer code points',
183
+ { context: { got: typeof codePoints } });
184
+ }
185
+ const seen = new Set();
186
+ for (const cp of codePoints) {
187
+ if (!Number.isInteger(cp) || cp < 0 || cp > 0x10FFFF) {
188
+ throw new ContractError('pdf/embed/bad-codepoints',
189
+ 'codePoints must contain integer code points in [0, 0x10FFFF]',
190
+ { context: { cp } });
191
+ }
192
+ seen.add(cp);
193
+ }
194
+ if (seen.size === 0) {
195
+ throw new ContractError('pdf/embed/bad-codepoints',
196
+ 'codePoints must not be empty', { context: { size: 0 } });
197
+ }
198
+ return [...seen].sort((a, b) => a - b);
199
+ }
200
+
201
+ /** `subset.widths` is per NEW gid in font units — rescale to 1000/em. */
202
+ function scaledWidthByCp(parsedFont, subset, cps) {
203
+ const upem = parsedFont.unitsPerEm || 1000;
204
+ const glyphMap = (subset.glyphMap instanceof Map) ? subset.glyphMap : new Map();
205
+ const widths = Array.isArray(subset.widths) ? subset.widths : null;
206
+ const out = new Map();
207
+ for (const cp of cps) {
208
+ const ng = glyphMap.get(cp);
209
+ let raw;
210
+ if (widths && ng != null && Number.isFinite(widths[ng])) {
211
+ raw = widths[ng];
212
+ } else {
213
+ raw = parsedFont.advanceWidth(parsedFont.glyphIndexForCodePoint(cp));
214
+ }
215
+ out.set(cp, Math.round((raw * 1000) / upem));
216
+ }
217
+ return out;
218
+ }
219
+
220
+ /** Per NEW gid, in 1000/em — the source of the `/W` array. */
221
+ function scaledWidthByGid(parsedFont, subset) {
222
+ const upem = parsedFont.unitsPerEm || 1000;
223
+ const out = new Map();
224
+ if (Array.isArray(subset.widths)) {
225
+ for (let g = 0; g < subset.widths.length; g++) {
226
+ if (Number.isFinite(subset.widths[g])) {
227
+ out.set(g, Math.round((subset.widths[g] * 1000) / upem));
228
+ }
229
+ }
230
+ return out;
231
+ }
232
+ const glyphMap = (subset.glyphMap instanceof Map) ? subset.glyphMap : new Map();
233
+ for (const [cp, ng] of glyphMap) {
234
+ const raw = parsedFont.advanceWidth(parsedFont.glyphIndexForCodePoint(cp));
235
+ out.set(ng, Math.round((raw * 1000) / upem));
236
+ }
237
+ return out;
238
+ }
239
+
240
+ /** `[ c [w …] c [w …] … ]` — consecutive CIDs grouped into one run. */
241
+ function compactWArray(byGid) {
242
+ const gids = [...byGid.keys()].sort((a, b) => a - b);
243
+ const items = [];
244
+ let i = 0;
245
+ while (i < gids.length) {
246
+ const start = gids[i];
247
+ const run = [byGid.get(gids[i])];
248
+ let j = i + 1;
249
+ while (j < gids.length && gids[j] === gids[j - 1] + 1) {
250
+ run.push(byGid.get(gids[j]));
251
+ j++;
252
+ }
253
+ items.push(obj.int(start));
254
+ items.push(obj.array(run.map(w => obj.int(w))));
255
+ i = j;
256
+ }
257
+ return obj.array(items);
258
+ }
259
+
260
+ function streamOfCmap(cmap) {
261
+ const raw = (cmap instanceof Uint8Array)
262
+ ? cmap
263
+ : new TextEncoder().encode(String(cmap));
264
+ return obj.stream(obj.dict({}), raw);
265
+ }
266
+
267
+ /**
268
+ * A `/ToUnicode` CMap is keyed by the font's CHARACTER CODES, so the
269
+ * two routes need different sources:
270
+ *
271
+ * - composite (`Identity-H`): the code IS the CID, i.e. the subset's
272
+ * new gid, which is exactly what `subset.toUnicodeCmap` is keyed by
273
+ * (`subsetForPdf` inverts `glyphMap` into `newGid → String.fromCodePoint(cp)`).
274
+ * Used as-is; the local builder is only the fallback for a subsetter that omits it.
275
+ * - simple (`WinAnsiEncoding`): the codes are WinAnsi BYTES, so the
276
+ * subset's gid-keyed CMap would be plain wrong. The adapter builds
277
+ * the byte-keyed map and hands it to `embedBuildToUnicode`.
278
+ *
279
+ * Never both for one route — two CMaps over the same glyph set can
280
+ * only diverge.
281
+ */
282
+ function toUnicodeStreamForCid(deps, subset) {
283
+ if (subset.toUnicodeCmap != null) return streamOfCmap(subset.toUnicodeCmap);
284
+ const gidToUnicode = new Map();
285
+ const glyphMap = (subset.glyphMap instanceof Map) ? subset.glyphMap : new Map();
286
+ for (const [cp, ng] of glyphMap) gidToUnicode.set(ng, String.fromCodePoint(cp));
287
+ return streamOfCmap(deps.embedBuildToUnicode(gidToUnicode, {}));
288
+ }
289
+
290
+ function baseFontName(subset) {
291
+ const n = subset && subset.postScriptName;
292
+ return (typeof n === 'string' && n.length) ? n : 'Embedded';
293
+ }
294
+
295
+ function createEmbed(deps) {
296
+ if (!deps
297
+ || typeof deps.subsetForPdf !== 'function'
298
+ || typeof deps.buildFontDescriptor !== 'function'
299
+ || typeof deps.buildCidSystemInfo !== 'function'
300
+ || typeof deps.embedBuildToUnicode !== 'function') {
301
+ throw new ContractError('pdf/embed/missing-fonts-embed',
302
+ 'createEmbed requires the @awacloud/fonts/embed-pdf factory output',
303
+ { context: { keys: deps && Object.keys(deps) } });
304
+ }
305
+
306
+ /**
307
+ * The subset's descriptor already carries the font program; only
308
+ * rebuild it when the subsetter omitted it entirely.
309
+ */
310
+ function descriptorOf(parsedFont, subset) {
311
+ const raw = (subset.fontDescriptor && typeof subset.fontDescriptor === 'object')
312
+ ? subset.fontDescriptor
313
+ : deps.buildFontDescriptor(parsedFont, { subsetBytes: subset.subsetBytes });
314
+ const split = splitDescriptor(raw);
315
+ if (!split.fontFile && subset.subsetBytes instanceof Uint8Array) {
316
+ split.fontFile = subset.subsetBytes;
317
+ }
318
+ return split;
319
+ }
320
+
321
+ /**
322
+ * Simple (non-composite) TrueType embedding with
323
+ * `/WinAnsiEncoding`. Every code point must be representable in
324
+ * CP1252 — a Unicode caller picks {@link embedCid} instead.
325
+ *
326
+ * The `/ToUnicode` CMap is built with a one-byte codespace (`<00> <FF>`),
327
+ * matching the single-byte WinAnsi codes.
328
+ *
329
+ * @param {object} parsedFont Output of `fonts.read(bytes)`.
330
+ * @param {Iterable<number>} codePoints
331
+ * @param {object} [opts] Forwarded to `subsetForPdf`.
332
+ */
333
+ function embedSimple(parsedFont, codePoints, opts) {
334
+ assertFont(parsedFont);
335
+ const cps = normaliseCodePoints(codePoints);
336
+ for (const cp of cps) {
337
+ if (!WIN_ANSI_BYTE.has(cp)) {
338
+ throw new ContractError('pdf/embed/not-winansi',
339
+ 'embedSimple requires WinAnsi (CP1252)-representable code points; use embedCid',
340
+ { context: { cp } });
341
+ }
342
+ }
343
+
344
+ const subset = deps.subsetForPdf(parsedFont, cps, opts);
345
+ const { descriptor, fontFile, fontFileKey } = descriptorOf(parsedFont, subset);
346
+
347
+ const byteToUnicode = new Map();
348
+ for (const cp of cps) byteToUnicode.set(WIN_ANSI_BYTE.get(cp), String.fromCodePoint(cp));
349
+ const toUnicodeStream = streamOfCmap(deps.embedBuildToUnicode(byteToUnicode, { codeBytes: 1 }));
350
+
351
+ const byCp = scaledWidthByCp(parsedFont, subset, cps);
352
+ const bytes = cps.map(cp => WIN_ANSI_BYTE.get(cp));
353
+ const firstChar = Math.min(...bytes);
354
+ const lastChar = Math.max(...bytes);
355
+ const byByte = new Map();
356
+ for (const cp of cps) byByte.set(WIN_ANSI_BYTE.get(cp), byCp.get(cp));
357
+ const widths = [];
358
+ for (let b = firstChar; b <= lastChar; b++) {
359
+ widths.push(obj.int(byByte.has(b) ? byByte.get(b) : 0));
360
+ }
361
+
362
+ const cpSet = new Set(cps);
363
+ function encode(text) {
364
+ const out = [];
365
+ for (const ch of String(text)) {
366
+ const cp = ch.codePointAt(0);
367
+ if (!cpSet.has(cp) || !WIN_ANSI_BYTE.has(cp)) {
368
+ throw new ContractError('pdf/embed/not-winansi',
369
+ 'code point is not part of this WinAnsi embedding',
370
+ { context: { cp } });
371
+ }
372
+ out.push(WIN_ANSI_BYTE.get(cp));
373
+ }
374
+ return Uint8Array.from(out);
375
+ }
376
+ function widthOf(cp) {
377
+ return byCp.has(cp) ? byCp.get(cp) : 0;
378
+ }
379
+
380
+ const fontDict = obj.dict({
381
+ Type: obj.name('Font'),
382
+ Subtype: obj.name('TrueType'),
383
+ BaseFont: obj.name(baseFontName(subset)),
384
+ Encoding: obj.name('WinAnsiEncoding'),
385
+ FirstChar: obj.int(firstChar),
386
+ LastChar: obj.int(lastChar),
387
+ Widths: obj.array(widths),
388
+ FontDescriptor: descriptor,
389
+ ToUnicode: toUnicodeStream
390
+ });
391
+
392
+ return {
393
+ subtype: 'TrueType',
394
+ fontDict,
395
+ descriptor,
396
+ toUnicodeStream,
397
+ fontFile,
398
+ fontFileKey,
399
+ encode,
400
+ widthOf,
401
+ codePoints: cps
402
+ };
403
+ }
404
+
405
+ /**
406
+ * Composite `/Type0` + `/Identity-H` embedding. The subset is
407
+ * renumbered, so CID === new gid and `/CIDToGIDMap` is
408
+ * `/Identity`.
409
+ *
410
+ * @param {object} parsedFont Output of `fonts.read(bytes)`.
411
+ * @param {Iterable<number>} codePoints
412
+ * @param {object} [opts] Forwarded to `subsetForPdf`.
413
+ */
414
+ function embedCid(parsedFont, codePoints, opts) {
415
+ assertFont(parsedFont);
416
+ const cps = normaliseCodePoints(codePoints);
417
+
418
+ const subset = deps.subsetForPdf(parsedFont, cps, { ...(opts || {}), cid: true });
419
+ const { descriptor, fontFile, fontFileKey } = descriptorOf(parsedFont, subset);
420
+ const toUnicodeStream = toUnicodeStreamForCid(deps, subset);
421
+ const cidSys = wrapCidSysInfo(deps.buildCidSystemInfo(parsedFont));
422
+
423
+ const byCp = scaledWidthByCp(parsedFont, subset, cps);
424
+ const byGid = scaledWidthByGid(parsedFont, subset);
425
+ const glyphMap = (subset.glyphMap instanceof Map) ? subset.glyphMap : new Map();
426
+
427
+ function encode(text) {
428
+ const out = [];
429
+ for (const ch of String(text)) {
430
+ const cp = ch.codePointAt(0);
431
+ let gid = glyphMap.get(cp);
432
+ if (gid == null) { gid = 0; encode.missing++; }
433
+ out.push((gid >> 8) & 0xFF, gid & 0xFF);
434
+ }
435
+ return Uint8Array.from(out);
436
+ }
437
+ encode.missing = 0;
438
+ function widthOf(cp) {
439
+ return byCp.has(cp) ? byCp.get(cp) : 0;
440
+ }
441
+
442
+ const baseFont = baseFontName(subset);
443
+ const cidFontDict = obj.dict({
444
+ Type: obj.name('Font'),
445
+ Subtype: obj.name('CIDFontType2'),
446
+ BaseFont: obj.name(baseFont),
447
+ CIDSystemInfo: cidSys,
448
+ FontDescriptor: descriptor,
449
+ DW: obj.int(1000),
450
+ W: compactWArray(byGid),
451
+ CIDToGIDMap: obj.name('Identity')
452
+ });
453
+ const type0Dict = obj.dict({
454
+ Type: obj.name('Font'),
455
+ Subtype: obj.name('Type0'),
456
+ BaseFont: obj.name(baseFont),
457
+ Encoding: obj.name('Identity-H'),
458
+ DescendantFonts: obj.array([cidFontDict]),
459
+ ToUnicode: toUnicodeStream
460
+ });
461
+
462
+ return {
463
+ type0Dict,
464
+ cidFontDict,
465
+ descriptor,
466
+ toUnicodeStream,
467
+ fontFile,
468
+ fontFileKey,
469
+ encode,
470
+ widthOf,
471
+ codePoints: cps
472
+ };
473
+ }
474
+
475
+ return { embedSimple, embedCid };
476
+ }
477
+
478
+ return createEmbed(fontsEmbedPdf);
479
+ }
480
+ };
@@ -0,0 +1,92 @@
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 Font encoding resolution per ISO 32000-2:2020 §9.6.5.
8
+ *
9
+ * A simple Font's `/Encoding` entry is either a name or a dict with
10
+ * `{ BaseEncoding?: name, Differences?: array }`.
11
+ *
12
+ * `resolveEncoding(entry, lookupNamed)` returns a 256-entry array of
13
+ * glyph names (or `null` for unmapped slots).
14
+ *
15
+ * @module pdf/font/encoding
16
+ */
17
+
18
+ /**
19
+ * Module factory — worker-safe, self-contained.
20
+ */
21
+ import { pdfErrors } from '../errors.js';
22
+
23
+ export const pdfFontEncoding = {
24
+ name: 'pdfFontEncoding',
25
+ dependencies: ['pdfErrors'],
26
+ deps: [pdfErrors],
27
+ factory(errors) {
28
+ const { ParseError } = errors;
29
+
30
+ function isType(v, kind) { return !!(v && v.type === kind); }
31
+
32
+ function applyDifferences(diffsArr, table) {
33
+ if (diffsArr.type !== 'array') {
34
+ throw new ParseError('pdf/encoding/bad-differences',
35
+ '/Differences must be an array');
36
+ }
37
+ let cur = 0;
38
+ for (const it of diffsArr.items) {
39
+ if (it.type === 'int') cur = it.value;
40
+ else if (it.type === 'name') {
41
+ if (cur >= 0 && cur < 256) table[cur] = it.value;
42
+ cur++;
43
+ } else {
44
+ throw new ParseError('pdf/encoding/bad-differences-entry',
45
+ '/Differences entries must be ints or names',
46
+ { context: { kind: it.type } });
47
+ }
48
+ }
49
+ }
50
+
51
+ function resolveEncoding(entry, lookupNamed) {
52
+ const table = new Array(256).fill(null);
53
+ let baseName;
54
+
55
+ if (!entry) baseName = 'StandardEncoding';
56
+ else if (entry.type === 'name') baseName = entry.value;
57
+ else if (entry.type === 'dict') {
58
+ const be = entry.entries.BaseEncoding;
59
+ if (be) {
60
+ if (be.type !== 'name') {
61
+ throw new ParseError('pdf/encoding/bad-base',
62
+ 'BaseEncoding must be a name',
63
+ { context: { type: be.type } });
64
+ }
65
+ baseName = be.value;
66
+ } else {
67
+ baseName = 'StandardEncoding';
68
+ }
69
+ } else {
70
+ throw new ParseError('pdf/encoding/bad-shape',
71
+ '/Encoding must be a name or a dict',
72
+ { context: { type: entry.type } });
73
+ }
74
+
75
+ if (typeof lookupNamed === 'function') {
76
+ const named = lookupNamed(baseName);
77
+ if (named) {
78
+ for (let i = 0; i < 256 && i < named.length; i++) {
79
+ table[i] = named[i] || null;
80
+ }
81
+ }
82
+ }
83
+
84
+ if (isType(entry, 'dict') && entry.entries.Differences) {
85
+ applyDifferences(entry.entries.Differences, table);
86
+ }
87
+ return table;
88
+ }
89
+
90
+ return { resolveEncoding };
91
+ }
92
+ };
@@ -0,0 +1,101 @@
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 Font dict typing per ISO 32000-2:2020 §9.6 / §9.7.
8
+ *
9
+ * This module **does not parse font files**. All actual font byte
10
+ * parsing (Type 1 PFB/PFA, TrueType/OpenType, CFF, CIDFont) is
11
+ * delegated to `@awacloud/fonts`. Here we only type the Font dict itself
12
+ * (`/Type /Font`, `/Subtype`, `/BaseFont`, `/Encoding`, ...).
13
+ *
14
+ * @module pdf/font/font
15
+ */
16
+
17
+ /**
18
+ * Module factory — worker-safe, self-contained.
19
+ */
20
+ import { pdfErrors } from '../errors.js';
21
+ import { pdfParser } from '../syntax/parser.js';
22
+
23
+ export const pdfFont = {
24
+ name: 'pdfFont',
25
+ dependencies: ['pdfErrors', 'pdfParser'],
26
+ deps: [pdfErrors, pdfParser],
27
+ factory(errors, parserMod) {
28
+ const { ParseError } = errors;
29
+ const isType = (parserMod && parserMod.isType)
30
+ || ((v, kind) => !!(v && v.type === kind));
31
+
32
+ const KNOWN_SUBTYPES = new Set([
33
+ 'Type0', 'Type1', 'MMType1', 'Type3', 'TrueType',
34
+ 'CIDFontType0', 'CIDFontType2'
35
+ ]);
36
+
37
+ function typeFont(dict, opts) {
38
+ if (!isType(dict, 'dict')) {
39
+ throw new ParseError('pdf/font/not-dict',
40
+ 'Font must be a dictionary',
41
+ { context: { type: dict && dict.type } });
42
+ }
43
+ const e = dict.entries;
44
+ if (e.Type && (e.Type.type !== 'name' || e.Type.value !== 'Font')) {
45
+ throw new ParseError('pdf/font/bad-type',
46
+ '/Type must be /Font when present',
47
+ { context: { actual: e.Type.value } });
48
+ }
49
+ if (!e.Subtype || e.Subtype.type !== 'name') {
50
+ throw new ParseError('pdf/font/missing-subtype',
51
+ 'Font dict missing required /Subtype');
52
+ }
53
+ if (!KNOWN_SUBTYPES.has(e.Subtype.value)) {
54
+ throw new ParseError('pdf/font/unknown-subtype',
55
+ `unknown font subtype "${e.Subtype.value}"`,
56
+ { context: { subtype: e.Subtype.value } });
57
+ }
58
+ const baseFont = e.BaseFont && e.BaseFont.type === 'name'
59
+ ? e.BaseFont.value : null;
60
+
61
+ const out = {
62
+ subtype: e.Subtype.value,
63
+ baseFont,
64
+ encoding: e.Encoding || null,
65
+ firstChar: e.FirstChar && e.FirstChar.type === 'int' ? e.FirstChar.value : null,
66
+ lastChar: e.LastChar && e.LastChar.type === 'int' ? e.LastChar.value : null,
67
+ widths: e.Widths && e.Widths.type === 'array'
68
+ ? e.Widths.items.filter(it => it.type === 'int' || it.type === 'real')
69
+ .map(it => it.value)
70
+ : null,
71
+ fontDescriptor: e.FontDescriptor || null,
72
+ toUnicode: e.ToUnicode || null,
73
+ descendantFonts: e.DescendantFonts && e.DescendantFonts.type === 'array'
74
+ ? e.DescendantFonts.items.slice() : null,
75
+ standard14: null,
76
+ raw: dict
77
+ };
78
+
79
+ if ((out.subtype === 'Type1' || out.subtype === 'MMType1')
80
+ && !out.fontDescriptor
81
+ && opts && opts.standard14
82
+ && baseFont
83
+ && opts.standard14.isStandard14(baseFont)) {
84
+ out.standard14 = opts.standard14.lookupStandard14(baseFont);
85
+ }
86
+
87
+ return out;
88
+ }
89
+
90
+ function resolveDescendant(type0Font, resolveRef) {
91
+ if (!type0Font || type0Font.subtype !== 'Type0') return null;
92
+ const descs = type0Font.descendantFonts;
93
+ if (!descs || descs.length === 0) return null;
94
+ const d = descs[0];
95
+ const dict = d.type === 'ref' ? resolveRef(d) : d;
96
+ return typeFont(dict);
97
+ }
98
+
99
+ return { typeFont, resolveDescendant };
100
+ }
101
+ };