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,209 @@
1
+ import type { RenderBox, TextPaintOptions, PageSize, Rect } from "./types.js";
2
+ import type { PagePainter } from "./page-painter.js";
3
+ import type { HeaderFooterVariant, HeaderFooterLayout } from "./header-footer-layout.js";
4
+ import type { FontRegistry } from "./font/font-registry.js";
5
+ import { applyPlaceholders } from "./header-footer-tokens.js";
6
+ import {
7
+ renderHeaderFooterHtml,
8
+ paintRenderedHeaderFooter,
9
+ type RenderedHeaderFooter,
10
+ } from "./header-footer-renderer.js";
11
+ import { paintBoxAtomic } from "./renderer/box-painter.js";
12
+ import { log } from "../logging/debug.js";
13
+ import type { Environment } from "../environment/environment.js";
14
+
15
+ export interface HeaderFooterPaintContext {
16
+ /** Page margins in pixels */
17
+ margins: { top: number; right: number; bottom: number; left: number };
18
+ /** Page width in pixels */
19
+ pageWidthPx: number;
20
+ /** Page height in pixels */
21
+ pageHeightPx: number;
22
+ /** Font registry for text rendering */
23
+ fontRegistry: FontRegistry;
24
+ /** Page offset Y for coordinate transformation */
25
+ pageOffsetY: number;
26
+ /** Optional CSS for header/footer styling */
27
+ css?: string;
28
+ /** Platform environment (Node/browser) */
29
+ environment?: Environment;
30
+ }
31
+
32
+ /**
33
+ * Renders and paints headers and footers as full HTML content.
34
+ * This provides Word/mPDF-like behavior where headers and footers
35
+ * are fully rendered HTML with complete styling support.
36
+ */
37
+ export async function paintHeaderFooter(
38
+ painter: PagePainter,
39
+ header: HeaderFooterVariant | undefined,
40
+ footer: HeaderFooterVariant | undefined,
41
+ tokens: Map<string, string | ((page: number, total: number) => string)>,
42
+ pageIndex: number,
43
+ totalPages: number,
44
+ baseOptions: TextPaintOptions = { fontSizePt: 10 },
45
+ under = false,
46
+ context?: HeaderFooterPaintContext,
47
+ ): Promise<void> {
48
+ void under;
49
+
50
+ // If we have full context, use the new HTML rendering path
51
+ if (context) {
52
+ log("layout", "debug", "Using HTML rendering path for headers/footers", {
53
+ hasHeader: !!header?.content,
54
+ hasFooter: !!footer?.content,
55
+ margins: context.margins,
56
+ });
57
+ await paintHeaderFooterWithContext(
58
+ painter,
59
+ header,
60
+ footer,
61
+ tokens,
62
+ pageIndex,
63
+ totalPages,
64
+ context,
65
+ );
66
+ return;
67
+ }
68
+
69
+ log("layout", "debug", "Using legacy text rendering for headers/footers (no context)");
70
+ // Fallback to legacy text-only rendering for backwards compatibility
71
+ await paintHeaderFooterLegacy(painter, header, footer, tokens, pageIndex, totalPages, baseOptions);
72
+ }
73
+
74
+ /**
75
+ * New HTML rendering path for headers/footers.
76
+ * Renders headers and footers as full HTML through the layout pipeline.
77
+ */
78
+ async function paintHeaderFooterWithContext(
79
+ painter: PagePainter,
80
+ header: HeaderFooterVariant | undefined,
81
+ footer: HeaderFooterVariant | undefined,
82
+ tokens: Map<string, string | ((page: number, total: number) => string)>,
83
+ pageIndex: number,
84
+ totalPages: number,
85
+ context: HeaderFooterPaintContext,
86
+ ): Promise<void> {
87
+ const { margins, pageWidthPx, pageHeightPx, fontRegistry, pageOffsetY, css } = context;
88
+
89
+ // Calculate content width (page width minus left and right margins)
90
+ const contentWidthPx = pageWidthPx - margins.left - margins.right;
91
+
92
+ log("layout", "debug", "paintHeaderFooterWithContext", {
93
+ headerContent: header?.content ? String(header.content).slice(0, 100) : "none",
94
+ footerContent: footer?.content ? String(footer.content).slice(0, 100) : "none",
95
+ contentWidthPx,
96
+ pageWidthPx,
97
+ pageHeightPx,
98
+ });
99
+
100
+ // Render and paint header
101
+ if (header?.content) {
102
+ const headerHtml = stringify(header.content);
103
+ log("layout", "debug", "Rendering header HTML", { headerHtml: headerHtml.slice(0, 200) });
104
+ if (headerHtml) {
105
+ try {
106
+ const rendered = await renderHeaderFooterHtml({
107
+ html: headerHtml,
108
+ css,
109
+ widthPx: contentWidthPx,
110
+ maxHeightPx: header.maxHeightPx,
111
+ tokens,
112
+ pageNumber: pageIndex,
113
+ totalPages,
114
+ environment: context.environment,
115
+ });
116
+
117
+ if (rendered) {
118
+ log("layout", "debug", "Header rendered successfully", { heightPx: rendered.heightPx });
119
+ // Header is positioned at the top margin area
120
+ await paintRenderedHeaderFooter(
121
+ painter,
122
+ rendered,
123
+ margins.left,
124
+ margins.top,
125
+ fontRegistry,
126
+ pageOffsetY,
127
+ );
128
+ }
129
+ } catch (err) {
130
+ log("layout", "warn", "Failed to render header HTML", { error: err });
131
+ }
132
+ }
133
+ }
134
+
135
+ // Render and paint footer
136
+ if (footer?.content) {
137
+ const footerHtml = stringify(footer.content);
138
+ if (footerHtml) {
139
+ try {
140
+ const rendered = await renderHeaderFooterHtml({
141
+ html: footerHtml,
142
+ css,
143
+ widthPx: contentWidthPx,
144
+ maxHeightPx: footer.maxHeightPx,
145
+ tokens,
146
+ pageNumber: pageIndex,
147
+ totalPages,
148
+ environment: context.environment,
149
+ });
150
+
151
+ if (rendered) {
152
+ // Footer is positioned at the bottom of the page
153
+ const footerY = pageHeightPx - margins.bottom - footer.maxHeightPx;
154
+ await paintRenderedHeaderFooter(
155
+ painter,
156
+ rendered,
157
+ margins.left,
158
+ footerY,
159
+ fontRegistry,
160
+ pageOffsetY,
161
+ );
162
+ }
163
+ } catch (err) {
164
+ log("layout", "warn", "Failed to render footer HTML", { error: err });
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Legacy text-only rendering for headers/footers.
172
+ * Used for backwards compatibility when context is not provided.
173
+ */
174
+ async function paintHeaderFooterLegacy(
175
+ painter: PagePainter,
176
+ header: HeaderFooterVariant | undefined,
177
+ footer: HeaderFooterVariant | undefined,
178
+ tokens: Map<string, string | ((page: number, total: number) => string)>,
179
+ pageIndex: number,
180
+ totalPages: number,
181
+ baseOptions: TextPaintOptions,
182
+ ): Promise<void> {
183
+ const headerText = header?.content ? stringify(header.content) : undefined;
184
+ const footerText = footer?.content ? stringify(footer.content) : undefined;
185
+
186
+ if (headerText) {
187
+ const rendered = applyPlaceholders(headerText, tokens, pageIndex, totalPages);
188
+ await painter.drawText(rendered, 16, header?.maxHeightPx ?? 24, { ...baseOptions, absolute: true });
189
+ }
190
+
191
+ if (footerText) {
192
+ const rendered = applyPlaceholders(footerText, tokens, pageIndex, totalPages);
193
+ const yPx = painter.pageHeightPx ? painter.pageHeightPx - ((footer?.maxHeightPx ?? 24) + 16) : 16;
194
+ await painter.drawText(rendered, 16, yPx, { ...baseOptions, absolute: true });
195
+ }
196
+ }
197
+
198
+ function stringify(content: unknown): string {
199
+ if (content == null) {
200
+ return "";
201
+ }
202
+ if (typeof content === "string") {
203
+ return content;
204
+ }
205
+ if (typeof content === "function") {
206
+ return String(content());
207
+ }
208
+ return JSON.stringify(content);
209
+ }
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Header/Footer Renderer
3
+ *
4
+ * This module provides Word/mPDF-like header and footer rendering.
5
+ * Headers and footers are rendered as full HTML through the same layout pipeline
6
+ * as the main content, giving them full styling capabilities.
7
+ *
8
+ * Key behaviors:
9
+ * - Headers are rendered at the top of each page (within top margin area)
10
+ * - Footers are rendered at the bottom of each page (within bottom margin area)
11
+ * - Main content area is automatically reduced to exclude header/footer space
12
+ * - Placeholders like {{pageNumber}}, {{totalPages}}, {{date}} are supported
13
+ */
14
+
15
+ import { parseHTML } from "linkedom";
16
+ import type { FontConfig } from "../types/fonts.js";
17
+ import { parseCss } from "../html/css/parse-css.js";
18
+ import { makeUnitParsers, type UnitCtx, pxToPt } from "../units/units.js";
19
+ import { LayoutNode } from "../dom/node.js";
20
+ import { ComputedStyle } from "../css/style.js";
21
+ import { layoutTree } from "../layout/pipeline/layout-tree.js";
22
+ import { buildRenderTree, type RenderTreeOptions } from "./layout-tree-builder.js";
23
+ import type { RenderBox, Rect, LayoutTree, RGBA, Run, TextPaintOptions } from "./types.js";
24
+ import { convertDomNode } from "../html/dom-converter.js";
25
+ import { computeStyleForElement } from "../css/compute-style.js";
26
+ import { Display } from "../css/enums.js";
27
+ import { log } from "../logging/debug.js";
28
+ import { offsetRenderTree } from "../render/offset.js";
29
+ import type { FontEmbedder } from "./font/embedder.js";
30
+ import { FontRegistryResolver } from "../fonts/font-registry-resolver.js";
31
+ import type { FontRegistry } from "./font/font-registry.js";
32
+ import { computeGlyphRun, applyWordSpacingToGlyphRun } from "./utils/node-text-run-factory.js";
33
+ import type { PagePainter } from "./page-painter.js";
34
+ import { paintBoxAtomic } from "./renderer/box-painter.js";
35
+ import { applyPlaceholders } from "./header-footer-tokens.js";
36
+ import type { Environment } from "../environment/environment.js";
37
+
38
+ export interface HeaderFooterRenderOptions {
39
+ /** The HTML content for the header/footer */
40
+ html: string;
41
+ /** Optional CSS to apply */
42
+ css?: string;
43
+ /** Width of the header/footer area in pixels */
44
+ widthPx: number;
45
+ /** Maximum height of the header/footer area in pixels */
46
+ maxHeightPx: number;
47
+ /** Font configuration for rendering */
48
+ fontConfig?: FontConfig;
49
+ /** Font embedder for PDF output */
50
+ fontEmbedder?: FontEmbedder | null;
51
+ /** Resource base directory for loading assets */
52
+ resourceBaseDir?: string;
53
+ /** Asset root directory */
54
+ assetRootDir?: string;
55
+ /** Tokens for placeholder replacement */
56
+ tokens?: Map<string, string | ((page: number, total: number) => string)>;
57
+ /** Current page number (1-indexed) */
58
+ pageNumber?: number;
59
+ /** Total number of pages */
60
+ totalPages?: number;
61
+ /** Platform environment (Node/browser) for resource loading */
62
+ environment?: Environment;
63
+ }
64
+
65
+ export interface RenderedHeaderFooter {
66
+ /** The render tree for the header/footer */
67
+ renderTree: LayoutTree;
68
+ /** The actual height of the rendered content in pixels */
69
+ heightPx: number;
70
+ /** The root render box */
71
+ root: RenderBox;
72
+ }
73
+
74
+ /**
75
+ * Renders header or footer HTML into a layout tree.
76
+ * This uses the same pipeline as the main content for full HTML/CSS support.
77
+ */
78
+ export async function renderHeaderFooterHtml(
79
+ options: HeaderFooterRenderOptions
80
+ ): Promise<RenderedHeaderFooter | null> {
81
+ const {
82
+ html,
83
+ css = "",
84
+ widthPx,
85
+ maxHeightPx,
86
+ fontEmbedder,
87
+ resourceBaseDir,
88
+ assetRootDir,
89
+ tokens,
90
+ pageNumber = 1,
91
+ totalPages = 1,
92
+ environment,
93
+ } = options;
94
+
95
+ const resolvedResourceBase = resourceBaseDir ?? "";
96
+ const resolvedAssetRoot = assetRootDir ?? resolvedResourceBase;
97
+
98
+ if (!html || !html.trim()) {
99
+ return null;
100
+ }
101
+
102
+ // Apply placeholder replacements
103
+ let processedHtml = html;
104
+ if (tokens) {
105
+ processedHtml = applyPlaceholders(html, tokens, pageNumber, totalPages);
106
+ }
107
+
108
+ // Wrap in a container if not already a full document
109
+ const normalizedHtml = normalizeHtmlFragment(processedHtml);
110
+
111
+ const unitCtx: UnitCtx = { viewport: { width: widthPx, height: maxHeightPx } };
112
+ const units = makeUnitParsers(unitCtx);
113
+
114
+ const { document } = parseHTML(normalizedHtml);
115
+
116
+ // Parse CSS
117
+ const { styleRules: cssRules } = parseCss(css);
118
+
119
+ // Get the root element
120
+ const rootElement = document.body || document.documentElement;
121
+
122
+ const baseParentStyle = new ComputedStyle();
123
+ const rootFontSize = baseParentStyle.fontSize;
124
+
125
+ let rootStyle = computeStyleForElement(rootElement, cssRules, baseParentStyle, units, rootFontSize);
126
+ if (isInlineDisplay(rootStyle.display)) {
127
+ rootStyle.display = Display.Block;
128
+ }
129
+
130
+ const rootLayout = new LayoutNode(rootStyle, [], { tagName: rootElement?.tagName?.toLowerCase() });
131
+
132
+ const conversionContext = { resourceBaseDir: resolvedResourceBase, assetRootDir: resolvedAssetRoot, units, rootFontSize, environment };
133
+
134
+ if (rootElement) {
135
+ for (const child of Array.from(rootElement.childNodes)) {
136
+ if (child.nodeType === child.ELEMENT_NODE) {
137
+ const tagName = (child as HTMLElement).tagName.toLowerCase();
138
+ if (tagName === "head" || tagName === "meta" || tagName === "title" || tagName === "link" || tagName === "script") {
139
+ continue;
140
+ }
141
+ }
142
+ const layoutChild = await convertDomNode(child, cssRules, rootStyle, conversionContext);
143
+ if (layoutChild) rootLayout.appendChild(layoutChild);
144
+ }
145
+ }
146
+
147
+ // Layout with constrained dimensions
148
+ layoutTree(rootLayout, { width: widthPx, height: maxHeightPx }, fontEmbedder ?? null);
149
+
150
+ const renderTree = buildRenderTree(rootLayout, {});
151
+
152
+ // Calculate actual height
153
+ const actualHeight = Math.min(calculateTreeHeight(renderTree.root), maxHeightPx);
154
+
155
+ log("layout", "debug", "Header/footer rendered", {
156
+ widthPx,
157
+ maxHeightPx,
158
+ actualHeight,
159
+ hasContent: renderTree.root.children.length > 0,
160
+ });
161
+
162
+ return {
163
+ renderTree,
164
+ heightPx: actualHeight,
165
+ root: renderTree.root,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Deep clones a render box tree for per-page rendering.
171
+ * This is needed because we apply offsets that are page-specific.
172
+ */
173
+ function cloneRenderBox(box: RenderBox): RenderBox {
174
+ const clonedRuns = box.textRuns.map((run) => ({
175
+ ...run,
176
+ lineMatrix: run.lineMatrix ? { ...run.lineMatrix } : undefined,
177
+ decorations: run.decorations ? { ...run.decorations } : undefined,
178
+ glyphs: run.glyphs ? { ...run.glyphs } : undefined,
179
+ })) as Run[];
180
+
181
+ const clonedBox: RenderBox = {
182
+ ...box,
183
+ contentBox: { ...box.contentBox },
184
+ paddingBox: { ...box.paddingBox },
185
+ borderBox: { ...box.borderBox },
186
+ visualOverflow: { ...box.visualOverflow },
187
+ padding: { ...box.padding },
188
+ border: { ...box.border },
189
+ borderRadius: {
190
+ topLeft: { ...box.borderRadius.topLeft },
191
+ topRight: { ...box.borderRadius.topRight },
192
+ bottomRight: { ...box.borderRadius.bottomRight },
193
+ bottomLeft: { ...box.borderRadius.bottomLeft },
194
+ },
195
+ background: box.background
196
+ ? {
197
+ color: box.background.color ? { ...box.background.color } : undefined,
198
+ image: box.background.image
199
+ ? {
200
+ ...box.background.image,
201
+ rect: { ...box.background.image.rect },
202
+ originRect: { ...box.background.image.originRect },
203
+ }
204
+ : undefined,
205
+ gradient: box.background.gradient
206
+ ? {
207
+ ...box.background.gradient,
208
+ rect: { ...box.background.gradient.rect },
209
+ originRect: { ...box.background.gradient.originRect },
210
+ }
211
+ : undefined,
212
+ }
213
+ : { color: undefined },
214
+ textRuns: clonedRuns,
215
+ markerRect: box.markerRect ? { ...box.markerRect } : undefined,
216
+ boxShadows: box.boxShadows.map((s) => ({ ...s, color: { ...s.color } })),
217
+ links: box.links.map((link) => ({
218
+ rect: { ...link.rect },
219
+ target: { ...link.target },
220
+ })),
221
+ children: box.children.map((child) => cloneRenderBox(child)),
222
+ };
223
+
224
+ return clonedBox;
225
+ }
226
+
227
+ /**
228
+ * Paints a rendered header/footer onto a page.
229
+ */
230
+ export async function paintRenderedHeaderFooter(
231
+ painter: PagePainter,
232
+ rendered: RenderedHeaderFooter,
233
+ xOffsetPx: number,
234
+ yOffsetPx: number,
235
+ fontRegistry: FontRegistry,
236
+ pageOffsetY: number,
237
+ ): Promise<void> {
238
+ // Clone the tree to avoid mutating the original
239
+ const clonedRoot = cloneRenderBox(rendered.root);
240
+
241
+ // Enrich text runs with glyph data
242
+ const fontResolver = new FontRegistryResolver(fontRegistry);
243
+ await enrichTreeWithGlyphRuns(clonedRoot, fontResolver);
244
+
245
+ // Offset the tree to the correct position
246
+ offsetRenderTree(clonedRoot, xOffsetPx, yOffsetPx - pageOffsetY, false);
247
+
248
+ // Paint all boxes
249
+ const stack: RenderBox[] = [clonedRoot];
250
+ while (stack.length > 0) {
251
+ const box = stack.pop()!;
252
+ await paintBoxAtomic(painter, box);
253
+ // Add children in reverse order to paint in correct order
254
+ for (let i = box.children.length - 1; i >= 0; i--) {
255
+ stack.push(box.children[i]);
256
+ }
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Calculates the configuration for page content area when headers/footers are present.
262
+ */
263
+ export interface PageContentAreaConfig {
264
+ /** Y offset where content should start (after header) */
265
+ contentStartY: number;
266
+ /** Available height for content (excluding header and footer) */
267
+ contentHeightPx: number;
268
+ /** Y position where footer should be placed */
269
+ footerY: number;
270
+ }
271
+
272
+ export function calculatePageContentArea(
273
+ pageHeightPx: number,
274
+ marginTop: number,
275
+ marginBottom: number,
276
+ headerHeightPx: number,
277
+ footerHeightPx: number,
278
+ ): PageContentAreaConfig {
279
+ // Header is placed at marginTop
280
+ // Content starts after header
281
+ const contentStartY = marginTop + headerHeightPx;
282
+
283
+ // Footer is placed at (pageHeight - marginBottom - footerHeight)
284
+ const footerY = pageHeightPx - marginBottom - footerHeightPx;
285
+
286
+ // Content height is the space between header and footer
287
+ const contentHeightPx = footerY - contentStartY;
288
+
289
+ return {
290
+ contentStartY,
291
+ contentHeightPx: Math.max(0, contentHeightPx),
292
+ footerY,
293
+ };
294
+ }
295
+
296
+ function normalizeHtmlFragment(html: string): string {
297
+ const hasHtmlTag = /<\s*html[\s>]/i.test(html);
298
+ if (hasHtmlTag) {
299
+ return html;
300
+ }
301
+ return `<!doctype html><html><head></head><body>${html}</body></html>`;
302
+ }
303
+
304
+ function isInlineDisplay(display: Display): boolean {
305
+ return (
306
+ display === Display.Inline ||
307
+ display === Display.InlineBlock ||
308
+ display === Display.InlineFlex ||
309
+ display === Display.InlineGrid ||
310
+ display === Display.InlineTable
311
+ );
312
+ }
313
+
314
+ function calculateTreeHeight(root: RenderBox): number {
315
+ let maxBottom = 0;
316
+
317
+ function traverse(box: RenderBox): void {
318
+ const bottom = box.borderBox.y + box.borderBox.height;
319
+ maxBottom = Math.max(maxBottom, bottom);
320
+ for (const child of box.children) {
321
+ traverse(child);
322
+ }
323
+ }
324
+
325
+ traverse(root);
326
+ return maxBottom;
327
+ }
328
+
329
+ async function enrichTreeWithGlyphRuns(root: RenderBox, fontResolver: FontRegistryResolver): Promise<void> {
330
+ async function enrichRun(run: Run): Promise<void> {
331
+ if (run.glyphs) {
332
+ return;
333
+ }
334
+ try {
335
+ const font = await fontResolver.resolve(run.fontFamily, run.fontWeight, run.fontStyle);
336
+ const letterSpacing = run.letterSpacing ?? 0;
337
+ const glyphRun = computeGlyphRun(font, run.text, run.fontSize, letterSpacing);
338
+ applyWordSpacingToGlyphRun(glyphRun, run.text, run.wordSpacing);
339
+ run.glyphs = glyphRun;
340
+ } catch {
341
+ // Ignore font resolution errors for headers/footers
342
+ }
343
+ }
344
+
345
+ async function traverse(box: RenderBox): Promise<void> {
346
+ if (box.textRuns && box.textRuns.length > 0) {
347
+ for (const run of box.textRuns) {
348
+ await enrichRun(run);
349
+ }
350
+ }
351
+ for (const child of box.children) {
352
+ await traverse(child);
353
+ }
354
+ }
355
+
356
+ await traverse(root);
357
+ }
@@ -0,0 +1,55 @@
1
+ import type { HeaderFooterHTML } from "./types.js";
2
+
3
+ export function computeHfTokens(
4
+ placeholders: HeaderFooterHTML["placeholders"],
5
+ _totalPages: number,
6
+ meta: { title?: string } = {},
7
+ ): Map<string, string | ((page: number, total: number) => string)> {
8
+ const tokens = new Map<string, string | ((page: number, total: number) => string)>();
9
+ for (const [key, value] of Object.entries(placeholders ?? {})) {
10
+ tokens.set(key, value);
11
+ }
12
+ // Support multiple naming conventions for page numbers
13
+ tokens.set("page", (page, _total) => String(page));
14
+ tokens.set("pageNumber", (page, _total) => String(page));
15
+ tokens.set("pages", (_page, total) => String(total));
16
+ tokens.set("totalPages", (_page, total) => String(total));
17
+ if (!tokens.has("title") && meta.title) {
18
+ tokens.set("title", meta.title);
19
+ }
20
+ tokens.set("date", () => new Date().toLocaleDateString());
21
+ return tokens;
22
+ }
23
+
24
+ export function applyPlaceholders(
25
+ template: string,
26
+ tokens: Map<string, string | ((page: number, total: number) => string)>,
27
+ pageIndex: number,
28
+ totalPages: number,
29
+ ): string {
30
+ // Support both {{key}} (double curly braces) and {key} (single curly braces) syntax
31
+ // First replace double braces, then single braces
32
+ let result = template.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
33
+ const entry = tokens.get(key.trim());
34
+ if (entry === undefined) {
35
+ return "";
36
+ }
37
+ if (typeof entry === "string") {
38
+ return entry;
39
+ }
40
+ return entry(pageIndex, totalPages);
41
+ });
42
+
43
+ result = result.replace(/\{([^}]+)\}/g, (_, key) => {
44
+ const entry = tokens.get(key.trim());
45
+ if (entry === undefined) {
46
+ return "";
47
+ }
48
+ if (typeof entry === "string") {
49
+ return entry;
50
+ }
51
+ return entry(pageIndex, totalPages);
52
+ });
53
+
54
+ return result;
55
+ }
@@ -0,0 +1,25 @@
1
+ export {
2
+ type HeaderFooterContext,
3
+ type HeaderFooterLayout,
4
+ type HeaderFooterVariant,
5
+ type HeaderFooterVariants,
6
+ initHeaderFooterContext,
7
+ layoutHeaderFooterTrees,
8
+ adjustPageBoxForHf,
9
+ pickHeaderVariant,
10
+ pickFooterVariant,
11
+ } from "./header-footer-layout.js";
12
+
13
+ export { computeHfTokens, applyPlaceholders } from "./header-footer-tokens.js";
14
+
15
+ export { paintHeaderFooter, type HeaderFooterPaintContext } from "./header-footer-painter.js";
16
+
17
+ export {
18
+ renderHeaderFooterHtml,
19
+ paintRenderedHeaderFooter,
20
+ calculatePageContentArea,
21
+ type HeaderFooterRenderOptions,
22
+ type RenderedHeaderFooter,
23
+ type PageContentAreaConfig,
24
+ } from "./header-footer-renderer.js";
25
+ export type { Environment as HeaderFooterEnvironment } from "../environment/environment.js";