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,305 @@
1
+ /**
2
+ * MaxRects glyph atlas packer (separate module).
3
+ *
4
+ * Implements MaxRects (Best Short Side Fit) packing with basic free-rect
5
+ * splitting and pruning. This lives alongside the existing shelf packer so
6
+ * we can switch to it safely and run tests incrementally.
7
+ *
8
+ * API mirrors glyph-atlas.ts: GlyphAtlasMaxRects with pack/getPages/getPlacement.
9
+ */
10
+
11
+ export interface AtlasPlacement {
12
+ readonly pageIndex: number;
13
+ readonly x: number;
14
+ readonly y: number;
15
+ readonly width: number;
16
+ readonly height: number;
17
+ }
18
+
19
+ export interface AtlasPage {
20
+ readonly width: number;
21
+ readonly height: number;
22
+ readonly data: Uint8Array; // RGBA
23
+ }
24
+
25
+ type Key = string;
26
+
27
+ const DEFAULT_PAGE_SIZE = 2048;
28
+ const DEFAULT_PADDING = 3;
29
+
30
+ class Rect {
31
+ x: number;
32
+ y: number;
33
+ width: number;
34
+ height: number;
35
+ constructor(x: number, y: number, w: number, h: number) {
36
+ this.x = x; this.y = y; this.width = w; this.height = h;
37
+ }
38
+ }
39
+
40
+ function fnv1a32(buf: Uint8Array): number {
41
+ let h = 0x811c9dc5;
42
+ for (let i = 0; i < buf.length; i++) {
43
+ h ^= buf[i];
44
+ h = Math.imul(h, 0x01000193) >>> 0;
45
+ }
46
+ return h >>> 0;
47
+ }
48
+
49
+ export class GlyphAtlasMaxRects {
50
+ private pageSize: number;
51
+ private padding: number;
52
+ private pages: {
53
+ width: number;
54
+ height: number;
55
+ data: Uint8Array;
56
+ freeRects: Rect[];
57
+ usedArea: number;
58
+ lastUsed: number;
59
+ }[] = [];
60
+ private placements = new Map<Key, AtlasPlacement>();
61
+ private hashIndex = new Map<number, AtlasPlacement[]>();
62
+
63
+ // Eviction / memory control (LRU)
64
+ private maxPages = 8;
65
+ setMaxPages(n: number) {
66
+ if (!Number.isFinite(n) || n < 1) return;
67
+ this.maxPages = Math.max(1, Math.floor(n));
68
+ this.evictIfNeeded();
69
+ }
70
+
71
+ constructor(pageSize = DEFAULT_PAGE_SIZE, padding = DEFAULT_PADDING) {
72
+ this.pageSize = Math.max(128, Math.floor(pageSize));
73
+ this.padding = Math.max(0, Math.floor(padding));
74
+ }
75
+
76
+ setPageSize(px: number) {
77
+ if (!Number.isFinite(px) || px <= 0) return;
78
+ this.pageSize = Math.max(128, Math.floor(px));
79
+ }
80
+
81
+ setPadding(p: number) {
82
+ this.padding = Math.max(0, Math.floor(p));
83
+ }
84
+
85
+ clear() {
86
+ this.pages = [];
87
+ this.placements.clear();
88
+ this.hashIndex.clear();
89
+ }
90
+
91
+ getPages(): AtlasPage[] {
92
+ return this.pages.map(p => ({ width: p.width, height: p.height, data: p.data.slice() }));
93
+ }
94
+
95
+ has(key: Key) { return this.placements.has(key); }
96
+ getPlacement(key: Key): AtlasPlacement | null { return this.placements.get(key) ?? null; }
97
+ stats() { return { pages: this.pages.length, placements: this.placements.size }; }
98
+
99
+ pack(key: Key, mask: Uint8ClampedArray, w: number, h: number): AtlasPlacement | null {
100
+ if (this.placements.has(key)) return this.placements.get(key)!;
101
+ if (!mask || mask.length !== w * h) return null;
102
+
103
+ const rw = w + this.padding * 2;
104
+ const rh = h + this.padding * 2;
105
+ if (rw <= 0 || rh <= 0) return null;
106
+
107
+ // dedupe
108
+ const hash = fnv1a32(new Uint8Array(mask.buffer, mask.byteOffset, mask.byteLength));
109
+ const candList = this.hashIndex.get(hash);
110
+ if (candList) {
111
+ for (const cand of candList) {
112
+ const page = this.pages[cand.pageIndex];
113
+ if (!page) continue;
114
+ let equal = true;
115
+ for (let row = 0; row < h && equal; row++) {
116
+ const srcOff = row * w;
117
+ const dstRow = (cand.y + row) * page.width;
118
+ for (let col = 0; col < w; col++) {
119
+ const di = (dstRow + (cand.x + col)) * 4 + 3;
120
+ if (mask[srcOff + col] !== page.data[di]) { equal = false; break; }
121
+ }
122
+ }
123
+ if (equal) { this.placements.set(key, cand); return cand; }
124
+ }
125
+ }
126
+
127
+ // try existing pages
128
+ for (let pi = 0; pi < this.pages.length; pi++) {
129
+ const page = this.pages[pi];
130
+ const node = this.findPosition(page.freeRects, rw, rh);
131
+ if (node) {
132
+ const dstX = node.x + this.padding;
133
+ const dstY = node.y + this.padding;
134
+ this.blitToPage(page.data, page.width, page.height, dstX, dstY, mask, w, h);
135
+ page.usedArea += rw * rh;
136
+ page.lastUsed = Date.now();
137
+ this.splitFreeRectangles(node, page.freeRects);
138
+ this.pruneFreeList(page.freeRects);
139
+ const placement: AtlasPlacement = { pageIndex: pi, x: dstX, y: dstY, width: w, height: h };
140
+ this.placements.set(key, placement);
141
+ const list = this.hashIndex.get(hash) ?? [];
142
+ list.push(placement);
143
+ this.hashIndex.set(hash, list);
144
+ return placement;
145
+ }
146
+ }
147
+
148
+ // create new page
149
+ const pageW = this.pageSize, pageH = this.pageSize;
150
+ if (rw > pageW || rh > pageH) return null;
151
+ const data = new Uint8Array(pageW * pageH * 4);
152
+ const freeRects: Rect[] = [new Rect(0, 0, pageW, pageH)];
153
+ const page = { width: pageW, height: pageH, data, freeRects, usedArea: 0, lastUsed: Date.now() };
154
+ this.pages.push(page);
155
+ // enforce max pages after adding
156
+ this.evictIfNeeded();
157
+
158
+ const node = this.findPosition(page.freeRects, rw, rh);
159
+ if (!node) return null;
160
+ const dstX = node.x + this.padding;
161
+ const dstY = node.y + this.padding;
162
+ this.blitToPage(page.data, page.width, page.height, dstX, dstY, mask, w, h);
163
+ page.usedArea += rw * rh;
164
+ page.lastUsed = Date.now();
165
+ this.splitFreeRectangles(node, page.freeRects);
166
+ this.pruneFreeList(page.freeRects);
167
+ const placement: AtlasPlacement = { pageIndex: this.pages.length - 1, x: dstX, y: dstY, width: w, height: h };
168
+ this.placements.set(key, placement);
169
+ const list = this.hashIndex.get(hash) ?? [];
170
+ list.push(placement);
171
+ this.hashIndex.set(hash, list);
172
+ this.evictIfNeeded();
173
+ return placement;
174
+ }
175
+
176
+ // Best Short Side Fit
177
+ private findPosition(freeRects: Rect[], width: number, height: number): Rect | null {
178
+ let bestRect: Rect | null = null;
179
+ let bestShort = Infinity;
180
+ let bestLong = Infinity;
181
+ for (const r of freeRects) {
182
+ if (r.width >= width && r.height >= height) {
183
+ const leftoverW = r.width - width;
184
+ const leftoverH = r.height - height;
185
+ const shortSide = Math.min(leftoverW, leftoverH);
186
+ const longSide = Math.max(leftoverW, leftoverH);
187
+ if (shortSide < bestShort || (shortSide === bestShort && longSide < bestLong)) {
188
+ bestShort = shortSide; bestLong = longSide;
189
+ bestRect = new Rect(r.x, r.y, width, height);
190
+ }
191
+ }
192
+ // rotated
193
+ if (r.width >= height && r.height >= width) {
194
+ const leftoverW = r.width - height;
195
+ const leftoverH = r.height - width;
196
+ const shortSide = Math.min(leftoverW, leftoverH);
197
+ const longSide = Math.max(leftoverW, leftoverH);
198
+ if (shortSide < bestShort || (shortSide === bestShort && longSide < bestLong)) {
199
+ bestShort = shortSide; bestLong = longSide;
200
+ bestRect = new Rect(r.x, r.y, height, width);
201
+ }
202
+ }
203
+ }
204
+ return bestRect;
205
+ }
206
+
207
+ private splitFreeRectangles(placed: Rect, freeRects: Rect[]) {
208
+ const toAdd: Rect[] = [];
209
+ for (let i = freeRects.length - 1; i >= 0; i--) {
210
+ const r = freeRects[i];
211
+ if (!this.intersect(r, placed)) continue;
212
+ freeRects.splice(i, 1);
213
+ if (placed.x > r.x && placed.x < r.x + r.width) toAdd.push(new Rect(r.x, r.y, placed.x - r.x, r.height));
214
+ if (placed.x + placed.width < r.x + r.width) toAdd.push(new Rect(placed.x + placed.width, r.y, (r.x + r.width) - (placed.x + placed.width), r.height));
215
+ if (placed.y > r.y && placed.y < r.y + r.height) toAdd.push(new Rect(r.x, r.y, r.width, placed.y - r.y));
216
+ if (placed.y + placed.height < r.y + r.height) toAdd.push(new Rect(r.x, placed.y + placed.height, r.width, (r.y + r.height) - (placed.y + placed.height)));
217
+ }
218
+ for (const nr of toAdd) if (nr.width > 0 && nr.height > 0) freeRects.push(nr);
219
+ }
220
+
221
+ private pruneFreeList(freeRects: Rect[]) {
222
+ for (let i = 0; i < freeRects.length; i++) {
223
+ for (let j = i + 1; j < freeRects.length; j++) {
224
+ const a = freeRects[i], b = freeRects[j];
225
+ if (this.isContained(a, b)) { freeRects.splice(i, 1); i--; break; }
226
+ if (this.isContained(b, a)) { freeRects.splice(j, 1); j--; }
227
+ }
228
+ }
229
+ }
230
+
231
+ private intersect(a: Rect, b: Rect) {
232
+ return !(b.x >= a.x + a.width || b.x + b.width <= a.x || b.y >= a.y + a.height || b.y + b.height <= a.y);
233
+ }
234
+ private isContained(a: Rect, b: Rect) {
235
+ return a.x >= b.x && a.y >= b.y && (a.x + a.width) <= (b.x + b.width) && (a.y + a.height) <= (b.y + b.height);
236
+ }
237
+
238
+ private evictIfNeeded() {
239
+ while (this.pages.length > this.maxPages) {
240
+ this.evictLeastRecentlyUsedPage();
241
+ }
242
+ }
243
+
244
+ private evictLeastRecentlyUsedPage() {
245
+ if (this.pages.length === 0) return;
246
+ let oldest = 0;
247
+ let oldestTime = Infinity;
248
+ for (let i = 0; i < this.pages.length; i++) {
249
+ const p = this.pages[i];
250
+ if (p.lastUsed < oldestTime) {
251
+ oldestTime = p.lastUsed;
252
+ oldest = i;
253
+ }
254
+ }
255
+ this.evictPage(oldest);
256
+ }
257
+
258
+ private evictPage(idx: number) {
259
+ if (idx < 0 || idx >= this.pages.length) return;
260
+ // Remove page data
261
+ this.pages.splice(idx, 1);
262
+ // Remove placements that pointed to this page and adjust pageIndex for later pages
263
+ for (const [k, placement] of Array.from(this.placements.entries())) {
264
+ if (placement.pageIndex === idx) {
265
+ this.placements.delete(k);
266
+ } else if (placement.pageIndex > idx) {
267
+ this.placements.set(k, { ...placement, pageIndex: placement.pageIndex - 1 });
268
+ }
269
+ }
270
+ // Rebuild hashIndex: remove entries referencing removed page and adjust indices
271
+ const newHash = new Map<number, AtlasPlacement[]>();
272
+ for (const [h, list] of this.hashIndex.entries()) {
273
+ const newList: AtlasPlacement[] = [];
274
+ for (const p of list) {
275
+ if (p.pageIndex === idx) continue;
276
+ if (p.pageIndex > idx) {
277
+ newList.push({ ...p, pageIndex: p.pageIndex - 1 });
278
+ } else {
279
+ newList.push(p);
280
+ }
281
+ }
282
+ if (newList.length > 0) newHash.set(h, newList);
283
+ }
284
+ this.hashIndex = newHash;
285
+ }
286
+
287
+ private blitToPage(pageData: Uint8Array, pageW: number, _pageH: number, dstX: number, dstY: number, mask: Uint8ClampedArray, w: number, h: number) {
288
+ for (let row = 0; row < h; row++) {
289
+ const srcOff = row * w;
290
+ const dstRow = (dstY + row) * pageW;
291
+ let dstCol = dstX;
292
+ for (let col = 0; col < w; col++) {
293
+ const a = mask[srcOff + col];
294
+ const di = (dstRow + dstCol) * 4;
295
+ pageData[di] = 0;
296
+ pageData[di + 1] = 0;
297
+ pageData[di + 2] = 0;
298
+ pageData[di + 3] = a;
299
+ dstCol++;
300
+ }
301
+ }
302
+ }
303
+ }
304
+
305
+ export const globalGlyphAtlasMaxRects = new GlyphAtlasMaxRects(DEFAULT_PAGE_SIZE, DEFAULT_PADDING);
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Simple heuristic tuner for glyph atlas parameters.
3
+ *
4
+ * Exports pickAtlasSettingsFromSamples(samples)
5
+ * - samples: Array of { width:number, height:number } representing a sample
6
+ * of glyph mask sizes (in px) for a font/size combination.
7
+ *
8
+ * Heuristic:
9
+ * - Try candidate page sizes (1024, 2048, 4096)
10
+ * - Try padding values (0..3)
11
+ * - For each (pageSize, padding) compute:
12
+ * * If any glyph (w+2p > pageSize or h+2p > pageSize) -> invalid
13
+ * * totalPaddedArea = sum((w+2p)*(h+2p))
14
+ * * estimatedPages = ceil(totalPaddedArea / (pageSize*pageSize))
15
+ * * wastedRatio = estimatedPages * pageSize*pageSize / totalPaddedArea
16
+ * - Prefer configurations with smallest estimatedPages; tie-break by smallest wastedRatio,
17
+ * then smaller pageSize and smaller padding.
18
+ *
19
+ * This is fast and conservative. It doesn't run an actual packer; it's only a
20
+ * lightweight selector to choose sensible defaults for pageSize and padding.
21
+ */
22
+
23
+ export interface AtlasSettings {
24
+ pageSize: number;
25
+ padding: number;
26
+ estimatedPages: number;
27
+ wastedRatio: number;
28
+ valid: boolean;
29
+ }
30
+
31
+ export function pickAtlasSettingsFromSamples(
32
+ samples: Array<{ width: number; height: number }>,
33
+ opts?: { candidates?: number[]; paddings?: number[] }
34
+ ): AtlasSettings {
35
+ const candidates = opts?.candidates ?? [1024, 2048, 4096];
36
+ const paddings = opts?.paddings ?? [0, 1, 2, 3];
37
+
38
+ // sanitize samples
39
+ const filtered = (samples || []).map(s => ({ width: Math.max(0, Math.floor(s.width)), height: Math.max(0, Math.floor(s.height)) }));
40
+ if (filtered.length === 0) {
41
+ // default safe
42
+ return { pageSize: 2048, padding: 1, estimatedPages: 1, wastedRatio: 1, valid: true };
43
+ }
44
+
45
+ let best: AtlasSettings | null = null;
46
+
47
+ for (const pageSize of candidates) {
48
+ for (const padding of paddings) {
49
+ let invalid = false;
50
+ let totalPaddedArea = 0;
51
+ for (const s of filtered) {
52
+ const pw = s.width + padding * 2;
53
+ const ph = s.height + padding * 2;
54
+ if (pw > pageSize || ph > pageSize) {
55
+ invalid = true;
56
+ break;
57
+ }
58
+ totalPaddedArea += pw * ph;
59
+ }
60
+ if (invalid) continue;
61
+ const pageArea = pageSize * pageSize;
62
+ const estimatedPages = Math.max(1, Math.ceil(totalPaddedArea / pageArea));
63
+ const wastedRatio = (estimatedPages * pageArea) / Math.max(1, totalPaddedArea);
64
+
65
+ const candidate: AtlasSettings = {
66
+ pageSize,
67
+ padding,
68
+ estimatedPages,
69
+ wastedRatio,
70
+ valid: true,
71
+ };
72
+
73
+ if (!best) {
74
+ best = candidate;
75
+ continue;
76
+ }
77
+
78
+ // prefer fewer pages, then lower wastedRatio, then smaller pageSize, then smaller padding
79
+ if (candidate.estimatedPages < best.estimatedPages) {
80
+ best = candidate;
81
+ } else if (candidate.estimatedPages === best.estimatedPages) {
82
+ if (candidate.wastedRatio < best.wastedRatio - 1e-9) {
83
+ best = candidate;
84
+ } else if (Math.abs(candidate.wastedRatio - best.wastedRatio) < 1e-9) {
85
+ if (candidate.pageSize < best.pageSize) best = candidate;
86
+ else if (candidate.pageSize === best.pageSize && candidate.padding < best.padding) best = candidate;
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ if (!best) {
93
+ // fallback conservative
94
+ return { pageSize: 2048, padding: 1, estimatedPages: 1, wastedRatio: 1, valid: false };
95
+ }
96
+
97
+ return best;
98
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Simple shelf-based glyph atlas packer.
3
+ *
4
+ * Purpose:
5
+ * - Pack many small glyph alpha masks into one or more RGBA atlas pages.
6
+ * - Provide placement metadata so caller can later extract or reference the region.
7
+ *
8
+ * Notes:
9
+ * - This implementation focuses on correctness and simplicity (shelf packer).
10
+ * - Atlas pages are stored as RGBA Uint8Array buffers where R=G=B=0 and A=mask.
11
+ * - Does NOT yet integrate automatically with PDF image registration;
12
+ * that will be done in the rendering path in a follow-up step.
13
+ */
14
+
15
+ export interface AtlasPlacement {
16
+ readonly pageIndex: number;
17
+ readonly x: number;
18
+ readonly y: number;
19
+ readonly width: number;
20
+ readonly height: number;
21
+ }
22
+
23
+ export interface AtlasPage {
24
+ readonly width: number;
25
+ readonly height: number;
26
+ readonly data: Uint8Array; // RGBA
27
+ }
28
+
29
+ type Key = string;
30
+
31
+ const DEFAULT_PAGE_SIZE = 2048; // square atlas page default
32
+ // Increase base padding to reduce risk of blur bleeding between packed glyphs.
33
+ // We still add extraPadding from the caller (e.g. glyph-cache) for blur radius.
34
+ const PADDING = 2; // padding around glyphs to avoid bleeding
35
+
36
+ class Shelf {
37
+ y: number;
38
+ height: number;
39
+ xCursor: number;
40
+ remaining: number;
41
+ constructor(y: number, height: number, pageWidth: number) {
42
+ this.y = y;
43
+ this.height = height;
44
+ this.xCursor = 0;
45
+ this.remaining = pageWidth;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * AtlasManager
51
+ */
52
+ export class GlyphAtlas {
53
+ private pageSize = DEFAULT_PAGE_SIZE;
54
+ private pages: { width: number; height: number; data: Uint8Array; shelves: Shelf[]; used: number; lastUsed: number }[] = [];
55
+ private placements = new Map<Key, AtlasPlacement>();
56
+ // LRU eviction control for shelf atlas
57
+ private maxPages = 8;
58
+ setMaxPages(n: number) {
59
+ if (!Number.isFinite(n) || n < 1) return;
60
+ this.maxPages = Math.max(1, Math.floor(n));
61
+ this.evictIfNeeded();
62
+ }
63
+
64
+ constructor(pageSize = DEFAULT_PAGE_SIZE) {
65
+ this.pageSize = pageSize;
66
+ }
67
+
68
+ clear() {
69
+ this.pages = [];
70
+ this.placements.clear();
71
+ }
72
+
73
+ getPages(): AtlasPage[] {
74
+ return this.pages.map((p) => ({ width: p.width, height: p.height, data: p.data.slice() }));
75
+ }
76
+
77
+ has(key: Key): boolean {
78
+ return this.placements.has(key);
79
+ }
80
+
81
+ getPlacement(key: Key): AtlasPlacement | null {
82
+ return this.placements.get(key) ?? null;
83
+ }
84
+
85
+ /**
86
+ * Pack an alpha mask into the atlas.
87
+ * - key: unique identifier for the glyph (e.g., fontUid|gid|size|ss)
88
+ * - mask: Uint8ClampedArray length = w*h
89
+ * - w,h: dimensions
90
+ * Returns placement or null on failure.
91
+ */
92
+ pack(key: Key, mask: Uint8ClampedArray, w: number, h: number, extraPadding = 0): AtlasPlacement | null {
93
+ if (this.placements.has(key)) {
94
+ return this.placements.get(key)!;
95
+ }
96
+ const pw = this.pageSize;
97
+ const ph = this.pageSize;
98
+ const pad = PADDING + Math.max(0, Math.floor(extraPadding));
99
+ const rw = w + pad * 2;
100
+ const rh = h + pad * 2;
101
+
102
+ // Try existing pages
103
+ for (let pi = 0; pi < this.pages.length; pi++) {
104
+ const page = this.pages[pi];
105
+ // try to fit in existing shelves
106
+ for (const shelf of page.shelves) {
107
+ if (rh <= shelf.height && rw <= shelf.remaining) {
108
+ // place it
109
+ const x = shelf.xCursor + pad;
110
+ const y = shelf.y + pad;
111
+ this.blitToPage(page.data, page.width, page.height, x, y, mask, w, h);
112
+ shelf.xCursor += rw;
113
+ shelf.remaining -= rw;
114
+ page.used += rw * rh;
115
+ page.lastUsed = Date.now();
116
+ const placement: AtlasPlacement = { pageIndex: pi, x, y, width: w, height: h };
117
+ this.placements.set(key, placement);
118
+ return placement;
119
+ }
120
+ }
121
+
122
+ // If no shelf fits, try to create a new shelf at bottom if space allows
123
+ const usedHeight = page.shelves.reduce((s, sh) => s + sh.height, 0);
124
+ if (usedHeight + rh <= page.height) {
125
+ const shelfY = usedHeight;
126
+ const shelf = new Shelf(shelfY, rh, page.width);
127
+ page.shelves.push(shelf);
128
+ // place at start
129
+ const x = shelf.xCursor + pad;
130
+ const y = shelf.y + pad;
131
+ this.blitToPage(page.data, page.width, page.height, x, y, mask, w, h);
132
+ shelf.xCursor += rw;
133
+ shelf.remaining -= rw;
134
+ page.used += rw * rh;
135
+ page.lastUsed = Date.now();
136
+ const placement: AtlasPlacement = { pageIndex: pi, x, y, width: w, height: h };
137
+ this.placements.set(key, placement);
138
+ return placement;
139
+ }
140
+ }
141
+
142
+ // No existing page fits: create a new page
143
+ const pageW = pw;
144
+ const pageH = ph;
145
+ const pageData = new Uint8Array(pageW * pageH * 4); // RGBA
146
+ // default rgba zeros
147
+ const newPage = { width: pageW, height: pageH, data: pageData, shelves: [] as Shelf[], used: 0, lastUsed: Date.now() };
148
+ // create first shelf
149
+ if (rh > pageH || rw > pageW) {
150
+ // too large for page
151
+ return null;
152
+ }
153
+ const shelf = new Shelf(0, rh, pageW);
154
+ newPage.shelves.push(shelf);
155
+ this.pages.push(newPage);
156
+ // enforce max pages after adding page
157
+ this.evictIfNeeded();
158
+ const x = shelf.xCursor + pad;
159
+ const y = shelf.y + pad;
160
+ this.blitToPage(newPage.data, newPage.width, newPage.height, x, y, mask, w, h);
161
+ shelf.xCursor += rw;
162
+ shelf.remaining -= rw;
163
+ newPage.used += rw * rh;
164
+ newPage.lastUsed = Date.now();
165
+ const placement: AtlasPlacement = { pageIndex: this.pages.length - 1, x, y, width: w, height: h };
166
+ this.placements.set(key, placement);
167
+ return placement;
168
+ }
169
+
170
+ private evictIfNeeded() {
171
+ while (this.pages.length > this.maxPages) {
172
+ this.evictLeastRecentlyUsedPage();
173
+ }
174
+ }
175
+
176
+ private evictLeastRecentlyUsedPage() {
177
+ if (this.pages.length === 0) return;
178
+ let oldest = 0;
179
+ let oldestTime = Infinity;
180
+ for (let i = 0; i < this.pages.length; i++) {
181
+ const p = this.pages[i];
182
+ if (p.lastUsed < oldestTime) {
183
+ oldestTime = p.lastUsed;
184
+ oldest = i;
185
+ }
186
+ }
187
+ this.evictPage(oldest);
188
+ }
189
+
190
+ private evictPage(idx: number) {
191
+ if (idx < 0 || idx >= this.pages.length) return;
192
+ // Remove page data
193
+ this.pages.splice(idx, 1);
194
+ // Remove placements that pointed to this page and adjust pageIndex for later pages
195
+ for (const [k, placement] of Array.from(this.placements.entries())) {
196
+ if (placement.pageIndex === idx) {
197
+ this.placements.delete(k);
198
+ } else if (placement.pageIndex > idx) {
199
+ this.placements.set(k, { ...placement, pageIndex: placement.pageIndex - 1 });
200
+ }
201
+ }
202
+ // NOTE: shelf atlas does not maintain a hashIndex for dedupe.
203
+ // Eviction only updates placements above; nothing more to adjust here.
204
+ }
205
+
206
+ private blitToPage(pageData: Uint8Array, pageW: number, _pageH: number, dstX: number, dstY: number, mask: Uint8ClampedArray, w: number, h: number) {
207
+ // pageData is RGBA; we write R=G=B=0, A = mask
208
+ for (let row = 0; row < h; row++) {
209
+ const srcOff = row * w;
210
+ const dstRow = (dstY + row) * pageW;
211
+ let dstCol = dstX;
212
+ for (let col = 0; col < w; col++) {
213
+ const a = mask[srcOff + col];
214
+ const di = (dstRow + dstCol) * 4;
215
+ pageData[di] = 0;
216
+ pageData[di + 1] = 0;
217
+ pageData[di + 2] = 0;
218
+ pageData[di + 3] = a;
219
+ dstCol++;
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+ /** Singleton atlas for now (can be replaced with per-font/per-size atlases later) */
226
+ export const globalGlyphAtlas = new GlyphAtlas(DEFAULT_PAGE_SIZE);