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,196 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ calculateTotalGap,
4
+ calculateAvailableSpace,
5
+ calculateItemOffsets,
6
+ calculateTrackOffsets,
7
+ GapCalculator,
8
+ } from '../../src/layout/utils/gap-calculator.js';
9
+
10
+ describe('Gap Calculator Utilities', () => {
11
+ describe('calculateTotalGap', () => {
12
+ it('should return 0 for single item', () => {
13
+ expect(calculateTotalGap(10, 1)).toBe(0);
14
+ });
15
+
16
+ it('should return 0 for zero items', () => {
17
+ expect(calculateTotalGap(10, 0)).toBe(0);
18
+ });
19
+
20
+ it('should return 0 for negative gap', () => {
21
+ expect(calculateTotalGap(-10, 3)).toBe(0);
22
+ });
23
+
24
+ it('should return 0 for zero gap', () => {
25
+ expect(calculateTotalGap(0, 3)).toBe(0);
26
+ });
27
+
28
+ it('should calculate correct gap for 2 items', () => {
29
+ expect(calculateTotalGap(10, 2)).toBe(10);
30
+ });
31
+
32
+ it('should calculate correct gap for 3 items', () => {
33
+ expect(calculateTotalGap(10, 3)).toBe(20);
34
+ });
35
+
36
+ it('should calculate correct gap for 5 items', () => {
37
+ expect(calculateTotalGap(15, 5)).toBe(60); // 15 * (5-1) = 60
38
+ });
39
+
40
+ it('should handle decimal gaps', () => {
41
+ expect(calculateTotalGap(10.5, 3)).toBe(21); // 10.5 * 2 = 21
42
+ });
43
+ });
44
+
45
+ describe('calculateAvailableSpace', () => {
46
+ it('should return full space when gap is 0', () => {
47
+ expect(calculateAvailableSpace(100, 0, 3)).toBe(100);
48
+ });
49
+
50
+ it('should return full space for single item', () => {
51
+ expect(calculateAvailableSpace(100, 10, 1)).toBe(100);
52
+ });
53
+
54
+ it('should subtract total gap from available space', () => {
55
+ expect(calculateAvailableSpace(100, 10, 3)).toBe(80); // 100 - 20
56
+ });
57
+
58
+ it('should return 0 when gap exceeds available space', () => {
59
+ expect(calculateAvailableSpace(50, 30, 3)).toBe(0); // 50 - 60 = -10, clamped to 0
60
+ });
61
+
62
+ it('should handle negative total space gracefully', () => {
63
+ expect(calculateAvailableSpace(-10, 5, 3)).toBe(0);
64
+ });
65
+
66
+ it('should work with decimal values', () => {
67
+ expect(calculateAvailableSpace(100.5, 10.5, 3)).toBe(79.5); // 100.5 - 21
68
+ });
69
+ });
70
+
71
+ describe('calculateItemOffsets', () => {
72
+ it('should return [0] for single item', () => {
73
+ expect(calculateItemOffsets([50], 10)).toEqual([0]);
74
+ });
75
+
76
+ it('should calculate offsets with no gap', () => {
77
+ expect(calculateItemOffsets([50, 30, 40], 0)).toEqual([0, 50, 80]);
78
+ });
79
+
80
+ it('should calculate offsets with gap', () => {
81
+ expect(calculateItemOffsets([50, 30, 40], 10)).toEqual([0, 60, 100]);
82
+ // Item 0: 0
83
+ // Item 1: 50 + 10 = 60
84
+ // Item 2: 50 + 10 + 30 + 10 = 100
85
+ });
86
+
87
+ it('should handle empty array', () => {
88
+ expect(calculateItemOffsets([], 10)).toEqual([]);
89
+ });
90
+
91
+ it('should handle uniform sizes', () => {
92
+ expect(calculateItemOffsets([50, 50, 50], 10)).toEqual([0, 60, 120]);
93
+ });
94
+
95
+ it('should work with decimal values', () => {
96
+ expect(calculateItemOffsets([50.5, 30.5, 40], 10.5)).toEqual([0, 61, 102]);
97
+ // Item 0: 0
98
+ // Item 1: 50.5 + 10.5 = 61
99
+ // Item 2: 50.5 + 10.5 + 30.5 + 10.5 = 102
100
+ });
101
+
102
+ it('should handle zero-sized items', () => {
103
+ expect(calculateItemOffsets([0, 50, 0, 30], 10)).toEqual([0, 10, 70, 80]);
104
+ });
105
+ });
106
+
107
+ describe('calculateTrackOffsets', () => {
108
+ it('should be equivalent to calculateItemOffsets', () => {
109
+ const sizes = [100, 150, 200];
110
+ const gap = 20;
111
+ expect(calculateTrackOffsets(sizes, gap)).toEqual(calculateItemOffsets(sizes, gap));
112
+ });
113
+
114
+ it('should calculate grid track offsets correctly', () => {
115
+ expect(calculateTrackOffsets([100, 150, 200], 20)).toEqual([0, 120, 290]);
116
+ // Track 0: 0
117
+ // Track 1: 100 + 20 = 120
118
+ // Track 2: 100 + 20 + 150 + 20 = 290
119
+ });
120
+ });
121
+
122
+ describe('GapCalculator', () => {
123
+ describe('getMainAxisGap', () => {
124
+ it('should return columnGap for row direction', () => {
125
+ const calc = new GapCalculator({ rowGap: 10, columnGap: 20 });
126
+ expect(calc.getMainAxisGap(true)).toBe(20);
127
+ });
128
+
129
+ it('should return rowGap for column direction', () => {
130
+ const calc = new GapCalculator({ rowGap: 10, columnGap: 20 });
131
+ expect(calc.getMainAxisGap(false)).toBe(10);
132
+ });
133
+ });
134
+
135
+ describe('getCrossAxisGap', () => {
136
+ it('should return rowGap for row direction', () => {
137
+ const calc = new GapCalculator({ rowGap: 10, columnGap: 20 });
138
+ expect(calc.getCrossAxisGap(true)).toBe(10);
139
+ });
140
+
141
+ it('should return columnGap for column direction', () => {
142
+ const calc = new GapCalculator({ rowGap: 10, columnGap: 20 });
143
+ expect(calc.getCrossAxisGap(false)).toBe(20);
144
+ });
145
+ });
146
+
147
+ describe('calculateMainAxisTotalGap', () => {
148
+ it('should calculate gap for row direction', () => {
149
+ const calc = new GapCalculator({ rowGap: 10, columnGap: 20 });
150
+ expect(calc.calculateMainAxisTotalGap(true, 3)).toBe(40); // columnGap * 2
151
+ });
152
+
153
+ it('should calculate gap for column direction', () => {
154
+ const calc = new GapCalculator({ rowGap: 10, columnGap: 20 });
155
+ expect(calc.calculateMainAxisTotalGap(false, 3)).toBe(20); // rowGap * 2
156
+ });
157
+
158
+ it('should return 0 for single item', () => {
159
+ const calc = new GapCalculator({ rowGap: 10, columnGap: 20 });
160
+ expect(calc.calculateMainAxisTotalGap(true, 1)).toBe(0);
161
+ });
162
+ });
163
+
164
+ describe('calculateMainAxisAvailableSpace', () => {
165
+ it('should calculate available space for row direction', () => {
166
+ const calc = new GapCalculator({ rowGap: 10, columnGap: 20 });
167
+ expect(calc.calculateMainAxisAvailableSpace(true, 100, 3)).toBe(60); // 100 - 40
168
+ });
169
+
170
+ it('should calculate available space for column direction', () => {
171
+ const calc = new GapCalculator({ rowGap: 10, columnGap: 20 });
172
+ expect(calc.calculateMainAxisAvailableSpace(false, 100, 3)).toBe(80); // 100 - 20
173
+ });
174
+
175
+ it('should handle insufficient space', () => {
176
+ const calc = new GapCalculator({ rowGap: 50, columnGap: 50 });
177
+ expect(calc.calculateMainAxisAvailableSpace(true, 80, 3)).toBe(0); // 80 - 100 = -20, clamped to 0
178
+ });
179
+ });
180
+
181
+ describe('edge cases', () => {
182
+ it('should handle zero gaps', () => {
183
+ const calc = new GapCalculator({ rowGap: 0, columnGap: 0 });
184
+ expect(calc.getMainAxisGap(true)).toBe(0);
185
+ expect(calc.calculateMainAxisTotalGap(true, 5)).toBe(0);
186
+ expect(calc.calculateMainAxisAvailableSpace(true, 100, 5)).toBe(100);
187
+ });
188
+
189
+ it('should handle same row and column gap', () => {
190
+ const calc = new GapCalculator({ rowGap: 15, columnGap: 15 });
191
+ expect(calc.getMainAxisGap(true)).toBe(15);
192
+ expect(calc.getCrossAxisGap(true)).toBe(15);
193
+ });
194
+ });
195
+ });
196
+ });
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { renderTreeForHtml, collectBoxes } from "../helpers/render-utils.js";
3
+
4
+ // Tolerance for position comparison (accounts for padding and minor layout differences)
5
+ const POSITION_TOLERANCE_PX = 10;
6
+ // Tolerance for width comparison (allows 10% difference)
7
+ const WIDTH_TOLERANCE_RATIO = 0.9;
8
+
9
+ describe("inline background alignment", () => {
10
+ it("aligns background with text for inline span elements", async () => {
11
+ const html = `
12
+ <p>Normal text <span style="background-color: yellow">highlighted text</span> after.</p>
13
+ `;
14
+ const renderTree = await renderTreeForHtml(html);
15
+ const boxes = collectBoxes(renderTree.root);
16
+
17
+ // Find the span element
18
+ const spanBox = boxes.find(b => b.tagName === "span");
19
+ expect(spanBox).toBeDefined();
20
+
21
+ // Find the text node inside the span (should be a child with textRuns)
22
+ const textNodeInsideSpan = spanBox?.children.find(c => c.textRuns && c.textRuns.length > 0);
23
+ expect(textNodeInsideSpan).toBeDefined();
24
+
25
+ if (spanBox && textNodeInsideSpan && textNodeInsideSpan.textRuns && textNodeInsideSpan.textRuns.length > 0) {
26
+ // The span's borderBox.x should be at or before the first text run's position
27
+ const firstRunX = textNodeInsideSpan.textRuns[0].lineMatrix?.e ?? 0;
28
+ const spanBorderX = spanBox.borderBox.x;
29
+
30
+ // The span's border box X should match or be very close to where the first text run starts
31
+ // (accounting for any padding)
32
+ expect(Math.abs(spanBorderX - firstRunX)).toBeLessThan(POSITION_TOLERANCE_PX);
33
+
34
+ // The span should have a background color
35
+ expect(spanBox.background?.color).toBeDefined();
36
+ }
37
+ });
38
+
39
+ it("calculates correct width for inline span with multiple text fragments", async () => {
40
+ const html = `
41
+ <p>Before <span style="background-color: lightblue">text one two three</span> after.</p>
42
+ `;
43
+ const renderTree = await renderTreeForHtml(html);
44
+ const boxes = collectBoxes(renderTree.root);
45
+
46
+ const spanBox = boxes.find(b => b.tagName === "span");
47
+ expect(spanBox).toBeDefined();
48
+
49
+ if (spanBox && spanBox.children.length > 0) {
50
+ const textNode = spanBox.children[0];
51
+ if (textNode.textRuns && textNode.textRuns.length > 0) {
52
+ // Find the extent of all text runs
53
+ let minX = Infinity;
54
+ let maxX = -Infinity;
55
+
56
+ for (const run of textNode.textRuns) {
57
+ const runX = run.lineMatrix?.e ?? 0;
58
+ const runWidth = run.advanceWidth ?? 0;
59
+ minX = Math.min(minX, runX);
60
+ maxX = Math.max(maxX, runX + runWidth);
61
+ }
62
+
63
+ // The span's border box should cover the text content
64
+ const spanWidth = spanBox.borderBox.width;
65
+ const textExtent = maxX - minX;
66
+
67
+ // The span width should be at least as wide as the text extent
68
+ expect(spanWidth).toBeGreaterThanOrEqual(textExtent * WIDTH_TOLERANCE_RATIO);
69
+ }
70
+ }
71
+ });
72
+
73
+ it("positions background correctly when span follows other content", async () => {
74
+ const html = `
75
+ <p>Some initial text <span style="background-color: pink">highlighted</span> end.</p>
76
+ `;
77
+ const renderTree = await renderTreeForHtml(html);
78
+ const boxes = collectBoxes(renderTree.root);
79
+
80
+ const spanBox = boxes.find(b => b.tagName === "span");
81
+
82
+ expect(spanBox).toBeDefined();
83
+
84
+ if (spanBox) {
85
+ // The span's borderBox.x should be positive (not at the start of the line)
86
+ // since it comes after "Some initial text "
87
+ expect(spanBox.borderBox.x).toBeGreaterThan(0);
88
+
89
+ // The span should have a background
90
+ expect(spanBox.background?.color).toBeDefined();
91
+ }
92
+ });
93
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { renderTreeForHtml, collectRuns } from "../helpers/render-utils.js";
3
+
4
+ describe("inline fragment layout", () => {
5
+ it("lays out inline fragments without stacking glyphs", async () => {
6
+ const renderTree = await renderTreeForHtml("<em>itálico</em>");
7
+ const runs = collectRuns(renderTree.root).filter((r) => r.text.length > 0);
8
+
9
+ const xPositions = runs.map((r) => r.lineMatrix?.e ?? 0);
10
+ for (let i = 1; i < xPositions.length; i++) {
11
+ expect(xPositions[i]).toBeGreaterThan(xPositions[i - 1]);
12
+ }
13
+ });
14
+
15
+ it("keeps justified inline fragments in increasing x order", async () => {
16
+ const html =
17
+ '<p style="text-align: justify; width: 200px">normal <b>bold</b> normal</p>';
18
+ const renderTree = await renderTreeForHtml(html);
19
+ const runs = collectRuns(renderTree.root).filter((r) => r.text.length > 0);
20
+
21
+ const xPositions = runs.map((r) => r.lineMatrix?.e ?? 0);
22
+ for (let i = 1; i < xPositions.length; i++) {
23
+ expect(xPositions[i]).toBeGreaterThan(xPositions[i - 1]);
24
+ }
25
+ });
26
+ });
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { RunPlacer } from "../../src/layout/inline/run-placer.js";
3
+ import { LayoutNode } from "../../src/dom/node.js";
4
+ import { ComputedStyle } from "../../src/css/style.js";
5
+ import type { LayoutItem } from "../../src/layout/inline/types.js";
6
+ import type { FontEmbedder } from "../../src/pdf/font/embedder.js";
7
+
8
+ describe("RunPlacer", () => {
9
+ it("uses font metrics for baseline calculation when FontEmbedder is provided", () => {
10
+ const mockFontEmbedder = {
11
+ getMetrics: vi.fn().mockReturnValue({
12
+ metrics: {
13
+ unitsPerEm: 1000,
14
+ ascender: 800,
15
+ descender: -200,
16
+ }
17
+ })
18
+ } as unknown as FontEmbedder;
19
+
20
+ const placer = new RunPlacer(mockFontEmbedder);
21
+
22
+ const style = new ComputedStyle({
23
+ fontSize: 20,
24
+ fontFamily: "TestFont",
25
+ lineHeight: { kind: "length", value: 20 }
26
+ });
27
+ const node = new LayoutNode(style);
28
+
29
+ const item: LayoutItem = {
30
+ kind: "word",
31
+ width: 50,
32
+ height: 20,
33
+ lineHeight: 20,
34
+ node: node,
35
+ style: style,
36
+ text: "Test",
37
+ };
38
+
39
+ const lineContext = {
40
+ lineTop: 100,
41
+ lineHeight: 20,
42
+ lineStartX: 0,
43
+ lineIndex: 0,
44
+ availableWidth: 100,
45
+ offsetShift: 0,
46
+ isLastLine: false,
47
+ contentX: 0,
48
+ inlineOffsetStart: 0,
49
+ };
50
+
51
+ placer.placeRunsForLine([{ item, offset: 0 }], lineContext);
52
+
53
+ expect(mockFontEmbedder.getMetrics).toHaveBeenCalledWith("TestFont", 400, "normal");
54
+
55
+ const runs = placer.getNodeRuns().get(node);
56
+ expect(runs).toBeDefined();
57
+ expect(runs!.length).toBe(1);
58
+
59
+ // Baseline calculation:
60
+ // ascent = (800 / 1000) * 20 = 16
61
+ // leading = 20 - 20 = 0
62
+ // halfLeading = 0
63
+ // baseline = 100 + 0 + 16 = 116
64
+ expect(runs![0].baseline).toBe(116);
65
+ });
66
+
67
+ it("uses default heuristic when FontEmbedder is not provided", () => {
68
+ const placer = new RunPlacer(null);
69
+
70
+ const style = new ComputedStyle({
71
+ fontSize: 20,
72
+ lineHeight: { kind: "length", value: 20 }
73
+ });
74
+ const node = new LayoutNode(style);
75
+
76
+ const item: LayoutItem = {
77
+ kind: "word",
78
+ width: 50,
79
+ height: 20,
80
+ lineHeight: 20,
81
+ node: node,
82
+ style: style,
83
+ text: "Test",
84
+ };
85
+
86
+ const lineContext = {
87
+ lineTop: 100,
88
+ lineHeight: 20,
89
+ lineStartX: 0,
90
+ lineIndex: 0,
91
+ availableWidth: 100,
92
+ offsetShift: 0,
93
+ isLastLine: false,
94
+ contentX: 0,
95
+ inlineOffsetStart: 0,
96
+ };
97
+
98
+ placer.placeRunsForLine([{ item, offset: 0 }], lineContext);
99
+
100
+ const runs = placer.getNodeRuns().get(node);
101
+ expect(runs).toBeDefined();
102
+
103
+ // Default heuristic:
104
+ // ascent = 20 * 0.75 = 15
105
+ // baseline = 100 + 15 = 115
106
+ expect(runs![0].baseline).toBe(115);
107
+ });
108
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { renderRuns } from "../helpers/render-utils.js";
3
+
4
+ describe("PDF text alignment (non-justify)", () => {
5
+ it("does not apply justification to left/center/right alignments", async () => {
6
+ const html = `
7
+ <div>
8
+ <p style="width: 260px; text-align: left;">
9
+ Some sample text that wraps into multiple lines for left.
10
+ </p>
11
+ <p style="width: 260px; text-align: center;">
12
+ Some sample text that wraps into multiple lines for center.
13
+ </p>
14
+ <p style="width: 260px; text-align: right;">
15
+ Some sample text that wraps into multiple lines for right.
16
+ </p>
17
+ </div>
18
+ `;
19
+
20
+ const runs = await renderRuns(html);
21
+
22
+ // no positive wordSpacing anywhere
23
+ const hasWordSpacing = runs.some((r) => (r.wordSpacing ?? 0) > 0);
24
+ expect(hasWordSpacing).toBe(false);
25
+ });
26
+ });
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect, beforeAll } from "vitest";
2
+ import { registerAllPropertyParsers } from "../../src/css/parsers/register-parsers.js";
3
+ import {
4
+ parseBackgroundImage,
5
+ applyBackgroundClipDecl,
6
+ } from "../../src/css/parsers/background-parser-extended.js";
7
+ import { resolveTextGradientLayer } from "../../src/pdf/utils/background-layer-resolver.js";
8
+ import { ComputedStyle } from "../../src/css/style.js";
9
+ import { LayoutNode } from "../../src/dom/node.js";
10
+ import type { StyleAccumulator } from "../../src/css/style.js";
11
+ import type { GradientBackgroundLayer } from "../../src/css/background-types.js";
12
+ describe("background-clip CSS handling", () => {
13
+ beforeAll(() => {
14
+ registerAllPropertyParsers();
15
+ });
16
+
17
+ it("marks gradients with background-clip: text", () => {
18
+ const target: StyleAccumulator = {};
19
+ parseBackgroundImage("linear-gradient(red, blue)", target);
20
+ applyBackgroundClipDecl("text", target);
21
+
22
+ expect(target.backgroundLayers).toBeDefined();
23
+ const gradientLayer = target.backgroundLayers?.find((layer) => layer.kind === "gradient");
24
+ expect(gradientLayer).toBeDefined();
25
+ expect(gradientLayer?.clip).toBe("text");
26
+ });
27
+
28
+ it("resolveTextGradientLayer returns gradient data for text clips", () => {
29
+ const style = new ComputedStyle();
30
+ style.backgroundLayers = [
31
+ {
32
+ kind: "gradient",
33
+ gradient: {
34
+ type: "linear",
35
+ angleOrTo: "to right",
36
+ stops: [
37
+ { color: "#ff0000" },
38
+ { color: "#0000ff" },
39
+ ],
40
+ },
41
+ clip: "text",
42
+ },
43
+ ] as GradientBackgroundLayer[];
44
+
45
+ const node = new LayoutNode(style);
46
+ const boxes = {
47
+ borderBox: { x: 0, y: 0, width: 200, height: 50 },
48
+ paddingBox: { x: 0, y: 0, width: 200, height: 50 },
49
+ contentBox: { x: 0, y: 0, width: 200, height: 50 },
50
+ };
51
+
52
+ const result = resolveTextGradientLayer(node, boxes);
53
+ expect(result).toBeDefined();
54
+ expect(result?.rect.width).toBeGreaterThan(0);
55
+ expect(result?.gradient).toBeDefined();
56
+ });
57
+ });
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { computeBackgroundTileRects } from "../../src/pdf/utils/background-tiles.js";
3
+ import type { Rect } from "../../src/pdf/types.js";
4
+
5
+ describe("background-repeat space and round modes", () => {
6
+ const tileRect: Rect = { x: 0, y: 0, width: 50, height: 50 };
7
+ const clipRect: Rect = { x: 0, y: 0, width: 300, height: 200 };
8
+
9
+ it("space mode distributes tiles evenly with spacing", () => {
10
+ const tiles = computeBackgroundTileRects(tileRect, clipRect, "space");
11
+
12
+ expect(tiles.length).toBeGreaterThan(1);
13
+ // All tiles should have the original dimensions
14
+ expect(tiles[0].width).toBe(50);
15
+ expect(tiles[0].height).toBe(50);
16
+ });
17
+
18
+ it("round mode scales tiles to fit perfectly", () => {
19
+ const tiles = computeBackgroundTileRects(tileRect, clipRect, "round");
20
+
21
+ expect(tiles.length).toBeGreaterThan(1);
22
+ // All tiles should have consistent scaled dimensions
23
+ expect(tiles[0].width).toBeGreaterThan(0);
24
+ expect(tiles[0].height).toBeGreaterThan(0);
25
+ });
26
+
27
+ it("round mode scales with non-perfect dimensions", () => {
28
+ const oddTile: Rect = { x: 0, y: 0, width: 55, height: 55 };
29
+ const tiles = computeBackgroundTileRects(oddTile, clipRect, "round");
30
+
31
+ // Tiles should be scaled (not 55px)
32
+ expect(tiles.length).toBeGreaterThan(0);
33
+ expect(tiles[0].width).not.toBe(55);
34
+ });
35
+ });
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect, beforeAll } from "vitest";
2
+ import { registerAllPropertyParsers } from "../../src/css/parsers/register-parsers.js";
3
+ import { applyBackgroundRepeatDecl } from "../../src/css/parsers/background-parser-extended.js";
4
+ import { parseBackgroundImage, parseBackground } from "../../src/css/parsers/background-parser-extended.js";
5
+ import type { ImageBackgroundLayer, GradientBackgroundLayer } from "../../src/css/background-types.js";
6
+
7
+ interface StyleAccumulator {
8
+ backgroundLayers?: Array<ImageBackgroundLayer | GradientBackgroundLayer>;
9
+ [key: string]: any;
10
+ }
11
+
12
+ describe("background-repeat CSS parsing", () => {
13
+ beforeAll(() => {
14
+ registerAllPropertyParsers();
15
+ });
16
+
17
+ it("parses background-repeat: no-repeat", () => {
18
+ const target: StyleAccumulator = {};
19
+ parseBackgroundImage('url(test.png)', target);
20
+ applyBackgroundRepeatDecl('no-repeat', target);
21
+
22
+ expect(target.backgroundLayers).toBeDefined();
23
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
24
+ expect(imageLayer).toBeDefined();
25
+ expect(imageLayer?.repeat).toBe("no-repeat");
26
+ });
27
+
28
+ it("parses background-repeat: repeat", () => {
29
+ const target: StyleAccumulator = {};
30
+ parseBackgroundImage('url(test.png)', target);
31
+ applyBackgroundRepeatDecl('repeat', target);
32
+
33
+ expect(target.backgroundLayers).toBeDefined();
34
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
35
+ expect(imageLayer).toBeDefined();
36
+ expect(imageLayer?.repeat).toBe("repeat");
37
+ });
38
+
39
+ it("parses background-repeat: repeat-x", () => {
40
+ const target: StyleAccumulator = {};
41
+ parseBackgroundImage('url(test.png)', target);
42
+ applyBackgroundRepeatDecl('repeat-x', target);
43
+
44
+ expect(target.backgroundLayers).toBeDefined();
45
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
46
+ expect(imageLayer).toBeDefined();
47
+ expect(imageLayer?.repeat).toBe("repeat-x");
48
+ });
49
+
50
+ it("parses background-repeat: repeat-y", () => {
51
+ const target: StyleAccumulator = {};
52
+ parseBackgroundImage('url(test.png)', target);
53
+ applyBackgroundRepeatDecl('repeat-y', target);
54
+
55
+ expect(target.backgroundLayers).toBeDefined();
56
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
57
+ expect(imageLayer).toBeDefined();
58
+ expect(imageLayer?.repeat).toBe("repeat-y");
59
+ });
60
+
61
+ it("parses background-repeat: space", () => {
62
+ const target: StyleAccumulator = {};
63
+ parseBackgroundImage('url(test.png)', target);
64
+ applyBackgroundRepeatDecl('space', target);
65
+
66
+ expect(target.backgroundLayers).toBeDefined();
67
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
68
+ expect(imageLayer).toBeDefined();
69
+ expect(imageLayer?.repeat).toBe("space");
70
+ });
71
+
72
+ it("parses background-repeat: round", () => {
73
+ const target: StyleAccumulator = {};
74
+ parseBackgroundImage('url(test.png)', target);
75
+ applyBackgroundRepeatDecl('round', target);
76
+
77
+ expect(target.backgroundLayers).toBeDefined();
78
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
79
+ expect(imageLayer).toBeDefined();
80
+ expect(imageLayer?.repeat).toBe("round");
81
+ });
82
+
83
+ it("defaults to repeat when background-repeat is not specified", () => {
84
+ const target: StyleAccumulator = {};
85
+ parseBackgroundImage('url(test.png)', target);
86
+
87
+ expect(target.backgroundLayers).toBeDefined();
88
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
89
+ expect(imageLayer).toBeDefined();
90
+ expect(imageLayer?.repeat).toBe("repeat");
91
+ });
92
+
93
+ it("parses background-repeat from background shorthand", () => {
94
+ const target: StyleAccumulator = {};
95
+ parseBackground('url(test.png) no-repeat', target);
96
+
97
+ expect(target.backgroundLayers).toBeDefined();
98
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
99
+ expect(imageLayer).toBeDefined();
100
+ expect(imageLayer?.repeat).toBe("no-repeat");
101
+ });
102
+
103
+ it("applies background-repeat to gradients", () => {
104
+ const target: StyleAccumulator = {};
105
+ parseBackgroundImage('linear-gradient(red, blue)', target);
106
+ applyBackgroundRepeatDecl('no-repeat', target);
107
+
108
+ expect(target.backgroundLayers).toBeDefined();
109
+ const gradientLayer = target.backgroundLayers?.find((l) => l.kind === "gradient") as GradientBackgroundLayer;
110
+ expect(gradientLayer).toBeDefined();
111
+ expect(gradientLayer?.repeat).toBe("no-repeat");
112
+ });
113
+
114
+ it("overrides background-repeat when specified after background shorthand", () => {
115
+ const target: StyleAccumulator = {};
116
+ parseBackground('url(test.png) repeat', target);
117
+ applyBackgroundRepeatDecl('no-repeat', target);
118
+
119
+ expect(target.backgroundLayers).toBeDefined();
120
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
121
+ expect(imageLayer).toBeDefined();
122
+ expect(imageLayer?.repeat).toBe("no-repeat");
123
+ });
124
+
125
+
126
+ it("ignores invalid background-repeat values", () => {
127
+ const target: StyleAccumulator = {};
128
+ parseBackgroundImage('url(test.png)', target);
129
+ applyBackgroundRepeatDecl('invalid-value', target);
130
+
131
+ expect(target.backgroundLayers).toBeDefined();
132
+ const imageLayer = target.backgroundLayers?.find((l) => l.kind === "image") as ImageBackgroundLayer;
133
+ expect(imageLayer).toBeDefined();
134
+ // Should keep the default 'repeat' value
135
+ expect(imageLayer?.repeat).toBe("repeat");
136
+ });
137
+ });