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
@@ -1,6 +1,6 @@
1
1
  import { LayoutNode } from "../../dom/node.js";
2
2
  import { AlignItems, Display, JustifyContent } from "../../css/enums.js";
3
- import { containingBlock, resolveBoxMetrics } from "../utils/node-math.js";
3
+ import { adjustForBoxSizing, containingBlock, resolveBoxMetrics, resolveWidthBlock } from "../utils/node-math.js";
4
4
  import { resolveLength, isAutoLength } from "../../css/length.js";
5
5
  import { GapCalculator, calculateTotalGap } from "../utils/gap-calculator.js";
6
6
  function blockifyFlexItemDisplay(display) {
@@ -33,6 +33,153 @@ function allowsPreferredShrink(originalDisplay, effectiveDisplay) {
33
33
  }
34
34
  return originalDisplay === Display.Inline;
35
35
  }
36
+ function buildFlexLines(items, containerMainSize, mainAxisGap) {
37
+ if (items.length === 0) {
38
+ return [];
39
+ }
40
+ const lines = [];
41
+ let current = { items: [], mainSizeWithGaps: 0, crossSize: 0 };
42
+ for (const item of items) {
43
+ const addition = current.items.length === 0 ? item.mainContribution : mainAxisGap + item.mainContribution;
44
+ const shouldWrap = current.items.length > 0 &&
45
+ containerMainSize > 0 &&
46
+ current.mainSizeWithGaps + addition > containerMainSize + 0.01;
47
+ if (shouldWrap) {
48
+ lines.push(current);
49
+ current = { items: [], mainSizeWithGaps: 0, crossSize: 0 };
50
+ }
51
+ if (current.items.length > 0) {
52
+ current.mainSizeWithGaps += mainAxisGap;
53
+ }
54
+ current.items.push(item);
55
+ current.mainSizeWithGaps += item.mainContribution;
56
+ current.crossSize = Math.max(current.crossSize, item.crossContribution);
57
+ }
58
+ if (current.items.length > 0) {
59
+ lines.push(current);
60
+ }
61
+ return lines;
62
+ }
63
+ function calculateLinesCrossSize(lines, crossAxisGap) {
64
+ if (lines.length === 0) {
65
+ return 0;
66
+ }
67
+ const totalLines = lines.reduce((sum, line) => sum + line.crossSize, 0);
68
+ return totalLines + calculateTotalGap(crossAxisGap, lines.length);
69
+ }
70
+ function refreshFlexItemSizes(item, isRow) {
71
+ item.mainSize = isRow ? item.node.box.borderBoxWidth : item.node.box.borderBoxHeight;
72
+ item.crossSize = isRow ? item.node.box.borderBoxHeight : item.node.box.borderBoxWidth;
73
+ item.mainContribution = item.mainSize + item.mainMarginStart + item.mainMarginEnd;
74
+ item.crossContribution = item.crossSize + item.crossMarginStart + item.crossMarginEnd;
75
+ }
76
+ function recomputeLineMetrics(line, mainAxisGap) {
77
+ let mainSizeWithGaps = 0;
78
+ let crossSize = 0;
79
+ for (let i = 0; i < line.items.length; i++) {
80
+ const item = line.items[i];
81
+ if (i > 0) {
82
+ mainSizeWithGaps += mainAxisGap;
83
+ }
84
+ mainSizeWithGaps += item.mainContribution;
85
+ crossSize = Math.max(crossSize, item.crossContribution);
86
+ }
87
+ line.mainSizeWithGaps = mainSizeWithGaps;
88
+ line.crossSize = crossSize;
89
+ }
90
+ function relayoutFlexItemForMainSize(container, item, context, targetMainSize, isRow) {
91
+ if (!Number.isFinite(targetMainSize) || targetMainSize < 0) {
92
+ return;
93
+ }
94
+ const prevContainerWidth = container.box.contentWidth;
95
+ const prevContainerHeight = container.box.contentHeight;
96
+ const targetContribution = targetMainSize + item.mainMarginStart + item.mainMarginEnd;
97
+ let displayMutated = false;
98
+ if (item.node.style.display !== item.effectiveDisplay) {
99
+ item.node.style.display = item.effectiveDisplay;
100
+ displayMutated = true;
101
+ }
102
+ try {
103
+ if (isRow) {
104
+ container.box.contentWidth = Math.max(0, targetContribution);
105
+ }
106
+ else {
107
+ container.box.contentHeight = Math.max(0, targetContribution);
108
+ }
109
+ context.layoutChild(item.node);
110
+ }
111
+ finally {
112
+ container.box.contentWidth = prevContainerWidth;
113
+ container.box.contentHeight = prevContainerHeight;
114
+ if (displayMutated) {
115
+ item.node.style.display = item.originalDisplay;
116
+ }
117
+ }
118
+ }
119
+ function distributeFlexGrowAcrossLines(lines, container, context, containerMainSize, mainAxisGap, isRow) {
120
+ if (lines.length === 0 || !(containerMainSize > 0)) {
121
+ return;
122
+ }
123
+ for (const line of lines) {
124
+ const freeSpace = containerMainSize - line.mainSizeWithGaps;
125
+ if (!(freeSpace > 0)) {
126
+ continue;
127
+ }
128
+ const growItems = line.items.filter((item) => item.flexGrow > 0);
129
+ const totalGrow = growItems.reduce((sum, item) => sum + item.flexGrow, 0);
130
+ if (!(totalGrow > 0)) {
131
+ continue;
132
+ }
133
+ for (const item of growItems) {
134
+ const delta = (freeSpace * item.flexGrow) / totalGrow;
135
+ const targetMainSize = Math.max(0, item.mainSize + delta);
136
+ if (Math.abs(targetMainSize - item.mainSize) < 0.01) {
137
+ continue;
138
+ }
139
+ relayoutFlexItemForMainSize(container, item, context, targetMainSize, isRow);
140
+ refreshFlexItemSizes(item, isRow);
141
+ }
142
+ recomputeLineMetrics(line, mainAxisGap);
143
+ }
144
+ }
145
+ function resolveAlignContentLayout(lines, alignContent, containerCrossSize, crossAxisGap) {
146
+ const lineCrossSizes = lines.map((line) => line.crossSize);
147
+ if (lines.length === 0) {
148
+ return { lineCrossSizes, initialOffset: 0, additionalGap: 0 };
149
+ }
150
+ const naturalCross = calculateLinesCrossSize(lines, crossAxisGap);
151
+ const freeSpace = Math.max(0, containerCrossSize - naturalCross);
152
+ switch (alignContent) {
153
+ case "stretch":
154
+ if (freeSpace > 0) {
155
+ const extraPerLine = freeSpace / lines.length;
156
+ for (let i = 0; i < lineCrossSizes.length; i++) {
157
+ lineCrossSizes[i] += extraPerLine;
158
+ }
159
+ }
160
+ return { lineCrossSizes, initialOffset: 0, additionalGap: 0 };
161
+ case "flex-end":
162
+ return { lineCrossSizes, initialOffset: freeSpace, additionalGap: 0 };
163
+ case "center":
164
+ return { lineCrossSizes, initialOffset: freeSpace / 2, additionalGap: 0 };
165
+ case "space-between":
166
+ if (lines.length <= 1) {
167
+ return { lineCrossSizes, initialOffset: 0, additionalGap: 0 };
168
+ }
169
+ return { lineCrossSizes, initialOffset: 0, additionalGap: freeSpace / (lines.length - 1) };
170
+ case "space-around": {
171
+ const gap = freeSpace / lines.length;
172
+ return { lineCrossSizes, initialOffset: gap / 2, additionalGap: gap };
173
+ }
174
+ case "space-evenly": {
175
+ const gap = freeSpace / (lines.length + 1);
176
+ return { lineCrossSizes, initialOffset: gap, additionalGap: gap };
177
+ }
178
+ case "flex-start":
179
+ default:
180
+ return { lineCrossSizes, initialOffset: 0, additionalGap: 0 };
181
+ }
182
+ }
36
183
  export class FlexLayoutStrategy {
37
184
  constructor() {
38
185
  this.supportedDisplays = new Set([Display.Flex, Display.InlineFlex]);
@@ -42,24 +189,34 @@ export class FlexLayoutStrategy {
42
189
  }
43
190
  layout(node, context) {
44
191
  const cb = containingBlock(node, context.env.viewport);
192
+ const containerRefs = { containerWidth: cb.width, containerHeight: cb.height };
45
193
  const isRow = isRowDirection(node.style.flexDirection);
46
194
  const cbMain = isRow ? cb.width : cb.height;
47
195
  const cbCross = isRow ? cb.height : cb.width;
48
- const specifiedMain = resolveFlexSize(isRow ? node.style.width : node.style.height, cbMain);
49
- const specifiedCross = resolveFlexSize(isRow ? node.style.height : node.style.width, cbCross);
196
+ // Resolve box metrics (padding/border)
197
+ const boxMetrics = resolveBoxMetrics(node, cb.width, cb.height);
198
+ const hExtras = boxMetrics.paddingLeft + boxMetrics.paddingRight + boxMetrics.borderLeft + boxMetrics.borderRight;
199
+ const vExtras = boxMetrics.paddingTop + boxMetrics.paddingBottom + boxMetrics.borderTop + boxMetrics.borderBottom;
200
+ let specifiedMain = resolveFlexSize(isRow ? node.style.width : node.style.height, cbMain, cb.width, cb.height);
201
+ let specifiedCross = resolveFlexSize(isRow ? node.style.height : node.style.width, cbCross, cb.width, cb.height);
202
+ if (specifiedMain !== undefined) {
203
+ specifiedMain = adjustForBoxSizing(specifiedMain, node.style.boxSizing, isRow ? hExtras : vExtras);
204
+ }
205
+ if (specifiedCross !== undefined) {
206
+ specifiedCross = adjustForBoxSizing(specifiedCross, node.style.boxSizing, isRow ? vExtras : hExtras);
207
+ }
50
208
  // Read gap properties for flex layout
51
209
  const rowGap = node.style.rowGap ?? 0;
52
210
  const columnGap = node.style.columnGap ?? 0;
53
211
  const gapCalculator = new GapCalculator({ rowGap, columnGap });
54
212
  const mainAxisGap = gapCalculator.getMainAxisGap(isRow);
55
- // Resolve box metrics (padding/border)
56
- const boxMetrics = resolveBoxMetrics(node, cb.width, cb.height);
213
+ const defaultContentWidth = resolveWidthBlock(node, cb.width, cb.height);
57
214
  if (isRow) {
58
- node.box.contentWidth = resolveInitialDimension(specifiedMain, cbMain);
215
+ node.box.contentWidth = resolveInitialDimension(specifiedMain, defaultContentWidth);
59
216
  node.box.contentHeight = resolveInitialDimension(specifiedCross, cbCross);
60
217
  }
61
218
  else {
62
- node.box.contentWidth = resolveInitialDimension(specifiedCross, cbCross);
219
+ node.box.contentWidth = resolveInitialDimension(specifiedCross, defaultContentWidth);
63
220
  node.box.contentHeight = resolveInitialDimension(specifiedMain, cbMain);
64
221
  }
65
222
  node.box.borderBoxWidth =
@@ -90,27 +247,42 @@ export class FlexLayoutStrategy {
90
247
  if (displayMutated) {
91
248
  child.style.display = blockifiedDisplay;
92
249
  }
250
+ const parentContentWidth = node.box.contentWidth;
251
+ const parentContentHeight = node.box.contentHeight;
93
252
  try {
253
+ const basisReference = isRow ? cb.width : cb.height;
254
+ const basis = resolveFlexSize(child.style.flexBasis, basisReference, cb.width, cb.height);
255
+ if (basis !== undefined) {
256
+ if (isRow) {
257
+ node.box.contentWidth = Math.max(0, basis);
258
+ }
259
+ else {
260
+ node.box.contentHeight = Math.max(0, basis);
261
+ }
262
+ }
94
263
  context.layoutChild(child);
95
- const marginLeft = resolveLength(child.style.marginLeft, cb.width, { auto: "zero" });
96
- const marginRight = resolveLength(child.style.marginRight, cb.width, { auto: "zero" });
97
- const marginTop = resolveLength(child.style.marginTop, cb.height, { auto: "zero" });
98
- const marginBottom = resolveLength(child.style.marginBottom, cb.height, { auto: "zero" });
264
+ node.box.contentWidth = parentContentWidth;
265
+ node.box.contentHeight = parentContentHeight;
266
+ const marginLeft = resolveLength(child.style.marginLeft, cb.width, { auto: "zero", ...containerRefs });
267
+ const marginRight = resolveLength(child.style.marginRight, cb.width, { auto: "zero", ...containerRefs });
268
+ const marginTop = resolveLength(child.style.marginTop, cb.height, { auto: "zero", ...containerRefs });
269
+ const marginBottom = resolveLength(child.style.marginBottom, cb.height, { auto: "zero", ...containerRefs });
99
270
  const mainMarginStart = isRow ? marginLeft : marginTop;
100
271
  const mainMarginEnd = isRow ? marginRight : marginBottom;
101
272
  const crossMarginStart = isRow ? marginTop : marginLeft;
102
273
  const crossMarginEnd = isRow ? marginBottom : marginRight;
103
274
  let mainSize = isRow ? child.box.borderBoxWidth : child.box.borderBoxHeight;
275
+ const hasExplicitFlexBasis = resolveFlexSize(child.style.flexBasis, isRow ? cb.width : cb.height, cb.width, cb.height) !== undefined;
104
276
  const childDisplay = blockifiedDisplay;
105
277
  const allowPreferredShrink = allowsPreferredShrink(originalDisplay, childDisplay);
106
- if (isRow && allowPreferredShrink && isAutoMainSize(child.style.width)) {
278
+ if (isRow && allowPreferredShrink && isAutoMainSize(child.style.width) && !hasExplicitFlexBasis) {
107
279
  const preferredContent = computePreferredInlineWidth(child);
108
280
  if (preferredContent !== undefined && preferredContent >= 0) {
109
281
  const minWidth = child.style.minWidth !== undefined
110
- ? resolveLength(child.style.minWidth, cb.width, { auto: "zero" })
282
+ ? resolveLength(child.style.minWidth, cb.width, { auto: "zero", ...containerRefs })
111
283
  : undefined;
112
284
  const maxWidth = child.style.maxWidth !== undefined
113
- ? resolveLength(child.style.maxWidth, cb.width, { auto: "reference" })
285
+ ? resolveLength(child.style.maxWidth, cb.width, { auto: "reference", ...containerRefs })
114
286
  : undefined;
115
287
  let targetContentWidth = preferredContent;
116
288
  if (minWidth !== undefined) {
@@ -120,15 +292,20 @@ export class FlexLayoutStrategy {
120
292
  targetContentWidth = Math.min(targetContentWidth, maxWidth);
121
293
  }
122
294
  if (targetContentWidth < child.box.contentWidth) {
123
- const paddingLeft = resolveLength(child.style.paddingLeft, cb.width, { auto: "zero" });
124
- const paddingRight = resolveLength(child.style.paddingRight, cb.width, { auto: "zero" });
125
- const borderLeft = resolveLength(child.style.borderLeft, cb.width, { auto: "zero" });
126
- const borderRight = resolveLength(child.style.borderRight, cb.width, { auto: "zero" });
295
+ const paddingLeft = resolveLength(child.style.paddingLeft, cb.width, { auto: "zero", ...containerRefs });
296
+ const paddingRight = resolveLength(child.style.paddingRight, cb.width, { auto: "zero", ...containerRefs });
297
+ const borderLeft = resolveLength(child.style.borderLeft, cb.width, { auto: "zero", ...containerRefs });
298
+ const borderRight = resolveLength(child.style.borderRight, cb.width, { auto: "zero", ...containerRefs });
127
299
  child.box.contentWidth = targetContentWidth;
128
300
  child.box.borderBoxWidth = targetContentWidth + paddingLeft + paddingRight + borderLeft + borderRight;
129
301
  child.box.marginBoxWidth = child.box.borderBoxWidth + marginLeft + marginRight;
130
302
  child.box.scrollWidth = Math.max(child.box.scrollWidth, child.box.contentWidth);
131
303
  mainSize = child.box.borderBoxWidth;
304
+ // Re-layout with corrected width so inline text positions are correct
305
+ const prevW = node.box.contentWidth;
306
+ node.box.contentWidth = child.box.borderBoxWidth + marginLeft + marginRight;
307
+ context.layoutChild(child);
308
+ node.box.contentWidth = prevW;
132
309
  }
133
310
  }
134
311
  }
@@ -140,15 +317,15 @@ export class FlexLayoutStrategy {
140
317
  const preferredContent = computePreferredInlineWidth(child);
141
318
  if (preferredContent !== undefined && preferredContent > 0) {
142
319
  const widthRef = cb.width;
143
- const paddingLeft = resolveLength(child.style.paddingLeft, widthRef, { auto: "zero" });
144
- const paddingRight = resolveLength(child.style.paddingRight, widthRef, { auto: "zero" });
145
- const borderLeft = resolveLength(child.style.borderLeft, widthRef, { auto: "zero" });
146
- const borderRight = resolveLength(child.style.borderRight, widthRef, { auto: "zero" });
320
+ const paddingLeft = resolveLength(child.style.paddingLeft, widthRef, { auto: "zero", ...containerRefs });
321
+ const paddingRight = resolveLength(child.style.paddingRight, widthRef, { auto: "zero", ...containerRefs });
322
+ const borderLeft = resolveLength(child.style.borderLeft, widthRef, { auto: "zero", ...containerRefs });
323
+ const borderRight = resolveLength(child.style.borderRight, widthRef, { auto: "zero", ...containerRefs });
147
324
  const minWidth = child.style.minWidth !== undefined
148
- ? resolveLength(child.style.minWidth, widthRef, { auto: "zero" })
325
+ ? resolveLength(child.style.minWidth, widthRef, { auto: "zero", ...containerRefs })
149
326
  : undefined;
150
327
  const maxWidth = child.style.maxWidth !== undefined
151
- ? resolveLength(child.style.maxWidth, widthRef, { auto: "reference" })
328
+ ? resolveLength(child.style.maxWidth, widthRef, { auto: "reference", ...containerRefs })
152
329
  : undefined;
153
330
  let targetContentWidth = Math.min(preferredContent, child.box.contentWidth);
154
331
  if (minWidth !== undefined) {
@@ -174,6 +351,8 @@ export class FlexLayoutStrategy {
174
351
  const crossContribution = crossSize + crossMarginStart + crossMarginEnd;
175
352
  items.push({
176
353
  node: child,
354
+ originalDisplay,
355
+ effectiveDisplay: blockifiedDisplay,
177
356
  mainMarginStart,
178
357
  mainMarginEnd,
179
358
  crossMarginStart,
@@ -182,11 +361,15 @@ export class FlexLayoutStrategy {
182
361
  crossSize,
183
362
  mainContribution,
184
363
  crossContribution,
364
+ flexGrow: Math.max(0, child.style.flexGrow ?? 0),
365
+ flexShrink: Math.max(0, child.style.flexShrink ?? 0),
185
366
  });
186
367
  totalMain += mainContribution;
187
368
  maxCrossContribution = Math.max(maxCrossContribution, crossContribution);
188
369
  }
189
370
  finally {
371
+ node.box.contentWidth = parentContentWidth;
372
+ node.box.contentHeight = parentContentHeight;
190
373
  if (displayMutated) {
191
374
  child.style.display = originalDisplay;
192
375
  }
@@ -194,30 +377,60 @@ export class FlexLayoutStrategy {
194
377
  }
195
378
  // Account for gaps in total main size
196
379
  const gapSpace = calculateTotalGap(mainAxisGap, items.length);
197
- const totalMainWithGaps = totalMain + gapSpace;
380
+ let totalMainWithGaps = totalMain + gapSpace;
381
+ const wrapEnabled = isRow && node.style.flexWrap;
382
+ const crossAxisGap = gapCalculator.getCrossAxisGap(isRow);
198
383
  let containerMainSize;
199
384
  if (specifiedMain !== undefined) {
200
385
  containerMainSize = specifiedMain;
201
386
  }
387
+ else if (isRow) {
388
+ const reference = Number.isFinite(defaultContentWidth) && defaultContentWidth > 0 ? defaultContentWidth : totalMainWithGaps;
389
+ containerMainSize = wrapEnabled ? reference : Math.max(reference, totalMainWithGaps);
390
+ }
202
391
  else {
203
- const reference = Number.isFinite(cbMain) && cbMain > 0 ? cbMain : totalMainWithGaps;
204
- containerMainSize = Math.max(reference, totalMainWithGaps);
392
+ // For column flex containers with auto height, use content-based sizing.
393
+ // Falling back to containing block height incorrectly stretches the box
394
+ // to the page/viewport height in HTML->PDF flow layouts.
395
+ containerMainSize = totalMainWithGaps;
205
396
  }
397
+ const lines = wrapEnabled ? buildFlexLines(items, containerMainSize, mainAxisGap) : [];
398
+ if (wrapEnabled) {
399
+ distributeFlexGrowAcrossLines(lines, node, context, containerMainSize, mainAxisGap, isRow);
400
+ }
401
+ else if (items.length > 0) {
402
+ const singleLine = {
403
+ items,
404
+ mainSizeWithGaps: totalMainWithGaps,
405
+ crossSize: maxCrossContribution,
406
+ };
407
+ distributeFlexGrowAcrossLines([singleLine], node, context, containerMainSize, mainAxisGap, isRow);
408
+ totalMain = items.reduce((sum, item) => sum + item.mainContribution, 0);
409
+ maxCrossContribution = items.reduce((max, item) => Math.max(max, item.crossContribution), 0);
410
+ totalMainWithGaps = totalMain + gapSpace;
411
+ }
412
+ const wrappedCrossContribution = wrapEnabled ? calculateLinesCrossSize(lines, crossAxisGap) : 0;
206
413
  let containerCrossSize;
207
414
  if (specifiedCross !== undefined) {
208
- containerCrossSize = Math.max(specifiedCross, maxCrossContribution);
415
+ const measuredCross = wrapEnabled ? wrappedCrossContribution : maxCrossContribution;
416
+ containerCrossSize = Math.max(specifiedCross, measuredCross);
209
417
  }
210
418
  else if (!isRow) {
211
- const referenceCross = Number.isFinite(cbCross) && cbCross > 0 ? cbCross : maxCrossContribution;
419
+ const referenceCross = Number.isFinite(defaultContentWidth) && defaultContentWidth > 0 ? defaultContentWidth : maxCrossContribution;
212
420
  containerCrossSize = Math.max(referenceCross, maxCrossContribution);
213
421
  }
214
422
  else {
215
- containerCrossSize = maxCrossContribution;
423
+ containerCrossSize = wrapEnabled ? wrappedCrossContribution : maxCrossContribution;
216
424
  }
217
425
  const minCrossValue = isRow ? node.style.minHeight : node.style.minWidth;
218
426
  const maxCrossValue = isRow ? node.style.maxHeight : node.style.maxWidth;
219
- const minCross = minCrossValue !== undefined ? resolveLength(minCrossValue, cbCross, { auto: "zero" }) : undefined;
220
- const maxCross = maxCrossValue !== undefined ? resolveLength(maxCrossValue, cbCross, { auto: "reference" }) : undefined;
427
+ const crossExtras = isRow ? vExtras : hExtras;
428
+ const minCross = minCrossValue !== undefined
429
+ ? adjustForBoxSizing(resolveLength(minCrossValue, cbCross, { auto: "zero", ...containerRefs }), node.style.boxSizing, crossExtras)
430
+ : undefined;
431
+ const maxCross = maxCrossValue !== undefined
432
+ ? adjustForBoxSizing(resolveLength(maxCrossValue, cbCross, { auto: "reference", ...containerRefs }), node.style.boxSizing, crossExtras)
433
+ : undefined;
221
434
  if (minCross !== undefined) {
222
435
  containerCrossSize = Math.max(containerCrossSize, minCross);
223
436
  }
@@ -226,30 +439,62 @@ export class FlexLayoutStrategy {
226
439
  }
227
440
  const justify = node.style.justifyContent ?? JustifyContent.FlexStart;
228
441
  const align = alignContainer;
229
- // Calculate justify spacing based on free space AFTER accounting for gaps
230
- // This makes gap additive with justify-content spacing (per CSS Flexbox spec)
231
- const freeSpaceAfterGaps = containerMainSize - totalMainWithGaps;
232
- const { offset: initialOffset, gap: justifyGap } = resolveJustifySpacing(justify, freeSpaceAfterGaps, items.length);
233
- let cursor = initialOffset;
234
- for (let index = 0; index < items.length; index++) {
235
- const item = items[index];
236
- const alignSelf = resolveItemAlignment(item.node.style.alignSelf, align);
237
- const crossOffset = computeCrossOffset(alignSelf, containerCrossSize, item.crossSize, item.crossMarginStart, item.crossMarginEnd);
238
- const previousX = item.node.box.x;
239
- const previousY = item.node.box.y;
240
- if (isRow) {
241
- item.node.box.x = boxMetrics.contentBoxX + cursor + item.mainMarginStart;
242
- item.node.box.y = boxMetrics.contentBoxY + crossOffset + item.crossMarginStart;
243
- }
244
- else {
245
- item.node.box.x = boxMetrics.contentBoxX + crossOffset + item.crossMarginStart;
246
- item.node.box.y = boxMetrics.contentBoxY + cursor + item.mainMarginStart;
442
+ if (wrapEnabled) {
443
+ const alignContent = node.style.alignContent;
444
+ const crossLayout = resolveAlignContentLayout(lines, alignContent, containerCrossSize, crossAxisGap);
445
+ let crossCursor = crossLayout.initialOffset;
446
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
447
+ const line = lines[lineIndex];
448
+ const lineCrossSize = crossLayout.lineCrossSizes[lineIndex] ?? line.crossSize;
449
+ const freeSpaceAfterGaps = containerMainSize - line.mainSizeWithGaps;
450
+ const { offset: initialOffset, gap: justifyGap } = resolveJustifySpacing(justify, freeSpaceAfterGaps, line.items.length);
451
+ let mainCursor = initialOffset;
452
+ for (let index = 0; index < line.items.length; index++) {
453
+ const item = line.items[index];
454
+ const alignSelf = resolveItemAlignment(item.node.style.alignSelf, align);
455
+ const crossOffset = computeCrossOffset(alignSelf, lineCrossSize, item.crossSize, item.crossMarginStart, item.crossMarginEnd);
456
+ const previousX = item.node.box.x;
457
+ const previousY = item.node.box.y;
458
+ item.node.box.x = boxMetrics.contentBoxX + mainCursor + item.mainMarginStart;
459
+ item.node.box.y = boxMetrics.contentBoxY + crossCursor + crossOffset + item.crossMarginStart;
460
+ offsetLayoutSubtree(item.node, item.node.box.x - previousX, item.node.box.y - previousY);
461
+ mainCursor += item.mainContribution;
462
+ if (index < line.items.length - 1) {
463
+ mainCursor += mainAxisGap + justifyGap;
464
+ }
465
+ }
466
+ crossCursor += lineCrossSize;
467
+ if (lineIndex < lines.length - 1) {
468
+ crossCursor += crossAxisGap + crossLayout.additionalGap;
469
+ }
247
470
  }
248
- offsetLayoutSubtree(item.node, item.node.box.x - previousX, item.node.box.y - previousY);
249
- cursor += item.mainContribution;
250
- if (index < items.length - 1) {
251
- // Apply explicit gap PLUS justify spacing (additive per spec)
252
- cursor += mainAxisGap + justifyGap;
471
+ }
472
+ else {
473
+ // Calculate justify spacing based on free space AFTER accounting for gaps.
474
+ // This makes gap additive with justify-content spacing (per CSS Flexbox spec).
475
+ const freeSpaceAfterGaps = containerMainSize - totalMainWithGaps;
476
+ const { offset: initialOffset, gap: justifyGap } = resolveJustifySpacing(justify, freeSpaceAfterGaps, items.length);
477
+ let cursor = initialOffset;
478
+ for (let index = 0; index < items.length; index++) {
479
+ const item = items[index];
480
+ const alignSelf = resolveItemAlignment(item.node.style.alignSelf, align);
481
+ const crossOffset = computeCrossOffset(alignSelf, containerCrossSize, item.crossSize, item.crossMarginStart, item.crossMarginEnd);
482
+ const previousX = item.node.box.x;
483
+ const previousY = item.node.box.y;
484
+ if (isRow) {
485
+ item.node.box.x = boxMetrics.contentBoxX + cursor + item.mainMarginStart;
486
+ item.node.box.y = boxMetrics.contentBoxY + crossOffset + item.crossMarginStart;
487
+ }
488
+ else {
489
+ item.node.box.x = boxMetrics.contentBoxX + crossOffset + item.crossMarginStart;
490
+ item.node.box.y = boxMetrics.contentBoxY + cursor + item.mainMarginStart;
491
+ }
492
+ offsetLayoutSubtree(item.node, item.node.box.x - previousX, item.node.box.y - previousY);
493
+ cursor += item.mainContribution;
494
+ if (index < items.length - 1) {
495
+ // Apply explicit gap PLUS justify spacing (additive per spec).
496
+ cursor += mainAxisGap + justifyGap;
497
+ }
253
498
  }
254
499
  }
255
500
  if (isRow) {
@@ -285,7 +530,7 @@ function resolveInitialDimension(specified, fallback) {
285
530
  }
286
531
  return 0;
287
532
  }
288
- function resolveFlexSize(value, reference) {
533
+ function resolveFlexSize(value, reference, containerWidth = reference, containerHeight = reference) {
289
534
  if (value === undefined) {
290
535
  return undefined;
291
536
  }
@@ -295,7 +540,7 @@ function resolveFlexSize(value, reference) {
295
540
  if (value === "auto" || isAutoLength(value)) {
296
541
  return undefined;
297
542
  }
298
- return resolveLength(value, reference, { auto: "reference" });
543
+ return resolveLength(value, reference, { auto: "reference", containerWidth, containerHeight });
299
544
  }
300
545
  function resolveItemAlignment(alignSelf, containerAlign) {
301
546
  if (alignSelf && alignSelf !== "auto") {
@@ -393,11 +638,10 @@ function offsetLayoutSubtree(node, deltaX, deltaY) {
393
638
  return;
394
639
  }
395
640
  node.walk((desc) => {
396
- if (desc === node) {
397
- return;
641
+ if (desc !== node) {
642
+ desc.box.x += deltaX;
643
+ desc.box.y += deltaY;
398
644
  }
399
- desc.box.x += deltaX;
400
- desc.box.y += deltaY;
401
645
  desc.box.baseline += deltaY;
402
646
  // Update inline runs if they exist
403
647
  if (desc.inlineRuns && desc.inlineRuns.length > 0) {
@@ -2,6 +2,8 @@ import { LayoutNode } from "../../dom/node.js";
2
2
  import type { LayoutContext, LayoutStrategy } from "../pipeline/strategy.js";
3
3
  export declare class FormLayoutStrategy implements LayoutStrategy {
4
4
  private readonly formTags;
5
+ private resolveExplicitOrAutoWidth;
6
+ private resolveExplicitOrAutoHeight;
5
7
  canLayout(node: LayoutNode): boolean;
6
8
  layout(node: LayoutNode, context: LayoutContext): void;
7
9
  }
@@ -1,5 +1,5 @@
1
1
  import { LayoutNode } from "../../dom/node.js";
2
- import { containingBlock } from "../utils/node-math.js";
2
+ import { adjustForBoxSizing, containingBlock } from "../utils/node-math.js";
3
3
  import { resolveLength } from "../../css/length.js";
4
4
  import { verticalNonContent, horizontalNonContent } from "../utils/node-math.js";
5
5
  import { finalizeOverflow } from "../utils/overflow.js";
@@ -14,6 +14,30 @@ export class FormLayoutStrategy {
14
14
  constructor() {
15
15
  this.formTags = new Set(['input', 'select', 'textarea', 'button']);
16
16
  }
17
+ resolveExplicitOrAutoWidth(node, cbWidth, autoValue, extras, containerHeight) {
18
+ const hasExplicitWidth = node.style.width !== "auto" && node.style.width !== undefined;
19
+ if (!hasExplicitWidth) {
20
+ return autoValue;
21
+ }
22
+ const specified = resolveLength(node.style.width, cbWidth, {
23
+ auto: autoValue,
24
+ containerWidth: cbWidth,
25
+ containerHeight,
26
+ });
27
+ return adjustForBoxSizing(specified, node.style.boxSizing, extras);
28
+ }
29
+ resolveExplicitOrAutoHeight(node, cbHeight, autoValue, extras, containerWidth) {
30
+ const hasExplicitHeight = node.style.height !== "auto" && node.style.height !== undefined;
31
+ if (!hasExplicitHeight) {
32
+ return autoValue;
33
+ }
34
+ const specified = resolveLength(node.style.height, cbHeight, {
35
+ auto: autoValue,
36
+ containerWidth,
37
+ containerHeight: cbHeight,
38
+ });
39
+ return adjustForBoxSizing(specified, node.style.boxSizing, extras);
40
+ }
17
41
  canLayout(node) {
18
42
  if (!node.tagName)
19
43
  return false;
@@ -21,10 +45,13 @@ export class FormLayoutStrategy {
21
45
  }
22
46
  layout(node, context) {
23
47
  const cb = containingBlock(node, context.env.viewport);
48
+ const containerRefs = { containerWidth: cb.width, containerHeight: cb.height };
24
49
  const tagName = node.tagName?.toLowerCase() ?? '';
25
50
  const formControl = node.customData?.formControl;
26
51
  let contentWidth;
27
52
  let contentHeight;
53
+ const horizontalExtras = horizontalNonContent(node, cb.width, cb.height);
54
+ const verticalExtras = verticalNonContent(node, cb.height, cb.width);
28
55
  switch (tagName) {
29
56
  case 'input': {
30
57
  const inputType = formControl?.inputType ?? 'text';
@@ -37,24 +64,24 @@ export class FormLayoutStrategy {
37
64
  contentHeight = 0;
38
65
  }
39
66
  else {
40
- contentWidth = resolveLength(node.style.width, cb.width, { auto: DEFAULT_INPUT_WIDTH });
41
- contentHeight = resolveLength(node.style.height, cb.height, { auto: DEFAULT_INPUT_HEIGHT });
67
+ contentWidth = this.resolveExplicitOrAutoWidth(node, cb.width, DEFAULT_INPUT_WIDTH, horizontalExtras, cb.height);
68
+ contentHeight = this.resolveExplicitOrAutoHeight(node, cb.height, DEFAULT_INPUT_HEIGHT, verticalExtras, cb.width);
42
69
  }
43
70
  break;
44
71
  }
45
72
  case 'select':
46
- contentWidth = resolveLength(node.style.width, cb.width, { auto: DEFAULT_INPUT_WIDTH });
47
- contentHeight = resolveLength(node.style.height, cb.height, { auto: DEFAULT_SELECT_HEIGHT });
73
+ contentWidth = this.resolveExplicitOrAutoWidth(node, cb.width, DEFAULT_INPUT_WIDTH, horizontalExtras, cb.height);
74
+ contentHeight = this.resolveExplicitOrAutoHeight(node, cb.height, DEFAULT_SELECT_HEIGHT, verticalExtras, cb.width);
48
75
  break;
49
76
  case 'textarea': {
50
- contentWidth = resolveLength(node.style.width, cb.width, { auto: DEFAULT_TEXTAREA_WIDTH });
77
+ contentWidth = this.resolveExplicitOrAutoWidth(node, cb.width, DEFAULT_TEXTAREA_WIDTH, horizontalExtras, cb.height);
51
78
  const rows = formControl?.rows ?? 3;
52
- contentHeight = resolveLength(node.style.height, cb.height, { auto: rows * 24 });
79
+ contentHeight = this.resolveExplicitOrAutoHeight(node, cb.height, rows * 24, verticalExtras, cb.width);
53
80
  break;
54
81
  }
55
82
  case 'button':
56
- contentWidth = resolveLength(node.style.width, cb.width, { auto: DEFAULT_BUTTON_MIN_WIDTH });
57
- contentHeight = resolveLength(node.style.height, cb.height, { auto: DEFAULT_BUTTON_HEIGHT });
83
+ contentWidth = this.resolveExplicitOrAutoWidth(node, cb.width, DEFAULT_BUTTON_MIN_WIDTH, horizontalExtras, cb.height);
84
+ contentHeight = this.resolveExplicitOrAutoHeight(node, cb.height, DEFAULT_BUTTON_HEIGHT, verticalExtras, cb.width);
58
85
  break;
59
86
  default:
60
87
  contentWidth = DEFAULT_INPUT_WIDTH;
@@ -62,14 +89,12 @@ export class FormLayoutStrategy {
62
89
  }
63
90
  node.box.contentWidth = Math.max(0, contentWidth);
64
91
  node.box.contentHeight = Math.max(0, contentHeight);
65
- const horizontalExtras = horizontalNonContent(node, contentWidth);
66
92
  node.box.borderBoxWidth = node.box.contentWidth + horizontalExtras;
67
- const verticalExtras = verticalNonContent(node, contentWidth);
68
93
  node.box.borderBoxHeight = node.box.contentHeight + verticalExtras;
69
94
  node.box.x = cb.x;
70
95
  node.box.y = cb.y;
71
- const marginLeft = resolveLength(node.style.marginLeft, cb.width, { auto: "zero" });
72
- const marginRight = resolveLength(node.style.marginRight, cb.width, { auto: "zero" });
96
+ const marginLeft = resolveLength(node.style.marginLeft, cb.width, { auto: "zero", ...containerRefs });
97
+ const marginRight = resolveLength(node.style.marginRight, cb.width, { auto: "zero", ...containerRefs });
73
98
  node.box.usedMarginLeft = marginLeft;
74
99
  node.box.usedMarginRight = marginRight;
75
100
  node.box.marginBoxWidth = node.box.borderBoxWidth + marginLeft + marginRight;