@react-x11/components 0.9.0 → 0.11.0

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 (579) hide show
  1. package/README.md +10 -4
  2. package/dist/charts/node.d.ts +11 -1
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +44 -14
  5. package/dist/charts/node.js.map +1 -1
  6. package/dist/charts/render.d.ts +12 -3
  7. package/dist/charts/render.d.ts.map +1 -1
  8. package/dist/charts/render.js +94 -25
  9. package/dist/charts/render.js.map +1 -1
  10. package/dist/code/index.d.ts.map +1 -1
  11. package/dist/code/index.js +27 -24
  12. package/dist/code/index.js.map +1 -1
  13. package/dist/code-editor/completion.d.ts.map +1 -1
  14. package/dist/code-editor/completion.js +3 -2
  15. package/dist/code-editor/completion.js.map +1 -1
  16. package/dist/code-editor/index.d.ts.map +1 -1
  17. package/dist/code-editor/index.js +19 -0
  18. package/dist/code-editor/index.js.map +1 -1
  19. package/dist/code-editor/node.d.ts +177 -7
  20. package/dist/code-editor/node.d.ts.map +1 -1
  21. package/dist/code-editor/node.js +1329 -164
  22. package/dist/code-editor/node.js.map +1 -1
  23. package/dist/code-language/index.d.ts +1 -1
  24. package/dist/code-language/index.d.ts.map +1 -1
  25. package/dist/code-language/index.js +1 -1
  26. package/dist/code-language/index.js.map +1 -1
  27. package/dist/code-language/languages/glsl.d.ts.map +1 -1
  28. package/dist/code-language/languages/glsl.js +10 -2
  29. package/dist/code-language/languages/glsl.js.map +1 -1
  30. package/dist/code-language/languages/javascript.d.ts.map +1 -1
  31. package/dist/code-language/languages/javascript.js +18 -3
  32. package/dist/code-language/languages/javascript.js.map +1 -1
  33. package/dist/code-language/languages/json.d.ts.map +1 -1
  34. package/dist/code-language/languages/json.js +10 -2
  35. package/dist/code-language/languages/json.js.map +1 -1
  36. package/dist/code-language/languages/shell.d.ts.map +1 -1
  37. package/dist/code-language/languages/shell.js +10 -2
  38. package/dist/code-language/languages/shell.js.map +1 -1
  39. package/dist/code-language/languages/sql.d.ts.map +1 -1
  40. package/dist/code-language/languages/sql.js +20 -2
  41. package/dist/code-language/languages/sql.js.map +1 -1
  42. package/dist/code-language/runs.d.ts +31 -0
  43. package/dist/code-language/runs.d.ts.map +1 -1
  44. package/dist/code-language/runs.js +105 -0
  45. package/dist/code-language/runs.js.map +1 -1
  46. package/dist/code-language/stream.d.ts +22 -0
  47. package/dist/code-language/stream.d.ts.map +1 -1
  48. package/dist/code-language/stream.js +259 -9
  49. package/dist/code-language/stream.js.map +1 -1
  50. package/dist/code-language/types.d.ts +5 -4
  51. package/dist/code-language/types.d.ts.map +1 -1
  52. package/dist/codeblock/index.d.ts +26 -2
  53. package/dist/codeblock/index.d.ts.map +1 -1
  54. package/dist/codeblock/index.js +57 -4
  55. package/dist/codeblock/index.js.map +1 -1
  56. package/dist/flow/draw.d.ts +50 -0
  57. package/dist/flow/draw.d.ts.map +1 -1
  58. package/dist/flow/draw.js +132 -33
  59. package/dist/flow/draw.js.map +1 -1
  60. package/dist/flow/gl/color.d.ts +13 -0
  61. package/dist/flow/gl/color.d.ts.map +1 -0
  62. package/dist/flow/gl/color.js +82 -0
  63. package/dist/flow/gl/color.js.map +1 -0
  64. package/dist/flow/gl/index.d.ts +60 -0
  65. package/dist/flow/gl/index.d.ts.map +1 -0
  66. package/dist/flow/gl/index.js +283 -0
  67. package/dist/flow/gl/index.js.map +1 -0
  68. package/dist/flow/gl/pack.d.ts +133 -0
  69. package/dist/flow/gl/pack.d.ts.map +1 -0
  70. package/dist/flow/gl/pack.js +408 -0
  71. package/dist/flow/gl/pack.js.map +1 -0
  72. package/dist/flow/gl/renderer.d.ts +95 -0
  73. package/dist/flow/gl/renderer.d.ts.map +1 -0
  74. package/dist/flow/gl/renderer.js +459 -0
  75. package/dist/flow/gl/renderer.js.map +1 -0
  76. package/dist/flow/gl/shaders.d.ts +79 -0
  77. package/dist/flow/gl/shaders.d.ts.map +1 -0
  78. package/dist/flow/gl/shaders.js +346 -0
  79. package/dist/flow/gl/shaders.js.map +1 -0
  80. package/dist/flow/gl/text.d.ts +318 -0
  81. package/dist/flow/gl/text.d.ts.map +1 -0
  82. package/dist/flow/gl/text.js +1103 -0
  83. package/dist/flow/gl/text.js.map +1 -0
  84. package/dist/flow/index.d.ts +1 -1
  85. package/dist/flow/index.d.ts.map +1 -1
  86. package/dist/flow/index.js +619 -64
  87. package/dist/flow/index.js.map +1 -1
  88. package/dist/flow/node.d.ts +621 -72
  89. package/dist/flow/node.d.ts.map +1 -1
  90. package/dist/flow/node.js +2253 -1018
  91. package/dist/flow/node.js.map +1 -1
  92. package/dist/flow/paint.d.ts +35 -0
  93. package/dist/flow/paint.d.ts.map +1 -0
  94. package/dist/flow/paint.js +393 -0
  95. package/dist/flow/paint.js.map +1 -0
  96. package/dist/flow/paths.d.ts +14 -0
  97. package/dist/flow/paths.d.ts.map +1 -1
  98. package/dist/flow/paths.js +45 -19
  99. package/dist/flow/paths.js.map +1 -1
  100. package/dist/flow/scene.d.ts +584 -0
  101. package/dist/flow/scene.d.ts.map +1 -0
  102. package/dist/flow/scene.js +1453 -0
  103. package/dist/flow/scene.js.map +1 -0
  104. package/dist/flow/types.d.ts +111 -1
  105. package/dist/flow/types.d.ts.map +1 -1
  106. package/dist/html/controls.d.ts +28 -0
  107. package/dist/html/controls.d.ts.map +1 -1
  108. package/dist/html/controls.js +52 -2
  109. package/dist/html/controls.js.map +1 -1
  110. package/dist/html/css/calc.d.ts +14 -0
  111. package/dist/html/css/calc.d.ts.map +1 -0
  112. package/dist/html/css/calc.js +344 -0
  113. package/dist/html/css/calc.js.map +1 -0
  114. package/dist/html/css/cascade.d.ts +262 -6
  115. package/dist/html/css/cascade.d.ts.map +1 -1
  116. package/dist/html/css/cascade.js +1373 -67
  117. package/dist/html/css/cascade.js.map +1 -1
  118. package/dist/html/css/color.d.ts +23 -0
  119. package/dist/html/css/color.d.ts.map +1 -0
  120. package/dist/html/css/color.js +740 -0
  121. package/dist/html/css/color.js.map +1 -0
  122. package/dist/html/css/content.d.ts +92 -0
  123. package/dist/html/css/content.d.ts.map +1 -0
  124. package/dist/html/css/content.js +388 -0
  125. package/dist/html/css/content.js.map +1 -0
  126. package/dist/html/css/counter-styles.d.ts +76 -0
  127. package/dist/html/css/counter-styles.d.ts.map +1 -0
  128. package/dist/html/css/counter-styles.js +893 -0
  129. package/dist/html/css/counter-styles.js.map +1 -0
  130. package/dist/html/css/decode.d.ts +12 -0
  131. package/dist/html/css/decode.d.ts.map +1 -0
  132. package/dist/html/css/decode.js +116 -0
  133. package/dist/html/css/decode.js.map +1 -0
  134. package/dist/html/css/parse.d.ts +110 -10
  135. package/dist/html/css/parse.d.ts.map +1 -1
  136. package/dist/html/css/parse.js +1473 -180
  137. package/dist/html/css/parse.js.map +1 -1
  138. package/dist/html/css/style.d.ts +501 -15
  139. package/dist/html/css/style.d.ts.map +1 -1
  140. package/dist/html/css/style.js +3729 -206
  141. package/dist/html/css/style.js.map +1 -1
  142. package/dist/html/css/ua.d.ts.map +1 -1
  143. package/dist/html/css/ua.js +65 -16
  144. package/dist/html/css/ua.js.map +1 -1
  145. package/dist/html/css/values.d.ts +37 -3
  146. package/dist/html/css/values.d.ts.map +1 -1
  147. package/dist/html/css/values.js +122 -48
  148. package/dist/html/css/values.js.map +1 -1
  149. package/dist/html/css/vars.d.ts +52 -0
  150. package/dist/html/css/vars.d.ts.map +1 -0
  151. package/dist/html/css/vars.js +358 -0
  152. package/dist/html/css/vars.js.map +1 -0
  153. package/dist/html/dom.d.ts +30 -4
  154. package/dist/html/dom.d.ts.map +1 -1
  155. package/dist/html/dom.js +278 -17
  156. package/dist/html/dom.js.map +1 -1
  157. package/dist/html/fonts.d.ts +106 -0
  158. package/dist/html/fonts.d.ts.map +1 -0
  159. package/dist/html/fonts.js +565 -0
  160. package/dist/html/fonts.js.map +1 -0
  161. package/dist/html/gif.d.ts +15 -0
  162. package/dist/html/gif.d.ts.map +1 -0
  163. package/dist/html/gif.js +229 -0
  164. package/dist/html/gif.js.map +1 -0
  165. package/dist/html/index.d.ts +50 -11
  166. package/dist/html/index.d.ts.map +1 -1
  167. package/dist/html/index.js +115 -9
  168. package/dist/html/index.js.map +1 -1
  169. package/dist/html/layout/block.d.ts +226 -3
  170. package/dist/html/layout/block.d.ts.map +1 -1
  171. package/dist/html/layout/block.js +3077 -219
  172. package/dist/html/layout/block.js.map +1 -1
  173. package/dist/html/layout/boxes.d.ts +367 -17
  174. package/dist/html/layout/boxes.d.ts.map +1 -1
  175. package/dist/html/layout/boxes.js +1742 -182
  176. package/dist/html/layout/boxes.js.map +1 -1
  177. package/dist/html/layout/cache.d.ts +58 -0
  178. package/dist/html/layout/cache.d.ts.map +1 -0
  179. package/dist/html/layout/cache.js +224 -0
  180. package/dist/html/layout/cache.js.map +1 -0
  181. package/dist/html/layout/collapse.d.ts +45 -0
  182. package/dist/html/layout/collapse.d.ts.map +1 -0
  183. package/dist/html/layout/collapse.js +246 -0
  184. package/dist/html/layout/collapse.js.map +1 -0
  185. package/dist/html/layout/css-grid.d.ts +7 -0
  186. package/dist/html/layout/css-grid.d.ts.map +1 -0
  187. package/dist/html/layout/css-grid.js +900 -0
  188. package/dist/html/layout/css-grid.js.map +1 -0
  189. package/dist/html/layout/flex.d.ts.map +1 -1
  190. package/dist/html/layout/flex.js +1025 -89
  191. package/dist/html/layout/flex.js.map +1 -1
  192. package/dist/html/layout/floats.d.ts +45 -6
  193. package/dist/html/layout/floats.d.ts.map +1 -1
  194. package/dist/html/layout/floats.js +113 -14
  195. package/dist/html/layout/floats.js.map +1 -1
  196. package/dist/html/layout/grid-lines.d.ts +39 -0
  197. package/dist/html/layout/grid-lines.d.ts.map +1 -0
  198. package/dist/html/layout/grid-lines.js +119 -0
  199. package/dist/html/layout/grid-lines.js.map +1 -0
  200. package/dist/html/layout/grid.d.ts +53 -0
  201. package/dist/html/layout/grid.d.ts.map +1 -0
  202. package/dist/html/layout/grid.js +153 -0
  203. package/dist/html/layout/grid.js.map +1 -0
  204. package/dist/html/layout/inline.d.ts +154 -1
  205. package/dist/html/layout/inline.d.ts.map +1 -1
  206. package/dist/html/layout/inline.js +3598 -175
  207. package/dist/html/layout/inline.js.map +1 -1
  208. package/dist/html/layout/shaping.d.ts +6 -0
  209. package/dist/html/layout/shaping.d.ts.map +1 -0
  210. package/dist/html/layout/shaping.js +100 -0
  211. package/dist/html/layout/shaping.js.map +1 -0
  212. package/dist/html/layout/table.d.ts +3 -0
  213. package/dist/html/layout/table.d.ts.map +1 -1
  214. package/dist/html/layout/table.js +681 -175
  215. package/dist/html/layout/table.js.map +1 -1
  216. package/dist/html/layout/tracks.d.ts +44 -0
  217. package/dist/html/layout/tracks.d.ts.map +1 -0
  218. package/dist/html/layout/tracks.js +349 -0
  219. package/dist/html/layout/tracks.js.map +1 -0
  220. package/dist/html/node.d.ts +169 -4
  221. package/dist/html/node.d.ts.map +1 -1
  222. package/dist/html/node.js +1429 -137
  223. package/dist/html/node.js.map +1 -1
  224. package/dist/html/paint.d.ts +92 -3
  225. package/dist/html/paint.d.ts.map +1 -1
  226. package/dist/html/paint.js +3635 -107
  227. package/dist/html/paint.js.map +1 -1
  228. package/dist/html/resources.d.ts +60 -14
  229. package/dist/html/resources.d.ts.map +1 -1
  230. package/dist/html/resources.js +122 -36
  231. package/dist/html/resources.js.map +1 -1
  232. package/dist/html/surfaces.d.ts +23 -0
  233. package/dist/html/surfaces.d.ts.map +1 -0
  234. package/dist/html/surfaces.js +68 -0
  235. package/dist/html/surfaces.js.map +1 -0
  236. package/dist/html/svg.d.ts +80 -0
  237. package/dist/html/svg.d.ts.map +1 -0
  238. package/dist/html/svg.js +442 -0
  239. package/dist/html/svg.js.map +1 -0
  240. package/dist/html/url.d.ts +23 -0
  241. package/dist/html/url.d.ts.map +1 -0
  242. package/dist/html/url.js +80 -0
  243. package/dist/html/url.js.map +1 -0
  244. package/dist/index.d.ts +1 -1
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js.map +1 -1
  247. package/dist/internal/codelines.d.ts +42 -0
  248. package/dist/internal/codelines.d.ts.map +1 -0
  249. package/dist/internal/codelines.js +170 -0
  250. package/dist/internal/codelines.js.map +1 -0
  251. package/dist/internal/collate.d.ts +3 -0
  252. package/dist/internal/collate.d.ts.map +1 -0
  253. package/dist/internal/collate.js +11 -0
  254. package/dist/internal/collate.js.map +1 -0
  255. package/dist/internal/markdown/ast.d.ts +11 -0
  256. package/dist/internal/markdown/ast.d.ts.map +1 -1
  257. package/dist/internal/markdown/parse.d.ts.map +1 -1
  258. package/dist/internal/markdown/parse.js +109 -8
  259. package/dist/internal/markdown/parse.js.map +1 -1
  260. package/dist/internal/prewarm.d.ts +18 -0
  261. package/dist/internal/prewarm.d.ts.map +1 -0
  262. package/dist/internal/prewarm.js +42 -0
  263. package/dist/internal/prewarm.js.map +1 -0
  264. package/dist/internal/sdf.d.ts +20 -0
  265. package/dist/internal/sdf.d.ts.map +1 -0
  266. package/dist/internal/sdf.js +216 -0
  267. package/dist/internal/sdf.js.map +1 -0
  268. package/dist/internal/splice.d.ts +12 -0
  269. package/dist/internal/splice.d.ts.map +1 -0
  270. package/dist/internal/splice.js +23 -0
  271. package/dist/internal/splice.js.map +1 -0
  272. package/dist/internal/window.d.ts +12 -0
  273. package/dist/internal/window.d.ts.map +1 -1
  274. package/dist/internal/window.js +23 -6
  275. package/dist/internal/window.js.map +1 -1
  276. package/dist/maps/anchors.d.ts +6 -1
  277. package/dist/maps/anchors.d.ts.map +1 -1
  278. package/dist/maps/anchors.js +260 -1
  279. package/dist/maps/anchors.js.map +1 -1
  280. package/dist/maps/controller.d.ts +57 -0
  281. package/dist/maps/controller.d.ts.map +1 -1
  282. package/dist/maps/controller.js +97 -18
  283. package/dist/maps/controller.js.map +1 -1
  284. package/dist/maps/gl/buckets.d.ts +5 -1
  285. package/dist/maps/gl/buckets.d.ts.map +1 -1
  286. package/dist/maps/gl/buckets.js +5 -2
  287. package/dist/maps/gl/buckets.js.map +1 -1
  288. package/dist/maps/gl/build-worker.d.ts +2 -0
  289. package/dist/maps/gl/build-worker.d.ts.map +1 -1
  290. package/dist/maps/gl/build-worker.js +1 -1
  291. package/dist/maps/gl/build-worker.js.map +1 -1
  292. package/dist/maps/gl/cover.d.ts +23 -4
  293. package/dist/maps/gl/cover.d.ts.map +1 -1
  294. package/dist/maps/gl/cover.js +61 -48
  295. package/dist/maps/gl/cover.js.map +1 -1
  296. package/dist/maps/gl/placement.d.ts +21 -8
  297. package/dist/maps/gl/placement.d.ts.map +1 -1
  298. package/dist/maps/gl/placement.js +133 -58
  299. package/dist/maps/gl/placement.js.map +1 -1
  300. package/dist/maps/gl/renderer.d.ts +36 -4
  301. package/dist/maps/gl/renderer.d.ts.map +1 -1
  302. package/dist/maps/gl/renderer.js +81 -24
  303. package/dist/maps/gl/renderer.js.map +1 -1
  304. package/dist/maps/gl/shaders.d.ts +18 -17
  305. package/dist/maps/gl/shaders.d.ts.map +1 -1
  306. package/dist/maps/gl/shaders.js +34 -32
  307. package/dist/maps/gl/shaders.js.map +1 -1
  308. package/dist/maps/gl/store.d.ts +10 -0
  309. package/dist/maps/gl/store.d.ts.map +1 -1
  310. package/dist/maps/gl/store.js +47 -4
  311. package/dist/maps/gl/store.js.map +1 -1
  312. package/dist/maps/gl/text.d.ts +153 -24
  313. package/dist/maps/gl/text.d.ts.map +1 -1
  314. package/dist/maps/gl/text.js +379 -63
  315. package/dist/maps/gl/text.js.map +1 -1
  316. package/dist/maps/gl/view.d.ts +293 -2
  317. package/dist/maps/gl/view.d.ts.map +1 -1
  318. package/dist/maps/gl/view.js +317 -82
  319. package/dist/maps/gl/view.js.map +1 -1
  320. package/dist/maps/icons.d.ts +32 -0
  321. package/dist/maps/icons.d.ts.map +1 -0
  322. package/dist/maps/icons.js +151 -0
  323. package/dist/maps/icons.js.map +1 -0
  324. package/dist/maps/index.d.ts +2 -0
  325. package/dist/maps/index.d.ts.map +1 -1
  326. package/dist/maps/index.js +2 -0
  327. package/dist/maps/index.js.map +1 -1
  328. package/dist/maps/labels.d.ts +56 -4
  329. package/dist/maps/labels.d.ts.map +1 -1
  330. package/dist/maps/labels.js +213 -46
  331. package/dist/maps/labels.js.map +1 -1
  332. package/dist/maps/node.d.ts +17 -0
  333. package/dist/maps/node.d.ts.map +1 -1
  334. package/dist/maps/node.js +64 -30
  335. package/dist/maps/node.js.map +1 -1
  336. package/dist/maps/proj.d.ts +48 -0
  337. package/dist/maps/proj.d.ts.map +1 -1
  338. package/dist/maps/proj.js +73 -0
  339. package/dist/maps/proj.js.map +1 -1
  340. package/dist/maps/sources.d.ts +5 -3
  341. package/dist/maps/sources.d.ts.map +1 -1
  342. package/dist/maps/sources.js +5 -3
  343. package/dist/maps/sources.js.map +1 -1
  344. package/dist/maps/style.d.ts +35 -0
  345. package/dist/maps/style.d.ts.map +1 -1
  346. package/dist/maps/style.js.map +1 -1
  347. package/dist/maps/styles.d.ts +12 -0
  348. package/dist/maps/styles.d.ts.map +1 -1
  349. package/dist/maps/styles.js +120 -6
  350. package/dist/maps/styles.js.map +1 -1
  351. package/dist/maps/tiles.d.ts +31 -11
  352. package/dist/maps/tiles.d.ts.map +1 -1
  353. package/dist/maps/tiles.js +61 -27
  354. package/dist/maps/tiles.js.map +1 -1
  355. package/dist/maps/types.d.ts +28 -4
  356. package/dist/maps/types.d.ts.map +1 -1
  357. package/dist/markdown/index.d.ts.map +1 -1
  358. package/dist/markdown/index.js +209 -68
  359. package/dist/markdown/index.js.map +1 -1
  360. package/dist/markdown/spans.d.ts.map +1 -1
  361. package/dist/markdown/spans.js +6 -0
  362. package/dist/markdown/spans.js.map +1 -1
  363. package/dist/qml/containers.d.ts.map +1 -1
  364. package/dist/qml/containers.js +6 -1
  365. package/dist/qml/containers.js.map +1 -1
  366. package/dist/qml/interaction.d.ts.map +1 -1
  367. package/dist/qml/interaction.js +38 -5
  368. package/dist/qml/interaction.js.map +1 -1
  369. package/dist/qml/layouts.d.ts.map +1 -1
  370. package/dist/qml/layouts.js +21 -6
  371. package/dist/qml/layouts.js.map +1 -1
  372. package/dist/qml/qtquick.d.ts.map +1 -1
  373. package/dist/qml/qtquick.js +9 -2
  374. package/dist/qml/qtquick.js.map +1 -1
  375. package/dist/qml/react.d.ts.map +1 -1
  376. package/dist/qml/react.js +9 -7
  377. package/dist/qml/react.js.map +1 -1
  378. package/dist/reorder/clock.d.ts +7 -0
  379. package/dist/reorder/clock.d.ts.map +1 -0
  380. package/dist/reorder/clock.js +26 -0
  381. package/dist/reorder/clock.js.map +1 -0
  382. package/dist/reorder/index.d.ts +25 -3
  383. package/dist/reorder/index.d.ts.map +1 -1
  384. package/dist/reorder/index.js +64 -19
  385. package/dist/reorder/index.js.map +1 -1
  386. package/dist/rich-text-editor/index.d.ts +0 -11
  387. package/dist/rich-text-editor/index.d.ts.map +1 -1
  388. package/dist/rich-text-editor/index.js +38 -9
  389. package/dist/rich-text-editor/index.js.map +1 -1
  390. package/dist/rich-text-editor/keymap.d.ts.map +1 -1
  391. package/dist/rich-text-editor/keymap.js +2 -1
  392. package/dist/rich-text-editor/keymap.js.map +1 -1
  393. package/dist/rich-text-editor/keys.d.ts +17 -6
  394. package/dist/rich-text-editor/keys.d.ts.map +1 -1
  395. package/dist/rich-text-editor/keys.js +135 -26
  396. package/dist/rich-text-editor/keys.js.map +1 -1
  397. package/dist/rich-text-editor/markdown.d.ts.map +1 -1
  398. package/dist/rich-text-editor/markdown.js +30 -1
  399. package/dist/rich-text-editor/markdown.js.map +1 -1
  400. package/dist/rich-text-editor/marks.d.ts +9 -0
  401. package/dist/rich-text-editor/marks.d.ts.map +1 -0
  402. package/dist/rich-text-editor/marks.js +83 -0
  403. package/dist/rich-text-editor/marks.js.map +1 -0
  404. package/dist/rich-text-editor/render.d.ts.map +1 -1
  405. package/dist/rich-text-editor/render.js +28 -10
  406. package/dist/rich-text-editor/render.js.map +1 -1
  407. package/dist/rich-text-editor/replace.d.ts +13 -0
  408. package/dist/rich-text-editor/replace.d.ts.map +1 -0
  409. package/dist/rich-text-editor/replace.js +64 -0
  410. package/dist/rich-text-editor/replace.js.map +1 -0
  411. package/dist/rich-text-editor/toolbar.d.ts.map +1 -1
  412. package/dist/rich-text-editor/toolbar.js +1 -1
  413. package/dist/rich-text-editor/toolbar.js.map +1 -1
  414. package/dist/rich-text-editor/virtual.d.ts.map +1 -1
  415. package/dist/rich-text-editor/virtual.js +17 -0
  416. package/dist/rich-text-editor/virtual.js.map +1 -1
  417. package/dist/richtext/node.d.ts +46 -2
  418. package/dist/richtext/node.d.ts.map +1 -1
  419. package/dist/richtext/node.js +187 -4
  420. package/dist/richtext/node.js.map +1 -1
  421. package/dist/richtext/runs.d.ts +13 -8
  422. package/dist/richtext/runs.d.ts.map +1 -1
  423. package/dist/richtext/runs.js +18 -8
  424. package/dist/richtext/runs.js.map +1 -1
  425. package/dist/table/index.d.ts.map +1 -1
  426. package/dist/table/index.js +23 -0
  427. package/dist/table/index.js.map +1 -1
  428. package/dist/table/rows.d.ts +3 -2
  429. package/dist/table/rows.d.ts.map +1 -1
  430. package/dist/table/rows.js +20 -6
  431. package/dist/table/rows.js.map +1 -1
  432. package/dist/terminal/index.d.ts.map +1 -1
  433. package/dist/terminal/index.js +4 -0
  434. package/dist/terminal/index.js.map +1 -1
  435. package/dist/terminal/vt/pty.d.ts +13 -2
  436. package/dist/terminal/vt/pty.d.ts.map +1 -1
  437. package/dist/terminal/vt/pty.js +18 -3
  438. package/dist/terminal/vt/pty.js.map +1 -1
  439. package/dist/terminal-output/index.d.ts.map +1 -1
  440. package/dist/terminal-output/index.js +103 -23
  441. package/dist/terminal-output/index.js.map +1 -1
  442. package/dist/terminal-output/runs.d.ts +4 -1
  443. package/dist/terminal-output/runs.d.ts.map +1 -1
  444. package/dist/terminal-output/runs.js +5 -1
  445. package/dist/terminal-output/runs.js.map +1 -1
  446. package/dist/three/canvas.d.ts.map +1 -1
  447. package/dist/three/canvas.js +85 -7
  448. package/dist/three/canvas.js.map +1 -1
  449. package/dist/three/events.d.ts +22 -4
  450. package/dist/three/events.d.ts.map +1 -1
  451. package/dist/three/events.js +34 -4
  452. package/dist/three/events.js.map +1 -1
  453. package/dist/tree/index.d.ts.map +1 -1
  454. package/dist/tree/index.js +17 -0
  455. package/dist/tree/index.js.map +1 -1
  456. package/package.json +42 -33
  457. package/src/charts/node.ts +62 -26
  458. package/src/charts/render.ts +120 -30
  459. package/src/code/index.ts +36 -32
  460. package/src/code-editor/completion.ts +3 -2
  461. package/src/code-editor/index.ts +19 -0
  462. package/src/code-editor/node.ts +1574 -184
  463. package/src/code-language/index.ts +1 -1
  464. package/src/code-language/languages/glsl.ts +12 -2
  465. package/src/code-language/languages/javascript.ts +21 -4
  466. package/src/code-language/languages/json.ts +12 -2
  467. package/src/code-language/languages/shell.ts +12 -2
  468. package/src/code-language/languages/sql.ts +22 -2
  469. package/src/code-language/runs.ts +111 -1
  470. package/src/code-language/stream.ts +257 -17
  471. package/src/code-language/types.ts +5 -4
  472. package/src/codeblock/index.ts +68 -4
  473. package/src/flow/draw.ts +167 -41
  474. package/src/flow/gl/color.ts +85 -0
  475. package/src/flow/gl/index.ts +342 -0
  476. package/src/flow/gl/pack.ts +585 -0
  477. package/src/flow/gl/renderer.ts +679 -0
  478. package/src/flow/gl/shaders.ts +371 -0
  479. package/src/flow/gl/text.ts +1295 -0
  480. package/src/flow/index.ts +794 -67
  481. package/src/flow/node.ts +2496 -1256
  482. package/src/flow/paint.ts +511 -0
  483. package/src/flow/paths.ts +80 -15
  484. package/src/flow/scene.ts +2199 -0
  485. package/src/flow/types.ts +113 -1
  486. package/src/html/controls.ts +73 -2
  487. package/src/html/css/calc.ts +385 -0
  488. package/src/html/css/cascade.ts +1481 -62
  489. package/src/html/css/color.ts +755 -0
  490. package/src/html/css/content.ts +417 -0
  491. package/src/html/css/counter-styles.ts +973 -0
  492. package/src/html/css/decode.ts +130 -0
  493. package/src/html/css/parse.ts +1514 -160
  494. package/src/html/css/style.ts +4131 -223
  495. package/src/html/css/ua.ts +66 -17
  496. package/src/html/css/values.ts +150 -50
  497. package/src/html/css/vars.ts +348 -0
  498. package/src/html/dom.ts +284 -17
  499. package/src/html/fonts.ts +641 -0
  500. package/src/html/gif.ts +233 -0
  501. package/src/html/index.ts +180 -20
  502. package/src/html/layout/block.ts +3560 -208
  503. package/src/html/layout/boxes.ts +2141 -190
  504. package/src/html/layout/cache.ts +289 -0
  505. package/src/html/layout/collapse.ts +317 -0
  506. package/src/html/layout/css-grid.ts +1063 -0
  507. package/src/html/layout/flex.ts +1115 -106
  508. package/src/html/layout/floats.ts +128 -14
  509. package/src/html/layout/grid-lines.ts +141 -0
  510. package/src/html/layout/grid.ts +181 -0
  511. package/src/html/layout/inline.ts +4187 -192
  512. package/src/html/layout/shaping.ts +140 -0
  513. package/src/html/layout/table.ts +696 -186
  514. package/src/html/layout/tracks.ts +437 -0
  515. package/src/html/node.ts +1585 -156
  516. package/src/html/paint.ts +4582 -267
  517. package/src/html/resources.ts +161 -50
  518. package/src/html/surfaces.ts +81 -0
  519. package/src/html/svg.ts +504 -0
  520. package/src/html/url.ts +86 -0
  521. package/src/index.ts +2 -0
  522. package/src/internal/codelines.ts +200 -0
  523. package/src/internal/collate.ts +12 -0
  524. package/src/internal/markdown/ast.ts +11 -0
  525. package/src/internal/markdown/parse.ts +154 -11
  526. package/src/internal/prewarm.ts +45 -0
  527. package/src/internal/sdf.ts +227 -0
  528. package/src/internal/splice.ts +25 -0
  529. package/src/internal/window.ts +36 -5
  530. package/src/maps/anchors.ts +307 -1
  531. package/src/maps/controller.ts +116 -31
  532. package/src/maps/gl/buckets.ts +5 -1
  533. package/src/maps/gl/build-worker.ts +13 -2
  534. package/src/maps/gl/cover.ts +79 -44
  535. package/src/maps/gl/placement.ts +221 -69
  536. package/src/maps/gl/renderer.ts +104 -20
  537. package/src/maps/gl/shaders.ts +35 -32
  538. package/src/maps/gl/store.ts +66 -17
  539. package/src/maps/gl/text.ts +444 -71
  540. package/src/maps/gl/view.ts +361 -96
  541. package/src/maps/icons.ts +183 -0
  542. package/src/maps/index.ts +4 -0
  543. package/src/maps/labels.ts +269 -49
  544. package/src/maps/node.ts +82 -49
  545. package/src/maps/proj.ts +87 -0
  546. package/src/maps/sources.ts +5 -3
  547. package/src/maps/style.ts +32 -0
  548. package/src/maps/styles.ts +215 -4
  549. package/src/maps/tiles.ts +64 -31
  550. package/src/maps/types.ts +28 -4
  551. package/src/markdown/index.ts +293 -92
  552. package/src/markdown/spans.ts +5 -0
  553. package/src/qml/containers.tsx +6 -1
  554. package/src/qml/interaction.tsx +42 -5
  555. package/src/qml/layouts.tsx +21 -6
  556. package/src/qml/qtquick.tsx +9 -2
  557. package/src/qml/react.tsx +7 -5
  558. package/src/reorder/clock.ts +27 -0
  559. package/src/reorder/index.ts +100 -27
  560. package/src/rich-text-editor/index.ts +37 -12
  561. package/src/rich-text-editor/keymap.ts +1 -1
  562. package/src/rich-text-editor/keys.ts +135 -26
  563. package/src/rich-text-editor/markdown.ts +36 -1
  564. package/src/rich-text-editor/marks.ts +101 -0
  565. package/src/rich-text-editor/render.ts +49 -12
  566. package/src/rich-text-editor/replace.ts +85 -0
  567. package/src/rich-text-editor/toolbar.ts +1 -1
  568. package/src/rich-text-editor/virtual.ts +25 -0
  569. package/src/richtext/node.ts +235 -14
  570. package/src/richtext/runs.ts +23 -12
  571. package/src/table/index.ts +29 -0
  572. package/src/table/rows.ts +18 -8
  573. package/src/terminal/index.ts +4 -0
  574. package/src/terminal/vt/pty.ts +16 -2
  575. package/src/terminal-output/index.ts +129 -31
  576. package/src/terminal-output/runs.ts +16 -2
  577. package/src/three/canvas.ts +102 -8
  578. package/src/three/events.ts +49 -9
  579. package/src/tree/index.ts +19 -0
@@ -10,13 +10,16 @@
10
10
  // Float coordinates are absolute for the same reason, which is what lets the
11
11
  // inline pass ask "how wide is the line at this y" without knowing whose
12
12
  // formatting context it is inside.
13
- import { AUTO, isPct, resolve, resolveOrNull } from '../css/values.js';
14
- import { Box } from './boxes.js';
13
+ import { AUTO, gapOf, isPct, isTransparent, resolve, resolveOrNull, } from '../css/values.js';
14
+ import { CONTAIN_INLINE_SIZE, CONTAIN_LAYOUT, CONTAIN_PAINT, CONTAIN_SIZE, scrolls, } from '../css/style.js';
15
+ import { lineOf, spanToName } from './grid-lines.js';
16
+ import { BOX_RAISES, Box, CLAMPED, CUT_BLOCKS, GRID_TRACKS, FIRST_LINE, INLINE_OFFSETS, MOVED_OFF_LINES, LINE_BOX_RAISES, SHIFTED_LINES, TEXT_SHIFTS, isBlank, } from './boxes.js';
15
17
  import { FloatContext } from './floats.js';
16
- import { layoutInline } from './inline.js';
18
+ import { faceExtentOf, layoutInline, lineHeightMultiplier, spaceBreaksOnly, strutOf, widestWord, } from './inline.js';
17
19
  import { layoutFlex } from './flex.js';
18
- import { layoutTable } from './table.js';
19
- import { computePaintBounds } from '../paint.js';
20
+ import { finishCaptions, layoutTable } from './table.js';
21
+ import { collapseEdges } from './collapse.js';
22
+ import { computePaintBounds, hoistNegative, stackLayers } from '../paint.js';
20
23
  /** Lay the whole document out at a width. */
21
24
  export function layoutDocument(tree, fonts, viewportWidth, viewportHeight) {
22
25
  const ctx = {
@@ -25,6 +28,10 @@ export function layoutDocument(tree, fonts, viewportWidth, viewportHeight) {
25
28
  viewportHeight,
26
29
  positioned: [],
27
30
  layoutSubtree: (box, width) => layoutSubtree(box, ctx, width),
31
+ firstLine: tree.firstLine,
32
+ firstLineStyler: tree.firstLineStyler,
33
+ nestedOutOfLine: tree.nestedOutOfLine,
34
+ clipText: tree.clipText,
28
35
  };
29
36
  const root = tree.root;
30
37
  root.x = 0;
@@ -32,11 +39,17 @@ export function layoutDocument(tree, fonts, viewportWidth, viewportHeight) {
32
39
  root.width = viewportWidth;
33
40
  // The root box stands in for `<body>` when the document has none (see
34
41
  // `Cascade.rootStyle`), so it may carry a margin — and a margin on the
35
- // initial containing block has nothing to collapse against and nothing to
36
- // sit inside. Folding it into the padding is what a browser's body margin
37
- // amounts to, and it keeps `contentX` the one thing layout reads.
42
+ // initial containing block has nothing to sit inside. Folded into the
43
+ // padding at the sides and the bottom, which keeps `contentX` the one
44
+ // thing layout reads. Not at the top: a body's top margin collapses with
45
+ // its first block's, `<html>` being the formatting context's root and
46
+ // `<body>` not, so it is handed to the flow as the margin already pending
47
+ // there — or `<p>hi</p>` would stand 8px lower than the same paragraph in
48
+ // `<body>`.
38
49
  resolveEdges(root, viewportWidth);
39
- root.padTop += root.marginTop;
50
+ const body = fragmentBodyHeight(tree, viewportHeight);
51
+ const leading = root.marginTop;
52
+ const trailing = root.marginBottom;
40
53
  root.padRight += root.marginRight;
41
54
  root.padBottom += root.marginBottom;
42
55
  root.padLeft += root.marginLeft;
@@ -44,11 +57,31 @@ export function layoutDocument(tree, fonts, viewportWidth, viewportHeight) {
44
57
  root.marginRight = 0;
45
58
  root.marginBottom = 0;
46
59
  root.marginLeft = 0;
60
+ if (body)
61
+ handPercentBase(root, body.inner);
47
62
  const contentWidth = Math.max(0, viewportWidth - root.horizontalExtra);
48
63
  const floats = new FloatContext(root.contentX, root.contentX + contentWidth);
49
- const flow = layoutChildren(root, ctx, floats, root.contentY, contentWidth);
64
+ // the initial containing block is the viewport's size, which a percentage
65
+ // height on the root element resolves against (CSS 2.1 10.1, 10.5); a
66
+ // fragment has no root element, and the box standing in for its body has
67
+ // the body's `auto` height to give
68
+ let readsViewportHeight = body?.viewport === true;
69
+ for (const child of root.children) {
70
+ if (child.el?.name !== 'html')
71
+ continue;
72
+ child.percentHeightBase = viewportHeight;
73
+ const { height, minHeight, maxHeight } = child.style;
74
+ if (isPct(height) ||
75
+ isPct(minHeight) ||
76
+ (maxHeight !== 'none' && isPct(maxHeight))) {
77
+ readsViewportHeight = true;
78
+ }
79
+ }
80
+ const flow = layoutChildren(root, ctx, floats, root.contentY, contentWidth, leading);
50
81
  const floatBottom = floats.bottom === -Infinity ? 0 : floats.bottom - root.contentY;
51
- root.height = Math.max(flow.height, floatBottom) + root.verticalExtra;
82
+ root.height = body
83
+ ? leading + body.outer + trailing
84
+ : Math.max(flow.height, floatBottom) + root.verticalExtra;
52
85
  // Positioned boxes last, and in the order they were found, so a later one
53
86
  // can be positioned against an earlier one's resolved rectangle.
54
87
  for (let i = 0; i < ctx.positioned.length; i += 1) {
@@ -59,42 +92,511 @@ export function layoutDocument(tree, fonts, viewportWidth, viewportHeight) {
59
92
  // against where it *was* — which is the whole of what makes it relative —
60
93
  // and the ink bounds are computed after that, so culling sees where boxes
61
94
  // ended up rather than where they were laid out.
62
- applyRelativeOffsets(root);
63
- computePaintBounds(root);
64
- let bottom = root.height;
95
+ if (tree.relative)
96
+ applyRelativeOffsets(root);
97
+ const reach = computePaintBounds(root, tree.movedInline);
98
+ if (tree.negative)
99
+ hoistNegative(root);
100
+ stackLayers(root);
101
+ // The document is as tall as what overflows the root, not the root: an
102
+ // `html, body { height: 100% }` a window tall holds a message longer than
103
+ // the window, and the element sizes to all of it.
104
+ let bottom = Math.max(root.height, reach);
65
105
  for (const { box } of ctx.positioned) {
66
106
  if (box.style.position !== 'fixed')
67
107
  bottom = Math.max(bottom, box.y + box.height);
68
108
  }
69
- return { width: viewportWidth, height: bottom };
109
+ return {
110
+ width: viewportWidth,
111
+ height: bottom,
112
+ readsViewportHeight: readsViewportHeight || ctx.readViewportHeight === true,
113
+ };
114
+ }
115
+ /**
116
+ * How tall the body a fragment's root box stands in for says it is, where
117
+ * that is definite (CSS 2.1 10.5): a length, `100vh` among them, or a
118
+ * percentage of the `<html>` implied around it where that has a height of
119
+ * its own — a length, or a percentage of the viewport, as `html, body {
120
+ * height: 100% }` fills a window. Its border box, and its content box,
121
+ * which what it holds takes its percentages of; `viewport` where the
122
+ * answer read the viewport's height. Null where the document has an
123
+ * `<html>` or a `<body>`, and where the body's height is `auto`, and the
124
+ * box is as tall as what is in it. Measured before its margins are folded
125
+ * into its padding.
126
+ */
127
+ function fragmentBodyHeight(tree, viewportHeight) {
128
+ const html = tree.impliedHtml;
129
+ if (!html)
130
+ return null;
131
+ const root = tree.root;
132
+ const style = root.style;
133
+ const base = resolveOrNull(html.height, viewportHeight);
134
+ const set = resolveOrNull(style.height, base ?? NaN);
135
+ if (set === null)
136
+ return null;
137
+ root.percentHeightBase = base ?? NaN;
138
+ const extra = root.verticalExtra;
139
+ const outer = clampHeight(root, style.boxSizing === 'border-box' ? Math.max(set, extra) : set + extra);
140
+ return {
141
+ outer,
142
+ inner: Math.max(0, outer - extra),
143
+ viewport: isPct(style.height) && isPct(html.height),
144
+ };
145
+ }
146
+ /** No margin at all, shared: most joins leave a strut as it was. */
147
+ const NO_MARGIN = Object.freeze({ pos: 0, neg: 0 });
148
+ /** One margin, as a strut to collapse others with. */
149
+ function marginStrut(margin) {
150
+ if (margin > 0)
151
+ return { pos: margin, neg: 0 };
152
+ if (margin < 0)
153
+ return { pos: 0, neg: margin };
154
+ return NO_MARGIN;
155
+ }
156
+ /**
157
+ * A strut with one more margin collapsed into it: the largest positive
158
+ * and the most negative stay apart, and add up only where the margin is
159
+ * read (`marginOf`) — CSS 8.3.1's rule, whole. A `-8px` pull still works
160
+ * against a `40px` push and comes out at `32`, and a third margin after
161
+ * them collapses with the two, not with their sum.
162
+ */
163
+ function join(strut, margin) {
164
+ if (margin > strut.pos)
165
+ return { pos: margin, neg: strut.neg };
166
+ if (margin < strut.neg)
167
+ return { pos: strut.pos, neg: margin };
168
+ return strut;
169
+ }
170
+ /** Two sets of adjoining margins collapsed into one. */
171
+ function joinStruts(a, b) {
172
+ if (b.pos <= a.pos && b.neg >= a.neg)
173
+ return a;
174
+ if (a.pos <= b.pos && a.neg >= b.neg)
175
+ return b;
176
+ return { pos: Math.max(a.pos, b.pos), neg: Math.min(a.neg, b.neg) };
177
+ }
178
+ /** The margin a strut comes to. */
179
+ function marginOf(strut) {
180
+ return strut.pos + strut.neg;
181
+ }
182
+ /** A box's bottom margin as the next sibling's collapses with it, with
183
+ * what came out through its bottom edge where anything did. */
184
+ function bottomOf(box) {
185
+ return box.bottomStrut ?? marginStrut(box.marginBottom);
186
+ }
187
+ /**
188
+ * A block's top margin as its placement uses it: its own, collapsed with
189
+ * every margin that adjoins it (CSS 2.1 8.3.1). That is its first in-flow
190
+ * child's while nothing parts them — no top border, no top padding, no
191
+ * formatting context of its own, no line of text first — and that child's
192
+ * first child's, down the chain; and past a child that is empty, its bottom
193
+ * margin and the next child's top, because an empty block's two margins
194
+ * adjoin each other. The children the chain went through are marked, so
195
+ * their own layout does not apply the margin a second time. Without it
196
+ * `<div><p>` stood a paragraph's margin lower than `<p>`, and a `<div>`
197
+ * holding only an absolute image stood the rest of a document a body's
198
+ * margin lower than a browser does.
199
+ */
200
+ function collapsedTopMargin(box, containingWidth, floats = null, y = 0, pending = NO_MARGIN) {
201
+ floatsPassed = false;
202
+ floatsLeft = 0;
203
+ floatsRight = 0;
204
+ passedLeft = false;
205
+ passedRight = false;
206
+ walkFloats = floats;
207
+ walkY = y;
208
+ walkPending = pending;
209
+ return absorbChildren(box, containingWidth, marginStrut(box.marginTop));
210
+ }
211
+ /** Whether the last `absorbChildren` went through every child in flow of
212
+ * its box, each empty and absorbed whole — which is what lets the caller
213
+ * take that box's bottom margin too. Read straight after the call. */
214
+ let throughAll = false;
215
+ /** Whether the walk has passed a float, and how wide the floats it passed
216
+ * on each side are, their margin boxes as their styles state them — NaN
217
+ * once one does not. Such a float is placed where the margin ends, so it
218
+ * moves with the margin, and a new formatting context the margin reaches
219
+ * next has to be able to sit beside it. */
220
+ let floatsPassed = false;
221
+ let floatsLeft = 0;
222
+ let floatsRight = 0;
223
+ /** Which sides the floats the walk passed are on. */
224
+ let passedLeft = false;
225
+ let passedRight = false;
226
+ /** Where the margin the walk collapses starts, and the floats placed before
227
+ * it: what says whether a cleared child in it has clearance. */
228
+ let walkFloats = null;
229
+ let walkY = 0;
230
+ let walkPending = NO_MARGIN;
231
+ /**
232
+ * Collapse into `margin` the margins of `at`'s children that adjoin its top
233
+ * edge, marking each child it takes, and return it. The walk stops at the
234
+ * first child something parts from the margin — a line, a clearance, a
235
+ * border, padding or height, a formatting context of its own — and counts
236
+ * a child empty only where its layout is sure to agree, since this runs
237
+ * before anything is laid out and the parent is placed by what it answers:
238
+ * an inline box with an edge, or a percentage, stops it where it might have
239
+ * gone on.
240
+ */
241
+ function absorbChildren(at, width, margin) {
242
+ let whole = true;
243
+ /** `at`'s content width, worked out when a child first needs it: most
244
+ * blocks start with text, and never do. */
245
+ let inner = -1;
246
+ for (const child of at.children) {
247
+ if (child.outOfFlow)
248
+ continue;
249
+ if (child.kind === 'text') {
250
+ if (makesNoLine(child))
251
+ continue;
252
+ // a line box stops a margin
253
+ whole = false;
254
+ break;
255
+ }
256
+ const floated = child.isFloat;
257
+ const inLine = !floated &&
258
+ (child.kind === 'inline' ||
259
+ child.kind === 'break' ||
260
+ isInlineLevel(child));
261
+ if (inLine) {
262
+ const made = phantom(child);
263
+ if (made === 0) {
264
+ whole = false;
265
+ break;
266
+ }
267
+ if (made === 1)
268
+ continue;
269
+ }
270
+ if (inner < 0) {
271
+ if (!topOpen(at)) {
272
+ whole = false;
273
+ break;
274
+ }
275
+ inner = Math.max(0, blockWidth(at, width) - at.horizontalExtra);
276
+ }
277
+ if (floated) {
278
+ passFloat(child, inner);
279
+ continue;
280
+ }
281
+ if (inLine) {
282
+ passFloatsIn(child, inner);
283
+ continue;
284
+ }
285
+ resolveEdges(child, inner);
286
+ /** The margin that collapses up through the child's top edge, where
287
+ * it had to be worked out before the child was taken. */
288
+ let through = null;
289
+ if (child.style.clear !== 'none') {
290
+ // Where it would be with `clear: none` is where its top margin and
291
+ // every one that comes up through its top edge put it: a large
292
+ // margin inside it takes it past the floats as surely as its own
293
+ // (CSS 2.1 9.5.2). The walk into it is undone where it has
294
+ // clearance, which it passes no float for.
295
+ const passed = passedFloats(child.style.clear);
296
+ const walked = passed ? null : saveWalk();
297
+ if (!passed) {
298
+ through = absorbChildren(child, inner, join(margin, child.marginTop));
299
+ }
300
+ if (passed || hasClearance(child, through)) {
301
+ if (walked)
302
+ restoreWalk(walked);
303
+ child.topAbsorbed = 0;
304
+ whole = false;
305
+ break;
306
+ }
307
+ }
308
+ if (floatsPassed &&
309
+ (child.kind === 'replaced' || establishesBFC(child)) &&
310
+ !fitsBeside(child, inner)) {
311
+ // it would be pushed below the floats the margin carries, so it
312
+ // separates from them as clearance does: the margin stops above it,
313
+ // and it sits under the floats rather than a margin under them
314
+ child.topAbsorbed = 3;
315
+ whole = false;
316
+ break;
317
+ }
318
+ margin =
319
+ through ?? absorbChildren(child, inner, join(margin, child.marginTop));
320
+ if (!throughAll || !bottomOpen(child)) {
321
+ child.topAbsorbed = 1;
322
+ whole = false;
323
+ break;
324
+ }
325
+ child.topAbsorbed = 2;
326
+ margin = join(margin, child.marginBottom);
327
+ }
328
+ throughAll = whole && (inner >= 0 || topOpen(at));
329
+ return margin;
330
+ }
331
+ /** Whether a box's top margin adjoins its first child's: a block, with no
332
+ * border or padding at the top and no formatting context of its own. */
333
+ function topOpen(box) {
334
+ return (box.kind === 'block' &&
335
+ box.borderTop === 0 &&
336
+ box.padTop === 0 &&
337
+ !establishesBFC(box));
338
+ }
339
+ /** Whether nothing at an empty block's bottom holds its margins apart: no
340
+ * border, padding or height there — nor a height its ratio gives it from
341
+ * its width — and no marker to stand a line tall. A percentage says no,
342
+ * whatever it resolves to. */
343
+ function bottomOpen(box) {
344
+ const { height, minHeight, aspectRatio } = box.style;
345
+ return (box.borderBottom === 0 &&
346
+ box.padBottom === 0 &&
347
+ !box.marker &&
348
+ (height === 0 ||
349
+ (height === AUTO &&
350
+ !aspectRatio &&
351
+ box.style.heightKeyword !== 'stretch')) &&
352
+ (minHeight === AUTO || minHeight === 0) &&
353
+ box.style.minHeightKeyword !== 'stretch');
354
+ }
355
+ /** Whether a text makes no line of its own: white space that collapses
356
+ * away. A no-break space is not white space to CSS, though `\s` says it
357
+ * is, and white space that is kept makes a line. */
358
+ function makesNoLine(text) {
359
+ const ws = text.style.whiteSpace;
360
+ return (ws === 'normal' || ws === 'nowrap') && COLLAPSIBLE.test(text.text);
361
+ }
362
+ const COLLAPSIBLE = /^[ \t\n\r\f]*$/;
363
+ /** Whether an inline box makes no line either — nothing in it that does,
364
+ * and no margin, border or padding on any side: the phantom line of CSS
365
+ * 2.1 9.4.2, which a margin collapses through. `0` where it makes one,
366
+ * `1` where it does not, and `2` where it does not and holds a float, which
367
+ * the walk has to pass. The children go first: most inline boxes hold text,
368
+ * and that answers without a look at their edges. */
369
+ function phantom(box) {
370
+ if (box.kind !== 'inline' || box.style.display !== 'inline')
371
+ return 0;
372
+ let made = 1;
373
+ for (const child of box.children) {
374
+ if (child.outOfFlow)
375
+ continue;
376
+ if (child.isFloat) {
377
+ made = 2;
378
+ continue;
379
+ }
380
+ if (child.kind === 'text') {
381
+ if (makesNoLine(child))
382
+ continue;
383
+ return 0;
384
+ }
385
+ const inner = phantom(child);
386
+ if (inner === 0)
387
+ return 0;
388
+ if (inner === 2)
389
+ made = 2;
390
+ }
391
+ const style = box.style;
392
+ return noLength(style.marginLeft) &&
393
+ noLength(style.marginRight) &&
394
+ noLength(style.marginTop) &&
395
+ noLength(style.marginBottom) &&
396
+ noLength(style.paddingLeft) &&
397
+ noLength(style.paddingRight) &&
398
+ noLength(style.paddingTop) &&
399
+ noLength(style.paddingBottom) &&
400
+ !hasBorder(style.borderLeftStyle, style.borderLeftWidth) &&
401
+ !hasBorder(style.borderRightStyle, style.borderRightWidth) &&
402
+ !hasBorder(style.borderTopStyle, style.borderTopWidth) &&
403
+ !hasBorder(style.borderBottomStyle, style.borderBottomWidth)
404
+ ? made
405
+ : 0;
406
+ }
407
+ /** Pass the floats in a phantom inline box, at any depth. */
408
+ function passFloatsIn(box, width) {
409
+ for (const child of box.children) {
410
+ if (child.isFloat)
411
+ passFloat(child, width);
412
+ else if (child.kind === 'inline')
413
+ passFloatsIn(child, width);
414
+ }
415
+ }
416
+ function noLength(len) {
417
+ return len === 0 || len === AUTO;
418
+ }
419
+ function hasBorder(style, width) {
420
+ return style !== 'none' && style !== 'hidden' && width !== 0;
421
+ }
422
+ /** Count a float the walk passes toward the room a new formatting context
423
+ * after it would have beside it. */
424
+ function passFloat(float, width) {
425
+ resolveEdges(float, width);
426
+ floatsPassed = true;
427
+ if (float.style.float === 'right') {
428
+ floatsRight += statedOuterWidth(float, width);
429
+ passedRight = true;
430
+ }
431
+ else {
432
+ floatsLeft += statedOuterWidth(float, width);
433
+ passedLeft = true;
434
+ }
435
+ }
436
+ /** Whether the walk passed a float that a `clear` clears, which the margin
437
+ * carries down with it: a child that clears one has clearance whatever
438
+ * its margin is. */
439
+ function passedFloats(clear) {
440
+ return (((clear === 'left' || clear === 'both') && passedLeft) ||
441
+ ((clear === 'right' || clear === 'both') && passedRight));
442
+ }
443
+ /**
444
+ * Whether a cleared child in the margin the walk collapses has clearance
445
+ * from a float placed before the walk, which parts it from that margin
446
+ * (CSS 2.1 9.5.2): where `through`, the margin that collapses up through
447
+ * its top edge, does not take it below the float already. A child with none
448
+ * is like any other, and the margin goes on up through it: stopped there, a
449
+ * large margin under a float was spent inside its parent, whose background
450
+ * showed above it.
451
+ */
452
+ function hasClearance(child, through) {
453
+ if (!walkFloats)
454
+ return true;
455
+ const floor = walkFloats.clearance(child.style.clear);
456
+ if (floor === -Infinity)
457
+ return false;
458
+ return walkY + marginOf(joinStruts(walkPending, through)) < floor;
459
+ }
460
+ /** What the walk has passed, to be put back where a look ahead into a
461
+ * child is undone. */
462
+ function saveWalk() {
463
+ return [floatsPassed, floatsLeft, floatsRight, passedLeft, passedRight];
464
+ }
465
+ function restoreWalk([passed, left, right, onLeft, onRight]) {
466
+ floatsPassed = passed;
467
+ floatsLeft = left;
468
+ floatsRight = right;
469
+ passedLeft = onLeft;
470
+ passedRight = onRight;
471
+ }
472
+ /** Whether a new formatting context fits beside the floats the walk passed
473
+ * (CSS 2.1 9.5), as `layoutBesideFloats` places it: where its width and
474
+ * theirs are stated, whether its border box, set in from its margins or
475
+ * the floats, ends before them or the room does; where the floats' are
476
+ * not, yes, and a box of `width: auto` there shrinks to the room it has. */
477
+ function fitsBeside(box, width) {
478
+ const stated = statedOuterWidth(box, width) - box.marginLeft - box.marginRight;
479
+ const lo = floatsLeft ? floatsLeft : -Infinity;
480
+ const hi = floatsRight ? width - floatsRight : Infinity;
481
+ const [start, end] = besideFloats(box, 0, width, lo, hi);
482
+ const across = Number.isNaN(stated)
483
+ ? Math.max(box.horizontalExtra, end - start)
484
+ : stated;
485
+ return box.parent?.style.direction === 'rtl'
486
+ ? !(end - across < Math.max(Math.min(0, box.marginLeft), lo) - 0.01)
487
+ : !(start + across >
488
+ Math.min(width - Math.min(0, box.marginRight), hi) + 0.01);
489
+ }
490
+ /** A box's margin-box width as its style states it, before it is laid out:
491
+ * NaN for `width: auto`, which only layout can answer. */
492
+ function statedOuterWidth(box, containingWidth) {
493
+ const { width, boxSizing } = box.style;
494
+ if (width === AUTO)
495
+ return NaN;
496
+ const set = resolve(width, containingWidth, NaN);
497
+ const border = boxSizing === 'border-box'
498
+ ? Math.max(set, box.horizontalExtra)
499
+ : set + box.horizontalExtra;
500
+ return border + box.marginLeft + box.marginRight;
70
501
  }
71
502
  /**
72
- * The collapsed value of two adjoining margins: the largest positive plus
73
- * the most negative — CSS 8.3.1's rule, whole. Both-positive takes the max,
74
- * both-negative the min, and a mixed pair genuinely adds, which is what
75
- * makes a `-8px` pull work against a `40px` push and come out at `32`.
503
+ * Whether a box's top and bottom margins collapse through it: a block with
504
+ * no height, no border or padding, no line in it and no formatting context
505
+ * of its own, whose blocks are all the same (CSS 2.1 8.3.1). An empty
506
+ * `<div>` between two paragraphs is the case: its margins and theirs are
507
+ * one margin, where they were stacked.
76
508
  */
77
- function collapseMargins(a, b) {
78
- return Math.max(0, Math.max(a, b)) + Math.min(0, Math.min(a, b));
509
+ function collapsesThrough(box) {
510
+ if (box.kind !== 'block' || box.height !== 0)
511
+ return false;
512
+ if (box.lines?.length || establishesBFC(box))
513
+ return false;
514
+ const min = resolveOrNull(box.style.minHeight, box.percentHeightBase);
515
+ if (min !== null && min > 0)
516
+ return false;
517
+ for (const child of box.children) {
518
+ if (child.outOfFlow || child.isFloat)
519
+ continue;
520
+ // what is in line made a line or did not, and the lines said which
521
+ if (child.kind === 'text' ||
522
+ child.kind === 'inline' ||
523
+ child.kind === 'break' ||
524
+ isInlineLevel(child)) {
525
+ continue;
526
+ }
527
+ if (!collapsesThrough(child))
528
+ return false;
529
+ }
530
+ return true;
79
531
  }
80
532
  /**
81
533
  * Lay out a box's children as a block formatting context's contents, or as
82
534
  * one inline formatting context when they are all inline-level.
83
535
  */
84
- function layoutChildren(box, ctx, floats, contentTop, contentWidth) {
536
+ function layoutChildren(box, ctx, floats, contentTop, contentWidth, leading = 0) {
85
537
  const contentLeft = box.contentX;
86
538
  if (establishesInlineContext(box)) {
87
- const height = layoutInlineContent(box, ctx, floats, contentTop, contentWidth, contentLeft);
88
- return { height, hanging: 0 };
539
+ // a line box stops a margin: the leading one is spent above the lines
540
+ const height = layoutInlineContent(box, ctx, floats, contentTop + leading, contentWidth, contentLeft);
541
+ return { height: height + leading, hanging: NO_MARGIN };
89
542
  }
90
543
  let y = contentTop;
91
- /** The margin left hanging by the previous sibling, for collapsing. */
92
- let pendingMargin = 0;
544
+ /** The margin left hanging by the previous sibling — or, before the first
545
+ * child, the one `leading` hands down — for collapsing. */
546
+ let pendingMargin = marginStrut(leading);
547
+ /** How many floats were placed before this box's content began, while
548
+ * nothing has yet fixed where the content is: a margin that collapses
549
+ * up through its top edge would take the ones placed since down with
550
+ * it (`clearanceSince`). Infinity once a child in flow has fixed it,
551
+ * and from the start where a border, padding or a formatting context
552
+ * of its own does. */
553
+ let openFloats = topOpen(box) ? floats.count : Infinity;
93
554
  let first = true;
555
+ /** Whether every child in flow so far was absorbed whole into this box's
556
+ * top margin: the walk that marked the next child reached it only
557
+ * through them, so past any other its mark is an earlier pass's. Asked
558
+ * of the box only once a child carries a mark, which few do. */
559
+ let open = null;
560
+ /** Whether the margin still hanging collapsed with a top margin that has
561
+ * clearance, an empty block cleared past a float's, and the margin that
562
+ * makes: the block's own two and what the empty blocks after it add to
563
+ * them. It stays in this box rather than escape its bottom (CSS 2.1
564
+ * 8.3.1, 10.6.3). */
565
+ let cleared = false;
566
+ let afterClear = NO_MARGIN;
567
+ /** The cleared block's own top margin: its top border edge is that far
568
+ * inside the margin its margins and the ones after it collapse to. */
569
+ let clearTop = 0;
570
+ // this box's first formatted line is its first child's in flow (CSS 2.1
571
+ // 5.12.1), which its `::first-line` is handed to
572
+ let firstLine = ctx.firstLine ? firstLineOf(box) : null;
573
+ // and the room a list item's marker takes on it (`MARKER_ROOM`)
574
+ let marked = MARKER_ROOM.get(box) ?? null;
575
+ const clamp = ctx.clamp ?? null;
94
576
  for (const child of box.children) {
95
- if (child.kind === 'text' && !child.text.trim())
577
+ if (child.kind === 'text' && isBlank(child.text))
96
578
  continue;
579
+ // Past a line-clamp container's clamp point, a box in flow or floating
580
+ // is invisible and takes no room (CSS Overflow 4, 5.3.1); a positioned
581
+ // box keeps its place, invisible only where its containing block is.
582
+ if (clamp !== null && !child.outOfFlow) {
583
+ if (clamp.done) {
584
+ hideClamped(child);
585
+ continue;
586
+ }
587
+ CLAMPED.delete(child);
588
+ }
589
+ if (firstLine && !child.outOfFlow && !child.isFloat) {
590
+ if (!FIRST_LINE.has(child))
591
+ HANDED.set(child, firstLine);
592
+ firstLine = null;
593
+ }
594
+ if (marked && !child.outOfFlow && !child.isFloat) {
595
+ MARKER_ROOM.set(child, marked);
596
+ marked = null;
597
+ }
97
598
  if (child.outOfFlow) {
599
+ placeStatic(child, box, contentLeft, contentWidth, y + marginOf(pendingMargin));
98
600
  ctx.positioned.push({
99
601
  box: child,
100
602
  containing: containingBlockFor(child) ?? box,
@@ -102,20 +604,100 @@ function layoutChildren(box, ctx, floats, contentTop, contentWidth) {
102
604
  continue;
103
605
  }
104
606
  if (child.isFloat) {
105
- layoutFloat(child, ctx, floats, y + pendingMargin, contentWidth);
607
+ layoutFloat(child, ctx, floats, y + marginOf(pendingMargin), contentLeft, contentWidth);
106
608
  continue;
107
609
  }
108
610
  resolveEdges(child, contentWidth);
109
- const top = child.marginTop;
110
- const collapsed = first ? top : collapseMargins(pendingMargin, top);
111
- let childY = y + collapsed;
112
- const clearance = floats.clearance(child.style.clear);
113
- if (clearance > -Infinity && clearance > childY)
114
- childY = clearance;
115
- layoutBlockLevel(child, ctx, floats, contentLeft, childY, contentWidth);
116
- y = child.y + child.height;
117
- pendingMargin = child.marginBottom;
611
+ // A child whose margin this box already spent — collapsed through its
612
+ // top edge — sits at the content top; any other brings its own margin,
613
+ // collapsed with its first descendants' where nothing parts them.
614
+ let absorbed = child.topAbsorbed;
615
+ if (absorbed !== 0 && !(open ??= topOpen(box)))
616
+ absorbed = 0;
617
+ const top = absorbed
618
+ ? NO_MARGIN
619
+ : collapsedTopMargin(child, contentWidth, floats, y, pendingMargin);
620
+ const collapsed = joinStruts(pendingMargin, top);
621
+ // After an empty block cleared past a float, what collapses with its
622
+ // margins is placed from its top border edge, less its own top
623
+ // margin, which is above that edge: the depth the margin they make
624
+ // reaches below it (CSS 2.1 8.3.1), as the box's bottom is below it
625
+ // where the block is the last in it
626
+ const at = cleared
627
+ ? y + Math.max(0, marginOf(joinStruts(afterClear, top)) - clearTop)
628
+ : y + marginOf(collapsed);
629
+ let childY = at;
630
+ const clear = child.style.clear;
631
+ const clearance = floats.clearance(clear);
632
+ if (clearance > -Infinity) {
633
+ // A float placed before anything fixed where this box's content is
634
+ // goes down with the margin where `clear` is none, so the child is
635
+ // never below it there: it has clearance, and its border edge goes
636
+ // under the floats whatever its margin is, which may take it back
637
+ // up (CSS 2.1 9.5.2, as the browsers read it). A margin under a
638
+ // float in an empty block put a block that cleared it the margin
639
+ // below the float, and its parent's background showed between.
640
+ // And one the walk that placed this box reached through every child
641
+ // before it, and stopped at for its clearance, has it too: where
642
+ // it would have been with `clear: none`, its margin collapsed up
643
+ // through this box's top, is above the floats, so its border edge
644
+ // goes under them, and its own margin, held apart there, may not
645
+ // take it lower. The clearance is negative then.
646
+ if ((openFloats !== Infinity &&
647
+ floats.clearanceSince(clear, openFloats) > -Infinity) ||
648
+ (child.topAbsorbed === 0 && (open ?? topOpen(box)))) {
649
+ childY = clearance;
650
+ }
651
+ else if (clearance > childY)
652
+ childY = clearance;
653
+ }
654
+ const counted = clamp?.fit?.lines ?? 0;
655
+ if (!floats.isEmpty &&
656
+ (child.kind === 'replaced' || establishesBFC(child))) {
657
+ layoutBesideFloats(child, ctx, floats, contentLeft, childY, contentWidth);
658
+ }
659
+ else {
660
+ layoutBlockLevel(child, ctx, floats, contentLeft, childY, contentWidth);
661
+ }
662
+ // a block with none of the flow's lines in it — an image, a box of a
663
+ // formatting context of its own — puts the clamp point of an `auto`
664
+ // clamp before it where it ends below the room
665
+ const fit = clamp?.fit;
666
+ if (fit && !fit.over && fit.lines === counted) {
667
+ const bottom = child.y +
668
+ child.height -
669
+ child.padBottom -
670
+ child.borderBottom +
671
+ closingBelow(child, clamp.box);
672
+ if (bottom > fit.floor)
673
+ fit.over = true;
674
+ }
118
675
  first = false;
676
+ const moved = childY !== at;
677
+ if (!moved && collapsesThrough(child)) {
678
+ // nothing in it parts its margins: they and the ones either side of
679
+ // it are one (CSS 2.1 8.3.1), still hanging for what comes next —
680
+ // or, where the walk took them all into this box's top margin, spent
681
+ if (absorbed === 2) {
682
+ pendingMargin = collapsed;
683
+ continue;
684
+ }
685
+ open = false;
686
+ pendingMargin = joinStruts(collapsed, bottomOf(child));
687
+ if (cleared) {
688
+ afterClear = joinStruts(joinStruts(afterClear, top), bottomOf(child));
689
+ }
690
+ continue;
691
+ }
692
+ open = false;
693
+ y = child.y + child.height;
694
+ pendingMargin = bottomOf(child);
695
+ openFloats = Infinity;
696
+ // cleared, and empty: its margins collapse together, and what follows
697
+ // collapses with them, a top margin that has clearance (CSS 2.1 8.3.1)
698
+ cleared = moved && collapsesThrough(child);
699
+ afterClear = cleared ? joinStruts(top, bottomOf(child)) : NO_MARGIN;
700
+ clearTop = cleared ? marginOf(top) : 0;
119
701
  }
120
702
  // The last child's bottom margin collapses through the parent's bottom
121
703
  // edge unless a border, padding, a specified height or a formatting
@@ -124,38 +706,219 @@ function layoutChildren(box, ctx, floats, contentTop, contentWidth) {
124
706
  // escapes, and the caller merges it into the box's own bottom margin so
125
707
  // the next sibling still sees it. Dropping it here was the bug that made
126
708
  // `<div><p>…</p></div><p>…</p>` set the two paragraphs solid.
709
+ // A percentage of a height that depends on content computes to `auto`
710
+ // (CSS 2.1 10.5), and lets it through as `auto` does: a `height: 100%`
711
+ // wrapper in an `auto` body kept its last child's margin inside it
127
712
  if (!first &&
713
+ !cleared &&
128
714
  !box.borderBottom &&
129
715
  !box.padBottom &&
130
- box.style.height === AUTO &&
716
+ specifiedHeight(box) === null &&
131
717
  !establishesBFC(box)) {
132
- return { height: y - contentTop, hanging: pendingMargin };
718
+ const height = y - contentTop;
719
+ // A minimum height that makes the box taller parts the margin from its
720
+ // bottom, and spends it: the margin neither escapes nor makes the box
721
+ // taller still. 8.3.1 has it collapse only through a box of no
722
+ // `min-height`; browsers part it only where the minimum is what sets
723
+ // the height, and the suite has it so. A maximum leaves it to collapse
724
+ // through, as 8.3.1 has it, where the suite has a test either way
725
+ const parted = box.style.minHeight !== AUTO &&
726
+ clampHeight(box, height + box.verticalExtra) > height + box.verticalExtra;
727
+ return { height, hanging: parted ? NO_MARGIN : pendingMargin };
728
+ }
729
+ // The cleared block's top border edge is where it would be with a border
730
+ // at its bottom, its top margin's depth inside the margin they all make:
731
+ // the box ends where that margin does, below the edge by what exceeds
732
+ // the top margin (8.3.1, 10.6.3)
733
+ if (cleared) {
734
+ return {
735
+ height: y + Math.max(0, marginOf(afterClear) - clearTop) - contentTop,
736
+ hanging: NO_MARGIN,
737
+ };
133
738
  }
134
- return { height: y + pendingMargin - contentTop, hanging: 0 };
739
+ return {
740
+ height: y + marginOf(pendingMargin) - contentTop,
741
+ hanging: NO_MARGIN,
742
+ };
135
743
  }
136
- function layoutInlineContent(box, ctx, floats, contentTop, contentWidth, contentLeft) {
137
- // Floats inside an inline context are placed before the lines are built,
138
- // so the lines already know to avoid them.
139
- for (const child of box.children) {
140
- if (child.outOfFlow) {
141
- ctx.positioned.push({
142
- box: child,
143
- containing: containingBlockFor(child) ?? box,
144
- });
145
- }
146
- else if (child.isFloat) {
147
- layoutFloat(child, ctx, floats, contentTop, contentWidth);
744
+ /**
745
+ * A block that makes its own formatting context — a table, a box that
746
+ * clips its overflow — or a block-level image does not flow round the
747
+ * floats beside it: it is a rectangle beside them (CSS 2.1 9.5), in the
748
+ * room they leave at its top, or lower down where it does not fit there.
749
+ * An image floated left with an `overflow: hidden` block of text beside it
750
+ * is the layout this is for; the block ran under the image.
751
+ */
752
+ function layoutBesideFloats(box, ctx, floats, contentLeft, y, contentWidth) {
753
+ const right = contentLeft + contentWidth;
754
+ const style = box.style;
755
+ // auto margins, and HTML's alignment, place the box in the room the
756
+ // floats leave, as browsers do
757
+ const inRoom = style.marginLeft === AUTO ||
758
+ style.marginRight === AUTO ||
759
+ !!box.parent?.style.alignBlocks;
760
+ const rtl = box.parent?.style.direction === 'rtl';
761
+ // Otherwise its margins are its containing block's, and may overlap a
762
+ // float on their own side; its border box may not. So a column beside a
763
+ // 200px sidebar with a margin of 220px starts 220px in, not 420px. A
764
+ // float is looked for as far out as a negative margin takes the box
765
+ resolveEdges(box, contentWidth);
766
+ const reachLeft = inRoom
767
+ ? contentLeft
768
+ : Math.min(contentLeft, contentLeft + box.marginLeft);
769
+ const reachRight = inRoom ? right : Math.max(right, right - box.marginRight);
770
+ let at = y;
771
+ for (let tries = 0; tries < 64; tries += 1) {
772
+ let band = floats.bandAt(at, 1, reachLeft, reachRight, !inRoom);
773
+ let fits = false;
774
+ // The room is what the floats leave over the box's whole height, not at
775
+ // its top: a float that starts lower down, beside the box, narrows it
776
+ // too (CSS 2.1 9.5). Known only once the box is laid out, so a box
777
+ // that runs into one is laid out again in what is left.
778
+ for (let settle = 0; settle < 8; settle += 1) {
779
+ const room = band.right - band.left;
780
+ const lo = band.left > reachLeft ? band.left : -Infinity;
781
+ const hi = band.right < reachRight ? band.right : Infinity;
782
+ if (inRoom)
783
+ layoutBlockLevel(box, ctx, floats, band.left, at, room);
784
+ else {
785
+ const [start, end] = besideFloats(box, contentLeft, right, lo, hi);
786
+ layoutBlockLevel(box, ctx, floats, start - box.marginLeft, at, end - start + box.marginLeft + box.marginRight, contentWidth);
787
+ }
788
+ // its border box, from its top: its margins may overlap a float, and
789
+ // a negative top margin takes the box up past one it then meets
790
+ const over = floats.bandAt(at, box.height, reachLeft, reachRight, !inRoom);
791
+ if (over.left > band.left + 0.5 || over.right < band.right - 0.5) {
792
+ band = over;
793
+ continue;
794
+ }
795
+ // What is too wide overflows at the end of the line, as far as a
796
+ // negative margin there takes it, but no further into a float or
797
+ // past the room; at the start it would run into the floats, and
798
+ // waits below them (the suite's floats-wrap-bfc-with-margin tests)
799
+ if (inRoom) {
800
+ fits = box.marginLeft + box.width + box.marginRight <= room + 0.5;
801
+ }
802
+ else if (rtl) {
803
+ const limit = Math.max(contentLeft + Math.min(0, box.marginLeft), lo);
804
+ fits = box.x >= limit - 0.5;
805
+ }
806
+ else {
807
+ const limit = Math.min(right - Math.min(0, box.marginRight), hi);
808
+ fits = box.x + box.width <= limit + 0.5;
809
+ }
810
+ break;
148
811
  }
812
+ if (fits)
813
+ return;
814
+ // under the float it meets, which may start below its top
815
+ const below = floats.nextEdgeBelow(at, Math.max(1, box.height));
816
+ if (below === null || below <= at)
817
+ break;
818
+ at = below;
819
+ }
820
+ layoutBlockLevel(box, ctx, floats, contentLeft, at, contentWidth);
821
+ }
822
+ /**
823
+ * The left and right edges a block with a formatting context of its own
824
+ * has for its border box beside floats that reach `lo` from the left and
825
+ * `hi` from the right (infinite where none does): each margin set in from
826
+ * its containing block's edge, or the float's where that is further in.
827
+ */
828
+ function besideFloats(box, left, right, lo, hi) {
829
+ // Where a float narrows the containing block's room on either side, a
830
+ // negative margin takes the box no further out than the containing
831
+ // block's edge, as every engine has it; where none does, the margins
832
+ // are the containing block's in full. The suite's
833
+ // floats-wrap-bfc-with-margin-006 to -009 propose otherwise, and pass in
834
+ // no browser.
835
+ const beside = lo > left || hi < right;
836
+ const ml = beside ? Math.max(0, box.marginLeft) : box.marginLeft;
837
+ const mr = beside ? Math.max(0, box.marginRight) : box.marginRight;
838
+ return [Math.max(left + ml, lo), Math.min(right - mr, hi)];
839
+ }
840
+ function layoutInlineContent(box, ctx, floats, contentTop, contentWidth, contentLeft) {
841
+ // a line-clamp container whose clamp point is at its start shows none of
842
+ // its own lines
843
+ const clamp = ctx.clamp ?? null;
844
+ if (clamp !== null && (clamp.done || clamp.left <= 0)) {
845
+ box.lines = [];
846
+ box.floatRow = 0;
847
+ return 0;
149
848
  }
849
+ // An absolute box's static position is the block's; a float is placed
850
+ // on the line it is on, as the lines reach it (`floatBoxes`).
851
+ const floated = placeOutOfLine(box, box, ctx, contentTop, contentWidth, contentLeft);
150
852
  // Atomics have to be sized before the line breaker can place them.
151
853
  sizeAtomics(box, ctx, contentWidth);
152
- const result = layoutInline(box, {
854
+ const options = {
153
855
  fonts: ctx.fonts,
154
856
  width: contentWidth,
155
857
  startY: contentTop,
156
858
  floats,
157
859
  originX: contentLeft,
158
- });
860
+ clipText: ctx.clipText,
861
+ firstStrut: MARKER_ROOM.get(box),
862
+ floatBoxes: floated
863
+ ? {
864
+ size: (child) => sizeFloat(child, ctx, contentWidth),
865
+ place: (child, y) => placeFloat(child, floats, y, contentLeft, contentWidth),
866
+ }
867
+ : undefined,
868
+ // the lines the clamp leaves, where it cuts rather than counts
869
+ clamp: clamp !== null && clamp.fit === null && clamp.left !== Infinity
870
+ ? {
871
+ lines: clamp.left,
872
+ ellipsis: box.style.blockEllipsis,
873
+ follows: followsInFlow(box, clamp.box),
874
+ }
875
+ : undefined,
876
+ };
877
+ const firstLine = ctx.firstLine ? firstLineOf(box) : null;
878
+ // A `::first-line` that sets the line's fonts moves where it breaks, so
879
+ // the line is found in them (`InlineOptions.firstLineStyle`); its text
880
+ // takes the pseudo-element's colour there too
881
+ const restyled = firstLine && ctx.firstLineStyler && setsFonts(firstLine, box.style)
882
+ ? { style: firstLine, styler: ctx.firstLineStyler }
883
+ : undefined;
884
+ let result = layoutInline(box, restyled ? { ...options, firstLineStyle: restyled } : options);
885
+ if (firstLine &&
886
+ !restyled &&
887
+ firstLine.color !== box.style.color &&
888
+ result.lines.length) {
889
+ // the first line's text in its `::first-line` colour, laid out again
890
+ // with its runs cut where the line ends (CSS 2.1 5.12.1), beside the
891
+ // floats the first pass placed — and measured with them
892
+ const placed = result;
893
+ result = layoutInline(box, {
894
+ ...options,
895
+ floatBoxes: undefined,
896
+ firstLine: {
897
+ color: firstLine.color,
898
+ from: box.style.color,
899
+ end: result.lines[0].textEnd,
900
+ },
901
+ });
902
+ if (placed.lines.length === result.lines.length) {
903
+ placed.lines.forEach((line, i) => {
904
+ if (line.floats)
905
+ result.lines[i].floats = line.floats;
906
+ });
907
+ }
908
+ result.floatRow = placed.floatRow;
909
+ }
910
+ box.floatRow = result.floatRow ?? 0;
911
+ if (clamp !== null)
912
+ countLines(clamp, box, result, contentTop, options.clamp);
913
+ if (firstLine &&
914
+ ctx.fonts &&
915
+ result.lines.length &&
916
+ !isTransparent(firstLine.backgroundColor)) {
917
+ result.lines[0].background = {
918
+ style: firstLine,
919
+ ...faceExtentOf(ctx.fonts, firstLine),
920
+ };
921
+ }
159
922
  // The inline pass works in content-box coordinates; move the result into
160
923
  // document space now, so nothing below this line has to know the
161
924
  // difference.
@@ -171,10 +934,142 @@ function layoutInlineContent(box, ctx, floats, contentTop, contentWidth, content
171
934
  text.drawX += contentLeft;
172
935
  text.drawY += contentTop;
173
936
  }
937
+ for (const edge of line.edges ?? [])
938
+ edge.x += contentLeft;
174
939
  }
175
940
  box.lines = result.lines;
176
941
  return result.height;
177
942
  }
943
+ /**
944
+ * A block's lines, counted against the clamp: the ones left, or for an
945
+ * `auto` clamp's first pass, the ones that end above its floor. The clamp
946
+ * point is passed where they were cut, or where they were all the lines
947
+ * left and more of the container follows them.
948
+ */
949
+ function countLines(clamp, box, result, contentTop, cut) {
950
+ const fit = clamp.fit;
951
+ if (fit !== null) {
952
+ if (fit.over)
953
+ return;
954
+ const below = closingBelow(box, clamp.box);
955
+ for (const line of result.lines) {
956
+ if (contentTop + line.y + line.height + below > fit.floor) {
957
+ fit.over = true;
958
+ return;
959
+ }
960
+ fit.lines += 1;
961
+ }
962
+ return;
963
+ }
964
+ clamp.left -= result.lines.length;
965
+ if (result.cut || (clamp.left <= 0 && cut?.follows))
966
+ clamp.done = true;
967
+ }
968
+ /** Mark a box past a clamp point, and take from everything in it the lines
969
+ * a layout before gave it: a hidden box is not laid out, and nothing —
970
+ * a selection, a caret — may find text in it where it stood. */
971
+ function hideClamped(box) {
972
+ CLAMPED.add(box);
973
+ const stack = [box];
974
+ while (stack.length) {
975
+ const at = stack.pop();
976
+ at.lines = null;
977
+ for (const child of at.children)
978
+ stack.push(child);
979
+ }
980
+ }
981
+ /**
982
+ * How far below a box's content a line-clamp container's content ends,
983
+ * where the clamp point is just after it: the bottom padding, border and
984
+ * margin of the box and of each block around it in the container, the
985
+ * margins collapsed where nothing parts them.
986
+ */
987
+ function closingBelow(box, container) {
988
+ let below = 0;
989
+ let margin = 0;
990
+ for (let at = box; at && at !== container; at = at.parent) {
991
+ const edge = at.padBottom + at.borderBottom;
992
+ if (edge) {
993
+ below += margin + edge;
994
+ margin = 0;
995
+ }
996
+ margin = Math.max(margin, at.marginBottom);
997
+ }
998
+ return below + margin;
999
+ }
1000
+ /**
1001
+ * Whether anything in flow follows a block in a line-clamp container — a
1002
+ * block after it, or after a block around it. Then a clamp point is just
1003
+ * after its last line, which ends in an ellipsis (CSS Overflow 4, 4.2);
1004
+ * where nothing does, the lines end where the content does.
1005
+ */
1006
+ function followsInFlow(box, container) {
1007
+ for (let at = box; at !== container && at.parent; at = at.parent) {
1008
+ const siblings = at.parent.children;
1009
+ for (let i = siblings.indexOf(at) + 1; i < siblings.length; i += 1) {
1010
+ const next = siblings[i];
1011
+ if (next.outOfFlow || next.isFloat || makesNothing(next))
1012
+ continue;
1013
+ return true;
1014
+ }
1015
+ }
1016
+ return false;
1017
+ }
1018
+ /** Whether a box in flow makes neither a line nor a height of its own: a
1019
+ * block of phantom lines — the rest of an inline box a block broke — with
1020
+ * no edge or height to part it, which puts no clamp point after the line
1021
+ * before it. */
1022
+ function makesNothing(box) {
1023
+ if (box.kind === 'text')
1024
+ return makesNoLine(box);
1025
+ if (box.kind === 'inline')
1026
+ return phantom(box) !== 0;
1027
+ if (box.kind !== 'block' || establishesBFC(box))
1028
+ return false;
1029
+ const style = box.style;
1030
+ if (style.height !== AUTO ||
1031
+ style.minHeight !== AUTO ||
1032
+ !noLength(style.paddingTop) ||
1033
+ !noLength(style.paddingBottom) ||
1034
+ hasBorder(style.borderTopStyle, style.borderTopWidth) ||
1035
+ hasBorder(style.borderBottomStyle, style.borderBottomWidth)) {
1036
+ return false;
1037
+ }
1038
+ for (const child of box.children) {
1039
+ if (child.outOfFlow || child.isFloat)
1040
+ continue;
1041
+ if (!makesNothing(child))
1042
+ return false;
1043
+ }
1044
+ return true;
1045
+ }
1046
+ /** Whether a `::first-line` style sets its line's text otherwise than its
1047
+ * block's: in another face, size or line height, spaced apart, or in
1048
+ * capitals. */
1049
+ function setsFonts(line, block) {
1050
+ return (line.fontFamily !== block.fontFamily ||
1051
+ line.fontSize !== block.fontSize ||
1052
+ line.fontWeight !== block.fontWeight ||
1053
+ line.fontStyle !== block.fontStyle ||
1054
+ line.lineHeight !== block.lineHeight ||
1055
+ line.lineHeightIsLength !== block.lineHeightIsLength ||
1056
+ line.letterSpacing !== block.letterSpacing ||
1057
+ line.fontVariantCaps !== block.fontVariantCaps ||
1058
+ line.fontVariantNumeric !== block.fontVariantNumeric ||
1059
+ line.fontVariantLigatures !== block.fontVariantLigatures ||
1060
+ line.fontVariantPosition !== block.fontVariantPosition ||
1061
+ line.fontKerning !== block.fontKerning ||
1062
+ line.fontFeatureSettings !== block.fontFeatureSettings ||
1063
+ line.textTransform !== block.textTransform);
1064
+ }
1065
+ /** `::first-line` styles handed down to the first child in flow of a box
1066
+ * that has one, whose first line is the box's (`layoutChildren`). */
1067
+ const HANDED = new WeakMap();
1068
+ /** The `::first-line` style a box's first line takes: its own, or one an
1069
+ * ancestor handed it. */
1070
+ function firstLineOf(box) {
1071
+ return FIRST_LINE.get(box) ?? HANDED.get(box) ?? null;
1072
+ }
178
1073
  /** Size every atomic in an inline context. */
179
1074
  function sizeAtomics(box, ctx, contentWidth) {
180
1075
  for (const child of box.children) {
@@ -198,20 +1093,171 @@ function layoutAtomic(box, ctx, availableWidth) {
198
1093
  return;
199
1094
  }
200
1095
  const width = shrinkToFitWidth(box, ctx, availableWidth);
1096
+ layoutOwn(box, ctx, width);
1097
+ }
1098
+ /**
1099
+ * Lay out a box that sizes itself — a float, an inline-block, an absolute
1100
+ * box — at its own width, where nothing outside it reaches in: or, where
1101
+ * this pass has already laid it out at that width with the same edges,
1102
+ * leave it as it was and move it back to where a layout puts it.
1103
+ *
1104
+ * Measuring a box's content lays its content out, and so a float in a
1105
+ * float was laid out twice a level — as its parent measured, and as its
1106
+ * parent was laid out at the width it measured — and a hundred of them,
1107
+ * the markup of a broken page, never finished. What a caller does to the
1108
+ * box after its layout is move it, and stretch its height, which is put
1109
+ * back; its own, and its content's, are the layout's.
1110
+ */
1111
+ function layoutOwn(box, ctx, width) {
1112
+ const laid = LAID_OWN.get(box);
1113
+ if (laid !== undefined &&
1114
+ laid.ctx === ctx &&
1115
+ // and nothing, a probe of its content, has laid it out since
1116
+ laid.serial === box.layoutSerial &&
1117
+ laid.width === width &&
1118
+ // NaN where no height is a base, which `===` would never find again
1119
+ Object.is(laid.base, box.percentHeightBase) &&
1120
+ laid.padTop === box.padTop &&
1121
+ laid.padRight === box.padRight &&
1122
+ laid.padBottom === box.padBottom &&
1123
+ laid.padLeft === box.padLeft &&
1124
+ // and was put somewhere it can be moved back from: a right float in a
1125
+ // content measured at no width limit stands at an infinite x, and
1126
+ // moved back by it, came out at NaN
1127
+ Number.isFinite(box.x) &&
1128
+ Number.isFinite(box.y)) {
1129
+ box.height = laid.height;
1130
+ moveTo(box, 0, 0);
1131
+ return;
1132
+ }
201
1133
  layoutInternals(box, ctx, width, 0, 0);
1134
+ LAID_OWN.set(box, {
1135
+ ctx,
1136
+ serial: box.layoutSerial,
1137
+ width,
1138
+ base: box.percentHeightBase,
1139
+ padTop: box.padTop,
1140
+ padRight: box.padRight,
1141
+ padBottom: box.padBottom,
1142
+ padLeft: box.padLeft,
1143
+ height: box.height,
1144
+ });
202
1145
  }
203
- function layoutBlockLevel(box, ctx, outerFloats, contentLeft, y, containingWidth) {
204
- resolveEdges(box, containingWidth);
1146
+ /** Every box's layout counted, for `Box.layoutSerial`. */
1147
+ let layoutSerial = 0;
1148
+ /**
1149
+ * Whether a box's layout is one this pass made at `width`, with the same
1150
+ * edges and the same height to take percentages of, and nothing has laid
1151
+ * it out since (`keep`): what a table in a table in a table is asked for
1152
+ * again at every level, and at the same width each time. Its height is put
1153
+ * back, where a caller may have stretched it; where it stands is the
1154
+ * caller's to move it to.
1155
+ */
1156
+ function kept(box, ctx, width) {
1157
+ const laid = LAID_OWN.get(box);
1158
+ if (laid === undefined ||
1159
+ laid.ctx !== ctx ||
1160
+ laid.serial !== box.layoutSerial ||
1161
+ laid.width !== width ||
1162
+ !Object.is(laid.base, box.percentHeightBase) ||
1163
+ laid.padTop !== box.padTop ||
1164
+ laid.padRight !== box.padRight ||
1165
+ laid.padBottom !== box.padBottom ||
1166
+ laid.padLeft !== box.padLeft ||
1167
+ !Number.isFinite(box.x) ||
1168
+ !Number.isFinite(box.y)) {
1169
+ return false;
1170
+ }
1171
+ box.height = laid.height;
1172
+ return true;
1173
+ }
1174
+ /** Note the layout a box was just given, at `width`, for `kept`. */
1175
+ function keep(box, ctx, width) {
1176
+ LAID_OWN.set(box, {
1177
+ ctx,
1178
+ serial: box.layoutSerial,
1179
+ width,
1180
+ base: box.percentHeightBase,
1181
+ padTop: box.padTop,
1182
+ padRight: box.padRight,
1183
+ padBottom: box.padBottom,
1184
+ padLeft: box.padLeft,
1185
+ height: box.height,
1186
+ });
1187
+ }
1188
+ /** What `layoutOwn` laid each box out at, and in which pass. */
1189
+ const LAID_OWN = new WeakMap();
1190
+ function layoutBlockLevel(box, ctx, outerFloats, contentLeft, y, containingWidth,
1191
+ /** What percentages are of: the containing block's width, where the
1192
+ * room is narrower than it beside floats */
1193
+ percentBase = containingWidth) {
1194
+ resolveEdges(box, percentBase);
205
1195
  if (box.kind === 'replaced') {
206
- sizeReplaced(box, containingWidth);
1196
+ sizeReplaced(box, percentBase, containingWidth);
1197
+ placeBlock(box, contentLeft, y, containingWidth);
1198
+ return;
1199
+ }
1200
+ const width = blockWidth(box, containingWidth, percentBase, ctx);
1201
+ if (box.kind === 'table' && kept(box, ctx, width)) {
1202
+ // laid out at this width earlier in the pass, and not since: put where
1203
+ // a layout would put it, and moved there whole
1204
+ const laid = box.width;
1205
+ const x = box.x;
1206
+ const top = box.y;
1207
+ box.width = width;
207
1208
  placeBlock(box, contentLeft, y, containingWidth);
1209
+ if (laid !== width && Number.isFinite(containingWidth)) {
1210
+ box.width = laid;
1211
+ placeBlock(box, contentLeft, y, containingWidth);
1212
+ }
1213
+ box.width = laid;
1214
+ const to = { x: box.x, y: box.y };
1215
+ box.x = x;
1216
+ box.y = top;
1217
+ moveTo(box, to.x, to.y);
208
1218
  return;
209
1219
  }
210
- const width = blockWidth(box, containingWidth);
211
1220
  box.width = width;
212
1221
  placeBlock(box, contentLeft, y, containingWidth);
213
1222
  layoutInternals(box, ctx, width, box.x, box.y, outerFloats);
1223
+ if (box.kind === 'table' &&
1224
+ box.width !== width &&
1225
+ Number.isFinite(containingWidth)) {
1226
+ // a table shrinks to its columns once it is laid out, where it was
1227
+ // placed as wide as its room: placed again, its auto margins centre it
1228
+ // — in a room that has a width, and not in the unbounded one a
1229
+ // shrink-to-fit probe lays it out in
1230
+ const x = box.x;
1231
+ placeBlock(box, contentLeft, y, containingWidth);
1232
+ const dx = box.x - x;
1233
+ box.x = x;
1234
+ translate(box, dx, 0);
1235
+ }
1236
+ if (box.kind === 'table')
1237
+ keep(box, ctx, width);
1238
+ }
1239
+ /**
1240
+ * Lay a block-level box out as a block in a containing block of a width,
1241
+ * at its own `width` or at the room its margins leave, with the offset its
1242
+ * margins give it; the caller moves it into place. What a table's caption
1243
+ * is, in the box around the table.
1244
+ */
1245
+ export function layoutBlockIn(box, ctx, containingWidth) {
1246
+ resolveEdges(box, containingWidth);
1247
+ const width = blockWidth(box, containingWidth, containingWidth, ctx);
1248
+ box.width = width;
1249
+ placeBlock(box, 0, 0, containingWidth);
1250
+ layoutInternals(box, ctx, width, box.x, 0);
214
1251
  }
1252
+ /**
1253
+ * The width a min-content probe lays a box out at: none at all. Every line
1254
+ * breaks at every opportunity there, and the widest is the longest word —
1255
+ * which is what both text engines answer at zero; react-x11's CoreText
1256
+ * engine answers it at zero only, and at a pixel breaks inside words, so a
1257
+ * probe a pixel wide gave every table cell on macOS a min-content a
1258
+ * character or two wide.
1259
+ */
1260
+ export const MIN_CONTENT_PROBE = 0;
215
1261
  /**
216
1262
  * Lay a box out at a width and report the widest thing it drew.
217
1263
  *
@@ -221,12 +1267,19 @@ function layoutBlockLevel(box, ctx, outerFloats, contentLeft, y, containingWidth
221
1267
  * *is* unbounded: it fills its containing block, and its containing block was
222
1268
  * `Infinity`. The answer has to come from what the content came to, which is
223
1269
  * exactly what `intrinsicWidth` walks.
1270
+ *
1271
+ * A box with a width of its own answers with it, as its contribution to
1272
+ * whatever is sizing it — unless `content` asks for what the content
1273
+ * alone came to, which is how a table cell's is taken: its `width` is
1274
+ * weighed apart, and answered with the width the probe laid it out at,
1275
+ * a cell set to 10% measured no wider than its padding.
224
1276
  */
225
- export function measureIntrinsicWidth(box, ctx, available) {
1277
+ export function measureIntrinsicWidth(box, ctx, available, content = false) {
226
1278
  ctx.layoutSubtree(box, available);
227
1279
  const specified = box.style.width;
228
- if (specified !== AUTO && Number.isFinite(box.width))
1280
+ if (!content && specified !== AUTO && Number.isFinite(box.width)) {
229
1281
  return box.width;
1282
+ }
230
1283
  return intrinsicWidth(box) + box.horizontalExtra;
231
1284
  }
232
1285
  /**
@@ -253,12 +1306,73 @@ function layoutSubtree(box, ctx, width) {
253
1306
  * float context, passed only when this box does *not* establish one of its
254
1307
  * own — which is the difference between text flowing beside a float that
255
1308
  * started in an earlier sibling and text that starts below it.
1309
+ *
1310
+ * A line-clamp container's clamp is carried through its formatting context
1311
+ * (`Clamp`), and a box that makes one of its own is laid out outside it.
256
1312
  */
257
1313
  function layoutInternals(box, ctx, borderBoxWidth, x, y, outerFloats) {
1314
+ const outer = ctx.clamp ?? null;
1315
+ const lines = box.style.lineClamp;
1316
+ if (lines === null && (outer === null || !establishesBFC(box))) {
1317
+ layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats);
1318
+ return;
1319
+ }
1320
+ if (lines === null) {
1321
+ ctx.clamp = null;
1322
+ layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats);
1323
+ }
1324
+ else {
1325
+ layoutClamped(box, ctx, lines, borderBoxWidth, x, y, outerFloats);
1326
+ }
1327
+ ctx.clamp = outer;
1328
+ }
1329
+ /**
1330
+ * A line-clamp container, laid out with its clamp. `line-clamp: auto`'s
1331
+ * clamp point is after the lines its height holds, where it has a height
1332
+ * of its own (CSS Overflow 4, 5.3.1): the content is laid out whole to
1333
+ * count them, and again cut after them, where they are not all of it.
1334
+ */
1335
+ function layoutClamped(box, ctx, lines, borderBoxWidth, x, y, outerFloats) {
1336
+ const floor = lines === Infinity ? autoFloor(box, y) : Infinity;
1337
+ if (floor === Infinity) {
1338
+ // counted against the number, or where `auto` has no height to fill,
1339
+ // against none: the boxes a clamp hid before show again
1340
+ ctx.clamp = { box, left: lines, done: false, fit: null };
1341
+ layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats);
1342
+ return;
1343
+ }
1344
+ const fit = { floor, lines: 0, over: false };
1345
+ ctx.clamp = { box, left: Infinity, done: false, fit };
1346
+ const positioned = ctx.positioned.length;
1347
+ layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats);
1348
+ if (!fit.over)
1349
+ return;
1350
+ ctx.positioned.length = positioned;
1351
+ ctx.clamp = { box, left: fit.lines, done: fit.lines === 0, fit: null };
1352
+ layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats);
1353
+ }
1354
+ /** Where an `auto` clamp's lines have to end by, in document coordinates:
1355
+ * the content bottom of a box as tall as its `height`, or `max-height`,
1356
+ * lets it be — Infinity where neither is set. */
1357
+ function autoFloor(box, y) {
1358
+ const set = resolveOrNull(box.style.height, box.percentHeightBase);
1359
+ const outer = set === null
1360
+ ? Infinity
1361
+ : box.style.boxSizing === 'border-box'
1362
+ ? Math.max(set, box.verticalExtra)
1363
+ : set + box.verticalExtra;
1364
+ const height = clampHeight(box, outer);
1365
+ return Number.isFinite(height)
1366
+ ? y + height - box.padBottom - box.borderBottom
1367
+ : Infinity;
1368
+ }
1369
+ function layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats) {
1370
+ box.layoutSerial = ++layoutSerial;
258
1371
  box.x = x;
259
1372
  box.y = y;
260
1373
  box.width = borderBoxWidth;
261
1374
  const contentWidth = box.contentWidth;
1375
+ givePercentBase(box, percentBaseInside(box));
262
1376
  if (box.kind === 'flex') {
263
1377
  const height = layoutFlex(box, ctx, contentWidth);
264
1378
  finishHeight(box, height);
@@ -267,28 +1381,99 @@ function layoutInternals(box, ctx, borderBoxWidth, x, y, outerFloats) {
267
1381
  if (box.kind === 'table') {
268
1382
  const height = layoutTable(box, ctx, contentWidth);
269
1383
  finishHeight(box, height);
1384
+ finishCaptions(box);
270
1385
  return;
271
1386
  }
272
1387
  const ownFloats = establishesBFC(box) || !outerFloats;
273
1388
  const floats = ownFloats
274
1389
  ? new FloatContext(box.contentX, box.contentX + contentWidth)
275
1390
  : outerFloats;
1391
+ // the line a pass before gave a marker, which this one may not
1392
+ if (box.marker)
1393
+ box.lines = null;
1394
+ if (box.marker && ctx.fonts) {
1395
+ const room = markerRoom(box, box.marker, ctx.fonts);
1396
+ if (room)
1397
+ MARKER_ROOM.set(box, room);
1398
+ else
1399
+ MARKER_ROOM.delete(box);
1400
+ }
276
1401
  const flow = layoutChildren(box, ctx, floats, box.contentY, contentWidth);
1402
+ // A list item with a marker and no line holds one, the marker's, a line
1403
+ // of its own face tall: an empty `<li>` is a line tall in a browser, and
1404
+ // an inline-block around one sits on its marker's baseline.
1405
+ let height = flow.height;
1406
+ if (box.marker && ctx.fonts && !firstLineIn(box)) {
1407
+ const own = strutOf(ctx.fonts, box.style);
1408
+ // as tall as the marker on it, as a first line with text is
1409
+ const room = MARKER_ROOM.get(box);
1410
+ const strut = room
1411
+ ? {
1412
+ ascent: Math.max(own.ascent, room.ascent),
1413
+ descent: Math.max(own.descent, room.descent),
1414
+ }
1415
+ : own;
1416
+ const line = strut.ascent + strut.descent;
1417
+ box.lines = [
1418
+ {
1419
+ x: box.contentX,
1420
+ y: box.contentY,
1421
+ width: 0,
1422
+ height: line,
1423
+ baseline: strut.ascent,
1424
+ texts: [],
1425
+ textStart: box.subtreeTextStart,
1426
+ textEnd: box.subtreeTextStart,
1427
+ atomics: [],
1428
+ },
1429
+ ];
1430
+ height = Math.max(height, line);
1431
+ }
277
1432
  // A box that establishes a formatting context contains its own floats, so
278
1433
  // it has to be at least as tall as they are. One that does not, does not —
279
1434
  // that is the classic "collapsed parent" every author has met.
280
- const withFloats = ownFloats
281
- ? Math.max(flow.height, floats.bottom === -Infinity ? 0 : floats.bottom - box.contentY)
282
- : flow.height;
1435
+ // Past a clamp point, a line-clamp container clips its floats rather
1436
+ // than holding them (CSS Overflow 4, 5.3.1).
1437
+ const clamped = ctx.clamp?.box === box && ctx.clamp.done;
1438
+ const withFloats = ownFloats && !clamped
1439
+ ? Math.max(height, floats.bottom === -Infinity ? 0 : floats.bottom - box.contentY)
1440
+ : height;
283
1441
  finishHeight(box, withFloats);
284
1442
  // The margin that escaped through this box's bottom edge becomes part of
285
1443
  // its own: the parent's flow loop reads `child.marginBottom` for the next
286
1444
  // sibling's collapse, which is exactly where an escaped margin goes.
287
- if (flow.hanging) {
288
- box.marginBottom = collapseMargins(box.marginBottom, flow.hanging);
1445
+ // Kept as a strut too, for the next sibling's margins to collapse with
1446
+ // all of the ones it came to rather than with their sum.
1447
+ if (flow.hanging.pos || flow.hanging.neg) {
1448
+ const bottom = join(flow.hanging, box.marginBottom);
1449
+ box.bottomStrut = bottom;
1450
+ box.marginBottom = marginOf(bottom);
1451
+ }
1452
+ else
1453
+ box.bottomStrut = null;
1454
+ if (box.marker)
1455
+ layoutMarker(box, box.marker, ctx);
1456
+ }
1457
+ /**
1458
+ * The room a list item's marker takes about its first line's baseline,
1459
+ * handed to the box that line is in: an image's height above it, its
1460
+ * bottom on the baseline as an inline image's is, and a marker in a face
1461
+ * of its own — a `::marker` rule's — its face at its line height. CSS 2.1
1462
+ * (12.5.1) leaves where an outside marker goes to the user agent; Blink
1463
+ * and Gecko both set it on the item's first line and make the line as tall
1464
+ * as it, and design 032's bullets, images taller than its 10px text, set
1465
+ * every item's first line lower than ours did.
1466
+ */
1467
+ const MARKER_ROOM = new WeakMap();
1468
+ function markerRoom(box, marker, fonts) {
1469
+ if (marker.image) {
1470
+ return marker.image.height > 0
1471
+ ? { ascent: marker.image.height, descent: 0 }
1472
+ : null;
289
1473
  }
290
- if (box.markerText)
291
- layoutMarker(box, ctx);
1474
+ return marker.style && marker.style !== box.style
1475
+ ? strutOf(fonts, marker.style)
1476
+ : null;
292
1477
  }
293
1478
  /** The first line box anywhere under a box, in layout order. */
294
1479
  function firstLineIn(box) {
@@ -315,85 +1500,585 @@ function firstLineIn(box) {
315
1500
  * content: it must not join the selection, or copying a list would paste a
316
1501
  * bullet before every line.
317
1502
  */
318
- function layoutMarker(box, ctx) {
1503
+ const MARKER_GAPS = new WeakMap();
1504
+ /**
1505
+ * The gap after a marker, a space's width in its face: kept per face and
1506
+ * fonts, since a list is a column of markers set alike, and laying the
1507
+ * space out for each of them was most of what a long document's markers
1508
+ * cost.
1509
+ */
1510
+ function markerGap(fonts, face) {
1511
+ let gaps = MARKER_GAPS.get(fonts);
1512
+ if (!gaps)
1513
+ MARKER_GAPS.set(fonts, (gaps = new Map()));
1514
+ const key = `${face.family}\u0001${face.size}\u0001${String(face.weight ?? '')}\u0001${String(face.style ?? '')}`;
1515
+ let gap = gaps.get(key);
1516
+ if (gap === undefined) {
1517
+ gap = fonts.layout([{ text: '\u00a0', ...face }], face, {}).width;
1518
+ gaps.set(key, gap);
1519
+ }
1520
+ return gap;
1521
+ }
1522
+ function layoutMarker(box, marker, ctx) {
319
1523
  const fonts = ctx.fonts;
320
1524
  if (!fonts)
321
1525
  return;
322
1526
  const style = box.style;
323
- const layout = fonts.layout([
324
- {
325
- text: box.markerText,
326
- family: style.fontFamily,
327
- size: style.fontSize,
328
- color: style.color,
329
- },
330
- ], { family: style.fontFamily, size: style.fontSize, color: style.color }, {});
331
- box.markerLayout = layout;
332
- const gap = Math.round(style.fontSize * 0.4);
1527
+ if (marker.image) {
1528
+ // an image: its bottom on the first line's baseline, as an inline
1529
+ // image's is, and the gap a bullet has before the content
1530
+ const first = firstLineIn(box);
1531
+ marker.y = first
1532
+ ? first.y + first.baseline - marker.image.height
1533
+ : box.contentY;
1534
+ const gap = Math.round(style.fontSize * 0.4);
1535
+ // at the start of the line, which is its right in a right-to-left item
1536
+ marker.x =
1537
+ style.direction === 'rtl'
1538
+ ? box.contentX + box.contentWidth + gap
1539
+ : box.contentX - gap - marker.image.width;
1540
+ return;
1541
+ }
1542
+ // in its own style where a `::marker` rule gives it one — the colour and
1543
+ // the face a bullet or a number is set in — and the item's where not
1544
+ const set = marker.style ?? style;
1545
+ const face = {
1546
+ family: set.fontFamily,
1547
+ size: set.fontSize,
1548
+ color: set.color,
1549
+ ...(marker.style
1550
+ ? {
1551
+ weight: set.fontWeight,
1552
+ style: set.fontStyle === 'normal'
1553
+ ? 'normal'
1554
+ : 'italic',
1555
+ }
1556
+ : null),
1557
+ };
1558
+ // set in a line of the item's own height, so that where the item has no
1559
+ // line of its own the marker stands where its first would have been
1560
+ // in the item's direction, which puts a number's full stop on its left
1561
+ // in a right-to-left list, as the bidi algorithm has it
1562
+ const layout = fonts.layout([{ text: marker.text, ...face }], face, {
1563
+ lineHeight: lineHeightMultiplier(fonts, style),
1564
+ direction: style.direction,
1565
+ });
1566
+ marker.layout = layout;
1567
+ // a space between it and the content — the one its style's suffix ends
1568
+ // in, or its own text's — in its own face: a line measures without it
1569
+ const gap = marker.flush && !/\s$/.test(marker.text) ? 0 : markerGap(fonts, face);
333
1570
  // The marker sits on the first line of the item's *content*, which is not
334
1571
  // always the item's own: an `<li>` holding a paragraph, or one holding text
335
1572
  // and a nested list, has its inline content in an anonymous block. Looking
336
1573
  // only at `box.lines` puts the marker of every such item at the content
337
- // top, which reads as a missing bullet rather than a misplaced one.
1574
+ // top, which reads as a missing bullet rather than a misplaced one. An
1575
+ // empty item is still a list item, and its marker a line at its top.
338
1576
  const first = firstLineIn(box);
339
- const baselineY = first ? first.y + first.baseline : box.contentY;
340
1577
  const own = layout.lines[0];
341
- box.markerY = baselineY - (own ? own.baseline : style.fontSize);
342
- box.markerX =
1578
+ marker.y = first
1579
+ ? first.y + first.baseline - (own ? own.baseline : style.fontSize)
1580
+ : box.contentY;
1581
+ marker.x =
343
1582
  style.listStylePosition === 'inside'
344
1583
  ? box.contentX
345
- : box.contentX - gap - layout.width;
1584
+ : style.direction === 'rtl'
1585
+ ? box.contentX + box.contentWidth + gap
1586
+ : box.contentX - gap - layout.width;
346
1587
  }
347
- function finishHeight(box, contentHeight) {
348
- const specified = resolveOrNull(box.style.height, NaN);
349
- const height = specified ?? contentHeight;
350
- const borderBox = box.style.boxSizing === 'border-box' && specified !== null
351
- ? Math.max(height, box.verticalExtra)
352
- : height + box.verticalExtra;
353
- box.height = clampHeight(box, borderBox);
1588
+ /**
1589
+ * The height a percentage `height` inside a box resolves against: its own
1590
+ * content height, where that is set rather than grown from its content —
1591
+ * a length, or a percentage that itself resolved (CSS 2.1 10.5). NaN, and
1592
+ * so `auto`, where it is not. An anonymous box is no containing block for
1593
+ * this (9.2.1.1) and hands on its parent's.
1594
+ *
1595
+ * The root element's is the viewport's height, so `html, body { height:
1596
+ * 100% }` — which mail sets as often as not — is a window tall, as in a
1597
+ * browser; the document is as tall as what overflows it, so a message
1598
+ * longer than the window is not cut off (`layoutDocument`).
1599
+ */
1600
+ export function percentBaseInside(box) {
1601
+ const flexed = FLEXED_HEIGHT.get(box);
1602
+ if (flexed !== undefined)
1603
+ return flexed;
1604
+ if (!box.el && !box.pseudo)
1605
+ return box.percentHeightBase;
1606
+ const resolved = specifiedHeight(box);
1607
+ if (resolved === null) {
1608
+ // a height `aspect-ratio` gives from a width is as definite as the width
1609
+ const ratio = ratioHeight(box);
1610
+ if (ratio === null)
1611
+ return NaN;
1612
+ return Math.max(0, clampHeight(box, ratio + box.verticalExtra) - box.verticalExtra);
1613
+ }
1614
+ const set = Math.max(0, resolved);
1615
+ const borderBox = box.style.boxSizing === 'border-box'
1616
+ ? Math.max(set, box.verticalExtra)
1617
+ : set + box.verticalExtra;
1618
+ return Math.max(0, clampHeight(box, borderBox) - box.verticalExtra);
354
1619
  }
355
- function clampHeight(box, height) {
356
- let out = height;
357
- const min = resolveOrNull(box.style.minHeight, NaN);
358
- const max = box.style.maxHeight === 'none'
359
- ? null
360
- : resolveOrNull(box.style.maxHeight, NaN);
361
- if (max !== null)
362
- out = Math.min(out, max + (box.style.boxSizing === 'border-box' ? 0 : box.verticalExtra));
363
- if (min !== null)
364
- out = Math.max(out, min + (box.style.boxSizing === 'border-box' ? 0 : box.verticalExtra));
365
- return Math.max(0, out);
1620
+ /** Whether anything in a box takes a percentage of a height: what a height
1621
+ * a flex or grid layout makes definite changes. Kept for the tree's life. */
1622
+ export function percentHeightsIn(box) {
1623
+ let found = PERCENT_HEIGHTS.get(box);
1624
+ if (found === undefined) {
1625
+ found = false;
1626
+ // a column's basis is a height too
1627
+ const column = box.kind === 'flex' &&
1628
+ !box.style.grid &&
1629
+ box.style.flexDirection.startsWith('column');
1630
+ for (const child of box.children) {
1631
+ const style = child.style;
1632
+ if (isPct(style.height) ||
1633
+ isPct(style.minHeight) ||
1634
+ (style.maxHeight !== 'none' && isPct(style.maxHeight)) ||
1635
+ (column && style.flexBasis !== 'content' && isPct(style.flexBasis)) ||
1636
+ percentHeightsIn(child)) {
1637
+ found = true;
1638
+ break;
1639
+ }
1640
+ }
1641
+ PERCENT_HEIGHTS.set(box, found);
1642
+ }
1643
+ return found;
366
1644
  }
367
- /** Place a block-level box horizontally, honouring `margin: auto`. */
368
- function placeBlock(box, contentLeft, y, containingWidth) {
1645
+ const PERCENT_HEIGHTS = new WeakMap();
1646
+ /**
1647
+ * A box's own height, where it has one, as `box-sizing` measures it: a
1648
+ * length, a percentage of a height that resolves, or `stretch`'s, and else
1649
+ * none, which is `auto`.
1650
+ */
1651
+ function specifiedHeight(box) {
369
1652
  const style = box.style;
370
- const leftAuto = style.marginLeft === AUTO;
371
- const rightAuto = style.marginRight === AUTO;
372
- const slack = containingWidth - box.width - box.marginLeft - box.marginRight;
373
- let left = contentLeft + box.marginLeft;
1653
+ if (style.heightKeyword === 'stretch') {
1654
+ const outer = stretchHeight(box);
1655
+ if (outer === null)
1656
+ return null;
1657
+ return style.boxSizing === 'border-box'
1658
+ ? outer
1659
+ : Math.max(0, outer - box.verticalExtra);
1660
+ }
1661
+ return resolveOrNull(style.height, box.percentHeightBase);
1662
+ }
1663
+ /** Where an absolutely positioned box's `stretch` height starts in its
1664
+ * containing block: its `top`, or its static position where it has
1665
+ * neither offset (`layoutPositioned`). */
1666
+ const STRETCH_TOP = new WeakMap();
1667
+ /**
1668
+ * A replaced box's `stretch` sizes, which `sizeReplaced` knows nothing of:
1669
+ * the width it was given for one across, the height its offsets leave for
1670
+ * one down, and through its ratio, the other where that is `auto`.
1671
+ */
1672
+ function stretchReplaced(box, across, containingWidth) {
1673
+ const style = box.style;
1674
+ const down = style.heightKeyword === 'stretch' ? stretchHeight(box) : null;
1675
+ if (across === null && down === null)
1676
+ return;
1677
+ if (across !== null)
1678
+ box.width = across;
1679
+ if (down !== null)
1680
+ box.height = clampHeight(box, down);
1681
+ if (across !== null && down === null && style.height === AUTO) {
1682
+ const tall = heightThroughRatio(box, across);
1683
+ if (tall !== null)
1684
+ box.height = clampHeight(box, tall);
1685
+ }
1686
+ else if (down !== null && across === null && style.width === AUTO) {
1687
+ const wide = widthThroughRatio(box, box.height);
1688
+ if (wide !== null)
1689
+ box.width = clampWidth(box, wide, containingWidth);
1690
+ }
1691
+ }
1692
+ /**
1693
+ * The border-box height `stretch` makes of a box (CSS Sizing 3, 4.2): what
1694
+ * its margins leave of the height its percentages are of, where that is
1695
+ * definite, and else none. A margin of an in-flow block that its parent's
1696
+ * border or padding does not part from the parent's edge, in a parent that
1697
+ * is no formatting context of its own, counts as none, as the margin would
1698
+ * collapse with the parent's.
1699
+ */
1700
+ function stretchHeight(box) {
1701
+ const base = box.percentHeightBase;
1702
+ if (!Number.isFinite(base))
1703
+ return null;
1704
+ const style = box.style;
1705
+ if (style.position === 'absolute' || style.position === 'fixed') {
1706
+ // what its offsets leave of its containing block, an `auto` one none
1707
+ // — or its static position, where both are (`layoutPositioned`)
1708
+ const top = STRETCH_TOP.get(box) ?? resolveOrNull(style.top, base) ?? 0;
1709
+ const bottom = resolveOrNull(style.bottom, base) ?? 0;
1710
+ return Math.max(box.verticalExtra, base - top - bottom - box.marginTop - box.marginBottom);
1711
+ }
1712
+ const parent = box.parent;
1713
+ const inFlow = parent !== null &&
1714
+ !box.outOfFlow &&
1715
+ !box.isFloat &&
1716
+ box.kind !== 'replaced' &&
1717
+ !isInlineLevel(box) &&
1718
+ !establishesBFC(parent);
1719
+ const top = inFlow && !parent.borderTop && !parent.padTop ? 0 : box.marginTop;
1720
+ const bottom = inFlow && !parent.borderBottom && !parent.padBottom ? 0 : box.marginBottom;
1721
+ return Math.max(box.verticalExtra, base - top - bottom);
1722
+ }
1723
+ /**
1724
+ * The content height a flex layout made definite for an item — a stretched
1725
+ * item's, a flexed one's in a container of a definite height (CSS Flexbox
1726
+ * 9.8) — which what is in it takes its percentages of. Set by `flex.ts`
1727
+ * for the item's own layout alone.
1728
+ */
1729
+ export const FLEXED_HEIGHT = new WeakMap();
1730
+ /**
1731
+ * Hand a box's children the height their percentages resolve against —
1732
+ * through inline boxes, which are no containing block, to the inline-block
1733
+ * or image in them. Nearly every box has none to give, and its children
1734
+ * hold NaN until it has, so it visits them only when there is a height, or
1735
+ * was one on the last pass: visiting every child of every block on every
1736
+ * pass cost more than laying out a table.
1737
+ */
1738
+ function givePercentBase(box, base) {
1739
+ const given = !Number.isNaN(base);
1740
+ if (!given && !box.gavePercentBase)
1741
+ return;
1742
+ box.gavePercentBase = given;
1743
+ handPercentBase(box, base);
1744
+ }
1745
+ function handPercentBase(box, base) {
1746
+ for (const child of box.children) {
1747
+ child.percentHeightBase = base;
1748
+ if (child.kind === 'inline')
1749
+ handPercentBase(child, base);
1750
+ }
1751
+ }
1752
+ /**
1753
+ * The content height `aspect-ratio` gives a box whose height is `auto`, from
1754
+ * its width (CSS Sizing 4, 5.1): the ratio is of the box its `box-sizing`
1755
+ * names, which is the border box in everything Tailwind writes. Null where
1756
+ * it has none, or a height of its own; a replaced box is sized apart.
1757
+ */
1758
+ export function ratioHeight(box) {
1759
+ const ratio = boxRatio(box);
1760
+ if (!ratio)
1761
+ return null;
1762
+ if (resolveOrNull(box.style.height, box.percentHeightBase) !== null) {
1763
+ return null;
1764
+ }
1765
+ return ratio.border
1766
+ ? Math.max(0, box.width / ratio.ratio - box.verticalExtra)
1767
+ : Math.max(0, box.contentWidth / ratio.ratio);
1768
+ }
1769
+ /**
1770
+ * A box's `aspect-ratio` where it is not replaced, and whether it is of its
1771
+ * border box — a ratio written alone under `box-sizing: border-box` — or
1772
+ * of its content box, as one written `auto` with it is whatever the
1773
+ * `box-sizing` (CSS Sizing 4, 5.1).
1774
+ */
1775
+ export function boxRatio(box) {
1776
+ const aspect = box.style.aspectRatio;
1777
+ if (!aspect || box.kind === 'replaced' || !(aspect.ratio > 0))
1778
+ return null;
1779
+ return {
1780
+ ratio: aspect.ratio,
1781
+ border: box.style.boxSizing === 'border-box' && !aspect.auto,
1782
+ };
1783
+ }
1784
+ /** A border-box height through a box's ratio into its border-box width,
1785
+ * or null where the box has no ratio of its own. */
1786
+ export function widthFromHeight(box, height) {
1787
+ const ratio = boxRatio(box);
1788
+ return ratio ? acrossRatio(box, ratio, height) : null;
1789
+ }
1790
+ /** A replaced element's ratio: its `aspect-ratio`, unless that says `auto`
1791
+ * and it has one of its own, as `sizeReplaced` takes it. */
1792
+ export function replacedRatio(box) {
1793
+ const own = box.intrinsic;
1794
+ const aspect = box.style.aspectRatio;
1795
+ const natural = own ? own.ratio : 0;
1796
+ return aspect && !(aspect.auto && natural > 0) ? aspect.ratio : natural;
1797
+ }
1798
+ /** A border-box height through any box's ratio into its border-box width
1799
+ * — a replaced element's is of its content box — or null where it has
1800
+ * none. */
1801
+ export function widthThroughRatio(box, height) {
1802
+ if (box.kind !== 'replaced')
1803
+ return widthFromHeight(box, height);
1804
+ const ratio = replacedRatio(box);
1805
+ return ratio > 0
1806
+ ? Math.max(0, height - box.verticalExtra) * ratio + box.horizontalExtra
1807
+ : null;
1808
+ }
1809
+ /** A border-box width through any box's ratio into its border-box height,
1810
+ * or null where it has none. */
1811
+ export function heightThroughRatio(box, width) {
1812
+ if (box.kind !== 'replaced')
1813
+ return heightFromWidth(box, width);
1814
+ const ratio = replacedRatio(box);
1815
+ return ratio > 0
1816
+ ? Math.max(0, width - box.horizontalExtra) / ratio + box.verticalExtra
1817
+ : null;
1818
+ }
1819
+ /** A border-box width through a box's ratio into its border-box height,
1820
+ * or null where the box has no ratio of its own. */
1821
+ export function heightFromWidth(box, width) {
1822
+ const ratio = boxRatio(box);
1823
+ if (!ratio)
1824
+ return null;
1825
+ return ratio.border
1826
+ ? width / ratio.ratio
1827
+ : Math.max(0, width - box.horizontalExtra) / ratio.ratio +
1828
+ box.verticalExtra;
1829
+ }
1830
+ /** A border-box height through a box's ratio into its border-box width. */
1831
+ function acrossRatio(box, ratio, height) {
1832
+ return ratio.border
1833
+ ? height * ratio.ratio
1834
+ : Math.max(0, height - box.verticalExtra) * ratio.ratio +
1835
+ box.horizontalExtra;
1836
+ }
1837
+ /**
1838
+ * The border-box width `aspect-ratio` gives a box whose width is `auto`
1839
+ * from a height of its own, within its least and greatest heights (CSS
1840
+ * Sizing 4, 5.1): a block is that wide, not as wide as its room — and so
1841
+ * is one whose width is its content's, `min-content` or `fit-content`, as
1842
+ * its content is that height through its ratio (5.3). Null where it has
1843
+ * no ratio, no height or a length for a width.
1844
+ */
1845
+ export function ratioWidth(box) {
1846
+ const ratio = boxRatio(box);
1847
+ const style = box.style;
1848
+ if (!ratio || style.width !== AUTO)
1849
+ return null;
1850
+ const set = resolveOrNull(style.height, box.percentHeightBase);
1851
+ if (set === null)
1852
+ return null;
1853
+ const outer = clampHeight(box, style.boxSizing === 'border-box'
1854
+ ? Math.max(set, box.verticalExtra)
1855
+ : set + box.verticalExtra);
1856
+ return acrossRatio(box, ratio, outer);
1857
+ }
1858
+ /**
1859
+ * A width its ratio gave a box, no narrower than its content at its
1860
+ * narrowest where its least width is `auto` and it shows what overflows
1861
+ * it — the automatic minimum of a box with a ratio (5.2) — though no
1862
+ * wider for that than its greatest width.
1863
+ */
1864
+ function ratioMinimum(box, width, containingWidth, ctx) {
1865
+ const style = box.style;
1866
+ if (!ctx ||
1867
+ style.minWidth !== AUTO ||
1868
+ style.minWidthKeyword ||
1869
+ scrolls(style)) {
1870
+ return width;
1871
+ }
1872
+ if (box.intrinsicMinContent < 0) {
1873
+ const saved = box.lines;
1874
+ box.intrinsicMinContent = measureIntrinsicWidth(box, ctx, MIN_CONTENT_PROBE);
1875
+ box.lines = saved;
1876
+ resolveEdges(box, containingWidth);
1877
+ }
1878
+ const max = style.maxWidth === 'none'
1879
+ ? Infinity
1880
+ : (resolveOrNull(style.maxWidth, containingWidth) ?? Infinity) +
1881
+ (style.boxSizing === 'border-box' ? 0 : box.horizontalExtra);
1882
+ return Math.max(width, Math.min(box.intrinsicMinContent, max));
1883
+ }
1884
+ /** A table cell's height as its content came to, border box, apart from a
1885
+ * height it sets: what `vertical-align` moves in the cell (`table.ts`). */
1886
+ export const CELL_CONTENT = new WeakMap();
1887
+ function finishHeight(box, contentHeight) {
1888
+ // as though it held nothing, under size containment
1889
+ if (contained(box, CONTAIN_SIZE)) {
1890
+ contentHeight = box.style.containIntrinsicHeight ?? 0;
1891
+ }
1892
+ if (box.kind === 'table-cell') {
1893
+ CELL_CONTENT.set(box, contentHeight + box.verticalExtra);
1894
+ }
1895
+ const set = specifiedHeight(box);
1896
+ // at least zero: a `calc()` may come to less
1897
+ const specified = set === null ? null : Math.max(0, set);
1898
+ if (specified !== null && box.kind === 'table') {
1899
+ // A table's height is a least one, which `layoutTable` has shared out
1900
+ // to its rows: one set shorter than they are leaves them their height
1901
+ // (CSS 2.1 17.5.3). Taken as the table's, it ended its background
1902
+ // over its rows, and a float after it went up beside them.
1903
+ const outer = box.style.boxSizing === 'border-box'
1904
+ ? specified
1905
+ : specified + box.verticalExtra;
1906
+ box.height = Math.max(clampHeight(box, outer), contentHeight + box.verticalExtra);
1907
+ return;
1908
+ }
1909
+ if (specified === null && box.style.aspectRatio) {
1910
+ const ratio = ratioHeight(box);
1911
+ if (ratio !== null) {
1912
+ // the ratio's height, grown to what the box holds unless it clips it
1913
+ // or has a least height of its own (5.2: the automatic minimum of a
1914
+ // box with a ratio is its content, and `min-height: 0` is none)
1915
+ const automatic = box.style.minHeight === AUTO &&
1916
+ box.style.minHeightKeyword === null &&
1917
+ !scrolls(box.style);
1918
+ contentHeight = automatic ? Math.max(ratio, contentHeight) : ratio;
1919
+ }
1920
+ }
1921
+ const height = specified ?? contentHeight;
1922
+ const borderBox = box.style.boxSizing === 'border-box' && specified !== null
1923
+ ? Math.max(height, box.verticalExtra)
1924
+ : height + box.verticalExtra;
1925
+ box.height = clampHeight(box, borderBox);
1926
+ }
1927
+ export function clampHeight(box, height) {
1928
+ let out = height;
1929
+ const base = box.percentHeightBase;
1930
+ // a percentage of a height nothing sets is zero for a minimum (CSS 2.1
1931
+ // 10.7), which leaves a `calc()` its pixels; so is `stretch`, which is a
1932
+ // border-box height where it is one
1933
+ if (box.style.maxHeightKeyword === 'stretch') {
1934
+ out = Math.min(out, stretchHeight(box) ?? Infinity);
1935
+ }
1936
+ if (box.style.minHeightKeyword === 'stretch') {
1937
+ out = Math.max(out, stretchHeight(box) ?? 0);
1938
+ }
1939
+ const min = box.style.minHeight === AUTO
1940
+ ? 0
1941
+ : resolve(box.style.minHeight, Number.isFinite(base) ? base : 0);
1942
+ const max = box.style.maxHeight === 'none'
1943
+ ? null
1944
+ : resolveOrNull(box.style.maxHeight, base);
1945
+ if (max !== null)
1946
+ out = Math.min(out, max + (box.style.boxSizing === 'border-box' ? 0 : box.verticalExtra));
1947
+ if (min !== null)
1948
+ out = Math.max(out, min + (box.style.boxSizing === 'border-box' ? 0 : box.verticalExtra));
1949
+ return Math.max(0, out);
1950
+ }
1951
+ /** Place a block-level box horizontally, honouring `margin: auto`. */
1952
+ function placeBlock(box, contentLeft, y, containingWidth) {
1953
+ const style = box.style;
1954
+ const leftAuto = style.marginLeft === AUTO;
1955
+ const rightAuto = style.marginRight === AUTO;
1956
+ const slack = containingWidth - box.width - box.marginLeft - box.marginRight;
1957
+ // what the width and the margins do not add up to goes to the margin at
1958
+ // the end: the right one, or in a right-to-left containing block the
1959
+ // left one, and a box too wide overflows there (CSS 2.1 10.3.3)
1960
+ const rtl = box.parent?.style.direction === 'rtl';
1961
+ // an alignment around it places a box its margins do not (HTML's
1962
+ // `<center>` and `align`)
1963
+ const aligned = leftAuto || rightAuto ? null : box.parent?.style.alignBlocks;
1964
+ let left = contentLeft + box.marginLeft;
1965
+ // In the unbounded room a shrink-to-fit probe lays a box out in, there is
1966
+ // no slack to share: half of an infinite one put an auto-margined box at
1967
+ // x = Infinity, and the pass after moved it from there by a finite amount
1968
+ // — NaN, which went up every ink bound above it and left a whole article
1969
+ // unpainted.
1970
+ if (!Number.isFinite(slack)) {
1971
+ box.x = left;
1972
+ box.y = y;
1973
+ return;
1974
+ }
374
1975
  if (slack > 0) {
375
1976
  if (leftAuto && rightAuto)
376
1977
  left = contentLeft + slack / 2 + box.marginLeft;
377
- else if (leftAuto)
1978
+ else if (aligned) {
1979
+ if (aligned === 'center')
1980
+ left += slack / 2;
1981
+ else if (aligned === 'right')
1982
+ left += slack;
1983
+ }
1984
+ else if (leftAuto || (rtl && !rightAuto)) {
378
1985
  left = contentLeft + slack + box.marginLeft;
1986
+ }
1987
+ }
1988
+ else if (rtl) {
1989
+ left = contentLeft + slack + box.marginLeft;
379
1990
  }
380
1991
  box.x = left;
381
1992
  box.y = y;
382
1993
  }
383
- /** The border-box width of an in-flow block-level box. */
384
- function blockWidth(box, containingWidth) {
1994
+ /** The border-box width of an in-flow block-level box: the room its
1995
+ * margins leave, a length, or an intrinsic size where `ctx` is there to
1996
+ * measure it with. */
1997
+ function blockWidth(box, containingWidth, percentBase = containingWidth, ctx) {
385
1998
  const style = box.style;
386
1999
  const available = containingWidth - box.marginLeft - box.marginRight;
387
2000
  if (style.width === AUTO) {
388
- return clampWidth(box, Math.max(0, available), containingWidth);
2001
+ // a width its height gives it through its ratio
2002
+ const fromRatio = ratioWidth(box);
2003
+ if (fromRatio !== null) {
2004
+ const clamped = clampWidth(box, fromRatio, percentBase, ctx);
2005
+ return ratioMinimum(box, clamped, percentBase, ctx);
2006
+ }
2007
+ const room = Math.max(0, available);
2008
+ const width = style.widthKeyword && ctx
2009
+ ? contentSizedWidth(box, ctx, style.widthKeyword, room, percentBase)
2010
+ : room;
2011
+ return clampWidth(box, transferredWidth(box, width), percentBase, ctx, containingWidth);
389
2012
  }
390
- const specified = resolve(style.width, containingWidth, 0);
2013
+ // at least zero: a `calc()` may come to less
2014
+ const specified = Math.max(0, resolve(style.width, percentBase, 0));
391
2015
  const borderBox = style.boxSizing === 'border-box'
392
2016
  ? Math.max(specified, box.horizontalExtra)
393
2017
  : specified + box.horizontalExtra;
394
- return clampWidth(box, borderBox, containingWidth);
2018
+ return clampWidth(box, borderBox, percentBase, ctx, containingWidth);
2019
+ }
2020
+ /**
2021
+ * A width a box with a ratio and no height of its own works out for itself
2022
+ * within the least and greatest widths its least and greatest heights make
2023
+ * through its ratio (CSS Sizing 4, 5.2) — those of its own winning where
2024
+ * the two disagree, as `clampWidth` after this makes them. A block the
2025
+ * room would stretch is no wider than its greatest height lets it be. A
2026
+ * width a flex box or a grid gives an item is not, which is theirs.
2027
+ */
2028
+ export function transferredWidth(box, width) {
2029
+ const style = box.style;
2030
+ const ratio = boxRatio(box);
2031
+ if (!ratio || resolveOrNull(style.height, box.percentHeightBase) !== null) {
2032
+ return width;
2033
+ }
2034
+ const heightOf = (len) => {
2035
+ const px = resolveOrNull(len, box.percentHeightBase);
2036
+ if (px === null || !Number.isFinite(px))
2037
+ return null;
2038
+ return style.boxSizing === 'border-box'
2039
+ ? Math.max(px, box.verticalExtra)
2040
+ : px + box.verticalExtra;
2041
+ };
2042
+ const maxH = style.maxHeight === 'none' ? null : heightOf(style.maxHeight);
2043
+ const minH = style.minHeight === AUTO ? null : heightOf(style.minHeight);
2044
+ let out = width;
2045
+ if (maxH !== null)
2046
+ out = Math.min(out, acrossRatio(box, ratio, maxH));
2047
+ if (minH !== null)
2048
+ out = Math.max(out, acrossRatio(box, ratio, minH));
2049
+ return out;
2050
+ }
2051
+ /**
2052
+ * A border-box height within what a box's ratio makes of its least and
2053
+ * greatest widths: how a flex item's content size suggestion down a
2054
+ * column is held (CSS Flexbox 4.5). Unchanged where it has no ratio.
2055
+ */
2056
+ export function transferredHeight(box, height, containingWidth) {
2057
+ const ratio = boxRatio(box);
2058
+ if (!ratio)
2059
+ return height;
2060
+ const style = box.style;
2061
+ const extra = style.boxSizing === 'border-box' ? 0 : box.horizontalExtra;
2062
+ const down = (width) => heightFromWidth(box, width + extra);
2063
+ let out = height;
2064
+ if (style.maxWidth !== 'none') {
2065
+ const most = resolveOrNull(style.maxWidth, containingWidth);
2066
+ if (most !== null)
2067
+ out = Math.min(out, down(most));
2068
+ }
2069
+ const least = style.minWidth === AUTO
2070
+ ? null
2071
+ : resolveOrNull(style.minWidth, containingWidth);
2072
+ if (least !== null && least > 0)
2073
+ out = Math.max(out, down(least));
2074
+ return out;
395
2075
  }
396
- function clampWidth(box, width, containingWidth) {
2076
+ /** A width within `min-width` and `max-width` — the minimum winning — the
2077
+ * intrinsic ones among them where `ctx` is there to measure them. */
2078
+ export function clampWidth(box, width, containingWidth, ctx,
2079
+ /** The room a `stretch` limit fills, where it is less than the
2080
+ * containing block beside floats. */
2081
+ room = containingWidth) {
397
2082
  const style = box.style;
398
2083
  const extra = style.boxSizing === 'border-box' ? 0 : box.horizontalExtra;
399
2084
  let out = width;
@@ -402,11 +2087,79 @@ function clampWidth(box, width, containingWidth) {
402
2087
  : resolveOrNull(style.maxWidth, containingWidth);
403
2088
  if (max !== null)
404
2089
  out = Math.min(out, max + extra);
405
- const min = resolveOrNull(style.minWidth, containingWidth);
2090
+ if (style.maxWidthKeyword && ctx) {
2091
+ out = Math.min(out, keywordWidth(box, ctx, style.maxWidthKeyword, containingWidth, room));
2092
+ }
2093
+ // `auto` is 0 but for a flex item, which `flex.ts` answers
2094
+ const min = style.minWidth === AUTO
2095
+ ? 0
2096
+ : resolveOrNull(style.minWidth, containingWidth);
406
2097
  if (min !== null)
407
2098
  out = Math.max(out, min + extra);
2099
+ if (style.minWidthKeyword && ctx) {
2100
+ out = Math.max(out, keywordWidth(box, ctx, style.minWidthKeyword, containingWidth, room));
2101
+ }
408
2102
  return Math.max(0, out);
409
2103
  }
2104
+ /** An intrinsic `min-width` or `max-width` in the room a box's margins
2105
+ * leave it. Apart from `clampWidth`, which every block is sized through,
2106
+ * so that it makes nothing for the ones that have none. */
2107
+ function keywordWidth(box, ctx, keyword, containingWidth, stretchRoom = containingWidth) {
2108
+ const of = keyword === 'stretch' ? stretchRoom : containingWidth;
2109
+ const room = Math.max(0, of - box.marginLeft - box.marginRight);
2110
+ return contentSizedWidth(box, ctx, keyword, room, containingWidth);
2111
+ }
2112
+ /**
2113
+ * A box's border-box width from an intrinsic size (CSS Sizing 3, 3.1): its
2114
+ * max-content width, its min-content width, or `fit-content` — the
2115
+ * max-content width where `available` holds it, and `available` where it
2116
+ * does not, but never less than the min-content width. Each is measured
2117
+ * once a box, as a table cell's are, and by laying the box out on its own,
2118
+ * which resolves its edges against nothing: they are resolved again
2119
+ * against `percentBase`, as its caller had them.
2120
+ */
2121
+ export function contentSizedWidth(box, ctx, size, available, percentBase) {
2122
+ // `stretch` is the room, whatever the content (CSS Sizing 4)
2123
+ if (size === 'stretch')
2124
+ return Math.max(0, available);
2125
+ // a height of its own through a ratio is its content's width at any size
2126
+ const fromRatio = ratioWidth(box);
2127
+ if (fromRatio !== null)
2128
+ return fromRatio;
2129
+ // the content's sizes, whatever width the box has of its own: a
2130
+ // `min-width: max-content` beside a `width` is its content's widest,
2131
+ // and a probe of the box at no width answered the probe's width
2132
+ let probed = false;
2133
+ if (box.intrinsicMaxContent < 0) {
2134
+ box.intrinsicMaxContent = measureIntrinsicWidth(box, ctx, Infinity, true);
2135
+ probed = true;
2136
+ }
2137
+ if (size !== 'max-content' && box.intrinsicMinContent < 0) {
2138
+ box.intrinsicMinContent = measureIntrinsicWidth(box, ctx, MIN_CONTENT_PROBE, true);
2139
+ probed = true;
2140
+ }
2141
+ if (probed)
2142
+ resolveEdges(box, percentBase);
2143
+ const max = box.intrinsicMaxContent;
2144
+ if (size === 'max-content')
2145
+ return max;
2146
+ const min = box.intrinsicMinContent;
2147
+ if (size === 'min-content')
2148
+ return min;
2149
+ return Math.min(max, Math.max(min, fitRoom(box, size, available, percentBase)));
2150
+ }
2151
+ /** The room `fit-content` fits a box's content in: what its margins leave,
2152
+ * or `fit-content()`'s argument as a width of its own, of `percentBase`
2153
+ * where it is a percentage — an indefinite one leaving the content its
2154
+ * widest. */
2155
+ function fitRoom(box, size, available, percentBase) {
2156
+ if (typeof size !== 'object')
2157
+ return available;
2158
+ const fit = Math.max(0, resolve(size.fit, percentBase, 0));
2159
+ return box.style.boxSizing === 'border-box'
2160
+ ? Math.max(fit, box.horizontalExtra)
2161
+ : fit + box.horizontalExtra;
2162
+ }
410
2163
  /**
411
2164
  * Shrink-to-fit: the width a float, an inline-block or a positioned box with
412
2165
  * `width: auto` takes.
@@ -419,21 +2172,219 @@ function clampWidth(box, width, containingWidth) {
419
2172
  * overflows either way. The measured pass is thrown away; the caller lays the
420
2173
  * box out again at the width this returns.
421
2174
  */
422
- function shrinkToFitWidth(box, ctx, available) {
2175
+ function shrinkToFitWidth(box, ctx, available,
2176
+ /** How far into the containing block the box's room starts: an
2177
+ * absolute box's offset, or its static position. */
2178
+ offset = 0) {
423
2179
  const style = box.style;
424
2180
  if (style.width !== AUTO) {
425
- const specified = resolve(style.width, available, 0);
2181
+ const specified = Math.max(0, resolve(style.width, available, 0));
426
2182
  const borderBox = style.boxSizing === 'border-box'
427
2183
  ? Math.max(specified, box.horizontalExtra)
428
2184
  : specified + box.horizontalExtra;
429
- return clampWidth(box, borderBox, available);
2185
+ return clampWidth(box, borderBox, available, ctx);
2186
+ }
2187
+ // `stretch` fills the room its margins leave, as a block's `auto` does
2188
+ if (style.widthKeyword === 'stretch') {
2189
+ const room = available - offset - box.marginLeft - box.marginRight;
2190
+ return clampWidth(box, Math.max(0, room), available, ctx);
2191
+ }
2192
+ // a width its height gives it through its ratio, as a block's
2193
+ const fromRatio = ratioWidth(box);
2194
+ if (fromRatio !== null) {
2195
+ const clamped = clampWidth(box, fromRatio, available, ctx);
2196
+ return ratioMinimum(box, clamped, available, ctx);
2197
+ }
2198
+ // `fit-content` is what shrink-to-fit is; the other two are not bounded
2199
+ // by the room, or not by the longest line, and `fit-content()` by a room
2200
+ // of its own
2201
+ const keyword = style.widthKeyword;
2202
+ if (keyword !== null && keyword !== 'fit-content') {
2203
+ const width = contentSizedWidth(box, ctx, keyword, available, available);
2204
+ return clampWidth(box, transferredWidth(box, width), available, ctx);
430
2205
  }
431
2206
  const probe = new FloatContext(0, Infinity);
432
2207
  const saved = box.lines;
433
- layoutInternals(box, ctx, Infinity, 0, 0, probe);
434
- const preferred = intrinsicWidth(box) + box.horizontalExtra;
2208
+ // Its content's width with no limit on it is the same at every width, so
2209
+ // it is probed once in the box's life, as a cell's is. Probed every time
2210
+ // it was asked, a float in a float was probed as often as there were
2211
+ // floats around it, and each probe laid out all of the floats inside.
2212
+ let content = PREFERRED.get(box);
2213
+ let probed = false;
2214
+ if (content === undefined) {
2215
+ layoutInternals(box, ctx, Infinity, 0, 0, probe);
2216
+ content = intrinsicWidth(box);
2217
+ PREFERRED.set(box, content);
2218
+ probed = true;
2219
+ }
2220
+ const preferred = content + box.horizontalExtra;
2221
+ // the room is the containing block's, less the box's margins and where
2222
+ // it starts (CSS 2.1 10.3.5, 10.3.7, 10.3.9): a float with side margins
2223
+ // was as wide as its containing block, and stood out of it by them
2224
+ const room = Math.max(0, available - offset - box.marginLeft - box.marginRight);
2225
+ let width = Math.max(preferred, 0);
2226
+ if (width > room &&
2227
+ box.intrinsicMinContent < 0 &&
2228
+ ctx.fonts !== null &&
2229
+ !probed) {
2230
+ // the words the floor is read from are the probe's
2231
+ layoutInternals(box, ctx, Infinity, 0, 0, probe);
2232
+ }
2233
+ // and where its content does not fit the room, its longest word is the
2234
+ // least it comes to: `min(max(min-content, room), max-content)`. That
2235
+ // matters only for a word wider than the room, which the widest word
2236
+ // between spaces bounds, from the probe just laid out; only past the
2237
+ // room is the min-content width measured — a second probe, at a width
2238
+ // every line breaks at — and it is kept on the box, as a cell's is
2239
+ const fonts = ctx.fonts;
2240
+ const floored = width > room &&
2241
+ box.intrinsicMinContent < 0 &&
2242
+ fonts !== null &&
2243
+ wordBound(box, fonts, false) + box.horizontalExtra > room &&
2244
+ wordBound(box, fonts, true) + box.horizontalExtra > room;
435
2245
  box.lines = saved;
436
- return clampWidth(box, Math.min(Math.max(preferred, 0), available), available);
2246
+ if (floored) {
2247
+ box.intrinsicMinContent = measureIntrinsicWidth(box, ctx, MIN_CONTENT_PROBE);
2248
+ box.lines = saved;
2249
+ // the probe resolved the box's edges against the width it was given
2250
+ resolveEdges(box, available);
2251
+ }
2252
+ if (width > room)
2253
+ width = Math.min(width, Math.max(box.intrinsicMinContent, room));
2254
+ return clampWidth(box, transferredWidth(box, width), available, ctx);
2255
+ }
2256
+ /** A shrink-to-fit box's content width at no width limit, the once
2257
+ * (`shrinkToFitWidth`); a box's lifetime is the cache's, as for
2258
+ * `Box.intrinsicMaxContent`. */
2259
+ const PREFERRED = new WeakMap();
2260
+ /**
2261
+ * A box's min-content width as its words give it, laid out at no width
2262
+ * limit, and null where they may not: blocks and plain text that breaks
2263
+ * only at its spaces (`spaceBreaksOnly`) are as narrow as their widest
2264
+ * word (`widestWord`) with the edges of the blocks between, which is what
2265
+ * the probe finds (`MIN_CONTENT_PROBE`) — for a fifteenth of the cost on
2266
+ * ntk, which searches every word of that probe for a place to cut it, as
2267
+ * none fits in no width at all. Anything else
2268
+ * is left to the probe: another kind of break, an inline box's edges, a
2269
+ * box on a line, a float, a table or a flex box, text that does not wrap
2270
+ * or is indented, a block with widths of its own.
2271
+ */
2272
+ export function exactMinContent(box, fonts) {
2273
+ if (hasWidths(box.style) || contained(box, CONTAIN_WIDTH))
2274
+ return null;
2275
+ const inner = exactWords(box, fonts);
2276
+ return inner === null ? null : inner + box.horizontalExtra;
2277
+ }
2278
+ /** Whether a style sets its box's width, or its least or greatest width. */
2279
+ function hasWidths(style) {
2280
+ return (style.width !== AUTO ||
2281
+ style.widthKeyword !== null ||
2282
+ (style.minWidth !== 0 && style.minWidth !== AUTO) ||
2283
+ style.minWidthKeyword !== null ||
2284
+ style.maxWidth !== 'none' ||
2285
+ style.maxWidthKeyword !== null);
2286
+ }
2287
+ function exactWords(box, fonts) {
2288
+ const style = box.style;
2289
+ if (style.whiteSpace !== 'normal' || style.textIndent !== 0)
2290
+ return null;
2291
+ let widest = 0;
2292
+ if (box.lines) {
2293
+ let last = null;
2294
+ for (const line of box.lines) {
2295
+ if (line.atomics.length || line.edges?.length)
2296
+ return null;
2297
+ for (const text of line.texts) {
2298
+ if (text.layout === last)
2299
+ continue;
2300
+ last = text.layout;
2301
+ if (!spaceBreaksOnly(text.layout))
2302
+ return null;
2303
+ widest = Math.max(widest, widestWord(fonts, text.layout));
2304
+ }
2305
+ }
2306
+ }
2307
+ for (const child of box.children) {
2308
+ if (child.kind === 'text' || child.kind === 'break')
2309
+ continue;
2310
+ if (child.kind === 'inline') {
2311
+ if (!plainInline(child))
2312
+ return null;
2313
+ continue;
2314
+ }
2315
+ if (child.kind !== 'block' || child.isFloat)
2316
+ return null;
2317
+ if (child.outOfFlow)
2318
+ continue;
2319
+ if (hasWidths(child.style))
2320
+ return null;
2321
+ const inner = exactWords(child, fonts);
2322
+ if (inner === null)
2323
+ return null;
2324
+ widest = Math.max(widest, inner + child.horizontalExtra + child.marginLeft + child.marginRight);
2325
+ }
2326
+ return widest;
2327
+ }
2328
+ /** Whether an inline box and what is in it are text that wraps. */
2329
+ function plainInline(box) {
2330
+ if (box.style.whiteSpace !== 'normal')
2331
+ return false;
2332
+ for (const child of box.children) {
2333
+ if (child.kind === 'text' || child.kind === 'break')
2334
+ continue;
2335
+ if (child.kind !== 'inline' || !plainInline(child))
2336
+ return false;
2337
+ }
2338
+ return true;
2339
+ }
2340
+ /**
2341
+ * An upper bound of a laid-out block's min-content width inside its edges:
2342
+ * the widest word of its lines (`widestWord`, measured or estimated from
2343
+ * its characters), the widest box on them, and the same of each block in
2344
+ * it with that block's edges. A table, a flex
2345
+ * box and anything else not laid out as blocks and lines is as wide as the
2346
+ * probe made it, which is more than it has to be; a box that is itself one
2347
+ * has no bound.
2348
+ */
2349
+ function wordBound(box, fonts, measured) {
2350
+ if (box.kind !== 'block')
2351
+ return Infinity;
2352
+ let widest = 0;
2353
+ // a line that does not wrap is one word: a `nowrap` tooltip in a narrow
2354
+ // containing block was cut to its width, the rest of the line out of it
2355
+ const unbroken = box.style.whiteSpace === 'nowrap' || box.style.whiteSpace === 'pre';
2356
+ if (box.lines) {
2357
+ let last = null;
2358
+ for (const line of box.lines) {
2359
+ if (unbroken)
2360
+ widest = Math.max(widest, line.width);
2361
+ for (const text of line.texts) {
2362
+ if (text.layout === last)
2363
+ continue;
2364
+ last = text.layout;
2365
+ widest = Math.max(widest, widestWord(fonts, text.layout, measured));
2366
+ }
2367
+ for (const placed of line.atomics) {
2368
+ const b = placed.box;
2369
+ widest = Math.max(widest, b.width + b.marginLeft + b.marginRight);
2370
+ }
2371
+ }
2372
+ }
2373
+ for (const child of box.children) {
2374
+ if (child.outOfFlow)
2375
+ continue;
2376
+ if (child.kind !== 'block' &&
2377
+ child.kind !== 'flex' &&
2378
+ child.kind !== 'table' &&
2379
+ child.kind !== 'replaced')
2380
+ continue;
2381
+ const edges = child.marginLeft + child.marginRight;
2382
+ const inner = child.kind === 'block' && child.style.width === AUTO
2383
+ ? wordBound(child, fonts, measured) + child.horizontalExtra
2384
+ : child.width;
2385
+ widest = Math.max(widest, inner + edges);
2386
+ }
2387
+ return widest;
437
2388
  }
438
2389
  /**
439
2390
  * The widest thing a laid-out box drew — its max-content width.
@@ -444,77 +2395,330 @@ function shrinkToFitWidth(box, ctx, available) {
444
2395
  * Skipping the non-finite ones is what stops the probe from answering
445
2396
  * `Infinity` for every box that contains a paragraph.
446
2397
  */
447
- function intrinsicWidth(box) {
2398
+ export function intrinsicWidth(box) {
2399
+ // under size containment, as though it held nothing: the size
2400
+ // `contain-intrinsic-size` gives it, or none (CSS Containment 2, 3.2)
2401
+ if (contained(box, CONTAIN_WIDTH)) {
2402
+ return box.style.containIntrinsicWidth ?? 0;
2403
+ }
2404
+ // a grid laid out at no width limit has its columns at their widest, and
2405
+ // is as wide as they are — an item that runs past its column makes it no
2406
+ // wider — or, where it has none, as where its items end
2407
+ if (box.style.grid) {
2408
+ const cols = GRID_TRACKS.get(box)?.cols;
2409
+ if (cols?.length)
2410
+ return cols[cols.length - 1][1] - cols[0][0];
2411
+ let right = 0;
2412
+ for (const child of box.children) {
2413
+ if (child.outOfFlow || child.kind === 'text')
2414
+ continue;
2415
+ right = Math.max(right, child.x - box.contentX + child.width + child.marginRight);
2416
+ }
2417
+ return right;
2418
+ }
448
2419
  let widest = 0;
449
- if (box.lines) {
450
- for (const line of box.lines)
451
- widest = Math.max(widest, line.width);
2420
+ // A row of flex items is as wide as all of them side by side, and gaps
2421
+ // between them; any other box is as wide as the widest thing in it
2422
+ const display = box.style.display;
2423
+ const row = (display === 'flex' || display === 'inline-flex') &&
2424
+ box.style.flexDirection.startsWith('row');
2425
+ let total = 0;
2426
+ let items = 0;
2427
+ const lines = box.lines;
2428
+ if (lines) {
2429
+ // a float met in a line stands beside what the line holds, at its
2430
+ // width, as does a row of them beside no line
2431
+ for (const line of lines) {
2432
+ widest = Math.max(widest, line.width + (line.floats ?? 0));
2433
+ }
2434
+ widest = Math.max(widest, box.floatRow);
452
2435
  }
2436
+ // Floats among blocks stand side by side too, as many as come together,
2437
+ // where each block in flow is a line of its own; one with a formatting
2438
+ // context of its own stands beside them (as Blink measures them). Taken
2439
+ // for the widest of them, a floated menu's items were one under another.
2440
+ // That is at the box's widest, laid out at no width limit; at its
2441
+ // narrowest each is as narrow as it can be, and one under another.
2442
+ const sideBySide = !Number.isFinite(box.width);
2443
+ let left = 0;
2444
+ let right = 0;
453
2445
  for (const child of box.children) {
454
2446
  if (child.kind === 'text' || child.kind === 'break')
455
2447
  continue;
456
2448
  if (child.outOfFlow)
457
2449
  continue;
2450
+ // what sits in the lines is measured with them, indent and all: an
2451
+ // inline-block after a negative `text-indent` ends where its line does
2452
+ if (lines && (isInlineLevel(child) || child.isFloat))
2453
+ continue;
458
2454
  const margins = child.marginLeft + child.marginRight;
459
- const inner = intrinsicWidth(child) + child.horizontalExtra + margins;
460
2455
  const own = Number.isFinite(child.width) ? child.width + margins : 0;
461
- widest = Math.max(widest, inner, own);
2456
+ const style = child.style;
2457
+ // a minimum's percentage is of zero here (CSS Sizing 3 5.2.1), which
2458
+ // leaves a `calc()` its pixels
2459
+ const min = style.minWidth === 0 || style.minWidth === AUTO
2460
+ ? 0
2461
+ : resolve(style.minWidth, 0) + contentExtra(child);
2462
+ let contribution;
2463
+ // A width of its own is what a child contributes, whatever its content
2464
+ // does past it (CSS Sizing 3 5.1); a percentage one is cyclic here and
2465
+ // counts as `auto`, so its content decides.
2466
+ if (typeof style.width === 'number') {
2467
+ contribution = Math.max(own, min + margins);
2468
+ }
2469
+ else {
2470
+ let inner = intrinsicWidth(child) + child.horizontalExtra;
2471
+ if (typeof style.maxWidth === 'number') {
2472
+ inner = Math.min(inner, style.maxWidth + contentExtra(child));
2473
+ }
2474
+ if (min > inner)
2475
+ inner = min;
2476
+ contribution = Math.max(inner + margins, own);
2477
+ }
2478
+ if (row) {
2479
+ total += contribution;
2480
+ items += 1;
2481
+ }
2482
+ else if (!sideBySide)
2483
+ widest = Math.max(widest, contribution);
2484
+ else if (child.isFloat) {
2485
+ const clear = style.clear;
2486
+ if (clear !== 'none') {
2487
+ widest = Math.max(widest, left + right);
2488
+ if (clear !== 'right')
2489
+ left = 0;
2490
+ if (clear !== 'left')
2491
+ right = 0;
2492
+ }
2493
+ if (style.float === 'right')
2494
+ right += contribution;
2495
+ else
2496
+ left += contribution;
2497
+ }
2498
+ else {
2499
+ const beside = establishesBFC(child) ? left + right : 0;
2500
+ widest = Math.max(widest, beside + contribution, left + right);
2501
+ left = 0;
2502
+ right = 0;
2503
+ }
2504
+ }
2505
+ widest = Math.max(widest, left + right);
2506
+ if (row && items) {
2507
+ const gap = gapOf(box.style.columnGap, NaN);
2508
+ widest = Math.max(widest, total + gap * (items - 1));
462
2509
  }
463
2510
  return widest;
464
2511
  }
465
- /** A replaced box's size: the style wins, then the attributes, then the
466
- * intrinsic size, and an aspect ratio is kept when only one axis is given. */
467
- function sizeReplaced(box, containingWidth) {
2512
+ /** What a box's own width leaves out of its border box: its padding and
2513
+ * border, unless `box-sizing` puts them in. */
2514
+ function contentExtra(box) {
2515
+ return box.style.boxSizing === 'border-box' ? 0 : box.horizontalExtra;
2516
+ }
2517
+ /**
2518
+ * A replaced box's size, from its style and what it has of an intrinsic
2519
+ * width, height and ratio (CSS 2.1 10.3.2 and 10.6.2), within its minimum
2520
+ * and maximum (10.4, 10.7).
2521
+ *
2522
+ * An image has all three, a frame and a control their default size and no
2523
+ * ratio — so the axis the style does not set keeps its own, and a text field
2524
+ * `width: 100%` is not stretched to twice its height. An SVG may have any of
2525
+ * them: a height and nothing else is 300 pixels wide, a ratio and nothing
2526
+ * else fills its containing block, and nothing at all is 300 by 150 — the
2527
+ * default object size, which the builder leaves in `Intrinsic` for an axis
2528
+ * its `missing` names.
2529
+ *
2530
+ * A limit applied to one axis carries to the other through the ratio: an
2531
+ * image `max-width: 100%` narrower than itself keeps its proportions, where
2532
+ * clamping each axis alone squashed it — the `<img width="600">` of every
2533
+ * mail template, in a narrow column.
2534
+ */
2535
+ function sizeReplaced(box, containingWidth,
2536
+ /** The room `stretch` fills: the containing block's width, or less of
2537
+ * it beside floats (CSS Sizing 3, 4.2). */
2538
+ stretchRoom = containingWidth) {
468
2539
  const style = box.style;
469
- const intrinsicW = box.intrinsicWidth || 0;
470
- const intrinsicH = box.intrinsicHeight || 0;
471
- const ratio = intrinsicW > 0 && intrinsicH > 0 ? intrinsicH / intrinsicW : 0;
472
- let width = resolveOrNull(style.width, containingWidth);
473
- let height = resolveOrNull(style.height, NaN);
474
- if (width === null && height === null) {
475
- width = intrinsicW;
476
- height = intrinsicH;
477
- }
478
- else if (width === null) {
479
- width = ratio ? height / ratio : intrinsicW;
480
- }
481
- else if (height === null) {
482
- height = ratio ? width * ratio : intrinsicH;
483
- }
484
- const contentW = Math.max(0, width ?? 0);
485
- const contentH = Math.max(0, height ?? 0);
486
- const borderBoxW = style.boxSizing === 'border-box'
487
- ? Math.max(contentW, box.horizontalExtra)
488
- : contentW + box.horizontalExtra;
489
- const borderBoxH = style.boxSizing === 'border-box'
490
- ? Math.max(contentH, box.verticalExtra)
491
- : contentH + box.verticalExtra;
492
- box.width = clampWidth(box, borderBoxW, containingWidth);
493
- box.height = clampHeight(box, borderBoxH);
494
- // `hr` has no intrinsic size and its whole appearance is its border, so a
495
- // zero content height is the right answer rather than a missing one.
496
- if (box.replaced === 'hr' && box.style.height === AUTO) {
497
- box.height = box.verticalExtra;
498
- }
499
- }
500
- /** Place and size a float, and register it with the formatting context. */
501
- function layoutFloat(box, ctx, floats, y, containingWidth) {
2540
+ const base = box.percentHeightBase;
2541
+ // everything below is the content box's; a `border-box` length is not
2542
+ const hx = style.boxSizing === 'border-box' ? box.horizontalExtra : 0;
2543
+ const vx = style.boxSizing === 'border-box' ? box.verticalExtra : 0;
2544
+ const across = (len) => {
2545
+ const v = resolveOrNull(len, containingWidth);
2546
+ return v === null ? null : Math.max(0, v - hx);
2547
+ };
2548
+ const down = (len) => {
2549
+ const v = resolveOrNull(len, base);
2550
+ return v === null ? null : Math.max(0, v - vx);
2551
+ };
2552
+ // `stretch` is what the margins leave of the containing block, as a
2553
+ // content box, and of its height where that is definite
2554
+ const stretchedHeight = style.heightKeyword === 'stretch' ? stretchHeight(box) : null;
2555
+ const stretchedWidth = Number.isFinite(stretchRoom)
2556
+ ? Math.max(0, stretchRoom - box.marginLeft - box.marginRight - box.horizontalExtra)
2557
+ : null;
2558
+ const width = style.widthKeyword === 'stretch' && stretchedWidth !== null
2559
+ ? stretchedWidth
2560
+ : across(style.width);
2561
+ const height = stretchedHeight !== null
2562
+ ? Math.max(0, stretchedHeight - box.verticalExtra)
2563
+ : down(style.height);
2564
+ const minW = (style.minWidthKeyword === 'stretch' ? stretchedWidth : null) ??
2565
+ across(style.minWidth) ??
2566
+ 0;
2567
+ const minH = down(style.minHeight) ?? 0;
2568
+ // a maximum below the minimum is the minimum (10.4)
2569
+ const maxW = Math.max(minW, style.maxWidthKeyword === 'stretch' && stretchedWidth !== null
2570
+ ? stretchedWidth
2571
+ : style.maxWidth === 'none'
2572
+ ? Infinity
2573
+ : (across(style.maxWidth) ?? Infinity));
2574
+ const maxH = Math.max(minH, style.maxHeight === 'none' ? Infinity : (down(style.maxHeight) ?? Infinity));
2575
+ // under size containment, as though it had no size or ratio of its own
2576
+ // but the one `contain-intrinsic-size` gives it (CSS Containment 2, 3.2)
2577
+ const own = contained(box, CONTAIN_SIZE)
2578
+ ? {
2579
+ width: style.containIntrinsicWidth ?? 0,
2580
+ height: style.containIntrinsicHeight ?? 0,
2581
+ ratio: 0,
2582
+ missing: 0,
2583
+ }
2584
+ : (box.intrinsic ?? NO_INTRINSIC);
2585
+ const iw = own.missing & 1 ? null : own.width;
2586
+ const ih = own.missing & 2 ? null : own.height;
2587
+ // `aspect-ratio` over its own, unless written `auto` and it has one
2588
+ const aspect = style.aspectRatio;
2589
+ const ratio = aspect && !(aspect.auto && own.ratio > 0) ? aspect.ratio : own.ratio;
2590
+ // the width a block would have here: what a ratio with no size fills, and
2591
+ // an `hr`, whose whole appearance is its border across the line
2592
+ const room = () => {
2593
+ const w = containingWidth - box.marginLeft - box.marginRight - box.horizontalExtra;
2594
+ return Number.isFinite(w) ? Math.max(0, w) : 0;
2595
+ };
2596
+ let w;
2597
+ let h;
2598
+ if (width === null && height === null && ratio > 0) {
2599
+ // both auto, with a ratio: the size it asks for, then the table in 10.4,
2600
+ // which moves both axes together
2601
+ if (iw !== null) {
2602
+ w = iw;
2603
+ h = ih ?? iw / ratio;
2604
+ }
2605
+ else if (ih !== null) {
2606
+ h = ih;
2607
+ w = ih * ratio;
2608
+ }
2609
+ else {
2610
+ // a ratio and no size: CSS 2.1 leaves it undefined and suggests the
2611
+ // width a block would have, which is what browsers do — or, where the
2612
+ // containing block waits on this box, the default object size
2613
+ w = Number.isFinite(containingWidth) ? room() : own.width;
2614
+ h = w / ratio;
2615
+ }
2616
+ [w, h] = constrained(w, h, minW, maxW, minH, maxH);
2617
+ }
2618
+ else {
2619
+ // one axis set, or no ratio: the width, clamped, and then the height
2620
+ // from the width that was used (10.3.2's "the rules above are applied
2621
+ // again" for a limit)
2622
+ const usedHeight = height === null ? null : clamp(height, minH, maxH);
2623
+ if (width !== null)
2624
+ w = width;
2625
+ else if (usedHeight !== null && ratio > 0)
2626
+ w = usedHeight * ratio;
2627
+ else if (box.replaced === 'hr')
2628
+ w = room();
2629
+ else
2630
+ w = own.width;
2631
+ w = clamp(w, minW, maxW);
2632
+ if (usedHeight !== null)
2633
+ h = usedHeight;
2634
+ else if (ratio > 0)
2635
+ h = clamp(w / ratio, minH, maxH);
2636
+ else
2637
+ h = clamp(own.height, minH, maxH);
2638
+ }
2639
+ box.width = w + box.horizontalExtra;
2640
+ box.height = h + box.verticalExtra;
2641
+ }
2642
+ /** What a replaced box with nothing to say about its size has: `hr`. */
2643
+ const NO_INTRINSIC = { width: 0, height: 0, missing: 0, ratio: 0 };
2644
+ function clamp(value, min, max) {
2645
+ return Math.max(min, Math.min(value, max));
2646
+ }
2647
+ /**
2648
+ * CSS 2.1 10.4's table, for a replaced box sized by its ratio alone: a
2649
+ * limit on either axis is met by scaling both, and where the two axes' limits
2650
+ * disagree, the one that asks for more change wins.
2651
+ */
2652
+ function constrained(w, h, minW, maxW, minH, maxH) {
2653
+ if (!(w > 0 && h > 0))
2654
+ return [clamp(w, minW, maxW), clamp(h, minH, maxH)];
2655
+ if (w > maxW && h > maxH) {
2656
+ return maxW / w <= maxH / h
2657
+ ? [maxW, Math.max(minH, (maxW * h) / w)]
2658
+ : [Math.max(minW, (maxH * w) / h), maxH];
2659
+ }
2660
+ if (w < minW && h < minH) {
2661
+ return minW / w <= minH / h
2662
+ ? [Math.min(maxW, (minH * w) / h), minH]
2663
+ : [minW, Math.min(maxH, (minW * h) / w)];
2664
+ }
2665
+ if (w < minW && h > maxH)
2666
+ return [minW, maxH];
2667
+ if (w > maxW && h < minH)
2668
+ return [maxW, minH];
2669
+ if (w > maxW)
2670
+ return [maxW, Math.max((maxW * h) / w, minH)];
2671
+ if (w < minW)
2672
+ return [minW, Math.min((minW * h) / w, maxH)];
2673
+ if (h > maxH)
2674
+ return [Math.max((maxH * w) / h, minW), maxH];
2675
+ if (h < minH)
2676
+ return [Math.min((minH * w) / h, maxW), minH];
2677
+ return [w, h];
2678
+ }
2679
+ /**
2680
+ * Place and size a float, and register it with the formatting context.
2681
+ *
2682
+ * The formatting context holds every float in it, and its band is as wide
2683
+ * as its root; a float is placed within its own containing block's content
2684
+ * box, which may be narrower (CSS 2.1 9.5.1, rules 1 and 7). Floats placed
2685
+ * earlier still push it over wherever they reach into that box. Placed in
2686
+ * the formatting context's band, a float in a nested block sat at the
2687
+ * root's edge — outside its parent's padding and the body's margin.
2688
+ */
2689
+ function layoutFloat(box, ctx, floats, y, containingLeft, containingWidth) {
2690
+ sizeFloat(box, ctx, containingWidth);
2691
+ placeFloat(box, floats, y, containingLeft, containingWidth);
2692
+ }
2693
+ /** Lay a float out at its own width, and answer its outer width. */
2694
+ function sizeFloat(box, ctx, containingWidth) {
502
2695
  resolveEdges(box, containingWidth);
503
2696
  if (box.kind === 'replaced')
504
2697
  sizeReplaced(box, containingWidth);
505
- else {
506
- const width = shrinkToFitWidth(box, ctx, containingWidth);
507
- layoutInternals(box, ctx, width, 0, 0);
508
- }
2698
+ else
2699
+ layoutOwn(box, ctx, shrinkToFitWidth(box, ctx, containingWidth));
2700
+ return box.width + box.marginLeft + box.marginRight;
2701
+ }
2702
+ /** Put a float that is laid out as high as it goes from `y`, and to its
2703
+ * side, and note it in the float context. */
2704
+ function placeFloat(box, floats, y, containingLeft, containingWidth) {
509
2705
  const outerWidth = box.width + box.marginLeft + box.marginRight;
510
2706
  const clearance = floats.clearance(box.style.clear);
511
2707
  const from = Math.max(y, clearance === -Infinity ? y : clearance);
512
- const top = floats.placeAt(from, outerWidth, box.style.float === 'right' ? 'right' : 'left');
513
- const band = floats.bandAt(top, 1);
2708
+ const left = containingLeft;
2709
+ const right = containingLeft + containingWidth;
2710
+ const top = floats.placeAt(from, outerWidth, box.style.float === 'right' ? 'right' : 'left', left, right);
2711
+ const band = floats.bandAt(top, 1, left, right);
514
2712
  const x = box.style.float === 'right'
515
2713
  ? band.right - outerWidth + box.marginLeft
516
2714
  : band.left + box.marginLeft;
517
- moveTo(box, x, top + box.marginTop);
2715
+ // A right float in content measured at no width limit stands at an
2716
+ // infinite x, where its box is not put: a box at infinity cannot be moved
2717
+ // back, so everything in it was laid out again at its next layout — at
2718
+ // every level of a nest of such floats, the innermost as many times as
2719
+ // there were levels. What a measure reads of it is its width, and the
2720
+ // float context keeps where it stands.
2721
+ moveTo(box, Number.isFinite(x) ? x : band.left + box.marginLeft, top + box.marginTop);
518
2722
  floats.add({
519
2723
  left: x - box.marginLeft,
520
2724
  right: x - box.marginLeft + outerWidth,
@@ -524,74 +2728,578 @@ function layoutFloat(box, ctx, floats, y, containingWidth) {
524
2728
  });
525
2729
  }
526
2730
  /**
527
- * An absolutely positioned box, against its containing block.
528
- *
529
- * The static position — where the box would have been in flow — is not
530
- * tracked: a box with neither `top` nor `bottom` is placed at its containing
531
- * block's content top rather than where its markup sat. That is the one
532
- * deliberate simplification in positioning, and it is invisible for the
533
- * overwhelmingly common `position: absolute` with an explicit offset, which
534
- * is how a badge, a tooltip and an overlay are all written.
2731
+ * An absolutely positioned box, against its containing block: a box's
2732
+ * padding box, or an inline box's fragments (`inlineContainingBlock`). An
2733
+ * axis with neither offset takes the static position, where the box would
2734
+ * have been in flow (`placeStatic`, and `staticPositions` in a line).
535
2735
  */
536
2736
  function layoutPositioned(box, containing, ctx) {
537
- const cbWidth = containing.contentWidth;
538
- const cbHeight = containing.contentHeight;
539
- const cbX = containing.contentX;
540
- const cbY = containing.contentY;
2737
+ // a grid's is the box's grid area (CSS Grid 1, 9.1), whether the box is
2738
+ // the grid's child or deeper in it
2739
+ const area = containing.kind === 'inline' ? null : gridArea(box, containing, ctx);
2740
+ const { x: cbX, y: cbY, width: cbWidth, height: cbHeight, } = containing.kind === 'inline'
2741
+ ? inlineContainingBlock(containing, box, ctx)
2742
+ : (area ?? containingRect(containing, ctx));
541
2743
  resolveEdges(box, cbWidth);
2744
+ box.percentHeightBase = cbHeight;
542
2745
  const style = box.style;
543
2746
  const left = resolveOrNull(style.left, cbWidth);
544
2747
  const right = resolveOrNull(style.right, cbWidth);
545
2748
  const top = resolveOrNull(style.top, cbHeight);
546
2749
  const bottom = resolveOrNull(style.bottom, cbHeight);
2750
+ // with neither offset on an axis, the box is where the flow would have
2751
+ // put it (CSS 2.1 10.3.7, 10.6.4): against its start edge, which is the
2752
+ // right one in a right-to-left flow. A grid's own child is aligned in its
2753
+ // grid area instead, where that is its containing block; a box deeper in
2754
+ // the grid is where its own flow put it.
2755
+ const inArea = area !== null &&
2756
+ box.staticPosition?.inside !== undefined &&
2757
+ box.staticPosition.from === containing;
2758
+ const at = inArea ? null : box.staticPosition;
2759
+ const rtl = (at?.from ?? containing).style.direction === 'rtl';
2760
+ // where the box is as a flex box's or a grid's one item, aligned in it —
2761
+ // or in its grid area
2762
+ const room = inArea
2763
+ ? {
2764
+ x: cbX,
2765
+ y: cbY,
2766
+ width: cbWidth,
2767
+ height: cbHeight,
2768
+ ...staticAlignment(box, containing, false),
2769
+ }
2770
+ : at?.inside
2771
+ ? { ...insideRect(at.from, at.inside.box), ...at.inside }
2772
+ : null;
2773
+ // and a shrink-to-fit box's room starts at the offset on the side it has
2774
+ // one, or at its static position: `left: 50%` leaves it half the width
2775
+ const offset = left ??
2776
+ right ??
2777
+ (!at
2778
+ ? 0
2779
+ : rtl
2780
+ ? cbX + cbWidth - (at.from.x + at.right)
2781
+ : at.from.x + at.x - cbX);
547
2782
  let width;
2783
+ let stretched = NaN;
548
2784
  if (style.width !== AUTO) {
549
- width = blockWidth(box, cbWidth);
2785
+ width = blockWidth(box, cbWidth, cbWidth, ctx);
2786
+ }
2787
+ else if (style.widthKeyword === 'stretch') {
2788
+ // what its offsets and margins leave of its containing block — an
2789
+ // `auto` offset leaving all it has, but where both are, the start is
2790
+ // its static position (CSS Position 3, 4.1)
2791
+ const start = left ?? (right === null ? offset : 0);
2792
+ const room = cbWidth - start - (right ?? 0) - box.marginLeft - box.marginRight;
2793
+ width = clampWidth(box, Math.max(0, room), cbWidth, ctx);
2794
+ }
2795
+ else if (style.widthKeyword || ratioWidth(box) !== null) {
2796
+ // an intrinsic size, which both offsets do not stretch, or a height of
2797
+ // its own through its ratio
2798
+ width = shrinkToFitWidth(box, ctx, cbWidth, offset);
2799
+ }
2800
+ else if (boxRatio(box) &&
2801
+ (left === null || right === null) &&
2802
+ top !== null &&
2803
+ bottom !== null) {
2804
+ // the height its offsets leave it through its ratio, where its width
2805
+ // has no pair of them to stretch it (CSS Sizing 4, 5.1)
2806
+ const fill = clampHeight(box, Math.max(0, cbHeight - top - bottom - box.marginTop - box.marginBottom));
2807
+ width = ratioMinimum(box, clampWidth(box, widthFromHeight(box, fill), cbWidth, ctx), cbWidth, ctx);
550
2808
  }
551
2809
  else if (left !== null && right !== null) {
552
- width = Math.max(0, cbWidth - left - right - box.marginLeft - box.marginRight);
2810
+ stretched = Math.max(0, cbWidth - left - right - box.marginLeft - box.marginRight);
2811
+ // within its least and greatest width, which make it a width as one
2812
+ // set would, and the margins' rules run again with it (CSS 2.1 10.4) —
2813
+ // and those of its height, through a ratio (CSS Sizing 4, 5.1)
2814
+ width = clampWidth(box, transferredWidth(box, stretched), cbWidth, ctx);
553
2815
  }
554
2816
  else {
555
- width = shrinkToFitWidth(box, ctx, cbWidth);
2817
+ width = shrinkToFitWidth(box, ctx, cbWidth, offset);
556
2818
  }
557
- if (box.kind === 'replaced')
2819
+ // `stretch` down: what its offsets leave, from its static position where
2820
+ // it has neither
2821
+ if (style.heightKeyword === 'stretch' ||
2822
+ style.minHeightKeyword === 'stretch') {
2823
+ STRETCH_TOP.set(box, top ?? (bottom === null && at ? at.from.y + at.y - cbY : 0));
2824
+ }
2825
+ if (box.kind === 'replaced') {
558
2826
  sizeReplaced(box, cbWidth);
2827
+ stretchReplaced(box, style.widthKeyword === 'stretch' ? width : null, cbWidth);
2828
+ }
559
2829
  else
560
- layoutInternals(box, ctx, width, 0, 0);
561
- const x = left !== null
562
- ? cbX + left + box.marginLeft
563
- : right !== null
564
- ? cbX + cbWidth - right - box.width - box.marginRight
565
- : cbX;
566
- const y = top !== null
567
- ? cbY + top + box.marginTop
568
- : bottom !== null
569
- ? cbY + cbHeight - bottom - box.height - box.marginBottom
570
- : cbY;
2830
+ layoutOwn(box, ctx, width);
2831
+ let x;
2832
+ if (left !== null &&
2833
+ right !== null &&
2834
+ (style.width !== AUTO ||
2835
+ style.widthKeyword !== null ||
2836
+ box.kind === 'replaced' ||
2837
+ width !== stretched)) {
2838
+ // Both offsets and a width: what is left over goes to the margins that
2839
+ // are `auto`, shared where both are, and where none is, the end offset
2840
+ // gives way (10.3.7, 10.3.8).
2841
+ const rest = cbWidth - left - right - box.width;
2842
+ const autoLeft = style.marginLeft === AUTO;
2843
+ const autoRight = style.marginRight === AUTO;
2844
+ const endRtl = containing.style.direction === 'rtl';
2845
+ if (autoLeft && autoRight) {
2846
+ if (rest >= 0) {
2847
+ box.marginLeft = rest / 2;
2848
+ box.marginRight = rest / 2;
2849
+ }
2850
+ else if (endRtl) {
2851
+ box.marginRight = 0;
2852
+ box.marginLeft = rest;
2853
+ }
2854
+ else {
2855
+ box.marginLeft = 0;
2856
+ box.marginRight = rest;
2857
+ }
2858
+ }
2859
+ else if (autoLeft) {
2860
+ box.marginLeft = rest - box.marginRight;
2861
+ }
2862
+ else if (autoRight) {
2863
+ box.marginRight = rest - box.marginLeft;
2864
+ }
2865
+ x =
2866
+ endRtl && !autoLeft && !autoRight
2867
+ ? cbX + cbWidth - right - box.width - box.marginRight
2868
+ : cbX + left + box.marginLeft;
2869
+ }
2870
+ else {
2871
+ x =
2872
+ left !== null
2873
+ ? cbX + left + box.marginLeft
2874
+ : right !== null
2875
+ ? cbX + cbWidth - right - box.width - box.marginRight
2876
+ : room
2877
+ ? alignedIn(room.x, room.width, room.across, box.width + box.marginLeft + box.marginRight, rtl) + box.marginLeft
2878
+ : rtl
2879
+ ? (at ? at.from.x + at.right : cbX + cbWidth) -
2880
+ box.width -
2881
+ box.marginRight
2882
+ : (at ? at.from.x + at.x : cbX) + box.marginLeft;
2883
+ }
2884
+ let y;
2885
+ if (top !== null && bottom !== null) {
2886
+ const fill = Math.max(0, cbHeight - top - bottom - box.marginTop - box.marginBottom);
2887
+ // — a box with a ratio has its height from its width
2888
+ const stretches = style.height === AUTO && box.kind !== 'replaced' && !boxRatio(box);
2889
+ // both offsets and no height: the box fills what they leave, its
2890
+ // `auto` margins nothing (10.6.4, rule 5) — unless `min-height` or
2891
+ // `max-height` moves that, which makes it a height like one set, and
2892
+ // the rules run again with it (10.7)
2893
+ if (stretches)
2894
+ box.height = clampHeight(box, fill);
2895
+ if (!stretches || box.height !== fill) {
2896
+ // and a height: the `auto` margins share the rest, and where none
2897
+ // is `auto`, `bottom` gives way (10.6.4, 10.6.5)
2898
+ const rest = cbHeight - top - bottom - box.height;
2899
+ const autoTop = style.marginTop === AUTO;
2900
+ const autoBottom = style.marginBottom === AUTO;
2901
+ if (autoTop && autoBottom) {
2902
+ box.marginTop = rest / 2;
2903
+ box.marginBottom = rest / 2;
2904
+ }
2905
+ else if (autoTop) {
2906
+ box.marginTop = rest - box.marginBottom;
2907
+ }
2908
+ else if (autoBottom) {
2909
+ box.marginBottom = rest - box.marginTop;
2910
+ }
2911
+ }
2912
+ y = cbY + top + box.marginTop;
2913
+ }
2914
+ else {
2915
+ y =
2916
+ top !== null
2917
+ ? cbY + top + box.marginTop
2918
+ : bottom !== null
2919
+ ? cbY + cbHeight - bottom - box.height - box.marginBottom
2920
+ : room
2921
+ ? alignedIn(room.y, room.height, room.down, box.height + box.marginTop + box.marginBottom, false) + box.marginTop
2922
+ : (at ? at.from.y + at.y : cbY) + box.marginTop;
2923
+ }
571
2924
  moveTo(box, x, y);
572
2925
  }
2926
+ /** A box's content box or its padding box, as its layout left them. */
2927
+ function insideRect(box, which) {
2928
+ if (which === 'content') {
2929
+ return {
2930
+ x: box.contentX,
2931
+ y: box.contentY,
2932
+ width: box.contentWidth,
2933
+ height: Math.max(0, box.height - box.verticalExtra),
2934
+ };
2935
+ }
2936
+ return {
2937
+ x: box.x + box.borderLeft,
2938
+ y: box.y + box.borderTop,
2939
+ width: Math.max(0, box.width - box.borderLeft - box.borderRight),
2940
+ height: Math.max(0, box.height - box.borderTop - box.borderBottom),
2941
+ };
2942
+ }
2943
+ /** Where a margin box of a size starts in a room, `along` of the free room
2944
+ * before it from the room's start — its right end where `rtl`. */
2945
+ function alignedIn(start, size, along, outer, rtl) {
2946
+ const free = size - outer;
2947
+ return rtl ? start + size - outer - along * free : start + along * free;
2948
+ }
2949
+ /**
2950
+ * The grid area a box positioned in a grid takes for its containing block
2951
+ * (CSS Grid 1, 9.1): between the lines its placement names, where the grid
2952
+ * has them, and the grid's padding edge where a line is `auto`, is no line
2953
+ * of the grid, or is only a `span`. Null for a box whose containing block
2954
+ * is no grid.
2955
+ */
2956
+ function gridArea(box, containing, ctx) {
2957
+ const tracks = GRID_TRACKS.get(containing);
2958
+ if (!tracks)
2959
+ return null;
2960
+ const style = box.style;
2961
+ const pad = containingRect(containing, ctx);
2962
+ const [x, width] = areaSpan(style.gridColumnStart, style.gridColumnEnd, tracks.cols, tracks.colLines, containing.contentX, pad.x, pad.width);
2963
+ const [y, height] = areaSpan(style.gridRowStart, style.gridRowEnd, tracks.rows, tracks.rowLines, containing.contentY, pad.y, pad.height);
2964
+ return { x, y, width, height };
2965
+ }
2966
+ /** One axis of a grid area: its start and its size in document
2967
+ * coordinates. A line is a track's start where it starts one and a
2968
+ * track's end where it ends one, so a gap is in neither; a line by name
2969
+ * is the one the placement would take. */
2970
+ function areaSpan(start, end, tracks, lines, origin, edge, size) {
2971
+ const count = tracks.length;
2972
+ // a line the grid has, or null for auto, a span and one it has not
2973
+ const at = (line, side) => {
2974
+ const index = lineOf(lines, line, side);
2975
+ return index !== null && count > 0 && index >= 0 && index <= count
2976
+ ? index
2977
+ : null;
2978
+ };
2979
+ const spanOf = (line) => (line && 'span' in line ? line : null);
2980
+ let from = at(start, 'start');
2981
+ let to = at(end, 'end');
2982
+ const startSpan = spanOf(start);
2983
+ const endSpan = spanOf(end);
2984
+ if (from === null && to !== null && startSpan) {
2985
+ const f = startSpan.name !== undefined
2986
+ ? spanToName(lines, startSpan.name, startSpan.span, to, false)
2987
+ : to - startSpan.span;
2988
+ from = f >= 0 ? f : null;
2989
+ }
2990
+ if (to === null && from !== null && endSpan) {
2991
+ const t = endSpan.name !== undefined
2992
+ ? spanToName(lines, endSpan.name, endSpan.span, from, true)
2993
+ : from + endSpan.span;
2994
+ to = t <= count ? t : null;
2995
+ }
2996
+ if (from !== null && to !== null && to < from)
2997
+ [from, to] = [to, from];
2998
+ const a = from === null
2999
+ ? edge
3000
+ : origin + (from < count ? tracks[from][0] : tracks[from - 1][1]);
3001
+ const b = to === null || to === from
3002
+ ? edge + size
3003
+ : origin + (to >= 1 ? tracks[to - 1][1] : tracks[0][0]);
3004
+ return [a, Math.max(0, b - a)];
3005
+ }
3006
+ /**
3007
+ * The containing block a box that lays itself out gives: its padding box
3008
+ * (CSS 2.1 10.1), not its content box — `left: 0` in a padded box is at
3009
+ * its padding edge — and with nothing positioned around it the initial
3010
+ * one, as tall as the viewport rather than as the document, which a fixed
3011
+ * box's is too.
3012
+ */
3013
+ function containingRect(containing, ctx) {
3014
+ // a layout that reads the viewport's height lays out differently in
3015
+ // another (`LayoutResult.readsViewportHeight`)
3016
+ if (!containing.parent)
3017
+ ctx.readViewportHeight = true;
3018
+ return {
3019
+ x: containing.x + containing.borderLeft,
3020
+ y: containing.y + containing.captionTop + containing.borderTop,
3021
+ width: Math.max(0, containing.width - containing.borderLeft - containing.borderRight),
3022
+ height: !containing.parent
3023
+ ? ctx.viewportHeight
3024
+ : Math.max(0, containing.height -
3025
+ containing.captionTop -
3026
+ containing.captionBottom -
3027
+ containing.borderTop -
3028
+ containing.borderBottom),
3029
+ };
3030
+ }
3031
+ /**
3032
+ * The containing block a `position: relative` inline box gives (CSS 2.1
3033
+ * 10.1, item 4): from the padding edge its first fragment starts at to the
3034
+ * one its last ends at, over the top of the first and the bottom of the
3035
+ * last. The box lays nothing out of its own — its fragments are on its
3036
+ * block's lines — so read as a box that does, it was a rectangle of no size
3037
+ * at the page's corner, and a tooltip under a positioned link came up
3038
+ * there. A box with no fragment at all stands where the box inside it
3039
+ * would have been in flow.
3040
+ */
3041
+ function inlineContainingBlock(inline, box, ctx) {
3042
+ let block = inline.parent;
3043
+ while (block && block.kind === 'inline')
3044
+ block = block.parent;
3045
+ const face = ctx.fonts ? faceExtentOf(ctx.fonts, inline.style) : null;
3046
+ let first = null;
3047
+ let last = null;
3048
+ for (const line of block?.lines ?? []) {
3049
+ const span = spanOn(line, inline);
3050
+ if (!span)
3051
+ continue;
3052
+ let top = line.y;
3053
+ let bottom = line.y + line.height;
3054
+ if (face) {
3055
+ const baseline = line.y +
3056
+ line.baseline -
3057
+ (LINE_BOX_RAISES.get(line)?.get(inline) ?? BOX_RAISES.get(inline) ?? 0);
3058
+ top = baseline - face.ascent - inline.padTop;
3059
+ bottom = baseline + face.descent + inline.padBottom;
3060
+ }
3061
+ const rect = {
3062
+ x: span[0],
3063
+ y: top,
3064
+ width: span[1] - span[0],
3065
+ height: bottom - top,
3066
+ };
3067
+ first ??= rect;
3068
+ last = rect;
3069
+ }
3070
+ if (!first || !last) {
3071
+ const at = box.staticPosition;
3072
+ const x = at ? at.from.x + at.x : 0;
3073
+ const y = at ? at.from.y + at.y : 0;
3074
+ const height = face ? face.ascent + face.descent : 0;
3075
+ return { x, y, width: 0, height };
3076
+ }
3077
+ const rtl = inline.style.direction === 'rtl';
3078
+ const start = rtl ? first.x + first.width : first.x;
3079
+ const end = rtl ? last.x : last.x + last.width;
3080
+ let x = Math.min(start, end);
3081
+ let width = Math.abs(end - start);
3082
+ if (rtl ? end > start : end < start) {
3083
+ // the last fragment ends before the first begins, which CSS 2.1 leaves
3084
+ // undefined: both, then, as a browser takes them
3085
+ x = Math.min(first.x, last.x);
3086
+ width = Math.max(first.x + first.width, last.x + last.width) - x;
3087
+ }
3088
+ return { x, y: first.y, width, height: last.y + last.height - first.y };
3089
+ }
3090
+ /** How far an inline box's padding box reaches across a line, left and
3091
+ * right; null where nothing of it is on the line. */
3092
+ function spanOn(line, inline) {
3093
+ let left = Infinity;
3094
+ let right = -Infinity;
3095
+ for (const text of line.texts) {
3096
+ const natural = text.layout.lines[text.layoutLine];
3097
+ const boxAt = text.spans.boxAt;
3098
+ if (!natural || !boxAt)
3099
+ continue;
3100
+ const x = text.drawX + natural.x;
3101
+ for (const run of natural.runs) {
3102
+ const owner = boxAt.call(text.spans, run.start);
3103
+ if (!owner || !holds(inline, owner))
3104
+ continue;
3105
+ left = Math.min(left, x + run.x);
3106
+ right = Math.max(right, x + run.x + run.width);
3107
+ }
3108
+ }
3109
+ for (const placed of line.atomics) {
3110
+ if (!holds(inline, placed.box))
3111
+ continue;
3112
+ left = Math.min(left, placed.x - placed.box.marginLeft);
3113
+ right = Math.max(right, placed.x + placed.box.width + placed.box.marginRight);
3114
+ }
3115
+ for (const edge of line.edges ?? []) {
3116
+ if (edge.box !== inline) {
3117
+ if (!holds(inline, edge.box))
3118
+ continue;
3119
+ left = Math.min(left, edge.x);
3120
+ right = Math.max(right, edge.x + edge.width);
3121
+ continue;
3122
+ }
3123
+ // its own: the padding edge, inside the margin and the border
3124
+ const onLeft = (edge.side === 'start') !== (inline.style.direction === 'rtl');
3125
+ if (onLeft) {
3126
+ const at = edge.x + inline.marginLeft + inline.borderLeft;
3127
+ left = Math.min(left, at);
3128
+ right = Math.max(right, at);
3129
+ }
3130
+ else {
3131
+ const at = edge.x + edge.width - inline.marginRight - inline.borderRight;
3132
+ left = Math.min(left, at);
3133
+ right = Math.max(right, at);
3134
+ }
3135
+ }
3136
+ return left <= right ? [left, right] : null;
3137
+ }
3138
+ /**
3139
+ * Lay out the floats in a block's inline content and give its positioned
3140
+ * boxes their static positions — at any depth of inline box, which lays
3141
+ * nothing out of its own: a float or an absolute box inside a `<span>` is
3142
+ * the paragraph's as much as one beside it. Met only among the block's own
3143
+ * children, one in an inline box was never laid out at all, and stood at
3144
+ * the page's corner with no size.
3145
+ */
3146
+ /** Give the absolute boxes in a block's inline content their static
3147
+ * position, and answer whether the content holds a float. */
3148
+ function placeOutOfLine(parent, block, ctx, contentTop, contentWidth, contentLeft) {
3149
+ let floated = false;
3150
+ for (const child of parent.children) {
3151
+ if (child.outOfFlow) {
3152
+ placeStatic(child, block, contentLeft, contentWidth, contentTop);
3153
+ ctx.positioned.push({
3154
+ box: child,
3155
+ containing: containingBlockFor(child) ?? block,
3156
+ });
3157
+ }
3158
+ else if (child.isFloat) {
3159
+ floated = true;
3160
+ }
3161
+ else if (child.kind === 'inline' && ctx.nestedOutOfLine) {
3162
+ const inner = placeOutOfLine(child, block, ctx, contentTop, contentWidth, contentLeft);
3163
+ floated ||= inner;
3164
+ }
3165
+ }
3166
+ return floated;
3167
+ }
3168
+ /** Where an out-of-flow box would have gone in its parent's flow: its
3169
+ * margin edge's, at either side, kept from the parent's corner, which may
3170
+ * yet move. */
3171
+ function placeStatic(box, parent, x, width, y) {
3172
+ box.staticPosition = {
3173
+ from: parent,
3174
+ x: x - parent.x,
3175
+ right: x + width - parent.x,
3176
+ y: y - parent.y,
3177
+ };
3178
+ }
3179
+ /**
3180
+ * An absolutely positioned child of a flex box or a grid, laid out once the
3181
+ * flow is: against its own containing block, which the box is only where
3182
+ * it is positioned, from where it is as the box's one item — at the start
3183
+ * of a flex box's content box (CSS Flexbox 4.1), of a grid's padding box
3184
+ * (CSS Grid 1, 9.1). Pushed against the box whatever it was, one in a
3185
+ * static flex box was placed from the box's corner, and `fixed` one from
3186
+ * a grid.
3187
+ */
3188
+ export function positionOutOfFlow(child, container, ctx, flex) {
3189
+ if (flex) {
3190
+ placeStatic(child, container, container.contentX, container.contentWidth, container.contentY);
3191
+ }
3192
+ else {
3193
+ placeStatic(child, container, container.x + container.borderLeft, container.width - container.borderLeft - container.borderRight, container.y + container.borderTop);
3194
+ }
3195
+ child.staticPosition.inside = {
3196
+ box: flex ? 'content' : 'padding',
3197
+ ...staticAlignment(child, container, flex),
3198
+ };
3199
+ ctx.positioned.push({
3200
+ box: child,
3201
+ containing: containingBlockFor(child) ?? container,
3202
+ });
3203
+ }
3204
+ /** How far along its free room a flex box's or a grid's one item is set:
3205
+ * 0 at the start, ½ in the middle, 1 at the end, across (from the inline
3206
+ * start) and down. A flex box's main axis by `justify-content`, and the
3207
+ * cross by `align-self`; a grid's by `justify-self` and `align-self`,
3208
+ * each falling back to the container's `-items`. */
3209
+ function staticAlignment(child, container, flex) {
3210
+ const own = child.style;
3211
+ const style = container.style;
3212
+ const factor = (align) => align === 'flex-end' ? 1 : align === 'center' ? 0.5 : 0;
3213
+ const cross = factor(own.alignSelf === AUTO ? style.alignItems : own.alignSelf);
3214
+ if (!flex) {
3215
+ const justify = own.justifySelf === 'auto' ? style.justifyItems : own.justifySelf;
3216
+ return { across: factor(justify), down: cross };
3217
+ }
3218
+ const justify = style.justifyContent;
3219
+ let main = justify === 'flex-end' || justify === 'end' || justify === 'right'
3220
+ ? 1
3221
+ : justify === 'center' ||
3222
+ justify === 'space-around' ||
3223
+ justify === 'space-evenly'
3224
+ ? 0.5
3225
+ : 0;
3226
+ if (style.flexDirection.endsWith('-reverse'))
3227
+ main = 1 - main;
3228
+ return style.flexDirection.startsWith('row')
3229
+ ? { across: main, down: cross }
3230
+ : { across: cross, down: main };
3231
+ }
573
3232
  /** The nearest positioned ancestor, or null for the initial containing
574
3233
  * block. Walks the box tree rather than the DOM, so an anonymous box in
575
3234
  * between is transparent — which is what the spec means by "the nearest
576
3235
  * positioned ancestor". */
577
3236
  function containingBlockFor(box) {
3237
+ // a fixed box's is the viewport, whatever is positioned around it — but
3238
+ // a transformed box is one for it too, and one with layout or paint
3239
+ // containment (CSS Containment 2, 3.3)
3240
+ const fixed = box.style.position === 'fixed';
578
3241
  let node = box.parent;
579
3242
  while (node) {
580
- if (node.style.position !== 'static' || node.parent === null)
3243
+ if ((!fixed && node.style.position !== 'static') ||
3244
+ transformed(node.style) ||
3245
+ contained(node, CONTAIN_LAYOUT | CONTAIN_PAINT) ||
3246
+ node.parent === null) {
581
3247
  return node;
3248
+ }
582
3249
  node = node.parent;
583
3250
  }
584
3251
  return null;
585
3252
  }
3253
+ /** The size containments that hold a box's width: `size` and
3254
+ * `inline-size`. */
3255
+ const CONTAIN_WIDTH = CONTAIN_SIZE | CONTAIN_INLINE_SIZE;
3256
+ /**
3257
+ * Whether a box has any of the containments `bits` names, as they apply
3258
+ * to it (CSS Containment 2, 3): none to an inline box that is no atomic
3259
+ * one, and none to a table's inner parts but a cell — nor size
3260
+ * containment to a table or a cell.
3261
+ */
3262
+ export function contained(box, bits) {
3263
+ if (!(box.style.contain & bits))
3264
+ return false;
3265
+ switch (box.kind) {
3266
+ case 'block':
3267
+ case 'flex':
3268
+ case 'replaced':
3269
+ return true;
3270
+ case 'table':
3271
+ case 'table-cell':
3272
+ return !!(box.style.contain & bits & ~CONTAIN_WIDTH);
3273
+ default:
3274
+ return false;
3275
+ }
3276
+ }
586
3277
  /** Move a box and everything under it, keeping the subtree's shape. */
587
3278
  export function moveTo(box, x, y) {
3279
+ // A position only a probe of an unbounded width comes to — a column after
3280
+ // an infinitely wide one — is no place to move a box to: it stays where
3281
+ // it is, since the probe only wants its size, and the pass after moves it
3282
+ // from somewhere finite (see `placeBlock`).
3283
+ if (!Number.isFinite(x) || !Number.isFinite(y))
3284
+ return;
588
3285
  translate(box, x - box.x, y - box.y);
589
3286
  }
3287
+ /** Move what a box holds down, and not the box: a table cell's content
3288
+ * sits where its `vertical-align` puts it, in a box that fills the row. */
3289
+ export function moveContent(box, dy) {
3290
+ translate(box, 0, dy);
3291
+ box.y -= dy;
3292
+ }
590
3293
  function translate(box, dx, dy) {
591
3294
  if (!dx && !dy)
592
3295
  return;
593
3296
  box.x += dx;
594
3297
  box.y += dy;
3298
+ // a list item's marker is placed in the same coordinates as its lines
3299
+ if (box.marker) {
3300
+ box.marker.x += dx;
3301
+ box.marker.y += dy;
3302
+ }
595
3303
  if (box.lines) {
596
3304
  for (const line of box.lines) {
597
3305
  line.x += dx;
@@ -604,6 +3312,8 @@ function translate(box, dx, dy) {
604
3312
  placed.x += dx;
605
3313
  placed.y += dy;
606
3314
  }
3315
+ for (const edge of line.edges ?? [])
3316
+ edge.x += dx;
607
3317
  }
608
3318
  }
609
3319
  for (const child of box.children)
@@ -630,23 +3340,41 @@ export function resolveEdges(box, containingWidth) {
630
3340
  style.borderLeftStyle === 'none' || style.borderLeftStyle === 'hidden'
631
3341
  ? 0
632
3342
  : style.borderLeftWidth;
633
- box.padTop = edge(style.paddingTop, containingWidth);
634
- box.padRight = edge(style.paddingRight, containingWidth);
635
- box.padBottom = edge(style.paddingBottom, containingWidth);
636
- box.padLeft = edge(style.paddingLeft, containingWidth);
3343
+ // at least zero: a `calc()` may come to less
3344
+ box.padTop = Math.max(0, edge(style.paddingTop, containingWidth));
3345
+ box.padRight = Math.max(0, edge(style.paddingRight, containingWidth));
3346
+ box.padBottom = Math.max(0, edge(style.paddingBottom, containingWidth));
3347
+ box.padLeft = Math.max(0, edge(style.paddingLeft, containingWidth));
637
3348
  box.marginTop = edge(style.marginTop, containingWidth);
638
3349
  box.marginRight = edge(style.marginRight, containingWidth);
639
3350
  box.marginBottom = edge(style.marginBottom, containingWidth);
640
3351
  box.marginLeft = edge(style.marginLeft, containingWidth);
3352
+ box.bottomStrut = null;
3353
+ // A table's parts but its caption have no margins, and its rows and row
3354
+ // groups no padding either (CSS 2.1 8.3, 8.4): a cell set `margin: 50px`
3355
+ // left a gap in its table that no browser draws. The values stay theirs,
3356
+ // for a cell to inherit (`padding: inherit`); they only do nothing here.
3357
+ if (box.kind === 'table-cell' ||
3358
+ box.kind === 'table-row' ||
3359
+ box.kind === 'table-row-group') {
3360
+ box.marginTop = 0;
3361
+ box.marginRight = 0;
3362
+ box.marginBottom = 0;
3363
+ box.marginLeft = 0;
3364
+ if (box.kind !== 'table-cell') {
3365
+ box.padTop = 0;
3366
+ box.padRight = 0;
3367
+ box.padBottom = 0;
3368
+ box.padLeft = 0;
3369
+ }
3370
+ }
3371
+ if (box.kind === 'table' || box.kind === 'table-cell')
3372
+ collapseEdges(box);
641
3373
  }
642
3374
  function edge(len, containingWidth) {
643
3375
  if (len === AUTO)
644
3376
  return 0;
645
- if (isPct(len))
646
- return Number.isFinite(containingWidth)
647
- ? (len.pct / 100) * containingWidth
648
- : 0;
649
- return len;
3377
+ return resolve(len, containingWidth, 0);
650
3378
  }
651
3379
  /** Whether every in-flow child is inline-level, which is what makes this box
652
3380
  * an inline formatting context rather than a block one. */
@@ -663,9 +3391,6 @@ function establishesInlineContext(box) {
663
3391
  case 'break':
664
3392
  sawInline = true;
665
3393
  break;
666
- case 'replaced':
667
- sawInline = true;
668
- break;
669
3394
  default:
670
3395
  if (isInlineLevel(child))
671
3396
  sawInline = true;
@@ -681,13 +3406,24 @@ function isInlineLevel(box) {
681
3406
  return (d === 'inline' ||
682
3407
  d === 'inline-block' ||
683
3408
  d === 'inline-flex' ||
684
- d === 'inline-table');
3409
+ d === 'inline-table' ||
3410
+ // an image told to be a table's part is inline (CSS Display 3, 2.4)
3411
+ (box.kind === 'replaced' && d.startsWith('table-')));
685
3412
  }
686
3413
  /** Whether a box establishes a block formatting context — contains its own
687
3414
  * floats, and does not collapse margins through its edges. */
688
3415
  export function establishesBFC(box) {
689
3416
  const style = box.style;
690
- if (style.overflowX !== 'visible' || style.overflowY !== 'visible')
3417
+ if (scrolls(style))
3418
+ return true;
3419
+ if (style.flowRoot)
3420
+ return true;
3421
+ // layout and paint containment make an independent formatting context
3422
+ if (contained(box, CONTAIN_LAYOUT | CONTAIN_PAINT))
3423
+ return true;
3424
+ // `continue: collapse` makes a block container a formatting context of
3425
+ // its own (CSS Overflow 4, 5.3)
3426
+ if (style.lineClamp !== null)
691
3427
  return true;
692
3428
  if (style.float !== 'none')
693
3429
  return true;
@@ -698,27 +3434,149 @@ export function establishesBFC(box) {
698
3434
  style.display === 'inline-flex') {
699
3435
  return true;
700
3436
  }
701
- if (box.kind === 'table-cell' || box.kind === 'table')
3437
+ if (box.kind === 'table-cell' ||
3438
+ box.kind === 'table-caption' ||
3439
+ box.kind === 'table') {
3440
+ return true;
3441
+ }
3442
+ // The root element establishes the document's formatting context. Here it
3443
+ // is a box below the synthetic initial containing block, so it is named:
3444
+ // without it, <html>'s own margin collapsed with <body>'s first block's.
3445
+ if (box.el?.name === 'html')
702
3446
  return true;
703
3447
  return box.parent === null;
704
3448
  }
705
3449
  /** What a box's `position: relative` offset moves it by, applied after
706
3450
  * layout so it does not affect anything else's position — which is the
707
- * whole of what makes it *relative*. */
3451
+ * whole of what makes it *relative* — and its translation with it. */
708
3452
  export function applyRelativeOffsets(box) {
709
3453
  for (const child of box.children)
710
3454
  applyRelativeOffsets(child);
711
3455
  const style = box.style;
3456
+ if (transformed(style) && box.kind !== 'inline') {
3457
+ // a transform moves the box it is on, and an inline box is none
3458
+ const [dx, dy] = translationOf(box);
3459
+ if (dx || dy) {
3460
+ translate(box, dx, dy);
3461
+ movedOffLine(box);
3462
+ }
3463
+ }
712
3464
  if (style.position !== 'relative' && style.position !== 'sticky')
713
3465
  return;
3466
+ const [dx, dy] = relativeOffset(box);
3467
+ if (!(dx || dy))
3468
+ return;
3469
+ translate(box, dx, dy);
3470
+ if (box.kind !== 'inline') {
3471
+ movedOffLine(box);
3472
+ return;
3473
+ }
3474
+ offsetInline(box, dx, dy);
3475
+ // the blocks it was broken around move with it (9.2.1.1)
3476
+ const blocks = box.cut ? CUT_BLOCKS.get(box) : undefined;
3477
+ if (blocks)
3478
+ for (const block of blocks)
3479
+ translate(block, dx, dy);
3480
+ }
3481
+ /** Whether a box is transformed, which is what makes it a containing
3482
+ * block and a layer as a positioned box is (CSS Transforms 1, 2). */
3483
+ export function transformed(style) {
3484
+ return style.translate !== null || style.transformTranslate !== null;
3485
+ }
3486
+ /** How far `translate` and a `transform` move a box: a percentage is of
3487
+ * its own border box (CSS Transforms 1, 7). */
3488
+ function translationOf(box) {
3489
+ let dx = 0;
3490
+ let dy = 0;
3491
+ for (const moved of [box.style.translate, box.style.transformTranslate]) {
3492
+ if (!moved)
3493
+ continue;
3494
+ dx += resolve(moved[0], box.width, 0);
3495
+ dy += resolve(moved[1], box.height, 0);
3496
+ }
3497
+ return [dx, dy];
3498
+ }
3499
+ /** How far `position: relative` moves a box. */
3500
+ function relativeOffset(box) {
3501
+ const style = box.style;
714
3502
  const parentWidth = box.parent ? box.parent.contentWidth : 0;
715
- const parentHeight = box.parent ? box.parent.contentHeight : 0;
716
3503
  const left = resolveOrNull(style.left, parentWidth);
717
3504
  const right = resolveOrNull(style.right, parentWidth);
718
- const top = resolveOrNull(style.top, parentHeight);
719
- const bottom = resolveOrNull(style.bottom, parentHeight);
720
- const dx = left ?? (right !== null ? -right : 0);
3505
+ // a percentage down is of a height the containing block sets, as a
3506
+ // percentage height is, and `auto` where its content decides it
3507
+ const top = resolveOrNull(style.top, box.percentHeightBase);
3508
+ const bottom = resolveOrNull(style.bottom, box.percentHeightBase);
3509
+ // both set is over-constrained, and the containing block's direction
3510
+ // says which wins: `left` left to right, `right` right to left (9.4.3)
3511
+ const rtl = box.parent?.style.direction === 'rtl';
3512
+ const dx = right !== null && (left === null || rtl) ? -right : (left ?? 0);
721
3513
  const dy = top ?? (bottom !== null ? -bottom : 0);
722
- translate(box, dx, dy);
3514
+ return [dx, dy];
3515
+ }
3516
+ /**
3517
+ * Move an inline box's text where `position: relative` puts the box. Its
3518
+ * text is on its block's lines, laid out apart from the text around it
3519
+ * (`collect`), and those fragments are what move; the lines stay where
3520
+ * they are (CSS 2.1 9.4.3). A box inside it has moved its own already, so
3521
+ * a text in both moves by the two together.
3522
+ */
3523
+ function offsetInline(box, dx, dy) {
3524
+ let block = box.parent;
3525
+ while (block && block.kind === 'inline')
3526
+ block = block.parent;
3527
+ const lines = block?.lines;
3528
+ if (!lines)
3529
+ return;
3530
+ INLINE_OFFSETS.set(box, { x: dx, y: dy });
3531
+ for (const line of lines) {
3532
+ for (const text of line.texts) {
3533
+ const owner = text.spans.boxAt?.(text.layoutStart);
3534
+ if (!owner || !holds(box, owner))
3535
+ continue;
3536
+ text.drawX += dx;
3537
+ text.drawY += dy;
3538
+ const was = TEXT_SHIFTS.get(text);
3539
+ TEXT_SHIFTS.set(text, {
3540
+ x: (was?.x ?? 0) + dx,
3541
+ y: (was?.y ?? 0) + dy,
3542
+ });
3543
+ SHIFTED_LINES.add(line);
3544
+ markMovedOff(lines, line);
3545
+ }
3546
+ }
3547
+ }
3548
+ /** Mark the line an inline-block or an image a box moved off was placed
3549
+ * on, where it was on one: the line stays, and the box is drawn where it
3550
+ * went (`MOVED_OFF_LINES`). */
3551
+ function movedOffLine(box) {
3552
+ let block = box.parent;
3553
+ while (block && block.kind === 'inline')
3554
+ block = block.parent;
3555
+ const lines = block?.lines;
3556
+ if (!lines)
3557
+ return;
3558
+ for (const line of lines) {
3559
+ for (const placed of line.atomics) {
3560
+ if (placed.box !== box)
3561
+ continue;
3562
+ markMovedOff(lines, line);
3563
+ return;
3564
+ }
3565
+ }
3566
+ }
3567
+ function markMovedOff(lines, line) {
3568
+ const moved = MOVED_OFF_LINES.get(lines);
3569
+ if (moved)
3570
+ moved.add(line);
3571
+ else
3572
+ MOVED_OFF_LINES.set(lines, new Set([line]));
3573
+ }
3574
+ /** Whether `inner` is `outer` or inside it. */
3575
+ function holds(outer, inner) {
3576
+ for (let at = inner; at; at = at.parent) {
3577
+ if (at === outer)
3578
+ return true;
3579
+ }
3580
+ return false;
723
3581
  }
724
3582
  //# sourceMappingURL=block.js.map