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