pagyra-js 0.0.20 → 0.0.21

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 (286) hide show
  1. package/README.md +55 -0
  2. package/dist/assets/fonts/licenses/selawik/SIL Open Font License.txt +43 -0
  3. package/dist/assets/fonts/ttf/arimo/Arimo-Bold.ttf +0 -0
  4. package/dist/assets/fonts/ttf/arimo/Arimo-BoldItalic.ttf +0 -0
  5. package/dist/assets/fonts/ttf/arimo/Arimo-Italic.ttf +0 -0
  6. package/dist/assets/fonts/ttf/arimo/Arimo-Regular.ttf +0 -0
  7. package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Black.ttf +0 -0
  8. package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Bold.ttf +0 -0
  9. package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Regular.ttf +0 -0
  10. package/dist/assets/fonts/ttf/dejavu/DejaVuSans.ttf +0 -0
  11. package/dist/assets/fonts/ttf/firecode/FiraCode-Bold.ttf +0 -0
  12. package/dist/assets/fonts/ttf/firecode/FiraCode-Light.ttf +0 -0
  13. package/dist/assets/fonts/ttf/firecode/FiraCode-Medium.ttf +0 -0
  14. package/dist/assets/fonts/ttf/firecode/FiraCode-Regular.ttf +0 -0
  15. package/dist/assets/fonts/ttf/firecode/FiraCode-SemiBold.ttf +0 -0
  16. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Bold.ttf +0 -0
  17. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Light.ttf +0 -0
  18. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Medium.ttf +0 -0
  19. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Regular.ttf +0 -0
  20. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-SemiBold.ttf +0 -0
  21. package/dist/assets/fonts/ttf/notosans/NotoSans-Regular.ttf +0 -0
  22. package/dist/assets/fonts/ttf/roboto/Roboto-Bold.ttf +0 -0
  23. package/dist/assets/fonts/ttf/roboto/Roboto-BoldItalic.ttf +0 -0
  24. package/dist/assets/fonts/ttf/roboto/Roboto-Italic.ttf +0 -0
  25. package/dist/assets/fonts/ttf/roboto/Roboto-Regular.ttf +0 -0
  26. package/dist/assets/fonts/ttf/selawik/selawk.ttf +0 -0
  27. package/dist/assets/fonts/ttf/selawik/selawkb.ttf +0 -0
  28. package/dist/assets/fonts/ttf/selawik/selawkl.ttf +0 -0
  29. package/dist/assets/fonts/ttf/selawik/selawksb.ttf +0 -0
  30. package/dist/assets/fonts/ttf/selawik/selawksl.ttf +0 -0
  31. package/dist/assets/fonts/ttf/stixtwomath/STIXTwoMath-Regular.ttf +0 -0
  32. package/dist/assets/fonts/ttf/tinos/Tinos-Bold.ttf +0 -0
  33. package/dist/assets/fonts/ttf/tinos/Tinos-BoldItalic.ttf +0 -0
  34. package/dist/assets/fonts/ttf/tinos/Tinos-Italic.ttf +0 -0
  35. package/dist/assets/fonts/ttf/tinos/Tinos-Regular.ttf +0 -0
  36. package/dist/assets/fonts/woff/lato/lato-latin-400-italic.woff +0 -0
  37. package/dist/assets/fonts/woff/lato/lato-latin-400-normal.woff +0 -0
  38. package/dist/assets/fonts/woff/lato/lato-latin-700-italic.woff +0 -0
  39. package/dist/assets/fonts/woff/lato/lato-latin-700-normal.woff +0 -0
  40. package/dist/assets/fonts/woff2/caveat/Caveat-Bold.woff2 +0 -0
  41. package/dist/assets/fonts/woff2/caveat/Caveat-Regular.woff2 +0 -0
  42. package/dist/assets/fonts/woff2/lato/lato-latin-400-italic.woff2 +0 -0
  43. package/dist/assets/fonts/woff2/lato/lato-latin-400-normal.woff2 +0 -0
  44. package/dist/assets/fonts/woff2/lato/lato-latin-700-italic.woff2 +0 -0
  45. package/dist/assets/fonts/woff2/lato/lato-latin-700-normal.woff2 +0 -0
  46. package/dist/browser/pagyra.min.js +34 -34
  47. package/dist/browser/pagyra.min.js.map +4 -4
  48. package/dist/playground/server.js +2 -0
  49. package/dist/src/css/compute-style/base-options.d.ts +7 -0
  50. package/dist/src/css/compute-style/base-options.js +24 -0
  51. package/dist/src/css/compute-style/declarations.d.ts +10 -0
  52. package/dist/src/css/compute-style/declarations.js +77 -0
  53. package/dist/src/css/compute-style/decoration.d.ts +8 -0
  54. package/dist/src/css/compute-style/decoration.js +55 -0
  55. package/dist/src/css/compute-style/defaults.d.ts +3 -0
  56. package/dist/src/css/compute-style/defaults.js +34 -0
  57. package/dist/src/css/compute-style/display.d.ts +3 -0
  58. package/dist/src/css/compute-style/display.js +85 -0
  59. package/dist/src/css/compute-style/float.d.ts +2 -0
  60. package/dist/src/css/compute-style/float.js +13 -0
  61. package/dist/src/css/compute-style/font.d.ts +12 -0
  62. package/dist/src/css/compute-style/font.js +57 -0
  63. package/dist/src/css/compute-style/overrides.d.ts +3 -0
  64. package/dist/src/css/compute-style/overrides.js +241 -0
  65. package/dist/src/css/compute-style.d.ts +2 -0
  66. package/dist/src/css/compute-style.js +34 -487
  67. package/dist/src/css/enums.d.ts +4 -0
  68. package/dist/src/css/enums.js +5 -0
  69. package/dist/src/css/layout-property-resolver.js +30 -18
  70. package/dist/src/css/length.d.ts +26 -2
  71. package/dist/src/css/length.js +48 -0
  72. package/dist/src/css/parsers/background-parser.js +1 -1
  73. package/dist/src/css/parsers/calc-parser.d.ts +2 -0
  74. package/dist/src/css/parsers/calc-parser.js +310 -0
  75. package/dist/src/css/parsers/content-parser.d.ts +2 -1
  76. package/dist/src/css/parsers/content-parser.js +7 -2
  77. package/dist/src/css/parsers/dimension-parser.js +37 -18
  78. package/dist/src/css/parsers/display-flex-parser.d.ts +4 -0
  79. package/dist/src/css/parsers/display-flex-parser.js +97 -0
  80. package/dist/src/css/parsers/filter-parser.d.ts +14 -0
  81. package/dist/src/css/parsers/filter-parser.js +255 -0
  82. package/dist/src/css/parsers/grid-parser-extended.d.ts +1 -0
  83. package/dist/src/css/parsers/grid-parser-extended.js +40 -1
  84. package/dist/src/css/parsers/grid-parser.d.ts +5 -2
  85. package/dist/src/css/parsers/grid-parser.js +71 -7
  86. package/dist/src/css/parsers/length-parser.d.ts +8 -3
  87. package/dist/src/css/parsers/length-parser.js +45 -2
  88. package/dist/src/css/parsers/margin-block-parser.js +3 -3
  89. package/dist/src/css/parsers/margin-parser.js +3 -3
  90. package/dist/src/css/parsers/padding-block-parser.js +3 -3
  91. package/dist/src/css/parsers/padding-inline-parser.js +3 -3
  92. package/dist/src/css/parsers/padding-parser.js +6 -6
  93. package/dist/src/css/parsers/position-parser.js +2 -22
  94. package/dist/src/css/parsers/register-parsers.js +29 -2
  95. package/dist/src/css/parsers/word-break-parser.d.ts +2 -0
  96. package/dist/src/css/parsers/word-break-parser.js +23 -0
  97. package/dist/src/css/properties/grid.d.ts +16 -2
  98. package/dist/src/css/properties/layout.d.ts +3 -1
  99. package/dist/src/css/properties/layout.js +1 -1
  100. package/dist/src/css/properties/misc.d.ts +5 -0
  101. package/dist/src/css/properties/typography.d.ts +3 -0
  102. package/dist/src/css/properties/visual.d.ts +36 -0
  103. package/dist/src/css/shorthands/box-shorthand.d.ts +2 -2
  104. package/dist/src/css/style-inheritance.d.ts +2 -1
  105. package/dist/src/css/style-inheritance.js +1 -0
  106. package/dist/src/css/style.d.ts +30 -10
  107. package/dist/src/css/style.js +8 -1
  108. package/dist/src/css/ua-defaults/base-defaults.d.ts +1 -0
  109. package/dist/src/css/ua-defaults/base-defaults.js +10 -1
  110. package/dist/src/css/ua-defaults/element-defaults.js +0 -2
  111. package/dist/src/html/css/parse-css.d.ts +2 -0
  112. package/dist/src/html/css/parse-css.js +32 -3
  113. package/dist/src/html/dom-converter/background-images.d.ts +3 -0
  114. package/dist/src/html/dom-converter/background-images.js +88 -0
  115. package/dist/src/html/dom-converter/convert-dom-node.d.ts +5 -0
  116. package/dist/src/html/dom-converter/convert-dom-node.js +81 -0
  117. package/dist/src/html/dom-converter/handlers/br-handler.d.ts +2 -0
  118. package/dist/src/html/dom-converter/handlers/br-handler.js +20 -0
  119. package/dist/src/html/dom-converter/handlers/form-control-handler.d.ts +2 -0
  120. package/dist/src/html/dom-converter/handlers/form-control-handler.js +28 -0
  121. package/dist/src/html/dom-converter/handlers/img-handler.d.ts +2 -0
  122. package/dist/src/html/dom-converter/handlers/img-handler.js +4 -0
  123. package/dist/src/html/dom-converter/handlers/index.d.ts +4 -0
  124. package/dist/src/html/dom-converter/handlers/index.js +19 -0
  125. package/dist/src/html/dom-converter/handlers/svg-handler.d.ts +2 -0
  126. package/dist/src/html/dom-converter/handlers/svg-handler.js +32 -0
  127. package/dist/src/html/dom-converter/handlers/types.d.ts +12 -0
  128. package/dist/src/html/dom-converter/handlers/types.js +2 -0
  129. package/dist/src/html/dom-converter/helpers.d.ts +7 -0
  130. package/dist/src/html/dom-converter/helpers.js +35 -0
  131. package/dist/src/html/dom-converter/index.d.ts +1 -0
  132. package/dist/src/html/dom-converter/index.js +1 -0
  133. package/dist/src/html/dom-converter/pseudo-elements.d.ts +6 -0
  134. package/dist/src/html/dom-converter/pseudo-elements.js +48 -0
  135. package/dist/src/html/dom-converter/text.d.ts +15 -0
  136. package/dist/src/html/dom-converter/text.js +170 -0
  137. package/dist/src/html/dom-converter.d.ts +1 -5
  138. package/dist/src/html/dom-converter.js +1 -417
  139. package/dist/src/html/image-converter.d.ts +5 -0
  140. package/dist/src/html-to-pdf/document-css.d.ts +14 -0
  141. package/dist/src/html-to-pdf/document-css.js +45 -0
  142. package/dist/src/html-to-pdf/fonts.d.ts +16 -0
  143. package/dist/src/html-to-pdf/fonts.js +74 -0
  144. package/dist/src/html-to-pdf/header-footer.d.ts +14 -0
  145. package/dist/src/html-to-pdf/header-footer.js +101 -0
  146. package/dist/src/html-to-pdf/html-parser.d.ts +6 -0
  147. package/dist/src/html-to-pdf/html-parser.js +81 -0
  148. package/dist/src/html-to-pdf/index.d.ts +3 -0
  149. package/dist/src/html-to-pdf/index.js +2 -0
  150. package/dist/src/html-to-pdf/layout-build.d.ts +37 -0
  151. package/dist/src/html-to-pdf/layout-build.js +73 -0
  152. package/dist/src/html-to-pdf/prepare-html-render.d.ts +2 -0
  153. package/dist/src/html-to-pdf/prepare-html-render.js +121 -0
  154. package/dist/src/html-to-pdf/render-finalize.d.ts +15 -0
  155. package/dist/src/html-to-pdf/render-finalize.js +27 -0
  156. package/dist/src/html-to-pdf/render-html-to-pdf.d.ts +3 -0
  157. package/dist/src/html-to-pdf/render-html-to-pdf.js +25 -0
  158. package/dist/src/html-to-pdf/resource-loader.d.ts +6 -0
  159. package/dist/src/html-to-pdf/resource-loader.js +120 -0
  160. package/dist/src/html-to-pdf/types.d.ts +38 -0
  161. package/dist/src/html-to-pdf/types.js +2 -0
  162. package/dist/src/html-to-pdf.d.ts +1 -37
  163. package/dist/src/html-to-pdf.js +1 -537
  164. package/dist/src/image/js-png-backend.d.ts +7 -0
  165. package/dist/src/image/js-png-backend.js +9 -0
  166. package/dist/src/image/png-backend.d.ts +5 -0
  167. package/dist/src/image/png-backend.js +1 -0
  168. package/dist/src/image/png-wasm-loader.d.ts +5 -0
  169. package/dist/src/image/png-wasm-loader.js +59 -0
  170. package/dist/src/image/wasm/png_decoder_wasm.d.ts +8 -0
  171. package/dist/src/image/wasm/png_decoder_wasm.js +24 -0
  172. package/dist/src/image/wasm/png_decoder_wasm_bg.js +16 -0
  173. package/dist/src/image/wasm-png-backend.d.ts +6 -0
  174. package/dist/src/image/wasm-png-backend.js +17 -0
  175. package/dist/src/layout/counter.d.ts +1 -2
  176. package/dist/src/layout/counter.js +18 -18
  177. package/dist/src/layout/inline/inline-utils.d.ts +1 -1
  178. package/dist/src/layout/inline/inline-utils.js +8 -7
  179. package/dist/src/layout/inline/layout.js +16 -3
  180. package/dist/src/layout/inline/run-placer.d.ts +1 -0
  181. package/dist/src/layout/inline/run-placer.js +2 -10
  182. package/dist/src/layout/pipeline/out-of-flow-manager.js +25 -1
  183. package/dist/src/layout/strategies/block.js +35 -24
  184. package/dist/src/layout/strategies/flex.js +305 -61
  185. package/dist/src/layout/strategies/form.d.ts +2 -0
  186. package/dist/src/layout/strategies/form.js +38 -13
  187. package/dist/src/layout/strategies/grid.js +166 -29
  188. package/dist/src/layout/strategies/image.js +53 -27
  189. package/dist/src/layout/strategies/inline.js +26 -21
  190. package/dist/src/layout/strategies/table.js +26 -18
  191. package/dist/src/layout/utils/content-measurer.d.ts +1 -1
  192. package/dist/src/layout/utils/content-measurer.js +8 -7
  193. package/dist/src/layout/utils/floats.d.ts +1 -0
  194. package/dist/src/layout/utils/floats.js +14 -12
  195. package/dist/src/layout/utils/margin.d.ts +4 -4
  196. package/dist/src/layout/utils/margin.js +20 -16
  197. package/dist/src/layout/utils/node-math.d.ts +12 -6
  198. package/dist/src/layout/utils/node-math.js +71 -41
  199. package/dist/src/layout/utils/sizing.js +2 -1
  200. package/dist/src/pdf/font-subset/font-registry.d.ts +6 -0
  201. package/dist/src/pdf/font-subset/font-registry.js +30 -2
  202. package/dist/src/pdf/header-footer-renderer.js +12 -1
  203. package/dist/src/pdf/layout-tree-builder.js +5 -1
  204. package/dist/src/pdf/page-painter.js +13 -0
  205. package/dist/src/pdf/pagination.js +2 -2
  206. package/dist/src/pdf/renderer/box-painter.js +28 -3
  207. package/dist/src/pdf/renderer/page-paint.js +11 -3
  208. package/dist/src/pdf/renderers/radius-utils.js +31 -38
  209. package/dist/src/pdf/renderers/shape-renderer.js +1 -1
  210. package/dist/src/pdf/renderers/shape-utils.js +1 -1
  211. package/dist/src/pdf/renderers/text-renderer.d.ts +9 -1
  212. package/dist/src/pdf/renderers/text-renderer.js +36 -2
  213. package/dist/src/pdf/stacking/build-stacking-contexts.js +1 -2
  214. package/dist/src/pdf/stacking/resolve-paint-order.d.ts +5 -6
  215. package/dist/src/pdf/stacking/resolve-paint-order.js +29 -9
  216. package/dist/src/pdf/stacking/types.d.ts +14 -0
  217. package/dist/src/pdf/svg/shape-renderer.js +47 -20
  218. package/dist/src/pdf/types.d.ts +7 -1
  219. package/dist/src/pdf/utils/border-radius-utils.js +31 -38
  220. package/dist/src/pdf/utils/color-utils.js +17 -2
  221. package/dist/src/pdf/utils/filter-utils.d.ts +29 -0
  222. package/dist/src/pdf/utils/filter-utils.js +85 -0
  223. package/dist/src/pdf/utils/node-text-run-factory.js +1 -1
  224. package/dist/src/pdf/utils/text-layout-adjuster.d.ts +0 -8
  225. package/dist/src/pdf/utils/text-layout-adjuster.js +12 -9
  226. package/dist/src/units/units.d.ts +1 -1
  227. package/dist/tests/css/box-sizing.spec.d.ts +1 -0
  228. package/dist/tests/css/box-sizing.spec.js +46 -0
  229. package/dist/tests/css/calc-parser.spec.d.ts +1 -0
  230. package/dist/tests/css/calc-parser.spec.js +68 -0
  231. package/dist/tests/css/container-query-units.spec.d.ts +1 -0
  232. package/dist/tests/css/container-query-units.spec.js +64 -0
  233. package/dist/tests/css/content-parser.spec.js +13 -0
  234. package/dist/tests/css/filter-parser.spec.d.ts +1 -0
  235. package/dist/tests/css/filter-parser.spec.js +116 -0
  236. package/dist/tests/css/flex-shorthand.spec.d.ts +1 -0
  237. package/dist/tests/css/flex-shorthand.spec.js +45 -0
  238. package/dist/tests/css/grid-clamp.spec.d.ts +1 -0
  239. package/dist/tests/css/grid-clamp.spec.js +82 -0
  240. package/dist/tests/css/parse-css-pseudo.spec.d.ts +1 -0
  241. package/dist/tests/css/parse-css-pseudo.spec.js +26 -0
  242. package/dist/tests/helpers/render-utils.d.ts +18 -2
  243. package/dist/tests/helpers/render-utils.js +25 -12
  244. package/dist/tests/html/dom-converter-pseudo-elements.spec.d.ts +1 -0
  245. package/dist/tests/html/dom-converter-pseudo-elements.spec.js +33 -0
  246. package/dist/tests/html/dom-converter-text.spec.d.ts +1 -0
  247. package/dist/tests/html/dom-converter-text.spec.js +67 -0
  248. package/dist/tests/image/png-backend.spec.d.ts +1 -0
  249. package/dist/tests/image/png-backend.spec.js +34 -0
  250. package/dist/tests/layout/box-sizing.spec.d.ts +1 -0
  251. package/dist/tests/layout/box-sizing.spec.js +75 -0
  252. package/dist/tests/layout/calc-padding.spec.d.ts +1 -0
  253. package/dist/tests/layout/calc-padding.spec.js +19 -0
  254. package/dist/tests/layout/container-query-units.spec.d.ts +1 -0
  255. package/dist/tests/layout/container-query-units.spec.js +24 -0
  256. package/dist/tests/layout/flex-auto-height.spec.d.ts +1 -0
  257. package/dist/tests/layout/flex-auto-height.spec.js +35 -0
  258. package/dist/tests/layout/flex-wrap-cards.spec.d.ts +1 -0
  259. package/dist/tests/layout/flex-wrap-cards.spec.js +16 -0
  260. package/dist/tests/layout/flex-wrap-grow-align-content.spec.d.ts +1 -0
  261. package/dist/tests/layout/flex-wrap-grow-align-content.spec.js +20 -0
  262. package/dist/tests/layout/grid-clamp-gap.spec.d.ts +1 -0
  263. package/dist/tests/layout/grid-clamp-gap.spec.js +22 -0
  264. package/dist/tests/layout/inline-fragments.spec.js +38 -0
  265. package/dist/tests/layout/paged-body-margin.spec.d.ts +1 -0
  266. package/dist/tests/layout/paged-body-margin.spec.js +92 -0
  267. package/dist/tests/layout/pseudo-counters-generated-content.spec.d.ts +1 -0
  268. package/dist/tests/layout/pseudo-counters-generated-content.spec.js +51 -0
  269. package/dist/tests/layout/responsive-clamp-grid-parity.spec.d.ts +1 -0
  270. package/dist/tests/layout/responsive-clamp-grid-parity.spec.js +75 -0
  271. package/dist/tests/layout/run-placer-baseline.spec.js +13 -11
  272. package/dist/tests/pdf/backdrop-filter-noop.spec.d.ts +1 -0
  273. package/dist/tests/pdf/backdrop-filter-noop.spec.js +140 -0
  274. package/dist/tests/pdf/filter-drop-shadow.spec.d.ts +1 -0
  275. package/dist/tests/pdf/filter-drop-shadow.spec.js +74 -0
  276. package/dist/tests/pdf/filter-opacity.spec.d.ts +1 -0
  277. package/dist/tests/pdf/filter-opacity.spec.js +30 -0
  278. package/dist/tests/pdf/font-subset-registry-key.spec.d.ts +1 -0
  279. package/dist/tests/pdf/font-subset-registry-key.spec.js +66 -0
  280. package/dist/tests/pdf/selawik-opt-in.spec.d.ts +1 -0
  281. package/dist/tests/pdf/selawik-opt-in.spec.js +106 -0
  282. package/dist/tests/pdf/system-ui-fallback-subset-regression.spec.d.ts +1 -0
  283. package/dist/tests/pdf/system-ui-fallback-subset-regression.spec.js +39 -0
  284. package/dist/tests/pdf/text-renderer-fallback.spec.js +55 -0
  285. package/dist/tests/pdf/text-transform-matrix.spec.js +8 -7
  286. package/package.json +2 -2
@@ -0,0 +1,101 @@
1
+ import { log } from "../logging/debug.js";
2
+ import { renderHeaderFooterHtml } from "../pdf/header-footer-renderer.js";
3
+ const AUTO_HEADER_FOOTER_FALLBACK_PX = 64;
4
+ export async function resolveHeaderFooterMaxHeights(options) {
5
+ const { headerFooter } = options;
6
+ if (!headerFooter) {
7
+ return undefined;
8
+ }
9
+ const resolved = { ...headerFooter };
10
+ const contentWidthPx = Math.max(1, options.pageWidthPx - options.margins.left - options.margins.right);
11
+ const measurementMaxHeightPx = Math.max(1, options.pageHeightPx - options.margins.top - options.margins.bottom);
12
+ if (!hasOwnProperty(headerFooter, "maxHeaderHeightPx")) {
13
+ const headerCandidates = collectHeaderVariants(headerFooter);
14
+ if (headerCandidates.length > 0) {
15
+ const measured = await measureMaxHeaderFooterVariantHeight({
16
+ variants: headerCandidates,
17
+ widthPx: contentWidthPx,
18
+ maxHeightPx: measurementMaxHeightPx,
19
+ resourceBaseDir: options.resourceBaseDir,
20
+ assetRootDir: options.assetRootDir,
21
+ environment: options.environment,
22
+ });
23
+ resolved.maxHeaderHeightPx = measured > 0 ? measured : AUTO_HEADER_FOOTER_FALLBACK_PX;
24
+ }
25
+ }
26
+ if (!hasOwnProperty(headerFooter, "maxFooterHeightPx")) {
27
+ const footerCandidates = collectFooterVariants(headerFooter);
28
+ if (footerCandidates.length > 0) {
29
+ const measured = await measureMaxHeaderFooterVariantHeight({
30
+ variants: footerCandidates,
31
+ widthPx: contentWidthPx,
32
+ maxHeightPx: measurementMaxHeightPx,
33
+ resourceBaseDir: options.resourceBaseDir,
34
+ assetRootDir: options.assetRootDir,
35
+ environment: options.environment,
36
+ });
37
+ resolved.maxFooterHeightPx = measured > 0 ? measured : AUTO_HEADER_FOOTER_FALLBACK_PX;
38
+ }
39
+ }
40
+ return resolved;
41
+ }
42
+ async function measureMaxHeaderFooterVariantHeight(options) {
43
+ const heights = await Promise.all(options.variants.map(async (html) => {
44
+ try {
45
+ const rendered = await renderHeaderFooterHtml({
46
+ html,
47
+ widthPx: options.widthPx,
48
+ maxHeightPx: options.maxHeightPx,
49
+ resourceBaseDir: options.resourceBaseDir,
50
+ assetRootDir: options.assetRootDir,
51
+ environment: options.environment,
52
+ });
53
+ return rendered ? Math.ceil(rendered.heightPx) : 0;
54
+ }
55
+ catch (error) {
56
+ log("layout", "warn", "Failed to auto-measure header/footer variant height", {
57
+ error: error instanceof Error ? error.message : String(error),
58
+ });
59
+ return 0;
60
+ }
61
+ }));
62
+ return Math.max(0, ...heights);
63
+ }
64
+ function collectHeaderVariants(headerFooter) {
65
+ const values = [
66
+ headerFooter.headerHtml,
67
+ headerFooter.headerFirstHtml,
68
+ headerFooter.headerEvenHtml,
69
+ headerFooter.headerOddHtml,
70
+ ];
71
+ return values.map(stringifyHeaderFooterContent).filter((value) => value.length > 0);
72
+ }
73
+ function collectFooterVariants(headerFooter) {
74
+ const values = [
75
+ headerFooter.footerHtml,
76
+ headerFooter.footerFirstHtml,
77
+ headerFooter.footerEvenHtml,
78
+ headerFooter.footerOddHtml,
79
+ ];
80
+ return values.map(stringifyHeaderFooterContent).filter((value) => value.length > 0);
81
+ }
82
+ function stringifyHeaderFooterContent(content) {
83
+ if (content == null) {
84
+ return "";
85
+ }
86
+ if (typeof content === "string") {
87
+ return content;
88
+ }
89
+ if (typeof content === "function") {
90
+ try {
91
+ return String(content());
92
+ }
93
+ catch {
94
+ return "";
95
+ }
96
+ }
97
+ return String(content);
98
+ }
99
+ function hasOwnProperty(target, key) {
100
+ return Object.prototype.hasOwnProperty.call(target, key);
101
+ }
@@ -0,0 +1,6 @@
1
+ export declare function normalizeHtmlInput(html: string): string;
2
+ export declare function wrapHtml(html: string): string;
3
+ export declare function parseDocument(html: string): Document | undefined;
4
+ export declare function needsReparse(document: Document | undefined): boolean;
5
+ export declare function selectContentRoot(document: Document): Element | null;
6
+ export declare function shouldSkipContentNode(node: ChildNode): boolean;
@@ -0,0 +1,81 @@
1
+ import { parseHTML } from "linkedom";
2
+ export function normalizeHtmlInput(html) {
3
+ const hasHtmlTag = /<\s*html[\s>]/i.test(html);
4
+ if (hasHtmlTag) {
5
+ return html;
6
+ }
7
+ return wrapHtml(html);
8
+ }
9
+ export function wrapHtml(html) {
10
+ return `<!doctype html><html><head></head><body>${html}</body></html>`;
11
+ }
12
+ export function parseDocument(html) {
13
+ const parsed = parseHTML(html);
14
+ if (!parsed || typeof parsed !== "object") {
15
+ return undefined;
16
+ }
17
+ if (isDocumentLike(parsed)) {
18
+ return parsed;
19
+ }
20
+ let maybeDocument;
21
+ try {
22
+ maybeDocument = parsed.document;
23
+ }
24
+ catch {
25
+ maybeDocument = undefined;
26
+ }
27
+ if (isDocumentLike(maybeDocument)) {
28
+ return maybeDocument;
29
+ }
30
+ return undefined;
31
+ }
32
+ export function needsReparse(document) {
33
+ if (!document)
34
+ return true;
35
+ let docEl;
36
+ try {
37
+ docEl = document.documentElement?.tagName;
38
+ }
39
+ catch {
40
+ return true;
41
+ }
42
+ const docIsHtml = docEl?.toUpperCase() === "HTML";
43
+ if (!docIsHtml)
44
+ return true;
45
+ try {
46
+ if (!document.body)
47
+ return true;
48
+ }
49
+ catch {
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ export function selectContentRoot(document) {
55
+ const rootElement = document.body;
56
+ let processChildrenOf = rootElement;
57
+ if (rootElement && rootElement.childNodes.length === 0) {
58
+ const meaningfulChildren = Array.from(document.documentElement.childNodes).filter((node) => {
59
+ return node.nodeType === node.ELEMENT_NODE && node.tagName !== "HEAD";
60
+ });
61
+ if (meaningfulChildren.length > 0) {
62
+ processChildrenOf = document.documentElement;
63
+ }
64
+ }
65
+ else if (!rootElement) {
66
+ processChildrenOf = document.documentElement;
67
+ }
68
+ return processChildrenOf;
69
+ }
70
+ export function shouldSkipContentNode(node) {
71
+ if (node.nodeType !== node.ELEMENT_NODE) {
72
+ return false;
73
+ }
74
+ const tagName = node.tagName.toLowerCase();
75
+ return tagName === "head" || tagName === "meta" || tagName === "title" || tagName === "link" || tagName === "script";
76
+ }
77
+ function isDocumentLike(value) {
78
+ if (!value || typeof value !== "object")
79
+ return false;
80
+ return "querySelectorAll" in value && "childNodes" in value;
81
+ }
@@ -0,0 +1,3 @@
1
+ export type { PreparedRender, RenderHtmlOptions, PagedBodyMarginMode, InterBlockWhitespaceMode, } from "./types.js";
2
+ export { prepareHtmlRender } from "./prepare-html-render.js";
3
+ export { renderHtmlToPdf, renderHtmlToPdfBase64 } from "./render-html-to-pdf.js";
@@ -0,0 +1,2 @@
1
+ export { prepareHtmlRender } from "./prepare-html-render.js";
2
+ export { renderHtmlToPdf, renderHtmlToPdfBase64 } from "./render-html-to-pdf.js";
@@ -0,0 +1,37 @@
1
+ import { ComputedStyle } from "../css/style.js";
2
+ import { LayoutNode } from "../dom/node.js";
3
+ import type { CssRuleEntry } from "../html/css/parse-css.js";
4
+ import type { ConversionContext } from "../html/image-converter.js";
5
+ import type { UnitParsers } from "../units/units.js";
6
+ import type { Environment } from "../environment/environment.js";
7
+ import type { InterBlockWhitespaceMode, PagedBodyMarginMode } from "./types.js";
8
+ interface BuildRootLayoutContextOptions {
9
+ document: Document;
10
+ cssRules: CssRuleEntry[];
11
+ units: UnitParsers;
12
+ pagedBodyMargin?: PagedBodyMarginMode;
13
+ }
14
+ export declare function buildRootLayoutContext(options: BuildRootLayoutContextOptions): {
15
+ processChildrenOf: Element | null;
16
+ rootStyle: ComputedStyle;
17
+ rootLayout: LayoutNode;
18
+ rootFontSize: number;
19
+ };
20
+ interface CreateDomConversionContextOptions {
21
+ resourceBaseDir: string;
22
+ assetRootDir: string;
23
+ units: UnitParsers;
24
+ rootFontSize: number;
25
+ environment: Environment;
26
+ interBlockWhitespace?: InterBlockWhitespaceMode;
27
+ }
28
+ export declare function createDomConversionContext(options: CreateDomConversionContextOptions): ConversionContext;
29
+ interface AppendConvertedChildrenOptions {
30
+ processChildrenOf: Element | null;
31
+ rootLayout: LayoutNode;
32
+ cssRules: CssRuleEntry[];
33
+ rootStyle: ComputedStyle;
34
+ conversionContext: ConversionContext;
35
+ }
36
+ export declare function appendConvertedChildren(options: AppendConvertedChildrenOptions): Promise<void>;
37
+ export {};
@@ -0,0 +1,73 @@
1
+ import { ComputedStyle } from "../css/style.js";
2
+ import { computeStyleForElement } from "../css/compute-style.js";
3
+ import { Display } from "../css/enums.js";
4
+ import { LayoutNode } from "../dom/node.js";
5
+ import { convertDomNode } from "../html/dom-converter.js";
6
+ import { createCounterContext } from "../layout/counter.js";
7
+ import { log } from "../logging/debug.js";
8
+ import { selectContentRoot, shouldSkipContentNode } from "./html-parser.js";
9
+ export function buildRootLayoutContext(options) {
10
+ const processChildrenOf = selectContentRoot(options.document);
11
+ const baseParentStyle = new ComputedStyle();
12
+ const htmlElement = options.document.documentElement;
13
+ const documentElementStyle = htmlElement
14
+ ? computeStyleForElement(htmlElement, options.cssRules, baseParentStyle, options.units, baseParentStyle.fontSize)
15
+ : baseParentStyle;
16
+ const rootFontSize = documentElementStyle.fontSize;
17
+ let rootStyle;
18
+ if (!processChildrenOf || processChildrenOf === htmlElement) {
19
+ rootStyle = documentElementStyle;
20
+ }
21
+ else {
22
+ rootStyle = computeStyleForElement(processChildrenOf, options.cssRules, documentElementStyle, options.units, rootFontSize);
23
+ }
24
+ if (isInlineDisplay(rootStyle.display)) {
25
+ rootStyle.display = Display.Block;
26
+ }
27
+ if (options.pagedBodyMargin === "zero" && processChildrenOf?.tagName?.toLowerCase() === "body") {
28
+ rootStyle.marginTop = 0;
29
+ rootStyle.marginRight = 0;
30
+ rootStyle.marginBottom = 0;
31
+ rootStyle.marginLeft = 0;
32
+ }
33
+ const rootLayout = new LayoutNode(rootStyle, [], { tagName: processChildrenOf?.tagName?.toLowerCase() });
34
+ return { processChildrenOf, rootStyle, rootLayout, rootFontSize };
35
+ }
36
+ export function createDomConversionContext(options) {
37
+ const counterContext = createCounterContext();
38
+ const rootCounterScopeId = counterContext.registerScope(null);
39
+ return {
40
+ resourceBaseDir: options.resourceBaseDir,
41
+ assetRootDir: options.assetRootDir,
42
+ units: options.units,
43
+ rootFontSize: options.rootFontSize,
44
+ environment: options.environment,
45
+ interBlockWhitespace: options.interBlockWhitespace ?? "collapse",
46
+ counterContext,
47
+ rootCounterScopeId,
48
+ };
49
+ }
50
+ export async function appendConvertedChildren(options) {
51
+ const { processChildrenOf, rootLayout, cssRules, rootStyle, conversionContext } = options;
52
+ if (!processChildrenOf) {
53
+ return;
54
+ }
55
+ log("html-to-pdf", "debug", `prepareHtmlRender - processing children of: ${processChildrenOf.tagName}, count: ${processChildrenOf.childNodes.length}`);
56
+ for (const child of Array.from(processChildrenOf.childNodes)) {
57
+ const childTagName = child.nodeType === child.ELEMENT_NODE ? child.tagName : "text node";
58
+ log("html-to-pdf", "debug", `prepareHtmlRender - processing child: ${childTagName}, type: ${child.nodeType}`);
59
+ if (shouldSkipContentNode(child)) {
60
+ continue;
61
+ }
62
+ const layoutChild = await convertDomNode(child, cssRules, rootStyle, conversionContext);
63
+ if (layoutChild)
64
+ rootLayout.appendChild(layoutChild);
65
+ }
66
+ }
67
+ function isInlineDisplay(display) {
68
+ return (display === Display.Inline ||
69
+ display === Display.InlineBlock ||
70
+ display === Display.InlineFlex ||
71
+ display === Display.InlineGrid ||
72
+ display === Display.InlineTable);
73
+ }
@@ -0,0 +1,2 @@
1
+ import type { PreparedRender, RenderHtmlOptions } from "./types.js";
2
+ export declare function prepareHtmlRender(options: RenderHtmlOptions): Promise<PreparedRender>;
@@ -0,0 +1,121 @@
1
+ import { configureDebug, log } from "../logging/debug.js";
2
+ import { makeUnitParsers, pxToPt } from "../units/units.js";
3
+ import { layoutTree } from "../layout/pipeline/layout-tree.js";
4
+ import { buildRenderTree } from "../pdf/layout-tree-builder.js";
5
+ import { setViewportSize } from "../css/apply-declarations.js";
6
+ import { DEFAULT_PAGE_WIDTH_PX, DEFAULT_PAGE_HEIGHT_PX, resolvePageMarginsPx, sanitizeDimension, maxContentDimension, } from "../units/page-utils.js";
7
+ import { NodeEnvironment } from "../environment/node-environment.js";
8
+ import { appendFontFacesFromCssRules, ensureFontFaceDataLoaded } from "./fonts.js";
9
+ import { resolveHeaderFooterMaxHeights } from "./header-footer.js";
10
+ import { normalizeHtmlInput } from "./html-parser.js";
11
+ import { collectCssArtifacts, parseInputDocument } from "./document-css.js";
12
+ import { appendConvertedChildren, buildRootLayoutContext, createDomConversionContext } from "./layout-build.js";
13
+ import { finalizeRenderTreePositioning, initializeFontEmbedder } from "./render-finalize.js";
14
+ export async function prepareHtmlRender(options) {
15
+ const pageWidth = sanitizeDimension(options.pageWidth, DEFAULT_PAGE_WIDTH_PX);
16
+ const pageHeight = sanitizeDimension(options.pageHeight, DEFAULT_PAGE_HEIGHT_PX);
17
+ const marginsPx = mergePageMargins(resolvePageMarginsPx(pageWidth, pageHeight), options.margins, pageWidth, pageHeight);
18
+ const maxContentWidth = maxContentDimension(pageWidth, marginsPx.left + marginsPx.right);
19
+ const maxContentHeight = maxContentDimension(pageHeight, marginsPx.top + marginsPx.bottom);
20
+ const viewportWidth = Math.min(sanitizeDimension(options.viewportWidth, maxContentWidth), maxContentWidth);
21
+ const viewportHeight = Math.min(sanitizeDimension(options.viewportHeight, maxContentHeight), maxContentHeight);
22
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
23
+ const { html, css, pageWidth: _, pageHeight: __, margins: ___, ...restOptions } = options;
24
+ const { html: htmlInput, css: cssInput = "" } = { html, css: css, ...restOptions };
25
+ const { debug = false, debugLevel, debugCats, headerFooter, resourceBaseDir, assetRootDir, environment: envOverride, pagedBodyMargin = "auto", interBlockWhitespace = "collapse", } = options;
26
+ const normalizedHtml = normalizeHtmlInput(htmlInput);
27
+ setViewportSize(viewportWidth, viewportHeight);
28
+ const resourceBaseDirVal = resourceBaseDir ?? assetRootDir ?? "";
29
+ const assetRootDirVal = assetRootDir ?? resourceBaseDirVal;
30
+ const environment = envOverride ?? new NodeEnvironment(assetRootDirVal);
31
+ const resolvedHeaderFooter = await resolveHeaderFooterMaxHeights({
32
+ headerFooter,
33
+ pageWidthPx: pageWidth,
34
+ pageHeightPx: pageHeight,
35
+ margins: marginsPx,
36
+ resourceBaseDir: resourceBaseDirVal,
37
+ assetRootDir: assetRootDirVal,
38
+ environment,
39
+ });
40
+ if (debugLevel || debugCats) {
41
+ configureDebug({ level: debugLevel ?? (debug ? "debug" : "info"), cats: debugCats });
42
+ }
43
+ const unitCtx = { viewport: { width: viewportWidth, height: viewportHeight } };
44
+ const units = makeUnitParsers(unitCtx);
45
+ const document = parseInputDocument(htmlInput, normalizedHtml);
46
+ const { cssRules, fontFaceRules } = await collectCssArtifacts({
47
+ document,
48
+ cssInput,
49
+ resourceBaseDir: resourceBaseDirVal,
50
+ assetRootDir: assetRootDirVal,
51
+ environment,
52
+ });
53
+ const { processChildrenOf, rootStyle, rootLayout, rootFontSize } = buildRootLayoutContext({
54
+ document,
55
+ cssRules,
56
+ units,
57
+ pagedBodyMargin,
58
+ });
59
+ const conversionContext = createDomConversionContext({
60
+ resourceBaseDir: resourceBaseDirVal,
61
+ assetRootDir: assetRootDirVal,
62
+ units,
63
+ rootFontSize,
64
+ environment,
65
+ interBlockWhitespace,
66
+ });
67
+ await appendConvertedChildren({
68
+ processChildrenOf,
69
+ rootLayout,
70
+ cssRules,
71
+ rootStyle,
72
+ conversionContext,
73
+ });
74
+ await appendFontFacesFromCssRules(fontFaceRules, options.fontConfig, {
75
+ resourceBaseDir: resourceBaseDirVal,
76
+ assetRootDir: assetRootDirVal,
77
+ environment,
78
+ });
79
+ await ensureFontFaceDataLoaded(options.fontConfig, {
80
+ resourceBaseDir: resourceBaseDirVal,
81
+ assetRootDir: assetRootDirVal,
82
+ environment,
83
+ });
84
+ const fontEmbedder = await initializeFontEmbedder(options.fontConfig);
85
+ layoutTree(rootLayout, { width: viewportWidth, height: viewportHeight }, fontEmbedder);
86
+ log("layout", "debug", "Layout complete");
87
+ const renderTree = buildRenderTree(rootLayout, { headerFooter: resolvedHeaderFooter });
88
+ finalizeRenderTreePositioning({
89
+ renderTree,
90
+ resolvedHeaderFooter,
91
+ pageHeight,
92
+ marginsPx,
93
+ debug,
94
+ });
95
+ const pageSize = { widthPt: pxToPt(pageWidth), heightPt: pxToPt(pageHeight) };
96
+ return { layoutRoot: rootLayout, renderTree, pageSize, margins: marginsPx };
97
+ }
98
+ function mergePageMargins(defaults, provided, pageWidth, pageHeight) {
99
+ const margins = { ...defaults };
100
+ if (provided) {
101
+ for (const side of ["top", "right", "bottom", "left"]) {
102
+ const value = provided[side];
103
+ if (Number.isFinite(value)) {
104
+ margins[side] = Math.max(Number(value), 0);
105
+ }
106
+ }
107
+ }
108
+ const horizontalSum = margins.left + margins.right;
109
+ const verticalSum = margins.top + margins.bottom;
110
+ if (horizontalSum > pageWidth) {
111
+ const scale = pageWidth / (horizontalSum || 1);
112
+ margins.left *= scale;
113
+ margins.right *= scale;
114
+ }
115
+ if (verticalSum > pageHeight) {
116
+ const scale = pageHeight / (verticalSum || 1);
117
+ margins.top *= scale;
118
+ margins.bottom *= scale;
119
+ }
120
+ return margins;
121
+ }
@@ -0,0 +1,15 @@
1
+ import { FontEmbedder } from "../pdf/font/embedder.js";
2
+ import type { FontConfig } from "../types/fonts.js";
3
+ import type { HeaderFooterHTML } from "../pdf/types.js";
4
+ import { buildRenderTree } from "../pdf/layout-tree-builder.js";
5
+ import type { PageMarginsPx } from "../units/page-utils.js";
6
+ export declare function initializeFontEmbedder(fontConfig: FontConfig | undefined): Promise<FontEmbedder | null>;
7
+ interface FinalizeRenderTreePositioningOptions {
8
+ renderTree: ReturnType<typeof buildRenderTree>;
9
+ resolvedHeaderFooter: Partial<HeaderFooterHTML> | undefined;
10
+ pageHeight: number;
11
+ marginsPx: PageMarginsPx;
12
+ debug: boolean;
13
+ }
14
+ export declare function finalizeRenderTreePositioning(options: FinalizeRenderTreePositioningOptions): void;
15
+ export {};
@@ -0,0 +1,27 @@
1
+ import { FontEmbedder } from "../pdf/font/embedder.js";
2
+ import { PdfDocument } from "../pdf/primitives/pdf-document.js";
3
+ import { applyPageVerticalMarginsWithHf, offsetRenderTree } from "../render/offset.js";
4
+ import { applyTextLayoutAdjustments } from "../pdf/utils/text-layout-adjuster.js";
5
+ import { buildRenderTree } from "../pdf/layout-tree-builder.js";
6
+ export async function initializeFontEmbedder(fontConfig) {
7
+ if (!fontConfig) {
8
+ return null;
9
+ }
10
+ const pdfDoc = new PdfDocument();
11
+ const fontEmbedder = new FontEmbedder(fontConfig, pdfDoc);
12
+ await fontEmbedder.initialize();
13
+ return fontEmbedder;
14
+ }
15
+ export function finalizeRenderTreePositioning(options) {
16
+ const { renderTree, resolvedHeaderFooter, pageHeight, marginsPx, debug } = options;
17
+ applyTextLayoutAdjustments(renderTree.root);
18
+ const headerHeightPx = resolvedHeaderFooter?.maxHeaderHeightPx ?? 0;
19
+ const footerHeightPx = resolvedHeaderFooter?.maxFooterHeightPx ?? 0;
20
+ applyPageVerticalMarginsWithHf(renderTree.root, {
21
+ pageHeight,
22
+ margins: marginsPx,
23
+ headerHeightPx,
24
+ footerHeightPx,
25
+ });
26
+ offsetRenderTree(renderTree.root, marginsPx.left, 0, debug);
27
+ }
@@ -0,0 +1,3 @@
1
+ import type { RenderHtmlOptions } from "./types.js";
2
+ export declare function renderHtmlToPdf(options: RenderHtmlOptions): Promise<Uint8Array>;
3
+ export declare function renderHtmlToPdfBase64(options: RenderHtmlOptions): Promise<string>;
@@ -0,0 +1,25 @@
1
+ import { renderPdf } from "../pdf/render.js";
2
+ import { loadBuiltinFontConfig } from "../pdf/font/builtin-fonts.js";
3
+ import { NodeEnvironment } from "../environment/node-environment.js";
4
+ import { encodeUint8ArrayToBase64 } from "../utils/base64.js";
5
+ import { prepareHtmlRender } from "./prepare-html-render.js";
6
+ export async function renderHtmlToPdf(options) {
7
+ const environment = options.environment ?? new NodeEnvironment(options.assetRootDir ?? options.resourceBaseDir);
8
+ const resolvedResourceBaseDir = options.resourceBaseDir ?? options.assetRootDir ?? "";
9
+ const resolvedAssetRootDir = options.assetRootDir ?? resolvedResourceBaseDir;
10
+ const resolvedFontConfig = options.fontConfig ?? (await loadBuiltinFontConfig(environment));
11
+ const preparedOptions = resolvedFontConfig ? { ...options, fontConfig: resolvedFontConfig, environment } : { ...options, environment };
12
+ const prepared = await prepareHtmlRender(preparedOptions);
13
+ return renderPdf(prepared.renderTree, {
14
+ pageSize: prepared.pageSize,
15
+ fontConfig: resolvedFontConfig ?? undefined,
16
+ margins: prepared.margins,
17
+ environment,
18
+ resourceBaseDir: resolvedResourceBaseDir,
19
+ assetRootDir: resolvedAssetRootDir,
20
+ });
21
+ }
22
+ export async function renderHtmlToPdfBase64(options) {
23
+ const pdfBuffer = await renderHtmlToPdf(options);
24
+ return encodeUint8ArrayToBase64(pdfBuffer);
25
+ }
@@ -0,0 +1,6 @@
1
+ import type { Environment } from "../environment/environment.js";
2
+ export declare function resolveLocalPath(target: string, resourceBaseDir: string, assetRootDir: string, environment: Environment): string;
3
+ export declare function selectLocalBase(target: string, resourceBaseDir: string, assetRootDir: string): string;
4
+ export declare function loadStylesheetFromHref(href: string, resourceBaseDir: string, assetRootDir: string, environment: Environment): Promise<string>;
5
+ export declare function rewriteCssUrls(cssText: string, baseHref: string): string;
6
+ export declare function loadFontData(src: string, resourceBaseDir: string, assetRootDir: string, environment: Environment): Promise<ArrayBuffer | null>;
@@ -0,0 +1,120 @@
1
+ import { log } from "../logging/debug.js";
2
+ import { decodeBase64ToUint8Array } from "../utils/base64.js";
3
+ export function resolveLocalPath(target, resourceBaseDir, assetRootDir, environment) {
4
+ let result = target;
5
+ if (result.startsWith("file://")) {
6
+ result = environment.fileURLToPath ? environment.fileURLToPath(result) : result;
7
+ }
8
+ else if (result.startsWith("/")) {
9
+ result = environment.pathResolve ? environment.pathResolve(assetRootDir, `.${result}`) : result;
10
+ }
11
+ else if (!(environment.pathIsAbsolute ? environment.pathIsAbsolute(result) : result.includes(":"))) {
12
+ result = environment.pathResolve ? environment.pathResolve(resourceBaseDir, result) : result;
13
+ }
14
+ return result;
15
+ }
16
+ export function selectLocalBase(target, resourceBaseDir, assetRootDir) {
17
+ if (target.trim().startsWith("/")) {
18
+ return assetRootDir || resourceBaseDir;
19
+ }
20
+ return resourceBaseDir || assetRootDir;
21
+ }
22
+ export async function loadStylesheetFromHref(href, resourceBaseDir, assetRootDir, environment) {
23
+ const trimmed = href.trim();
24
+ if (!trimmed)
25
+ return "";
26
+ try {
27
+ if (/^https?:\/\//i.test(trimmed) || trimmed.startsWith("//")) {
28
+ const absoluteHref = trimmed.startsWith("//") ? `https:${trimmed}` : trimmed;
29
+ const response = await fetch(absoluteHref);
30
+ if (!response.ok) {
31
+ throw new Error(`HTTP ${response.status}`);
32
+ }
33
+ const cssText = await response.text();
34
+ return rewriteCssUrls(cssText, absoluteHref);
35
+ }
36
+ const localBase = selectLocalBase(trimmed, resourceBaseDir, assetRootDir);
37
+ const cssPath = environment.resolveLocal
38
+ ? environment.resolveLocal(trimmed, localBase || undefined)
39
+ : resolveLocalPath(trimmed, resourceBaseDir, assetRootDir, environment);
40
+ const cssBuffer = await environment.loader.load(cssPath);
41
+ const cssText = new TextDecoder("utf-8").decode(cssBuffer);
42
+ const baseHref = /^https?:\/\//i.test(cssPath) || cssPath.startsWith("file:")
43
+ ? cssPath
44
+ : (environment.pathToFileURL ? environment.pathToFileURL(cssPath) : cssPath);
45
+ return rewriteCssUrls(cssText, baseHref);
46
+ }
47
+ catch (error) {
48
+ log("parse", "warn", "Failed to load stylesheet", { href, error: error instanceof Error ? error.message : String(error) });
49
+ return "";
50
+ }
51
+ }
52
+ export function rewriteCssUrls(cssText, baseHref) {
53
+ let base;
54
+ try {
55
+ base = new URL(baseHref);
56
+ }
57
+ catch {
58
+ return cssText;
59
+ }
60
+ const urlRegex = /url\(\s*(['"]?)([^'")]+)\1\s*\)/gi;
61
+ return cssText.replace(urlRegex, (match, quote, rawUrl) => {
62
+ const candidate = (rawUrl || "").trim();
63
+ if (!candidate || /^data:/i.test(candidate))
64
+ return match;
65
+ if (/^[a-z][a-z0-9+\-.]*:/i.test(candidate))
66
+ return match;
67
+ try {
68
+ const resolved = new URL(candidate, base).toString();
69
+ const q = quote || "";
70
+ return `url(${q}${resolved}${q})`;
71
+ }
72
+ catch {
73
+ return match;
74
+ }
75
+ });
76
+ }
77
+ export async function loadFontData(src, resourceBaseDir, assetRootDir, environment) {
78
+ const trimmed = src.trim();
79
+ if (!trimmed)
80
+ return null;
81
+ let target = trimmed;
82
+ try {
83
+ if (target.startsWith("//")) {
84
+ target = `https:${target}`;
85
+ }
86
+ if (/^data:/i.test(target)) {
87
+ const commaIdx = target.indexOf(",");
88
+ if (commaIdx === -1) {
89
+ throw new Error("Invalid data URI");
90
+ }
91
+ const meta = target.slice(5, commaIdx);
92
+ if (!/;base64/i.test(meta)) {
93
+ throw new Error("Only base64-encoded data URIs are supported for fonts");
94
+ }
95
+ const data = decodeBase64ToUint8Array(target.slice(commaIdx + 1));
96
+ const copy = data.slice();
97
+ return copy.buffer;
98
+ }
99
+ if (/^https?:\/\//i.test(target)) {
100
+ const response = await fetch(target);
101
+ if (!response.ok) {
102
+ throw new Error(`HTTP ${response.status}`);
103
+ }
104
+ return await response.arrayBuffer();
105
+ }
106
+ if (/^file:/i.test(target)) {
107
+ const localPath = environment.fileURLToPath ? environment.fileURLToPath(target) : target;
108
+ return await environment.loader.load(localPath);
109
+ }
110
+ const localBase = selectLocalBase(target, resourceBaseDir, assetRootDir);
111
+ const resolved = environment.resolveLocal
112
+ ? environment.resolveLocal(target, localBase || undefined)
113
+ : resolveLocalPath(target, resourceBaseDir, assetRootDir, environment);
114
+ return await environment.loader.load(resolved);
115
+ }
116
+ catch (error) {
117
+ log("font", "warn", "Failed to load font data", { src, error: error instanceof Error ? error.message : String(error) });
118
+ return null;
119
+ }
120
+ }