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,463 @@
1
+ import type {
2
+ SvgCircleNode,
3
+ SvgEllipseNode,
4
+ SvgLineNode,
5
+ SvgPathNode,
6
+ SvgPolygonNode,
7
+ SvgPolylineNode,
8
+ SvgRectNode,
9
+ SvgTextNode,
10
+ } from "../../svg/types.js";
11
+ import type { SvgRenderContext } from "./render-svg.js";
12
+ import type { SvgStyle } from "./style-computer.js";
13
+ import type { PathCommand } from "../renderers/shape-renderer.js";
14
+ import type { StrokeOptions } from "../types.js";
15
+ import { parsePathData } from "../../svg/path-data.js";
16
+ import { buildEllipseSegments, buildRectSegments, buildRoundedRectSegments } from "./geometry-builder.js";
17
+ import { mapPathSegments, mapPoints, mapSvgPoint } from "./coordinate-mapper.js";
18
+ import { resolvePaint } from "./style-computer.js";
19
+ import { clampAlpha } from "../utils/color-utils.js";
20
+ import { applyMatrixToPoint, multiplyMatrices } from "../../geometry/matrix.js";
21
+ import { parseTransform } from "../../transform/css-parser.js";
22
+ import type { SvgLinearGradientNode } from "../../svg/types.js";
23
+ import type { SvgRadialGradientNode } from "../../svg/types.js";
24
+ import { parseLinearGradient, type LinearGradient } from "../../css/parsers/gradient-parser.js";
25
+ import type { RadialGradient } from "../../css/parsers/gradient-parser.js";
26
+
27
+ function isLinearGradientPaint(value: unknown): value is LinearGradient {
28
+ if (typeof value !== "object" || value === null) return false;
29
+ const candidate = value as Partial<LinearGradient>;
30
+ return candidate.type === "linear" && Array.isArray((candidate as any).stops);
31
+ }
32
+
33
+ function isRadialGradientPaint(value: unknown): value is RadialGradient {
34
+ if (typeof value !== "object" || value === null) return false;
35
+ const candidate = value as Partial<RadialGradient>;
36
+ return candidate.type === "radial" && typeof (candidate as any).r === "number";
37
+ }
38
+
39
+ export function renderRect(node: SvgRectNode, style: SvgStyle, context: SvgRenderContext): void {
40
+ const width = Number.isFinite(node.width ?? NaN) ? node.width ?? 0 : 0;
41
+ const height = Number.isFinite(node.height ?? NaN) ? node.height ?? 0 : 0;
42
+ if (width <= 0 || height <= 0) {
43
+ return;
44
+ }
45
+ const x = Number.isFinite(node.x ?? NaN) ? node.x ?? 0 : 0;
46
+ const y = Number.isFinite(node.y ?? NaN) ? node.y ?? 0 : 0;
47
+
48
+ let rx = node.rx ?? node.ry ?? 0;
49
+ let ry = node.ry ?? node.rx ?? 0;
50
+ if (!Number.isFinite(rx)) rx = 0;
51
+ if (!Number.isFinite(ry)) ry = 0;
52
+ rx = clampRadius(rx, width / 2);
53
+ ry = clampRadius(ry, height / 2);
54
+
55
+ const segments = rx > 0 || ry > 0 ? buildRoundedRectSegments(x, y, width, height, rx, ry) : buildRectSegments(x, y, width, height);
56
+ // If fill is a gradient (including url(...) referencing a <linearGradient> or <radialGradient> in defs), handle via painter gradient APIs
57
+ const gradient = resolveGradientPaint(style.fill, context);
58
+ if (gradient) {
59
+ // Map rectangle corners to viewport/page coordinates
60
+ const p1 = mapSvgPoint(x, y, context);
61
+ const p2 = mapSvgPoint(x + width, y + height, context);
62
+ if (p1 && p2) {
63
+ const pxRect = { x: p1.x, y: p1.y, width: p2.x - p1.x, height: p2.y - p1.y };
64
+ if ((gradient as RadialGradient).type === "radial") {
65
+ if (rx > 0 || ry > 0) {
66
+ context.painter.fillRoundedRect(pxRect, {
67
+ topLeft: { x: rx, y: ry },
68
+ topRight: { x: rx, y: ry },
69
+ bottomRight: { x: rx, y: ry },
70
+ bottomLeft: { x: rx, y: ry },
71
+ }, gradient as RadialGradient);
72
+ } else {
73
+ context.painter.fillRect(pxRect, gradient as RadialGradient);
74
+ }
75
+ } else {
76
+ if (rx > 0 || ry > 0) {
77
+ context.painter.fillRoundedRect(pxRect, {
78
+ topLeft: { x: rx, y: ry },
79
+ topRight: { x: rx, y: ry },
80
+ bottomRight: { x: rx, y: ry },
81
+ bottomLeft: { x: rx, y: ry },
82
+ }, gradient as LinearGradient);
83
+ } else {
84
+ context.painter.fillRect(pxRect, gradient as LinearGradient);
85
+ }
86
+ }
87
+ return;
88
+ }
89
+ }
90
+
91
+ const commands = mapPathSegments(segments, context);
92
+ if (!commands || commands.length === 0) {
93
+ return;
94
+ }
95
+ paintPathCommands(commands, style, context, style.fillRule);
96
+ }
97
+
98
+ export function renderCircle(node: SvgCircleNode, style: SvgStyle, context: SvgRenderContext): void {
99
+ const radius = Number.isFinite(node.r ?? NaN) ? node.r ?? 0 : 0;
100
+ if (radius <= 0) {
101
+ return;
102
+ }
103
+ const cx = Number.isFinite(node.cx ?? NaN) ? node.cx ?? 0 : 0;
104
+ const cy = Number.isFinite(node.cy ?? NaN) ? node.cy ?? 0 : 0;
105
+ // Try resolving gradient paint first. For circle fills we special-case gradients and
106
+ // draw them by creating a rounded-rect (square) clipping path sized to the circle's
107
+ // bounding box. This reuses the ShapeRenderer's rounded-rect clipping + shading logic
108
+ // and produces a visually correct circular fill via the cubic-curve approximation.
109
+ const gradient = resolveGradientPaint(style.fill, context);
110
+ if (gradient) {
111
+ // Map center and an edge point to page pixels
112
+ const center = mapSvgPoint(cx, cy, context);
113
+ const edge = mapSvgPoint(cx + radius, cy, context);
114
+ if (center && edge) {
115
+ const rPx = Math.sqrt((edge.x - center.x) ** 2 + (edge.y - center.y) ** 2);
116
+ const pxRect = { x: center.x - rPx, y: center.y - rPx, width: rPx * 2, height: rPx * 2 };
117
+ const radii = {
118
+ topLeft: { x: rPx, y: rPx },
119
+ topRight: { x: rPx, y: rPx },
120
+ bottomRight: { x: rPx, y: rPx },
121
+ bottomLeft: { x: rPx, y: rPx },
122
+ };
123
+ if ((gradient as RadialGradient).type === "radial") {
124
+ context.painter.fillRoundedRect(pxRect, radii, gradient as RadialGradient);
125
+ } else {
126
+ context.painter.fillRoundedRect(pxRect, radii, gradient as LinearGradient);
127
+ }
128
+ return;
129
+ }
130
+ // If mapping failed, fall back to path-based fill below
131
+ }
132
+
133
+ const segments = buildEllipseSegments(cx, cy, radius, radius);
134
+ const commands = mapPathSegments(segments, context);
135
+ if (!commands || commands.length === 0) {
136
+ return;
137
+ }
138
+ paintPathCommands(commands, style, context, style.fillRule);
139
+ }
140
+
141
+ export function renderEllipse(node: SvgEllipseNode, style: SvgStyle, context: SvgRenderContext): void {
142
+ const rx = Number.isFinite(node.rx ?? NaN) ? node.rx ?? 0 : 0;
143
+ const ry = Number.isFinite(node.ry ?? NaN) ? node.ry ?? 0 : 0;
144
+ if (rx <= 0 || ry <= 0) {
145
+ return;
146
+ }
147
+ const cx = Number.isFinite(node.cx ?? NaN) ? node.cx ?? 0 : 0;
148
+ const cy = Number.isFinite(node.cy ?? NaN) ? node.cy ?? 0 : 0;
149
+ const segments = buildEllipseSegments(cx, cy, rx, ry);
150
+ const commands = mapPathSegments(segments, context);
151
+ if (!commands || commands.length === 0) {
152
+ return;
153
+ }
154
+ paintPathCommands(commands, style, context, style.fillRule);
155
+ }
156
+
157
+ function clampRadius(value: number, limit: number): number {
158
+ if (!Number.isFinite(value) || value <= 0) {
159
+ return 0;
160
+ }
161
+ if (value > limit) {
162
+ return limit;
163
+ }
164
+ return value;
165
+ }
166
+
167
+ export function renderPolygon(node: SvgPolygonNode, style: SvgStyle, context: SvgRenderContext): void {
168
+ const points = node.points ?? [];
169
+ if (points.length < 2) {
170
+ return;
171
+ }
172
+ const mapped = mapPoints(points, context);
173
+ if (mapped.length < 2) {
174
+ return;
175
+ }
176
+ const fillColor = resolvePaint(style.fill, style.opacity * style.fillOpacity);
177
+ if (fillColor) {
178
+ context.painter.fillPolygon(mapped, fillColor, true);
179
+ }
180
+ const strokeColor = resolvePaint(style.stroke, style.opacity * style.strokeOpacity);
181
+ if (strokeColor) {
182
+ context.painter.strokePolyline(mapped, strokeColor, {
183
+ ...getStrokeOptions(style, context),
184
+ close: true,
185
+ });
186
+ }
187
+ }
188
+
189
+ export function renderPolyline(node: SvgPolylineNode, style: SvgStyle, context: SvgRenderContext): void {
190
+ const points = node.points ?? [];
191
+ if (points.length < 2) {
192
+ return;
193
+ }
194
+ const mapped = mapPoints(points, context);
195
+ if (mapped.length < 2) {
196
+ return;
197
+ }
198
+ const strokeColor = resolvePaint(style.stroke, style.opacity * style.strokeOpacity);
199
+ if (strokeColor) {
200
+ context.painter.strokePolyline(mapped, strokeColor, {
201
+ ...getStrokeOptions(style, context),
202
+ close: false,
203
+ });
204
+ }
205
+ }
206
+
207
+ export function renderLine(node: SvgLineNode, style: SvgStyle, context: SvgRenderContext): void {
208
+ const strokeColor = resolvePaint(style.stroke, style.opacity * style.strokeOpacity);
209
+ if (!strokeColor) {
210
+ return;
211
+ }
212
+ const start = mapSvgPoint(node.x1 ?? 0, node.y1 ?? 0, context);
213
+ const end = mapSvgPoint(node.x2 ?? 0, node.y2 ?? 0, context);
214
+ if (!start || !end) {
215
+ return;
216
+ }
217
+ const points = [start, end];
218
+ context.painter.strokePolyline(points, strokeColor, {
219
+ ...getStrokeOptions(style, context),
220
+ close: false,
221
+ });
222
+ }
223
+
224
+ export function renderPath(node: SvgPathNode, style: SvgStyle, context: SvgRenderContext): void {
225
+ const segments = parsePathData(node.d);
226
+ if (segments.length === 0) {
227
+ return;
228
+ }
229
+ const commands = mapPathSegments(segments, context);
230
+ if (!commands || commands.length === 0) {
231
+ return;
232
+ }
233
+ paintPathCommands(commands, style, context, style.fillRule);
234
+ }
235
+
236
+ function paintPathCommands(commands: PathCommand[], style: SvgStyle, context: SvgRenderContext, fillRule?: "nonzero" | "evenodd"): void {
237
+ if (commands.length === 0) {
238
+ return;
239
+ }
240
+ // Try to resolve a gradient paint server first (supports url(#id) references and CSS gradients)
241
+ const gradient = resolveGradientPaint(style.fill, context);
242
+ if (gradient) {
243
+ // Use the new painter API to paint arbitrary path commands with a gradient. The ShapeRenderer will
244
+ // create a clipping path from the provided commands and paint the shading clipped to the path.
245
+ context.painter.fillPathWithGradient(commands, gradient, { fillRule });
246
+ return;
247
+ }
248
+ const fillColor = resolvePaint(style.fill, style.opacity * style.fillOpacity);
249
+ if (fillColor) {
250
+ context.painter.fillPath(commands, fillColor, { fillRule: fillRule ?? style.fillRule });
251
+ }
252
+ const strokeColor = resolvePaint(style.stroke, style.opacity * style.strokeOpacity);
253
+ if (strokeColor) {
254
+ context.painter.strokePath(commands, strokeColor, getStrokeOptions(style, context));
255
+ }
256
+ }
257
+
258
+ export async function renderText(node: SvgTextNode, style: SvgStyle, context: SvgRenderContext): Promise<void> {
259
+ const fillColor = resolvePaint(style.fill, style.opacity * style.fillOpacity);
260
+ if (!fillColor) {
261
+ return;
262
+ }
263
+ const fontSize = node.fontSize ?? style.fontSize;
264
+ if (!Number.isFinite(fontSize) || fontSize <= 0) {
265
+ return;
266
+ }
267
+ const anchor = node.textAnchor ?? style.textAnchor ?? "start";
268
+ const combined = multiplyMatrices(context.viewportMatrix, context.transform);
269
+ const anchorX = Number.isFinite(node.x ?? NaN) ? node.x ?? 0 : 0;
270
+ const anchorY = Number.isFinite(node.y ?? NaN) ? node.y ?? 0 : 0;
271
+ const origin = applyMatrixToPoint(combined, anchorX, anchorY);
272
+
273
+ const axisX = { x: combined.a, y: combined.b };
274
+ const approxWidth = estimateTextWidth(node.text, fontSize);
275
+ const anchorFactor = anchor === "middle" ? 0.5 : anchor === "end" ? 1 : 0;
276
+ let baselineX = origin.x;
277
+ let baselineY = origin.y;
278
+ if (anchorFactor !== 0 && (axisX.x !== 0 || axisX.y !== 0)) {
279
+ baselineX -= axisX.x * approxWidth * anchorFactor;
280
+ baselineY -= axisX.y * approxWidth * anchorFactor;
281
+ }
282
+
283
+ const color = { r: fillColor.r, g: fillColor.g, b: fillColor.b, a: clampAlpha(fillColor.a ?? 1) };
284
+ const fontFamily = node.fontFamily ?? style.fontFamily ?? "Helvetica";
285
+ await context.painter.drawTextRun({
286
+ text: node.text,
287
+ fontFamily,
288
+ fontSize,
289
+ fill: color,
290
+ lineMatrix: {
291
+ a: combined.a,
292
+ b: combined.b,
293
+ c: combined.c,
294
+ d: combined.d,
295
+ e: baselineX,
296
+ f: baselineY,
297
+ },
298
+ });
299
+ }
300
+
301
+ function estimateTextWidth(text: string, fontSize: number): number {
302
+ if (!text) {
303
+ return 0;
304
+ }
305
+ const averageFactor = 0.6;
306
+ return text.length * fontSize * averageFactor;
307
+ }
308
+
309
+ function resolveGradientPaint(paint: unknown, context?: SvgRenderContext): LinearGradient | RadialGradient | null {
310
+ // If already gradient object, return it
311
+ if (isLinearGradientPaint(paint) || isRadialGradientPaint(paint)) return paint as LinearGradient | RadialGradient;
312
+ if (typeof paint === "string") {
313
+ const trimmed = paint.trim();
314
+ const urlMatch = trimmed.match(/^url\(\s*#([^\)\s]+)\s*\)$/i);
315
+ if (urlMatch && context) {
316
+ const defs = (context as any).defs as Map<string, any> | undefined;
317
+ if (defs) {
318
+ const node = defs.get(urlMatch[1]);
319
+ if (node && (node.type === "lineargradient" || node.type === "radialgradient")) {
320
+ if (node.type === "lineargradient") {
321
+ return svgLinearNodeToLinearGradient(node as unknown as SvgLinearGradientNode, context);
322
+ }
323
+ return svgRadialNodeToRadialGradient(node as unknown as SvgRadialGradientNode, context);
324
+ }
325
+ }
326
+ }
327
+ // fallback to CSS linear-gradient(...) strings
328
+ return parseLinearGradient(paint);
329
+ }
330
+ return null;
331
+ }
332
+
333
+ function svgLinearNodeToLinearGradient(node: SvgLinearGradientNode, context?: SvgRenderContext): LinearGradient {
334
+ const x1 = node.x1 ?? 0;
335
+ const y1 = node.y1 ?? 0;
336
+ const x2 = node.x2 ?? 1;
337
+ const y2 = node.y2 ?? 0;
338
+
339
+ const stops = (node.stops ?? []).map((s) => ({ color: s.color, position: s.offset }));
340
+
341
+ // Default: objectBoundingBox coordinates (ratio 0..1)
342
+ const units = node.gradientUnits === "userSpaceOnUse" ? "userSpace" : "ratio";
343
+
344
+ // If userSpaceOnUse, map points to page pixels using the SVG render context
345
+ if (units === "userSpace" && context) {
346
+ const p1 = mapSvgPoint(x1, y1, context);
347
+ const p2 = mapSvgPoint(x2, y2, context);
348
+ if (p1 && p2) {
349
+ // coords in absolute page pixels; gradient-service will convert to rectangle-local points
350
+ return { type: "linear", direction: "to right", stops, coords: { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y, units: "userSpace" } };
351
+ }
352
+ // fallback to ratio if mapping failed
353
+ }
354
+
355
+ // objectBoundingBox: keep ratio coords (0..1)
356
+ // Apply gradientTransform if present — treat it as operating in gradient coordinate space
357
+ let rp1 = { x: x1, y: y1 };
358
+ let rp2 = { x: x2, y: y2 };
359
+ const rawTransform = (node.attributes && (node.attributes["gradientTransform"] ?? node.attributes["gradienttransform"])) as string | undefined;
360
+ if (rawTransform) {
361
+ const t = parseTransform(rawTransform) || undefined;
362
+ if (t) {
363
+ rp1 = applyMatrixToPoint(t, rp1.x, rp1.y);
364
+ rp2 = applyMatrixToPoint(t, rp2.x, rp2.y);
365
+ }
366
+ }
367
+
368
+ // compute direction angle from ratio coords (useful when no explicit coords provided)
369
+ const dx = rp2.x - rp1.x;
370
+ const dy = rp2.y - rp1.y;
371
+ const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
372
+ const direction = `${angle.toFixed(2)}deg`;
373
+
374
+ return { type: "linear", direction, stops, coords: { x1: rp1.x, y1: rp1.y, x2: rp2.x, y2: rp2.y, units: "ratio" } };
375
+ }
376
+
377
+ function svgRadialNodeToRadialGradient(node: SvgRadialGradientNode, context?: SvgRenderContext): RadialGradient {
378
+ const cx = node.cx ?? 0.5;
379
+ const cy = node.cy ?? 0.5;
380
+ const r = node.r ?? 0.5;
381
+ const fx = node.fx;
382
+ const fy = node.fy;
383
+ const stops = (node.stops ?? []).map((s) => ({ color: s.color, position: s.offset }));
384
+
385
+ const units = node.gradientUnits === "userSpaceOnUse" ? "userSpace" : "ratio";
386
+
387
+ if (units === "userSpace" && context) {
388
+ const center = mapSvgPoint(cx, cy, context);
389
+ const focal = fx !== undefined && fy !== undefined ? mapSvgPoint(fx, fy, context) : undefined;
390
+ const radiusPt = (() => {
391
+ // map a point at cx + r, cy to user space and compute distance
392
+ const edge = mapSvgPoint(cx + r, cy, context);
393
+ if (center && edge) {
394
+ const dx = edge.x - center.x;
395
+ const dy = edge.y - center.y;
396
+ return Math.sqrt(dx * dx + dy * dy);
397
+ }
398
+ return undefined;
399
+ })();
400
+
401
+ // If mapping succeeded, return a userSpace radial gradient with absolute coords
402
+ if (center && radiusPt !== undefined) {
403
+ const rad: RadialGradient = {
404
+ type: "radial",
405
+ cx: center.x,
406
+ cy: center.y,
407
+ r: radiusPt,
408
+ stops,
409
+ coordsUnits: "userSpace",
410
+ };
411
+ if (focal) {
412
+ rad.fx = focal.x;
413
+ rad.fy = focal.y;
414
+ }
415
+ return rad;
416
+ }
417
+ // fallback to ratio below
418
+ }
419
+ // objectBoundingBox (ratio) coordinates;
420
+ // Keep raw coords in ratio units and, if a gradientTransform is present, preserve it
421
+ // on the returned RadialGradient so the shading creation can emit a PDF /Matrix that
422
+ // maps ratio-space circles to device-space ellipses exactly.
423
+ const radRatio: RadialGradient = {
424
+ type: "radial",
425
+ cx: cx,
426
+ cy: cy,
427
+ r: r,
428
+ stops,
429
+ };
430
+ if (fx !== undefined && fy !== undefined) {
431
+ radRatio.fx = fx;
432
+ radRatio.fy = fy;
433
+ }
434
+
435
+ const rawTransform = (node.attributes && (node.attributes["gradientTransform"] ?? node.attributes["gradienttransform"])) as string | undefined;
436
+ if (rawTransform) {
437
+ const t = parseTransform(rawTransform) || undefined;
438
+ if (t) {
439
+ // Preserve the parsed transform matrix on the RadialGradient for use by the
440
+ // gradient service when building the PDF shading dictionary.
441
+ radRatio.transform = { a: t.a, b: t.b, c: t.c, d: t.d, e: t.e, f: t.f };
442
+ }
443
+ }
444
+
445
+ return radRatio;
446
+ }
447
+
448
+ function getStrokeOptions(style: SvgStyle, context: SvgRenderContext): StrokeOptions {
449
+ const strokeWidthPx = (style.strokeWidth ?? 1) * context.strokeScale;
450
+ const options: StrokeOptions = {
451
+ lineWidth: strokeWidthPx > 0 ? strokeWidthPx : undefined,
452
+ lineCap: style.strokeLinecap,
453
+ lineJoin: style.strokeLinejoin,
454
+ };
455
+
456
+ if (style.strokeDashArray && style.strokeDashArray.length > 0) {
457
+ const pattern = style.strokeDashArray.map((v) => v * context.strokeScale);
458
+ const phase = (style.strokeDashOffset ?? 0) * context.strokeScale;
459
+ options.dash = { pattern, phase };
460
+ }
461
+
462
+ return options;
463
+ }
@@ -0,0 +1,246 @@
1
+ import type { SvgDrawableNode, SvgGroupNode, SvgRootNode } from "../../svg/types.js";
2
+ import { parseColor } from "../utils/color-utils.js";
3
+
4
+ export interface SvgStyle {
5
+ fill?: string;
6
+ stroke?: string;
7
+ strokeWidth?: number;
8
+ strokeLinecap?: "butt" | "round" | "square";
9
+ strokeLinejoin?: "miter" | "round" | "bevel";
10
+ strokeDashArray?: number[];
11
+ strokeDashOffset?: number;
12
+ fillRule: "nonzero" | "evenodd";
13
+ opacity: number;
14
+ fillOpacity: number;
15
+ strokeOpacity: number;
16
+ fontSize: number;
17
+ fontFamily?: string;
18
+ textAnchor?: "start" | "middle" | "end";
19
+ }
20
+
21
+ export function deriveStyle(base: SvgStyle, node: SvgDrawableNode | SvgGroupNode | SvgRootNode): SvgStyle {
22
+ const style: SvgStyle = { ...base };
23
+ const attrs = node.attributes ?? {};
24
+
25
+ if (attrs.opacity !== undefined) {
26
+ const value = parseOpacity(attrs.opacity);
27
+ if (value !== undefined) {
28
+ style.opacity = clamp01(style.opacity * value);
29
+ }
30
+ }
31
+
32
+ if (attrs.fill !== undefined) {
33
+ const fillValue = attrs.fill.trim();
34
+ style.fill = !fillValue || fillValue === "none" ? undefined : fillValue;
35
+ }
36
+
37
+ if (attrs["fill-opacity"] !== undefined) {
38
+ const value = parseOpacity(attrs["fill-opacity"]);
39
+ if (value !== undefined) {
40
+ style.fillOpacity = clamp01(value);
41
+ }
42
+ }
43
+
44
+ if (attrs.stroke !== undefined) {
45
+ const strokeValue = attrs.stroke.trim();
46
+ style.stroke = !strokeValue || strokeValue === "none" ? undefined : strokeValue;
47
+ }
48
+
49
+ if (attrs["stroke-opacity"] !== undefined) {
50
+ const value = parseOpacity(attrs["stroke-opacity"]);
51
+ if (value !== undefined) {
52
+ style.strokeOpacity = clamp01(value);
53
+ }
54
+ }
55
+
56
+ if (attrs["stroke-width"] !== undefined) {
57
+ const value = parseNumber(attrs["stroke-width"]);
58
+ if (value !== undefined) {
59
+ style.strokeWidth = value;
60
+ }
61
+ }
62
+
63
+ if (attrs["stroke-linecap"] !== undefined) {
64
+ const cap = normalizeLineCap(attrs["stroke-linecap"]);
65
+ if (cap) {
66
+ style.strokeLinecap = cap;
67
+ }
68
+ }
69
+
70
+ if (attrs["stroke-linejoin"] !== undefined) {
71
+ const join = normalizeLineJoin(attrs["stroke-linejoin"]);
72
+ if (join) {
73
+ style.strokeLinejoin = join;
74
+ }
75
+ }
76
+
77
+ if (attrs["stroke-dasharray"] !== undefined) {
78
+ const value = parseDashArray(attrs["stroke-dasharray"]);
79
+ if (value) {
80
+ style.strokeDashArray = value;
81
+ }
82
+ }
83
+
84
+ if (attrs["stroke-dashoffset"] !== undefined) {
85
+ const value = parseNumber(attrs["stroke-dashoffset"]);
86
+ if (value !== undefined) {
87
+ style.strokeDashOffset = value;
88
+ }
89
+ }
90
+
91
+ if (attrs["fill-rule"] !== undefined) {
92
+ const rule = normalizeFillRule(attrs["fill-rule"]);
93
+ if (rule) {
94
+ style.fillRule = rule;
95
+ }
96
+ }
97
+
98
+ if (attrs["font-size"] !== undefined) {
99
+ const value = parseNumber(attrs["font-size"]);
100
+ if (value !== undefined) {
101
+ style.fontSize = value;
102
+ }
103
+ }
104
+
105
+ if (attrs["font-family"] !== undefined) {
106
+ const family = attrs["font-family"].trim();
107
+ if (family) {
108
+ style.fontFamily = family;
109
+ }
110
+ }
111
+
112
+ if (attrs["text-anchor"] !== undefined) {
113
+ const anchor = normalizeTextAnchor(attrs["text-anchor"]);
114
+ if (anchor) {
115
+ style.textAnchor = anchor;
116
+ }
117
+ }
118
+
119
+ return style;
120
+ }
121
+
122
+ export function createDefaultStyle(): SvgStyle {
123
+ return {
124
+ fill: "#000000",
125
+ stroke: undefined,
126
+ strokeWidth: 1,
127
+ strokeLinecap: "butt",
128
+ strokeLinejoin: "miter",
129
+ strokeDashArray: undefined,
130
+ strokeDashOffset: 0,
131
+ fillRule: "nonzero",
132
+ opacity: 1,
133
+ fillOpacity: 1,
134
+ strokeOpacity: 1,
135
+ fontSize: 16,
136
+ fontFamily: undefined,
137
+ textAnchor: "start",
138
+ };
139
+ }
140
+
141
+ export function resolvePaint(value: string | undefined, opacity: number): { r: number; g: number; b: number; a: number } | undefined {
142
+ if (!value) {
143
+ return undefined;
144
+ }
145
+ if (value === "none") {
146
+ return undefined;
147
+ }
148
+ const color = parseColor(value);
149
+ if (!color) {
150
+ return undefined;
151
+ }
152
+ const baseAlpha = color.a ?? 1;
153
+ return {
154
+ r: color.r,
155
+ g: color.g,
156
+ b: color.b,
157
+ a: clamp01(baseAlpha * opacity),
158
+ };
159
+ }
160
+
161
+ function parseOpacity(value: string | undefined): number | undefined {
162
+ const parsed = parseNumber(value);
163
+ if (parsed === undefined) {
164
+ return undefined;
165
+ }
166
+ return clamp01(parsed);
167
+ }
168
+
169
+ function parseNumber(value: string | undefined): number | undefined {
170
+ if (!value) {
171
+ return undefined;
172
+ }
173
+ const trimmed = value.trim();
174
+ if (!trimmed) {
175
+ return undefined;
176
+ }
177
+ const num = Number.parseFloat(trimmed);
178
+ if (!Number.isFinite(num)) {
179
+ return undefined;
180
+ }
181
+ return num;
182
+ }
183
+
184
+ function normalizeLineCap(value: string): "butt" | "round" | "square" | undefined {
185
+ const lower = value.trim().toLowerCase();
186
+ if (lower === "butt" || lower === "round" || lower === "square") {
187
+ return lower;
188
+ }
189
+ return undefined;
190
+ }
191
+
192
+ function normalizeLineJoin(value: string): "miter" | "round" | "bevel" | undefined {
193
+ const lower = value.trim().toLowerCase();
194
+ if (lower === "miter" || lower === "round" || lower === "bevel") {
195
+ return lower;
196
+ }
197
+ return undefined;
198
+ }
199
+
200
+ function normalizeFillRule(value: string): "nonzero" | "evenodd" | undefined {
201
+ const lower = value.trim().toLowerCase();
202
+ if (lower === "nonzero" || lower === "evenodd") {
203
+ return lower;
204
+ }
205
+ return undefined;
206
+ }
207
+
208
+ function normalizeTextAnchor(value: string): "start" | "middle" | "end" | undefined {
209
+ const lower = value.trim().toLowerCase();
210
+ if (lower === "start" || lower === "middle" || lower === "end") {
211
+ return lower;
212
+ }
213
+ return undefined;
214
+ }
215
+
216
+ function clamp01(value: number): number {
217
+ if (!Number.isFinite(value)) {
218
+ return 0;
219
+ }
220
+ if (value <= 0) {
221
+ return 0;
222
+ }
223
+ if (value >= 1) {
224
+ return 1;
225
+ }
226
+ return value;
227
+ }
228
+
229
+ function parseDashArray(value: string | undefined): number[] | undefined {
230
+ if (!value) {
231
+ return undefined;
232
+ }
233
+ const trimmed = value.trim();
234
+ if (!trimmed || trimmed === "none") {
235
+ return undefined;
236
+ }
237
+ const parts = trimmed
238
+ .split(/[\s,]+/)
239
+ .map((part) => Number.parseFloat(part))
240
+ .filter((num) => Number.isFinite(num) && num >= 0);
241
+
242
+ if (parts.length === 0) {
243
+ return undefined;
244
+ }
245
+ return parts;
246
+ }