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,262 @@
1
+ import { log } from "../../logging/debug.js";
2
+ import type { Environment } from "../../environment/environment.js";
3
+ import { BrowserEnvironment } from "../../environment/browser-environment.js";
4
+ import type { FontConfig, FontFaceDef } from "../../types/fonts.js";
5
+
6
+ type FontSource = "assets" | "google";
7
+
8
+ type BuiltinFace = Omit<FontFaceDef, "src" | "data"> & { file: string; google?: string };
9
+
10
+ const BUILTIN_FACES: BuiltinFace[] = [
11
+ // Sans: primary and UI-friendly
12
+ { name: "Lato-Regular", family: "Lato", weight: 400, style: "normal", file: "woff2/lato/lato-latin-400-normal.woff2", google: "Lato" },
13
+ { name: "Lato-Bold", family: "Lato", weight: 700, style: "normal", file: "woff2/lato/lato-latin-700-normal.woff2", google: "Lato" },
14
+ { name: "Lato-Italic", family: "Lato", weight: 400, style: "italic", file: "woff2/lato/lato-latin-400-italic.woff2", google: "Lato" },
15
+ { name: "Lato-BoldItalic", family: "Lato", weight: 700, style: "italic", file: "woff2/lato/lato-latin-700-italic.woff2", google: "Lato" },
16
+ { name: "Roboto-Regular", family: "Roboto", weight: 400, style: "normal", file: "ttf/roboto/Roboto-Regular.ttf", google: "Roboto" },
17
+ { name: "Roboto-Bold", family: "Roboto", weight: 700, style: "normal", file: "ttf/roboto/Roboto-Bold.ttf", google: "Roboto" },
18
+ { name: "Roboto-Italic", family: "Roboto", weight: 400, style: "italic", file: "ttf/roboto/Roboto-Italic.ttf", google: "Roboto" },
19
+ { name: "Roboto-BoldItalic", family: "Roboto", weight: 700, style: "italic", file: "ttf/roboto/Roboto-BoldItalic.ttf", google: "Roboto" },
20
+ { name: "Arimo-Regular", family: "Arimo", weight: 400, style: "normal", file: "ttf/arimo/Arimo-Regular.ttf", google: "Arimo" },
21
+ { name: "Arimo-Bold", family: "Arimo", weight: 700, style: "normal", file: "ttf/arimo/Arimo-Bold.ttf", google: "Arimo" },
22
+ { name: "Arimo-Italic", family: "Arimo", weight: 400, style: "italic", file: "ttf/arimo/Arimo-Italic.ttf", google: "Arimo" },
23
+ { name: "Arimo-BoldItalic", family: "Arimo", weight: 700, style: "italic", file: "ttf/arimo/Arimo-BoldItalic.ttf", google: "Arimo" },
24
+ { name: "NotoSans-Regular", family: "Noto Sans", weight: 400, style: "normal", file: "ttf/notosans/NotoSans-Regular.ttf", google: "Noto Sans" },
25
+ { name: "DejaVuSans-Regular", family: "DejaVu Sans", weight: 400, style: "normal", file: "ttf/dejavu/DejaVuSans.ttf" },
26
+
27
+ // Serif / display
28
+ { name: "Tinos-Regular", family: "Tinos", weight: 400, style: "normal", file: "ttf/tinos/Tinos-Regular.ttf", google: "Tinos" },
29
+ { name: "Tinos-Bold", family: "Tinos", weight: 700, style: "normal", file: "ttf/tinos/Tinos-Bold.ttf", google: "Tinos" },
30
+ { name: "Tinos-Italic", family: "Tinos", weight: 400, style: "italic", file: "ttf/tinos/Tinos-Italic.ttf", google: "Tinos" },
31
+ { name: "Tinos-BoldItalic", family: "Tinos", weight: 700, style: "italic", file: "ttf/tinos/Tinos-BoldItalic.ttf", google: "Tinos" },
32
+ { name: "CinzelDecorative-Regular", family: "Cinzel Decorative", weight: 400, style: "normal", file: "ttf/cinzeldecorative/CinzelDecorative-Regular.ttf", google: "Cinzel Decorative" },
33
+ { name: "CinzelDecorative-Bold", family: "Cinzel Decorative", weight: 700, style: "normal", file: "ttf/cinzeldecorative/CinzelDecorative-Bold.ttf", google: "Cinzel Decorative" },
34
+ { name: "CinzelDecorative-Black", family: "Cinzel Decorative", weight: 900, style: "normal", file: "ttf/cinzeldecorative/CinzelDecorative-Black.ttf", google: "Cinzel Decorative" },
35
+ { name: "Caveat-Regular", family: "Caveat", weight: 400, style: "normal", file: "woff2/caveat/Caveat-Regular.woff2", google: "Caveat" },
36
+ { name: "Caveat-Bold", family: "Caveat", weight: 700, style: "normal", file: "woff2/caveat/Caveat-Bold.woff2", google: "Caveat" },
37
+
38
+ // Monospace
39
+ { name: "FiraCode-Light", family: "Fira Code", weight: 300, style: "normal", file: "ttf/firecode/FiraCode-Light.ttf", google: "Fira Code" },
40
+ { name: "FiraCode-Regular", family: "Fira Code", weight: 400, style: "normal", file: "ttf/firecode/FiraCode-Regular.ttf", google: "Fira Code" },
41
+ { name: "FiraCode-Medium", family: "Fira Code", weight: 500, style: "normal", file: "ttf/firecode/FiraCode-Medium.ttf", google: "Fira Code" },
42
+ { name: "FiraCode-SemiBold", family: "Fira Code", weight: 600, style: "normal", file: "ttf/firecode/FiraCode-SemiBold.ttf", google: "Fira Code" },
43
+ { name: "FiraCode-Bold", family: "Fira Code", weight: 700, style: "normal", file: "ttf/firecode/FiraCode-Bold.ttf", google: "Fira Code" },
44
+
45
+ // Emoji
46
+ { name: "NotoEmoji-Light", family: "Noto Emoji", weight: 300, style: "normal", file: "ttf/notoemoji/NotoEmoji-Light.ttf" },
47
+ { name: "NotoEmoji-Regular", family: "Noto Emoji", weight: 400, style: "normal", file: "ttf/notoemoji/NotoEmoji-Regular.ttf" },
48
+ { name: "NotoEmoji-Medium", family: "Noto Emoji", weight: 500, style: "normal", file: "ttf/notoemoji/NotoEmoji-Medium.ttf" },
49
+ { name: "NotoEmoji-SemiBold", family: "Noto Emoji", weight: 600, style: "normal", file: "ttf/notoemoji/NotoEmoji-SemiBold.ttf" },
50
+ { name: "NotoEmoji-Bold", family: "Noto Emoji", weight: 700, style: "normal", file: "ttf/notoemoji/NotoEmoji-Bold.ttf" },
51
+
52
+ // Math
53
+ { name: "STIXTwoMath-Regular", family: "STIX Two Math", weight: 400, style: "normal", file: "ttf/stixtwomath/STIXTwoMath-Regular.ttf" },
54
+ ];
55
+
56
+ const DEFAULT_STACK: string[] = [
57
+ "Lato",
58
+ "Roboto",
59
+ "Arimo",
60
+ "Noto Sans",
61
+ "DejaVu Sans",
62
+ "Tinos",
63
+ "Fira Code",
64
+ "Caveat",
65
+ "Cinzel Decorative",
66
+ "Noto Emoji",
67
+ "STIX Two Math",
68
+ ];
69
+
70
+ let cachedConfig: FontConfig | null | undefined;
71
+ let cachedSource: FontSource | null = null;
72
+ let loading: Promise<FontConfig | null> | null = null;
73
+
74
+ export async function loadBuiltinFontConfig(environment: Environment = new BrowserEnvironment()): Promise<FontConfig | null> {
75
+ const source = resolveFontSource();
76
+ if (cachedConfig !== undefined && cachedSource === source) {
77
+ return cachedConfig;
78
+ }
79
+ if (loading && cachedSource === source) {
80
+ return loading;
81
+ }
82
+
83
+ cachedSource = source;
84
+
85
+ loading = (async () => {
86
+ // Try the selected source; if google fails and was explicitly selected, fall back to assets.
87
+ if (source === "google") {
88
+ const googleFaces = await loadFromGoogleFonts(environment);
89
+ if (googleFaces.length) {
90
+ cachedConfig = { fontFaceDefs: googleFaces, defaultStack: DEFAULT_STACK };
91
+ return cachedConfig;
92
+ }
93
+ log("font", "warn", "Google Fonts requested but none loaded; falling back to bundled assets");
94
+ }
95
+
96
+ const assetFaces = await loadFromAssets(environment);
97
+ cachedConfig = assetFaces.length ? { fontFaceDefs: assetFaces, defaultStack: DEFAULT_STACK } : null;
98
+ return cachedConfig;
99
+ })();
100
+
101
+ try {
102
+ return await loading;
103
+ } finally {
104
+ loading = null;
105
+ }
106
+ }
107
+
108
+ function computeBaseUrl(): string {
109
+ const globalBase = (globalThis as any).__PAGYRA_FONT_BASE;
110
+ if (typeof globalBase === "string" && globalBase.trim().length > 0) {
111
+ return ensureTrailingSlash(globalBase.trim());
112
+ }
113
+ if (typeof window !== "undefined" && window.location) {
114
+ const origin = window.location.origin.endsWith("/") ? window.location.origin : `${window.location.origin}/`;
115
+ return `${origin}assets/fonts/`;
116
+ }
117
+ try {
118
+ return new URL("../../assets/fonts/", import.meta.url).toString();
119
+ } catch {
120
+ return "/assets/fonts/";
121
+ }
122
+ }
123
+
124
+ function ensureTrailingSlash(input: string): string {
125
+ return input.endsWith("/") ? input : `${input}/`;
126
+ }
127
+
128
+ function resolveFontSource(): FontSource {
129
+ const globalSource = (globalThis as any).__PAGYRA_FONT_SOURCE;
130
+ const legacyGoogle = (globalThis as any).__PAGYRA_USE_GOOGLE_FONTS;
131
+ if (typeof globalSource === "string") {
132
+ const normalized = globalSource.toLowerCase();
133
+ if (normalized === "google") return "google";
134
+ if (normalized === "assets") return "assets";
135
+ }
136
+ if (legacyGoogle === true) {
137
+ return "google";
138
+ }
139
+ return "assets";
140
+ }
141
+
142
+ async function loadFromAssets(environment: Environment): Promise<FontFaceDef[]> {
143
+ const baseUrl = computeBaseUrl();
144
+ const faces: FontFaceDef[] = [];
145
+ for (const face of BUILTIN_FACES) {
146
+ const url = new URL(face.file, baseUrl).toString();
147
+ try {
148
+ log("font", "debug", "Loading browser font file", { url });
149
+ const buffer = await environment.loader.load(url);
150
+ faces.push({
151
+ name: face.name,
152
+ family: face.family,
153
+ weight: face.weight,
154
+ style: face.style,
155
+ src: url,
156
+ data: buffer,
157
+ });
158
+ } catch (error) {
159
+ log("font", "warn", "Failed to load font file", { url, error });
160
+ }
161
+ }
162
+ return faces;
163
+ }
164
+
165
+ async function loadFromGoogleFonts(environment: Environment): Promise<FontFaceDef[]> {
166
+ const requestable = BUILTIN_FACES.filter((f) => !!f.google);
167
+ if (!requestable.length) return [];
168
+
169
+ const cssUrl = buildGoogleCssUrl(requestable);
170
+ if (!cssUrl) return [];
171
+
172
+ try {
173
+ log("font", "debug", "Fetching Google Fonts CSS", { url: cssUrl });
174
+ const cssBuffer = await environment.loader.load(cssUrl);
175
+ const cssText = new TextDecoder().decode(cssBuffer);
176
+ const faceUrls = parseGoogleCss(cssText);
177
+
178
+ const faces: FontFaceDef[] = [];
179
+ for (const face of requestable) {
180
+ const key = faceKey(face.family, face.weight, face.style);
181
+ const url = faceUrls.get(key);
182
+ if (!url) {
183
+ log("font", "warn", "Google Fonts entry missing", { family: face.family, weight: face.weight, style: face.style });
184
+ continue;
185
+ }
186
+ try {
187
+ log("font", "debug", "Downloading Google font file", { url, family: face.family, weight: face.weight, style: face.style });
188
+ const buffer = await environment.loader.load(url);
189
+ faces.push({
190
+ name: face.name,
191
+ family: face.family,
192
+ weight: face.weight,
193
+ style: face.style,
194
+ src: url,
195
+ data: buffer,
196
+ });
197
+ } catch (error) {
198
+ log("font", "warn", "Failed to download Google font", { url, family: face.family, weight: face.weight, style: face.style, error });
199
+ }
200
+ }
201
+ return faces;
202
+ } catch (error) {
203
+ log("font", "warn", "Failed to load Google Fonts CSS", { url: cssUrl, error });
204
+ return [];
205
+ }
206
+ }
207
+
208
+ function buildGoogleCssUrl(faces: BuiltinFace[]): string {
209
+ const byFamily = new Map<string, Set<string>>();
210
+ for (const face of faces) {
211
+ if (!face.google) continue;
212
+ const key = face.google;
213
+ let set = byFamily.get(key);
214
+ if (!set) {
215
+ set = new Set();
216
+ byFamily.set(key, set);
217
+ }
218
+ const ital = face.style === "italic" ? 1 : 0;
219
+ set.add(`${ital},${face.weight}`);
220
+ }
221
+
222
+ const families: string[] = [];
223
+ for (const [family, combos] of byFamily) {
224
+ const comboList = Array.from(combos).sort();
225
+ const param = `family=${encodeGoogleFamily(family)}:ital,wght@${comboList.join(";")}`;
226
+ families.push(param);
227
+ }
228
+
229
+ if (!families.length) return "";
230
+ return `https://fonts.googleapis.com/css2?${families.join("&")}&display=swap`;
231
+ }
232
+
233
+ function encodeGoogleFamily(family: string): string {
234
+ return encodeURIComponent(family.replace(/\s+/g, "+"));
235
+ }
236
+
237
+ function parseGoogleCss(cssText: string): Map<string, string> {
238
+ const result = new Map<string, string>();
239
+ const blockRegex = /@font-face\s*{[^}]*}/g;
240
+ for (const blockMatch of cssText.matchAll(blockRegex)) {
241
+ const block = blockMatch[0];
242
+ const familyMatch = /font-family:\s*['"]?([^'";]+)['"]?/i.exec(block);
243
+ const weightMatch = /font-weight:\s*([0-9]+)/i.exec(block);
244
+ const styleMatch = /font-style:\s*(italic|normal)/i.exec(block);
245
+ const srcMatch = /src:\s*[^;]*url\(([^)]+)\)/i.exec(block);
246
+ if (!familyMatch || !weightMatch || !styleMatch || !srcMatch) continue;
247
+
248
+ const family = familyMatch[1].trim();
249
+ const weight = Number.parseInt(weightMatch[1], 10);
250
+ const style = styleMatch[1].toLowerCase() === "italic" ? "italic" : "normal";
251
+ const url = srcMatch[1].replace(/['"]/g, "").trim();
252
+ const key = faceKey(family, weight, style);
253
+ if (!result.has(key)) {
254
+ result.set(key, url);
255
+ }
256
+ }
257
+ return result;
258
+ }
259
+
260
+ function faceKey(family: string, weight: number, style: string): string {
261
+ return `${family.toLowerCase()}@${weight}@${style.toLowerCase()}`;
262
+ }
@@ -0,0 +1,126 @@
1
+ import * as path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { log, type LogLevel } from "../../logging/debug.js";
4
+ import type { FontConfig, FontFaceDef } from "../../types/fonts.js";
5
+ import type { Environment } from "../../environment/environment.js";
6
+ import { NodeEnvironment } from "../../environment/node-environment.js";
7
+
8
+ type BuiltinFace = Omit<FontFaceDef, "src" | "data"> & { file: string };
9
+
10
+ const BUILTIN_FACES: BuiltinFace[] = [
11
+ // Sans: primary and UI-friendly
12
+ { name: "Lato-Regular", family: "Lato", weight: 400, style: "normal", file: "woff2/lato/lato-latin-400-normal.woff2" },
13
+ { name: "Lato-Bold", family: "Lato", weight: 700, style: "normal", file: "woff2/lato/lato-latin-700-normal.woff2" },
14
+ { name: "Lato-Italic", family: "Lato", weight: 400, style: "italic", file: "woff2/lato/lato-latin-400-italic.woff2" },
15
+ { name: "Lato-BoldItalic", family: "Lato", weight: 700, style: "italic", file: "woff2/lato/lato-latin-700-italic.woff2" },
16
+ { name: "Roboto-Regular", family: "Roboto", weight: 400, style: "normal", file: "ttf/roboto/Roboto-Regular.ttf" },
17
+ { name: "Roboto-Bold", family: "Roboto", weight: 700, style: "normal", file: "ttf/roboto/Roboto-Bold.ttf" },
18
+ { name: "Roboto-Italic", family: "Roboto", weight: 400, style: "italic", file: "ttf/roboto/Roboto-Italic.ttf" },
19
+ { name: "Roboto-BoldItalic", family: "Roboto", weight: 700, style: "italic", file: "ttf/roboto/Roboto-BoldItalic.ttf" },
20
+ { name: "Arimo-Regular", family: "Arimo", weight: 400, style: "normal", file: "ttf/arimo/Arimo-Regular.ttf" },
21
+ { name: "Arimo-Bold", family: "Arimo", weight: 700, style: "normal", file: "ttf/arimo/Arimo-Bold.ttf" },
22
+ { name: "Arimo-Italic", family: "Arimo", weight: 400, style: "italic", file: "ttf/arimo/Arimo-Italic.ttf" },
23
+ { name: "Arimo-BoldItalic", family: "Arimo", weight: 700, style: "italic", file: "ttf/arimo/Arimo-BoldItalic.ttf" },
24
+ { name: "NotoSans-Regular", family: "Noto Sans", weight: 400, style: "normal", file: "ttf/notosans/NotoSans-Regular.ttf" },
25
+ { name: "DejaVuSans-Regular", family: "DejaVu Sans", weight: 400, style: "normal", file: "ttf/dejavu/DejaVuSans.ttf" },
26
+
27
+ // Serif / display
28
+ { name: "Tinos-Regular", family: "Tinos", weight: 400, style: "normal", file: "ttf/tinos/Tinos-Regular.ttf" },
29
+ { name: "Tinos-Bold", family: "Tinos", weight: 700, style: "normal", file: "ttf/tinos/Tinos-Bold.ttf" },
30
+ { name: "Tinos-Italic", family: "Tinos", weight: 400, style: "italic", file: "ttf/tinos/Tinos-Italic.ttf" },
31
+ { name: "Tinos-BoldItalic", family: "Tinos", weight: 700, style: "italic", file: "ttf/tinos/Tinos-BoldItalic.ttf" },
32
+ { name: "CinzelDecorative-Regular", family: "Cinzel Decorative", weight: 400, style: "normal", file: "ttf/cinzeldecorative/CinzelDecorative-Regular.ttf" },
33
+ { name: "CinzelDecorative-Bold", family: "Cinzel Decorative", weight: 700, style: "normal", file: "ttf/cinzeldecorative/CinzelDecorative-Bold.ttf" },
34
+ { name: "CinzelDecorative-Black", family: "Cinzel Decorative", weight: 900, style: "normal", file: "ttf/cinzeldecorative/CinzelDecorative-Black.ttf" },
35
+ { name: "Caveat-Regular", family: "Caveat", weight: 400, style: "normal", file: "woff2/caveat/Caveat-Regular.woff2" },
36
+ { name: "Caveat-Bold", family: "Caveat", weight: 700, style: "normal", file: "woff2/caveat/Caveat-Bold.woff2" },
37
+
38
+ // Monospace
39
+ { name: "FiraCode-Light", family: "Fira Code", weight: 300, style: "normal", file: "ttf/firecode/FiraCode-Light.ttf" },
40
+ { name: "FiraCode-Regular", family: "Fira Code", weight: 400, style: "normal", file: "ttf/firecode/FiraCode-Regular.ttf" },
41
+ { name: "FiraCode-Medium", family: "Fira Code", weight: 500, style: "normal", file: "ttf/firecode/FiraCode-Medium.ttf" },
42
+ { name: "FiraCode-SemiBold", family: "Fira Code", weight: 600, style: "normal", file: "ttf/firecode/FiraCode-SemiBold.ttf" },
43
+ { name: "FiraCode-Bold", family: "Fira Code", weight: 700, style: "normal", file: "ttf/firecode/FiraCode-Bold.ttf" },
44
+
45
+ // Emoji
46
+ { name: "NotoEmoji-Light", family: "Noto Emoji", weight: 300, style: "normal", file: "ttf/notoemoji/NotoEmoji-Light.ttf" },
47
+ { name: "NotoEmoji-Regular", family: "Noto Emoji", weight: 400, style: "normal", file: "ttf/notoemoji/NotoEmoji-Regular.ttf" },
48
+ { name: "NotoEmoji-Medium", family: "Noto Emoji", weight: 500, style: "normal", file: "ttf/notoemoji/NotoEmoji-Medium.ttf" },
49
+ { name: "NotoEmoji-SemiBold", family: "Noto Emoji", weight: 600, style: "normal", file: "ttf/notoemoji/NotoEmoji-SemiBold.ttf" },
50
+ { name: "NotoEmoji-Bold", family: "Noto Emoji", weight: 700, style: "normal", file: "ttf/notoemoji/NotoEmoji-Bold.ttf" },
51
+
52
+ // Math
53
+ { name: "STIXTwoMath-Regular", family: "STIX Two Math", weight: 400, style: "normal", file: "ttf/stixtwomath/STIXTwoMath-Regular.ttf" },
54
+ ];
55
+
56
+ let cachedConfig: FontConfig | null | undefined;
57
+ let loading: Promise<FontConfig | null> | null = null;
58
+
59
+ export async function loadBuiltinFontConfig(environment: Environment = new NodeEnvironment()): Promise<FontConfig | null> {
60
+ if (loading) {
61
+ return loading;
62
+ }
63
+ if (!isNodeRuntime()) {
64
+ cachedConfig = null;
65
+ return null;
66
+ }
67
+
68
+ loading = (async () => {
69
+ try {
70
+ const baseDir = resolveFontsDir();
71
+ log('font', 'debug', "Builtin font baseDir:", baseDir);
72
+ const faces: FontFaceDef[] = [];
73
+ for (const face of BUILTIN_FACES) {
74
+ const filePath = path.join(baseDir, face.file);
75
+ log('font', 'debug', "Loading font file:", filePath);
76
+ try {
77
+ const buffer = await environment.loader.load(filePath);
78
+ faces.push({
79
+ name: face.name,
80
+ family: face.family,
81
+ weight: face.weight,
82
+ style: face.style,
83
+ src: filePath,
84
+ data: buffer,
85
+ });
86
+ } catch (err) {
87
+ log('font', 'warn', `Failed to load font file: ${filePath}`, err);
88
+ }
89
+ }
90
+ cachedConfig = {
91
+ fontFaceDefs: faces,
92
+ defaultStack: [
93
+ "Lato",
94
+ "Roboto",
95
+ "Arimo",
96
+ "Noto Sans",
97
+ "DejaVu Sans",
98
+ "Tinos",
99
+ "Fira Code",
100
+ "Caveat",
101
+ "Cinzel Decorative",
102
+ "Noto Emoji",
103
+ "STIX Two Math",
104
+ ],
105
+ };
106
+ return cachedConfig;
107
+ } catch (error) {
108
+ log("font", 'warn', "Unable to load builtin font config", { error });
109
+ cachedConfig = null;
110
+ return null;
111
+ } finally {
112
+ loading = null;
113
+ }
114
+ })();
115
+
116
+ return loading;
117
+ }
118
+
119
+ function resolveFontsDir(): string {
120
+ const here = fileURLToPath(import.meta.url);
121
+ return path.resolve(path.dirname(here), "../../../assets/fonts");
122
+ }
123
+
124
+ function isNodeRuntime(): boolean {
125
+ return typeof process !== "undefined" && !!process.versions?.node;
126
+ }
@@ -0,0 +1,242 @@
1
+ import type { GlyphOutlineCmd } from "../../types/fonts.js";
2
+ import type { DataViewReader } from "./ttf-table-provider.js";
3
+ import { TransformationMatrix } from "./transformation-matrix.js";
4
+
5
+ /**
6
+ * Provider interface for retrieving glyph outlines.
7
+ * Used to resolve component glyph references in composite glyphs.
8
+ */
9
+ export interface GlyphOutlineProvider {
10
+ /**
11
+ * Gets the outline for a glyph.
12
+ * @param glyphId - The glyph ID
13
+ * @param depth - Current recursion depth (for preventing infinite recursion)
14
+ * @returns Glyph outline commands, or null if not available
15
+ */
16
+ getOutline(glyphId: number, depth?: number): GlyphOutlineCmd[] | null;
17
+ }
18
+
19
+ /**
20
+ * Component transformation and reference data.
21
+ */
22
+ interface ComponentData {
23
+ glyphIndex: number;
24
+ flags: number;
25
+ transform: TransformationMatrix;
26
+ hasMoreComponents: boolean;
27
+ }
28
+
29
+ /**
30
+ * Parses composite (component-based) TrueType glyphs.
31
+ *
32
+ * Composite glyphs are built from one or more simple glyphs (components),
33
+ * each with its own transformation (translation, scale, rotation).
34
+ */
35
+ export class CompositeGlyphParser {
36
+ private readonly recursionLimit = 8;
37
+ private readonly visited: Set<number> = new Set();
38
+
39
+ // Component flags
40
+ private static readonly ARG_1_AND_2_ARE_WORDS = 0x0001;
41
+ private static readonly WE_HAVE_A_SCALE = 0x0008;
42
+ private static readonly MORE_COMPONENTS = 0x0020;
43
+ private static readonly WE_HAVE_AN_X_AND_Y_SCALE = 0x0040;
44
+ private static readonly WE_HAVE_A_TWO_BY_TWO = 0x0080;
45
+
46
+ constructor(private readonly reader: DataViewReader) { }
47
+
48
+ /**
49
+ * Parses a composite glyph.
50
+ * @param view - DataView containing the composite glyph data
51
+ * @param provider - Provider for resolving component glyph references
52
+ * @param depth - Current recursion depth
53
+ * @returns Array of glyph outline commands, or null if parsing fails
54
+ */
55
+ parse(
56
+ view: DataView,
57
+ provider: GlyphOutlineProvider,
58
+ depth: number = 0
59
+ ): GlyphOutlineCmd[] | null {
60
+ if (depth > this.recursionLimit) return null;
61
+
62
+ // Composite glyph component records start at offset 10 (after header)
63
+ const cmds = this.parseComponentRecords(view, 10, provider, depth);
64
+
65
+ // Clear visited set for next parse
66
+ this.visited.clear();
67
+
68
+ return cmds;
69
+ }
70
+
71
+ /**
72
+ * Parses component records from a composite glyph.
73
+ * @param view - DataView containing the glyph data
74
+ * @param startOffset - Starting offset for component records
75
+ * @param provider - Provider for resolving component references
76
+ * @param depth - Current recursion depth
77
+ * @returns Combined outline commands from all components, or null if parsing fails
78
+ */
79
+ private parseComponentRecords(
80
+ view: DataView,
81
+ startOffset: number,
82
+ provider: GlyphOutlineProvider,
83
+ depth: number
84
+ ): GlyphOutlineCmd[] | null {
85
+ if (depth > this.recursionLimit) return null;
86
+
87
+ let offset = startOffset;
88
+ const allCmds: GlyphOutlineCmd[] = [];
89
+
90
+ while (true) {
91
+ const component = this.readComponentData(view, offset);
92
+ if (!component) return null;
93
+
94
+ offset = this.calculateNextOffset(view, offset, component.flags);
95
+
96
+ // Prevent infinite recursion cycles
97
+ if (this.visited.has(component.glyphIndex)) {
98
+ // Skip this component to avoid cycle
99
+ } else {
100
+ this.visited.add(component.glyphIndex);
101
+
102
+ // Get component's outline
103
+ const componentCmds = provider.getOutline(component.glyphIndex, depth + 1);
104
+ if (componentCmds && componentCmds.length > 0) {
105
+ // Transform the component's outline
106
+ const transformedCmds = component.transform.applyToCommands(componentCmds);
107
+
108
+ // Ensure proper moveTo at start if needed
109
+ if (transformedCmds.length > 0 && transformedCmds[0].type !== "moveTo") {
110
+ const firstCoord = this.findFirstCoordinate(transformedCmds);
111
+ if (firstCoord) {
112
+ allCmds.push({ type: "moveTo", x: firstCoord.x, y: firstCoord.y });
113
+ }
114
+ }
115
+
116
+ allCmds.push(...transformedCmds);
117
+ }
118
+ }
119
+
120
+ // Check if there are more components
121
+ if (!component.hasMoreComponents) break;
122
+ }
123
+
124
+ return allCmds.length > 0 ? allCmds : null;
125
+ }
126
+
127
+ /**
128
+ * Reads a single component's data from the view.
129
+ * @param view - DataView containing the glyph data
130
+ * @param offset - Starting offset for this component
131
+ * @returns Component data or null if reading fails
132
+ */
133
+ private readComponentData(view: DataView, offset: number): ComponentData | null {
134
+ if (offset + 4 > view.byteLength) return null;
135
+
136
+ const flags = this.reader.getUint16(view, offset);
137
+ const glyphIndex = this.reader.getUint16(view, offset + 2);
138
+ let p = offset + 4;
139
+
140
+ // Read arguments (translation or matching points)
141
+ let arg1 = 0;
142
+ let arg2 = 0;
143
+
144
+ if (flags & CompositeGlyphParser.ARG_1_AND_2_ARE_WORDS) {
145
+ // 16-bit signed arguments
146
+ if (p + 4 > view.byteLength) return null;
147
+ arg1 = this.reader.getInt16(view, p);
148
+ arg2 = this.reader.getInt16(view, p + 2);
149
+ p += 4;
150
+ } else {
151
+ // 8-bit signed arguments
152
+ if (p + 2 > view.byteLength) return null;
153
+ arg1 = this.reader.getInt8(view, p);
154
+ arg2 = this.reader.getInt8(view, p + 1);
155
+ p += 2;
156
+ }
157
+
158
+ // Read transformation matrix
159
+ let mxx = 1, mxy = 0, myx = 0, myy = 1;
160
+
161
+ if (flags & CompositeGlyphParser.WE_HAVE_A_SCALE) {
162
+ // Uniform scale
163
+ if (p + 2 > view.byteLength) return null;
164
+ const scale = this.reader.getInt16(view, p) / (1 << 14);
165
+ mxx = scale;
166
+ myy = scale;
167
+ } else if (flags & CompositeGlyphParser.WE_HAVE_AN_X_AND_Y_SCALE) {
168
+ // Non-uniform scale
169
+ if (p + 4 > view.byteLength) return null;
170
+ const sx = this.reader.getInt16(view, p) / (1 << 14);
171
+ const sy = this.reader.getInt16(view, p + 2) / (1 << 14);
172
+ mxx = sx;
173
+ myy = sy;
174
+ } else if (flags & CompositeGlyphParser.WE_HAVE_A_TWO_BY_TWO) {
175
+ // Full 2x2 matrix
176
+ if (p + 8 > view.byteLength) return null;
177
+ mxx = this.reader.getInt16(view, p) / (1 << 14);
178
+ mxy = this.reader.getInt16(view, p + 2) / (1 << 14);
179
+ myx = this.reader.getInt16(view, p + 4) / (1 << 14);
180
+ myy = this.reader.getInt16(view, p + 6) / (1 << 14);
181
+ }
182
+
183
+ // Treat args as translation (simplified - spec allows matching points too)
184
+ const transform = TransformationMatrix.fromComponents(arg1, arg2, mxx, mxy, myx, myy);
185
+
186
+ return {
187
+ glyphIndex,
188
+ flags,
189
+ transform,
190
+ hasMoreComponents: !!(flags & CompositeGlyphParser.MORE_COMPONENTS)
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Calculates the next offset after reading a component record.
196
+ * @param view - DataView containing the glyph data
197
+ * @param offset - Current offset
198
+ * @param flags - Component flags
199
+ * @returns Next offset
200
+ */
201
+ private calculateNextOffset(view: DataView, offset: number, flags: number): number {
202
+ let p = offset + 4; // flags + glyphIndex
203
+
204
+ // Skip arguments
205
+ if (flags & CompositeGlyphParser.ARG_1_AND_2_ARE_WORDS) {
206
+ p += 4;
207
+ } else {
208
+ p += 2;
209
+ }
210
+
211
+ // Skip transformation data
212
+ if (flags & CompositeGlyphParser.WE_HAVE_A_SCALE) {
213
+ p += 2;
214
+ } else if (flags & CompositeGlyphParser.WE_HAVE_AN_X_AND_Y_SCALE) {
215
+ p += 4;
216
+ } else if (flags & CompositeGlyphParser.WE_HAVE_A_TWO_BY_TWO) {
217
+ p += 8;
218
+ }
219
+
220
+ return p;
221
+ }
222
+
223
+ /**
224
+ * Finds the first coordinate in a list of commands.
225
+ * @param cmds - Array of glyph outline commands
226
+ * @returns First coordinate found, or null
227
+ */
228
+ private findFirstCoordinate(cmds: GlyphOutlineCmd[]): { x: number; y: number } | null {
229
+ for (const c of cmds) {
230
+ if (c.type === "moveTo" || c.type === "lineTo") {
231
+ return { x: c.x, y: c.y };
232
+ }
233
+ if (c.type === "quadTo") {
234
+ return { x: c.x, y: c.y };
235
+ }
236
+ if (c.type === "cubicTo") {
237
+ return { x: c.x, y: c.y };
238
+ }
239
+ }
240
+ return null;
241
+ }
242
+ }