@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
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { attr, childrenOf, isElement, isText, NON_RENDERED, tagOf, } from '../dom.js';
|
|
1
|
+
import { attr, childrenOf, isElement, isText, inImpliedHead, NON_RENDERED, tagOf, } from '../dom.js';
|
|
2
|
+
import { quoteAt } from '../css/content.js';
|
|
3
|
+
import { CONTAIN_STYLE, FIRST_LINE_INHERITED, copyStyle, firstLineParent, inherit, } from '../css/style.js';
|
|
4
|
+
import { AUTO } from '../css/values.js';
|
|
5
|
+
import { svgIntrinsics } from '../svg.js';
|
|
2
6
|
/**
|
|
3
7
|
* One box. Fields are assigned rather than passed so the shape stays
|
|
4
8
|
* monomorphic — every box has every field, which is what keeps the property
|
|
@@ -16,6 +20,9 @@ export class Box {
|
|
|
16
20
|
y = 0;
|
|
17
21
|
width = 0;
|
|
18
22
|
height = 0;
|
|
23
|
+
/** For an inline box broken around a block in it (CSS 2.1 9.2.1.1), the
|
|
24
|
+
* sides it goes on from, which have no edge: 1 its start, 2 its end. */
|
|
25
|
+
cut = 0;
|
|
19
26
|
/** Resolved edges — border + padding, per side. */
|
|
20
27
|
borderTop = 0;
|
|
21
28
|
borderRight = 0;
|
|
@@ -29,6 +36,34 @@ export class Box {
|
|
|
29
36
|
marginRight = 0;
|
|
30
37
|
marginBottom = 0;
|
|
31
38
|
marginLeft = 0;
|
|
39
|
+
/** The margin that came out through the box's bottom edge from its last
|
|
40
|
+
* children, collapsed with its own bottom margin, which is their sum:
|
|
41
|
+
* kept as a strut for the next sibling's to collapse with. Null where
|
|
42
|
+
* none came out, which is most boxes. Set by the block layout. */
|
|
43
|
+
bottomStrut = null;
|
|
44
|
+
/** An inline box with a background or a border to paint behind its
|
|
45
|
+
* fragments: the ascent and descent of its own face, the height CSS
|
|
46
|
+
* paints them over (10.6.1). Set by the inline layout. */
|
|
47
|
+
decoration = null;
|
|
48
|
+
/** What a percentage `height` resolves against: an absolutely positioned
|
|
49
|
+
* box's containing block's height, which is known before the box is laid
|
|
50
|
+
* out (CSS 2.1 10.5). NaN everywhere else, where that height depends on
|
|
51
|
+
* the content and a percentage is `auto`. */
|
|
52
|
+
percentHeightBase = NaN;
|
|
53
|
+
/** Whether this box last handed its children a percentage base that
|
|
54
|
+
* was a number: until it does, they hold NaN already, and the next
|
|
55
|
+
* pass need not visit them to say so again. */
|
|
56
|
+
gavePercentBase = false;
|
|
57
|
+
/** Whether this box's top margin collapsed through its parent's top edge
|
|
58
|
+
* and was spent placing the parent (CSS 2.1 8.3.1): its own layout puts
|
|
59
|
+
* it at the parent's content top, and applies no margin again. `2` when
|
|
60
|
+
* the box is empty and its bottom margin went the same way, so the next
|
|
61
|
+
* sibling's may have too; `3` for a new formatting context the margin
|
|
62
|
+
* stopped above, separated from the floats it carries, which sits where
|
|
63
|
+
* the margins before it end and applies none of its own. Set by the walk
|
|
64
|
+
* that places the parent, each pass, and read only while every sibling
|
|
65
|
+
* before it was a `2`. */
|
|
66
|
+
topAbsorbed = 0;
|
|
32
67
|
/** Text, for a `text` box: already whitespace-processed and transformed. */
|
|
33
68
|
text = '';
|
|
34
69
|
/** This box's slice of the document text index. */
|
|
@@ -36,11 +71,16 @@ export class Box {
|
|
|
36
71
|
textEnd = 0;
|
|
37
72
|
/** Lines, for a box that established an inline formatting context. */
|
|
38
73
|
lines = null;
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
74
|
+
/** The widest row of floats its lines placed beside none of them, side
|
|
75
|
+
* by side: what the box is measured with, as a line's floats are
|
|
76
|
+
* (`LineBox.floats`). */
|
|
77
|
+
floatRow = 0;
|
|
78
|
+
/** A replaced box's intrinsic size and ratio; null on every other box,
|
|
79
|
+
* which is nearly all of them, so it is one field rather than four. */
|
|
80
|
+
intrinsic = null;
|
|
42
81
|
/**
|
|
43
|
-
* Cached min-/max-content widths, for a table cell
|
|
82
|
+
* Cached min-/max-content widths, for a table cell and for a box an
|
|
83
|
+
* intrinsic size (`width: fit-content`) sizes. -1 until measured.
|
|
44
84
|
*
|
|
45
85
|
* Intrinsic widths are width-independent by definition, so measuring them
|
|
46
86
|
* per layout pass was this engine breaking its own phase rule — a resize
|
|
@@ -51,17 +91,23 @@ export class Box {
|
|
|
51
91
|
*/
|
|
52
92
|
intrinsicMinContent = -1;
|
|
53
93
|
intrinsicMaxContent = -1;
|
|
94
|
+
/** Which layout of the pass this box last had, counted across all boxes:
|
|
95
|
+
* what tells a layout kept for reuse (`layoutOwn`) from one a probe has
|
|
96
|
+
* laid over since. */
|
|
97
|
+
layoutSerial = 0;
|
|
54
98
|
/** What a replaced box is: the resource seam and the control host both
|
|
55
99
|
* key on this rather than re-reading the tag. */
|
|
56
100
|
replaced = 'none';
|
|
57
|
-
/** The marker
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
101
|
+
/** The marker of a `list-item`, if it generated one. The marker is not a
|
|
102
|
+
* box: it is not in the flow, nothing can select it (CSS spells that
|
|
103
|
+
* `::marker`, and no author styles it here), and giving it one would put
|
|
104
|
+
* a bullet in every copied list. */
|
|
105
|
+
marker = null;
|
|
106
|
+
/** Which of an element's pseudo-elements this box is. Its `el` is null —
|
|
107
|
+
* it is no element — and its text box's is the element it hangs off, so
|
|
108
|
+
* a click on a link's generated text or first letter is a click on the
|
|
109
|
+
* link. */
|
|
110
|
+
pseudo = null;
|
|
65
111
|
/**
|
|
66
112
|
* The bounds of everything this box and its descendants draw, in document
|
|
67
113
|
* coordinates — **ink** bounds, not the border box, because `overflow:
|
|
@@ -86,6 +132,10 @@ export class Box {
|
|
|
86
132
|
/** Out-of-flow children in paint order (z-index, then document order),
|
|
87
133
|
* precomputed so a paint does not filter and sort per box per frame. */
|
|
88
134
|
positionedPaint = null;
|
|
135
|
+
/** Whether a positioned box is somewhere under this one: what the pass
|
|
136
|
+
* that gives each stacking context its positioned boxes walks, so that
|
|
137
|
+
* a subtree with none is not walked at all (`stackLayers`). */
|
|
138
|
+
holdsLayers = false;
|
|
89
139
|
/** The tallest line box under this box — the slack a binary search over
|
|
90
140
|
* the y-sorted lines needs, since a line's bottom is not monotone. */
|
|
91
141
|
maxLineHeight = 0;
|
|
@@ -102,6 +152,20 @@ export class Box {
|
|
|
102
152
|
outOfFlow = false;
|
|
103
153
|
/** Set on a float, for the same reason. */
|
|
104
154
|
isFloat = false;
|
|
155
|
+
/** Where an out-of-flow box would have been in the flow it was taken
|
|
156
|
+
* from — its static position (CSS 2.1 10.3.7, 10.6.4). */
|
|
157
|
+
staticPosition = null;
|
|
158
|
+
/** A table's captions, above and below it: they are in the box's height,
|
|
159
|
+
* and outside the table's own border and background (CSS 2.1 17.4). */
|
|
160
|
+
captionTop = 0;
|
|
161
|
+
captionBottom = 0;
|
|
162
|
+
/** A table whose borders collapse: the border each segment of its grid
|
|
163
|
+
* carries, resolved once per build (`collapseTable`). */
|
|
164
|
+
collapsed = null;
|
|
165
|
+
/** Set on a table whose borders collapse, and on its cells: their border
|
|
166
|
+
* widths are the halves the collapsing model leaves them, and the table
|
|
167
|
+
* paints the borders rather than the boxes. */
|
|
168
|
+
bordersCollapsed = false;
|
|
105
169
|
constructor(kind, el, style) {
|
|
106
170
|
this.kind = kind;
|
|
107
171
|
this.el = el;
|
|
@@ -141,6 +205,72 @@ export class Box {
|
|
|
141
205
|
return this.borderTop + this.borderBottom + this.padTop + this.padBottom;
|
|
142
206
|
}
|
|
143
207
|
}
|
|
208
|
+
/** The `::first-line` style of each block container a rule gives one to,
|
|
209
|
+
* kept beside the boxes: few documents have any (`BoxTree.firstLine`). */
|
|
210
|
+
export const FIRST_LINE = new WeakMap();
|
|
211
|
+
/** The url of an image generated content put in a pseudo-element: it has
|
|
212
|
+
* no element of its own to name it (`imageUrlOf`). */
|
|
213
|
+
export const CONTENT_IMAGES = new WeakMap();
|
|
214
|
+
/** How far `position: relative` moved an inline box (`offsetInline`), how
|
|
215
|
+
* far each text on a line moved with the boxes it is in, and the lines
|
|
216
|
+
* holding one: beside the boxes and the lines, since few documents move an
|
|
217
|
+
* inline box. A moved text's `drawX` and `drawY` are where it is drawn;
|
|
218
|
+
* its box's decorations are worked out from where it was, and moved by
|
|
219
|
+
* their own box's offset. */
|
|
220
|
+
export const INLINE_OFFSETS = new WeakMap();
|
|
221
|
+
export const TEXT_SHIFTS = new WeakMap();
|
|
222
|
+
export const SHIFTED_LINES = new WeakSet();
|
|
223
|
+
/** Of a block's lines, by the array they are, those `position: relative`
|
|
224
|
+
* or a translation moved something off — a text in a moved inline box, or
|
|
225
|
+
* an inline-block or an image on it — which is drawn where it went while
|
|
226
|
+
* the line stays where it was: what the paint's cull of a block's lines
|
|
227
|
+
* has to look at past the lines the damage meets (`paintLines`). By the
|
|
228
|
+
* array, so that a layout that makes new lines leaves none of the old. */
|
|
229
|
+
export const MOVED_OFF_LINES = new WeakMap();
|
|
230
|
+
/** How far `vertical-align` raised a text above its line's baseline, and an
|
|
231
|
+
* inline box's own baseline, where it did: beside the lines and the boxes
|
|
232
|
+
* for the reason the offsets above are. A line holding a raised text is
|
|
233
|
+
* among `SHIFTED_LINES` too, as its text is off the line's baseline. */
|
|
234
|
+
export const TEXT_RAISES = new WeakMap();
|
|
235
|
+
/** The blocks some of whose text casts a shadow (`text-shadow`), which the
|
|
236
|
+
* paint pass looks for in them and in no other. */
|
|
237
|
+
export const SHADOWED_TEXT = new WeakSet();
|
|
238
|
+
export const BOX_RAISES = new WeakMap();
|
|
239
|
+
/** And the raise, on one line, of each inline box a `top` or `bottom` box
|
|
240
|
+
* holds, the box itself among them: that box's baseline is where the
|
|
241
|
+
* line's edge puts it, which is a different height on every line, so its
|
|
242
|
+
* background, drawn from its baseline, is put there by line. */
|
|
243
|
+
export const LINE_BOX_RAISES = new WeakMap();
|
|
244
|
+
/** The table cells wholly in columns `visibility: collapse` took out of
|
|
245
|
+
* their table (CSS 2.1 17.5.5), which the paint pass leaves out: a cell is
|
|
246
|
+
* no descendant of its column and inherits nothing from it, so its own
|
|
247
|
+
* style still says `visible`. */
|
|
248
|
+
export const COLLAPSED_CELLS = new WeakSet();
|
|
249
|
+
/** And the cells spanning into a column or a row taken out: laid out as
|
|
250
|
+
* they would have been, placed over what is left of their span, and
|
|
251
|
+
* clipped to it. */
|
|
252
|
+
export const CLIPPED_CELLS = new WeakSet();
|
|
253
|
+
/** The boxes after a line-clamp container's clamp point (CSS Overflow 4,
|
|
254
|
+
* 5.3.1): invisible, with all they hold, and no taller than nothing to
|
|
255
|
+
* the blocks around them. Marked by the layout of the flow they are in,
|
|
256
|
+
* which leaves them where the last layout put them, and passed over by
|
|
257
|
+
* paint and the paint bounds. */
|
|
258
|
+
export const CLAMPED = new WeakSet();
|
|
259
|
+
export const GRID_TRACKS = new WeakMap();
|
|
260
|
+
/** A flex box's children in the order it lays them out and paints them,
|
|
261
|
+
* `order` first and the document's after it (CSS Flexbox 5.4), where
|
|
262
|
+
* `order` moves any: an absolutely positioned child's is 0. */
|
|
263
|
+
export const PAINT_ORDER = new WeakMap();
|
|
264
|
+
/** The blocks that broke a relatively positioned inline box in pieces,
|
|
265
|
+
* under its first piece: its offset moves them too (CSS 2.1 9.2.1.1),
|
|
266
|
+
* though they stand outside it (`breakAround`). */
|
|
267
|
+
export const CUT_BLOCKS = new WeakMap();
|
|
268
|
+
/** The blocks that broke an inline box under full opacity in pieces, and
|
|
269
|
+
* the opacity they take from it — from each such inline box around them,
|
|
270
|
+
* multiplied. They stand outside it (CSS 2.1 9.2.1.1) and are still its
|
|
271
|
+
* content, which its opacity fades as a group: a green square in a
|
|
272
|
+
* `<span style="opacity: .5">` was drawn at full strength. */
|
|
273
|
+
export const FADED_BLOCKS = new WeakMap();
|
|
144
274
|
/** Build the box tree for a document. */
|
|
145
275
|
export function buildBoxes(root, options) {
|
|
146
276
|
const builder = new Builder(options);
|
|
@@ -153,66 +283,248 @@ export function buildBoxes(root, options) {
|
|
|
153
283
|
* output, a runaway template) from a stack overflow five phases later.
|
|
154
284
|
* Blink's parser flattens at 512 for the same reason; content past the cap
|
|
155
285
|
* is dropped, which beats the alternative of crashing the application.
|
|
286
|
+
* The parser keeps a document to 256 elements (`dom.ts`), so what reaches
|
|
287
|
+
* this now is the anonymous boxes a table builds round each of them.
|
|
156
288
|
*/
|
|
157
289
|
const MAX_DEPTH = 512;
|
|
290
|
+
/** The sharing key the root box's children share under: one root style per
|
|
291
|
+
* build, and the shared styles last a build (`Cascade.beginSharing`). */
|
|
292
|
+
const ROOT_SHARE_KEY = 0;
|
|
158
293
|
class Builder {
|
|
159
294
|
_options;
|
|
160
|
-
|
|
295
|
+
/** The document text, in the pieces it was pushed in — joined once at the
|
|
296
|
+
* end, so that taking back a line's last space is not a copy of it. */
|
|
297
|
+
_chunks = [];
|
|
298
|
+
_length = 0;
|
|
161
299
|
_textBoxes = [];
|
|
300
|
+
_textStyles = new Set();
|
|
301
|
+
_styles = new Map();
|
|
302
|
+
/** The last text box's style: runs of text share their parent's, so
|
|
303
|
+
* most text boxes repeat it and add nothing to `_textStyles`. */
|
|
304
|
+
_lastTextStyle = null;
|
|
162
305
|
_controls = [];
|
|
163
306
|
_links = [];
|
|
164
|
-
|
|
165
|
-
|
|
307
|
+
_backgrounds = [];
|
|
308
|
+
_contentImages = [];
|
|
309
|
+
_relative = false;
|
|
310
|
+
_negative = false;
|
|
311
|
+
_firstLine = false;
|
|
312
|
+
_nestedOutOfLine = false;
|
|
313
|
+
_movedInline = false;
|
|
314
|
+
_clipText = false;
|
|
315
|
+
/** The CSS counters in scope, for `counter()` in generated content. */
|
|
316
|
+
_scopes = new CounterScopes();
|
|
317
|
+
/** How many quotes generated content has opened and not closed. */
|
|
318
|
+
_quoteDepth = 0;
|
|
319
|
+
/** Where the inline content being built stands, for collapsing white
|
|
320
|
+
* space across element boundaries. */
|
|
321
|
+
_ws = 'start';
|
|
322
|
+
/** Whether the word the inline content being built is in has had its
|
|
323
|
+
* first letter, for `text-transform: capitalize`: a word runs on across
|
|
324
|
+
* element boundaries, as white space collapses across them. */
|
|
325
|
+
_lettered = false;
|
|
166
326
|
_depth = 0;
|
|
327
|
+
/** The `::first-letter` whose letter is still to come, for the block
|
|
328
|
+
* container whose first line has not begun. Null when there is none,
|
|
329
|
+
* and once anything but a letter begins that line. */
|
|
330
|
+
_firstLetter = null;
|
|
167
331
|
constructor(options) {
|
|
168
332
|
this._options = options;
|
|
169
333
|
}
|
|
170
334
|
run(root) {
|
|
171
335
|
const cascade = this._options.cascade;
|
|
172
|
-
|
|
336
|
+
cascade.beginSharing();
|
|
337
|
+
const { style: rootStyle, html: impliedHtml } = cascade.rootStyle(hasBody(root), hasHtml(root));
|
|
173
338
|
const rootBox = new Box('block', null, rootStyle);
|
|
339
|
+
// a fragment's root stands in for a `<body>`, counters and all
|
|
340
|
+
this._scopes.open();
|
|
341
|
+
this._counterChanges(rootStyle, {
|
|
342
|
+
node: root,
|
|
343
|
+
style: rootStyle,
|
|
344
|
+
key: ROOT_SHARE_KEY,
|
|
345
|
+
parent: null,
|
|
346
|
+
});
|
|
174
347
|
// The DOM's `<html>`/`<body>` are ordinary elements with ordinary styles;
|
|
175
348
|
// the box above them exists only to be the initial containing block, so
|
|
176
349
|
// it carries no margins of its own and cannot collapse with anything.
|
|
177
|
-
this._children(root, rootBox, rootStyle, false);
|
|
178
|
-
|
|
350
|
+
this._children(root, rootBox, rootStyle, false, null, ROOT_SHARE_KEY);
|
|
351
|
+
this._endLine();
|
|
352
|
+
fixUp(rootBox, anonymousStyles(cascade.initial));
|
|
179
353
|
assignSubtreeRanges(rootBox);
|
|
180
354
|
return {
|
|
181
355
|
root: rootBox,
|
|
182
|
-
text: this.
|
|
356
|
+
text: this._chunks.join(''),
|
|
183
357
|
textBoxes: this._textBoxes,
|
|
358
|
+
textStyles: this._textStyles,
|
|
359
|
+
styles: this._styles,
|
|
184
360
|
controls: this._controls,
|
|
185
361
|
links: this._links,
|
|
362
|
+
backgrounds: this._backgrounds,
|
|
363
|
+
contentImages: this._contentImages,
|
|
364
|
+
relative: this._relative || isRelative(rootStyle),
|
|
365
|
+
negative: this._negative,
|
|
366
|
+
firstLine: this._firstLine,
|
|
367
|
+
firstLineStyler: this._firstLine ? this._firstLineStyler() : null,
|
|
368
|
+
nestedOutOfLine: this._nestedOutOfLine,
|
|
369
|
+
movedInline: this._movedInline,
|
|
370
|
+
clipText: this._clipText,
|
|
371
|
+
impliedHtml,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
/** `BoxTree.firstLineStyler`, over this build's cascade. */
|
|
375
|
+
_firstLineStyler() {
|
|
376
|
+
const cascade = this._options.cascade;
|
|
377
|
+
const styles = this._styles;
|
|
378
|
+
return (box, parent) => {
|
|
379
|
+
const up = box.parent?.style ?? box.style;
|
|
380
|
+
const inherits = firstLineParent(up, parent);
|
|
381
|
+
const el = box.kind === 'text' ? null : box.el;
|
|
382
|
+
const own = el ? styles.get(el) : undefined;
|
|
383
|
+
if (el && own && own.style === box.style) {
|
|
384
|
+
return cascade.styleFor(el, inherits, own.inFlex);
|
|
385
|
+
}
|
|
386
|
+
// text, an anonymous box, a pseudo-element's: no rules of its own to
|
|
387
|
+
// be asked again, so what it took from its parent it takes from the
|
|
388
|
+
// parent's style on the line, and what it set it keeps
|
|
389
|
+
const out = copyStyle(box.style);
|
|
390
|
+
const to = out;
|
|
391
|
+
const mine = box.style;
|
|
392
|
+
const theirs = up;
|
|
393
|
+
const line = inherits;
|
|
394
|
+
for (const name of FIRST_LINE_INHERITED) {
|
|
395
|
+
if (mine[name] === theirs[name])
|
|
396
|
+
to[name] = line[name];
|
|
397
|
+
}
|
|
398
|
+
return out;
|
|
186
399
|
};
|
|
187
400
|
}
|
|
188
401
|
/** Build boxes for a parent's children into `into`. */
|
|
189
|
-
_children(node, into, parentStyle, inFlex, owner
|
|
402
|
+
_children(node, into, parentStyle, inFlex, owner, parentKey) {
|
|
403
|
+
// CSS 2.1 17.2.1: a column group holds columns, and anything else in it
|
|
404
|
+
// is not rendered
|
|
405
|
+
const onlyColumns = parentStyle.display === 'table-column-group';
|
|
406
|
+
// A closed `<details>` shows its first `<summary>` and nothing else:
|
|
407
|
+
// HTML renders the rest into a slot that is out of the box tree until
|
|
408
|
+
// the element is `open`.
|
|
409
|
+
const closed = owner !== null &&
|
|
410
|
+
tagOf(owner) === 'details' &&
|
|
411
|
+
owner.attribs.open === undefined;
|
|
412
|
+
let summarised = false;
|
|
190
413
|
for (const child of childrenOf(node)) {
|
|
414
|
+
if (closed) {
|
|
415
|
+
if (!isElement(child) || summarised || tagOf(child) !== 'summary') {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
summarised = true;
|
|
419
|
+
}
|
|
191
420
|
if (isText(child)) {
|
|
192
|
-
|
|
421
|
+
if (!onlyColumns)
|
|
422
|
+
this._textNode(child.data, into, parentStyle, owner);
|
|
193
423
|
continue;
|
|
194
424
|
}
|
|
195
425
|
if (!isElement(child))
|
|
196
426
|
continue;
|
|
197
|
-
this._element(child, into, parentStyle, inFlex);
|
|
427
|
+
this._element(child, into, parentStyle, inFlex, parentKey, onlyColumns);
|
|
198
428
|
}
|
|
199
429
|
}
|
|
200
|
-
_element(el, into, parentStyle, inFlex) {
|
|
430
|
+
_element(el, into, parentStyle, inFlex, parentKey, onlyColumns = false) {
|
|
201
431
|
const tag = tagOf(el);
|
|
202
|
-
if (NON_RENDERED.has(tag))
|
|
432
|
+
if (NON_RENDERED.has(tag) || inImpliedHead(el, tag))
|
|
203
433
|
return;
|
|
204
|
-
|
|
434
|
+
// shared with every element that must compute the same style, which in
|
|
435
|
+
// a long document is most of them (`Cascade.sharedStyleFor`)
|
|
436
|
+
const { style, key } = this._options.cascade.sharedStyleFor(el, parentStyle, parentKey, inFlex);
|
|
437
|
+
this._styles.set(el, { style, inFlex });
|
|
205
438
|
if (style.display === 'none')
|
|
206
439
|
return;
|
|
440
|
+
if (onlyColumns && style.display !== 'table-column')
|
|
441
|
+
return;
|
|
442
|
+
// what an element counts is in scope for it and what it holds, and for
|
|
443
|
+
// what follows it where its parent has no such counter
|
|
444
|
+
// (`CounterScopes`)
|
|
445
|
+
this._scopes.open();
|
|
446
|
+
// style containment keeps what the element's subtree does to counters
|
|
447
|
+
// and quotes in it (CSS Containment 2, 3.4)
|
|
448
|
+
const contains = (style.contain & CONTAIN_STYLE) !== 0 && style.display !== 'contents';
|
|
449
|
+
const quoteDepth = this._quoteDepth;
|
|
450
|
+
if (contains)
|
|
451
|
+
this._scopes.contain();
|
|
452
|
+
this._elementIn(el, tag, style, key, into, inFlex, {
|
|
453
|
+
node: el,
|
|
454
|
+
style,
|
|
455
|
+
key,
|
|
456
|
+
parent: { style: parentStyle, key: parentKey, inFlex },
|
|
457
|
+
});
|
|
458
|
+
if (contains)
|
|
459
|
+
this._quoteDepth = quoteDepth;
|
|
460
|
+
this._scopes.close();
|
|
461
|
+
}
|
|
462
|
+
_elementIn(el, tag, style, key, into, inFlex, place) {
|
|
463
|
+
if (style.display === 'contents') {
|
|
464
|
+
// no box of its own (CSS Display 3, 2.5): its `::before`, its
|
|
465
|
+
// children and its `::after` are its parent's, in its style — the
|
|
466
|
+
// items of a flex row still, through a wrapper Tailwind's `contents`
|
|
467
|
+
// takes out — and a replaced element, which has nothing to hand on,
|
|
468
|
+
// is not rendered
|
|
469
|
+
if (tag === 'br' || replacedKind(el, tag) !== 'none')
|
|
470
|
+
return;
|
|
471
|
+
this._counterChanges(style, place);
|
|
472
|
+
this._pseudo(el, 'before', style, into);
|
|
473
|
+
this._children(el, into, style, inFlex, el, key);
|
|
474
|
+
this._pseudo(el, 'after', style, into);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (isRelative(style)) {
|
|
478
|
+
this._relative = true;
|
|
479
|
+
if (style.display === 'inline' && isOffset(style)) {
|
|
480
|
+
this._movedInline = true;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// a translation is moved by the same pass
|
|
484
|
+
if (style.translate || style.transformTranslate)
|
|
485
|
+
this._relative = true;
|
|
486
|
+
if (style.backgroundClipText)
|
|
487
|
+
this._clipText = true;
|
|
488
|
+
if (style.verticalAlign !== 'baseline' && style.display === 'inline') {
|
|
489
|
+
this._movedInline = true;
|
|
490
|
+
}
|
|
491
|
+
if (isNegative(style))
|
|
492
|
+
this._negative = true;
|
|
493
|
+
// before anything else of the element's, including its `::before`,
|
|
494
|
+
// and for the element whatever box it makes (CSS 2.1 12.4)
|
|
495
|
+
this._counterChanges(style, place);
|
|
207
496
|
// `<br>` is a line break rather than a box, and it is the one element
|
|
208
497
|
// whose *absence* of a box still has to reach the inline layout.
|
|
209
498
|
if (tag === 'br') {
|
|
499
|
+
this._endLine();
|
|
500
|
+
// the first line ends with no letter on it (CSS 2.1 5.12.2)
|
|
501
|
+
this._abandonLetter();
|
|
502
|
+
if (style.clear !== 'none') {
|
|
503
|
+
// a break that clears the floats — `<br clear="all">` after a
|
|
504
|
+
// floated image — puts what follows it below them: an empty block
|
|
505
|
+
// that clears them, which the inline content around it is broken
|
|
506
|
+
// for (`breakInlines`), where a line break went on beside them
|
|
507
|
+
const clearing = copyStyle(style);
|
|
508
|
+
clearing.display = 'block';
|
|
509
|
+
into.append(new Box('block', el, clearing));
|
|
510
|
+
this._ws = 'start';
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
210
513
|
const box = new Box('break', el, style);
|
|
211
514
|
into.append(box);
|
|
212
515
|
this._push('\n', box);
|
|
516
|
+
this._ws = 'start';
|
|
213
517
|
return;
|
|
214
518
|
}
|
|
215
|
-
|
|
519
|
+
// an `<object>` is its image once it has one, and its content until;
|
|
520
|
+
// an `<embed>` its image, and a `<video>` its poster, or a frame
|
|
521
|
+
const replaced = tag === 'object'
|
|
522
|
+
? this._options.imageSize(el)
|
|
523
|
+
? 'image'
|
|
524
|
+
: 'none'
|
|
525
|
+
: (tag === 'embed' || tag === 'video') && this._options.imageSize(el)
|
|
526
|
+
? 'image'
|
|
527
|
+
: replacedKind(el, tag);
|
|
216
528
|
if (replaced !== 'none') {
|
|
217
529
|
this._replaced(el, tag, replaced, style, into);
|
|
218
530
|
return;
|
|
@@ -222,51 +534,185 @@ class Builder {
|
|
|
222
534
|
const kind = boxKindFor(style.display);
|
|
223
535
|
const box = new Box(kind, el, style);
|
|
224
536
|
into.append(box);
|
|
537
|
+
if ((kind === 'block' || kind === 'table-cell' || kind === 'table-caption') &&
|
|
538
|
+
this._options.cascade.hasFirstLine) {
|
|
539
|
+
const firstLine = this._options.cascade.firstLineStyle(el, style);
|
|
540
|
+
if (firstLine) {
|
|
541
|
+
FIRST_LINE.set(box, firstLine);
|
|
542
|
+
this._firstLine = true;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (style.backgroundImage ||
|
|
546
|
+
style.backgroundImages ||
|
|
547
|
+
typeof style.borderImage.source === 'string') {
|
|
548
|
+
this._backgrounds.push(box);
|
|
549
|
+
}
|
|
225
550
|
if (style.position === 'absolute' || style.position === 'fixed')
|
|
226
551
|
box.outOfFlow = true;
|
|
227
552
|
else if (style.float !== 'none')
|
|
228
553
|
box.isFloat = true;
|
|
554
|
+
if (into.kind === 'inline' && (box.outOfFlow || box.isFloat))
|
|
555
|
+
this._nestedOutOfLine = true;
|
|
229
556
|
if (attr(el, 'href') && (tag === 'a' || tag === 'area'))
|
|
230
557
|
this._links.push(box);
|
|
231
|
-
|
|
232
|
-
|
|
558
|
+
// a column's content is not rendered at all (CSS 2.1 17.2.1)
|
|
559
|
+
if (style.display === 'table-column')
|
|
560
|
+
return;
|
|
561
|
+
let insideMarker = null;
|
|
562
|
+
let markerStyle = null;
|
|
563
|
+
let ownMarker = false;
|
|
564
|
+
let markerImage = null;
|
|
565
|
+
let insideImage = null;
|
|
566
|
+
if (style.display === 'list-item' && style.listStyleImage) {
|
|
567
|
+
// asked for as generated content's images are, and the tree built
|
|
568
|
+
// again when it arrives; until it has, and where it never does, the
|
|
569
|
+
// item's marker is its `list-style-type`'s
|
|
570
|
+
const url = style.listStyleImage;
|
|
571
|
+
const size = this._options.urlSize?.(url) ?? null;
|
|
572
|
+
this._contentImages.push({ url, element: el, sized: !!size });
|
|
573
|
+
if (size)
|
|
574
|
+
markerImage = { url, size };
|
|
233
575
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
576
|
+
if (style.display === 'list-item') {
|
|
577
|
+
// the item's number, the `list-item` counter it has just counted
|
|
578
|
+
const written = markerFor(style, this._scopes.value('list-item'), this._options.cascade.counterStyles);
|
|
579
|
+
let text = written.text;
|
|
580
|
+
let flush = written.flush;
|
|
581
|
+
markerStyle = this._options.cascade.markerStyle(el, style);
|
|
582
|
+
// a `::marker` with a `content` is set as that, as it is written —
|
|
583
|
+
// its strings and its counters, where one of `none` is no marker
|
|
584
|
+
const content = markerStyle?.content;
|
|
585
|
+
if (content === 'none')
|
|
586
|
+
text = '';
|
|
587
|
+
else if (Array.isArray(content) && markerStyle) {
|
|
588
|
+
text = this._generated(content, markerStyle, el)
|
|
589
|
+
.filter((piece) => typeof piece === 'string')
|
|
590
|
+
.join('');
|
|
591
|
+
ownMarker = true;
|
|
592
|
+
flush = true;
|
|
593
|
+
}
|
|
594
|
+
else if (style.listStyleType.startsWith('"')) {
|
|
595
|
+
ownMarker = true;
|
|
596
|
+
flush = true;
|
|
597
|
+
}
|
|
598
|
+
if (markerImage && !ownMarker) {
|
|
599
|
+
if (style.listStylePosition === 'inside')
|
|
600
|
+
insideImage = markerImage.url;
|
|
601
|
+
else {
|
|
602
|
+
const scale = this._options.scale ?? 1;
|
|
603
|
+
box.marker = {
|
|
604
|
+
text: '',
|
|
605
|
+
layout: null,
|
|
606
|
+
x: 0,
|
|
607
|
+
y: 0,
|
|
608
|
+
style: markerStyle,
|
|
609
|
+
image: {
|
|
610
|
+
url: markerImage.url,
|
|
611
|
+
width: (markerImage.size.width ?? 0) * scale,
|
|
612
|
+
height: (markerImage.size.height ?? 0) * scale,
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
else if (text && style.listStylePosition === 'inside') {
|
|
618
|
+
insideMarker = flush ? text : `${text} `;
|
|
619
|
+
}
|
|
620
|
+
else if (text) {
|
|
621
|
+
box.marker = { text, layout: null, x: 0, y: 0, style: markerStyle };
|
|
622
|
+
if (flush)
|
|
623
|
+
box.marker.flush = true;
|
|
624
|
+
}
|
|
238
625
|
}
|
|
239
626
|
const childInFlex = style.display === 'flex' || style.display === 'inline-flex';
|
|
627
|
+
const flow = flowOf(style, box);
|
|
628
|
+
const around = this._ws;
|
|
629
|
+
const aroundWord = this._lettered;
|
|
630
|
+
if (flow === 'block')
|
|
631
|
+
this._endLine();
|
|
632
|
+
if (flow !== 'inline')
|
|
633
|
+
this._ws = 'start';
|
|
634
|
+
// A first letter is looked for in the first line of a block container,
|
|
635
|
+
// down through its inline content and its first blocks. A float, a
|
|
636
|
+
// positioned box and a flex container are no part of that line, and
|
|
637
|
+
// an atomic inline is something other than a letter at its start
|
|
638
|
+
// (CSS 2.1 5.12.2). A block container with rules of its own takes the
|
|
639
|
+
// search over.
|
|
640
|
+
const outerLetter = this._firstLetter;
|
|
641
|
+
const skipped = flow === 'out' || flow === 'atomic' || kind === 'flex';
|
|
642
|
+
if (skipped)
|
|
643
|
+
this._firstLetter = null;
|
|
644
|
+
// a block starts a line, and punctuation before it was on another
|
|
645
|
+
else if (flow === 'block' && outerLetter)
|
|
646
|
+
giveBack(outerLetter);
|
|
647
|
+
const ownRules = hasFirstLetter(style.display)
|
|
648
|
+
? this._options.cascade.firstLetterRules(el)
|
|
649
|
+
: null;
|
|
650
|
+
const ownLetter = ownRules ? { rules: ownRules, punctuation: [] } : null;
|
|
651
|
+
if (ownLetter)
|
|
652
|
+
this._firstLetter = ownLetter;
|
|
240
653
|
this._depth += 1;
|
|
241
|
-
|
|
654
|
+
if (insideImage) {
|
|
655
|
+
// an inline image at the start of the first line, as a generated
|
|
656
|
+
// image is, and the space a marker's text ends in
|
|
657
|
+
this._contentImage(insideImage, box, style, el);
|
|
658
|
+
this._textNode(' ', box, style, el);
|
|
659
|
+
}
|
|
660
|
+
else if (insideMarker) {
|
|
661
|
+
this._insideMarker(insideMarker, style, markerStyle, box, el);
|
|
662
|
+
}
|
|
663
|
+
this._pseudo(el, 'before', style, box);
|
|
664
|
+
this._children(el, box, style, childInFlex, el, key);
|
|
665
|
+
this._pseudo(el, 'after', style, box);
|
|
242
666
|
this._depth -= 1;
|
|
243
|
-
|
|
244
|
-
|
|
667
|
+
this._letterAfter(flow, skipped, outerLetter, ownLetter);
|
|
668
|
+
if (flow !== 'inline')
|
|
669
|
+
this._endLine();
|
|
670
|
+
this._ws = after(flow, this._ws, around);
|
|
671
|
+
this._lettered = letteredAfterFlow(flow, this._lettered, aroundWord);
|
|
245
672
|
}
|
|
246
673
|
_replaced(el, tag, replaced, style, into) {
|
|
247
674
|
const box = new Box('replaced', el, style);
|
|
248
675
|
box.replaced = replaced;
|
|
249
676
|
into.append(box);
|
|
677
|
+
if (style.backgroundImage ||
|
|
678
|
+
style.backgroundImages ||
|
|
679
|
+
typeof style.borderImage.source === 'string') {
|
|
680
|
+
this._backgrounds.push(box);
|
|
681
|
+
}
|
|
250
682
|
if (style.position === 'absolute' || style.position === 'fixed')
|
|
251
683
|
box.outOfFlow = true;
|
|
252
684
|
else if (style.float !== 'none')
|
|
253
685
|
box.isFloat = true;
|
|
686
|
+
if (into.kind === 'inline' && (box.outOfFlow || box.isFloat))
|
|
687
|
+
this._nestedOutOfLine = true;
|
|
688
|
+
const flow = flowOf(style, box);
|
|
689
|
+
if (flow === 'block')
|
|
690
|
+
this._endLine();
|
|
691
|
+
this._ws = after(flow, this._ws, this._ws);
|
|
692
|
+
this._lettered = letteredAfterFlow(flow, this._lettered, this._lettered);
|
|
693
|
+
if (flow === 'atomic')
|
|
694
|
+
this._abandonLetter();
|
|
254
695
|
if (replaced === 'image') {
|
|
255
696
|
// Both sources are CSS pixels — an image pixel is one, and so is an
|
|
256
697
|
// attribute — and the box is device.
|
|
257
698
|
const scale = this._options.scale ?? 1;
|
|
258
699
|
const loaded = this._options.imageSize(el);
|
|
259
700
|
if (loaded) {
|
|
260
|
-
box
|
|
261
|
-
box.intrinsicHeight = loaded.height * scale;
|
|
701
|
+
setIntrinsics(box, loaded, scale);
|
|
262
702
|
}
|
|
263
703
|
else {
|
|
264
704
|
// An image that has not arrived still needs a box, or the document
|
|
265
705
|
// reflows under the reader when it does. The attributes are the
|
|
266
706
|
// author telling us the size in advance; without them the box is a
|
|
267
707
|
// small placeholder rather than nothing.
|
|
268
|
-
|
|
269
|
-
|
|
708
|
+
const width = numberAttr(el, 'width') ?? 0;
|
|
709
|
+
const height = numberAttr(el, 'height') ?? 0;
|
|
710
|
+
box.intrinsic = {
|
|
711
|
+
width: width * scale,
|
|
712
|
+
height: height * scale,
|
|
713
|
+
missing: 0,
|
|
714
|
+
ratio: width > 0 && height > 0 ? width / height : 0,
|
|
715
|
+
};
|
|
270
716
|
}
|
|
271
717
|
// The alt text joins the document text, so a document read with the
|
|
272
718
|
// images blocked still copies as prose.
|
|
@@ -277,35 +723,494 @@ class Builder {
|
|
|
277
723
|
}
|
|
278
724
|
if (replaced === 'hr')
|
|
279
725
|
return;
|
|
726
|
+
if (replaced === 'svg') {
|
|
727
|
+
// its `em` is its own font size, in CSS pixels like the rest of it
|
|
728
|
+
const scale = this._options.scale ?? 1;
|
|
729
|
+
setIntrinsics(box, svgIntrinsics(el, style.fontSize / scale), scale);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (replaced === 'frame') {
|
|
733
|
+
const scale = this._options.scale ?? 1;
|
|
734
|
+
if (tag === 'canvas') {
|
|
735
|
+
// A canvas is the size of its bitmap, which its `width` and
|
|
736
|
+
// `height` attributes give, 300 by 150 where they do not, and
|
|
737
|
+
// keeps its proportions (HTML 4.12.5): no script draws in it, so it
|
|
738
|
+
// is that box with nothing in it. Taken for an element with no box
|
|
739
|
+
// of its own, it took no room, and one set a height kept no width.
|
|
740
|
+
const width = canvasSize(el, 'width', 300);
|
|
741
|
+
const height = canvasSize(el, 'height', 150);
|
|
742
|
+
box.intrinsic = {
|
|
743
|
+
width: width * scale,
|
|
744
|
+
height: height * scale,
|
|
745
|
+
missing: 0,
|
|
746
|
+
ratio: width > 0 && height > 0 ? width / height : 0,
|
|
747
|
+
};
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
// HTML's default object size, in CSS pixels; `width` and `height`
|
|
751
|
+
// attributes reach the style as presentational hints and win
|
|
752
|
+
box.intrinsic = {
|
|
753
|
+
width: 300 * scale,
|
|
754
|
+
height: 150 * scale,
|
|
755
|
+
missing: 0,
|
|
756
|
+
ratio: 0,
|
|
757
|
+
};
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
280
760
|
const size = this._options.controlSize(el, replaced, style);
|
|
281
|
-
box.
|
|
282
|
-
|
|
761
|
+
box.intrinsic = {
|
|
762
|
+
width: size.width,
|
|
763
|
+
height: size.height,
|
|
764
|
+
missing: 0,
|
|
765
|
+
ratio: 0,
|
|
766
|
+
};
|
|
283
767
|
this._controls.push(box);
|
|
284
768
|
// A control's value is the widget's, not the document's: putting it in
|
|
285
769
|
// the selection index would make Ctrl+A copy the contents of every text
|
|
286
770
|
// field, which no document viewer does.
|
|
287
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* An element's `::before` or `::after`, when a rule gives it content: a
|
|
774
|
+
* box of its own `display`, holding the text its `content` comes to, which
|
|
775
|
+
* goes through the same white-space processing as the document's text.
|
|
776
|
+
*/
|
|
777
|
+
_pseudo(el, which, elementStyle, into) {
|
|
778
|
+
const style = this._options.cascade.pseudoStyleFor(el, which, elementStyle);
|
|
779
|
+
if (!style || style.display === 'none')
|
|
780
|
+
return;
|
|
781
|
+
if (isRelative(style)) {
|
|
782
|
+
this._relative = true;
|
|
783
|
+
if (style.display === 'inline' && isOffset(style)) {
|
|
784
|
+
this._movedInline = true;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
// a translation is moved by the same pass
|
|
788
|
+
if (style.translate || style.transformTranslate)
|
|
789
|
+
this._relative = true;
|
|
790
|
+
if (style.backgroundClipText)
|
|
791
|
+
this._clipText = true;
|
|
792
|
+
if (style.verticalAlign !== 'baseline' && style.display === 'inline') {
|
|
793
|
+
this._movedInline = true;
|
|
794
|
+
}
|
|
795
|
+
if (isNegative(style))
|
|
796
|
+
this._negative = true;
|
|
797
|
+
// a column renders no content, and generated content is all it would
|
|
798
|
+
// hold; in a column group it is not a column either (CSS 2.1 17.2.1)
|
|
799
|
+
if (style.display === 'table-column' ||
|
|
800
|
+
style.display === 'table-column-group' ||
|
|
801
|
+
elementStyle.display === 'table-column-group') {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
// a scope of its own, which what it counts is in where its element has
|
|
805
|
+
// the counter already (`CounterScopes`)
|
|
806
|
+
this._scopes.open();
|
|
807
|
+
this._counterChanges(style, null);
|
|
808
|
+
const box = new Box(boxKindFor(style.display), null, style);
|
|
809
|
+
box.pseudo = which;
|
|
810
|
+
into.append(box);
|
|
811
|
+
// a list item it generates has a marker as an element's has, of the
|
|
812
|
+
// `list-item` counter it has just counted (CSS 2.1 12.5): outside it,
|
|
813
|
+
// or at the start of its content
|
|
814
|
+
const written = style.display === 'list-item'
|
|
815
|
+
? markerFor(style, this._scopes.value('list-item'), this._options.cascade.counterStyles)
|
|
816
|
+
: null;
|
|
817
|
+
let marker = written?.text ?? '';
|
|
818
|
+
if (marker && style.listStylePosition !== 'inside') {
|
|
819
|
+
box.marker = { text: marker, layout: null, x: 0, y: 0, style: null };
|
|
820
|
+
if (written?.flush)
|
|
821
|
+
box.marker.flush = true;
|
|
822
|
+
marker = '';
|
|
823
|
+
}
|
|
824
|
+
if (style.position === 'absolute' || style.position === 'fixed')
|
|
825
|
+
box.outOfFlow = true;
|
|
826
|
+
else if (style.float !== 'none')
|
|
827
|
+
box.isFloat = true;
|
|
828
|
+
if (into.kind === 'inline' && (box.outOfFlow || box.isFloat))
|
|
829
|
+
this._nestedOutOfLine = true;
|
|
830
|
+
const pieces = this._generated(style.content, style, el);
|
|
831
|
+
if (marker)
|
|
832
|
+
pieces.unshift(written?.flush ? marker : `${marker} `);
|
|
833
|
+
this._scopes.close();
|
|
834
|
+
const flow = flowOf(style, box);
|
|
835
|
+
const around = this._ws;
|
|
836
|
+
const aroundWord = this._lettered;
|
|
837
|
+
if (flow === 'block')
|
|
838
|
+
this._endLine();
|
|
839
|
+
if (flow !== 'inline')
|
|
840
|
+
this._ws = 'start';
|
|
841
|
+
// the first letter can be generated, and is looked for here as in an
|
|
842
|
+
// element of the same display
|
|
843
|
+
const outerLetter = this._firstLetter;
|
|
844
|
+
const skipped = flow === 'out' || flow === 'atomic';
|
|
845
|
+
if (skipped)
|
|
846
|
+
this._firstLetter = null;
|
|
847
|
+
else if (flow === 'block' && outerLetter)
|
|
848
|
+
giveBack(outerLetter);
|
|
849
|
+
for (const piece of pieces) {
|
|
850
|
+
if (typeof piece === 'string')
|
|
851
|
+
this._textNode(piece, box, style, el);
|
|
852
|
+
else
|
|
853
|
+
this._contentImage(piece.url, box, style, el);
|
|
854
|
+
}
|
|
855
|
+
this._letterAfter(flow, skipped, outerLetter, null);
|
|
856
|
+
if (flow !== 'inline')
|
|
857
|
+
this._endLine();
|
|
858
|
+
this._ws = after(flow, this._ws, around);
|
|
859
|
+
this._lettered = letteredAfterFlow(flow, this._lettered, aroundWord);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* An `inside` list marker: an inline box at the start of the item, the
|
|
863
|
+
* marker and a space, which takes its room on the first line as a
|
|
864
|
+
* `::marker` does (CSS Lists 3, 3.1), where a marker set beside the line
|
|
865
|
+
* was drawn over its first letters — every `list-style-position: inside`
|
|
866
|
+
* list, and every `<summary>`. It inherits the item's text style and has
|
|
867
|
+
* none of its box.
|
|
868
|
+
*/
|
|
869
|
+
_insideMarker(text, itemStyle, markerStyle, into, el) {
|
|
870
|
+
const style = inherit(markerStyle ?? itemStyle, this._options.cascade.initial);
|
|
871
|
+
style.display = 'inline';
|
|
872
|
+
// a marker's direction is its own, whatever the text after it, unless
|
|
873
|
+
// its rules say otherwise (the HTML style sheet's `::marker`)
|
|
874
|
+
style.unicodeBidi = markerStyle?.unicodeBidi ?? 'isolate';
|
|
875
|
+
const box = new Box('inline', null, style);
|
|
876
|
+
box.pseudo = 'before';
|
|
877
|
+
into.append(box);
|
|
878
|
+
this._textNode(text, box, style, el);
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* `counter-reset`, then `counter-increment`, then `counter-set`, as CSS
|
|
882
|
+
* Lists 3 orders them (4.2). A list item counts `list-item` besides,
|
|
883
|
+
* unless its `counter-increment` names it: one up, or one down in a list
|
|
884
|
+
* that counts down. `place` is where an element is, for a reversed
|
|
885
|
+
* counter to be counted from; a pseudo-element has none.
|
|
886
|
+
*/
|
|
887
|
+
_counterChanges(style, place) {
|
|
888
|
+
const resets = style.counterReset;
|
|
889
|
+
const increments = style.counterIncrement;
|
|
890
|
+
const sets = style.counterSet;
|
|
891
|
+
const listItem = style.display === 'list-item';
|
|
892
|
+
if (!resets && !increments && !sets && !listItem)
|
|
893
|
+
return;
|
|
894
|
+
if (resets) {
|
|
895
|
+
for (let i = 0; i < resets.length; i += 1) {
|
|
896
|
+
const change = resets[i];
|
|
897
|
+
// a name reset twice is reset by the last
|
|
898
|
+
if (resets.some((c, j) => j > i && c.name === change.name))
|
|
899
|
+
continue;
|
|
900
|
+
const value = change.counted
|
|
901
|
+
? this._countedStart(change.name, style, place)
|
|
902
|
+
: change.value;
|
|
903
|
+
this._scopes.reset(change.name, value, !!change.reversed);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
let counted = false;
|
|
907
|
+
for (const { name, value } of increments ?? []) {
|
|
908
|
+
this._scopes.increment(name, value);
|
|
909
|
+
if (name === 'list-item')
|
|
910
|
+
counted = true;
|
|
911
|
+
}
|
|
912
|
+
if (listItem && !counted) {
|
|
913
|
+
this._scopes.increment('list-item', this._scopes.reversed('list-item') ? -1 : 1);
|
|
914
|
+
}
|
|
915
|
+
for (const { name, value } of sets ?? [])
|
|
916
|
+
this._scopes.set(name, value);
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Where a reversed counter written with no number starts (CSS Lists 3,
|
|
920
|
+
* 4.4.2): minus what the elements and pseudo-elements in its scope add to
|
|
921
|
+
* it, as far as the first that sets it, and what that sets it to, and as
|
|
922
|
+
* much again as the last of them that counted took away — 1 for a list
|
|
923
|
+
* item. `<ol reversed>`'s items count down to 1, and to an item's
|
|
924
|
+
* `value` before it. The scope is walked ahead of the builder, from the styles it will
|
|
925
|
+
* find shared when it gets there (`Cascade.sharedStyleFor`), and only for
|
|
926
|
+
* such a counter.
|
|
927
|
+
*/
|
|
928
|
+
_countedStart(name, style, place) {
|
|
929
|
+
const cascade = this._options.cascade;
|
|
930
|
+
/** What each element and pseudo-element in the scope does to the
|
|
931
|
+
* counter, in document order: what it adds, and what it sets it to. */
|
|
932
|
+
const steps = [];
|
|
933
|
+
const note = (st) => {
|
|
934
|
+
let by = 0;
|
|
935
|
+
let increments = false;
|
|
936
|
+
for (const change of st.counterIncrement ?? []) {
|
|
937
|
+
if (change.name !== name)
|
|
938
|
+
continue;
|
|
939
|
+
by += change.value;
|
|
940
|
+
increments = true;
|
|
941
|
+
}
|
|
942
|
+
if (!increments && name === 'list-item' && st.display === 'list-item') {
|
|
943
|
+
by = -1;
|
|
944
|
+
increments = true;
|
|
945
|
+
}
|
|
946
|
+
let set = null;
|
|
947
|
+
for (const change of st.counterSet ?? []) {
|
|
948
|
+
if (change.name === name)
|
|
949
|
+
set = change.value;
|
|
950
|
+
}
|
|
951
|
+
if (increments || set !== null)
|
|
952
|
+
steps.push({ by, set });
|
|
953
|
+
};
|
|
954
|
+
const resets = (st) => st.counterReset?.some((change) => change.name === name) ?? false;
|
|
955
|
+
const pseudo = (el, which, st) => {
|
|
956
|
+
const own = cascade.pseudoStyleFor(el, which, st);
|
|
957
|
+
if (own && own.display !== 'none' && !resets(own))
|
|
958
|
+
note(own);
|
|
959
|
+
};
|
|
960
|
+
/** The elements from `from` on of a parent, and what is in them: a
|
|
961
|
+
* reset of the counter in one is a counter of its own, and a later
|
|
962
|
+
* sibling's reset at the scope's own level ends it (`stopAtReset`). */
|
|
963
|
+
const walk = (children, from, parentStyle, parentKey, inFlex, stopAtReset) => {
|
|
964
|
+
for (let i = from; i < children.length; i += 1) {
|
|
965
|
+
const child = children[i];
|
|
966
|
+
if (!isElement(child))
|
|
967
|
+
continue;
|
|
968
|
+
const tag = tagOf(child);
|
|
969
|
+
if (NON_RENDERED.has(tag) || inImpliedHead(child, tag))
|
|
970
|
+
continue;
|
|
971
|
+
const shared = cascade.sharedStyleFor(child, parentStyle, parentKey, inFlex);
|
|
972
|
+
const st = shared.style;
|
|
973
|
+
if (st.display === 'none')
|
|
974
|
+
continue;
|
|
975
|
+
if (resets(st)) {
|
|
976
|
+
if (stopAtReset)
|
|
977
|
+
return;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
note(st);
|
|
981
|
+
inside(child, st, shared.key);
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
const inside = (el, st, key) => {
|
|
985
|
+
pseudo(el, 'before', st);
|
|
986
|
+
walk(childrenOf(el), 0, st, key, st.display === 'flex' || st.display === 'inline-flex', false);
|
|
987
|
+
pseudo(el, 'after', st);
|
|
988
|
+
};
|
|
989
|
+
// the element that resets it counts in it too, after the reset
|
|
990
|
+
note(style);
|
|
991
|
+
if (place) {
|
|
992
|
+
const siblings = this._scopes.reachesSiblings(name);
|
|
993
|
+
inside(place.node, place.style, place.key);
|
|
994
|
+
const parent = place.parent;
|
|
995
|
+
const node = place.node;
|
|
996
|
+
if (siblings && parent && node.parent) {
|
|
997
|
+
const children = childrenOf(node.parent);
|
|
998
|
+
walk(children, children.indexOf(place.node) + 1, parent.style, parent.key, parent.inFlex, true);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
let num = 0;
|
|
1002
|
+
let last = 0;
|
|
1003
|
+
for (const { by, set } of steps) {
|
|
1004
|
+
if (by !== 0)
|
|
1005
|
+
last = -by;
|
|
1006
|
+
if (set !== null) {
|
|
1007
|
+
num += set;
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
num -= by;
|
|
1011
|
+
}
|
|
1012
|
+
return num + last;
|
|
1013
|
+
}
|
|
1014
|
+
/** A counter's value in a style the document can name, as `counter()`
|
|
1015
|
+
* writes it; `none` writes nothing. */
|
|
1016
|
+
_counterText(n, name, style) {
|
|
1017
|
+
if (name === 'none')
|
|
1018
|
+
return '';
|
|
1019
|
+
return this._options.cascade.counterStyles.text(n, name, style.direction === 'rtl');
|
|
1020
|
+
}
|
|
1021
|
+
/** What `content` comes to here, in document order: its text, broken
|
|
1022
|
+
* where it names an image. The quotes it opens and closes count for
|
|
1023
|
+
* everything after it. */
|
|
1024
|
+
_generated(items, style, el) {
|
|
1025
|
+
const pieces = [];
|
|
1026
|
+
let text = '';
|
|
1027
|
+
for (const item of items) {
|
|
1028
|
+
switch (item.kind) {
|
|
1029
|
+
case 'string':
|
|
1030
|
+
text += item.text;
|
|
1031
|
+
break;
|
|
1032
|
+
case 'url':
|
|
1033
|
+
if (text)
|
|
1034
|
+
pieces.push(text);
|
|
1035
|
+
text = '';
|
|
1036
|
+
pieces.push({ url: item.url });
|
|
1037
|
+
break;
|
|
1038
|
+
case 'attr':
|
|
1039
|
+
text += attr(el, item.name) ?? '';
|
|
1040
|
+
break;
|
|
1041
|
+
case 'counter':
|
|
1042
|
+
text += this._counterText(this._scopes.value(item.name), item.style, style);
|
|
1043
|
+
break;
|
|
1044
|
+
case 'counters':
|
|
1045
|
+
text += this._scopes
|
|
1046
|
+
.values(item.name)
|
|
1047
|
+
.map((v) => this._counterText(v, item.style, style))
|
|
1048
|
+
.join(item.separator);
|
|
1049
|
+
break;
|
|
1050
|
+
case 'open-quote':
|
|
1051
|
+
text += quoteAt(style.quotes, this._quoteDepth, 0);
|
|
1052
|
+
this._quoteDepth += 1;
|
|
1053
|
+
break;
|
|
1054
|
+
case 'close-quote':
|
|
1055
|
+
// a close with nothing open writes nothing and closes nothing
|
|
1056
|
+
if (this._quoteDepth > 0) {
|
|
1057
|
+
this._quoteDepth -= 1;
|
|
1058
|
+
text += quoteAt(style.quotes, this._quoteDepth, 1);
|
|
1059
|
+
}
|
|
1060
|
+
break;
|
|
1061
|
+
case 'no-open-quote':
|
|
1062
|
+
this._quoteDepth += 1;
|
|
1063
|
+
break;
|
|
1064
|
+
case 'no-close-quote':
|
|
1065
|
+
if (this._quoteDepth > 0)
|
|
1066
|
+
this._quoteDepth -= 1;
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
if (text)
|
|
1071
|
+
pieces.push(text);
|
|
1072
|
+
return pieces;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* An image generated content names: an inline replaced box in the
|
|
1076
|
+
* pseudo-element, of the style it inherits and no other (CSS 2.1 12.2),
|
|
1077
|
+
* the image's size once the host has it and none before. An image that
|
|
1078
|
+
* never arrives takes no room, as no image does.
|
|
1079
|
+
*/
|
|
1080
|
+
_contentImage(url, into, style, owner) {
|
|
1081
|
+
const box = new Box('replaced', null, {
|
|
1082
|
+
...inherit(style, this._options.cascade.initial),
|
|
1083
|
+
display: 'inline',
|
|
1084
|
+
});
|
|
1085
|
+
box.replaced = 'image';
|
|
1086
|
+
into.append(box);
|
|
1087
|
+
CONTENT_IMAGES.set(box, url);
|
|
1088
|
+
const size = this._options.urlSize?.(url) ?? null;
|
|
1089
|
+
this._contentImages.push({ url, element: owner, sized: !!size });
|
|
1090
|
+
if (size)
|
|
1091
|
+
setIntrinsics(box, size, this._options.scale ?? 1);
|
|
1092
|
+
else
|
|
1093
|
+
box.intrinsic = { width: 0, height: 0, missing: 0, ratio: 0 };
|
|
1094
|
+
this._ws = after('atomic', this._ws, this._ws);
|
|
1095
|
+
this._lettered = false;
|
|
1096
|
+
this._abandonLetter();
|
|
1097
|
+
}
|
|
288
1098
|
/** A text node, whitespace-processed per the inherited `white-space`. */
|
|
289
1099
|
_textNode(data, into, style, owner) {
|
|
1100
|
+
// text set at no size draws nothing and takes no room, so it needs no
|
|
1101
|
+
// box — and it is no part of the white space around it either
|
|
1102
|
+
if (!(style.fontSize > 0))
|
|
1103
|
+
return;
|
|
1104
|
+
// a word does not run on over the start of a line
|
|
1105
|
+
if (this._ws === 'start')
|
|
1106
|
+
this._lettered = false;
|
|
290
1107
|
const ws = style.whiteSpace;
|
|
291
1108
|
let text;
|
|
292
1109
|
if (ws === 'pre' || ws === 'pre-wrap') {
|
|
293
1110
|
text = data;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
1111
|
+
if (!text)
|
|
1112
|
+
return;
|
|
1113
|
+
// preserved spaces do not collapse with the ones after them
|
|
1114
|
+
this._ws = text.endsWith('\n') ? 'start' : 'content';
|
|
297
1115
|
}
|
|
298
1116
|
else {
|
|
299
|
-
text =
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
1117
|
+
text =
|
|
1118
|
+
ws === 'pre-line'
|
|
1119
|
+
? data.replace(/[ \t]+/g, ' ').replace(/ *\n */g, '\n')
|
|
1120
|
+
: COLLAPSIBLE.test(data)
|
|
1121
|
+
? data.replace(/[\t\n\r\f ]+/g, ' ')
|
|
1122
|
+
: data;
|
|
1123
|
+
// A space at the start of a line goes, and so does one after another
|
|
1124
|
+
// space, across element boundaries (CSS 2.1 16.6.1): `<p>\n Hi` has
|
|
1125
|
+
// no space before the H, and `Hi <b> there</b>` has one between.
|
|
1126
|
+
if (text.charCodeAt(0) === 32 && this._ws !== 'content') {
|
|
1127
|
+
text = text.slice(1);
|
|
1128
|
+
}
|
|
1129
|
+
if (!text)
|
|
304
1130
|
return;
|
|
1131
|
+
const last = text.charCodeAt(text.length - 1);
|
|
1132
|
+
this._ws = last === 32 ? 'space' : last === 10 ? 'start' : 'content';
|
|
305
1133
|
}
|
|
306
|
-
|
|
1134
|
+
text = transformText(text, style.textTransform, this._lettered);
|
|
1135
|
+
this._lettered = letteredAfter(text, this._lettered);
|
|
1136
|
+
const search = this._firstLetter;
|
|
1137
|
+
const letter = search ? FIRST_LETTER.exec(text) : null;
|
|
1138
|
+
if (!search || !letter) {
|
|
1139
|
+
const punctuation = search ? PUNCTUATION_ONLY.exec(text) : null;
|
|
1140
|
+
if (!search || !punctuation) {
|
|
1141
|
+
this._textBox(text, into, style, owner);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
// punctuation the letter comes after, in a text of its own —
|
|
1145
|
+
// `<q>`'s open quote — takes the letter's style, and gives it back
|
|
1146
|
+
// if no letter follows on the line
|
|
1147
|
+
const start = punctuation[1].length;
|
|
1148
|
+
if (start > 0)
|
|
1149
|
+
this._textBox(text.slice(0, start), into, style, owner);
|
|
1150
|
+
search.punctuation.push(this._letterBox(search, text.slice(start), into, style, owner));
|
|
307
1151
|
return;
|
|
308
|
-
|
|
1152
|
+
}
|
|
1153
|
+
// The first letter, with the punctuation around it, in a box of its
|
|
1154
|
+
// own inside the box it was found in, and so inheriting from that
|
|
1155
|
+
// (CSS 2.1 5.12.2): `<p><b>T</b>his` has a bold first letter.
|
|
1156
|
+
this._firstLetter = null;
|
|
1157
|
+
const start = letter[1].length;
|
|
1158
|
+
const end = letter[0].length;
|
|
1159
|
+
if (start > 0)
|
|
1160
|
+
this._textBox(text.slice(0, start), into, style, owner);
|
|
1161
|
+
this._letterBox(search, text.slice(start, end), into, style, owner);
|
|
1162
|
+
if (end < text.length) {
|
|
1163
|
+
this._textBox(text.slice(end), into, style, owner);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
/** A box of the first letter's style around `text`, in `into`. */
|
|
1167
|
+
_letterBox(search, text, into, style, owner) {
|
|
1168
|
+
const cascade = this._options.cascade;
|
|
1169
|
+
const letterStyle = cascade.firstLetterStyle(search.rules, style);
|
|
1170
|
+
const box = new Box(boxKindFor(letterStyle.display), null, letterStyle);
|
|
1171
|
+
box.pseudo = 'first-letter';
|
|
1172
|
+
if (letterStyle.float !== 'none') {
|
|
1173
|
+
box.isFloat = true;
|
|
1174
|
+
if (into.kind === 'inline')
|
|
1175
|
+
this._nestedOutOfLine = true;
|
|
1176
|
+
}
|
|
1177
|
+
into.append(box);
|
|
1178
|
+
if (letterStyle.textTransform !== style.textTransform) {
|
|
1179
|
+
text = transformText(text, letterStyle.textTransform);
|
|
1180
|
+
}
|
|
1181
|
+
return { box, text: this._textBox(text, box, letterStyle, owner), style };
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Where the search for a first letter stands after a box. A box that was
|
|
1185
|
+
* no part of the line — a float, a positioned box — leaves it where it
|
|
1186
|
+
* was, and one that was something other than a letter on it ends it. A
|
|
1187
|
+
* block that looked for a letter of its own and found its first line
|
|
1188
|
+
* ended its parent's too, and one that found no line leaves the parent
|
|
1189
|
+
* looking. A block ends the line it is on.
|
|
1190
|
+
*/
|
|
1191
|
+
_letterAfter(flow, skipped, outer, own) {
|
|
1192
|
+
const lined = own !== null && this._firstLetter !== own;
|
|
1193
|
+
if (own && !lined)
|
|
1194
|
+
this._abandonLetter();
|
|
1195
|
+
if (skipped || own)
|
|
1196
|
+
this._firstLetter = skipped || !lined ? outer : null;
|
|
1197
|
+
if (lined && !skipped && outer)
|
|
1198
|
+
giveBack(outer);
|
|
1199
|
+
if (flow === 'atomic')
|
|
1200
|
+
this._abandonLetter();
|
|
1201
|
+
else if (flow === 'block' && this._firstLetter)
|
|
1202
|
+
giveBack(this._firstLetter);
|
|
1203
|
+
}
|
|
1204
|
+
/** The first line ended, or began with something other than a letter:
|
|
1205
|
+
* there is no first letter, and punctuation that took its style gives
|
|
1206
|
+
* it back. */
|
|
1207
|
+
_abandonLetter() {
|
|
1208
|
+
const search = this._firstLetter;
|
|
1209
|
+
this._firstLetter = null;
|
|
1210
|
+
if (search)
|
|
1211
|
+
giveBack(search);
|
|
1212
|
+
}
|
|
1213
|
+
_textBox(text, into, style, owner) {
|
|
309
1214
|
// The owning element rides on the text box, and from there onto the
|
|
310
1215
|
// `TextRun`: hit testing inside a paragraph has no rectangle to test —
|
|
311
1216
|
// an inline box is the runs on its lines — so the run is what has to
|
|
@@ -314,19 +1219,198 @@ class Builder {
|
|
|
314
1219
|
box.text = text;
|
|
315
1220
|
into.append(box);
|
|
316
1221
|
this._push(text, box);
|
|
1222
|
+
return box;
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* A line ends here — a block's inline content is over, or a `<br>` or a
|
|
1226
|
+
* block interrupts it — and a collapsible space it ends on goes (CSS 2.1
|
|
1227
|
+
* 16.6.1). Content standing on a space means that space was the last
|
|
1228
|
+
* thing pushed, so taking it back is a character off the last chunk; a
|
|
1229
|
+
* text box it empties goes with it.
|
|
1230
|
+
*/
|
|
1231
|
+
_endLine() {
|
|
1232
|
+
if (this._ws !== 'space')
|
|
1233
|
+
return;
|
|
1234
|
+
this._ws = 'start';
|
|
1235
|
+
const box = this._textBoxes[this._textBoxes.length - 1];
|
|
1236
|
+
if (!box || box.textEnd !== this._length || !box.text.endsWith(' ')) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
box.text = box.text.slice(0, -1);
|
|
1240
|
+
box.textEnd -= 1;
|
|
1241
|
+
this._length -= 1;
|
|
1242
|
+
const last = this._chunks.length - 1;
|
|
1243
|
+
this._chunks[last] = this._chunks[last].slice(0, -1);
|
|
1244
|
+
if (!box.text) {
|
|
1245
|
+
this._textBoxes.pop();
|
|
1246
|
+
const siblings = box.parent?.children;
|
|
1247
|
+
const at = siblings ? siblings.lastIndexOf(box) : -1;
|
|
1248
|
+
if (at >= 0)
|
|
1249
|
+
siblings.splice(at, 1);
|
|
1250
|
+
}
|
|
317
1251
|
}
|
|
318
1252
|
/** Give a box its slice of the document text index. */
|
|
319
1253
|
_push(text, box) {
|
|
320
|
-
box.textStart = this.
|
|
321
|
-
this.
|
|
322
|
-
|
|
323
|
-
|
|
1254
|
+
box.textStart = this._length;
|
|
1255
|
+
this._chunks.push(text);
|
|
1256
|
+
this._length += text.length;
|
|
1257
|
+
box.textEnd = this._length;
|
|
1258
|
+
if (box.kind === 'text') {
|
|
324
1259
|
this._textBoxes.push(box);
|
|
1260
|
+
if (box.style !== this._lastTextStyle) {
|
|
1261
|
+
this._lastTextStyle = box.style;
|
|
1262
|
+
this._textStyles.add(box.style);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* The CSS counters in scope as the builder walks the document (CSS Lists 3,
|
|
1269
|
+
* 4.5). Every element and pseudo-element opens a level, which closes after
|
|
1270
|
+
* what it holds. A `counter-reset` makes an instance that reaches the
|
|
1271
|
+
* element's descendants and — where its parent has no counter of the name
|
|
1272
|
+
* — its later siblings too, so that instance belongs to the level the
|
|
1273
|
+
* element is on, its parent's, and goes when that closes; a later sibling's
|
|
1274
|
+
* reset takes its place. Where its parent has one, the new instance nests
|
|
1275
|
+
* in it and reaches the element's descendants alone, on the element's own
|
|
1276
|
+
* level: a list in a list item, a counter reset in a `::before`. `counter()`
|
|
1277
|
+
* reads the innermost instance and `counters()` all of them, outermost
|
|
1278
|
+
* first. A counter used where none is in scope is reset to 0 there, as
|
|
1279
|
+
* though the element had asked.
|
|
1280
|
+
*/
|
|
1281
|
+
class CounterScopes {
|
|
1282
|
+
/** Per name, its instances, outermost first: the level each goes with,
|
|
1283
|
+
* whether it is the own one of the element that level is, and whether it
|
|
1284
|
+
* counts down. */
|
|
1285
|
+
_instances = new Map();
|
|
1286
|
+
/** The names each open level made an instance of, so closing it drops
|
|
1287
|
+
* exactly those: null for a level that made none, which is nearly every
|
|
1288
|
+
* one, so that opening an element allocates nothing. */
|
|
1289
|
+
_made = [null];
|
|
1290
|
+
/** The levels of the open elements with style containment, innermost
|
|
1291
|
+
* last: below one, a counter made further out is not counted on. */
|
|
1292
|
+
_contained = [];
|
|
1293
|
+
open() {
|
|
1294
|
+
this._made.push(null);
|
|
1295
|
+
}
|
|
1296
|
+
close() {
|
|
1297
|
+
const level = this._made.length - 1;
|
|
1298
|
+
const made = this._made.pop();
|
|
1299
|
+
if (made) {
|
|
1300
|
+
for (const name of made) {
|
|
1301
|
+
const stack = this._instances.get(name);
|
|
1302
|
+
if (stack && stack[stack.length - 1]?.level === level)
|
|
1303
|
+
stack.pop();
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
const contained = this._contained;
|
|
1307
|
+
if (contained[contained.length - 1] === level)
|
|
1308
|
+
contained.pop();
|
|
1309
|
+
}
|
|
1310
|
+
/** The element whose level is open has style containment. */
|
|
1311
|
+
contain() {
|
|
1312
|
+
this._contained.push(this._made.length - 1);
|
|
1313
|
+
}
|
|
1314
|
+
/** Whether the parent of the element whose level is open has a counter
|
|
1315
|
+
* of this name: its own, or one from further out — not one a sibling
|
|
1316
|
+
* before the element made. */
|
|
1317
|
+
_parentHas(name) {
|
|
1318
|
+
const stack = this._instances.get(name);
|
|
1319
|
+
if (!stack?.length)
|
|
1320
|
+
return false;
|
|
1321
|
+
const parent = this._made.length - 2;
|
|
1322
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
1323
|
+
const instance = stack[i];
|
|
1324
|
+
if (instance.level < parent)
|
|
1325
|
+
return true;
|
|
1326
|
+
if (instance.level === parent && instance.own)
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
return false;
|
|
1330
|
+
}
|
|
1331
|
+
/** Whether a reset of `name` here reaches the element's later siblings,
|
|
1332
|
+
* or its descendants alone. */
|
|
1333
|
+
reachesSiblings(name) {
|
|
1334
|
+
return this._made.length < 2 || !this._parentHas(name);
|
|
1335
|
+
}
|
|
1336
|
+
reset(name, value, reversed) {
|
|
1337
|
+
const own = !this.reachesSiblings(name);
|
|
1338
|
+
const level = own ? this._made.length - 1 : this._made.length - 2;
|
|
1339
|
+
let stack = this._instances.get(name);
|
|
1340
|
+
if (!stack) {
|
|
1341
|
+
stack = [];
|
|
1342
|
+
this._instances.set(name, stack);
|
|
1343
|
+
}
|
|
1344
|
+
const top = stack[stack.length - 1];
|
|
1345
|
+
if (top?.level === level && top.own === own) {
|
|
1346
|
+
top.value = value;
|
|
1347
|
+
top.reversed = reversed;
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
stack.push({ level, own, value, reversed });
|
|
1351
|
+
(this._made[level] ??= []).push(name);
|
|
1352
|
+
}
|
|
1353
|
+
/** The innermost instance, made here at 0 where there is none. */
|
|
1354
|
+
_innermost(name) {
|
|
1355
|
+
let stack = this._instances.get(name);
|
|
1356
|
+
if (!stack?.length) {
|
|
1357
|
+
this.reset(name, 0, false);
|
|
1358
|
+
stack = this._instances.get(name);
|
|
1359
|
+
}
|
|
1360
|
+
return stack[stack.length - 1];
|
|
1361
|
+
}
|
|
1362
|
+
increment(name, by) {
|
|
1363
|
+
const instance = this._counted(name);
|
|
1364
|
+
instance.value = clampCounter(instance.value + by);
|
|
1365
|
+
}
|
|
1366
|
+
set(name, value) {
|
|
1367
|
+
this._counted(name).value = value;
|
|
1368
|
+
}
|
|
1369
|
+
/** The instance an increment or a set counts on: the innermost, unless
|
|
1370
|
+
* it was made outside an element with style containment that this is
|
|
1371
|
+
* inside, where a new one is made instead, as though this reset it for
|
|
1372
|
+
* itself and its later siblings — which one outside the element's parent
|
|
1373
|
+
* then does not see (CSS Containment 2, 3.4). */
|
|
1374
|
+
_counted(name) {
|
|
1375
|
+
const instance = this._innermost(name);
|
|
1376
|
+
const bound = this._contained[this._contained.length - 1];
|
|
1377
|
+
const level = this._made.length - 1;
|
|
1378
|
+
if (bound === undefined || level <= bound || instance.level >= bound) {
|
|
1379
|
+
return instance;
|
|
1380
|
+
}
|
|
1381
|
+
const parent = level - 1;
|
|
1382
|
+
const fresh = { level: parent, own: false, value: 0, reversed: false };
|
|
1383
|
+
this._instances.get(name).push(fresh);
|
|
1384
|
+
(this._made[parent] ??= []).push(name);
|
|
1385
|
+
return fresh;
|
|
1386
|
+
}
|
|
1387
|
+
/** Whether the innermost instance counts down: a list item takes one
|
|
1388
|
+
* from it rather than adding one. */
|
|
1389
|
+
reversed(name) {
|
|
1390
|
+
return this._innermost(name).reversed;
|
|
1391
|
+
}
|
|
1392
|
+
value(name) {
|
|
1393
|
+
return this._innermost(name).value;
|
|
325
1394
|
}
|
|
1395
|
+
values(name) {
|
|
1396
|
+
this._innermost(name);
|
|
1397
|
+
return this._instances.get(name).map((instance) => instance.value);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
/** A counter is a 32-bit integer in a browser, and saturates there. */
|
|
1401
|
+
function clampCounter(value) {
|
|
1402
|
+
return Math.max(-2147483648, Math.min(2147483647, value));
|
|
326
1403
|
}
|
|
327
1404
|
/** Whether the parsed document has a `<body>`. htmlparser2 does not
|
|
328
1405
|
* synthesise one — it parses what it was given — so a fragment has none,
|
|
329
1406
|
* and the root box stands in for it. */
|
|
1407
|
+
function hasHtml(root) {
|
|
1408
|
+
for (const child of childrenOf(root)) {
|
|
1409
|
+
if (isElement(child) && tagOf(child) === 'html')
|
|
1410
|
+
return true;
|
|
1411
|
+
}
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
330
1414
|
function hasBody(root) {
|
|
331
1415
|
for (const child of childrenOf(root)) {
|
|
332
1416
|
if (!isElement(child))
|
|
@@ -339,6 +1423,13 @@ function hasBody(root) {
|
|
|
339
1423
|
}
|
|
340
1424
|
return false;
|
|
341
1425
|
}
|
|
1426
|
+
/** A canvas's bitmap dimension: a non-negative integer, as HTML parses
|
|
1427
|
+
* one, or its default where the attribute is missing or no number. */
|
|
1428
|
+
function canvasSize(el, name, fallback) {
|
|
1429
|
+
const raw = attr(el, name);
|
|
1430
|
+
const n = raw == null ? NaN : parseInt(raw.trim(), 10);
|
|
1431
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
1432
|
+
}
|
|
342
1433
|
function numberAttr(el, name) {
|
|
343
1434
|
const raw = attr(el, name);
|
|
344
1435
|
if (!raw)
|
|
@@ -346,28 +1437,242 @@ function numberAttr(el, name) {
|
|
|
346
1437
|
const n = parseFloat(raw);
|
|
347
1438
|
return Number.isFinite(n) ? n : null;
|
|
348
1439
|
}
|
|
349
|
-
function
|
|
350
|
-
for (let i = box.children.length - 1; i >= 0; i -= 1) {
|
|
351
|
-
const kind = box.children[i].kind;
|
|
352
|
-
if (kind === 'text' || kind === 'inline' || kind === 'replaced')
|
|
353
|
-
return true;
|
|
354
|
-
if (kind === 'block' || kind === 'flex' || kind === 'table')
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
return false;
|
|
358
|
-
}
|
|
359
|
-
function transformText(text, transform) {
|
|
1440
|
+
function transformText(text, transform, lettered = false) {
|
|
360
1441
|
switch (transform) {
|
|
361
1442
|
case 'uppercase':
|
|
362
1443
|
return text.toUpperCase();
|
|
363
1444
|
case 'lowercase':
|
|
364
1445
|
return text.toLowerCase();
|
|
365
1446
|
case 'capitalize':
|
|
366
|
-
return text
|
|
1447
|
+
return capitalize(text, lettered);
|
|
367
1448
|
default:
|
|
368
1449
|
return text;
|
|
369
1450
|
}
|
|
370
1451
|
}
|
|
1452
|
+
/**
|
|
1453
|
+
* `text` transformed as `transform` says, where it stands for text laid out
|
|
1454
|
+
* already — a first line's, whose `::first-line` transforms it (CSS 2.1
|
|
1455
|
+
* 5.12.1): a character at a time where the transform would change its
|
|
1456
|
+
* length, which moves every offset after it — `ß` is `SS` in capitals —
|
|
1457
|
+
* and that character kept as it is.
|
|
1458
|
+
*/
|
|
1459
|
+
export function transformInPlace(text, transform, lettered = false) {
|
|
1460
|
+
const whole = transformText(text, transform, lettered);
|
|
1461
|
+
if (whole.length === text.length)
|
|
1462
|
+
return whole;
|
|
1463
|
+
let out = '';
|
|
1464
|
+
let state = lettered;
|
|
1465
|
+
for (const ch of text) {
|
|
1466
|
+
const one = transformText(ch, transform, state);
|
|
1467
|
+
out += one.length === ch.length ? one : ch;
|
|
1468
|
+
state = letteredAfter(ch, state);
|
|
1469
|
+
}
|
|
1470
|
+
return out;
|
|
1471
|
+
}
|
|
1472
|
+
/** A letter or a number: what a word's first typographic letter unit is
|
|
1473
|
+
* (CSS Text 3, 2.1). */
|
|
1474
|
+
const LETTER_UNIT = /[\p{L}\p{N}]/u;
|
|
1475
|
+
/**
|
|
1476
|
+
* What does not end a word (UAX #29): a combining mark, and the
|
|
1477
|
+
* punctuation that joins letters into one — `x.y`, `don't`, `a:b`. Any
|
|
1478
|
+
* other character that is not a letter or a number is between words: a
|
|
1479
|
+
* space, a no-break space, a hyphen, a bracket.
|
|
1480
|
+
*/
|
|
1481
|
+
const IN_WORD = /[\p{M}'.:\u00b7\u0387\u05f4\u2018\u2019\u2024\u2027\ufe13\ufe52\ufe55\uff07\uff0e\uff1a]/u;
|
|
1482
|
+
/**
|
|
1483
|
+
* `text-transform: capitalize` (CSS Text 3, 2.1): the first letter or
|
|
1484
|
+
* number of each word in upper case, and nothing else. Punctuation a word
|
|
1485
|
+
* starts with is not its first letter — `(p.p.)` is `(P.p.)` — and a word
|
|
1486
|
+
* the text continues, `lettered`, has had its own: `<b>fo</b>o` is `Foo`.
|
|
1487
|
+
*/
|
|
1488
|
+
function capitalize(text, lettered) {
|
|
1489
|
+
let out = '';
|
|
1490
|
+
let from = 0;
|
|
1491
|
+
let at = 0;
|
|
1492
|
+
for (const ch of text) {
|
|
1493
|
+
if (LETTER_UNIT.test(ch)) {
|
|
1494
|
+
if (!lettered) {
|
|
1495
|
+
const title = titleCase(ch);
|
|
1496
|
+
if (title !== ch) {
|
|
1497
|
+
out += text.slice(from, at) + title;
|
|
1498
|
+
from = at + ch.length;
|
|
1499
|
+
}
|
|
1500
|
+
lettered = true;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
else if (!IN_WORD.test(ch)) {
|
|
1504
|
+
lettered = false;
|
|
1505
|
+
}
|
|
1506
|
+
at += ch.length;
|
|
1507
|
+
}
|
|
1508
|
+
return from ? out + text.slice(from) : text;
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* A letter in title case (Unicode's SpecialCasing), which is its upper case
|
|
1512
|
+
* but for a letter that is two: a digraph is capitalized as its first
|
|
1513
|
+
* letter alone — `dž` as `Dž`, where upper case is `DŽ` — and a Greek vowel
|
|
1514
|
+
* with a subscript iota keeps the iota subscript, where upper case writes
|
|
1515
|
+
* it out. A letter whose upper case is two letters, as `ß` is `SS`, is the
|
|
1516
|
+
* first of them: `Ss`.
|
|
1517
|
+
*/
|
|
1518
|
+
function titleCase(ch) {
|
|
1519
|
+
const cp = ch.codePointAt(0);
|
|
1520
|
+
if (cp >= 0x1c4 && cp <= 0x1cc) {
|
|
1521
|
+
return String.fromCharCode(0x1c5 + 3 * Math.floor((cp - 0x1c4) / 3));
|
|
1522
|
+
}
|
|
1523
|
+
if (cp >= 0x1f1 && cp <= 0x1f3)
|
|
1524
|
+
return '\u01f2';
|
|
1525
|
+
if (cp >= 0x1f80 && cp <= 0x1faf)
|
|
1526
|
+
return String.fromCharCode(cp | 0x08);
|
|
1527
|
+
if (cp === 0x1fb3 || cp === 0x1fc3 || cp === 0x1ff3) {
|
|
1528
|
+
return String.fromCharCode(cp + 9);
|
|
1529
|
+
}
|
|
1530
|
+
if (cp === 0x1fbc || cp === 0x1fcc || cp === 0x1ffc)
|
|
1531
|
+
return ch;
|
|
1532
|
+
const upper = ch.toUpperCase();
|
|
1533
|
+
if (upper.length <= ch.length)
|
|
1534
|
+
return upper;
|
|
1535
|
+
const first = String.fromCodePoint(upper.codePointAt(0));
|
|
1536
|
+
return first + upper.slice(first.length).toLowerCase();
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Whether a word is under way after `text`, as `capitalize` reads words: its
|
|
1540
|
+
* last letter, number or character between words decides, and a text of
|
|
1541
|
+
* nothing but joining punctuation leaves it as it was. Asked of every text,
|
|
1542
|
+
* from the end, where it is almost always decided by the last character.
|
|
1543
|
+
*/
|
|
1544
|
+
export function letteredAfter(text, lettered) {
|
|
1545
|
+
for (let i = text.length - 1; i >= 0; i -= 1) {
|
|
1546
|
+
const c = text.charCodeAt(i);
|
|
1547
|
+
if (c < 0x80) {
|
|
1548
|
+
if ((c >= 0x61 && c <= 0x7a) ||
|
|
1549
|
+
(c >= 0x41 && c <= 0x5a) ||
|
|
1550
|
+
(c >= 0x30 && c <= 0x39)) {
|
|
1551
|
+
return true;
|
|
1552
|
+
}
|
|
1553
|
+
if (c === 0x27 || c === 0x2e || c === 0x3a)
|
|
1554
|
+
continue;
|
|
1555
|
+
return false;
|
|
1556
|
+
}
|
|
1557
|
+
// a low surrogate is read with the high one before it
|
|
1558
|
+
const start = c >= 0xdc00 && c <= 0xdfff && i > 0 && isHighSurrogate(text, i - 1)
|
|
1559
|
+
? i - 1
|
|
1560
|
+
: i;
|
|
1561
|
+
const ch = text.slice(start, i + 1);
|
|
1562
|
+
if (LETTER_UNIT.test(ch))
|
|
1563
|
+
return true;
|
|
1564
|
+
if (!IN_WORD.test(ch))
|
|
1565
|
+
return false;
|
|
1566
|
+
i = start;
|
|
1567
|
+
}
|
|
1568
|
+
return lettered;
|
|
1569
|
+
}
|
|
1570
|
+
function isHighSurrogate(text, i) {
|
|
1571
|
+
const c = text.charCodeAt(i);
|
|
1572
|
+
return c >= 0xd800 && c <= 0xdbff;
|
|
1573
|
+
}
|
|
1574
|
+
/** Whether a word is under way after a box, as `after` says where white
|
|
1575
|
+
* space stands: an inline box leaves it as its content did, an atomic
|
|
1576
|
+
* inline and a block end it, and a float or a positioned box leaves the
|
|
1577
|
+
* word around it as it was. */
|
|
1578
|
+
function letteredAfterFlow(flow, inside, before) {
|
|
1579
|
+
if (flow === 'inline')
|
|
1580
|
+
return inside;
|
|
1581
|
+
if (flow === 'out')
|
|
1582
|
+
return before;
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* A text's first letter, with the punctuation before and after it that CSS
|
|
1587
|
+
* 2.1 5.12.2 counts in — the Ps, Pe, Pi, Pf and Po classes — and the white
|
|
1588
|
+
* space before it in the first group. No match when the text ends, or
|
|
1589
|
+
* reaches a space, before any letter: its first letter is in a later text,
|
|
1590
|
+
* as browsers read it.
|
|
1591
|
+
*/
|
|
1592
|
+
const FIRST_LETTER = /^([ \t\n\r\f\u00a0]*)(?:[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]\p{M}*)*[^ \t\n\r\f\u00a0\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]\p{M}*(?:[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]\p{M}*)*/u;
|
|
1593
|
+
/** A text that is punctuation and nothing else, but for the white space
|
|
1594
|
+
* before it in the first group. */
|
|
1595
|
+
const PUNCTUATION_ONLY = /^([ \t\n\r\f\u00a0]*)(?:[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]\p{M}*)+$/u;
|
|
1596
|
+
/** Put punctuation a search styled back in its own box and style: the
|
|
1597
|
+
* letter it was waiting for is not on its line. */
|
|
1598
|
+
function giveBack(search) {
|
|
1599
|
+
for (const { box, text, style } of search.punctuation) {
|
|
1600
|
+
const parent = box.parent;
|
|
1601
|
+
if (!parent)
|
|
1602
|
+
continue;
|
|
1603
|
+
const at = parent.children.indexOf(box);
|
|
1604
|
+
if (at < 0)
|
|
1605
|
+
continue;
|
|
1606
|
+
parent.children[at] = text;
|
|
1607
|
+
text.parent = parent;
|
|
1608
|
+
text.style = style;
|
|
1609
|
+
}
|
|
1610
|
+
search.punctuation.length = 0;
|
|
1611
|
+
}
|
|
1612
|
+
/** Whether a box of this display is a block container, which is what can
|
|
1613
|
+
* have a first letter. */
|
|
1614
|
+
function hasFirstLetter(display) {
|
|
1615
|
+
switch (display) {
|
|
1616
|
+
case 'block':
|
|
1617
|
+
case 'inline-block':
|
|
1618
|
+
case 'list-item':
|
|
1619
|
+
case 'table-cell':
|
|
1620
|
+
case 'table-caption':
|
|
1621
|
+
return true;
|
|
1622
|
+
default:
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* How a box sits in its parent's inline content, for white space. An inline
|
|
1628
|
+
* box's content is its parent's own; an atomic inline — an inline-block, an
|
|
1629
|
+
* image — is one piece of it; a block ends the line it interrupts and starts
|
|
1630
|
+
* another; a float or a positioned box is no part of it at all.
|
|
1631
|
+
*/
|
|
1632
|
+
function flowOf(style, box) {
|
|
1633
|
+
if (box.outOfFlow || box.isFloat)
|
|
1634
|
+
return 'out';
|
|
1635
|
+
// an image told to be a table's part is an inline one (`isBlockLevel`)
|
|
1636
|
+
if (box.kind === 'replaced' && style.display.startsWith('table-')) {
|
|
1637
|
+
return 'atomic';
|
|
1638
|
+
}
|
|
1639
|
+
switch (style.display) {
|
|
1640
|
+
case 'inline':
|
|
1641
|
+
return box.kind === 'replaced' ? 'atomic' : 'inline';
|
|
1642
|
+
case 'inline-block':
|
|
1643
|
+
case 'inline-table':
|
|
1644
|
+
case 'inline-flex':
|
|
1645
|
+
return 'atomic';
|
|
1646
|
+
case 'table-row-group':
|
|
1647
|
+
case 'table-header-group':
|
|
1648
|
+
case 'table-footer-group':
|
|
1649
|
+
case 'table-row':
|
|
1650
|
+
case 'table-cell':
|
|
1651
|
+
case 'table-caption':
|
|
1652
|
+
case 'table-column':
|
|
1653
|
+
case 'table-column-group':
|
|
1654
|
+
// table parts in an inline box are given an inline table around them
|
|
1655
|
+
// (CSS 2.1 17.2.1), which sits in the line as an inline-block does:
|
|
1656
|
+
// the white space either side of it is the line's
|
|
1657
|
+
return box.parent?.kind === 'inline' ? 'atomic' : 'block';
|
|
1658
|
+
default:
|
|
1659
|
+
return 'block';
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
/** Where the inline content stands after a box, from where it stood inside
|
|
1663
|
+
* the box and before it. */
|
|
1664
|
+
function after(flow, inside, before) {
|
|
1665
|
+
switch (flow) {
|
|
1666
|
+
case 'inline':
|
|
1667
|
+
return inside;
|
|
1668
|
+
case 'atomic':
|
|
1669
|
+
return 'content';
|
|
1670
|
+
case 'out':
|
|
1671
|
+
return before;
|
|
1672
|
+
default:
|
|
1673
|
+
return 'start';
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
371
1676
|
function boxKindFor(display) {
|
|
372
1677
|
switch (display) {
|
|
373
1678
|
case 'inline':
|
|
@@ -400,18 +1705,37 @@ function boxKindFor(display) {
|
|
|
400
1705
|
return 'block';
|
|
401
1706
|
}
|
|
402
1707
|
}
|
|
1708
|
+
/** A replaced box's intrinsic size from what its content says, in CSS
|
|
1709
|
+
* pixels: an axis it does not give takes the default object size. */
|
|
1710
|
+
function setIntrinsics(box, size, scale) {
|
|
1711
|
+
box.intrinsic = {
|
|
1712
|
+
width: (size.width ?? 300) * scale,
|
|
1713
|
+
height: (size.height ?? 150) * scale,
|
|
1714
|
+
missing: (size.width === null ? 1 : 0) | (size.height === null ? 2 : 0),
|
|
1715
|
+
ratio: size.ratio,
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
403
1718
|
function replacedKind(el, tag) {
|
|
404
1719
|
switch (tag) {
|
|
405
1720
|
case 'img':
|
|
406
1721
|
return 'image';
|
|
1722
|
+
case 'svg':
|
|
1723
|
+
// XHTML's `<svg:svg>` too, which `tagOf` names `svg`
|
|
1724
|
+
return 'svg';
|
|
407
1725
|
case 'hr':
|
|
408
1726
|
return 'hr';
|
|
1727
|
+
case 'iframe':
|
|
1728
|
+
case 'video':
|
|
1729
|
+
case 'embed':
|
|
1730
|
+
case 'canvas':
|
|
1731
|
+
return 'frame';
|
|
409
1732
|
case 'textarea':
|
|
410
1733
|
return 'textarea';
|
|
411
1734
|
case 'select':
|
|
412
1735
|
return 'select';
|
|
413
|
-
|
|
414
|
-
|
|
1736
|
+
// A `<button>` is not one: its content is the document's, laid out and
|
|
1737
|
+
// drawn like any box's (the UA sheet's `button` rule), where an
|
|
1738
|
+
// `<input type=submit>` has only a value to show and is a widget.
|
|
415
1739
|
case 'input': {
|
|
416
1740
|
const type = (attr(el, 'type') ?? 'text').toLowerCase();
|
|
417
1741
|
if (type === 'checkbox')
|
|
@@ -428,82 +1752,28 @@ function replacedKind(el, tag) {
|
|
|
428
1752
|
return 'none';
|
|
429
1753
|
}
|
|
430
1754
|
}
|
|
431
|
-
const ROMAN = [
|
|
432
|
-
[1000, 'm'],
|
|
433
|
-
[900, 'cm'],
|
|
434
|
-
[500, 'd'],
|
|
435
|
-
[400, 'cd'],
|
|
436
|
-
[100, 'c'],
|
|
437
|
-
[90, 'xc'],
|
|
438
|
-
[50, 'l'],
|
|
439
|
-
[40, 'xl'],
|
|
440
|
-
[10, 'x'],
|
|
441
|
-
[9, 'ix'],
|
|
442
|
-
[5, 'v'],
|
|
443
|
-
[4, 'iv'],
|
|
444
|
-
[1, 'i'],
|
|
445
|
-
];
|
|
446
1755
|
/**
|
|
447
|
-
* The marker a `list-item` draws
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
1756
|
+
* The marker a `list-item` draws: its `list-item` counter written in its
|
|
1757
|
+
* `list-style-type`, between the style's prefix and suffix, or the string
|
|
1758
|
+
* the type is. The counter is the builder's (`CounterScopes`), which is
|
|
1759
|
+
* what lets `value` on an `<li>`, `start` and `reversed` on an `<ol>`, a
|
|
1760
|
+
* list in a list and an author's own `counter-reset` and `counter-set` all
|
|
1761
|
+
* count as a browser counts them. A suffix that ends in a space — `. `, a
|
|
1762
|
+
* bullet's — is written without it, and the layout sets the marker off by
|
|
1763
|
+
* its own gap; one that does not, `、`, sets the marker against the text.
|
|
451
1764
|
*/
|
|
452
|
-
function markerFor(
|
|
1765
|
+
function markerFor(style, n, styles) {
|
|
453
1766
|
const type = style.listStyleType;
|
|
454
1767
|
if (type === 'none')
|
|
455
|
-
return '';
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
case 'decimal':
|
|
465
|
-
return `${n}.`;
|
|
466
|
-
case 'decimal-leading-zero':
|
|
467
|
-
return `${n < 10 ? '0' : ''}${n}.`;
|
|
468
|
-
case 'lower-alpha':
|
|
469
|
-
case 'lower-latin':
|
|
470
|
-
return `${alpha(n).toLowerCase()}.`;
|
|
471
|
-
case 'upper-alpha':
|
|
472
|
-
case 'upper-latin':
|
|
473
|
-
return `${alpha(n)}.`;
|
|
474
|
-
case 'lower-roman':
|
|
475
|
-
return `${roman(n)}.`;
|
|
476
|
-
case 'upper-roman':
|
|
477
|
-
return `${roman(n).toUpperCase()}.`;
|
|
478
|
-
case 'circle':
|
|
479
|
-
return '◦';
|
|
480
|
-
case 'square':
|
|
481
|
-
return '▪';
|
|
482
|
-
case 'disc':
|
|
483
|
-
default:
|
|
484
|
-
return '•';
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
function alpha(n) {
|
|
488
|
-
let out = '';
|
|
489
|
-
let v = Math.max(1, n);
|
|
490
|
-
while (v > 0) {
|
|
491
|
-
const rem = (v - 1) % 26;
|
|
492
|
-
out = String.fromCharCode(65 + rem) + out;
|
|
493
|
-
v = Math.floor((v - 1) / 26);
|
|
494
|
-
}
|
|
495
|
-
return out;
|
|
496
|
-
}
|
|
497
|
-
function roman(n) {
|
|
498
|
-
let v = Math.max(1, Math.min(3999, n));
|
|
499
|
-
let out = '';
|
|
500
|
-
for (const [value, sym] of ROMAN) {
|
|
501
|
-
while (v >= value) {
|
|
502
|
-
out += sym;
|
|
503
|
-
v -= value;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
return out;
|
|
1768
|
+
return { text: '', flush: false };
|
|
1769
|
+
if (type.startsWith('"'))
|
|
1770
|
+
return { text: type.slice(1), flush: true };
|
|
1771
|
+
const { prefix, text, suffix } = styles.marker(n, type, style.direction === 'rtl');
|
|
1772
|
+
const spaced = /\s$/.test(suffix);
|
|
1773
|
+
return {
|
|
1774
|
+
text: prefix + text + (spaced ? suffix.trimEnd() : suffix),
|
|
1775
|
+
flush: !spaced,
|
|
1776
|
+
};
|
|
507
1777
|
}
|
|
508
1778
|
/**
|
|
509
1779
|
* Give every box the document range its subtree covers. Runs after `fixUp`,
|
|
@@ -546,23 +1816,38 @@ function assignSubtreeRanges(box) {
|
|
|
546
1816
|
* straight to `<tr>`, and CSS says the missing row group is generated
|
|
547
1817
|
* rather than the rows being dropped.
|
|
548
1818
|
*/
|
|
549
|
-
function fixUp(box) {
|
|
1819
|
+
function fixUp(box, anonymous) {
|
|
550
1820
|
for (const child of box.children)
|
|
551
|
-
fixUp(child);
|
|
1821
|
+
fixUp(child, anonymous);
|
|
1822
|
+
fixUpOwn(box, anonymous);
|
|
1823
|
+
}
|
|
1824
|
+
/** `fixUp` of a box whose children have had theirs. */
|
|
1825
|
+
function fixUpOwn(box, anonymous) {
|
|
552
1826
|
if (box.kind === 'table') {
|
|
553
|
-
fixUpTable(box);
|
|
1827
|
+
fixUpTable(box, anonymous);
|
|
554
1828
|
return;
|
|
555
1829
|
}
|
|
556
1830
|
if (box.kind === 'table-row-group') {
|
|
557
|
-
wrapOrphans(box, 'table-row', (k) => k === 'table-row');
|
|
1831
|
+
wrapOrphans(box, 'table-row', (k) => k === 'table-row', anonymous);
|
|
558
1832
|
return;
|
|
559
1833
|
}
|
|
560
1834
|
if (box.kind === 'table-row') {
|
|
561
|
-
wrapOrphans(box, 'table-cell', (k) => k === 'table-cell');
|
|
1835
|
+
wrapOrphans(box, 'table-cell', (k) => k === 'table-cell', anonymous);
|
|
562
1836
|
return;
|
|
563
1837
|
}
|
|
1838
|
+
// a column group holds its columns, which are where they belong, and the
|
|
1839
|
+
// builder kept nothing else in it
|
|
1840
|
+
if (box.style.display === 'table-column-group')
|
|
1841
|
+
return;
|
|
564
1842
|
if (!box.children.length)
|
|
565
1843
|
return;
|
|
1844
|
+
wrapTableParts(box, anonymous);
|
|
1845
|
+
// An inline box holds no block: a block in one is its block container's,
|
|
1846
|
+
// which breaks the inline box around it
|
|
1847
|
+
if (box.kind === 'inline')
|
|
1848
|
+
return;
|
|
1849
|
+
if (box.kind !== 'flex')
|
|
1850
|
+
breakInlines(box);
|
|
566
1851
|
let hasBlockLevel = false;
|
|
567
1852
|
let hasInlineLevel = false;
|
|
568
1853
|
for (const child of box.children) {
|
|
@@ -591,7 +1876,7 @@ function fixUp(box) {
|
|
|
591
1876
|
// finds itself; it does not force an anonymous block on its own.
|
|
592
1877
|
if (isBlockLevel(child) && !child.outOfFlow && !child.isFloat) {
|
|
593
1878
|
if (run) {
|
|
594
|
-
next.push(
|
|
1879
|
+
next.push(anonymousOf(box, 'block', run, anonymous));
|
|
595
1880
|
run = null;
|
|
596
1881
|
}
|
|
597
1882
|
next.push(child);
|
|
@@ -599,28 +1884,140 @@ function fixUp(box) {
|
|
|
599
1884
|
}
|
|
600
1885
|
// Whitespace between two blocks is not content and must not generate a
|
|
601
1886
|
// line box — `<div><p>a</p> <p>b</p></div>` has no blank line in it.
|
|
602
|
-
if (!run && child.kind === 'text' &&
|
|
1887
|
+
if (!run && child.kind === 'text' && isBlank(child.text))
|
|
603
1888
|
continue;
|
|
604
1889
|
(run ??= []).push(child);
|
|
605
1890
|
}
|
|
606
1891
|
if (run) {
|
|
607
|
-
if (run.every((c) => c.kind === 'text' &&
|
|
1892
|
+
if (run.every((c) => c.kind === 'text' && isBlank(c.text))) {
|
|
608
1893
|
// trailing whitespace after the last block: same rule
|
|
609
1894
|
}
|
|
610
1895
|
else {
|
|
611
|
-
next.push(
|
|
1896
|
+
next.push(anonymousOf(box, 'block', run, anonymous));
|
|
612
1897
|
}
|
|
613
1898
|
}
|
|
614
1899
|
box.children = next;
|
|
615
1900
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
1901
|
+
/**
|
|
1902
|
+
* Break a block container's inline children around the blocks in them
|
|
1903
|
+
* (CSS 2.1 9.2.1.1): an inline box with an in-flow block in it — at any
|
|
1904
|
+
* depth of inline box — becomes the pieces of it before, between and after
|
|
1905
|
+
* its blocks, and the blocks stand beside them as the container's own, for
|
|
1906
|
+
* the mixed-content rule to wrap the pieces in anonymous blocks.
|
|
1907
|
+
* `<font><p>One</p><p>Two</p></font>` laid its paragraphs side by side as
|
|
1908
|
+
* inline-blocks, and a link around a card's blocks set them in a line.
|
|
1909
|
+
*/
|
|
1910
|
+
function breakInlines(box) {
|
|
1911
|
+
let next = null;
|
|
1912
|
+
for (let i = 0; i < box.children.length; i += 1) {
|
|
1913
|
+
const child = box.children[i];
|
|
1914
|
+
const pieces = child.kind === 'inline' && holdsBlock(child) ? breakAround(child) : null;
|
|
1915
|
+
if (!pieces) {
|
|
1916
|
+
next?.push(child);
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
next ??= box.children.slice(0, i);
|
|
1920
|
+
for (const piece of pieces) {
|
|
1921
|
+
piece.parent = box;
|
|
1922
|
+
next.push(piece);
|
|
1923
|
+
}
|
|
622
1924
|
}
|
|
623
|
-
|
|
1925
|
+
if (next)
|
|
1926
|
+
box.children = next;
|
|
1927
|
+
}
|
|
1928
|
+
/** Whether an inline box has an in-flow block in it, at any depth of
|
|
1929
|
+
* inline box: asked first, and without making anything, since nearly
|
|
1930
|
+
* every inline box has none. */
|
|
1931
|
+
function holdsBlock(inline) {
|
|
1932
|
+
for (const child of inline.children) {
|
|
1933
|
+
if (child.kind === 'inline') {
|
|
1934
|
+
if (holdsBlock(child))
|
|
1935
|
+
return true;
|
|
1936
|
+
}
|
|
1937
|
+
else if (isBlockLevel(child) && !child.outOfFlow && !child.isFloat) {
|
|
1938
|
+
return true;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
return false;
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* An inline box broken around the in-flow blocks in it: its pieces, each a
|
|
1945
|
+
* box of its element and style holding the content between two blocks,
|
|
1946
|
+
* with the blocks between them; null where it has none, which is nearly
|
|
1947
|
+
* every inline box. A piece has no edge on a side a block cut: the start of
|
|
1948
|
+
* the first and the end of the last are the box's own.
|
|
1949
|
+
*/
|
|
1950
|
+
function breakAround(inline) {
|
|
1951
|
+
let out = null;
|
|
1952
|
+
const pieces = [];
|
|
1953
|
+
let run = [];
|
|
1954
|
+
const close = () => {
|
|
1955
|
+
const piece = new Box('inline', inline.el, inline.style);
|
|
1956
|
+
piece.pseudo = inline.pseudo;
|
|
1957
|
+
for (const child of run)
|
|
1958
|
+
piece.append(child);
|
|
1959
|
+
pieces.push(piece);
|
|
1960
|
+
(out ??= []).push(piece);
|
|
1961
|
+
run = [];
|
|
1962
|
+
};
|
|
1963
|
+
const blocks = [];
|
|
1964
|
+
for (const child of inline.children) {
|
|
1965
|
+
if (isBlockLevel(child) && !child.outOfFlow && !child.isFloat) {
|
|
1966
|
+
close();
|
|
1967
|
+
out.push(child);
|
|
1968
|
+
blocks.push(child);
|
|
1969
|
+
continue;
|
|
1970
|
+
}
|
|
1971
|
+
const inner = child.kind === 'inline' && holdsBlock(child) ? breakAround(child) : null;
|
|
1972
|
+
if (!inner) {
|
|
1973
|
+
run.push(child);
|
|
1974
|
+
continue;
|
|
1975
|
+
}
|
|
1976
|
+
for (const piece of inner) {
|
|
1977
|
+
if (piece.kind === 'inline') {
|
|
1978
|
+
run.push(piece);
|
|
1979
|
+
}
|
|
1980
|
+
else {
|
|
1981
|
+
close();
|
|
1982
|
+
out.push(piece);
|
|
1983
|
+
blocks.push(piece);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
if (!out)
|
|
1988
|
+
return null;
|
|
1989
|
+
close();
|
|
1990
|
+
for (let i = 0; i < pieces.length; i += 1) {
|
|
1991
|
+
pieces[i].cut = (i > 0 ? 1 : 0) | (i < pieces.length - 1 ? 2 : 0);
|
|
1992
|
+
}
|
|
1993
|
+
if (isRelative(inline.style))
|
|
1994
|
+
CUT_BLOCKS.set(pieces[0], blocks);
|
|
1995
|
+
const fade = inline.style.opacity;
|
|
1996
|
+
if (fade < 1) {
|
|
1997
|
+
for (const block of blocks) {
|
|
1998
|
+
FADED_BLOCKS.set(block, (FADED_BLOCKS.get(block) ?? 1) * fade);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
return out;
|
|
2002
|
+
}
|
|
2003
|
+
function anonymousStyles(initial) {
|
|
2004
|
+
const made = new WeakMap();
|
|
2005
|
+
return (parent, display) => {
|
|
2006
|
+
let byDisplay = made.get(parent.style);
|
|
2007
|
+
if (!byDisplay) {
|
|
2008
|
+
byDisplay = new Map();
|
|
2009
|
+
made.set(parent.style, byDisplay);
|
|
2010
|
+
}
|
|
2011
|
+
let style = byDisplay.get(display);
|
|
2012
|
+
if (!style) {
|
|
2013
|
+
// `display` is the one property it does not start from: it is the
|
|
2014
|
+
// box the fix-up made, and layout asks the style what a box is
|
|
2015
|
+
style = inherit(parent.style, initial);
|
|
2016
|
+
style.display = display;
|
|
2017
|
+
byDisplay.set(display, style);
|
|
2018
|
+
}
|
|
2019
|
+
return style;
|
|
2020
|
+
};
|
|
624
2021
|
}
|
|
625
2022
|
function isBlockLevel(box) {
|
|
626
2023
|
switch (box.kind) {
|
|
@@ -634,6 +2031,15 @@ function isBlockLevel(box) {
|
|
|
634
2031
|
// An `inline-block` or `inline-flex` is a block *container* with an
|
|
635
2032
|
// inline-level outer role, so it belongs to the inline run around it.
|
|
636
2033
|
return !isInlineLevelDisplay(box.style.display);
|
|
2034
|
+
case 'replaced':
|
|
2035
|
+
// an image is inline unless it is told otherwise, and then it is a
|
|
2036
|
+
// block: `img { display: block }`, which mail writes to lose the gap
|
|
2037
|
+
// under its images, stacks them. A table's part is no display it
|
|
2038
|
+
// can take, and one told to be a cell is inline (CSS Display 3,
|
|
2039
|
+
// 2.4), which is what a table wraps a cell around.
|
|
2040
|
+
return (box.style.display !== 'inline' &&
|
|
2041
|
+
!isInlineLevelDisplay(box.style.display) &&
|
|
2042
|
+
!box.style.display.startsWith('table-'));
|
|
637
2043
|
default:
|
|
638
2044
|
return false;
|
|
639
2045
|
}
|
|
@@ -644,7 +2050,7 @@ function isInlineLevelDisplay(display) {
|
|
|
644
2050
|
display === 'inline-table');
|
|
645
2051
|
}
|
|
646
2052
|
/** Wrap children that are not of `expect` in an anonymous box that is. */
|
|
647
|
-
function wrapOrphans(box, kind, accept) {
|
|
2053
|
+
function wrapOrphans(box, kind, accept, anonymous) {
|
|
648
2054
|
let needed = false;
|
|
649
2055
|
for (const child of box.children) {
|
|
650
2056
|
if (!accept(child.kind) && !isDroppableWhitespace(child)) {
|
|
@@ -654,27 +2060,50 @@ function wrapOrphans(box, kind, accept) {
|
|
|
654
2060
|
}
|
|
655
2061
|
if (!needed)
|
|
656
2062
|
return;
|
|
2063
|
+
// White space goes only between two of the parts, or beside one at an
|
|
2064
|
+
// end (CSS 2.1 17.2.1, rule 1): beside a child the fix-up wraps, it is
|
|
2065
|
+
// that child's run's, so `<span>a</span> <span>b</span>` in a row is one
|
|
2066
|
+
// anonymous cell of `a b`, where it was `ab`
|
|
657
2067
|
const next = [];
|
|
658
2068
|
let run = null;
|
|
2069
|
+
let space = [];
|
|
659
2070
|
for (const child of box.children) {
|
|
660
2071
|
if (accept(child.kind)) {
|
|
661
2072
|
if (run) {
|
|
662
|
-
|
|
2073
|
+
run.push(...space);
|
|
2074
|
+
next.push(anonymousOf(box, kind, run, anonymous));
|
|
663
2075
|
run = null;
|
|
664
2076
|
}
|
|
2077
|
+
space = [];
|
|
665
2078
|
next.push(child);
|
|
666
2079
|
continue;
|
|
667
2080
|
}
|
|
668
|
-
if (isDroppableWhitespace(child))
|
|
2081
|
+
if (isDroppableWhitespace(child)) {
|
|
2082
|
+
space.push(child);
|
|
669
2083
|
continue;
|
|
670
|
-
|
|
2084
|
+
}
|
|
2085
|
+
(run ??= []).push(...space, child);
|
|
2086
|
+
space = [];
|
|
2087
|
+
}
|
|
2088
|
+
if (run) {
|
|
2089
|
+
run.push(...space);
|
|
2090
|
+
next.push(anonymousOf(box, kind, run, anonymous));
|
|
671
2091
|
}
|
|
672
|
-
if (run)
|
|
673
|
-
next.push(anonymousOf(box, kind, run));
|
|
674
2092
|
box.children = next;
|
|
2093
|
+
// A cell made here is a block container the fix-up has not been to: its
|
|
2094
|
+
// children had theirs, as the table's, before it held them. Text beside a
|
|
2095
|
+
// block in it goes in an anonymous block as in any other (CSS 2.1
|
|
2096
|
+
// 9.2.1.1) — `<span style="display: inline-table">bcd<div>x</div>` laid
|
|
2097
|
+
// `bcd` out as nothing, and drew the table as wide as the `x`.
|
|
2098
|
+
if (kind === 'table-cell') {
|
|
2099
|
+
for (const child of next) {
|
|
2100
|
+
if (child.kind === 'table-cell' && !child.el)
|
|
2101
|
+
fixUpOwn(child, anonymous);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
675
2104
|
}
|
|
676
|
-
function anonymousOf(parent, kind, run) {
|
|
677
|
-
const box = new Box(kind, null, parent
|
|
2105
|
+
function anonymousOf(parent, kind, run, anonymous, display = kind) {
|
|
2106
|
+
const box = new Box(kind, null, anonymous(parent, display));
|
|
678
2107
|
box.parent = parent;
|
|
679
2108
|
for (const child of run) {
|
|
680
2109
|
child.parent = box;
|
|
@@ -682,17 +2111,140 @@ function anonymousOf(parent, kind, run) {
|
|
|
682
2111
|
}
|
|
683
2112
|
return box;
|
|
684
2113
|
}
|
|
2114
|
+
/** White space alone, which the table fix-up drops between the parts of a
|
|
2115
|
+
* table whether it is kept or not (CSS 2.1 17.2.1, rule 1). Kept, under
|
|
2116
|
+
* `white-space: pre`, it made a cell of the line break before every row. */
|
|
685
2117
|
function isDroppableWhitespace(box) {
|
|
686
|
-
return box.kind === 'text' &&
|
|
2118
|
+
return box.kind === 'text' && isBlank(box.text);
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Whether a text is white space alone: CSS's white space, which is the
|
|
2122
|
+
* space, the tab and the three line breaks and nothing else (CSS Text 3,
|
|
2123
|
+
* 4.1). JavaScript's `\s` and `trim` take in the no-break space and the
|
|
2124
|
+
* other Unicode spaces too, so a `<p> </p>` spacer or a
|
|
2125
|
+
* `<td> </td>` beside a block was dropped as if it held nothing. Tested
|
|
2126
|
+
* rather than trimmed, which copies a paragraph's text to find out.
|
|
2127
|
+
*/
|
|
2128
|
+
export function isBlank(text) {
|
|
2129
|
+
return BLANK.test(text);
|
|
2130
|
+
}
|
|
2131
|
+
const BLANK = /^[ \t\n\r\f]*$/;
|
|
2132
|
+
/** White space that collapsing would change: anything but a lone space.
|
|
2133
|
+
* Most of a document's text has none, and is its own collapsed form. */
|
|
2134
|
+
const COLLAPSIBLE = /[\t\n\r\f]| {2}/;
|
|
2135
|
+
function isRelative(style) {
|
|
2136
|
+
return style.position === 'relative' || style.position === 'sticky';
|
|
2137
|
+
}
|
|
2138
|
+
/** Whether `position: relative` moves a box: an inset is set. An inline box
|
|
2139
|
+
* that is moved has its text laid out apart (`collect`), so one whose
|
|
2140
|
+
* insets resolve to nothing still costs its paragraph the layout of a line
|
|
2141
|
+
* at a time, which a box with every inset `auto` does not. */
|
|
2142
|
+
export function isOffset(style) {
|
|
2143
|
+
if (!isRelative(style))
|
|
2144
|
+
return false;
|
|
2145
|
+
return (style.top !== AUTO ||
|
|
2146
|
+
style.right !== AUTO ||
|
|
2147
|
+
style.bottom !== AUTO ||
|
|
2148
|
+
style.left !== AUTO);
|
|
2149
|
+
}
|
|
2150
|
+
/** Whether a style may put its box under its stacking context's flow: a
|
|
2151
|
+
* negative `z-index` on a positioned box, or on a flex item, which that
|
|
2152
|
+
* makes a stacking context as it is (`stacksLayers`). */
|
|
2153
|
+
function isNegative(style) {
|
|
2154
|
+
return typeof style.zIndex === 'number' && style.zIndex < 0;
|
|
2155
|
+
}
|
|
2156
|
+
/** A box that belongs inside a table: a row group, a row, a cell, a
|
|
2157
|
+
* caption or a column. Asked of every child of every box in a build, so
|
|
2158
|
+
* a text or an inline box is answered from its kind, without its style. */
|
|
2159
|
+
function isTablePart(box) {
|
|
2160
|
+
switch (box.kind) {
|
|
2161
|
+
case 'table-row-group':
|
|
2162
|
+
case 'table-row':
|
|
2163
|
+
case 'table-cell':
|
|
2164
|
+
case 'table-caption':
|
|
2165
|
+
return !box.outOfFlow && !box.isFloat;
|
|
2166
|
+
case 'block': {
|
|
2167
|
+
if (box.outOfFlow || box.isFloat)
|
|
2168
|
+
return false;
|
|
2169
|
+
const display = box.style.display;
|
|
2170
|
+
return display === 'table-column' || display === 'table-column-group';
|
|
2171
|
+
}
|
|
2172
|
+
default:
|
|
2173
|
+
return false;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Table parts outside a table get one around them (CSS 2.1 17.2.1, rule 3):
|
|
2178
|
+
* each run of them — the white space between them no part of it — is
|
|
2179
|
+
* wrapped in an anonymous table, an inline one where the parent is inline,
|
|
2180
|
+
* and the table is then completed as any other. Laid out on their own, two
|
|
2181
|
+
* cells side by side in a line were two inline-blocks with a space between
|
|
2182
|
+
* them, and in a block two blocks, one above the other.
|
|
2183
|
+
*/
|
|
2184
|
+
function wrapTableParts(box, anonymous) {
|
|
2185
|
+
if (!box.children.some(isTablePart))
|
|
2186
|
+
return;
|
|
2187
|
+
const display = box.kind === 'inline' ? 'inline-table' : 'table';
|
|
2188
|
+
const next = [];
|
|
2189
|
+
let run = null;
|
|
2190
|
+
let space = [];
|
|
2191
|
+
const flush = () => {
|
|
2192
|
+
if (!run)
|
|
2193
|
+
return;
|
|
2194
|
+
const table = anonymousOf(box, 'table', run, anonymous, display);
|
|
2195
|
+
fixUpTable(table, anonymous);
|
|
2196
|
+
next.push(table);
|
|
2197
|
+
run = null;
|
|
2198
|
+
};
|
|
2199
|
+
for (const child of box.children) {
|
|
2200
|
+
if (isTablePart(child)) {
|
|
2201
|
+
run ??= [];
|
|
2202
|
+
run.push(child);
|
|
2203
|
+
space = [];
|
|
2204
|
+
continue;
|
|
2205
|
+
}
|
|
2206
|
+
if (run && isDroppableWhitespace(child)) {
|
|
2207
|
+
// held back: between two parts it goes, after the last it stays
|
|
2208
|
+
space.push(child);
|
|
2209
|
+
continue;
|
|
2210
|
+
}
|
|
2211
|
+
flush();
|
|
2212
|
+
next.push(...space, child);
|
|
2213
|
+
space = [];
|
|
2214
|
+
}
|
|
2215
|
+
flush();
|
|
2216
|
+
next.push(...space);
|
|
2217
|
+
box.children = next;
|
|
2218
|
+
for (const child of next)
|
|
2219
|
+
child.parent = box;
|
|
687
2220
|
}
|
|
688
|
-
function fixUpTable(table) {
|
|
2221
|
+
function fixUpTable(table, anonymous) {
|
|
689
2222
|
const groups = [];
|
|
690
2223
|
const captions = [];
|
|
2224
|
+
const columns = [];
|
|
691
2225
|
let looseRows = null;
|
|
2226
|
+
let looseCells = null;
|
|
2227
|
+
const flushCells = () => {
|
|
2228
|
+
if (!looseCells)
|
|
2229
|
+
return;
|
|
2230
|
+
(looseRows ??= []).push(anonymousOf(table, 'table-row', looseCells, anonymous));
|
|
2231
|
+
looseCells = null;
|
|
2232
|
+
};
|
|
692
2233
|
for (const child of table.children) {
|
|
693
|
-
|
|
2234
|
+
const display = child.style.display;
|
|
2235
|
+
// an image told to be a column is an inline image, as one told to be a
|
|
2236
|
+
// cell is (`flowOf`): taken for a column, it was never drawn
|
|
2237
|
+
if ((display === 'table-column' || display === 'table-column-group') &&
|
|
2238
|
+
child.kind !== 'replaced') {
|
|
2239
|
+
// A column is the table's, beside its rows: it lays out nothing and
|
|
2240
|
+
// paints nothing (17.2.1). Taken for a stray child, it was wrapped in
|
|
2241
|
+
// a row of its own and drawn as a cell.
|
|
2242
|
+
columns.push(child);
|
|
2243
|
+
}
|
|
2244
|
+
else if (child.kind === 'table-row-group') {
|
|
2245
|
+
flushCells();
|
|
694
2246
|
if (looseRows) {
|
|
695
|
-
groups.push(anonymousOf(table, 'table-row-group', looseRows));
|
|
2247
|
+
groups.push(anonymousOf(table, 'table-row-group', looseRows, anonymous));
|
|
696
2248
|
looseRows = null;
|
|
697
2249
|
}
|
|
698
2250
|
groups.push(child);
|
|
@@ -701,27 +2253,35 @@ function fixUpTable(table) {
|
|
|
701
2253
|
captions.push(child);
|
|
702
2254
|
}
|
|
703
2255
|
else if (isDroppableWhitespace(child)) {
|
|
2256
|
+
// after a loose child it goes with the loose children, and the cells
|
|
2257
|
+
// they are wrapped in say whether it stays (`wrapOrphans`)
|
|
2258
|
+
if (looseCells)
|
|
2259
|
+
looseCells.push(child);
|
|
704
2260
|
continue;
|
|
705
2261
|
}
|
|
2262
|
+
else if (child.kind === 'table-row') {
|
|
2263
|
+
flushCells();
|
|
2264
|
+
(looseRows ??= []).push(child);
|
|
2265
|
+
}
|
|
706
2266
|
else {
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
: anonymousOf(table, 'table-row', [child]);
|
|
713
|
-
(looseRows ??= []).push(row);
|
|
2267
|
+
// Anything else that ended up here is a cell, or goes in one, and a
|
|
2268
|
+
// run of them shares an anonymous row — which is how a browser
|
|
2269
|
+
// rescues `<table>text</table>`, and how three cells in a table
|
|
2270
|
+
// with no row are one row of three rather than three rows of one.
|
|
2271
|
+
(looseCells ??= []).push(child);
|
|
714
2272
|
}
|
|
715
2273
|
}
|
|
716
|
-
|
|
717
|
-
|
|
2274
|
+
flushCells();
|
|
2275
|
+
if (looseRows) {
|
|
2276
|
+
groups.push(anonymousOf(table, 'table-row-group', looseRows, anonymous));
|
|
2277
|
+
}
|
|
718
2278
|
for (const group of groups) {
|
|
719
|
-
wrapOrphans(group, 'table-row', (k) => k === 'table-row');
|
|
2279
|
+
wrapOrphans(group, 'table-row', (k) => k === 'table-row', anonymous);
|
|
720
2280
|
for (const row of group.children) {
|
|
721
|
-
wrapOrphans(row, 'table-cell', (k) => k === 'table-cell');
|
|
2281
|
+
wrapOrphans(row, 'table-cell', (k) => k === 'table-cell', anonymous);
|
|
722
2282
|
}
|
|
723
2283
|
}
|
|
724
|
-
table.children = [...captions, ...groups];
|
|
2284
|
+
table.children = [...captions, ...columns, ...groups];
|
|
725
2285
|
for (const child of table.children)
|
|
726
2286
|
child.parent = table;
|
|
727
2287
|
}
|