pagyra-js 0.0.1

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 (540) hide show
  1. package/.eslintrc.json +30 -0
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +275 -0
  4. package/UA_Styles_Chromium.md +93 -0
  5. package/_ext/woff2_conversion/brotli-decode.d.ts +139 -0
  6. package/_ext/woff2_conversion/brotli-encode.d.ts +129 -0
  7. package/_ext/woff2_conversion/brotli-port.d.ts +12 -0
  8. package/_ext/woff2_conversion/brotli-shared-dictionary.d.ts +25 -0
  9. package/_ext/woff2_conversion/brotli-types.d.ts +15 -0
  10. package/_ext/woff2_conversion/woff2-common.d.ts +37 -0
  11. package/_ext/woff2_conversion/woff2-decode.d.ts +32 -0
  12. package/_ext/woff2_conversion/woff2-encode.d.ts +31 -0
  13. package/_ext/woff2_conversion/woff2-output.d.ts +39 -0
  14. package/_ext/woff2_original_cpp/brotli/brotli.c +1559 -0
  15. package/_ext/woff2_original_cpp/brotli/brotli.md +116 -0
  16. package/_ext/woff2_original_cpp/brotli/decode.h +409 -0
  17. package/_ext/woff2_original_cpp/brotli/encode.h +505 -0
  18. package/_ext/woff2_original_cpp/brotli/port.h +302 -0
  19. package/_ext/woff2_original_cpp/brotli/shared_dictionary.h +100 -0
  20. package/_ext/woff2_original_cpp/brotli/types.h +83 -0
  21. package/_ext/woff2_original_cpp/cmake/FindBrotliDec.cmake +35 -0
  22. package/_ext/woff2_original_cpp/cmake/FindBrotliEnc.cmake +35 -0
  23. package/_ext/woff2_original_cpp/include/woff2/decode.h +36 -0
  24. package/_ext/woff2_original_cpp/include/woff2/encode.h +43 -0
  25. package/_ext/woff2_original_cpp/include/woff2/output.h +86 -0
  26. package/_ext/woff2_original_cpp/src/buffer.h +164 -0
  27. package/_ext/woff2_original_cpp/src/convert_woff2ttf_fuzzer.cc +13 -0
  28. package/_ext/woff2_original_cpp/src/convert_woff2ttf_fuzzer_new_entry.cc +12 -0
  29. package/_ext/woff2_original_cpp/src/file.h +30 -0
  30. package/_ext/woff2_original_cpp/src/font.cc +400 -0
  31. package/_ext/woff2_original_cpp/src/font.h +105 -0
  32. package/_ext/woff2_original_cpp/src/glyph.cc +383 -0
  33. package/_ext/woff2_original_cpp/src/glyph.h +71 -0
  34. package/_ext/woff2_original_cpp/src/normalize.cc +314 -0
  35. package/_ext/woff2_original_cpp/src/normalize.h +39 -0
  36. package/_ext/woff2_original_cpp/src/port.h +66 -0
  37. package/_ext/woff2_original_cpp/src/round.h +27 -0
  38. package/_ext/woff2_original_cpp/src/store_bytes.h +55 -0
  39. package/_ext/woff2_original_cpp/src/table_tags.cc +82 -0
  40. package/_ext/woff2_original_cpp/src/table_tags.h +30 -0
  41. package/_ext/woff2_original_cpp/src/transform.cc +430 -0
  42. package/_ext/woff2_original_cpp/src/transform.h +26 -0
  43. package/_ext/woff2_original_cpp/src/variable_length.cc +129 -0
  44. package/_ext/woff2_original_cpp/src/variable_length.h +30 -0
  45. package/_ext/woff2_original_cpp/src/woff2_common.cc +50 -0
  46. package/_ext/woff2_original_cpp/src/woff2_common.h +64 -0
  47. package/_ext/woff2_original_cpp/src/woff2_compress.cc +43 -0
  48. package/_ext/woff2_original_cpp/src/woff2_dec.cc +1398 -0
  49. package/_ext/woff2_original_cpp/src/woff2_decompress.cc +41 -0
  50. package/_ext/woff2_original_cpp/src/woff2_enc.cc +458 -0
  51. package/_ext/woff2_original_cpp/src/woff2_info.cc +142 -0
  52. package/_ext/woff2_original_cpp/src/woff2_out.cc +63 -0
  53. package/assets/fonts/ttf/arimo/Arimo-Bold.ttf +0 -0
  54. package/assets/fonts/ttf/arimo/Arimo-BoldItalic.ttf +0 -0
  55. package/assets/fonts/ttf/arimo/Arimo-Italic.ttf +0 -0
  56. package/assets/fonts/ttf/arimo/Arimo-Regular.ttf +0 -0
  57. package/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Black.ttf +0 -0
  58. package/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Bold.ttf +0 -0
  59. package/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Regular.ttf +0 -0
  60. package/assets/fonts/ttf/dejavu/DejaVuSans.ttf +0 -0
  61. package/assets/fonts/ttf/firecode/FiraCode-Bold.ttf +0 -0
  62. package/assets/fonts/ttf/firecode/FiraCode-Light.ttf +0 -0
  63. package/assets/fonts/ttf/firecode/FiraCode-Medium.ttf +0 -0
  64. package/assets/fonts/ttf/firecode/FiraCode-Regular.ttf +0 -0
  65. package/assets/fonts/ttf/firecode/FiraCode-SemiBold.ttf +0 -0
  66. package/assets/fonts/ttf/notoemoji/NotoEmoji-Bold.ttf +0 -0
  67. package/assets/fonts/ttf/notoemoji/NotoEmoji-Light.ttf +0 -0
  68. package/assets/fonts/ttf/notoemoji/NotoEmoji-Medium.ttf +0 -0
  69. package/assets/fonts/ttf/notoemoji/NotoEmoji-Regular.ttf +0 -0
  70. package/assets/fonts/ttf/notoemoji/NotoEmoji-SemiBold.ttf +0 -0
  71. package/assets/fonts/ttf/notosans/NotoSans-Regular.ttf +0 -0
  72. package/assets/fonts/ttf/roboto/Roboto-Bold.ttf +0 -0
  73. package/assets/fonts/ttf/roboto/Roboto-BoldItalic.ttf +0 -0
  74. package/assets/fonts/ttf/roboto/Roboto-Italic.ttf +0 -0
  75. package/assets/fonts/ttf/roboto/Roboto-Regular.ttf +0 -0
  76. package/assets/fonts/ttf/stixtwomath/STIXTwoMath-Regular.ttf +0 -0
  77. package/assets/fonts/ttf/tinos/Tinos-Bold.ttf +0 -0
  78. package/assets/fonts/ttf/tinos/Tinos-BoldItalic.ttf +0 -0
  79. package/assets/fonts/ttf/tinos/Tinos-Italic.ttf +0 -0
  80. package/assets/fonts/ttf/tinos/Tinos-Regular.ttf +0 -0
  81. package/assets/fonts/woff/lato/lato-latin-400-italic.woff +0 -0
  82. package/assets/fonts/woff/lato/lato-latin-400-normal.woff +0 -0
  83. package/assets/fonts/woff/lato/lato-latin-700-italic.woff +0 -0
  84. package/assets/fonts/woff/lato/lato-latin-700-normal.woff +0 -0
  85. package/assets/fonts/woff2/caveat/Caveat-Bold.woff2 +0 -0
  86. package/assets/fonts/woff2/caveat/Caveat-Regular.woff2 +0 -0
  87. package/assets/fonts/woff2/lato/lato-latin-400-italic.woff2 +0 -0
  88. package/assets/fonts/woff2/lato/lato-latin-400-normal.woff2 +0 -0
  89. package/assets/fonts/woff2/lato/lato-latin-700-italic.woff2 +0 -0
  90. package/assets/fonts/woff2/lato/lato-latin-700-normal.woff2 +0 -0
  91. package/docs/AGENTS.md +288 -0
  92. package/docs/BACKGROUND-REPEAT-IMPLEMENTATION.md +127 -0
  93. package/docs/BACKGROUND-REPEAT-REFERENCE.md +127 -0
  94. package/docs/BACKGROUND-REPEAT-SPACE-ROUND.md +164 -0
  95. package/docs/css-properties-support.md +256 -0
  96. package/docs/src_modules_table.md +172 -0
  97. package/docs/text-overlap-fix.md +85 -0
  98. package/docs/text-overlap-investigation.md +27 -0
  99. package/eslint.config.js +36 -0
  100. package/glyph_measure.htm +1458 -0
  101. package/package.json +50 -0
  102. package/playground/browser-entry.ts +2 -0
  103. package/playground/exports/background-text-debug.pdf +0 -0
  104. package/playground/public/app.js +875 -0
  105. package/playground/public/assets/1.webp +0 -0
  106. package/playground/public/examples/accents-test.html +24 -0
  107. package/playground/public/examples/advanced-selectors-demo.html +118 -0
  108. package/playground/public/examples/background-advanced-showcase.html +82 -0
  109. package/playground/public/examples/background-clip-text.html +36 -0
  110. package/playground/public/examples/background-origin-showcase.html +137 -0
  111. package/playground/public/examples/background-position-showcase.html +83 -0
  112. package/playground/public/examples/background-repeat-showcase.html +83 -0
  113. package/playground/public/examples/background-repeat-space-round.html +348 -0
  114. package/playground/public/examples/background-size-showcase.html +82 -0
  115. package/playground/public/examples/background-text-debug.html +18 -0
  116. package/playground/public/examples/baseline-test.html +24 -0
  117. package/playground/public/examples/bold-showcase.html +150 -0
  118. package/playground/public/examples/bold-strike-example.html +12 -0
  119. package/playground/public/examples/border-collapse-test.html +23 -0
  120. package/playground/public/examples/centered-shadow-div.html +72 -0
  121. package/playground/public/examples/css-variables.html +50 -0
  122. package/playground/public/examples/debug-accents.html +11 -0
  123. package/playground/public/examples/debug-text-overlap.html +46 -0
  124. package/playground/public/examples/flex-gap-column.html +130 -0
  125. package/playground/public/examples/flex-gap-row.html +137 -0
  126. package/playground/public/examples/flex-padding-test.html +29 -0
  127. package/playground/public/examples/flexbox-text-test.html +193 -0
  128. package/playground/public/examples/fonts-demo.html +126 -0
  129. package/playground/public/examples/footer-example.html +4 -0
  130. package/playground/public/examples/gradient-text.html +54 -0
  131. package/playground/public/examples/grid-gap-demo.html +156 -0
  132. package/playground/public/examples/header-example.html +4 -0
  133. package/playground/public/examples/header-footer-example.html +27 -0
  134. package/playground/public/examples/image-showcase.html +33 -0
  135. package/playground/public/examples/justify-text.html +22 -0
  136. package/playground/public/examples/linear-gradient-example.html +38 -0
  137. package/playground/public/examples/lorem-span.html +14 -0
  138. package/playground/public/examples/margin-block-showcase.html +21 -0
  139. package/playground/public/examples/margin-inline-showcase.html +21 -0
  140. package/playground/public/examples/monthly-summary.html +95 -0
  141. package/playground/public/examples/multi-page-lorem.html +190 -0
  142. package/playground/public/examples/opacity-debug.html +39 -0
  143. package/playground/public/examples/opacity-example.html +70 -0
  144. package/playground/public/examples/png-image-example.html +13 -0
  145. package/playground/public/examples/red-rectangle.html +18 -0
  146. package/playground/public/examples/repro.html +24 -0
  147. package/playground/public/examples/rounded-borders-test.html +24 -0
  148. package/playground/public/examples/simple-list.html +89 -0
  149. package/playground/public/examples/simple-svg.html +37 -0
  150. package/playground/public/examples/simple-table.html +52 -0
  151. package/playground/public/examples/skew-div.html +138 -0
  152. package/playground/public/examples/skew-text.html +21 -0
  153. package/playground/public/examples/starter-report.css +51 -0
  154. package/playground/public/examples/starter-report.html +23 -0
  155. package/playground/public/examples/svg-aspect-ratio-showcase.html +116 -0
  156. package/playground/public/examples/svg-gradients-linear.html +28 -0
  157. package/playground/public/examples/svg-gradients-radial.html +29 -0
  158. package/playground/public/examples/svg-gradients-showcase.html +66 -0
  159. package/playground/public/examples/svg-image-path-test.html +43 -0
  160. package/playground/public/examples/svg-images-clipping.html +27 -0
  161. package/playground/public/examples/svg-path-gallery.html +118 -0
  162. package/playground/public/examples/svg-radial-transform-demo.html +78 -0
  163. package/playground/public/examples/svg-transform-stack.html +103 -0
  164. package/playground/public/examples/svg-transforms-demo.html +127 -0
  165. package/playground/public/examples/table-merge-test.html +34 -0
  166. package/playground/public/examples/text-decoration-showcase.html +138 -0
  167. package/playground/public/examples/text-indent-showcase.html +137 -0
  168. package/playground/public/examples/text-shadow-example.html +29 -0
  169. package/playground/public/examples/very-complex-css.html +293 -0
  170. package/playground/public/examples/webp-example.html +13 -0
  171. package/playground/public/examples/z-index-demo.html +93 -0
  172. package/playground/public/examples.json +240 -0
  173. package/playground/public/images/dice.png +0 -0
  174. package/playground/public/images/duck.jpg +0 -0
  175. package/playground/public/index.html +149 -0
  176. package/playground/public/mode.js +1 -0
  177. package/playground/public/styles.css +382 -0
  178. package/playground/public/tmp-h2-debug.html +33 -0
  179. package/playground/public/tmp-italic-debug.html +32 -0
  180. package/playground/public/vendor/codemirror/codemirror.min.css +1 -0
  181. package/playground/public/vendor/codemirror/codemirror.min.js +1 -0
  182. package/playground/public/vendor/codemirror/css.min.js +1 -0
  183. package/playground/public/vendor/codemirror/darcula.min.css +1 -0
  184. package/playground/public/vendor/codemirror/htmlmixed.min.js +1 -0
  185. package/playground/public/vendor/codemirror/javascript.min.js +1 -0
  186. package/playground/public/vendor/codemirror/xml.min.js +1 -0
  187. package/playground/public/vendor/pagyra-playground-browser.js +165966 -0
  188. package/playground/public/vendor/pagyra-playground-browser.js.map +7 -0
  189. package/playground/server.d.ts +1 -0
  190. package/playground/server.js +68 -0
  191. package/playground/server.ts +128 -0
  192. package/scripts/browser-build.ts +101 -0
  193. package/scripts/build-browser-bundle.ts +52 -0
  194. package/scripts/glyph-comparison/simulate.ts +744 -0
  195. package/scripts/playground-browser-server.ts +57 -0
  196. package/scripts/probe-roboto.ts +6 -0
  197. package/scripts/render-playground-example.ts +121 -0
  198. package/scripts/run-glyph-atlas-tuner-runner.mjs +113 -0
  199. package/scripts/run-glyph-atlas-tuner.ts +141 -0
  200. package/scripts/top-ts-files.ps1 +39 -0
  201. package/scripts/top-ts-files.sh +37 -0
  202. package/scripts/woff2_info.ps1 +132 -0
  203. package/src/browser-entry.ts +14 -0
  204. package/src/compression/adler32.ts +45 -0
  205. package/src/compression/brotli/brotli.ts +463 -0
  206. package/src/compression/brotli/index.ts +15 -0
  207. package/src/compression/brotli/transform.ts +184 -0
  208. package/src/compression/brotli/types.ts +58 -0
  209. package/src/compression/brotli/utils.ts +157 -0
  210. package/src/compression/brotli/vendor/bit_reader.js +124 -0
  211. package/src/compression/brotli/vendor/context.js +250 -0
  212. package/src/compression/brotli/vendor/decode.d.ts +2 -0
  213. package/src/compression/brotli/vendor/decode.js +938 -0
  214. package/src/compression/brotli/vendor/dictionary-data.js +9469 -0
  215. package/src/compression/brotli/vendor/dictionary.js +36 -0
  216. package/src/compression/brotli/vendor/huffman.js +123 -0
  217. package/src/compression/brotli/vendor/package.json +3 -0
  218. package/src/compression/brotli/vendor/prefix.js +60 -0
  219. package/src/compression/brotli/vendor/streams.js +31 -0
  220. package/src/compression/brotli/vendor/transform.js +247 -0
  221. package/src/compression/brotli/vendor-decode.d.ts +4 -0
  222. package/src/compression/brotli/woff2-glyf-transform.ts +623 -0
  223. package/src/compression/decompress.ts +16 -0
  224. package/src/compression/deflate.ts +295 -0
  225. package/src/compression/index.ts +4 -0
  226. package/src/compression/types.ts +26 -0
  227. package/src/compression/utils.ts +107 -0
  228. package/src/core.ts +18 -0
  229. package/src/css/apply-declarations.ts +86 -0
  230. package/src/css/background-types.ts +65 -0
  231. package/src/css/browser-defaults.ts +16 -0
  232. package/src/css/clip-path-types.ts +13 -0
  233. package/src/css/compute-style.ts +494 -0
  234. package/src/css/css-unit-resolver.ts +65 -0
  235. package/src/css/custom-properties.ts +215 -0
  236. package/src/css/enums.ts +127 -0
  237. package/src/css/font-face-parser.ts +233 -0
  238. package/src/css/font-weight.ts +65 -0
  239. package/src/css/inline-style-parser.ts +27 -0
  240. package/src/css/layout-property-resolver.ts +75 -0
  241. package/src/css/length.ts +141 -0
  242. package/src/css/line-height.ts +96 -0
  243. package/src/css/named-colors.ts +150 -0
  244. package/src/css/parsers/background-parser-extended.ts +111 -0
  245. package/src/css/parsers/background-parser.ts +456 -0
  246. package/src/css/parsers/border-block-parser.ts +26 -0
  247. package/src/css/parsers/border-inline-parser.ts +26 -0
  248. package/src/css/parsers/border-parser-extended.ts +256 -0
  249. package/src/css/parsers/border-parser.ts +175 -0
  250. package/src/css/parsers/box-shadow-parser.ts +106 -0
  251. package/src/css/parsers/clip-path-parser.ts +92 -0
  252. package/src/css/parsers/color-parser.ts +14 -0
  253. package/src/css/parsers/dimension-parser.ts +117 -0
  254. package/src/css/parsers/display-flex-parser.ts +59 -0
  255. package/src/css/parsers/flex-parser.ts +144 -0
  256. package/src/css/parsers/font-parser.ts +40 -0
  257. package/src/css/parsers/gradient-parser.ts +366 -0
  258. package/src/css/parsers/grid-parser-extended.ts +55 -0
  259. package/src/css/parsers/grid-parser.ts +218 -0
  260. package/src/css/parsers/length-parser.ts +95 -0
  261. package/src/css/parsers/list-style-parser.ts +39 -0
  262. package/src/css/parsers/margin-block-parser.ts +12 -0
  263. package/src/css/parsers/margin-inline-parser.ts +12 -0
  264. package/src/css/parsers/margin-parser.ts +30 -0
  265. package/src/css/parsers/opacity-parser.ts +32 -0
  266. package/src/css/parsers/overflow-wrap-parser.ts +38 -0
  267. package/src/css/parsers/padding-block-parser.ts +12 -0
  268. package/src/css/parsers/padding-inline-parser.ts +12 -0
  269. package/src/css/parsers/padding-parser.ts +30 -0
  270. package/src/css/parsers/position-parser.ts +75 -0
  271. package/src/css/parsers/register-parsers.ts +302 -0
  272. package/src/css/parsers/registry.ts +18 -0
  273. package/src/css/parsers/text-parser-extended.ts +144 -0
  274. package/src/css/parsers/text-parser.ts +25 -0
  275. package/src/css/parsers/text-shadow-parser.ts +94 -0
  276. package/src/css/properties/box-model.ts +82 -0
  277. package/src/css/properties/flexbox.ts +44 -0
  278. package/src/css/properties/gap.ts +14 -0
  279. package/src/css/properties/grid.ts +94 -0
  280. package/src/css/properties/layout.ts +59 -0
  281. package/src/css/properties/misc.ts +44 -0
  282. package/src/css/properties/typography.ts +71 -0
  283. package/src/css/properties/visual.ts +68 -0
  284. package/src/css/selectors/matcher.ts +219 -0
  285. package/src/css/selectors/parser.ts +163 -0
  286. package/src/css/selectors/simple-key.ts +31 -0
  287. package/src/css/selectors/specificity.ts +41 -0
  288. package/src/css/selectors/types.ts +31 -0
  289. package/src/css/shorthands/border-shorthand.ts +68 -0
  290. package/src/css/shorthands/box-shorthand.ts +33 -0
  291. package/src/css/style-inheritance.ts +50 -0
  292. package/src/css/style.ts +402 -0
  293. package/src/css/ua-defaults/base-defaults.ts +266 -0
  294. package/src/css/ua-defaults/browser-defaults.ts +134 -0
  295. package/src/css/ua-defaults/element-defaults.ts +374 -0
  296. package/src/css/ua-defaults/types.ts +43 -0
  297. package/src/css/unit-conversion.ts +24 -0
  298. package/src/css/utils.ts +108 -0
  299. package/src/css/viewport.ts +17 -0
  300. package/src/debug/audit.ts +20 -0
  301. package/src/debug/ids.ts +13 -0
  302. package/src/debug/log.js +28 -0
  303. package/src/debug/log.ts +52 -0
  304. package/src/debug/tree.ts +57 -0
  305. package/src/dom/node.ts +133 -0
  306. package/src/environment/browser-environment.ts +78 -0
  307. package/src/environment/environment.ts +35 -0
  308. package/src/environment/global.ts +13 -0
  309. package/src/environment/node-environment.browser.ts +28 -0
  310. package/src/environment/node-environment.ts +64 -0
  311. package/src/fonts/detector.ts +28 -0
  312. package/src/fonts/engines/ttf-engine.ts +28 -0
  313. package/src/fonts/engines/woff-engine.ts +38 -0
  314. package/src/fonts/engines/woff2-engine.ts +41 -0
  315. package/src/fonts/extractors/metrics-extractor.ts +362 -0
  316. package/src/fonts/font-registry-resolver.ts +132 -0
  317. package/src/fonts/index.ts +3 -0
  318. package/src/fonts/orchestrator.ts +92 -0
  319. package/src/fonts/parsers/base-parser.ts +23 -0
  320. package/src/fonts/types.ts +85 -0
  321. package/src/fonts/utils/ttf-reconstructor.ts +120 -0
  322. package/src/fonts/woff/decoder.ts +105 -0
  323. package/src/fonts/woff2/buffer.ts +106 -0
  324. package/src/fonts/woff2/decoder.ts +981 -0
  325. package/src/geometry/box.ts +48 -0
  326. package/src/geometry/matrix.ts +59 -0
  327. package/src/html/css/parse-css.ts +85 -0
  328. package/src/html/dom-converter.ts +433 -0
  329. package/src/html/image-converter.ts +200 -0
  330. package/src/html-to-pdf.ts +410 -0
  331. package/src/image/base-decoder.ts +149 -0
  332. package/src/image/image-service.ts +188 -0
  333. package/src/image/jpeg-decoder.ts +73 -0
  334. package/src/image/png-decoder.ts +550 -0
  335. package/src/image/types.ts +20 -0
  336. package/src/image/webp-decoder.ts +242 -0
  337. package/src/image/webp-huffman.ts +218 -0
  338. package/src/image/webp-riff-parser.ts +54 -0
  339. package/src/image/webp-vp8l-decoder.ts +199 -0
  340. package/src/index.ts +35 -0
  341. package/src/layout/context/float-context.ts +62 -0
  342. package/src/layout/context/layout-environment.ts +29 -0
  343. package/src/layout/debug.ts +18 -0
  344. package/src/layout/inline/bounding-box-calculator.ts +132 -0
  345. package/src/layout/inline/font-baseline-calculator.ts +76 -0
  346. package/src/layout/inline/inline-utils.ts +94 -0
  347. package/src/layout/inline/layout.ts +285 -0
  348. package/src/layout/inline/line_breaker.ts +109 -0
  349. package/src/layout/inline/measurement.ts +144 -0
  350. package/src/layout/inline/run-placer.ts +139 -0
  351. package/src/layout/inline/text-alignment.ts +70 -0
  352. package/src/layout/inline/tokenizer.ts +195 -0
  353. package/src/layout/inline/types.ts +76 -0
  354. package/src/layout/pipeline/context-factory.ts +16 -0
  355. package/src/layout/pipeline/default-engine.ts +24 -0
  356. package/src/layout/pipeline/engine.ts +59 -0
  357. package/src/layout/pipeline/layout-tree.ts +13 -0
  358. package/src/layout/pipeline/out-of-flow-manager.ts +73 -0
  359. package/src/layout/pipeline/strategy.ts +12 -0
  360. package/src/layout/pipeline/text-metrics-initializer.ts +13 -0
  361. package/src/layout/strategies/block.ts +236 -0
  362. package/src/layout/strategies/display-none.ts +14 -0
  363. package/src/layout/strategies/fallback.ts +15 -0
  364. package/src/layout/strategies/flex.ts +477 -0
  365. package/src/layout/strategies/fragmentation.ts +17 -0
  366. package/src/layout/strategies/grid.ts +247 -0
  367. package/src/layout/strategies/image.ts +342 -0
  368. package/src/layout/strategies/inline.ts +128 -0
  369. package/src/layout/strategies/table.ts +595 -0
  370. package/src/layout/table/cell_layout.ts +31 -0
  371. package/src/layout/table/diagnostics.ts +19 -0
  372. package/src/layout/text-run.ts +42 -0
  373. package/src/layout/utils/content-measurer.ts +117 -0
  374. package/src/layout/utils/display-utils.ts +24 -0
  375. package/src/layout/utils/floats.ts +98 -0
  376. package/src/layout/utils/gap-calculator.ts +167 -0
  377. package/src/layout/utils/inline-formatter.ts +31 -0
  378. package/src/layout/utils/inline-formatting.ts +9 -0
  379. package/src/layout/utils/margin.ts +140 -0
  380. package/src/layout/utils/node-math.ts +237 -0
  381. package/src/layout/utils/overflow.ts +14 -0
  382. package/src/layout/utils/sizing.ts +12 -0
  383. package/src/layout/utils/text-metrics.ts +361 -0
  384. package/src/logging/debug.ts +58 -0
  385. package/src/pdf/font/base14/widths-courier-bold.ts +159 -0
  386. package/src/pdf/font/base14/widths-courier.ts +159 -0
  387. package/src/pdf/font/base14/widths-helvetica-bold.ts +158 -0
  388. package/src/pdf/font/base14/widths-helvetica.ts +158 -0
  389. package/src/pdf/font/base14/widths-times-bold.ts +158 -0
  390. package/src/pdf/font/base14/widths-times-roman.ts +158 -0
  391. package/src/pdf/font/base14/widths-types.ts +25 -0
  392. package/src/pdf/font/base14-widths.ts +32 -0
  393. package/src/pdf/font/blur.ts +81 -0
  394. package/src/pdf/font/builtin-fonts.browser.ts +262 -0
  395. package/src/pdf/font/builtin-fonts.ts +126 -0
  396. package/src/pdf/font/composite-glyph-parser.ts +242 -0
  397. package/src/pdf/font/embedder.ts +395 -0
  398. package/src/pdf/font/font-config.ts +190 -0
  399. package/src/pdf/font/font-registry.ts +263 -0
  400. package/src/pdf/font/font-subset.ts +258 -0
  401. package/src/pdf/font/glyph-atlas-maxrects.ts +305 -0
  402. package/src/pdf/font/glyph-atlas-tuner.ts +98 -0
  403. package/src/pdf/font/glyph-atlas.ts +226 -0
  404. package/src/pdf/font/glyph-cache.ts +127 -0
  405. package/src/pdf/font/loca-reader.ts +109 -0
  406. package/src/pdf/font/managers/font-resource-manager.ts +73 -0
  407. package/src/pdf/font/managers/subset-resource-manager.ts +164 -0
  408. package/src/pdf/font/rasterizer.ts +270 -0
  409. package/src/pdf/font/resolvers/base-font-mapper.ts +77 -0
  410. package/src/pdf/font/resolvers/family-resolver.ts +33 -0
  411. package/src/pdf/font/resolvers/weight-style-applicator.ts +63 -0
  412. package/src/pdf/font/simple-glyph-parser.ts +289 -0
  413. package/src/pdf/font/to-unicode.ts +109 -0
  414. package/src/pdf/font/transformation-matrix.ts +136 -0
  415. package/src/pdf/font/ttf-cmap.ts +180 -0
  416. package/src/pdf/font/ttf-global-metrics.ts +58 -0
  417. package/src/pdf/font/ttf-glyf.ts +125 -0
  418. package/src/pdf/font/ttf-glyph-metrics.ts +43 -0
  419. package/src/pdf/font/ttf-lite.ts +269 -0
  420. package/src/pdf/font/ttf-table-parser.ts +132 -0
  421. package/src/pdf/font/ttf-table-provider.ts +61 -0
  422. package/src/pdf/font/widths.ts +79 -0
  423. package/src/pdf/font-subset/font-registry.ts +127 -0
  424. package/src/pdf/header-footer-layout.ts +153 -0
  425. package/src/pdf/header-footer-painter.ts +209 -0
  426. package/src/pdf/header-footer-renderer.ts +357 -0
  427. package/src/pdf/header-footer-tokens.ts +55 -0
  428. package/src/pdf/header-footer.ts +25 -0
  429. package/src/pdf/layout-tree-builder.ts +261 -0
  430. package/src/pdf/page-painter.ts +241 -0
  431. package/src/pdf/pagination.ts +155 -0
  432. package/src/pdf/primitives/pdf-builder.ts +378 -0
  433. package/src/pdf/primitives/pdf-bytes.ts +40 -0
  434. package/src/pdf/primitives/pdf-document.ts +108 -0
  435. package/src/pdf/primitives/pdf-reference-manager.ts +47 -0
  436. package/src/pdf/primitives/pdf-resource-registries.ts +255 -0
  437. package/src/pdf/primitives/pdf-serializers.ts +194 -0
  438. package/src/pdf/primitives/pdf-types.ts +73 -0
  439. package/src/pdf/render.ts +210 -0
  440. package/src/pdf/renderer/box-painter.ts +236 -0
  441. package/src/pdf/renderer/page-paint.ts +102 -0
  442. package/src/pdf/renderer/paint-box-shadows.ts +218 -0
  443. package/src/pdf/renderer/radius.ts +58 -0
  444. package/src/pdf/renderers/graphics-state-manager.ts +40 -0
  445. package/src/pdf/renderers/image-renderer.ts +127 -0
  446. package/src/pdf/renderers/radius-utils.ts +80 -0
  447. package/src/pdf/renderers/rectangle-renderer.ts +129 -0
  448. package/src/pdf/renderers/rounded-rect-path.ts +120 -0
  449. package/src/pdf/renderers/shape-renderer.ts +563 -0
  450. package/src/pdf/renderers/shape-utils.ts +194 -0
  451. package/src/pdf/renderers/text-decoration-renderer.ts +313 -0
  452. package/src/pdf/renderers/text-encoder.ts +41 -0
  453. package/src/pdf/renderers/text-font-resolver.ts +75 -0
  454. package/src/pdf/renderers/text-renderer-utils.ts +28 -0
  455. package/src/pdf/renderers/text-renderer.ts +391 -0
  456. package/src/pdf/renderers/text-shadow-renderer.ts +300 -0
  457. package/src/pdf/shading/gradient-service.ts +525 -0
  458. package/src/pdf/shading/index.ts +1 -0
  459. package/src/pdf/stacking/build-stacking-contexts.ts +93 -0
  460. package/src/pdf/stacking/resolve-paint-order.ts +157 -0
  461. package/src/pdf/stacking/types.ts +40 -0
  462. package/src/pdf/svg/aspect-ratio.ts +81 -0
  463. package/src/pdf/svg/coordinate-mapper.ts +81 -0
  464. package/src/pdf/svg/geometry-builder.ts +45 -0
  465. package/src/pdf/svg/render-svg.ts +296 -0
  466. package/src/pdf/svg/shape-renderer.ts +463 -0
  467. package/src/pdf/svg/style-computer.ts +246 -0
  468. package/src/pdf/transform-adapter.ts +26 -0
  469. package/src/pdf/types.ts +377 -0
  470. package/src/pdf/utils/background-layer-resolver.ts +439 -0
  471. package/src/pdf/utils/background-tiles.ts +192 -0
  472. package/src/pdf/utils/border-dashes.ts +109 -0
  473. package/src/pdf/utils/border-radius-utils.ts +86 -0
  474. package/src/pdf/utils/box-dimensions-utils.ts +47 -0
  475. package/src/pdf/utils/clip-path-resolver.ts +50 -0
  476. package/src/pdf/utils/clipping-path-builder.ts +190 -0
  477. package/src/pdf/utils/color-utils.ts +102 -0
  478. package/src/pdf/utils/coordinate-transformer.ts +30 -0
  479. package/src/pdf/utils/drop-shadow-raster.ts +233 -0
  480. package/src/pdf/utils/encoding.ts +98 -0
  481. package/src/pdf/utils/glyph-atlas-registrar.ts +13 -0
  482. package/src/pdf/utils/glyph-run-renderer.ts +129 -0
  483. package/src/pdf/utils/image-command-partitioner.ts +28 -0
  484. package/src/pdf/utils/image-matrix-builder.ts +31 -0
  485. package/src/pdf/utils/image-utils.ts +26 -0
  486. package/src/pdf/utils/list-utils.ts +194 -0
  487. package/src/pdf/utils/node-text-run-factory.ts +202 -0
  488. package/src/pdf/utils/page-resource-registrar.ts +46 -0
  489. package/src/pdf/utils/result-combiner.ts +102 -0
  490. package/src/pdf/utils/shadow-utils.ts +127 -0
  491. package/src/pdf/utils/text-alignment-resolver.ts +76 -0
  492. package/src/pdf/utils/text-decoration-utils.ts +64 -0
  493. package/src/pdf/utils/text-layout-adjuster.ts +185 -0
  494. package/src/pdf/utils/text-utils.ts +193 -0
  495. package/src/pdf/utils/transform-scope-manager.ts +69 -0
  496. package/src/render/offset.ts +170 -0
  497. package/src/shim/empty.ts +2 -0
  498. package/src/shim/fs-empty.ts +5 -0
  499. package/src/shim/url-empty.ts +7 -0
  500. package/src/shim/zlib-empty.ts +9 -0
  501. package/src/style/shorthands/index.ts +19 -0
  502. package/src/style/ua/defaults.ts +69 -0
  503. package/src/svg/index.ts +4 -0
  504. package/src/svg/parser-registry.ts +71 -0
  505. package/src/svg/parser.ts +486 -0
  506. package/src/svg/path-data.ts +515 -0
  507. package/src/svg/types.ts +194 -0
  508. package/src/text/line-breaker.ts +321 -0
  509. package/src/text/text-transform.ts +43 -0
  510. package/src/text/text.ts +33 -0
  511. package/src/transform/css-parser.ts +95 -0
  512. package/src/types/fonts.ts +62 -0
  513. package/src/types/public.ts +19 -0
  514. package/src/units/page-utils.ts +58 -0
  515. package/src/units/units.ts +50 -0
  516. package/src/utils/base64.ts +24 -0
  517. package/test-output.txt +79 -0
  518. package/tests/css/background-parser.spec.ts +14 -0
  519. package/tests/css/clip-path-parser.spec.ts +66 -0
  520. package/tests/environment/path-resolution.spec.ts +104 -0
  521. package/tests/helpers/ai-layout-diagnostics.ts +141 -0
  522. package/tests/helpers/render-utils.ts +52 -0
  523. package/tests/helpers/text-geometry.ts +56 -0
  524. package/tests/layout/custom-properties.test.ts +38 -0
  525. package/tests/layout/gap-calculator.spec.ts +196 -0
  526. package/tests/layout/inline-background-alignment.spec.ts +93 -0
  527. package/tests/layout/inline-fragments.spec.ts +26 -0
  528. package/tests/layout/run-placer-baseline.spec.ts +108 -0
  529. package/tests/pdf/alignments.spec.ts +26 -0
  530. package/tests/pdf/background-clip.spec.ts +57 -0
  531. package/tests/pdf/background-repeat-space-round.spec.ts +35 -0
  532. package/tests/pdf/background-repeat.spec.ts +137 -0
  533. package/tests/pdf/border-radius.spec.ts +151 -0
  534. package/tests/pdf/clip-path.spec.ts +92 -0
  535. package/tests/pdf/radial-gradient.spec.ts +50 -0
  536. package/tests/pdf/svg-stroke-dash.spec.ts +81 -0
  537. package/tests/pdf/text-transform-matrix.spec.ts +43 -0
  538. package/tsconfig.json +17 -0
  539. package/types/fonts.js +10 -0
  540. package/vitest.config.ts +9 -0
@@ -0,0 +1,981 @@
1
+ import type { ParsedFont } from "../types.js";
2
+ import { decompressWoff2, type AsyncDecompressFn } from "../../compression/decompress.js";
3
+ import { Buf, readBase128, read255UShort } from "./buffer.js";
4
+
5
+ export interface Woff2Dependencies {
6
+ decompress?: AsyncDecompressFn;
7
+ transformers?: Map<number, Woff2Transform>;
8
+ }
9
+
10
+ interface ResolvedWoff2Dependencies {
11
+ decompress: AsyncDecompressFn;
12
+ transformers: Map<number, Woff2Transform>;
13
+ }
14
+
15
+ // --- Constants & helpers ----------------------------------------------------
16
+
17
+ const WOFF2_SIGNATURE = 0x774f4632; // "wOF2"
18
+ const WOFF2_HEADER_SIZE = 48;
19
+ const WOFF2_FLAG_TRANSFORM = 1 << 8;
20
+ const SFNT_HEADER_SIZE = 12;
21
+ const SFNT_ENTRY_SIZE = 16;
22
+ const CHECKSUM_ADJUSTMENT_OFFSET = 8;
23
+ const MAX_PLAUSIBLE_COMPRESSION_RATIO = 100;
24
+
25
+ const TAG = (text: string) =>
26
+ (text.charCodeAt(0) << 24) |
27
+ (text.charCodeAt(1) << 16) |
28
+ (text.charCodeAt(2) << 8) |
29
+ text.charCodeAt(3);
30
+
31
+ const TAG_GLYF = TAG("glyf");
32
+ const TAG_LOCA = TAG("loca");
33
+ const TAG_HMTX = TAG("hmtx");
34
+ const TAG_HHEA = TAG("hhea");
35
+ const TAG_HEAD = TAG("head");
36
+
37
+ // Known tag lookup as defined in table_tags.cc
38
+ const KNOWN_TAGS: number[] = [
39
+ TAG("cmap"),
40
+ TAG("head"),
41
+ TAG("hhea"),
42
+ TAG("hmtx"),
43
+ TAG("maxp"),
44
+ TAG("name"),
45
+ TAG("OS/2"),
46
+ TAG("post"),
47
+ TAG("cvt "),
48
+ TAG("fpgm"),
49
+ TAG_GLYF,
50
+ TAG_LOCA,
51
+ TAG("prep"),
52
+ TAG("CFF "),
53
+ TAG("VORG"),
54
+ TAG("EBDT"),
55
+ TAG("EBLC"),
56
+ TAG("gasp"),
57
+ TAG("hdmx"),
58
+ TAG("kern"),
59
+ TAG("LTSH"),
60
+ TAG("PCLT"),
61
+ TAG("VDMX"),
62
+ TAG("vhea"),
63
+ TAG("vmtx"),
64
+ TAG("BASE"),
65
+ TAG("GDEF"),
66
+ TAG("GPOS"),
67
+ TAG("GSUB"),
68
+ TAG("EBSC"),
69
+ TAG("JSTF"),
70
+ TAG("MATH"),
71
+ TAG("CBDT"),
72
+ TAG("CBLC"),
73
+ TAG("COLR"),
74
+ TAG("CPAL"),
75
+ TAG("SVG "),
76
+ TAG("sbix"),
77
+ TAG("acnt"),
78
+ TAG("avar"),
79
+ TAG("bdat"),
80
+ TAG("bloc"),
81
+ TAG("bsln"),
82
+ TAG("cvar"),
83
+ TAG("fdsc"),
84
+ TAG("feat"),
85
+ TAG("fmtx"),
86
+ TAG("fvar"),
87
+ TAG("gvar"),
88
+ TAG("hsty"),
89
+ TAG("just"),
90
+ TAG("lcar"),
91
+ TAG("mort"),
92
+ TAG("morx"),
93
+ TAG("opbd"),
94
+ TAG("prop"),
95
+ TAG("trak"),
96
+ TAG("Zapf"),
97
+ TAG("Silf"),
98
+ TAG("Glat"),
99
+ TAG("Gloc"),
100
+ TAG("Feat"),
101
+ TAG("Sill")
102
+ ];
103
+
104
+ function tagToString(tag: number): string {
105
+ return String.fromCharCode(
106
+ (tag >> 24) & 0xff,
107
+ (tag >> 16) & 0xff,
108
+ (tag >> 8) & 0xff,
109
+ tag & 0xff
110
+ );
111
+ }
112
+
113
+ const round4 = (v: number) => (v + 3) & ~3;
114
+
115
+ function resolveDependencies(deps?: Partial<Woff2Dependencies>): ResolvedWoff2Dependencies {
116
+ return {
117
+ decompress: deps?.decompress ?? decompressWoff2,
118
+ transformers: deps?.transformers ?? createDefaultTransformers(),
119
+ };
120
+ }
121
+
122
+ function computeULongSum(data: Uint8Array): number {
123
+ let checksum = 0 >>> 0;
124
+ const aligned = data.length & ~3;
125
+ for (let i = 0; i < aligned; i += 4) {
126
+ const value =
127
+ (data[i] << 24) |
128
+ (data[i + 1] << 16) |
129
+ (data[i + 2] << 8) |
130
+ data[i + 3];
131
+ checksum = (checksum + value) >>> 0;
132
+ }
133
+
134
+ if (aligned !== data.length) {
135
+ let v = 0;
136
+ for (let i = aligned; i < data.length; i++) {
137
+ v |= data[i] << (24 - 8 * (i & 3));
138
+ }
139
+ checksum = (checksum + v) >>> 0;
140
+ }
141
+ return checksum >>> 0;
142
+ }
143
+
144
+ // --- Data writers -----------------------------------------------------------
145
+
146
+ function store16(value: number, out: Uint8Array, offset: number): number {
147
+ const v = value & 0xffff;
148
+ out[offset] = (v >> 8) & 0xff;
149
+ out[offset + 1] = v & 0xff;
150
+ return offset + 2;
151
+ }
152
+
153
+ function store32(value: number, out: Uint8Array, offset: number): number {
154
+ out[offset] = (value >>> 24) & 0xff;
155
+ out[offset + 1] = (value >>> 16) & 0xff;
156
+ out[offset + 2] = (value >>> 8) & 0xff;
157
+ out[offset + 3] = value & 0xff;
158
+ return offset + 4;
159
+ }
160
+
161
+ // --- Types used internally --------------------------------------------------
162
+
163
+ interface Woff2Table {
164
+ tag: number;
165
+ flags: number;
166
+ srcOffset: number;
167
+ srcLength: number;
168
+ transformLength: number;
169
+ dstLength: number;
170
+ }
171
+
172
+ interface Woff2Header {
173
+ flavor: number;
174
+ numTables: number;
175
+ compressedOffset: number;
176
+ compressedLength: number;
177
+ uncompressedSize: number;
178
+ tables: Woff2Table[];
179
+ }
180
+
181
+ interface GlyfReconstruction {
182
+ glyfData: Uint8Array;
183
+ locaData: Uint8Array;
184
+ glyfChecksum: number;
185
+ locaChecksum: number;
186
+ numGlyphs: number;
187
+ indexFormat: number;
188
+ xMins: Int16Array;
189
+ }
190
+
191
+ interface TransformState {
192
+ glyfInfo: GlyfReconstruction | null;
193
+ numHMetrics: number;
194
+ }
195
+
196
+ interface TransformContext {
197
+ header: Woff2Header;
198
+ tableMap: Map<number, Uint8Array>;
199
+ tablesByTag: Map<number, Woff2Table>;
200
+ state: TransformState;
201
+ }
202
+
203
+ type Woff2Transform = (table: Woff2Table, srcData: Uint8Array, context: TransformContext) => void;
204
+
205
+ // --- Table directory parsing ------------------------------------------------
206
+
207
+ function readTableDirectory(buf: Buf, numTables: number): Woff2Table[] {
208
+ const tables: Woff2Table[] = [];
209
+ let srcOffset = 0;
210
+
211
+ for (let i = 0; i < numTables; i++) {
212
+ const flagByte = buf.readU8();
213
+ let tag: number;
214
+ if ((flagByte & 0x3f) === 0x3f) {
215
+ tag = buf.readU32();
216
+ } else {
217
+ tag = KNOWN_TAGS[flagByte & 0x3f] ?? 0;
218
+ }
219
+ const xformVersion = (flagByte >> 6) & 0x03;
220
+ let flags = 0;
221
+ if (tag === TAG_GLYF || tag === TAG_LOCA) {
222
+ if (xformVersion === 0) {
223
+ flags |= WOFF2_FLAG_TRANSFORM;
224
+ }
225
+ } else if (xformVersion !== 0) {
226
+ flags |= WOFF2_FLAG_TRANSFORM;
227
+ }
228
+ flags |= xformVersion;
229
+
230
+ const dstLength = readBase128(buf);
231
+ let transformLength = dstLength;
232
+ if (flags & WOFF2_FLAG_TRANSFORM) {
233
+ transformLength = readBase128(buf);
234
+ if (tag === TAG_LOCA && transformLength !== 0) {
235
+ throw new Error("Invalid WOFF2: transformed loca must have zero length");
236
+ }
237
+ }
238
+
239
+ tables.push({
240
+ tag,
241
+ flags,
242
+ srcOffset,
243
+ srcLength: transformLength,
244
+ transformLength,
245
+ dstLength
246
+ });
247
+
248
+ srcOffset += transformLength;
249
+ }
250
+
251
+ return tables;
252
+ }
253
+
254
+ function parseHeader(fontData: Uint8Array): Woff2Header {
255
+ if (fontData.byteLength < WOFF2_HEADER_SIZE) {
256
+ throw new Error("Invalid WOFF2: file too short");
257
+ }
258
+
259
+ const buf = new Buf(fontData);
260
+
261
+ const signature = buf.readU32();
262
+ if (signature !== WOFF2_SIGNATURE) {
263
+ throw new Error("Invalid WOFF2 signature");
264
+ }
265
+
266
+ const flavor = buf.readU32() >>> 0;
267
+ const reportedLength = buf.readU32();
268
+ if (reportedLength !== fontData.byteLength) {
269
+ throw new Error("Invalid WOFF2: length mismatch");
270
+ }
271
+
272
+ const numTables = buf.readU16();
273
+ if (numTables === 0) {
274
+ throw new Error("Invalid WOFF2: no tables");
275
+ }
276
+
277
+ buf.skip(2 + 4); // reserved + totalSfntSize (unused)
278
+ const compressedLength = buf.readU32();
279
+ buf.skip(4); // major/minorVersion
280
+
281
+ // metadata and private blocks (validate bounds if present)
282
+ const metaOffset = buf.readU32();
283
+ const metaLength = buf.readU32();
284
+ const metaOrigLen = buf.readU32();
285
+ if (metaOffset !== 0) {
286
+ if (metaOffset + metaLength > fontData.byteLength || metaOrigLen === 0) {
287
+ throw new Error("Invalid WOFF2 metadata block");
288
+ }
289
+ }
290
+
291
+ const privOffset = buf.readU32();
292
+ const privLength = buf.readU32();
293
+ if (privOffset !== 0 && privOffset + privLength > fontData.byteLength) {
294
+ throw new Error("Invalid WOFF2 private block");
295
+ }
296
+
297
+ const tables = readTableDirectory(buf, numTables);
298
+
299
+ const lastTable = tables[tables.length - 1];
300
+ const uncompressedSize = lastTable.srcOffset + lastTable.srcLength;
301
+
302
+ const compressedOffset = buf.offset;
303
+ if (compressedOffset + compressedLength > fontData.byteLength) {
304
+ throw new Error("Invalid WOFF2: compressed stream truncated");
305
+ }
306
+
307
+ // Basic ratio sanity check (similar to upstream impl)
308
+ const compressionRatio = uncompressedSize / Math.max(1, fontData.byteLength);
309
+ if (compressionRatio > MAX_PLAUSIBLE_COMPRESSION_RATIO) {
310
+ throw new Error("Invalid WOFF2: implausible compression ratio");
311
+ }
312
+
313
+ return {
314
+ flavor,
315
+ numTables,
316
+ compressedOffset,
317
+ compressedLength,
318
+ uncompressedSize,
319
+ tables
320
+ };
321
+ }
322
+
323
+ // --- Glyf reconstruction helpers -------------------------------------------
324
+
325
+ const GLYF_FLAGS = {
326
+ ON_CURVE: 1 << 0,
327
+ X_SHORT: 1 << 1,
328
+ Y_SHORT: 1 << 2,
329
+ REPEAT: 1 << 3,
330
+ X_SAME: 1 << 4,
331
+ Y_SAME: 1 << 5,
332
+ OVERLAP_SIMPLE: 1 << 6
333
+ };
334
+
335
+ const COMPOSITE_FLAGS = {
336
+ ARG_WORDS: 1 << 0,
337
+ WE_HAVE_A_SCALE: 1 << 3,
338
+ MORE_COMPONENTS: 1 << 5,
339
+ WE_HAVE_AN_XY_SCALE: 1 << 6,
340
+ WE_HAVE_A_TWO_BY_TWO: 1 << 7,
341
+ WE_HAVE_INSTRUCTIONS: 1 << 8
342
+ };
343
+
344
+ function withSign(flag: number, base: number): number {
345
+ return (flag & 1) ? base : -base;
346
+ }
347
+
348
+ function tripletDecode(
349
+ flagsIn: Uint8Array,
350
+ data: Uint8Array,
351
+ nPoints: number
352
+ ): { points: { x: number; y: number; onCurve: boolean }[]; consumed: number } {
353
+ let x = 0;
354
+ let y = 0;
355
+ let tripletIndex = 0;
356
+ const points: { x: number; y: number; onCurve: boolean }[] = new Array(
357
+ nPoints
358
+ );
359
+
360
+ for (let i = 0; i < nPoints; i++) {
361
+ let flag = flagsIn[i];
362
+ const onCurve = (flag >> 7) === 0;
363
+ flag &= 0x7f;
364
+ let nDataBytes = 1;
365
+ if (flag >= 84 && flag < 120) nDataBytes = 2;
366
+ else if (flag < 84) nDataBytes = 1;
367
+ else if (flag < 124) nDataBytes = 3;
368
+ else nDataBytes = 4;
369
+
370
+ if (tripletIndex + nDataBytes > data.length) {
371
+ throw new Error("Invalid WOFF2 glyf triplet");
372
+ }
373
+ let dx: number;
374
+ let dy: number;
375
+ if (flag < 10) {
376
+ dx = 0;
377
+ dy = withSign(flag, ((flag & 14) << 7) + data[tripletIndex]);
378
+ } else if (flag < 20) {
379
+ dx = withSign(flag, (((flag - 10) & 14) << 7) + data[tripletIndex]);
380
+ dy = 0;
381
+ } else if (flag < 84) {
382
+ const b0 = flag - 20;
383
+ const b1 = data[tripletIndex];
384
+ dx = withSign(flag, 1 + (b0 & 0x30) + (b1 >> 4));
385
+ dy = withSign(flag >> 1, 1 + ((b0 & 0x0c) << 2) + (b1 & 0x0f));
386
+ } else if (flag < 120) {
387
+ const b0 = flag - 84;
388
+ dx = withSign(flag, 1 + ((b0 / 12) << 8) + data[tripletIndex]);
389
+ dy = withSign(
390
+ flag >> 1,
391
+ 1 + (((b0 % 12) >> 2) << 8) + data[tripletIndex + 1]
392
+ );
393
+ } else if (flag < 124) {
394
+ const b2 = data[tripletIndex + 1];
395
+ dx = withSign(flag, (data[tripletIndex] << 4) + (b2 >> 4));
396
+ dy = withSign(flag >> 1, ((b2 & 0x0f) << 8) + data[tripletIndex + 2]);
397
+ } else {
398
+ dx = withSign(flag, (data[tripletIndex] << 8) + data[tripletIndex + 1]);
399
+ dy = withSign(
400
+ flag >> 1,
401
+ (data[tripletIndex + 2] << 8) + data[tripletIndex + 3]
402
+ );
403
+ }
404
+ tripletIndex += nDataBytes;
405
+ x += dx;
406
+ y += dy;
407
+ points[i] = { x, y, onCurve };
408
+ }
409
+
410
+ return { points, consumed: tripletIndex };
411
+ }
412
+
413
+ function storePoints(
414
+ points: { x: number; y: number; onCurve: boolean }[],
415
+ nContours: number,
416
+ instructionLength: number,
417
+ hasOverlap: boolean
418
+ ): Uint8Array {
419
+ const nPoints = points.length;
420
+ const xBytes: number[] = [];
421
+ const yBytes: number[] = [];
422
+
423
+ let lastX = 0;
424
+ let lastY = 0;
425
+ let lastFlag = -1;
426
+ let repeatCount = 0;
427
+
428
+ const flagOffset = 10 + 2 * nContours + 2 + instructionLength;
429
+
430
+ const flagsOut: number[] = [];
431
+
432
+ for (let i = 0; i < nPoints; i++) {
433
+ const point = points[i];
434
+ let flag = point.onCurve ? GLYF_FLAGS.ON_CURVE : 0;
435
+ if (hasOverlap && i === 0) {
436
+ flag |= GLYF_FLAGS.OVERLAP_SIMPLE;
437
+ }
438
+ const dx = point.x - lastX;
439
+ const dy = point.y - lastY;
440
+
441
+ if (dx === 0) flag |= GLYF_FLAGS.X_SAME;
442
+ else if (dx > -256 && dx < 256) {
443
+ flag |= GLYF_FLAGS.X_SHORT | (dx > 0 ? GLYF_FLAGS.X_SAME : 0);
444
+ xBytes.push(Math.abs(dx));
445
+ } else {
446
+ xBytes.push((dx >> 8) & 0xff, dx & 0xff);
447
+ }
448
+
449
+ if (dy === 0) flag |= GLYF_FLAGS.Y_SAME;
450
+ else if (dy > -256 && dy < 256) {
451
+ flag |= GLYF_FLAGS.Y_SHORT | (dy > 0 ? GLYF_FLAGS.Y_SAME : 0);
452
+ yBytes.push(Math.abs(dy));
453
+ } else {
454
+ yBytes.push((dy >> 8) & 0xff, dy & 0xff);
455
+ }
456
+
457
+ if (flag === lastFlag && repeatCount !== 255) {
458
+ flagsOut[flagsOut.length - 1] |= GLYF_FLAGS.REPEAT;
459
+ repeatCount++;
460
+ } else {
461
+ if (repeatCount !== 0) {
462
+ flagsOut.push(repeatCount);
463
+ }
464
+ flagsOut.push(flag);
465
+ repeatCount = 0;
466
+ lastFlag = flag;
467
+ }
468
+
469
+ lastX = point.x;
470
+ lastY = point.y;
471
+ }
472
+
473
+ if (repeatCount !== 0) {
474
+ flagsOut.push(repeatCount);
475
+ }
476
+
477
+ const totalSize = flagOffset + flagsOut.length + xBytes.length + yBytes.length;
478
+ const out = new Uint8Array(totalSize);
479
+ let offset = flagOffset;
480
+ for (const f of flagsOut) out[offset++] = f;
481
+ for (const b of xBytes) out[offset++] = b;
482
+ for (const b of yBytes) out[offset++] = b;
483
+ return out;
484
+ }
485
+
486
+ function computeBBox(points: { x: number; y: number }[]): [number, number, number, number] {
487
+ if (points.length === 0) {
488
+ return [0, 0, 0, 0];
489
+ }
490
+ let xMin = points[0].x;
491
+ let xMax = points[0].x;
492
+ let yMin = points[0].y;
493
+ let yMax = points[0].y;
494
+ for (let i = 1; i < points.length; i++) {
495
+ const p = points[i];
496
+ if (p.x < xMin) xMin = p.x;
497
+ if (p.x > xMax) xMax = p.x;
498
+ if (p.y < yMin) yMin = p.y;
499
+ if (p.y > yMax) yMax = p.y;
500
+ }
501
+ return [xMin, yMin, xMax, yMax];
502
+ }
503
+
504
+ function sizeOfComposite(stream: Buf): { size: number; haveInstructions: boolean } {
505
+ const start = stream.offset;
506
+ let flags = COMPOSITE_FLAGS.MORE_COMPONENTS;
507
+ let haveInstructions = false;
508
+
509
+ while (flags & COMPOSITE_FLAGS.MORE_COMPONENTS) {
510
+ flags = stream.readU16();
511
+ haveInstructions ||= (flags & COMPOSITE_FLAGS.WE_HAVE_INSTRUCTIONS) !== 0;
512
+ let argSize = 2; // glyph index
513
+ if (flags & COMPOSITE_FLAGS.ARG_WORDS) argSize += 4;
514
+ else argSize += 2;
515
+
516
+ if (flags & COMPOSITE_FLAGS.WE_HAVE_A_SCALE) argSize += 2;
517
+ else if (flags & COMPOSITE_FLAGS.WE_HAVE_AN_XY_SCALE) argSize += 4;
518
+ else if (flags & COMPOSITE_FLAGS.WE_HAVE_A_TWO_BY_TWO) argSize += 8;
519
+
520
+ stream.skip(argSize);
521
+ }
522
+
523
+ return { size: stream.offset - start, haveInstructions };
524
+ }
525
+
526
+ function storeLoca(
527
+ locaValues: number[],
528
+ indexFormat: number
529
+ ): { data: Uint8Array; checksum: number } {
530
+ const offsetSize = indexFormat ? 4 : 2;
531
+ const buffer = new Uint8Array(locaValues.length * offsetSize);
532
+ let off = 0;
533
+ for (const value of locaValues) {
534
+ if (indexFormat) {
535
+ off = store32(value, buffer, off);
536
+ } else {
537
+ off = store16(value >> 1, buffer, off);
538
+ }
539
+ }
540
+ return { data: buffer, checksum: computeULongSum(buffer) };
541
+ }
542
+
543
+ function reconstructGlyfTable(
544
+ transformed: Uint8Array,
545
+ locaDstLength: number
546
+ ): GlyfReconstruction {
547
+ const stream = new Buf(transformed);
548
+ /* const version = */ stream.readU16();
549
+ const flags = stream.readU16();
550
+ const hasOverlapBitmap = (flags & 1) !== 0;
551
+ const numGlyphs = stream.readU16();
552
+ const indexFormat = stream.readU16();
553
+
554
+ const substreamSizes: number[] = [];
555
+ for (let i = 0; i < 7; i++) {
556
+ substreamSizes.push(stream.readU32());
557
+ }
558
+
559
+ let dataOffset = (2 + 7) * 4; // header (version/flags/numGlyphs/indexFormat + sizes)
560
+ const substreams: Uint8Array[] = [];
561
+ for (const sz of substreamSizes) {
562
+ if (dataOffset + sz > transformed.length) {
563
+ throw new Error("Invalid WOFF2 glyf stream");
564
+ }
565
+ substreams.push(transformed.subarray(dataOffset, dataOffset + sz));
566
+ dataOffset += sz;
567
+ }
568
+
569
+ let overlapBitmap: Uint8Array | null = null;
570
+ if (hasOverlapBitmap) {
571
+ const len = (numGlyphs + 7) >> 3;
572
+ if (dataOffset + len > transformed.length) {
573
+ throw new Error("Invalid WOFF2 overlap bitmap");
574
+ }
575
+ overlapBitmap = transformed.subarray(dataOffset, dataOffset + len);
576
+ }
577
+
578
+ const nContourStream = new Buf(substreams[0]);
579
+ const nPointsStream = new Buf(substreams[1]);
580
+ const flagStream = new Buf(substreams[2]);
581
+ const glyphStream = new Buf(substreams[3]);
582
+ const compositeStream = new Buf(substreams[4]);
583
+ const bboxStream = new Buf(substreams[5]);
584
+ const instructionStream = new Buf(substreams[6]);
585
+
586
+ const bboxBitmapLen = ((numGlyphs + 31) >> 5) << 2;
587
+ const bboxBitmap = bboxStream.readBytes(bboxBitmapLen);
588
+
589
+ const locaValues = new Array<number>(numGlyphs + 1).fill(0);
590
+ const xMins = new Int16Array(numGlyphs);
591
+ const glyfChunks: Uint8Array[] = [];
592
+ let glyfChecksum = 0 >>> 0;
593
+ let currentGlyfOffset = 0;
594
+
595
+ for (let i = 0; i < numGlyphs; i++) {
596
+ const nContours = nContourStream.readU16();
597
+ const haveBbox = (bboxBitmap[i >> 3] & (0x80 >> (i & 7))) !== 0;
598
+ let glyphBytes: Uint8Array | null = null;
599
+
600
+ if (nContours === 0xffff) {
601
+ // Composite
602
+ const { size: compositeSize, haveInstructions } = sizeOfComposite(
603
+ new Buf(compositeStream.peekRemaining())
604
+ );
605
+ let instructionSize = 0;
606
+ if (haveInstructions) {
607
+ instructionSize = read255UShort(glyphStream);
608
+ }
609
+ const total =
610
+ 12 + compositeSize + instructionSize; // nContours + bbox + composite + instructions
611
+ const out = new Uint8Array(total);
612
+ let off = 0;
613
+ off = store16(nContours, out, off);
614
+ if (!haveBbox) {
615
+ throw new Error("Invalid WOFF2 glyf: composite without bbox");
616
+ }
617
+ const bboxData = bboxStream.readBytes(8);
618
+ const xMinRaw = (bboxData[0] << 8) | bboxData[1];
619
+ xMins[i] = xMinRaw & 0x8000 ? xMinRaw - 0x10000 : xMinRaw;
620
+ out.set(bboxData, off);
621
+ off += 8;
622
+ out.set(compositeStream.readBytes(compositeSize), off);
623
+ off += compositeSize;
624
+ if (haveInstructions) {
625
+ off = store16(instructionSize, out, off);
626
+ out.set(instructionStream.readBytes(instructionSize), off);
627
+ }
628
+ glyphBytes = out;
629
+ } else if (nContours > 0) {
630
+ const nPoints: number[] = [];
631
+ let totalPoints = 0;
632
+ for (let c = 0; c < nContours; c++) {
633
+ const pts = read255UShort(nPointsStream);
634
+ totalPoints += pts;
635
+ nPoints.push(pts);
636
+ }
637
+ const flagData = flagStream.readBytes(totalPoints);
638
+ const tripletsView = glyphStream.peekRemaining();
639
+ const { points, consumed } = tripletDecode(
640
+ flagData,
641
+ tripletsView,
642
+ totalPoints
643
+ );
644
+ glyphStream.offset += consumed;
645
+ const instructionSize = read255UShort(glyphStream);
646
+ const bbox = haveBbox ? bboxStream.readBytes(8) : null;
647
+
648
+ const baseSize = 12 + 2 * nContours + instructionSize;
649
+ const ptsBuf = storePoints(
650
+ points,
651
+ nContours,
652
+ instructionSize,
653
+ !!(overlapBitmap && (overlapBitmap[i >> 3] & (0x80 >> (i & 7))))
654
+ );
655
+ const glyphBuf = new Uint8Array(ptsBuf.length);
656
+ glyphBuf.set(ptsBuf);
657
+
658
+ let off = 0;
659
+ off = store16(nContours, glyphBuf, off);
660
+ if (bbox) {
661
+ glyphBuf.set(bbox, off);
662
+ off += 8;
663
+ } else {
664
+ const [xMin, yMin, xMax, yMax] = computeBBox(points);
665
+ off = store16(xMin, glyphBuf, off);
666
+ off = store16(yMin, glyphBuf, off);
667
+ off = store16(xMax, glyphBuf, off);
668
+ off = store16(yMax, glyphBuf, off);
669
+ }
670
+ let endPoint = -1;
671
+ for (const count of nPoints) {
672
+ endPoint += count;
673
+ off = store16(endPoint, glyphBuf, off);
674
+ }
675
+ off = store16(instructionSize, glyphBuf, off);
676
+ const remaining = instructionStream.peekRemaining().length;
677
+ const safeSize = Math.min(instructionSize, remaining);
678
+ const instructions = instructionStream.readBytes(safeSize);
679
+ glyphBuf.set(instructions, off);
680
+
681
+ glyphBytes = glyphBuf;
682
+
683
+ const xMinRaw = (glyphBuf[2] << 8) | glyphBuf[3];
684
+ xMins[i] = xMinRaw & 0x8000 ? xMinRaw - 0x10000 : xMinRaw;
685
+ } else {
686
+ // Empty glyph
687
+ if (haveBbox) {
688
+ throw new Error("Invalid WOFF2 glyf: empty glyph with bbox");
689
+ }
690
+ glyphBytes = new Uint8Array(0);
691
+ }
692
+
693
+ locaValues[i] = currentGlyfOffset;
694
+ glyfChunks.push(glyphBytes);
695
+ currentGlyfOffset += round4(glyphBytes.length);
696
+ glyfChecksum = (glyfChecksum + computeULongSum(glyphBytes)) >>> 0;
697
+ }
698
+
699
+ locaValues[numGlyphs] = currentGlyfOffset;
700
+
701
+ if (locaDstLength !== (indexFormat ? 4 : 2) * (numGlyphs + 1)) {
702
+ throw new Error("Invalid WOFF2 loca length");
703
+ }
704
+
705
+ // Build glyf table with per-glyph padding
706
+ const glyfSize = locaValues[numGlyphs];
707
+ const glyfData = new Uint8Array(glyfSize);
708
+ let glyfOffset = 0;
709
+ for (const chunk of glyfChunks) {
710
+ glyfData.set(chunk, glyfOffset);
711
+ glyfOffset += chunk.length;
712
+ const padded = round4(glyfOffset) - glyfOffset;
713
+ glyfOffset += padded;
714
+ }
715
+
716
+ const loca = storeLoca(locaValues, indexFormat);
717
+
718
+ return {
719
+ glyfData,
720
+ locaData: loca.data,
721
+ glyfChecksum,
722
+ locaChecksum: loca.checksum,
723
+ numGlyphs,
724
+ indexFormat,
725
+ xMins
726
+ };
727
+ }
728
+
729
+ // --- HMTX reconstruction ----------------------------------------------------
730
+
731
+ function readNumHMetrics(hheaTable: Uint8Array): number {
732
+ const buf = new Buf(hheaTable);
733
+ buf.skip(34);
734
+ return buf.readU16();
735
+ }
736
+
737
+ function reconstructTransformedHmtx(
738
+ transformed: Uint8Array,
739
+ numGlyphs: number,
740
+ numHMetrics: number,
741
+ xMins: Int16Array
742
+ ): { data: Uint8Array; checksum: number } {
743
+ const buf = new Buf(transformed);
744
+ const flags = buf.readU8();
745
+ const hasPropLSB = (flags & 1) === 0;
746
+ const hasMonoLSB = (flags & 2) === 0;
747
+ if ((flags & 0xfc) !== 0) {
748
+ throw new Error("Invalid hmtx flags");
749
+ }
750
+ if (hasPropLSB && hasMonoLSB) {
751
+ throw new Error("Invalid hmtx transform state");
752
+ }
753
+ if (numHMetrics < 1 || numHMetrics > numGlyphs) {
754
+ throw new Error("Invalid hmtx metrics count");
755
+ }
756
+
757
+ const advanceWidths: number[] = [];
758
+ const lsbs: number[] = [];
759
+
760
+ for (let i = 0; i < numHMetrics; i++) {
761
+ advanceWidths.push(buf.readU16());
762
+ }
763
+
764
+ for (let i = 0; i < numHMetrics; i++) {
765
+ lsbs.push(hasPropLSB ? buf.readS16() : xMins[i]);
766
+ }
767
+
768
+ for (let i = numHMetrics; i < numGlyphs; i++) {
769
+ lsbs.push(hasMonoLSB ? buf.readS16() : xMins[i]);
770
+ }
771
+
772
+ const out = new Uint8Array(2 * numGlyphs + 2 * numHMetrics);
773
+ let off = 0;
774
+ for (let i = 0; i < numGlyphs; i++) {
775
+ if (i < numHMetrics) {
776
+ off = store16(advanceWidths[i], out, off);
777
+ }
778
+ off = store16(lsbs[i], out, off);
779
+ }
780
+
781
+ return { data: out, checksum: computeULongSum(out) };
782
+ }
783
+
784
+ // --- Transform registry ----------------------------------------------------
785
+
786
+ function transformGlyfTable(
787
+ _table: Woff2Table,
788
+ srcData: Uint8Array,
789
+ context: TransformContext,
790
+ ): void {
791
+ const locaTable = context.tablesByTag.get(TAG_LOCA);
792
+ if (!locaTable) {
793
+ throw new Error("Invalid WOFF2: missing loca for glyf");
794
+ }
795
+ const glyfInfo = reconstructGlyfTable(srcData, locaTable.dstLength);
796
+ context.state.glyfInfo = glyfInfo;
797
+ context.tableMap.set(TAG_GLYF, glyfInfo.glyfData);
798
+ context.tableMap.set(TAG_LOCA, glyfInfo.locaData);
799
+ }
800
+
801
+ function transformLocaTable(
802
+ _table: Woff2Table,
803
+ _srcData: Uint8Array,
804
+ context: TransformContext,
805
+ ): void {
806
+ if (!context.tableMap.has(TAG_LOCA)) {
807
+ throw new Error("Invalid WOFF2: loca transformed without glyf");
808
+ }
809
+ }
810
+
811
+ function transformHmtxTable(
812
+ _table: Woff2Table,
813
+ srcData: Uint8Array,
814
+ context: TransformContext,
815
+ ): void {
816
+ const glyfInfo = context.state.glyfInfo;
817
+ if (!glyfInfo) {
818
+ throw new Error("Invalid WOFF2: hmtx transform before glyf");
819
+ }
820
+ const hmtx = reconstructTransformedHmtx(
821
+ srcData,
822
+ glyfInfo.numGlyphs,
823
+ context.state.numHMetrics,
824
+ glyfInfo.xMins
825
+ );
826
+ context.tableMap.set(TAG_HMTX, hmtx.data);
827
+ }
828
+
829
+ function createDefaultTransformers(): Map<number, Woff2Transform> {
830
+ return new Map<number, Woff2Transform>([
831
+ [TAG_GLYF, transformGlyfTable],
832
+ [TAG_LOCA, transformLocaTable],
833
+ [TAG_HMTX, transformHmtxTable],
834
+ ]);
835
+ }
836
+
837
+ // --- Font rebuild -----------------------------------------------------------
838
+
839
+ function buildSfnt(
840
+ flavor: number,
841
+ tableData: Map<number, Uint8Array>
842
+ ): { ttf: Uint8Array; tables: Record<string, Uint8Array> } {
843
+ const entries = Array.from(tableData.entries()).sort((a, b) => a[0] - b[0]);
844
+ const numTables = entries.length;
845
+ let offset = SFNT_HEADER_SIZE + SFNT_ENTRY_SIZE * numTables;
846
+
847
+ const records: {
848
+ tag: number;
849
+ checksum: number;
850
+ offset: number;
851
+ length: number;
852
+ data: Uint8Array;
853
+ }[] = [];
854
+
855
+ for (const [tag, data] of entries) {
856
+ const paddedLen = round4(data.length);
857
+ records.push({
858
+ tag,
859
+ checksum: computeULongSum(data),
860
+ offset,
861
+ length: data.length,
862
+ data
863
+ });
864
+ offset += paddedLen;
865
+ }
866
+
867
+ const ttf = new Uint8Array(offset);
868
+ let off = 0;
869
+ off = store32(flavor >>> 0, ttf, off);
870
+ off = store16(numTables, ttf, off);
871
+ let maxPow2 = 0;
872
+ while ((1 << (maxPow2 + 1)) <= numTables) maxPow2++;
873
+ const searchRange = (1 << maxPow2) * 16;
874
+ off = store16(searchRange, ttf, off);
875
+ off = store16(maxPow2, ttf, off);
876
+ off = store16(numTables * 16 - searchRange, ttf, off);
877
+
878
+ // table records
879
+ for (const rec of records) {
880
+ off = store32(rec.tag, ttf, off);
881
+ off = store32(rec.checksum, ttf, off);
882
+ off = store32(rec.offset, ttf, off);
883
+ off = store32(rec.length, ttf, off);
884
+ }
885
+
886
+ for (const rec of records) {
887
+ ttf.set(rec.data, rec.offset);
888
+ // zero padding already present
889
+ }
890
+
891
+ // checkSumAdjustment for 'head'
892
+ const headRecord = records.find((r) => r.tag === TAG_HEAD);
893
+ if (headRecord) {
894
+ const checksum = computeULongSum(ttf);
895
+ const adjustment = (0xb1b0afba - checksum) >>> 0;
896
+ store32(adjustment, ttf, headRecord.offset + CHECKSUM_ADJUSTMENT_OFFSET);
897
+ }
898
+
899
+ const tables: Record<string, Uint8Array> = {};
900
+ for (const [tag, data] of entries) {
901
+ tables[tagToString(tag)] = data;
902
+ }
903
+
904
+ return { ttf, tables };
905
+ }
906
+
907
+ function rebuildFont(
908
+ header: Woff2Header,
909
+ transformed: Uint8Array,
910
+ deps: ResolvedWoff2Dependencies
911
+ ): { ttf: Uint8Array; tables: Record<string, Uint8Array> } {
912
+ const tableMap = new Map<number, Uint8Array>();
913
+ const tablesByTag = new Map(header.tables.map((t) => [t.tag, t]));
914
+ const state: TransformState = { glyfInfo: null, numHMetrics: 0 };
915
+ const context: TransformContext = { header, tableMap, tablesByTag, state };
916
+ const transformers = deps.transformers;
917
+
918
+ const sortedTables = header.tables.slice().sort((a, b) => a.tag - b.tag);
919
+
920
+ for (const table of sortedTables) {
921
+ const srcStart = table.srcOffset;
922
+ const srcEnd = srcStart + table.srcLength;
923
+ if (srcEnd > transformed.length) {
924
+ throw new Error("Invalid WOFF2: table outside brotli buffer");
925
+ }
926
+ const srcData = transformed.subarray(srcStart, srcEnd);
927
+
928
+ if ((table.flags & WOFF2_FLAG_TRANSFORM) === 0) {
929
+ const data = new Uint8Array(srcData); // copy
930
+ if (table.tag === TAG_HEAD && data.length >= 12) {
931
+ // Zero checkSumAdjustment before checksums
932
+ data[CHECKSUM_ADJUSTMENT_OFFSET] = 0;
933
+ data[CHECKSUM_ADJUSTMENT_OFFSET + 1] = 0;
934
+ data[CHECKSUM_ADJUSTMENT_OFFSET + 2] = 0;
935
+ data[CHECKSUM_ADJUSTMENT_OFFSET + 3] = 0;
936
+ }
937
+ tableMap.set(table.tag, data);
938
+ if (table.tag === TAG_HHEA) {
939
+ state.numHMetrics = readNumHMetrics(data);
940
+ }
941
+ continue;
942
+ }
943
+
944
+ const transformer = transformers.get(table.tag);
945
+ if (!transformer) {
946
+ throw new Error(`Unsupported WOFF2 transform for ${tagToString(table.tag)}`);
947
+ }
948
+ transformer(table, srcData, context);
949
+ }
950
+
951
+ return buildSfnt(header.flavor, tableMap);
952
+ }
953
+
954
+ // --- Public API -------------------------------------------------------------
955
+
956
+ export async function decodeWoff2(
957
+ fontData: Uint8Array,
958
+ deps?: Partial<Woff2Dependencies>
959
+ ): Promise<{ parsed: ParsedFont; ttfBuffer: Uint8Array }> {
960
+ const resolvedDeps = resolveDependencies(deps);
961
+ const header = parseHeader(fontData);
962
+ const compressed = fontData.subarray(
963
+ header.compressedOffset,
964
+ header.compressedOffset + header.compressedLength
965
+ );
966
+
967
+ const decompressed = await resolvedDeps.decompress(compressed);
968
+ if (decompressed.byteLength !== header.uncompressedSize) {
969
+ throw new Error("Invalid WOFF2: brotli size mismatch");
970
+ }
971
+
972
+ const rebuilt = rebuildFont(header, decompressed, resolvedDeps);
973
+
974
+ const parsed: ParsedFont = {
975
+ flavor: header.flavor,
976
+ numTables: header.numTables,
977
+ tables: rebuilt.tables
978
+ };
979
+
980
+ return { parsed, ttfBuffer: rebuilt.ttf };
981
+ }