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