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,180 @@
1
+ import type { CmapData } from "../../types/fonts.js";
2
+ import { TtfTableParser } from "./ttf-table-parser.js";
3
+
4
+ // cmap table tags / helpers could live here if needed
5
+
6
+ export class CmapParser implements CmapData {
7
+ public readonly unicodeMap = new Map<number, number>();
8
+
9
+ constructor(parser: TtfTableParser, cmapTable: DataView) {
10
+ this.parseCmapTable(parser, cmapTable);
11
+ }
12
+
13
+ private parseCmapTable(parser: TtfTableParser, table: DataView): void {
14
+ if (table.byteLength < 4) throw new Error("Truncated cmap header");
15
+
16
+ const version = table.getUint16(0, false);
17
+ if (version !== 0) return; // only support version 0 for now
18
+
19
+ const numSubtables = table.getUint16(2, false);
20
+ const subtables: { platformId: number; encodingId: number; offset: number }[] = [];
21
+
22
+ // collect subtables with bounds checks
23
+ const expectedDirLen = 4 + numSubtables * 8;
24
+ if (expectedDirLen > table.byteLength) throw new Error("Truncated cmap subtables directory");
25
+
26
+ for (let i = 0; i < numSubtables; i++) {
27
+ const subtableOffset = 4 + i * 8;
28
+ const platformId = table.getUint16(subtableOffset, false);
29
+ const encodingId = table.getUint16(subtableOffset + 2, false);
30
+ const offset = table.getUint32(subtableOffset + 4, false);
31
+
32
+ if (offset >= table.byteLength) {
33
+ throw new Error("Invalid subtable offset in cmap");
34
+ }
35
+
36
+ subtables.push({ platformId, encodingId, offset });
37
+ }
38
+
39
+ // Selection strategy: prefer Platform 0 (Unicode) format 12/4, then Platform 3 encIDs 10/1 (format 12/4),
40
+ // otherwise fall back to any format 4 available.
41
+ const pickSubtable = (): { platformId: number; encodingId: number; offset: number } | null => {
42
+ const findPreferred = (platforms: { pid: number; eids: number[] }[]) => {
43
+ for (const p of platforms) {
44
+ for (const st of subtables) {
45
+ if (st.platformId !== p.pid) continue;
46
+ if (p.eids.length > 0 && !p.eids.includes(st.encodingId)) continue;
47
+
48
+ if (st.offset + 2 <= table.byteLength) {
49
+ const fmt = table.getUint16(st.offset, false);
50
+ if (fmt === 12) return st;
51
+ }
52
+ }
53
+ }
54
+ for (const p of platforms) {
55
+ for (const st of subtables) {
56
+ if (st.platformId !== p.pid) continue;
57
+ if (p.eids.length > 0 && !p.eids.includes(st.encodingId)) continue;
58
+ if (st.offset + 2 <= table.byteLength) {
59
+ const fmt = table.getUint16(st.offset, false);
60
+ if (fmt === 4) return st;
61
+ }
62
+ }
63
+ }
64
+ return null;
65
+ };
66
+
67
+ let chosen = findPreferred([{ pid: 0, eids: [] }]);
68
+ if (chosen) return chosen;
69
+
70
+ chosen = findPreferred([{ pid: 3, eids: [10] }, { pid: 3, eids: [1] }]);
71
+ if (chosen) return chosen;
72
+
73
+ for (const st of subtables) {
74
+ if (st.offset + 2 <= table.byteLength && table.getUint16(st.offset, false) === 4) return st;
75
+ }
76
+
77
+ return null;
78
+ };
79
+
80
+ const selected = pickSubtable();
81
+ if (!selected) {
82
+ // No suitable cmap found — leave unicodeMap empty.
83
+ return;
84
+ }
85
+
86
+ const format = table.getUint16(selected.offset, false);
87
+ if (format === 4) {
88
+ this.parseFormat4Table(parser, table, selected.offset);
89
+ } else if (format === 12) {
90
+ this.parseFormat12Table(parser, table, selected.offset);
91
+ } else {
92
+ // unsupported format for now; leave empty
93
+ return;
94
+ }
95
+ }
96
+
97
+ private parseFormat4Table(_parser: TtfTableParser, table: DataView, offset: number): void {
98
+ if (offset + 8 > table.byteLength) throw new Error("Truncated cmap format 4 header");
99
+ const format = table.getUint16(offset, false);
100
+ if (format !== 4) throw new Error("Unexpected cmap format (not 4)");
101
+
102
+ const length = table.getUint16(offset + 2, false);
103
+ if (offset + length > table.byteLength) throw new Error("Truncated cmap format 4 table");
104
+
105
+ const segCountX2 = table.getUint16(offset + 6, false);
106
+ const segCount = segCountX2 / 2;
107
+
108
+ const endCodeOffset = offset + 14;
109
+ const startCodeOffset = endCodeOffset + segCount * 2 + 2;
110
+ const idDeltaOffset = startCodeOffset + segCount * 2;
111
+ const idRangeOffsetOffset = idDeltaOffset + segCount * 2;
112
+
113
+ if (idRangeOffsetOffset > offset + length) throw new Error("Truncated cmap format 4 arrays");
114
+
115
+ for (let i = 0; i < segCount; i++) {
116
+ const endCode = table.getUint16(endCodeOffset + i * 2, false);
117
+ const startCode = table.getUint16(startCodeOffset + i * 2, false);
118
+ const idDelta = table.getInt16(idDeltaOffset + i * 2, false); // signed
119
+ const idRangeOffset = table.getUint16(idRangeOffsetOffset + i * 2, false);
120
+
121
+ if (startCode === 0xffff) break;
122
+
123
+ if (startCode > endCode) throw new Error("Invalid cmap segment (startCode > endCode)");
124
+
125
+ for (let code = startCode; code <= endCode; code++) {
126
+ let glyphId = 0;
127
+
128
+ if (idRangeOffset === 0) {
129
+ glyphId = (code + idDelta) & 0xffff;
130
+ } else {
131
+ const idRangeWordOffset = idRangeOffsetOffset + i * 2;
132
+ const glyphIndexOffset = idRangeWordOffset + idRangeOffset + (code - startCode) * 2;
133
+
134
+ if (glyphIndexOffset + 2 > offset + length) {
135
+ throw new Error("Truncated cmap glyphIndexArray");
136
+ }
137
+
138
+ glyphId = table.getUint16(glyphIndexOffset, false);
139
+ if (glyphId !== 0) {
140
+ glyphId = (glyphId + idDelta) & 0xffff;
141
+ }
142
+ }
143
+
144
+ this.unicodeMap.set(code, glyphId);
145
+ }
146
+ }
147
+ }
148
+
149
+ private parseFormat12Table(_parser: TtfTableParser, table: DataView, offset: number): void {
150
+ // Format 12 uses 32-bit fields
151
+ if (offset + 16 > table.byteLength) throw new Error("Truncated cmap format 12 header");
152
+ const format = table.getUint16(offset, false);
153
+ if (format !== 12) throw new Error("Unexpected cmap format (not 12)");
154
+ const nGroups = table.getUint32(offset + 12, false);
155
+ const groupsOffset = offset + 16;
156
+ const required = groupsOffset + nGroups * 12;
157
+ if (required > table.byteLength) throw new Error("Truncated cmap format 12 groups");
158
+
159
+ let p = groupsOffset;
160
+ for (let g = 0; g < nGroups; g++, p += 12) {
161
+ const startCode = table.getUint32(p, false);
162
+ const endCode = table.getUint32(p + 4, false);
163
+ const startGID = table.getUint32(p + 8, false);
164
+
165
+ if (startCode > endCode) throw new Error("Invalid cmap format 12 group (startCode > endCode)");
166
+
167
+ for (let cp = startCode; cp <= endCode; cp++) {
168
+ this.unicodeMap.set(cp, startGID + (cp - startCode));
169
+ }
170
+ }
171
+ }
172
+
173
+ getGlyphId(codePoint: number): number {
174
+ return this.unicodeMap.get(codePoint) ?? 0;
175
+ }
176
+
177
+ hasCodePoint(codePoint: number): boolean {
178
+ return this.unicodeMap.has(codePoint);
179
+ }
180
+ }
@@ -0,0 +1,58 @@
1
+ import type { TtfMetrics } from "../../types/fonts.js";
2
+ import { TtfTableParser } from "./ttf-table-parser.js";
3
+
4
+ // Table tags
5
+ const HEAD = 0x68656164; // 'head'
6
+ const HHEA = 0x68686561; // 'hhea'
7
+ const OS_2 = 0x4f532f32; // 'OS/2'
8
+
9
+ export function parseGlobalMetrics(parser: TtfTableParser): { metrics: TtfMetrics; numberOfHMetricsRaw: number; headBBox?: readonly [number, number, number, number] } {
10
+ const headTable = parser.getTable(HEAD);
11
+ if (!headTable) throw new Error("Missing head table");
12
+
13
+ const unitsPerEm = parser.getUint16(headTable, 18);
14
+
15
+ const hheaTable = parser.getTable(HHEA);
16
+ if (!hheaTable) throw new Error("Missing hhea table");
17
+
18
+ const ascender = parser.getInt16(hheaTable, 4);
19
+ const descender = parser.getInt16(hheaTable, 6);
20
+ const lineGap = parser.getInt16(hheaTable, 8);
21
+ const numberOfHMetricsRaw = parser.getUint16(hheaTable, 34);
22
+
23
+ // Parse OS/2 table for optional cap/x heights
24
+ const os2Table = parser.getTable(OS_2);
25
+ let capHeight = ascender;
26
+ let xHeight = Math.round(ascender * 0.5);
27
+
28
+ // Only use OS/2 capHeight/xHeight if the table exists and version >= 2 (fields present)
29
+ if (os2Table && os2Table.byteLength >= 4) {
30
+ const os2Version = parser.getUint16(os2Table, 0);
31
+ if (os2Version >= 2 && os2Table.byteLength >= 96) {
32
+ capHeight = parser.getInt16(os2Table, 88);
33
+ xHeight = parser.getInt16(os2Table, 86);
34
+ }
35
+ }
36
+
37
+ // Read head bbox (xMin, yMin, xMax, yMax) if present
38
+ let headBBox: readonly [number, number, number, number] | undefined = undefined;
39
+ // head table contains bbox at offsets 36..42 (int16)
40
+ if (headTable.byteLength >= 44) {
41
+ const xMin = parser.getInt16(headTable, 36);
42
+ const yMin = parser.getInt16(headTable, 38);
43
+ const xMax = parser.getInt16(headTable, 40);
44
+ const yMax = parser.getInt16(headTable, 42);
45
+ headBBox = [xMin, yMin, xMax, yMax];
46
+ }
47
+
48
+ const metrics: TtfMetrics = {
49
+ unitsPerEm,
50
+ ascender,
51
+ descender,
52
+ lineGap,
53
+ capHeight,
54
+ xHeight
55
+ };
56
+
57
+ return { metrics, numberOfHMetricsRaw, headBBox };
58
+ }
@@ -0,0 +1,125 @@
1
+ import type { GlyphOutlineCmd } from "../../types/fonts.js";
2
+ import type { GlyphTableProvider } from "./ttf-table-provider.js";
3
+ import type { GlyphOutlineProvider } from "./composite-glyph-parser.js";
4
+ import { LocaTableReader } from "./loca-reader.js";
5
+ import { SimpleGlyphParser } from "./simple-glyph-parser.js";
6
+ import { CompositeGlyphParser } from "./composite-glyph-parser.js";
7
+
8
+ // Table tags
9
+ const LOCA = 0x6c6f6361; // 'loca'
10
+ const GLYF = 0x676c7966; // 'glyf'
11
+ const HEAD = 0x68656164; // 'head'
12
+
13
+ /**
14
+ * Minimal TrueType glyf/loca parser.
15
+ *
16
+ * - Supports simple glyphs (contours with on-curve + off-curve quadratic points).
17
+ * - Supports composite glyphs (components with transformations).
18
+ * - Provides robust bounds checks and defensive behavior.
19
+ *
20
+ * Produces a function getGlyphOutline(gid) => GlyphOutlineCmd[] | null
21
+ */
22
+
23
+ /**
24
+ * Creates a glyph outline provider function for a TrueType font.
25
+ * @param parser - Provider for accessing font tables and reading binary data
26
+ * @returns Function that returns glyph outline commands for a given glyph ID
27
+ */
28
+ export function createGlyfOutlineProvider(
29
+ parser: GlyphTableProvider
30
+ ): (gid: number) => GlyphOutlineCmd[] | null {
31
+
32
+ // Read head table to determine loca format
33
+ const headTable = parser.getTable(HEAD);
34
+ if (!headTable) {
35
+ return () => null; // Cannot determine loca format
36
+ }
37
+
38
+ const indexToLocFormat = readIndexToLocFormat(headTable, parser);
39
+
40
+ // Get required tables
41
+ const locaTable = parser.getTable(LOCA);
42
+ const glyfTable = parser.getTable(GLYF);
43
+
44
+ if (!locaTable || !glyfTable) {
45
+ return () => null;
46
+ }
47
+
48
+ // Initialize parsers and readers
49
+ const locaReader = new LocaTableReader(locaTable, indexToLocFormat, parser);
50
+ const simpleParser = new SimpleGlyphParser(parser);
51
+ const compositeParser = new CompositeGlyphParser(parser);
52
+
53
+ /**
54
+ * Internal recursive function for getting glyph outlines.
55
+ * @param gid - Glyph ID
56
+ * @param depth - Current recursion depth
57
+ * @returns Glyph outline commands or null
58
+ */
59
+ const getOutlineInternal = (gid: number, depth: number = 0): GlyphOutlineCmd[] | null => {
60
+ // Get glyph offset range from loca table
61
+ const range = locaReader.getGlyphOffset(gid);
62
+ if (!range) return null;
63
+
64
+ // Check for empty glyph
65
+ if (range.start === range.end) return null;
66
+
67
+ // Validate range against glyf table size
68
+ if (!locaReader.validateRange(range, glyfTable.byteLength)) return null;
69
+
70
+ // Create DataView for this glyph's data
71
+ const view = createGlyphView(glyfTable, range.start, range.end);
72
+ if (!view || view.byteLength < 10) return null;
73
+
74
+ // Read numberOfContours to determine glyph type
75
+ const numberOfContours = view.getInt16(0, false);
76
+
77
+ if (numberOfContours >= 0) {
78
+ // Simple glyph
79
+ return simpleParser.parse(view);
80
+ } else {
81
+ // Composite glyph
82
+ const provider: GlyphOutlineProvider = {
83
+ getOutline: (componentGid: number, componentDepth?: number) =>
84
+ getOutlineInternal(componentGid, componentDepth ?? 0)
85
+ };
86
+ return compositeParser.parse(view, provider, depth);
87
+ }
88
+ };
89
+
90
+ // Return the public provider function
91
+ return (gid: number) => getOutlineInternal(gid, 0);
92
+ }
93
+
94
+ /**
95
+ * Reads the indexToLocFormat field from the head table.
96
+ * @param headTable - DataView of the head table
97
+ * @param reader - Binary data reader
98
+ * @returns Format value (0 = short, 1 = long)
99
+ */
100
+ function readIndexToLocFormat(headTable: DataView, reader: GlyphTableProvider): number {
101
+ try {
102
+ // indexToLocFormat is at offset 50 in the head table
103
+ return reader.getUint16(headTable, 50);
104
+ } catch {
105
+ return 0; // Default to short format on error
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Creates a DataView for a specific glyph's data within the glyf table.
111
+ * @param glyfTable - DataView of the glyf table
112
+ * @param start - Start offset
113
+ * @param end - End offset
114
+ * @returns DataView for the glyph data, or null if invalid
115
+ */
116
+ function createGlyphView(glyfTable: DataView, start: number, end: number): DataView | null {
117
+ try {
118
+ return new DataView(glyfTable.buffer, glyfTable.byteOffset + start, end - start);
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ // Re-export types for convenience
125
+ export type { GlyphTableProvider } from "./ttf-table-provider.js";
@@ -0,0 +1,43 @@
1
+ import type { GlyphMetrics } from "../../types/fonts.js";
2
+ import { TtfTableParser } from "./ttf-table-parser.js";
3
+
4
+ // Table tags
5
+ const HMTX = 0x686d7478; // 'hmtx'
6
+
7
+ export function parseGlyphMetrics(
8
+ parser: TtfTableParser,
9
+ numberOfHMetrics: number,
10
+ numGlyphs: number
11
+ ): Map<number, GlyphMetrics> {
12
+ const hmtxTable = parser.getTable(HMTX);
13
+ if (!hmtxTable) throw new Error("Missing hmtx table");
14
+
15
+ const glyphMetrics = new Map<number, GlyphMetrics>();
16
+
17
+ // Validate long metrics length
18
+ const longBytes = numberOfHMetrics * 4;
19
+ if (hmtxTable.byteLength < longBytes) {
20
+ throw new Error("Truncated hmtx long metrics");
21
+ }
22
+
23
+ // Read first numberOfHMetrics entries (advanceWidth + lsb)
24
+ let lastAdvanceWidth = 0;
25
+ for (let gid = 0; gid < numGlyphs; gid++) {
26
+ if (gid < numberOfHMetrics) {
27
+ const off = gid * 4;
28
+ if (off + 4 > hmtxTable.byteLength) throw new Error("Truncated hmtx entry");
29
+ const advanceWidth = parser.getUint16(hmtxTable, off);
30
+ const leftSideBearing = parser.getInt16(hmtxTable, off + 2);
31
+ glyphMetrics.set(gid, { advanceWidth, leftSideBearing });
32
+ lastAdvanceWidth = advanceWidth;
33
+ } else {
34
+ // LSBs follow after long metrics
35
+ const lsbOff = longBytes + (gid - numberOfHMetrics) * 2;
36
+ if (lsbOff + 2 > hmtxTable.byteLength) throw new Error("Truncated hmtx LSB array");
37
+ const leftSideBearing = parser.getInt16(hmtxTable, lsbOff);
38
+ glyphMetrics.set(gid, { advanceWidth: lastAdvanceWidth, leftSideBearing });
39
+ }
40
+ }
41
+
42
+ return glyphMetrics;
43
+ }
@@ -0,0 +1,269 @@
1
+ import { readFileSync } from "fs";
2
+ import { TtfFontMetrics } from "../../types/fonts.js";
3
+ import { TtfTableParser } from "./ttf-table-parser.js";
4
+ import { parseGlobalMetrics } from "./ttf-global-metrics.js";
5
+ import { parseGlyphMetrics } from "./ttf-glyph-metrics.js";
6
+ import { CmapParser } from "./ttf-cmap.js";
7
+ import { createGlyfOutlineProvider } from "./ttf-glyf.js";
8
+ import type { KerningMap } from "../../types/fonts.js";
9
+
10
+ export function parseTtfBuffer(buffer: ArrayBuffer): TtfFontMetrics {
11
+ const parser = new TtfTableParser(buffer);
12
+
13
+ // Parse global metrics (head, hhea, OS/2)
14
+ const { metrics, numberOfHMetricsRaw, headBBox } = parseGlobalMetrics(parser);
15
+
16
+ // Parse maxp for numGlyphs
17
+ const MAXP = 0x6d617870; // 'maxp'
18
+ const maxpTable = parser.getTable(MAXP);
19
+ if (!maxpTable) throw new Error("Missing maxp table");
20
+
21
+ const numGlyphs = parser.getUint16(maxpTable, 4);
22
+
23
+ // Clamp numberOfHMetrics to glyph count
24
+ const numberOfHMetrics = Math.min(numberOfHMetricsRaw, numGlyphs);
25
+
26
+ // Parse glyph metrics
27
+ const glyphMetrics = parseGlyphMetrics(parser, numberOfHMetrics, numGlyphs);
28
+
29
+ // Parse cmap
30
+ const CMAP = 0x636d6170; // 'cmap'
31
+ const cmapTable = parser.getTable(CMAP);
32
+ if (!cmapTable) throw new Error("Missing cmap table");
33
+
34
+ const cmap = new CmapParser(parser, cmapTable);
35
+ const kerning = mergeKerningMaps(parseKerningTable(parser), parseGposKerning(parser));
36
+
37
+ const glyfProvider = createGlyfOutlineProvider(parser);
38
+ return new TtfFontMetrics(metrics, glyphMetrics, cmap, headBBox, glyfProvider, kerning);
39
+ }
40
+
41
+ export function parseTtfFont(filePath: string): TtfFontMetrics {
42
+ const buffer = readFileSync(filePath);
43
+ const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
44
+ return parseTtfBuffer(arrayBuffer);
45
+ }
46
+
47
+ /**
48
+ * Parse the 'kern' table (format 0) into a nested map of adjustments in font units.
49
+ * Returns undefined when the table is missing or unsupported.
50
+ */
51
+ function parseKerningTable(parser: TtfTableParser): KerningMap | undefined {
52
+ const KERN = 0x6b65726e; // 'kern'
53
+ const table = parser.getTable(KERN);
54
+ if (!table) return undefined;
55
+
56
+ // kern header: version (uint16), nTables (uint16)
57
+ if (table.byteLength < 4) return undefined;
58
+ const nTables = table.getUint16(2, false);
59
+ let offset = 4;
60
+ const result: KerningMap = new Map();
61
+
62
+ for (let i = 0; i < nTables; i++) {
63
+ if (offset + 6 > table.byteLength) break;
64
+ const subtableVersion = table.getUint16(offset, false);
65
+ const length = table.getUint16(offset + 2, false);
66
+ const coverage = table.getUint16(offset + 4, false);
67
+ const format = coverage >> 8;
68
+ // Only support format 0 (uni-directional kerning pairs)
69
+ if (subtableVersion === 0 && format === 0) {
70
+ if (offset + length > table.byteLength) break;
71
+ const stOffset = offset + 6;
72
+ if (stOffset + 8 > table.byteLength) break;
73
+ const pairCount = table.getUint16(stOffset, false);
74
+ // skip searchRange, entrySelector, rangeShift (next 6 bytes)
75
+ let cursor = stOffset + 8;
76
+ for (let p = 0; p < pairCount; p++) {
77
+ if (cursor + 6 > table.byteLength) break;
78
+ const left = table.getUint16(cursor, false);
79
+ const right = table.getUint16(cursor + 2, false);
80
+ const value = table.getInt16(cursor + 4, false);
81
+ if (value !== 0) {
82
+ const rightMap = result.get(left) ?? new Map<number, number>();
83
+ rightMap.set(right, value);
84
+ result.set(left, rightMap);
85
+ }
86
+ cursor += 6;
87
+ }
88
+ }
89
+ offset += length;
90
+ }
91
+
92
+ return result.size > 0 ? result : undefined;
93
+ }
94
+
95
+ /**
96
+ * Parse GPOS PairPos (format 1) kerning into a nested map.
97
+ * Supports value1 xAdvance/xPlacement adjustments; ignores class-based and other lookup types.
98
+ */
99
+ function parseGposKerning(parser: TtfTableParser): KerningMap | undefined {
100
+ const GPOS = 0x47504f53; // 'GPOS'
101
+ const table = parser.getTable(GPOS);
102
+ if (!table) return undefined;
103
+
104
+ // Header
105
+ if (table.byteLength < 10) return undefined;
106
+ const gposStart = 0;
107
+ const lookupListOffset = table.getUint16(gposStart + 8, false);
108
+ if (lookupListOffset === 0 || lookupListOffset >= table.byteLength) return undefined;
109
+
110
+ const kerning: KerningMap = new Map();
111
+
112
+ // Lookup list
113
+ const lookupCount = table.getUint16(gposStart + lookupListOffset, false);
114
+ let lookupOffsetsPos = gposStart + lookupListOffset + 2;
115
+ for (let li = 0; li < lookupCount; li++) {
116
+ if (lookupOffsetsPos + 2 > table.byteLength) break;
117
+ const lookupOffset = table.getUint16(lookupOffsetsPos, false);
118
+ lookupOffsetsPos += 2;
119
+ if (lookupOffset === 0 || gposStart + lookupOffset >= table.byteLength) continue;
120
+
121
+ const lookupStart = gposStart + lookupOffset;
122
+ if (lookupStart + 6 > table.byteLength) continue;
123
+ const lookupType = table.getUint16(lookupStart, false);
124
+ const subTableCount = table.getUint16(lookupStart + 4, false);
125
+ let subTableOffsetsPos = lookupStart + 6;
126
+
127
+ if (lookupType !== 2) {
128
+ // Only Pair Adjustment lookups
129
+ continue;
130
+ }
131
+
132
+ for (let si = 0; si < subTableCount; si++) {
133
+ if (subTableOffsetsPos + 2 > table.byteLength) break;
134
+ const stOffset = table.getUint16(subTableOffsetsPos, false);
135
+ subTableOffsetsPos += 2;
136
+ if (stOffset === 0 || lookupStart + stOffset >= table.byteLength) continue;
137
+ const stStart = lookupStart + stOffset;
138
+ if (stStart + 10 > table.byteLength) continue;
139
+
140
+ const format = table.getUint16(stStart, false);
141
+ if (format !== 1) {
142
+ // Only handle PairPos format 1 (per-glyph pair sets)
143
+ continue;
144
+ }
145
+
146
+ const coverageOffset = table.getUint16(stStart + 2, false);
147
+ const valueFormat1 = table.getUint16(stStart + 4, false);
148
+ const valueFormat2 = table.getUint16(stStart + 6, false);
149
+ const pairSetCount = table.getUint16(stStart + 8, false);
150
+
151
+ const coverage = parseCoverageTable(table, stStart + coverageOffset);
152
+ const pairSetOffsetsStart = stStart + 10;
153
+
154
+ for (let pi = 0; pi < pairSetCount; pi++) {
155
+ const leftGlyph = coverage[pi];
156
+ if (leftGlyph === undefined) continue;
157
+
158
+ const pairSetOffsetPos = pairSetOffsetsStart + pi * 2;
159
+ if (pairSetOffsetPos + 2 > table.byteLength) break;
160
+ const pairSetOffset = table.getUint16(pairSetOffsetPos, false);
161
+ if (pairSetOffset === 0 || stStart + pairSetOffset >= table.byteLength) continue;
162
+
163
+ const pairSetStart = stStart + pairSetOffset;
164
+ if (pairSetStart + 2 > table.byteLength) continue;
165
+ const pairValueCount = table.getUint16(pairSetStart, false);
166
+ let recPos = pairSetStart + 2;
167
+
168
+ for (let r = 0; r < pairValueCount; r++) {
169
+ if (recPos + 2 > table.byteLength) break;
170
+ const rightGlyph = table.getUint16(recPos, false);
171
+ recPos += 2;
172
+
173
+ const val1 = readValueRecord(table, recPos, valueFormat1);
174
+ recPos += val1.length;
175
+
176
+ const val2 = readValueRecord(table, recPos, valueFormat2);
177
+ recPos += val2.length;
178
+
179
+ const adjust = (val1.xAdvance ?? 0) + (val1.xPlacement ?? 0) + (val2.xPlacement ?? 0);
180
+ if (adjust !== 0) {
181
+ const rightMap = kerning.get(leftGlyph) ?? new Map<number, number>();
182
+ rightMap.set(rightGlyph, adjust);
183
+ kerning.set(leftGlyph, rightMap);
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ return kerning.size > 0 ? kerning : undefined;
191
+ }
192
+
193
+ function parseCoverageTable(table: DataView, offset: number): number[] {
194
+ if (offset + 4 > table.byteLength) return [];
195
+ const format = table.getUint16(offset, false);
196
+ if (format === 1) {
197
+ const count = table.getUint16(offset + 2, false);
198
+ const glyphs: number[] = [];
199
+ let pos = offset + 4;
200
+ for (let i = 0; i < count; i++) {
201
+ if (pos + 2 > table.byteLength) break;
202
+ glyphs.push(table.getUint16(pos, false));
203
+ pos += 2;
204
+ }
205
+ return glyphs;
206
+ }
207
+ if (format === 2) {
208
+ const rangeCount = table.getUint16(offset + 2, false);
209
+ const glyphs: number[] = [];
210
+ let pos = offset + 4;
211
+ for (let i = 0; i < rangeCount; i++) {
212
+ if (pos + 6 > table.byteLength) break;
213
+ const start = table.getUint16(pos, false);
214
+ const end = table.getUint16(pos + 2, false);
215
+ const startCoverage = table.getUint16(pos + 4, false);
216
+ for (let g = 0; g <= end - start; g++) {
217
+ glyphs[startCoverage + g] = start + g;
218
+ }
219
+ pos += 6;
220
+ }
221
+ return glyphs;
222
+ }
223
+ return [];
224
+ }
225
+
226
+ function readValueRecord(table: DataView, offset: number, valueFormat: number): { xAdvance?: number; xPlacement?: number; length: number } {
227
+ let pos = offset;
228
+ let xPlacement: number | undefined;
229
+ let xAdvance: number | undefined;
230
+
231
+ const consume = (flag: number) => {
232
+ const v = table.getInt16(pos, false);
233
+ pos += 2;
234
+ return v;
235
+ };
236
+
237
+ if (valueFormat & 0x0001) xPlacement = consume(0x0001);
238
+ if (valueFormat & 0x0002) consume(0x0002); // yPlacement (ignored)
239
+ if (valueFormat & 0x0004) xAdvance = consume(0x0004);
240
+ if (valueFormat & 0x0008) consume(0x0008); // yAdvance (ignored)
241
+
242
+ // Skip Device/Variation tables if present (we don't apply them; consume offsets)
243
+ const deviceFlags = [0x0010, 0x0020, 0x0040, 0x0080];
244
+ for (const flag of deviceFlags) {
245
+ if (valueFormat & flag) {
246
+ pos += 2;
247
+ }
248
+ }
249
+
250
+ return { xAdvance, xPlacement, length: pos - offset };
251
+ }
252
+
253
+ function mergeKerningMaps(a?: KerningMap, b?: KerningMap): KerningMap | undefined {
254
+ if (!a && !b) return undefined;
255
+ const result: KerningMap = new Map();
256
+ const add = (map?: KerningMap) => {
257
+ if (!map) return;
258
+ for (const [left, rights] of map.entries()) {
259
+ const target = result.get(left) ?? new Map<number, number>();
260
+ for (const [right, val] of rights.entries()) {
261
+ target.set(right, (target.get(right) ?? 0) + val);
262
+ }
263
+ result.set(left, target);
264
+ }
265
+ };
266
+ add(a);
267
+ add(b);
268
+ return result;
269
+ }