@react-x11/components 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -38
- package/dist/charts/node.d.ts +12 -2
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +50 -16
- 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 +3 -1
- 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/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/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 +13 -2
- package/dist/codeblock/index.d.ts.map +1 -1
- package/dist/codeblock/index.js +20 -3
- package/dist/codeblock/index.js.map +1 -1
- package/dist/embed/client.d.ts +48 -3
- package/dist/embed/client.d.ts.map +1 -1
- package/dist/embed/client.js +79 -5
- package/dist/embed/client.js.map +1 -1
- package/dist/embed/index.d.ts +1 -1
- package/dist/embed/index.d.ts.map +1 -1
- package/dist/embed/index.js +1 -1
- package/dist/embed/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 +240 -6
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +1327 -66
- 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 +498 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +3698 -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 +57 -11
- 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 +2937 -197
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +340 -17
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +1714 -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 +151 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +3375 -174
- 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 +172 -5
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +1336 -103
- 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 +3503 -141
- 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 +435 -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 +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- 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/{markdown → internal/markdown}/ast.d.ts +23 -0
- package/dist/internal/markdown/ast.d.ts.map +1 -0
- package/dist/internal/markdown/ast.js +22 -0
- package/dist/internal/markdown/ast.js.map +1 -0
- package/dist/internal/markdown/parse.d.ts.map +1 -0
- package/dist/{markdown → internal/markdown}/parse.js +221 -30
- package/dist/internal/markdown/parse.js.map +1 -0
- package/dist/internal/markdown/stringify.d.ts +4 -0
- package/dist/internal/markdown/stringify.d.ts.map +1 -0
- package/dist/internal/markdown/stringify.js +684 -0
- package/dist/internal/markdown/stringify.js.map +1 -0
- package/dist/internal/markdown/tags.d.ts.map +1 -0
- package/dist/internal/markdown/tags.js.map +1 -0
- 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/scroll.d.ts +3 -2
- package/dist/internal/scroll.d.ts.map +1 -1
- package/dist/internal/scroll.js +1 -0
- package/dist/internal/scroll.js.map +1 -1
- 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 +103 -0
- package/dist/maps/anchors.d.ts.map +1 -0
- package/dist/maps/anchors.js +790 -0
- package/dist/maps/anchors.js.map +1 -0
- package/dist/maps/controller.d.ts +340 -0
- package/dist/maps/controller.d.ts.map +1 -0
- package/dist/maps/controller.js +822 -0
- package/dist/maps/controller.js.map +1 -0
- package/dist/maps/gl/buckets.d.ts +99 -0
- package/dist/maps/gl/buckets.d.ts.map +1 -0
- package/dist/maps/gl/buckets.js +365 -0
- package/dist/maps/gl/buckets.js.map +1 -0
- package/dist/maps/gl/build-worker.d.ts +15 -0
- package/dist/maps/gl/build-worker.d.ts.map +1 -0
- package/dist/maps/gl/build-worker.js +44 -0
- package/dist/maps/gl/build-worker.js.map +1 -0
- package/dist/maps/gl/color.d.ts +8 -0
- package/dist/maps/gl/color.d.ts.map +1 -0
- package/dist/maps/gl/color.js +57 -0
- package/dist/maps/gl/color.js.map +1 -0
- package/dist/maps/gl/cover.d.ts +70 -0
- package/dist/maps/gl/cover.d.ts.map +1 -0
- package/dist/maps/gl/cover.js +137 -0
- package/dist/maps/gl/cover.js.map +1 -0
- package/dist/maps/gl/index.d.ts +3 -0
- package/dist/maps/gl/index.d.ts.map +1 -0
- package/dist/maps/gl/index.js +20 -0
- package/dist/maps/gl/index.js.map +1 -0
- package/dist/maps/gl/markers.d.ts +28 -0
- package/dist/maps/gl/markers.d.ts.map +1 -0
- package/dist/maps/gl/markers.js +60 -0
- package/dist/maps/gl/markers.js.map +1 -0
- package/dist/maps/gl/overlays.d.ts +68 -0
- package/dist/maps/gl/overlays.d.ts.map +1 -0
- package/dist/maps/gl/overlays.js +300 -0
- package/dist/maps/gl/overlays.js.map +1 -0
- package/dist/maps/gl/pane.d.ts +12 -0
- package/dist/maps/gl/pane.d.ts.map +1 -0
- package/dist/maps/gl/pane.js +37 -0
- package/dist/maps/gl/pane.js.map +1 -0
- package/dist/maps/gl/placement.d.ts +124 -0
- package/dist/maps/gl/placement.d.ts.map +1 -0
- package/dist/maps/gl/placement.js +642 -0
- package/dist/maps/gl/placement.js.map +1 -0
- package/dist/maps/gl/renderer.d.ts +384 -0
- package/dist/maps/gl/renderer.d.ts.map +1 -0
- package/dist/maps/gl/renderer.js +1184 -0
- package/dist/maps/gl/renderer.js.map +1 -0
- package/dist/maps/gl/shaders.d.ts +121 -0
- package/dist/maps/gl/shaders.d.ts.map +1 -0
- package/dist/maps/gl/shaders.js +453 -0
- package/dist/maps/gl/shaders.js.map +1 -0
- package/dist/maps/gl/store.d.ts +125 -0
- package/dist/maps/gl/store.d.ts.map +1 -0
- package/dist/maps/gl/store.js +512 -0
- package/dist/maps/gl/store.js.map +1 -0
- package/dist/maps/gl/text.d.ts +283 -0
- package/dist/maps/gl/text.d.ts.map +1 -0
- package/dist/maps/gl/text.js +698 -0
- package/dist/maps/gl/text.js.map +1 -0
- package/dist/maps/gl/view.d.ts +345 -0
- package/dist/maps/gl/view.d.ts.map +1 -0
- package/dist/maps/gl/view.js +1209 -0
- package/dist/maps/gl/view.js.map +1 -0
- 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 +3 -1
- package/dist/maps/index.d.ts.map +1 -1
- package/dist/maps/index.js +218 -52
- package/dist/maps/index.js.map +1 -1
- package/dist/maps/labels.d.ts +91 -18
- package/dist/maps/labels.d.ts.map +1 -1
- package/dist/maps/labels.js +376 -140
- package/dist/maps/labels.js.map +1 -1
- package/dist/maps/node.d.ts +179 -56
- package/dist/maps/node.d.ts.map +1 -1
- package/dist/maps/node.js +663 -537
- package/dist/maps/node.js.map +1 -1
- package/dist/maps/overlay.d.ts +72 -9
- package/dist/maps/overlay.d.ts.map +1 -1
- package/dist/maps/overlay.js +108 -28
- package/dist/maps/overlay.js.map +1 -1
- package/dist/maps/paint.d.ts +3 -0
- package/dist/maps/paint.d.ts.map +1 -1
- package/dist/maps/paint.js.map +1 -1
- package/dist/maps/proj.d.ts +62 -0
- package/dist/maps/proj.d.ts.map +1 -1
- package/dist/maps/proj.js +101 -0
- package/dist/maps/proj.js.map +1 -1
- package/dist/maps/renderer.d.ts +69 -0
- package/dist/maps/renderer.d.ts.map +1 -0
- package/dist/maps/renderer.js +66 -0
- package/dist/maps/renderer.js.map +1 -0
- package/dist/maps/sources.d.ts +30 -6
- package/dist/maps/sources.d.ts.map +1 -1
- package/dist/maps/sources.js +22 -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/theme.d.ts +19 -0
- package/dist/maps/theme.d.ts.map +1 -0
- package/dist/maps/theme.js +44 -0
- package/dist/maps/theme.js.map +1 -0
- package/dist/maps/tiles.d.ts +147 -27
- package/dist/maps/tiles.d.ts.map +1 -1
- package/dist/maps/tiles.js +221 -54
- package/dist/maps/tiles.js.map +1 -1
- package/dist/maps/types.d.ts +213 -21
- package/dist/maps/types.d.ts.map +1 -1
- package/dist/markdown/expressions.d.ts +1 -1
- package/dist/markdown/expressions.d.ts.map +1 -1
- package/dist/markdown/expressions.js.map +1 -1
- package/dist/markdown/index.d.ts +7 -6
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +121 -50
- package/dist/markdown/index.js.map +1 -1
- package/dist/markdown/spans.d.ts +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/media-player/index.d.ts +11 -3
- package/dist/media-player/index.d.ts.map +1 -1
- package/dist/media-player/index.js +13 -2
- package/dist/media-player/index.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/clipboard.d.ts +23 -0
- package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
- package/dist/rich-text-editor/clipboard.js +307 -0
- package/dist/rich-text-editor/clipboard.js.map +1 -0
- package/dist/rich-text-editor/collab.d.ts +32 -0
- package/dist/rich-text-editor/collab.d.ts.map +1 -0
- package/dist/rich-text-editor/collab.js +55 -0
- package/dist/rich-text-editor/collab.js.map +1 -0
- package/dist/rich-text-editor/commands.d.ts +50 -0
- package/dist/rich-text-editor/commands.d.ts.map +1 -0
- package/dist/rich-text-editor/commands.js +376 -0
- package/dist/rich-text-editor/commands.js.map +1 -0
- package/dist/rich-text-editor/drag.d.ts +68 -0
- package/dist/rich-text-editor/drag.d.ts.map +1 -0
- package/dist/rich-text-editor/drag.js +148 -0
- package/dist/rich-text-editor/drag.js.map +1 -0
- package/dist/rich-text-editor/html.d.ts +145 -0
- package/dist/rich-text-editor/html.d.ts.map +1 -0
- package/dist/rich-text-editor/html.js +516 -0
- package/dist/rich-text-editor/html.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +252 -0
- package/dist/rich-text-editor/index.d.ts.map +1 -0
- package/dist/rich-text-editor/index.js +856 -0
- package/dist/rich-text-editor/index.js.map +1 -0
- package/dist/rich-text-editor/inline.d.ts +94 -0
- package/dist/rich-text-editor/inline.d.ts.map +1 -0
- package/dist/rich-text-editor/inline.js +252 -0
- package/dist/rich-text-editor/inline.js.map +1 -0
- package/dist/rich-text-editor/keymap.d.ts +71 -0
- package/dist/rich-text-editor/keymap.d.ts.map +1 -0
- package/dist/rich-text-editor/keymap.js +394 -0
- package/dist/rich-text-editor/keymap.js.map +1 -0
- package/dist/rich-text-editor/keys.d.ts +40 -0
- package/dist/rich-text-editor/keys.d.ts.map +1 -0
- package/dist/rich-text-editor/keys.js +202 -0
- package/dist/rich-text-editor/keys.js.map +1 -0
- package/dist/rich-text-editor/look.d.ts +71 -0
- package/dist/rich-text-editor/look.d.ts.map +1 -0
- package/dist/rich-text-editor/look.js +342 -0
- package/dist/rich-text-editor/look.js.map +1 -0
- package/dist/rich-text-editor/markdown.d.ts +18 -0
- package/dist/rich-text-editor/markdown.d.ts.map +1 -0
- package/dist/rich-text-editor/markdown.js +496 -0
- package/dist/rich-text-editor/markdown.js.map +1 -0
- 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/nodes.d.ts +135 -0
- package/dist/rich-text-editor/nodes.d.ts.map +1 -0
- package/dist/rich-text-editor/nodes.js +397 -0
- package/dist/rich-text-editor/nodes.js.map +1 -0
- package/dist/rich-text-editor/render.d.ts +68 -0
- package/dist/rich-text-editor/render.d.ts.map +1 -0
- package/dist/rich-text-editor/render.js +518 -0
- package/dist/rich-text-editor/render.js.map +1 -0
- package/dist/rich-text-editor/schema.d.ts +16 -0
- package/dist/rich-text-editor/schema.d.ts.map +1 -0
- package/dist/rich-text-editor/schema.js +386 -0
- package/dist/rich-text-editor/schema.js.map +1 -0
- package/dist/rich-text-editor/store.d.ts +11 -0
- package/dist/rich-text-editor/store.d.ts.map +1 -0
- package/dist/rich-text-editor/store.js +43 -0
- package/dist/rich-text-editor/store.js.map +1 -0
- package/dist/rich-text-editor/suggest.d.ts +127 -0
- package/dist/rich-text-editor/suggest.d.ts.map +1 -0
- package/dist/rich-text-editor/suggest.js +0 -0
- package/dist/rich-text-editor/suggest.js.map +1 -0
- package/dist/rich-text-editor/tables.d.ts +41 -0
- package/dist/rich-text-editor/tables.d.ts.map +1 -0
- package/dist/rich-text-editor/tables.js +204 -0
- package/dist/rich-text-editor/tables.js.map +1 -0
- package/dist/rich-text-editor/toolbar.d.ts +58 -0
- package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
- package/dist/rich-text-editor/toolbar.js +548 -0
- package/dist/rich-text-editor/toolbar.js.map +1 -0
- package/dist/rich-text-editor/view.d.ts +368 -0
- package/dist/rich-text-editor/view.d.ts.map +1 -0
- package/dist/rich-text-editor/view.js +1968 -0
- package/dist/rich-text-editor/view.js.map +1 -0
- package/dist/rich-text-editor/virtual.d.ts +54 -0
- package/dist/rich-text-editor/virtual.d.ts.map +1 -0
- package/dist/rich-text-editor/virtual.js +279 -0
- package/dist/rich-text-editor/virtual.js.map +1 -0
- package/dist/richtext/node.d.ts +71 -4
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +233 -28
- 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/backends.d.ts +4 -1
- package/dist/terminal/backends.d.ts.map +1 -1
- package/dist/terminal/backends.js.map +1 -1
- package/dist/terminal/index.d.ts +18 -7
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +32 -11
- 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 +6 -1
- package/dist/terminal-output/index.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 +59 -31
- package/src/charts/node.ts +68 -28
- package/src/charts/render.ts +120 -30
- package/src/code/index.ts +3 -1
- 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/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/stream.ts +257 -17
- package/src/code-language/types.ts +5 -4
- package/src/codeblock/index.ts +20 -3
- package/src/embed/client.ts +102 -8
- package/src/embed/index.ts +2 -0
- 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 +1403 -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 +4102 -222
- package/src/html/css/ua.ts +58 -12
- 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 +3456 -230
- package/src/html/layout/boxes.ts +2100 -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 +3943 -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 +1486 -121
- package/src/html/paint.ts +4293 -169
- package/src/html/resources.ts +161 -50
- package/src/html/surfaces.ts +81 -0
- package/src/html/svg.ts +493 -0
- package/src/html/url.ts +86 -0
- package/src/index.ts +4 -0
- package/src/internal/collate.ts +12 -0
- package/src/{markdown → internal/markdown}/ast.ts +35 -7
- package/src/{markdown → internal/markdown}/parse.ts +288 -35
- package/src/internal/markdown/stringify.ts +772 -0
- package/src/internal/prewarm.ts +45 -0
- package/src/internal/scroll.ts +5 -3
- 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 +913 -0
- package/src/maps/controller.ts +1084 -0
- package/src/maps/gl/buckets.ts +461 -0
- package/src/maps/gl/build-worker.ts +66 -0
- package/src/maps/gl/color.ts +62 -0
- package/src/maps/gl/cover.ts +205 -0
- package/src/maps/gl/index.ts +23 -0
- package/src/maps/gl/markers.ts +83 -0
- package/src/maps/gl/overlays.ts +379 -0
- package/src/maps/gl/pane.ts +42 -0
- package/src/maps/gl/placement.ts +968 -0
- package/src/maps/gl/renderer.ts +1660 -0
- package/src/maps/gl/shaders.ts +489 -0
- package/src/maps/gl/store.ts +615 -0
- package/src/maps/gl/text.ts +868 -0
- package/src/maps/gl/view.ts +1489 -0
- package/src/maps/icons.ts +183 -0
- package/src/maps/index.ts +274 -81
- package/src/maps/labels.ts +457 -144
- package/src/maps/node.ts +832 -665
- package/src/maps/overlay.ts +148 -43
- package/src/maps/paint.ts +3 -0
- package/src/maps/proj.ts +118 -0
- package/src/maps/renderer.ts +155 -0
- package/src/maps/sources.ts +43 -6
- package/src/maps/style.ts +32 -0
- package/src/maps/styles.ts +215 -4
- package/src/maps/theme.ts +51 -0
- package/src/maps/tiles.ts +253 -62
- package/src/maps/types.ts +213 -21
- package/src/markdown/expressions.ts +5 -1
- package/src/markdown/index.ts +163 -78
- package/src/markdown/spans.ts +6 -1
- package/src/media-player/index.ts +18 -4
- 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/clipboard.ts +439 -0
- package/src/rich-text-editor/collab.ts +91 -0
- package/src/rich-text-editor/commands.ts +448 -0
- package/src/rich-text-editor/dom-globals.d.ts +23 -0
- package/src/rich-text-editor/drag.ts +211 -0
- package/src/rich-text-editor/html.ts +619 -0
- package/src/rich-text-editor/index.ts +1364 -0
- package/src/rich-text-editor/inline.ts +345 -0
- package/src/rich-text-editor/keymap.ts +510 -0
- package/src/rich-text-editor/keys.ts +228 -0
- package/src/rich-text-editor/look.ts +427 -0
- package/src/rich-text-editor/markdown.ts +653 -0
- package/src/rich-text-editor/marks.ts +101 -0
- package/src/rich-text-editor/nodes.ts +497 -0
- package/src/rich-text-editor/render.ts +837 -0
- package/src/rich-text-editor/schema.ts +431 -0
- package/src/rich-text-editor/store.ts +39 -0
- package/src/rich-text-editor/suggest.ts +597 -0
- package/src/rich-text-editor/tables.ts +272 -0
- package/src/rich-text-editor/toolbar.ts +724 -0
- package/src/rich-text-editor/view.ts +2287 -0
- package/src/rich-text-editor/virtual.ts +372 -0
- package/src/richtext/node.ts +294 -44
- package/src/richtext/runs.ts +23 -12
- package/src/table/index.ts +29 -0
- package/src/table/rows.ts +18 -8
- package/src/terminal/backends.ts +4 -1
- package/src/terminal/index.ts +47 -15
- package/src/terminal/vt/pty.ts +16 -2
- package/src/terminal-output/index.ts +6 -1
- package/src/three/canvas.ts +102 -8
- package/src/three/events.ts +49 -9
- package/src/tree/index.ts +19 -0
- package/dist/markdown/ast.d.ts.map +0 -1
- package/dist/markdown/ast.js +0 -17
- package/dist/markdown/ast.js.map +0 -1
- package/dist/markdown/parse.d.ts.map +0 -1
- package/dist/markdown/parse.js.map +0 -1
- package/dist/markdown/tags.d.ts.map +0 -1
- package/dist/markdown/tags.js.map +0 -1
- /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
- /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
- /package/dist/{markdown → internal/markdown}/tags.js +0 -0
- /package/src/{markdown → internal/markdown}/tags.ts +0 -0
package/src/html/layout/boxes.ts
CHANGED
|
@@ -24,11 +24,27 @@ import {
|
|
|
24
24
|
childrenOf,
|
|
25
25
|
isElement,
|
|
26
26
|
isText,
|
|
27
|
+
inImpliedHead,
|
|
27
28
|
NON_RENDERED,
|
|
28
29
|
tagOf,
|
|
29
30
|
} from '../dom.js';
|
|
30
|
-
import type { Cascade } from '../css/cascade.js';
|
|
31
|
+
import type { Cascade, FirstLetterRules } from '../css/cascade.js';
|
|
32
|
+
import type { CollapsedTable } from './collapse.js';
|
|
33
|
+
import { quoteAt } from '../css/content.js';
|
|
34
|
+
import type { CounterStyles } from '../css/counter-styles.js';
|
|
35
|
+
import type { ContentItem } from '../css/content.js';
|
|
36
|
+
import {
|
|
37
|
+
CONTAIN_STYLE,
|
|
38
|
+
FIRST_LINE_INHERITED,
|
|
39
|
+
copyStyle,
|
|
40
|
+
firstLineParent,
|
|
41
|
+
inherit,
|
|
42
|
+
} from '../css/style.js';
|
|
43
|
+
import { AUTO } from '../css/values.js';
|
|
44
|
+
import { svgIntrinsics } from '../svg.js';
|
|
45
|
+
import type { IntrinsicSize } from '../svg.js';
|
|
31
46
|
import type { ComputedStyle } from '../css/style.js';
|
|
47
|
+
import type { GridLines } from './grid-lines.js';
|
|
32
48
|
|
|
33
49
|
export type BoxKind =
|
|
34
50
|
| 'block'
|
|
@@ -44,6 +60,68 @@ export type BoxKind =
|
|
|
44
60
|
| 'marker'
|
|
45
61
|
| 'break';
|
|
46
62
|
|
|
63
|
+
// What only a few boxes carry lives beside the box rather than in it: a box
|
|
64
|
+
// is made for every element and every run of text, and each field on it is
|
|
65
|
+
// memory every walk over the tree goes through — fifteen fields more, never
|
|
66
|
+
// read, made a document's first paint 6% slower.
|
|
67
|
+
|
|
68
|
+
/** A list item's marker: its text, a bullet or the item's number, and
|
|
69
|
+
* where it was laid out. */
|
|
70
|
+
export interface Marker {
|
|
71
|
+
text: string;
|
|
72
|
+
layout: TextLayoutLike | null;
|
|
73
|
+
x: number;
|
|
74
|
+
y: number;
|
|
75
|
+
/** Its own style where a `::marker` rule gives it one; the item's where
|
|
76
|
+
* none does. */
|
|
77
|
+
style: ComputedStyle | null;
|
|
78
|
+
/** The image it is, `list-style-image`'s, at its size in device pixels,
|
|
79
|
+
* where that has arrived; its text is then not drawn. */
|
|
80
|
+
image?: { url: string; width: number; height: number };
|
|
81
|
+
/** Its text is its own — a string `list-style-type`, a `::marker`'s
|
|
82
|
+
* `content` — and ends at the content's edge, with no gap a number
|
|
83
|
+
* or a bullet is set apart by. */
|
|
84
|
+
flush?: true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** An out-of-flow box's static position, as an offset from the box whose
|
|
88
|
+
* flow it was taken from, which may yet move. */
|
|
89
|
+
export interface StaticPosition {
|
|
90
|
+
from: Box;
|
|
91
|
+
x: number;
|
|
92
|
+
right: number;
|
|
93
|
+
y: number;
|
|
94
|
+
/**
|
|
95
|
+
* For a flex box's or a grid's child, the box it is where it would be as
|
|
96
|
+
* the one item in — `from`'s content box or its padding box, whose size
|
|
97
|
+
* is read once its layout is done — and how far along the free room in
|
|
98
|
+
* it its alignment puts it, from the start across and from the top down.
|
|
99
|
+
*/
|
|
100
|
+
inside?: {
|
|
101
|
+
box: 'content' | 'padding';
|
|
102
|
+
across: number;
|
|
103
|
+
down: number;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The ascent and descent of a decorated inline box's own face. */
|
|
108
|
+
export interface InlineDecoration {
|
|
109
|
+
ascent: number;
|
|
110
|
+
descent: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A set of adjoining margins as CSS 2.1 8.3.1 collapses them: the largest
|
|
115
|
+
* positive and the most negative, whose sum is the margin. Kept apart as
|
|
116
|
+
* margins join, because summing at each join is not associative once the
|
|
117
|
+
* signs mix: 2, -4 and 14 collapse to 10, and taken two at a time to 12,
|
|
118
|
+
* and to 8 with a -4 after.
|
|
119
|
+
*/
|
|
120
|
+
export interface MarginStrut {
|
|
121
|
+
pos: number;
|
|
122
|
+
neg: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
47
125
|
/** A laid-out line inside an inline formatting context. */
|
|
48
126
|
export interface LineBox {
|
|
49
127
|
/** Content-box relative, resolved to document coordinates at paint. */
|
|
@@ -51,6 +129,8 @@ export interface LineBox {
|
|
|
51
129
|
y: number;
|
|
52
130
|
width: number;
|
|
53
131
|
height: number;
|
|
132
|
+
/** From the line's top — where a text engine's own lines carry theirs
|
|
133
|
+
* from the top of the layout. */
|
|
54
134
|
baseline: number;
|
|
55
135
|
/**
|
|
56
136
|
* The text on this line, as one or more fragments.
|
|
@@ -65,6 +145,16 @@ export interface LineBox {
|
|
|
65
145
|
textEnd: number;
|
|
66
146
|
/** Atomic items sitting on this line — images, inline-blocks, controls. */
|
|
67
147
|
atomics: AtomicPlacement[];
|
|
148
|
+
/** The inline boxes that open or close on this line, where their own
|
|
149
|
+
* margin, border and padding sit. */
|
|
150
|
+
edges?: EdgePlacement[];
|
|
151
|
+
/** A `::first-line` style with a background, painted behind the line's
|
|
152
|
+
* content over its face's height, as an inline box's is. */
|
|
153
|
+
background?: { style: ComputedStyle } & InlineDecoration;
|
|
154
|
+
/** How wide the floats placed at this line's top are, side by side: what
|
|
155
|
+
* stands beside its content, and so what its content is measured with
|
|
156
|
+
* where a box is as wide as its content (`intrinsicWidth`). */
|
|
157
|
+
floats?: number;
|
|
68
158
|
}
|
|
69
159
|
|
|
70
160
|
/**
|
|
@@ -93,12 +183,42 @@ export interface LineText {
|
|
|
93
183
|
/** Code-unit offset within the layout's own text that `textStart` maps to,
|
|
94
184
|
* so a document index can be turned into a caret index in this layout. */
|
|
95
185
|
layoutStart: number;
|
|
186
|
+
/** Where in the layout's text an inline box's edge was laid out as a
|
|
187
|
+
* spacer: a unit of the layout that is no text of the document's
|
|
188
|
+
* (`documentOffsetOf`, `layoutOffsetOf`). */
|
|
189
|
+
gaps?: number[];
|
|
190
|
+
/** Any offset in the layout's own text as a document index — how a run
|
|
191
|
+
* under the pointer finds the element whose text it is. Per pass: the
|
|
192
|
+
* layout may be one an earlier pass made (`TextLayoutCache`), and the
|
|
193
|
+
* document index is this parse's. */
|
|
194
|
+
spans: {
|
|
195
|
+
documentAt(offset: number): number;
|
|
196
|
+
/** The text box an offset's text is, where the layout knows. */
|
|
197
|
+
boxAt?(offset: number): Box | null;
|
|
198
|
+
};
|
|
96
199
|
}
|
|
97
200
|
|
|
98
201
|
export interface AtomicPlacement {
|
|
99
202
|
box: Box;
|
|
100
203
|
x: number;
|
|
101
204
|
y: number;
|
|
205
|
+
/** How far its `vertical-align` raises its baseline above its parent's,
|
|
206
|
+
* worked out as it joined the line; absent for none, and for `top` and
|
|
207
|
+
* `bottom`, which the line box's edges place instead. */
|
|
208
|
+
raise?: number;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Where an inline box's own margin, border and padding on one side sit on a
|
|
213
|
+
* line: CSS 2.1 puts the start side's before the box's first fragment and the
|
|
214
|
+
* end side's after its last, taking room on those lines (10.3.1). `x` is the
|
|
215
|
+
* edge's outer side, margin included, and `width` all three.
|
|
216
|
+
*/
|
|
217
|
+
export interface EdgePlacement {
|
|
218
|
+
box: Box;
|
|
219
|
+
side: 'start' | 'end';
|
|
220
|
+
x: number;
|
|
221
|
+
width: number;
|
|
102
222
|
}
|
|
103
223
|
|
|
104
224
|
/** The slice of ntk's `TextLayout` this renderer reads. Structural for the
|
|
@@ -167,6 +287,10 @@ export class Box {
|
|
|
167
287
|
width = 0;
|
|
168
288
|
height = 0;
|
|
169
289
|
|
|
290
|
+
/** For an inline box broken around a block in it (CSS 2.1 9.2.1.1), the
|
|
291
|
+
* sides it goes on from, which have no edge: 1 its start, 2 its end. */
|
|
292
|
+
cut = 0;
|
|
293
|
+
|
|
170
294
|
/** Resolved edges — border + padding, per side. */
|
|
171
295
|
borderTop = 0;
|
|
172
296
|
borderRight = 0;
|
|
@@ -180,6 +304,34 @@ export class Box {
|
|
|
180
304
|
marginRight = 0;
|
|
181
305
|
marginBottom = 0;
|
|
182
306
|
marginLeft = 0;
|
|
307
|
+
/** The margin that came out through the box's bottom edge from its last
|
|
308
|
+
* children, collapsed with its own bottom margin, which is their sum:
|
|
309
|
+
* kept as a strut for the next sibling's to collapse with. Null where
|
|
310
|
+
* none came out, which is most boxes. Set by the block layout. */
|
|
311
|
+
bottomStrut: MarginStrut | null = null;
|
|
312
|
+
/** An inline box with a background or a border to paint behind its
|
|
313
|
+
* fragments: the ascent and descent of its own face, the height CSS
|
|
314
|
+
* paints them over (10.6.1). Set by the inline layout. */
|
|
315
|
+
decoration: InlineDecoration | null = null;
|
|
316
|
+
/** What a percentage `height` resolves against: an absolutely positioned
|
|
317
|
+
* box's containing block's height, which is known before the box is laid
|
|
318
|
+
* out (CSS 2.1 10.5). NaN everywhere else, where that height depends on
|
|
319
|
+
* the content and a percentage is `auto`. */
|
|
320
|
+
percentHeightBase = NaN;
|
|
321
|
+
/** Whether this box last handed its children a percentage base that
|
|
322
|
+
* was a number: until it does, they hold NaN already, and the next
|
|
323
|
+
* pass need not visit them to say so again. */
|
|
324
|
+
gavePercentBase = false;
|
|
325
|
+
/** Whether this box's top margin collapsed through its parent's top edge
|
|
326
|
+
* and was spent placing the parent (CSS 2.1 8.3.1): its own layout puts
|
|
327
|
+
* it at the parent's content top, and applies no margin again. `2` when
|
|
328
|
+
* the box is empty and its bottom margin went the same way, so the next
|
|
329
|
+
* sibling's may have too; `3` for a new formatting context the margin
|
|
330
|
+
* stopped above, separated from the floats it carries, which sits where
|
|
331
|
+
* the margins before it end and applies none of its own. Set by the walk
|
|
332
|
+
* that places the parent, each pass, and read only while every sibling
|
|
333
|
+
* before it was a `2`. */
|
|
334
|
+
topAbsorbed: 0 | 1 | 2 | 3 = 0;
|
|
183
335
|
|
|
184
336
|
/** Text, for a `text` box: already whitespace-processed and transformed. */
|
|
185
337
|
text = '';
|
|
@@ -189,12 +341,17 @@ export class Box {
|
|
|
189
341
|
|
|
190
342
|
/** Lines, for a box that established an inline formatting context. */
|
|
191
343
|
lines: LineBox[] | null = null;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
344
|
+
/** The widest row of floats its lines placed beside none of them, side
|
|
345
|
+
* by side: what the box is measured with, as a line's floats are
|
|
346
|
+
* (`LineBox.floats`). */
|
|
347
|
+
floatRow = 0;
|
|
348
|
+
|
|
349
|
+
/** A replaced box's intrinsic size and ratio; null on every other box,
|
|
350
|
+
* which is nearly all of them, so it is one field rather than four. */
|
|
351
|
+
intrinsic: Intrinsic | null = null;
|
|
196
352
|
/**
|
|
197
|
-
* Cached min-/max-content widths, for a table cell
|
|
353
|
+
* Cached min-/max-content widths, for a table cell and for a box an
|
|
354
|
+
* intrinsic size (`width: fit-content`) sizes. -1 until measured.
|
|
198
355
|
*
|
|
199
356
|
* Intrinsic widths are width-independent by definition, so measuring them
|
|
200
357
|
* per layout pass was this engine breaking its own phase rule — a resize
|
|
@@ -205,18 +362,24 @@ export class Box {
|
|
|
205
362
|
*/
|
|
206
363
|
intrinsicMinContent = -1;
|
|
207
364
|
intrinsicMaxContent = -1;
|
|
365
|
+
/** Which layout of the pass this box last had, counted across all boxes:
|
|
366
|
+
* what tells a layout kept for reuse (`layoutOwn`) from one a probe has
|
|
367
|
+
* laid over since. */
|
|
368
|
+
layoutSerial = 0;
|
|
208
369
|
/** What a replaced box is: the resource seam and the control host both
|
|
209
370
|
* key on this rather than re-reading the tag. */
|
|
210
371
|
replaced: ReplacedKind = 'none';
|
|
211
372
|
|
|
212
|
-
/** The marker
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
373
|
+
/** The marker of a `list-item`, if it generated one. The marker is not a
|
|
374
|
+
* box: it is not in the flow, nothing can select it (CSS spells that
|
|
375
|
+
* `::marker`, and no author styles it here), and giving it one would put
|
|
376
|
+
* a bullet in every copied list. */
|
|
377
|
+
marker: Marker | null = null;
|
|
378
|
+
/** Which of an element's pseudo-elements this box is. Its `el` is null —
|
|
379
|
+
* it is no element — and its text box's is the element it hangs off, so
|
|
380
|
+
* a click on a link's generated text or first letter is a click on the
|
|
381
|
+
* link. */
|
|
382
|
+
pseudo: 'before' | 'after' | 'first-letter' | null = null;
|
|
220
383
|
|
|
221
384
|
/**
|
|
222
385
|
* The bounds of everything this box and its descendants draw, in document
|
|
@@ -247,6 +410,10 @@ export class Box {
|
|
|
247
410
|
/** Out-of-flow children in paint order (z-index, then document order),
|
|
248
411
|
* precomputed so a paint does not filter and sort per box per frame. */
|
|
249
412
|
positionedPaint: Box[] | null = null;
|
|
413
|
+
/** Whether a positioned box is somewhere under this one: what the pass
|
|
414
|
+
* that gives each stacking context its positioned boxes walks, so that
|
|
415
|
+
* a subtree with none is not walked at all (`stackLayers`). */
|
|
416
|
+
holdsLayers = false;
|
|
250
417
|
/** The tallest line box under this box — the slack a binary search over
|
|
251
418
|
* the y-sorted lines needs, since a line's bottom is not monotone. */
|
|
252
419
|
maxLineHeight = 0;
|
|
@@ -265,6 +432,21 @@ export class Box {
|
|
|
265
432
|
outOfFlow = false;
|
|
266
433
|
/** Set on a float, for the same reason. */
|
|
267
434
|
isFloat = false;
|
|
435
|
+
/** Where an out-of-flow box would have been in the flow it was taken
|
|
436
|
+
* from — its static position (CSS 2.1 10.3.7, 10.6.4). */
|
|
437
|
+
staticPosition: StaticPosition | null = null;
|
|
438
|
+
|
|
439
|
+
/** A table's captions, above and below it: they are in the box's height,
|
|
440
|
+
* and outside the table's own border and background (CSS 2.1 17.4). */
|
|
441
|
+
captionTop = 0;
|
|
442
|
+
captionBottom = 0;
|
|
443
|
+
/** A table whose borders collapse: the border each segment of its grid
|
|
444
|
+
* carries, resolved once per build (`collapseTable`). */
|
|
445
|
+
collapsed: CollapsedTable | null = null;
|
|
446
|
+
/** Set on a table whose borders collapse, and on its cells: their border
|
|
447
|
+
* widths are the halves the collapsing model leaves them, and the table
|
|
448
|
+
* paints the borders rather than the boxes. */
|
|
449
|
+
bordersCollapsed = false;
|
|
268
450
|
|
|
269
451
|
constructor(kind: BoxKind, el: Element | null, style: ComputedStyle) {
|
|
270
452
|
this.kind = kind;
|
|
@@ -323,7 +505,119 @@ export type ReplacedKind =
|
|
|
323
505
|
| 'button'
|
|
324
506
|
| 'checkbox'
|
|
325
507
|
| 'radio'
|
|
326
|
-
| 'hr'
|
|
508
|
+
| 'hr'
|
|
509
|
+
/** An `<iframe>`, `<video>`, `<embed>` or `<canvas>`: what it would
|
|
510
|
+
* show is never loaded or drawn, so it is a box of its size with nothing
|
|
511
|
+
* in it. */
|
|
512
|
+
| 'frame'
|
|
513
|
+
/** An inline `<svg>`: a drawing, sized by what it says of its size. */
|
|
514
|
+
| 'svg';
|
|
515
|
+
|
|
516
|
+
/** A replaced box's intrinsic dimensions, in device pixels. */
|
|
517
|
+
export interface Intrinsic {
|
|
518
|
+
/** Its size — or, on an axis `missing` names, the default object size of
|
|
519
|
+
* 300 by 150 CSS pixels that CSS falls back to there (CSS 2.1 10.3.2,
|
|
520
|
+
* 10.6.2). */
|
|
521
|
+
width: number;
|
|
522
|
+
height: number;
|
|
523
|
+
/** The dimensions it lacks: 1 its width, 2 its height. Only an SVG can. */
|
|
524
|
+
missing: number;
|
|
525
|
+
/** Its ratio, width over height, or 0 where it has none: an image's, or
|
|
526
|
+
* an SVG's from its size or its `viewBox`. */
|
|
527
|
+
ratio: number;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** The `::first-line` style of each block container a rule gives one to,
|
|
531
|
+
* kept beside the boxes: few documents have any (`BoxTree.firstLine`). */
|
|
532
|
+
export const FIRST_LINE = new WeakMap<Box, ComputedStyle>();
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* A box's style on the first line of the block it is in, given its
|
|
536
|
+
* parent's style there (CSS Pseudo 4, 2.1.2): an element's own rules over
|
|
537
|
+
* what it inherits on the line, which takes the properties `::first-line`
|
|
538
|
+
* applies to from `parent` and the rest from its parent's own style.
|
|
539
|
+
*/
|
|
540
|
+
export type FirstLineStyler = (
|
|
541
|
+
box: Box,
|
|
542
|
+
parent: ComputedStyle,
|
|
543
|
+
) => ComputedStyle;
|
|
544
|
+
|
|
545
|
+
/** The url of an image generated content put in a pseudo-element: it has
|
|
546
|
+
* no element of its own to name it (`imageUrlOf`). */
|
|
547
|
+
export const CONTENT_IMAGES = new WeakMap<Box, string>();
|
|
548
|
+
|
|
549
|
+
/** How far `position: relative` moved an inline box (`offsetInline`), how
|
|
550
|
+
* far each text on a line moved with the boxes it is in, and the lines
|
|
551
|
+
* holding one: beside the boxes and the lines, since few documents move an
|
|
552
|
+
* inline box. A moved text's `drawX` and `drawY` are where it is drawn;
|
|
553
|
+
* its box's decorations are worked out from where it was, and moved by
|
|
554
|
+
* their own box's offset. */
|
|
555
|
+
export const INLINE_OFFSETS = new WeakMap<Box, { x: number; y: number }>();
|
|
556
|
+
export const TEXT_SHIFTS = new WeakMap<LineText, { x: number; y: number }>();
|
|
557
|
+
export const SHIFTED_LINES = new WeakSet<LineBox>();
|
|
558
|
+
|
|
559
|
+
/** How far `vertical-align` raised a text above its line's baseline, and an
|
|
560
|
+
* inline box's own baseline, where it did: beside the lines and the boxes
|
|
561
|
+
* for the reason the offsets above are. A line holding a raised text is
|
|
562
|
+
* among `SHIFTED_LINES` too, as its text is off the line's baseline. */
|
|
563
|
+
export const TEXT_RAISES = new WeakMap<LineText, number>();
|
|
564
|
+
|
|
565
|
+
/** The blocks some of whose text casts a shadow (`text-shadow`), which the
|
|
566
|
+
* paint pass looks for in them and in no other. */
|
|
567
|
+
export const SHADOWED_TEXT = new WeakSet<Box>();
|
|
568
|
+
export const BOX_RAISES = new WeakMap<Box, number>();
|
|
569
|
+
|
|
570
|
+
/** And the raise, on one line, of each inline box a `top` or `bottom` box
|
|
571
|
+
* holds, the box itself among them: that box's baseline is where the
|
|
572
|
+
* line's edge puts it, which is a different height on every line, so its
|
|
573
|
+
* background, drawn from its baseline, is put there by line. */
|
|
574
|
+
export const LINE_BOX_RAISES = new WeakMap<LineBox, Map<Box, number>>();
|
|
575
|
+
|
|
576
|
+
/** The table cells wholly in columns `visibility: collapse` took out of
|
|
577
|
+
* their table (CSS 2.1 17.5.5), which the paint pass leaves out: a cell is
|
|
578
|
+
* no descendant of its column and inherits nothing from it, so its own
|
|
579
|
+
* style still says `visible`. */
|
|
580
|
+
export const COLLAPSED_CELLS = new WeakSet<Box>();
|
|
581
|
+
/** And the cells spanning into a column or a row taken out: laid out as
|
|
582
|
+
* they would have been, placed over what is left of their span, and
|
|
583
|
+
* clipped to it. */
|
|
584
|
+
export const CLIPPED_CELLS = new WeakSet<Box>();
|
|
585
|
+
|
|
586
|
+
/** The boxes after a line-clamp container's clamp point (CSS Overflow 4,
|
|
587
|
+
* 5.3.1): invisible, with all they hold, and no taller than nothing to
|
|
588
|
+
* the blocks around them. Marked by the layout of the flow they are in,
|
|
589
|
+
* which leaves them where the last layout put them, and passed over by
|
|
590
|
+
* paint and the paint bounds. */
|
|
591
|
+
export const CLAMPED = new WeakSet<Box>();
|
|
592
|
+
|
|
593
|
+
/** A grid's tracks as its layout left them, each column's and each row's
|
|
594
|
+
* start and end from the content box's corner, with the lines of each
|
|
595
|
+
* axis by number and by name: what an absolutely positioned box's grid
|
|
596
|
+
* area is found in (CSS Grid 1, 9.1). */
|
|
597
|
+
export interface GridTracks {
|
|
598
|
+
cols: [number, number][];
|
|
599
|
+
rows: [number, number][];
|
|
600
|
+
colLines: GridLines;
|
|
601
|
+
rowLines: GridLines;
|
|
602
|
+
}
|
|
603
|
+
export const GRID_TRACKS = new WeakMap<Box, GridTracks>();
|
|
604
|
+
|
|
605
|
+
/** A flex box's children in the order it lays them out and paints them,
|
|
606
|
+
* `order` first and the document's after it (CSS Flexbox 5.4), where
|
|
607
|
+
* `order` moves any: an absolutely positioned child's is 0. */
|
|
608
|
+
export const PAINT_ORDER = new WeakMap<Box, Box[]>();
|
|
609
|
+
|
|
610
|
+
/** The blocks that broke a relatively positioned inline box in pieces,
|
|
611
|
+
* under its first piece: its offset moves them too (CSS 2.1 9.2.1.1),
|
|
612
|
+
* though they stand outside it (`breakAround`). */
|
|
613
|
+
export const CUT_BLOCKS = new WeakMap<Box, Box[]>();
|
|
614
|
+
|
|
615
|
+
/** The blocks that broke an inline box under full opacity in pieces, and
|
|
616
|
+
* the opacity they take from it — from each such inline box around them,
|
|
617
|
+
* multiplied. They stand outside it (CSS 2.1 9.2.1.1) and are still its
|
|
618
|
+
* content, which its opacity fades as a group: a green square in a
|
|
619
|
+
* `<span style="opacity: .5">` was drawn at full strength. */
|
|
620
|
+
export const FADED_BLOCKS = new WeakMap<Box, number>();
|
|
327
621
|
|
|
328
622
|
/** What the builder produced, plus the document-wide text it indexed. */
|
|
329
623
|
export interface BoxTree {
|
|
@@ -333,10 +627,49 @@ export interface BoxTree {
|
|
|
333
627
|
text: string;
|
|
334
628
|
/** Text boxes in document order — the selection binary-searches this. */
|
|
335
629
|
textBoxes: Box[];
|
|
630
|
+
/** The styles text is set in, each once: the faces a host warms before
|
|
631
|
+
* laying the text out, found here rather than by walking `textBoxes`
|
|
632
|
+
* again after every build. */
|
|
633
|
+
textStyles: Set<ComputedStyle>;
|
|
634
|
+
/** Each element the build gave a style, with it and whether its parent
|
|
635
|
+
* was a flex container: what a pointer move restyles an element from
|
|
636
|
+
* where it did not change the rest (`HtmlViewNode._hoverInPlace`). */
|
|
637
|
+
styles: Map<Element, { style: ComputedStyle; inFlex: boolean }>;
|
|
336
638
|
/** Every replaced box that needs a real widget, in document order. */
|
|
337
639
|
controls: Box[];
|
|
338
640
|
/** Every box carrying an `href`, for click and hover. */
|
|
339
641
|
links: Box[];
|
|
642
|
+
/** Every element's box with a `background-image` or a border image,
|
|
643
|
+
* for the host to be asked for: a document has a handful, and finding
|
|
644
|
+
* them was a walk over every box after every build. */
|
|
645
|
+
backgrounds: Box[];
|
|
646
|
+
/** Every image generated content names, the element whose
|
|
647
|
+
* pseudo-element names it, for the host to be asked for, and whether its
|
|
648
|
+
* size was known when the box was built. */
|
|
649
|
+
contentImages: { url: string; element: Element; sized: boolean }[];
|
|
650
|
+
/** Whether any box is relatively positioned: where none is, layout skips
|
|
651
|
+
* the walk that moves them. */
|
|
652
|
+
relative: boolean;
|
|
653
|
+
/** Whether any positioned box has a negative `z-index`: where none has,
|
|
654
|
+
* paint has no layer below the flow to find. */
|
|
655
|
+
negative: boolean;
|
|
656
|
+
/** Whether any block has a `::first-line` style (`FIRST_LINE`): where
|
|
657
|
+
* none has, layout looks for none. */
|
|
658
|
+
firstLine: boolean;
|
|
659
|
+
/** Each box's style on a first line (`FirstLineStyler`), where any
|
|
660
|
+
* block has a `::first-line`. */
|
|
661
|
+
firstLineStyler: FirstLineStyler | null;
|
|
662
|
+
/** Whether a float or an out-of-flow box sits in an inline box: where
|
|
663
|
+
* none does, layout looks for them among a block's own children and
|
|
664
|
+
* goes through no inline box to find them. */
|
|
665
|
+
nestedOutOfLine: boolean;
|
|
666
|
+
/** Whether `position: relative` moves an inline box or `vertical-align`
|
|
667
|
+
* raises one: where none does, no text is off its line's baseline, and
|
|
668
|
+
* the bounds walk looks for none. */
|
|
669
|
+
movedInline: boolean;
|
|
670
|
+
/** Whether any box paints its background through its text
|
|
671
|
+
* (`background-clip: text`): where none does, no paragraph looks. */
|
|
672
|
+
clipText: boolean;
|
|
340
673
|
}
|
|
341
674
|
|
|
342
675
|
export interface BuildOptions {
|
|
@@ -348,7 +681,9 @@ export interface BuildOptions {
|
|
|
348
681
|
/** Intrinsic size for an image the host has already loaded, in the
|
|
349
682
|
* image's own pixels. `null` when it has not: the box takes the attribute
|
|
350
683
|
* size, or a placeholder. */
|
|
351
|
-
imageSize(el: Element):
|
|
684
|
+
imageSize(el: Element): IntrinsicSize | null;
|
|
685
|
+
/** The same for an image named by url — generated content's. */
|
|
686
|
+
urlSize?(url: string): IntrinsicSize | null;
|
|
352
687
|
/** The size a real widget wants, so the box in the flow is the size the
|
|
353
688
|
* control will be drawn at. */
|
|
354
689
|
controlSize(
|
|
@@ -377,18 +712,60 @@ export function buildBoxes(
|
|
|
377
712
|
* output, a runaway template) from a stack overflow five phases later.
|
|
378
713
|
* Blink's parser flattens at 512 for the same reason; content past the cap
|
|
379
714
|
* is dropped, which beats the alternative of crashing the application.
|
|
715
|
+
* The parser keeps a document to 256 elements (`dom.ts`), so what reaches
|
|
716
|
+
* this now is the anonymous boxes a table builds round each of them.
|
|
380
717
|
*/
|
|
381
718
|
const MAX_DEPTH = 512;
|
|
382
719
|
|
|
720
|
+
/** The sharing key the root box's children share under: one root style per
|
|
721
|
+
* build, and the shared styles last a build (`Cascade.beginSharing`). */
|
|
722
|
+
const ROOT_SHARE_KEY = 0;
|
|
723
|
+
|
|
383
724
|
class Builder {
|
|
384
725
|
private _options: BuildOptions;
|
|
385
|
-
|
|
726
|
+
/** The document text, in the pieces it was pushed in — joined once at the
|
|
727
|
+
* end, so that taking back a line's last space is not a copy of it. */
|
|
728
|
+
private _chunks: string[] = [];
|
|
729
|
+
private _length = 0;
|
|
386
730
|
private _textBoxes: Box[] = [];
|
|
731
|
+
private _textStyles = new Set<ComputedStyle>();
|
|
732
|
+
private _styles = new Map<
|
|
733
|
+
Element,
|
|
734
|
+
{ style: ComputedStyle; inFlex: boolean }
|
|
735
|
+
>();
|
|
736
|
+
/** The last text box's style: runs of text share their parent's, so
|
|
737
|
+
* most text boxes repeat it and add nothing to `_textStyles`. */
|
|
738
|
+
private _lastTextStyle: ComputedStyle | null = null;
|
|
387
739
|
private _controls: Box[] = [];
|
|
388
740
|
private _links: Box[] = [];
|
|
389
|
-
|
|
390
|
-
private
|
|
741
|
+
private _backgrounds: Box[] = [];
|
|
742
|
+
private _contentImages: {
|
|
743
|
+
url: string;
|
|
744
|
+
element: Element;
|
|
745
|
+
sized: boolean;
|
|
746
|
+
}[] = [];
|
|
747
|
+
private _relative = false;
|
|
748
|
+
private _negative = false;
|
|
749
|
+
private _firstLine = false;
|
|
750
|
+
private _nestedOutOfLine = false;
|
|
751
|
+
private _movedInline = false;
|
|
752
|
+
private _clipText = false;
|
|
753
|
+
/** The CSS counters in scope, for `counter()` in generated content. */
|
|
754
|
+
private _scopes = new CounterScopes();
|
|
755
|
+
/** How many quotes generated content has opened and not closed. */
|
|
756
|
+
private _quoteDepth = 0;
|
|
757
|
+
/** Where the inline content being built stands, for collapsing white
|
|
758
|
+
* space across element boundaries. */
|
|
759
|
+
private _ws: Collapse = 'start';
|
|
760
|
+
/** Whether the word the inline content being built is in has had its
|
|
761
|
+
* first letter, for `text-transform: capitalize`: a word runs on across
|
|
762
|
+
* element boundaries, as white space collapses across them. */
|
|
763
|
+
private _lettered = false;
|
|
391
764
|
private _depth = 0;
|
|
765
|
+
/** The `::first-letter` whose letter is still to come, for the block
|
|
766
|
+
* container whose first line has not begun. Null when there is none,
|
|
767
|
+
* and once anything but a letter begins that line. */
|
|
768
|
+
private _firstLetter: LetterSearch | null = null;
|
|
392
769
|
|
|
393
770
|
constructor(options: BuildOptions) {
|
|
394
771
|
this._options = options;
|
|
@@ -396,20 +773,68 @@ class Builder {
|
|
|
396
773
|
|
|
397
774
|
run(root: Element): BoxTree {
|
|
398
775
|
const cascade = this._options.cascade;
|
|
399
|
-
|
|
776
|
+
cascade.beginSharing();
|
|
777
|
+
const rootStyle = cascade.rootStyle(hasBody(root), hasHtml(root));
|
|
400
778
|
const rootBox = new Box('block', null, rootStyle);
|
|
779
|
+
// a fragment's root stands in for a `<body>`, counters and all
|
|
780
|
+
this._scopes.open();
|
|
781
|
+
this._counterChanges(rootStyle, {
|
|
782
|
+
node: root,
|
|
783
|
+
style: rootStyle,
|
|
784
|
+
key: ROOT_SHARE_KEY,
|
|
785
|
+
parent: null,
|
|
786
|
+
});
|
|
401
787
|
// The DOM's `<html>`/`<body>` are ordinary elements with ordinary styles;
|
|
402
788
|
// the box above them exists only to be the initial containing block, so
|
|
403
789
|
// it carries no margins of its own and cannot collapse with anything.
|
|
404
|
-
this._children(root, rootBox, rootStyle, false);
|
|
405
|
-
|
|
790
|
+
this._children(root, rootBox, rootStyle, false, null, ROOT_SHARE_KEY);
|
|
791
|
+
this._endLine();
|
|
792
|
+
fixUp(rootBox, anonymousStyles(cascade.initial));
|
|
406
793
|
assignSubtreeRanges(rootBox);
|
|
407
794
|
return {
|
|
408
795
|
root: rootBox,
|
|
409
|
-
text: this.
|
|
796
|
+
text: this._chunks.join(''),
|
|
410
797
|
textBoxes: this._textBoxes,
|
|
798
|
+
textStyles: this._textStyles,
|
|
799
|
+
styles: this._styles,
|
|
411
800
|
controls: this._controls,
|
|
412
801
|
links: this._links,
|
|
802
|
+
backgrounds: this._backgrounds,
|
|
803
|
+
contentImages: this._contentImages,
|
|
804
|
+
relative: this._relative || isRelative(rootStyle),
|
|
805
|
+
negative: this._negative,
|
|
806
|
+
firstLine: this._firstLine,
|
|
807
|
+
firstLineStyler: this._firstLine ? this._firstLineStyler() : null,
|
|
808
|
+
nestedOutOfLine: this._nestedOutOfLine,
|
|
809
|
+
movedInline: this._movedInline,
|
|
810
|
+
clipText: this._clipText,
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/** `BoxTree.firstLineStyler`, over this build's cascade. */
|
|
815
|
+
private _firstLineStyler(): FirstLineStyler {
|
|
816
|
+
const cascade = this._options.cascade;
|
|
817
|
+
const styles = this._styles;
|
|
818
|
+
return (box, parent) => {
|
|
819
|
+
const up = box.parent?.style ?? box.style;
|
|
820
|
+
const inherits = firstLineParent(up, parent);
|
|
821
|
+
const el = box.kind === 'text' ? null : box.el;
|
|
822
|
+
const own = el ? styles.get(el) : undefined;
|
|
823
|
+
if (el && own && own.style === box.style) {
|
|
824
|
+
return cascade.styleFor(el, inherits, own.inFlex);
|
|
825
|
+
}
|
|
826
|
+
// text, an anonymous box, a pseudo-element's: no rules of its own to
|
|
827
|
+
// be asked again, so what it took from its parent it takes from the
|
|
828
|
+
// parent's style on the line, and what it set it keeps
|
|
829
|
+
const out = copyStyle(box.style);
|
|
830
|
+
const to = out as unknown as Record<string, unknown>;
|
|
831
|
+
const mine = box.style as unknown as Record<string, unknown>;
|
|
832
|
+
const theirs = up as unknown as Record<string, unknown>;
|
|
833
|
+
const line = inherits as unknown as Record<string, unknown>;
|
|
834
|
+
for (const name of FIRST_LINE_INHERITED) {
|
|
835
|
+
if (mine[name] === theirs[name]) to[name] = line[name];
|
|
836
|
+
}
|
|
837
|
+
return out;
|
|
413
838
|
};
|
|
414
839
|
}
|
|
415
840
|
|
|
@@ -419,15 +844,33 @@ class Builder {
|
|
|
419
844
|
into: Box,
|
|
420
845
|
parentStyle: ComputedStyle,
|
|
421
846
|
inFlex: boolean,
|
|
422
|
-
owner: Element | null
|
|
847
|
+
owner: Element | null,
|
|
848
|
+
parentKey: number,
|
|
423
849
|
): void {
|
|
850
|
+
// CSS 2.1 17.2.1: a column group holds columns, and anything else in it
|
|
851
|
+
// is not rendered
|
|
852
|
+
const onlyColumns = parentStyle.display === 'table-column-group';
|
|
853
|
+
// A closed `<details>` shows its first `<summary>` and nothing else:
|
|
854
|
+
// HTML renders the rest into a slot that is out of the box tree until
|
|
855
|
+
// the element is `open`.
|
|
856
|
+
const closed =
|
|
857
|
+
owner !== null &&
|
|
858
|
+
tagOf(owner) === 'details' &&
|
|
859
|
+
owner.attribs.open === undefined;
|
|
860
|
+
let summarised = false;
|
|
424
861
|
for (const child of childrenOf(node as Element)) {
|
|
862
|
+
if (closed) {
|
|
863
|
+
if (!isElement(child) || summarised || tagOf(child) !== 'summary') {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
summarised = true;
|
|
867
|
+
}
|
|
425
868
|
if (isText(child)) {
|
|
426
|
-
this._textNode(child.data, into, parentStyle, owner);
|
|
869
|
+
if (!onlyColumns) this._textNode(child.data, into, parentStyle, owner);
|
|
427
870
|
continue;
|
|
428
871
|
}
|
|
429
872
|
if (!isElement(child)) continue;
|
|
430
|
-
this._element(child, into, parentStyle, inFlex);
|
|
873
|
+
this._element(child, into, parentStyle, inFlex, parentKey, onlyColumns);
|
|
431
874
|
}
|
|
432
875
|
}
|
|
433
876
|
|
|
@@ -436,23 +879,116 @@ class Builder {
|
|
|
436
879
|
into: Box,
|
|
437
880
|
parentStyle: ComputedStyle,
|
|
438
881
|
inFlex: boolean,
|
|
882
|
+
parentKey: number,
|
|
883
|
+
onlyColumns = false,
|
|
439
884
|
): void {
|
|
440
885
|
const tag = tagOf(el);
|
|
441
|
-
if (NON_RENDERED.has(tag)) return;
|
|
442
|
-
|
|
443
|
-
|
|
886
|
+
if (NON_RENDERED.has(tag) || inImpliedHead(el, tag)) return;
|
|
887
|
+
|
|
888
|
+
// shared with every element that must compute the same style, which in
|
|
889
|
+
// a long document is most of them (`Cascade.sharedStyleFor`)
|
|
890
|
+
const { style, key } = this._options.cascade.sharedStyleFor(
|
|
891
|
+
el,
|
|
892
|
+
parentStyle,
|
|
893
|
+
parentKey,
|
|
894
|
+
inFlex,
|
|
895
|
+
);
|
|
896
|
+
this._styles.set(el, { style, inFlex });
|
|
444
897
|
if (style.display === 'none') return;
|
|
898
|
+
if (onlyColumns && style.display !== 'table-column') return;
|
|
899
|
+
// what an element counts is in scope for it and what it holds, and for
|
|
900
|
+
// what follows it where its parent has no such counter
|
|
901
|
+
// (`CounterScopes`)
|
|
902
|
+
this._scopes.open();
|
|
903
|
+
// style containment keeps what the element's subtree does to counters
|
|
904
|
+
// and quotes in it (CSS Containment 2, 3.4)
|
|
905
|
+
const contains =
|
|
906
|
+
(style.contain & CONTAIN_STYLE) !== 0 && style.display !== 'contents';
|
|
907
|
+
const quoteDepth = this._quoteDepth;
|
|
908
|
+
if (contains) this._scopes.contain();
|
|
909
|
+
this._elementIn(el, tag, style, key, into, inFlex, {
|
|
910
|
+
node: el,
|
|
911
|
+
style,
|
|
912
|
+
key,
|
|
913
|
+
parent: { style: parentStyle, key: parentKey, inFlex },
|
|
914
|
+
});
|
|
915
|
+
if (contains) this._quoteDepth = quoteDepth;
|
|
916
|
+
this._scopes.close();
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
private _elementIn(
|
|
920
|
+
el: Element,
|
|
921
|
+
tag: string,
|
|
922
|
+
style: ComputedStyle,
|
|
923
|
+
key: number,
|
|
924
|
+
into: Box,
|
|
925
|
+
inFlex: boolean,
|
|
926
|
+
place: CounterPlace,
|
|
927
|
+
): void {
|
|
928
|
+
if (style.display === 'contents') {
|
|
929
|
+
// no box of its own (CSS Display 3, 2.5): its `::before`, its
|
|
930
|
+
// children and its `::after` are its parent's, in its style — the
|
|
931
|
+
// items of a flex row still, through a wrapper Tailwind's `contents`
|
|
932
|
+
// takes out — and a replaced element, which has nothing to hand on,
|
|
933
|
+
// is not rendered
|
|
934
|
+
if (tag === 'br' || replacedKind(el, tag) !== 'none') return;
|
|
935
|
+
this._counterChanges(style, place);
|
|
936
|
+
this._pseudo(el, 'before', style, into);
|
|
937
|
+
this._children(el, into, style, inFlex, el, key);
|
|
938
|
+
this._pseudo(el, 'after', style, into);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (isRelative(style)) {
|
|
942
|
+
this._relative = true;
|
|
943
|
+
if (style.display === 'inline' && isOffset(style)) {
|
|
944
|
+
this._movedInline = true;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
// a translation is moved by the same pass
|
|
948
|
+
if (style.translate || style.transformTranslate) this._relative = true;
|
|
949
|
+
if (style.backgroundClipText) this._clipText = true;
|
|
950
|
+
if (style.verticalAlign !== 'baseline' && style.display === 'inline') {
|
|
951
|
+
this._movedInline = true;
|
|
952
|
+
}
|
|
953
|
+
if (isNegative(style)) this._negative = true;
|
|
954
|
+
// before anything else of the element's, including its `::before`,
|
|
955
|
+
// and for the element whatever box it makes (CSS 2.1 12.4)
|
|
956
|
+
this._counterChanges(style, place);
|
|
445
957
|
|
|
446
958
|
// `<br>` is a line break rather than a box, and it is the one element
|
|
447
959
|
// whose *absence* of a box still has to reach the inline layout.
|
|
448
960
|
if (tag === 'br') {
|
|
961
|
+
this._endLine();
|
|
962
|
+
// the first line ends with no letter on it (CSS 2.1 5.12.2)
|
|
963
|
+
this._abandonLetter();
|
|
964
|
+
if (style.clear !== 'none') {
|
|
965
|
+
// a break that clears the floats — `<br clear="all">` after a
|
|
966
|
+
// floated image — puts what follows it below them: an empty block
|
|
967
|
+
// that clears them, which the inline content around it is broken
|
|
968
|
+
// for (`breakInlines`), where a line break went on beside them
|
|
969
|
+
const clearing = copyStyle(style);
|
|
970
|
+
clearing.display = 'block';
|
|
971
|
+
into.append(new Box('block', el, clearing));
|
|
972
|
+
this._ws = 'start';
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
449
975
|
const box = new Box('break', el, style);
|
|
450
976
|
into.append(box);
|
|
451
977
|
this._push('\n', box);
|
|
978
|
+
this._ws = 'start';
|
|
452
979
|
return;
|
|
453
980
|
}
|
|
454
981
|
|
|
455
|
-
|
|
982
|
+
// an `<object>` is its image once it has one, and its content until;
|
|
983
|
+
// an `<embed>` its image, and a `<video>` its poster, or a frame
|
|
984
|
+
const replaced =
|
|
985
|
+
tag === 'object'
|
|
986
|
+
? this._options.imageSize(el)
|
|
987
|
+
? 'image'
|
|
988
|
+
: 'none'
|
|
989
|
+
: (tag === 'embed' || tag === 'video') && this._options.imageSize(el)
|
|
990
|
+
? 'image'
|
|
991
|
+
: replacedKind(el, tag);
|
|
456
992
|
if (replaced !== 'none') {
|
|
457
993
|
this._replaced(el, tag, replaced, style, into);
|
|
458
994
|
return;
|
|
@@ -462,29 +998,137 @@ class Builder {
|
|
|
462
998
|
const kind = boxKindFor(style.display);
|
|
463
999
|
const box = new Box(kind, el, style);
|
|
464
1000
|
into.append(box);
|
|
1001
|
+
if (
|
|
1002
|
+
(kind === 'block' || kind === 'table-cell' || kind === 'table-caption') &&
|
|
1003
|
+
this._options.cascade.hasFirstLine
|
|
1004
|
+
) {
|
|
1005
|
+
const firstLine = this._options.cascade.firstLineStyle(el, style);
|
|
1006
|
+
if (firstLine) {
|
|
1007
|
+
FIRST_LINE.set(box, firstLine);
|
|
1008
|
+
this._firstLine = true;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (
|
|
1012
|
+
style.backgroundImage ||
|
|
1013
|
+
style.backgroundImages ||
|
|
1014
|
+
typeof style.borderImage.source === 'string'
|
|
1015
|
+
) {
|
|
1016
|
+
this._backgrounds.push(box);
|
|
1017
|
+
}
|
|
465
1018
|
if (style.position === 'absolute' || style.position === 'fixed')
|
|
466
1019
|
box.outOfFlow = true;
|
|
467
1020
|
else if (style.float !== 'none') box.isFloat = true;
|
|
1021
|
+
if (into.kind === 'inline' && (box.outOfFlow || box.isFloat))
|
|
1022
|
+
this._nestedOutOfLine = true;
|
|
468
1023
|
|
|
469
1024
|
if (attr(el, 'href') && (tag === 'a' || tag === 'area'))
|
|
470
1025
|
this._links.push(box);
|
|
471
1026
|
|
|
472
|
-
|
|
473
|
-
|
|
1027
|
+
// a column's content is not rendered at all (CSS 2.1 17.2.1)
|
|
1028
|
+
if (style.display === 'table-column') return;
|
|
1029
|
+
|
|
1030
|
+
let insideMarker: string | null = null;
|
|
1031
|
+
let markerStyle: ComputedStyle | null = null;
|
|
1032
|
+
let ownMarker = false;
|
|
1033
|
+
let markerImage: { url: string; size: IntrinsicSize } | null = null;
|
|
1034
|
+
let insideImage: string | null = null;
|
|
1035
|
+
if (style.display === 'list-item' && style.listStyleImage) {
|
|
1036
|
+
// asked for as generated content's images are, and the tree built
|
|
1037
|
+
// again when it arrives; until it has, and where it never does, the
|
|
1038
|
+
// item's marker is its `list-style-type`'s
|
|
1039
|
+
const url = style.listStyleImage;
|
|
1040
|
+
const size = this._options.urlSize?.(url) ?? null;
|
|
1041
|
+
this._contentImages.push({ url, element: el, sized: !!size });
|
|
1042
|
+
if (size) markerImage = { url, size };
|
|
474
1043
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
1044
|
+
if (style.display === 'list-item') {
|
|
1045
|
+
// the item's number, the `list-item` counter it has just counted
|
|
1046
|
+
const written = markerFor(
|
|
1047
|
+
style,
|
|
1048
|
+
this._scopes.value('list-item'),
|
|
1049
|
+
this._options.cascade.counterStyles,
|
|
1050
|
+
);
|
|
1051
|
+
let text = written.text;
|
|
1052
|
+
let flush = written.flush;
|
|
1053
|
+
markerStyle = this._options.cascade.markerStyle(el, style);
|
|
1054
|
+
// a `::marker` with a `content` is set as that, as it is written —
|
|
1055
|
+
// its strings and its counters, where one of `none` is no marker
|
|
1056
|
+
const content = markerStyle?.content;
|
|
1057
|
+
if (content === 'none') text = '';
|
|
1058
|
+
else if (Array.isArray(content) && markerStyle) {
|
|
1059
|
+
text = this._generated(content, markerStyle, el)
|
|
1060
|
+
.filter((piece) => typeof piece === 'string')
|
|
1061
|
+
.join('');
|
|
1062
|
+
ownMarker = true;
|
|
1063
|
+
flush = true;
|
|
1064
|
+
} else if (style.listStyleType.startsWith('"')) {
|
|
1065
|
+
ownMarker = true;
|
|
1066
|
+
flush = true;
|
|
1067
|
+
}
|
|
1068
|
+
if (markerImage && !ownMarker) {
|
|
1069
|
+
if (style.listStylePosition === 'inside') insideImage = markerImage.url;
|
|
1070
|
+
else {
|
|
1071
|
+
const scale = this._options.scale ?? 1;
|
|
1072
|
+
box.marker = {
|
|
1073
|
+
text: '',
|
|
1074
|
+
layout: null,
|
|
1075
|
+
x: 0,
|
|
1076
|
+
y: 0,
|
|
1077
|
+
style: markerStyle,
|
|
1078
|
+
image: {
|
|
1079
|
+
url: markerImage.url,
|
|
1080
|
+
width: (markerImage.size.width ?? 0) * scale,
|
|
1081
|
+
height: (markerImage.size.height ?? 0) * scale,
|
|
1082
|
+
},
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
} else if (text && style.listStylePosition === 'inside') {
|
|
1086
|
+
insideMarker = flush ? text : `${text} `;
|
|
1087
|
+
} else if (text) {
|
|
1088
|
+
box.marker = { text, layout: null, x: 0, y: 0, style: markerStyle };
|
|
1089
|
+
if (flush) box.marker.flush = true;
|
|
1090
|
+
}
|
|
479
1091
|
}
|
|
480
|
-
|
|
481
1092
|
const childInFlex =
|
|
482
1093
|
style.display === 'flex' || style.display === 'inline-flex';
|
|
1094
|
+
const flow = flowOf(style, box);
|
|
1095
|
+
const around = this._ws;
|
|
1096
|
+
const aroundWord = this._lettered;
|
|
1097
|
+
if (flow === 'block') this._endLine();
|
|
1098
|
+
if (flow !== 'inline') this._ws = 'start';
|
|
1099
|
+
// A first letter is looked for in the first line of a block container,
|
|
1100
|
+
// down through its inline content and its first blocks. A float, a
|
|
1101
|
+
// positioned box and a flex container are no part of that line, and
|
|
1102
|
+
// an atomic inline is something other than a letter at its start
|
|
1103
|
+
// (CSS 2.1 5.12.2). A block container with rules of its own takes the
|
|
1104
|
+
// search over.
|
|
1105
|
+
const outerLetter = this._firstLetter;
|
|
1106
|
+
const skipped = flow === 'out' || flow === 'atomic' || kind === 'flex';
|
|
1107
|
+
if (skipped) this._firstLetter = null;
|
|
1108
|
+
// a block starts a line, and punctuation before it was on another
|
|
1109
|
+
else if (flow === 'block' && outerLetter) giveBack(outerLetter);
|
|
1110
|
+
const ownRules = hasFirstLetter(style.display)
|
|
1111
|
+
? this._options.cascade.firstLetterRules(el)
|
|
1112
|
+
: null;
|
|
1113
|
+
const ownLetter = ownRules ? { rules: ownRules, punctuation: [] } : null;
|
|
1114
|
+
if (ownLetter) this._firstLetter = ownLetter;
|
|
483
1115
|
this._depth += 1;
|
|
484
|
-
|
|
1116
|
+
if (insideImage) {
|
|
1117
|
+
// an inline image at the start of the first line, as a generated
|
|
1118
|
+
// image is, and the space a marker's text ends in
|
|
1119
|
+
this._contentImage(insideImage, box, style, el);
|
|
1120
|
+
this._textNode(' ', box, style, el);
|
|
1121
|
+
} else if (insideMarker) {
|
|
1122
|
+
this._insideMarker(insideMarker, style, markerStyle, box, el);
|
|
1123
|
+
}
|
|
1124
|
+
this._pseudo(el, 'before', style, box);
|
|
1125
|
+
this._children(el, box, style, childInFlex, el, key);
|
|
1126
|
+
this._pseudo(el, 'after', style, box);
|
|
485
1127
|
this._depth -= 1;
|
|
486
|
-
|
|
487
|
-
if (
|
|
1128
|
+
this._letterAfter(flow, skipped, outerLetter, ownLetter);
|
|
1129
|
+
if (flow !== 'inline') this._endLine();
|
|
1130
|
+
this._ws = after(flow, this._ws, around);
|
|
1131
|
+
this._lettered = letteredAfterFlow(flow, this._lettered, aroundWord);
|
|
488
1132
|
}
|
|
489
1133
|
|
|
490
1134
|
private _replaced(
|
|
@@ -497,9 +1141,23 @@ class Builder {
|
|
|
497
1141
|
const box = new Box('replaced', el, style);
|
|
498
1142
|
box.replaced = replaced;
|
|
499
1143
|
into.append(box);
|
|
1144
|
+
if (
|
|
1145
|
+
style.backgroundImage ||
|
|
1146
|
+
style.backgroundImages ||
|
|
1147
|
+
typeof style.borderImage.source === 'string'
|
|
1148
|
+
) {
|
|
1149
|
+
this._backgrounds.push(box);
|
|
1150
|
+
}
|
|
500
1151
|
if (style.position === 'absolute' || style.position === 'fixed')
|
|
501
1152
|
box.outOfFlow = true;
|
|
502
1153
|
else if (style.float !== 'none') box.isFloat = true;
|
|
1154
|
+
if (into.kind === 'inline' && (box.outOfFlow || box.isFloat))
|
|
1155
|
+
this._nestedOutOfLine = true;
|
|
1156
|
+
const flow = flowOf(style, box);
|
|
1157
|
+
if (flow === 'block') this._endLine();
|
|
1158
|
+
this._ws = after(flow, this._ws, this._ws);
|
|
1159
|
+
this._lettered = letteredAfterFlow(flow, this._lettered, this._lettered);
|
|
1160
|
+
if (flow === 'atomic') this._abandonLetter();
|
|
503
1161
|
|
|
504
1162
|
if (replaced === 'image') {
|
|
505
1163
|
// Both sources are CSS pixels — an image pixel is one, and so is an
|
|
@@ -507,15 +1165,20 @@ class Builder {
|
|
|
507
1165
|
const scale = this._options.scale ?? 1;
|
|
508
1166
|
const loaded = this._options.imageSize(el);
|
|
509
1167
|
if (loaded) {
|
|
510
|
-
box
|
|
511
|
-
box.intrinsicHeight = loaded.height * scale;
|
|
1168
|
+
setIntrinsics(box, loaded, scale);
|
|
512
1169
|
} else {
|
|
513
1170
|
// An image that has not arrived still needs a box, or the document
|
|
514
1171
|
// reflows under the reader when it does. The attributes are the
|
|
515
1172
|
// author telling us the size in advance; without them the box is a
|
|
516
1173
|
// small placeholder rather than nothing.
|
|
517
|
-
|
|
518
|
-
|
|
1174
|
+
const width = numberAttr(el, 'width') ?? 0;
|
|
1175
|
+
const height = numberAttr(el, 'height') ?? 0;
|
|
1176
|
+
box.intrinsic = {
|
|
1177
|
+
width: width * scale,
|
|
1178
|
+
height: height * scale,
|
|
1179
|
+
missing: 0,
|
|
1180
|
+
ratio: width > 0 && height > 0 ? width / height : 0,
|
|
1181
|
+
};
|
|
519
1182
|
}
|
|
520
1183
|
// The alt text joins the document text, so a document read with the
|
|
521
1184
|
// images blocked still copies as prose.
|
|
@@ -525,16 +1188,434 @@ class Builder {
|
|
|
525
1188
|
}
|
|
526
1189
|
|
|
527
1190
|
if (replaced === 'hr') return;
|
|
1191
|
+
if (replaced === 'svg') {
|
|
1192
|
+
// its `em` is its own font size, in CSS pixels like the rest of it
|
|
1193
|
+
const scale = this._options.scale ?? 1;
|
|
1194
|
+
setIntrinsics(box, svgIntrinsics(el, style.fontSize / scale), scale);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (replaced === 'frame') {
|
|
1198
|
+
const scale = this._options.scale ?? 1;
|
|
1199
|
+
if (tag === 'canvas') {
|
|
1200
|
+
// A canvas is the size of its bitmap, which its `width` and
|
|
1201
|
+
// `height` attributes give, 300 by 150 where they do not, and
|
|
1202
|
+
// keeps its proportions (HTML 4.12.5): no script draws in it, so it
|
|
1203
|
+
// is that box with nothing in it. Taken for an element with no box
|
|
1204
|
+
// of its own, it took no room, and one set a height kept no width.
|
|
1205
|
+
const width = canvasSize(el, 'width', 300);
|
|
1206
|
+
const height = canvasSize(el, 'height', 150);
|
|
1207
|
+
box.intrinsic = {
|
|
1208
|
+
width: width * scale,
|
|
1209
|
+
height: height * scale,
|
|
1210
|
+
missing: 0,
|
|
1211
|
+
ratio: width > 0 && height > 0 ? width / height : 0,
|
|
1212
|
+
};
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
// HTML's default object size, in CSS pixels; `width` and `height`
|
|
1216
|
+
// attributes reach the style as presentational hints and win
|
|
1217
|
+
box.intrinsic = {
|
|
1218
|
+
width: 300 * scale,
|
|
1219
|
+
height: 150 * scale,
|
|
1220
|
+
missing: 0,
|
|
1221
|
+
ratio: 0,
|
|
1222
|
+
};
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
528
1225
|
|
|
529
1226
|
const size = this._options.controlSize(el, replaced, style);
|
|
530
|
-
box.
|
|
531
|
-
|
|
1227
|
+
box.intrinsic = {
|
|
1228
|
+
width: size.width,
|
|
1229
|
+
height: size.height,
|
|
1230
|
+
missing: 0,
|
|
1231
|
+
ratio: 0,
|
|
1232
|
+
};
|
|
532
1233
|
this._controls.push(box);
|
|
533
1234
|
// A control's value is the widget's, not the document's: putting it in
|
|
534
1235
|
// the selection index would make Ctrl+A copy the contents of every text
|
|
535
1236
|
// field, which no document viewer does.
|
|
536
1237
|
}
|
|
537
1238
|
|
|
1239
|
+
/**
|
|
1240
|
+
* An element's `::before` or `::after`, when a rule gives it content: a
|
|
1241
|
+
* box of its own `display`, holding the text its `content` comes to, which
|
|
1242
|
+
* goes through the same white-space processing as the document's text.
|
|
1243
|
+
*/
|
|
1244
|
+
private _pseudo(
|
|
1245
|
+
el: Element,
|
|
1246
|
+
which: 'before' | 'after',
|
|
1247
|
+
elementStyle: ComputedStyle,
|
|
1248
|
+
into: Box,
|
|
1249
|
+
): void {
|
|
1250
|
+
const style = this._options.cascade.pseudoStyleFor(el, which, elementStyle);
|
|
1251
|
+
if (!style || style.display === 'none') return;
|
|
1252
|
+
if (isRelative(style)) {
|
|
1253
|
+
this._relative = true;
|
|
1254
|
+
if (style.display === 'inline' && isOffset(style)) {
|
|
1255
|
+
this._movedInline = true;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
// a translation is moved by the same pass
|
|
1259
|
+
if (style.translate || style.transformTranslate) this._relative = true;
|
|
1260
|
+
if (style.backgroundClipText) this._clipText = true;
|
|
1261
|
+
if (style.verticalAlign !== 'baseline' && style.display === 'inline') {
|
|
1262
|
+
this._movedInline = true;
|
|
1263
|
+
}
|
|
1264
|
+
if (isNegative(style)) this._negative = true;
|
|
1265
|
+
// a column renders no content, and generated content is all it would
|
|
1266
|
+
// hold; in a column group it is not a column either (CSS 2.1 17.2.1)
|
|
1267
|
+
if (
|
|
1268
|
+
style.display === 'table-column' ||
|
|
1269
|
+
style.display === 'table-column-group' ||
|
|
1270
|
+
elementStyle.display === 'table-column-group'
|
|
1271
|
+
) {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
// a scope of its own, which what it counts is in where its element has
|
|
1275
|
+
// the counter already (`CounterScopes`)
|
|
1276
|
+
this._scopes.open();
|
|
1277
|
+
this._counterChanges(style, null);
|
|
1278
|
+
const box = new Box(boxKindFor(style.display), null, style);
|
|
1279
|
+
box.pseudo = which;
|
|
1280
|
+
into.append(box);
|
|
1281
|
+
// a list item it generates has a marker as an element's has, of the
|
|
1282
|
+
// `list-item` counter it has just counted (CSS 2.1 12.5): outside it,
|
|
1283
|
+
// or at the start of its content
|
|
1284
|
+
const written =
|
|
1285
|
+
style.display === 'list-item'
|
|
1286
|
+
? markerFor(
|
|
1287
|
+
style,
|
|
1288
|
+
this._scopes.value('list-item'),
|
|
1289
|
+
this._options.cascade.counterStyles,
|
|
1290
|
+
)
|
|
1291
|
+
: null;
|
|
1292
|
+
let marker = written?.text ?? '';
|
|
1293
|
+
if (marker && style.listStylePosition !== 'inside') {
|
|
1294
|
+
box.marker = { text: marker, layout: null, x: 0, y: 0, style: null };
|
|
1295
|
+
if (written?.flush) box.marker.flush = true;
|
|
1296
|
+
marker = '';
|
|
1297
|
+
}
|
|
1298
|
+
if (style.position === 'absolute' || style.position === 'fixed')
|
|
1299
|
+
box.outOfFlow = true;
|
|
1300
|
+
else if (style.float !== 'none') box.isFloat = true;
|
|
1301
|
+
if (into.kind === 'inline' && (box.outOfFlow || box.isFloat))
|
|
1302
|
+
this._nestedOutOfLine = true;
|
|
1303
|
+
const pieces = this._generated(style.content as ContentItem[], style, el);
|
|
1304
|
+
if (marker) pieces.unshift(written?.flush ? marker : `${marker} `);
|
|
1305
|
+
this._scopes.close();
|
|
1306
|
+
const flow = flowOf(style, box);
|
|
1307
|
+
const around = this._ws;
|
|
1308
|
+
const aroundWord = this._lettered;
|
|
1309
|
+
if (flow === 'block') this._endLine();
|
|
1310
|
+
if (flow !== 'inline') this._ws = 'start';
|
|
1311
|
+
// the first letter can be generated, and is looked for here as in an
|
|
1312
|
+
// element of the same display
|
|
1313
|
+
const outerLetter = this._firstLetter;
|
|
1314
|
+
const skipped = flow === 'out' || flow === 'atomic';
|
|
1315
|
+
if (skipped) this._firstLetter = null;
|
|
1316
|
+
else if (flow === 'block' && outerLetter) giveBack(outerLetter);
|
|
1317
|
+
for (const piece of pieces) {
|
|
1318
|
+
if (typeof piece === 'string') this._textNode(piece, box, style, el);
|
|
1319
|
+
else this._contentImage(piece.url, box, style, el);
|
|
1320
|
+
}
|
|
1321
|
+
this._letterAfter(flow, skipped, outerLetter, null);
|
|
1322
|
+
if (flow !== 'inline') this._endLine();
|
|
1323
|
+
this._ws = after(flow, this._ws, around);
|
|
1324
|
+
this._lettered = letteredAfterFlow(flow, this._lettered, aroundWord);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* An `inside` list marker: an inline box at the start of the item, the
|
|
1329
|
+
* marker and a space, which takes its room on the first line as a
|
|
1330
|
+
* `::marker` does (CSS Lists 3, 3.1), where a marker set beside the line
|
|
1331
|
+
* was drawn over its first letters — every `list-style-position: inside`
|
|
1332
|
+
* list, and every `<summary>`. It inherits the item's text style and has
|
|
1333
|
+
* none of its box.
|
|
1334
|
+
*/
|
|
1335
|
+
private _insideMarker(
|
|
1336
|
+
text: string,
|
|
1337
|
+
itemStyle: ComputedStyle,
|
|
1338
|
+
markerStyle: ComputedStyle | null,
|
|
1339
|
+
into: Box,
|
|
1340
|
+
el: Element,
|
|
1341
|
+
): void {
|
|
1342
|
+
const style = inherit(
|
|
1343
|
+
markerStyle ?? itemStyle,
|
|
1344
|
+
this._options.cascade.initial,
|
|
1345
|
+
);
|
|
1346
|
+
style.display = 'inline';
|
|
1347
|
+
// a marker's direction is its own, whatever the text after it, unless
|
|
1348
|
+
// its rules say otherwise (the HTML style sheet's `::marker`)
|
|
1349
|
+
style.unicodeBidi = markerStyle?.unicodeBidi ?? 'isolate';
|
|
1350
|
+
const box = new Box('inline', null, style);
|
|
1351
|
+
box.pseudo = 'before';
|
|
1352
|
+
into.append(box);
|
|
1353
|
+
this._textNode(text, box, style, el);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* `counter-reset`, then `counter-increment`, then `counter-set`, as CSS
|
|
1358
|
+
* Lists 3 orders them (4.2). A list item counts `list-item` besides,
|
|
1359
|
+
* unless its `counter-increment` names it: one up, or one down in a list
|
|
1360
|
+
* that counts down. `place` is where an element is, for a reversed
|
|
1361
|
+
* counter to be counted from; a pseudo-element has none.
|
|
1362
|
+
*/
|
|
1363
|
+
private _counterChanges(
|
|
1364
|
+
style: ComputedStyle,
|
|
1365
|
+
place: CounterPlace | null,
|
|
1366
|
+
): void {
|
|
1367
|
+
const resets = style.counterReset;
|
|
1368
|
+
const increments = style.counterIncrement;
|
|
1369
|
+
const sets = style.counterSet;
|
|
1370
|
+
const listItem = style.display === 'list-item';
|
|
1371
|
+
if (!resets && !increments && !sets && !listItem) return;
|
|
1372
|
+
if (resets) {
|
|
1373
|
+
for (let i = 0; i < resets.length; i += 1) {
|
|
1374
|
+
const change = resets[i];
|
|
1375
|
+
// a name reset twice is reset by the last
|
|
1376
|
+
if (resets.some((c, j) => j > i && c.name === change.name)) continue;
|
|
1377
|
+
const value = change.counted
|
|
1378
|
+
? this._countedStart(change.name, style, place)
|
|
1379
|
+
: change.value;
|
|
1380
|
+
this._scopes.reset(change.name, value, !!change.reversed);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
let counted = false;
|
|
1384
|
+
for (const { name, value } of increments ?? []) {
|
|
1385
|
+
this._scopes.increment(name, value);
|
|
1386
|
+
if (name === 'list-item') counted = true;
|
|
1387
|
+
}
|
|
1388
|
+
if (listItem && !counted) {
|
|
1389
|
+
this._scopes.increment(
|
|
1390
|
+
'list-item',
|
|
1391
|
+
this._scopes.reversed('list-item') ? -1 : 1,
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
for (const { name, value } of sets ?? []) this._scopes.set(name, value);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Where a reversed counter written with no number starts (CSS Lists 3,
|
|
1399
|
+
* 4.4.2): minus what the elements and pseudo-elements in its scope add to
|
|
1400
|
+
* it, as far as the first that sets it, and what that sets it to, and as
|
|
1401
|
+
* much again as the last of them that counted took away — 1 for a list
|
|
1402
|
+
* item. `<ol reversed>`'s items count down to 1, and to an item's
|
|
1403
|
+
* `value` before it. The scope is walked ahead of the builder, from the styles it will
|
|
1404
|
+
* find shared when it gets there (`Cascade.sharedStyleFor`), and only for
|
|
1405
|
+
* such a counter.
|
|
1406
|
+
*/
|
|
1407
|
+
private _countedStart(
|
|
1408
|
+
name: string,
|
|
1409
|
+
style: ComputedStyle,
|
|
1410
|
+
place: CounterPlace | null,
|
|
1411
|
+
): number {
|
|
1412
|
+
const cascade = this._options.cascade;
|
|
1413
|
+
/** What each element and pseudo-element in the scope does to the
|
|
1414
|
+
* counter, in document order: what it adds, and what it sets it to. */
|
|
1415
|
+
const steps: { by: number; set: number | null }[] = [];
|
|
1416
|
+
const note = (st: ComputedStyle): void => {
|
|
1417
|
+
let by = 0;
|
|
1418
|
+
let increments = false;
|
|
1419
|
+
for (const change of st.counterIncrement ?? []) {
|
|
1420
|
+
if (change.name !== name) continue;
|
|
1421
|
+
by += change.value;
|
|
1422
|
+
increments = true;
|
|
1423
|
+
}
|
|
1424
|
+
if (!increments && name === 'list-item' && st.display === 'list-item') {
|
|
1425
|
+
by = -1;
|
|
1426
|
+
increments = true;
|
|
1427
|
+
}
|
|
1428
|
+
let set: number | null = null;
|
|
1429
|
+
for (const change of st.counterSet ?? []) {
|
|
1430
|
+
if (change.name === name) set = change.value;
|
|
1431
|
+
}
|
|
1432
|
+
if (increments || set !== null) steps.push({ by, set });
|
|
1433
|
+
};
|
|
1434
|
+
const resets = (st: ComputedStyle): boolean =>
|
|
1435
|
+
st.counterReset?.some((change) => change.name === name) ?? false;
|
|
1436
|
+
const pseudo = (
|
|
1437
|
+
el: Element,
|
|
1438
|
+
which: 'before' | 'after',
|
|
1439
|
+
st: ComputedStyle,
|
|
1440
|
+
): void => {
|
|
1441
|
+
const own = cascade.pseudoStyleFor(el, which, st);
|
|
1442
|
+
if (own && own.display !== 'none' && !resets(own)) note(own);
|
|
1443
|
+
};
|
|
1444
|
+
/** The elements from `from` on of a parent, and what is in them: a
|
|
1445
|
+
* reset of the counter in one is a counter of its own, and a later
|
|
1446
|
+
* sibling's reset at the scope's own level ends it (`stopAtReset`). */
|
|
1447
|
+
const walk = (
|
|
1448
|
+
children: ReturnType<typeof childrenOf>,
|
|
1449
|
+
from: number,
|
|
1450
|
+
parentStyle: ComputedStyle,
|
|
1451
|
+
parentKey: number,
|
|
1452
|
+
inFlex: boolean,
|
|
1453
|
+
stopAtReset: boolean,
|
|
1454
|
+
): void => {
|
|
1455
|
+
for (let i = from; i < children.length; i += 1) {
|
|
1456
|
+
const child = children[i];
|
|
1457
|
+
if (!isElement(child)) continue;
|
|
1458
|
+
const tag = tagOf(child);
|
|
1459
|
+
if (NON_RENDERED.has(tag) || inImpliedHead(child, tag)) continue;
|
|
1460
|
+
const shared = cascade.sharedStyleFor(
|
|
1461
|
+
child,
|
|
1462
|
+
parentStyle,
|
|
1463
|
+
parentKey,
|
|
1464
|
+
inFlex,
|
|
1465
|
+
);
|
|
1466
|
+
const st = shared.style;
|
|
1467
|
+
if (st.display === 'none') continue;
|
|
1468
|
+
if (resets(st)) {
|
|
1469
|
+
if (stopAtReset) return;
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
note(st);
|
|
1473
|
+
inside(child, st, shared.key);
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
const inside = (el: Element, st: ComputedStyle, key: number): void => {
|
|
1477
|
+
pseudo(el, 'before', st);
|
|
1478
|
+
walk(
|
|
1479
|
+
childrenOf(el),
|
|
1480
|
+
0,
|
|
1481
|
+
st,
|
|
1482
|
+
key,
|
|
1483
|
+
st.display === 'flex' || st.display === 'inline-flex',
|
|
1484
|
+
false,
|
|
1485
|
+
);
|
|
1486
|
+
pseudo(el, 'after', st);
|
|
1487
|
+
};
|
|
1488
|
+
|
|
1489
|
+
// the element that resets it counts in it too, after the reset
|
|
1490
|
+
note(style);
|
|
1491
|
+
if (place) {
|
|
1492
|
+
const siblings = this._scopes.reachesSiblings(name);
|
|
1493
|
+
inside(place.node, place.style, place.key);
|
|
1494
|
+
const parent = place.parent;
|
|
1495
|
+
const node = place.node as Element & { parent?: Element | null };
|
|
1496
|
+
if (siblings && parent && node.parent) {
|
|
1497
|
+
const children = childrenOf(node.parent);
|
|
1498
|
+
walk(
|
|
1499
|
+
children,
|
|
1500
|
+
children.indexOf(place.node) + 1,
|
|
1501
|
+
parent.style,
|
|
1502
|
+
parent.key,
|
|
1503
|
+
parent.inFlex,
|
|
1504
|
+
true,
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
let num = 0;
|
|
1509
|
+
let last = 0;
|
|
1510
|
+
for (const { by, set } of steps) {
|
|
1511
|
+
if (by !== 0) last = -by;
|
|
1512
|
+
if (set !== null) {
|
|
1513
|
+
num += set;
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1516
|
+
num -= by;
|
|
1517
|
+
}
|
|
1518
|
+
return num + last;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/** A counter's value in a style the document can name, as `counter()`
|
|
1522
|
+
* writes it; `none` writes nothing. */
|
|
1523
|
+
private _counterText(n: number, name: string, style: ComputedStyle): string {
|
|
1524
|
+
if (name === 'none') return '';
|
|
1525
|
+
return this._options.cascade.counterStyles.text(
|
|
1526
|
+
n,
|
|
1527
|
+
name,
|
|
1528
|
+
style.direction === 'rtl',
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
/** What `content` comes to here, in document order: its text, broken
|
|
1533
|
+
* where it names an image. The quotes it opens and closes count for
|
|
1534
|
+
* everything after it. */
|
|
1535
|
+
private _generated(
|
|
1536
|
+
items: ContentItem[],
|
|
1537
|
+
style: ComputedStyle,
|
|
1538
|
+
el: Element,
|
|
1539
|
+
): (string | { url: string })[] {
|
|
1540
|
+
const pieces: (string | { url: string })[] = [];
|
|
1541
|
+
let text = '';
|
|
1542
|
+
for (const item of items) {
|
|
1543
|
+
switch (item.kind) {
|
|
1544
|
+
case 'string':
|
|
1545
|
+
text += item.text;
|
|
1546
|
+
break;
|
|
1547
|
+
case 'url':
|
|
1548
|
+
if (text) pieces.push(text);
|
|
1549
|
+
text = '';
|
|
1550
|
+
pieces.push({ url: item.url });
|
|
1551
|
+
break;
|
|
1552
|
+
case 'attr':
|
|
1553
|
+
text += attr(el, item.name) ?? '';
|
|
1554
|
+
break;
|
|
1555
|
+
case 'counter':
|
|
1556
|
+
text += this._counterText(
|
|
1557
|
+
this._scopes.value(item.name),
|
|
1558
|
+
item.style,
|
|
1559
|
+
style,
|
|
1560
|
+
);
|
|
1561
|
+
break;
|
|
1562
|
+
case 'counters':
|
|
1563
|
+
text += this._scopes
|
|
1564
|
+
.values(item.name)
|
|
1565
|
+
.map((v) => this._counterText(v, item.style, style))
|
|
1566
|
+
.join(item.separator);
|
|
1567
|
+
break;
|
|
1568
|
+
case 'open-quote':
|
|
1569
|
+
text += quoteAt(style.quotes, this._quoteDepth, 0);
|
|
1570
|
+
this._quoteDepth += 1;
|
|
1571
|
+
break;
|
|
1572
|
+
case 'close-quote':
|
|
1573
|
+
// a close with nothing open writes nothing and closes nothing
|
|
1574
|
+
if (this._quoteDepth > 0) {
|
|
1575
|
+
this._quoteDepth -= 1;
|
|
1576
|
+
text += quoteAt(style.quotes, this._quoteDepth, 1);
|
|
1577
|
+
}
|
|
1578
|
+
break;
|
|
1579
|
+
case 'no-open-quote':
|
|
1580
|
+
this._quoteDepth += 1;
|
|
1581
|
+
break;
|
|
1582
|
+
case 'no-close-quote':
|
|
1583
|
+
if (this._quoteDepth > 0) this._quoteDepth -= 1;
|
|
1584
|
+
break;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
if (text) pieces.push(text);
|
|
1588
|
+
return pieces;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/**
|
|
1592
|
+
* An image generated content names: an inline replaced box in the
|
|
1593
|
+
* pseudo-element, of the style it inherits and no other (CSS 2.1 12.2),
|
|
1594
|
+
* the image's size once the host has it and none before. An image that
|
|
1595
|
+
* never arrives takes no room, as no image does.
|
|
1596
|
+
*/
|
|
1597
|
+
private _contentImage(
|
|
1598
|
+
url: string,
|
|
1599
|
+
into: Box,
|
|
1600
|
+
style: ComputedStyle,
|
|
1601
|
+
owner: Element,
|
|
1602
|
+
): void {
|
|
1603
|
+
const box = new Box('replaced', null, {
|
|
1604
|
+
...inherit(style, this._options.cascade.initial),
|
|
1605
|
+
display: 'inline',
|
|
1606
|
+
});
|
|
1607
|
+
box.replaced = 'image';
|
|
1608
|
+
into.append(box);
|
|
1609
|
+
CONTENT_IMAGES.set(box, url);
|
|
1610
|
+
const size = this._options.urlSize?.(url) ?? null;
|
|
1611
|
+
this._contentImages.push({ url, element: owner, sized: !!size });
|
|
1612
|
+
if (size) setIntrinsics(box, size, this._options.scale ?? 1);
|
|
1613
|
+
else box.intrinsic = { width: 0, height: 0, missing: 0, ratio: 0 };
|
|
1614
|
+
this._ws = after('atomic', this._ws, this._ws);
|
|
1615
|
+
this._lettered = false;
|
|
1616
|
+
this._abandonLetter();
|
|
1617
|
+
}
|
|
1618
|
+
|
|
538
1619
|
/** A text node, whitespace-processed per the inherited `white-space`. */
|
|
539
1620
|
private _textNode(
|
|
540
1621
|
data: string,
|
|
@@ -542,21 +1623,128 @@ class Builder {
|
|
|
542
1623
|
style: ComputedStyle,
|
|
543
1624
|
owner: Element | null,
|
|
544
1625
|
): void {
|
|
1626
|
+
// text set at no size draws nothing and takes no room, so it needs no
|
|
1627
|
+
// box — and it is no part of the white space around it either
|
|
1628
|
+
if (!(style.fontSize > 0)) return;
|
|
1629
|
+
// a word does not run on over the start of a line
|
|
1630
|
+
if (this._ws === 'start') this._lettered = false;
|
|
545
1631
|
const ws = style.whiteSpace;
|
|
546
1632
|
let text: string;
|
|
547
1633
|
if (ws === 'pre' || ws === 'pre-wrap') {
|
|
548
1634
|
text = data;
|
|
549
|
-
|
|
550
|
-
|
|
1635
|
+
if (!text) return;
|
|
1636
|
+
// preserved spaces do not collapse with the ones after them
|
|
1637
|
+
this._ws = text.endsWith('\n') ? 'start' : 'content';
|
|
551
1638
|
} else {
|
|
552
|
-
text =
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
1639
|
+
text =
|
|
1640
|
+
ws === 'pre-line'
|
|
1641
|
+
? data.replace(/[ \t]+/g, ' ').replace(/ *\n */g, '\n')
|
|
1642
|
+
: COLLAPSIBLE.test(data)
|
|
1643
|
+
? data.replace(/[\t\n\r\f ]+/g, ' ')
|
|
1644
|
+
: data;
|
|
1645
|
+
// A space at the start of a line goes, and so does one after another
|
|
1646
|
+
// space, across element boundaries (CSS 2.1 16.6.1): `<p>\n Hi` has
|
|
1647
|
+
// no space before the H, and `Hi <b> there</b>` has one between.
|
|
1648
|
+
if (text.charCodeAt(0) === 32 && this._ws !== 'content') {
|
|
1649
|
+
text = text.slice(1);
|
|
1650
|
+
}
|
|
1651
|
+
if (!text) return;
|
|
1652
|
+
const last = text.charCodeAt(text.length - 1);
|
|
1653
|
+
this._ws = last === 32 ? 'space' : last === 10 ? 'start' : 'content';
|
|
1654
|
+
}
|
|
1655
|
+
text = transformText(text, style.textTransform, this._lettered);
|
|
1656
|
+
this._lettered = letteredAfter(text, this._lettered);
|
|
1657
|
+
const search = this._firstLetter;
|
|
1658
|
+
const letter = search ? FIRST_LETTER.exec(text) : null;
|
|
1659
|
+
if (!search || !letter) {
|
|
1660
|
+
const punctuation = search ? PUNCTUATION_ONLY.exec(text) : null;
|
|
1661
|
+
if (!search || !punctuation) {
|
|
1662
|
+
this._textBox(text, into, style, owner);
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
// punctuation the letter comes after, in a text of its own —
|
|
1666
|
+
// `<q>`'s open quote — takes the letter's style, and gives it back
|
|
1667
|
+
// if no letter follows on the line
|
|
1668
|
+
const start = punctuation[1].length;
|
|
1669
|
+
if (start > 0) this._textBox(text.slice(0, start), into, style, owner);
|
|
1670
|
+
search.punctuation.push(
|
|
1671
|
+
this._letterBox(search, text.slice(start), into, style, owner),
|
|
1672
|
+
);
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
// The first letter, with the punctuation around it, in a box of its
|
|
1676
|
+
// own inside the box it was found in, and so inheriting from that
|
|
1677
|
+
// (CSS 2.1 5.12.2): `<p><b>T</b>his` has a bold first letter.
|
|
1678
|
+
this._firstLetter = null;
|
|
1679
|
+
const start = letter[1].length;
|
|
1680
|
+
const end = letter[0].length;
|
|
1681
|
+
if (start > 0) this._textBox(text.slice(0, start), into, style, owner);
|
|
1682
|
+
this._letterBox(search, text.slice(start, end), into, style, owner);
|
|
1683
|
+
if (end < text.length) {
|
|
1684
|
+
this._textBox(text.slice(end), into, style, owner);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/** A box of the first letter's style around `text`, in `into`. */
|
|
1689
|
+
private _letterBox(
|
|
1690
|
+
search: LetterSearch,
|
|
1691
|
+
text: string,
|
|
1692
|
+
into: Box,
|
|
1693
|
+
style: ComputedStyle,
|
|
1694
|
+
owner: Element | null,
|
|
1695
|
+
): { box: Box; text: Box; style: ComputedStyle } {
|
|
1696
|
+
const cascade = this._options.cascade;
|
|
1697
|
+
const letterStyle = cascade.firstLetterStyle(search.rules, style);
|
|
1698
|
+
const box = new Box(boxKindFor(letterStyle.display), null, letterStyle);
|
|
1699
|
+
box.pseudo = 'first-letter';
|
|
1700
|
+
if (letterStyle.float !== 'none') {
|
|
1701
|
+
box.isFloat = true;
|
|
1702
|
+
if (into.kind === 'inline') this._nestedOutOfLine = true;
|
|
1703
|
+
}
|
|
1704
|
+
into.append(box);
|
|
1705
|
+
if (letterStyle.textTransform !== style.textTransform) {
|
|
1706
|
+
text = transformText(text, letterStyle.textTransform);
|
|
1707
|
+
}
|
|
1708
|
+
return { box, text: this._textBox(text, box, letterStyle, owner), style };
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* Where the search for a first letter stands after a box. A box that was
|
|
1713
|
+
* no part of the line — a float, a positioned box — leaves it where it
|
|
1714
|
+
* was, and one that was something other than a letter on it ends it. A
|
|
1715
|
+
* block that looked for a letter of its own and found its first line
|
|
1716
|
+
* ended its parent's too, and one that found no line leaves the parent
|
|
1717
|
+
* looking. A block ends the line it is on.
|
|
1718
|
+
*/
|
|
1719
|
+
private _letterAfter(
|
|
1720
|
+
flow: 'inline' | 'atomic' | 'block' | 'out',
|
|
1721
|
+
skipped: boolean,
|
|
1722
|
+
outer: LetterSearch | null,
|
|
1723
|
+
own: LetterSearch | null,
|
|
1724
|
+
): void {
|
|
1725
|
+
const lined = own !== null && this._firstLetter !== own;
|
|
1726
|
+
if (own && !lined) this._abandonLetter();
|
|
1727
|
+
if (skipped || own) this._firstLetter = skipped || !lined ? outer : null;
|
|
1728
|
+
if (lined && !skipped && outer) giveBack(outer);
|
|
1729
|
+
if (flow === 'atomic') this._abandonLetter();
|
|
1730
|
+
else if (flow === 'block' && this._firstLetter) giveBack(this._firstLetter);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
/** The first line ended, or began with something other than a letter:
|
|
1734
|
+
* there is no first letter, and punctuation that took its style gives
|
|
1735
|
+
* it back. */
|
|
1736
|
+
private _abandonLetter(): void {
|
|
1737
|
+
const search = this._firstLetter;
|
|
1738
|
+
this._firstLetter = null;
|
|
1739
|
+
if (search) giveBack(search);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
private _textBox(
|
|
1743
|
+
text: string,
|
|
1744
|
+
into: Box,
|
|
1745
|
+
style: ComputedStyle,
|
|
1746
|
+
owner: Element | null,
|
|
1747
|
+
): Box {
|
|
560
1748
|
// The owning element rides on the text box, and from there onto the
|
|
561
1749
|
// `TextRun`: hit testing inside a paragraph has no rectangle to test —
|
|
562
1750
|
// an inline box is the runs on its lines — so the run is what has to
|
|
@@ -565,20 +1753,228 @@ class Builder {
|
|
|
565
1753
|
box.text = text;
|
|
566
1754
|
into.append(box);
|
|
567
1755
|
this._push(text, box);
|
|
1756
|
+
return box;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* A line ends here — a block's inline content is over, or a `<br>` or a
|
|
1761
|
+
* block interrupts it — and a collapsible space it ends on goes (CSS 2.1
|
|
1762
|
+
* 16.6.1). Content standing on a space means that space was the last
|
|
1763
|
+
* thing pushed, so taking it back is a character off the last chunk; a
|
|
1764
|
+
* text box it empties goes with it.
|
|
1765
|
+
*/
|
|
1766
|
+
private _endLine(): void {
|
|
1767
|
+
if (this._ws !== 'space') return;
|
|
1768
|
+
this._ws = 'start';
|
|
1769
|
+
const box = this._textBoxes[this._textBoxes.length - 1];
|
|
1770
|
+
if (!box || box.textEnd !== this._length || !box.text.endsWith(' ')) {
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
box.text = box.text.slice(0, -1);
|
|
1774
|
+
box.textEnd -= 1;
|
|
1775
|
+
this._length -= 1;
|
|
1776
|
+
const last = this._chunks.length - 1;
|
|
1777
|
+
this._chunks[last] = this._chunks[last].slice(0, -1);
|
|
1778
|
+
if (!box.text) {
|
|
1779
|
+
this._textBoxes.pop();
|
|
1780
|
+
const siblings = box.parent?.children;
|
|
1781
|
+
const at = siblings ? siblings.lastIndexOf(box) : -1;
|
|
1782
|
+
if (at >= 0) siblings!.splice(at, 1);
|
|
1783
|
+
}
|
|
568
1784
|
}
|
|
569
1785
|
|
|
570
1786
|
/** Give a box its slice of the document text index. */
|
|
571
1787
|
private _push(text: string, box: Box): void {
|
|
572
|
-
box.textStart = this.
|
|
573
|
-
this.
|
|
574
|
-
|
|
575
|
-
|
|
1788
|
+
box.textStart = this._length;
|
|
1789
|
+
this._chunks.push(text);
|
|
1790
|
+
this._length += text.length;
|
|
1791
|
+
box.textEnd = this._length;
|
|
1792
|
+
if (box.kind === 'text') {
|
|
1793
|
+
this._textBoxes.push(box);
|
|
1794
|
+
if (box.style !== this._lastTextStyle) {
|
|
1795
|
+
this._lastTextStyle = box.style;
|
|
1796
|
+
this._textStyles.add(box.style);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
/**
|
|
1803
|
+
* The CSS counters in scope as the builder walks the document (CSS Lists 3,
|
|
1804
|
+
* 4.5). Every element and pseudo-element opens a level, which closes after
|
|
1805
|
+
* what it holds. A `counter-reset` makes an instance that reaches the
|
|
1806
|
+
* element's descendants and — where its parent has no counter of the name
|
|
1807
|
+
* — its later siblings too, so that instance belongs to the level the
|
|
1808
|
+
* element is on, its parent's, and goes when that closes; a later sibling's
|
|
1809
|
+
* reset takes its place. Where its parent has one, the new instance nests
|
|
1810
|
+
* in it and reaches the element's descendants alone, on the element's own
|
|
1811
|
+
* level: a list in a list item, a counter reset in a `::before`. `counter()`
|
|
1812
|
+
* reads the innermost instance and `counters()` all of them, outermost
|
|
1813
|
+
* first. A counter used where none is in scope is reset to 0 there, as
|
|
1814
|
+
* though the element had asked.
|
|
1815
|
+
*/
|
|
1816
|
+
class CounterScopes {
|
|
1817
|
+
/** Per name, its instances, outermost first: the level each goes with,
|
|
1818
|
+
* whether it is the own one of the element that level is, and whether it
|
|
1819
|
+
* counts down. */
|
|
1820
|
+
private _instances = new Map<string, CounterInstance[]>();
|
|
1821
|
+
/** The names each open level made an instance of, so closing it drops
|
|
1822
|
+
* exactly those: null for a level that made none, which is nearly every
|
|
1823
|
+
* one, so that opening an element allocates nothing. */
|
|
1824
|
+
private _made: (string[] | null)[] = [null];
|
|
1825
|
+
/** The levels of the open elements with style containment, innermost
|
|
1826
|
+
* last: below one, a counter made further out is not counted on. */
|
|
1827
|
+
private _contained: number[] = [];
|
|
1828
|
+
|
|
1829
|
+
open(): void {
|
|
1830
|
+
this._made.push(null);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
close(): void {
|
|
1834
|
+
const level = this._made.length - 1;
|
|
1835
|
+
const made = this._made.pop();
|
|
1836
|
+
if (made) {
|
|
1837
|
+
for (const name of made) {
|
|
1838
|
+
const stack = this._instances.get(name);
|
|
1839
|
+
if (stack && stack[stack.length - 1]?.level === level) stack.pop();
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
const contained = this._contained;
|
|
1843
|
+
if (contained[contained.length - 1] === level) contained.pop();
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
/** The element whose level is open has style containment. */
|
|
1847
|
+
contain(): void {
|
|
1848
|
+
this._contained.push(this._made.length - 1);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
/** Whether the parent of the element whose level is open has a counter
|
|
1852
|
+
* of this name: its own, or one from further out — not one a sibling
|
|
1853
|
+
* before the element made. */
|
|
1854
|
+
private _parentHas(name: string): boolean {
|
|
1855
|
+
const stack = this._instances.get(name);
|
|
1856
|
+
if (!stack?.length) return false;
|
|
1857
|
+
const parent = this._made.length - 2;
|
|
1858
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
1859
|
+
const instance = stack[i];
|
|
1860
|
+
if (instance.level < parent) return true;
|
|
1861
|
+
if (instance.level === parent && instance.own) return true;
|
|
1862
|
+
}
|
|
1863
|
+
return false;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/** Whether a reset of `name` here reaches the element's later siblings,
|
|
1867
|
+
* or its descendants alone. */
|
|
1868
|
+
reachesSiblings(name: string): boolean {
|
|
1869
|
+
return this._made.length < 2 || !this._parentHas(name);
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
reset(name: string, value: number, reversed: boolean): void {
|
|
1873
|
+
const own = !this.reachesSiblings(name);
|
|
1874
|
+
const level = own ? this._made.length - 1 : this._made.length - 2;
|
|
1875
|
+
let stack = this._instances.get(name);
|
|
1876
|
+
if (!stack) {
|
|
1877
|
+
stack = [];
|
|
1878
|
+
this._instances.set(name, stack);
|
|
1879
|
+
}
|
|
1880
|
+
const top = stack[stack.length - 1];
|
|
1881
|
+
if (top?.level === level && top.own === own) {
|
|
1882
|
+
top.value = value;
|
|
1883
|
+
top.reversed = reversed;
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
stack.push({ level, own, value, reversed });
|
|
1887
|
+
(this._made[level] ??= []).push(name);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
/** The innermost instance, made here at 0 where there is none. */
|
|
1891
|
+
private _innermost(name: string): CounterInstance {
|
|
1892
|
+
let stack = this._instances.get(name);
|
|
1893
|
+
if (!stack?.length) {
|
|
1894
|
+
this.reset(name, 0, false);
|
|
1895
|
+
stack = this._instances.get(name)!;
|
|
1896
|
+
}
|
|
1897
|
+
return stack[stack.length - 1];
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
increment(name: string, by: number): void {
|
|
1901
|
+
const instance = this._counted(name);
|
|
1902
|
+
instance.value = clampCounter(instance.value + by);
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
set(name: string, value: number): void {
|
|
1906
|
+
this._counted(name).value = value;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
/** The instance an increment or a set counts on: the innermost, unless
|
|
1910
|
+
* it was made outside an element with style containment that this is
|
|
1911
|
+
* inside, where a new one is made instead, as though this reset it for
|
|
1912
|
+
* itself and its later siblings — which one outside the element's parent
|
|
1913
|
+
* then does not see (CSS Containment 2, 3.4). */
|
|
1914
|
+
private _counted(name: string): CounterInstance {
|
|
1915
|
+
const instance = this._innermost(name);
|
|
1916
|
+
const bound = this._contained[this._contained.length - 1];
|
|
1917
|
+
const level = this._made.length - 1;
|
|
1918
|
+
if (bound === undefined || level <= bound || instance.level >= bound) {
|
|
1919
|
+
return instance;
|
|
1920
|
+
}
|
|
1921
|
+
const parent = level - 1;
|
|
1922
|
+
const fresh = { level: parent, own: false, value: 0, reversed: false };
|
|
1923
|
+
this._instances.get(name)!.push(fresh);
|
|
1924
|
+
(this._made[parent] ??= []).push(name);
|
|
1925
|
+
return fresh;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
/** Whether the innermost instance counts down: a list item takes one
|
|
1929
|
+
* from it rather than adding one. */
|
|
1930
|
+
reversed(name: string): boolean {
|
|
1931
|
+
return this._innermost(name).reversed;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
value(name: string): number {
|
|
1935
|
+
return this._innermost(name).value;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
values(name: string): number[] {
|
|
1939
|
+
this._innermost(name);
|
|
1940
|
+
return this._instances.get(name)!.map((instance) => instance.value);
|
|
576
1941
|
}
|
|
577
1942
|
}
|
|
578
1943
|
|
|
1944
|
+
interface CounterInstance {
|
|
1945
|
+
level: number;
|
|
1946
|
+
own: boolean;
|
|
1947
|
+
value: number;
|
|
1948
|
+
reversed: boolean;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
/** A counter is a 32-bit integer in a browser, and saturates there. */
|
|
1952
|
+
function clampCounter(value: number): number {
|
|
1953
|
+
return Math.max(-2147483648, Math.min(2147483647, value));
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/** Where the element whose counters change is: what the walk that counts
|
|
1957
|
+
* a reversed counter's scope starts from (`BoxBuilder._countedStart`). */
|
|
1958
|
+
interface CounterPlace {
|
|
1959
|
+
node: Element;
|
|
1960
|
+
style: ComputedStyle;
|
|
1961
|
+
key: number;
|
|
1962
|
+
/** The element's parent's style and share key, and whether it is a flex
|
|
1963
|
+
* container: what its later siblings' styles are computed from. Null
|
|
1964
|
+
* for the root. */
|
|
1965
|
+
parent: { style: ComputedStyle; key: number; inFlex: boolean } | null;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
579
1968
|
/** Whether the parsed document has a `<body>`. htmlparser2 does not
|
|
580
1969
|
* synthesise one — it parses what it was given — so a fragment has none,
|
|
581
1970
|
* and the root box stands in for it. */
|
|
1971
|
+
function hasHtml(root: Element | { children: unknown }): boolean {
|
|
1972
|
+
for (const child of childrenOf(root as Element)) {
|
|
1973
|
+
if (isElement(child) && tagOf(child) === 'html') return true;
|
|
1974
|
+
}
|
|
1975
|
+
return false;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
582
1978
|
function hasBody(root: Element | { children: unknown }): boolean {
|
|
583
1979
|
for (const child of childrenOf(root as Element)) {
|
|
584
1980
|
if (!isElement(child)) continue;
|
|
@@ -589,6 +1985,14 @@ function hasBody(root: Element | { children: unknown }): boolean {
|
|
|
589
1985
|
return false;
|
|
590
1986
|
}
|
|
591
1987
|
|
|
1988
|
+
/** A canvas's bitmap dimension: a non-negative integer, as HTML parses
|
|
1989
|
+
* one, or its default where the attribute is missing or no number. */
|
|
1990
|
+
function canvasSize(el: Element, name: string, fallback: number): number {
|
|
1991
|
+
const raw = attr(el, name);
|
|
1992
|
+
const n = raw == null ? NaN : parseInt(raw.trim(), 10);
|
|
1993
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
592
1996
|
function numberAttr(el: Element, name: string): number | null {
|
|
593
1997
|
const raw = attr(el, name);
|
|
594
1998
|
if (!raw) return null;
|
|
@@ -596,19 +2000,10 @@ function numberAttr(el: Element, name: string): number | null {
|
|
|
596
2000
|
return Number.isFinite(n) ? n : null;
|
|
597
2001
|
}
|
|
598
2002
|
|
|
599
|
-
function hasInlineContent(box: Box): boolean {
|
|
600
|
-
for (let i = box.children.length - 1; i >= 0; i -= 1) {
|
|
601
|
-
const kind = box.children[i].kind;
|
|
602
|
-
if (kind === 'text' || kind === 'inline' || kind === 'replaced')
|
|
603
|
-
return true;
|
|
604
|
-
if (kind === 'block' || kind === 'flex' || kind === 'table') return false;
|
|
605
|
-
}
|
|
606
|
-
return false;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
2003
|
function transformText(
|
|
610
2004
|
text: string,
|
|
611
2005
|
transform: ComputedStyle['textTransform'],
|
|
2006
|
+
lettered = false,
|
|
612
2007
|
): string {
|
|
613
2008
|
switch (transform) {
|
|
614
2009
|
case 'uppercase':
|
|
@@ -616,15 +2011,246 @@ function transformText(
|
|
|
616
2011
|
case 'lowercase':
|
|
617
2012
|
return text.toLowerCase();
|
|
618
2013
|
case 'capitalize':
|
|
619
|
-
return text
|
|
620
|
-
/(^|\s)(\S)/g,
|
|
621
|
-
(_, sp: string, c: string) => sp + c.toUpperCase(),
|
|
622
|
-
);
|
|
2014
|
+
return capitalize(text, lettered);
|
|
623
2015
|
default:
|
|
624
2016
|
return text;
|
|
625
2017
|
}
|
|
626
2018
|
}
|
|
627
2019
|
|
|
2020
|
+
/** A letter or a number: what a word's first typographic letter unit is
|
|
2021
|
+
* (CSS Text 3, 2.1). */
|
|
2022
|
+
const LETTER_UNIT = /[\p{L}\p{N}]/u;
|
|
2023
|
+
|
|
2024
|
+
/**
|
|
2025
|
+
* What does not end a word (UAX #29): a combining mark, and the
|
|
2026
|
+
* punctuation that joins letters into one — `x.y`, `don't`, `a:b`. Any
|
|
2027
|
+
* other character that is not a letter or a number is between words: a
|
|
2028
|
+
* space, a no-break space, a hyphen, a bracket.
|
|
2029
|
+
*/
|
|
2030
|
+
const IN_WORD =
|
|
2031
|
+
/[\p{M}'.:\u00b7\u0387\u05f4\u2018\u2019\u2024\u2027\ufe13\ufe52\ufe55\uff07\uff0e\uff1a]/u;
|
|
2032
|
+
|
|
2033
|
+
/**
|
|
2034
|
+
* `text-transform: capitalize` (CSS Text 3, 2.1): the first letter or
|
|
2035
|
+
* number of each word in upper case, and nothing else. Punctuation a word
|
|
2036
|
+
* starts with is not its first letter — `(p.p.)` is `(P.p.)` — and a word
|
|
2037
|
+
* the text continues, `lettered`, has had its own: `<b>fo</b>o` is `Foo`.
|
|
2038
|
+
*/
|
|
2039
|
+
function capitalize(text: string, lettered: boolean): string {
|
|
2040
|
+
let out = '';
|
|
2041
|
+
let from = 0;
|
|
2042
|
+
let at = 0;
|
|
2043
|
+
for (const ch of text) {
|
|
2044
|
+
if (LETTER_UNIT.test(ch)) {
|
|
2045
|
+
if (!lettered) {
|
|
2046
|
+
const title = titleCase(ch);
|
|
2047
|
+
if (title !== ch) {
|
|
2048
|
+
out += text.slice(from, at) + title;
|
|
2049
|
+
from = at + ch.length;
|
|
2050
|
+
}
|
|
2051
|
+
lettered = true;
|
|
2052
|
+
}
|
|
2053
|
+
} else if (!IN_WORD.test(ch)) {
|
|
2054
|
+
lettered = false;
|
|
2055
|
+
}
|
|
2056
|
+
at += ch.length;
|
|
2057
|
+
}
|
|
2058
|
+
return from ? out + text.slice(from) : text;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
/**
|
|
2062
|
+
* A letter in title case (Unicode's SpecialCasing), which is its upper case
|
|
2063
|
+
* but for a letter that is two: a digraph is capitalized as its first
|
|
2064
|
+
* letter alone — `dž` as `Dž`, where upper case is `DŽ` — and a Greek vowel
|
|
2065
|
+
* with a subscript iota keeps the iota subscript, where upper case writes
|
|
2066
|
+
* it out. A letter whose upper case is two letters, as `ß` is `SS`, is the
|
|
2067
|
+
* first of them: `Ss`.
|
|
2068
|
+
*/
|
|
2069
|
+
function titleCase(ch: string): string {
|
|
2070
|
+
const cp = ch.codePointAt(0)!;
|
|
2071
|
+
if (cp >= 0x1c4 && cp <= 0x1cc) {
|
|
2072
|
+
return String.fromCharCode(0x1c5 + 3 * Math.floor((cp - 0x1c4) / 3));
|
|
2073
|
+
}
|
|
2074
|
+
if (cp >= 0x1f1 && cp <= 0x1f3) return '\u01f2';
|
|
2075
|
+
if (cp >= 0x1f80 && cp <= 0x1faf) return String.fromCharCode(cp | 0x08);
|
|
2076
|
+
if (cp === 0x1fb3 || cp === 0x1fc3 || cp === 0x1ff3) {
|
|
2077
|
+
return String.fromCharCode(cp + 9);
|
|
2078
|
+
}
|
|
2079
|
+
if (cp === 0x1fbc || cp === 0x1fcc || cp === 0x1ffc) return ch;
|
|
2080
|
+
const upper = ch.toUpperCase();
|
|
2081
|
+
if (upper.length <= ch.length) return upper;
|
|
2082
|
+
const first = String.fromCodePoint(upper.codePointAt(0)!);
|
|
2083
|
+
return first + upper.slice(first.length).toLowerCase();
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
/**
|
|
2087
|
+
* Whether a word is under way after `text`, as `capitalize` reads words: its
|
|
2088
|
+
* last letter, number or character between words decides, and a text of
|
|
2089
|
+
* nothing but joining punctuation leaves it as it was. Asked of every text,
|
|
2090
|
+
* from the end, where it is almost always decided by the last character.
|
|
2091
|
+
*/
|
|
2092
|
+
function letteredAfter(text: string, lettered: boolean): boolean {
|
|
2093
|
+
for (let i = text.length - 1; i >= 0; i -= 1) {
|
|
2094
|
+
const c = text.charCodeAt(i);
|
|
2095
|
+
if (c < 0x80) {
|
|
2096
|
+
if (
|
|
2097
|
+
(c >= 0x61 && c <= 0x7a) ||
|
|
2098
|
+
(c >= 0x41 && c <= 0x5a) ||
|
|
2099
|
+
(c >= 0x30 && c <= 0x39)
|
|
2100
|
+
) {
|
|
2101
|
+
return true;
|
|
2102
|
+
}
|
|
2103
|
+
if (c === 0x27 || c === 0x2e || c === 0x3a) continue;
|
|
2104
|
+
return false;
|
|
2105
|
+
}
|
|
2106
|
+
// a low surrogate is read with the high one before it
|
|
2107
|
+
const start =
|
|
2108
|
+
c >= 0xdc00 && c <= 0xdfff && i > 0 && isHighSurrogate(text, i - 1)
|
|
2109
|
+
? i - 1
|
|
2110
|
+
: i;
|
|
2111
|
+
const ch = text.slice(start, i + 1);
|
|
2112
|
+
if (LETTER_UNIT.test(ch)) return true;
|
|
2113
|
+
if (!IN_WORD.test(ch)) return false;
|
|
2114
|
+
i = start;
|
|
2115
|
+
}
|
|
2116
|
+
return lettered;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
function isHighSurrogate(text: string, i: number): boolean {
|
|
2120
|
+
const c = text.charCodeAt(i);
|
|
2121
|
+
return c >= 0xd800 && c <= 0xdbff;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
/** Whether a word is under way after a box, as `after` says where white
|
|
2125
|
+
* space stands: an inline box leaves it as its content did, an atomic
|
|
2126
|
+
* inline and a block end it, and a float or a positioned box leaves the
|
|
2127
|
+
* word around it as it was. */
|
|
2128
|
+
function letteredAfterFlow(
|
|
2129
|
+
flow: 'inline' | 'atomic' | 'block' | 'out',
|
|
2130
|
+
inside: boolean,
|
|
2131
|
+
before: boolean,
|
|
2132
|
+
): boolean {
|
|
2133
|
+
if (flow === 'inline') return inside;
|
|
2134
|
+
if (flow === 'out') return before;
|
|
2135
|
+
return false;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/** Where the inline content being built stands, for white space: at the
|
|
2139
|
+
* start of a line, just after a space that may collapse, or after anything
|
|
2140
|
+
* else. */
|
|
2141
|
+
type Collapse = 'start' | 'space' | 'content';
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* A text's first letter, with the punctuation before and after it that CSS
|
|
2145
|
+
* 2.1 5.12.2 counts in — the Ps, Pe, Pi, Pf and Po classes — and the white
|
|
2146
|
+
* space before it in the first group. No match when the text ends, or
|
|
2147
|
+
* reaches a space, before any letter: its first letter is in a later text,
|
|
2148
|
+
* as browsers read it.
|
|
2149
|
+
*/
|
|
2150
|
+
const FIRST_LETTER =
|
|
2151
|
+
/^([ \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;
|
|
2152
|
+
|
|
2153
|
+
/** A text that is punctuation and nothing else, but for the white space
|
|
2154
|
+
* before it in the first group. */
|
|
2155
|
+
const PUNCTUATION_ONLY =
|
|
2156
|
+
/^([ \t\n\r\f\u00a0]*)(?:[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]\p{M}*)+$/u;
|
|
2157
|
+
|
|
2158
|
+
/** A `::first-letter` looking for its letter. */
|
|
2159
|
+
interface LetterSearch {
|
|
2160
|
+
rules: FirstLetterRules;
|
|
2161
|
+
/** Punctuation already in the letter's style, from texts that ended
|
|
2162
|
+
* before the letter came. */
|
|
2163
|
+
punctuation: { box: Box; text: Box; style: ComputedStyle }[];
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
/** Put punctuation a search styled back in its own box and style: the
|
|
2167
|
+
* letter it was waiting for is not on its line. */
|
|
2168
|
+
function giveBack(search: LetterSearch): void {
|
|
2169
|
+
for (const { box, text, style } of search.punctuation) {
|
|
2170
|
+
const parent = box.parent;
|
|
2171
|
+
if (!parent) continue;
|
|
2172
|
+
const at = parent.children.indexOf(box);
|
|
2173
|
+
if (at < 0) continue;
|
|
2174
|
+
parent.children[at] = text;
|
|
2175
|
+
text.parent = parent;
|
|
2176
|
+
text.style = style;
|
|
2177
|
+
}
|
|
2178
|
+
search.punctuation.length = 0;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
/** Whether a box of this display is a block container, which is what can
|
|
2182
|
+
* have a first letter. */
|
|
2183
|
+
function hasFirstLetter(display: ComputedStyle['display']): boolean {
|
|
2184
|
+
switch (display) {
|
|
2185
|
+
case 'block':
|
|
2186
|
+
case 'inline-block':
|
|
2187
|
+
case 'list-item':
|
|
2188
|
+
case 'table-cell':
|
|
2189
|
+
case 'table-caption':
|
|
2190
|
+
return true;
|
|
2191
|
+
default:
|
|
2192
|
+
return false;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
/**
|
|
2197
|
+
* How a box sits in its parent's inline content, for white space. An inline
|
|
2198
|
+
* box's content is its parent's own; an atomic inline — an inline-block, an
|
|
2199
|
+
* image — is one piece of it; a block ends the line it interrupts and starts
|
|
2200
|
+
* another; a float or a positioned box is no part of it at all.
|
|
2201
|
+
*/
|
|
2202
|
+
function flowOf(
|
|
2203
|
+
style: ComputedStyle,
|
|
2204
|
+
box: Box,
|
|
2205
|
+
): 'inline' | 'atomic' | 'block' | 'out' {
|
|
2206
|
+
if (box.outOfFlow || box.isFloat) return 'out';
|
|
2207
|
+
// an image told to be a table's part is an inline one (`isBlockLevel`)
|
|
2208
|
+
if (box.kind === 'replaced' && style.display.startsWith('table-')) {
|
|
2209
|
+
return 'atomic';
|
|
2210
|
+
}
|
|
2211
|
+
switch (style.display) {
|
|
2212
|
+
case 'inline':
|
|
2213
|
+
return box.kind === 'replaced' ? 'atomic' : 'inline';
|
|
2214
|
+
case 'inline-block':
|
|
2215
|
+
case 'inline-table':
|
|
2216
|
+
case 'inline-flex':
|
|
2217
|
+
return 'atomic';
|
|
2218
|
+
case 'table-row-group':
|
|
2219
|
+
case 'table-header-group':
|
|
2220
|
+
case 'table-footer-group':
|
|
2221
|
+
case 'table-row':
|
|
2222
|
+
case 'table-cell':
|
|
2223
|
+
case 'table-caption':
|
|
2224
|
+
case 'table-column':
|
|
2225
|
+
case 'table-column-group':
|
|
2226
|
+
// table parts in an inline box are given an inline table around them
|
|
2227
|
+
// (CSS 2.1 17.2.1), which sits in the line as an inline-block does:
|
|
2228
|
+
// the white space either side of it is the line's
|
|
2229
|
+
return box.parent?.kind === 'inline' ? 'atomic' : 'block';
|
|
2230
|
+
default:
|
|
2231
|
+
return 'block';
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
/** Where the inline content stands after a box, from where it stood inside
|
|
2236
|
+
* the box and before it. */
|
|
2237
|
+
function after(
|
|
2238
|
+
flow: 'inline' | 'atomic' | 'block' | 'out',
|
|
2239
|
+
inside: Collapse,
|
|
2240
|
+
before: Collapse,
|
|
2241
|
+
): Collapse {
|
|
2242
|
+
switch (flow) {
|
|
2243
|
+
case 'inline':
|
|
2244
|
+
return inside;
|
|
2245
|
+
case 'atomic':
|
|
2246
|
+
return 'content';
|
|
2247
|
+
case 'out':
|
|
2248
|
+
return before;
|
|
2249
|
+
default:
|
|
2250
|
+
return 'start';
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
|
|
628
2254
|
function boxKindFor(display: ComputedStyle['display']): BoxKind {
|
|
629
2255
|
switch (display) {
|
|
630
2256
|
case 'inline':
|
|
@@ -658,18 +2284,38 @@ function boxKindFor(display: ComputedStyle['display']): BoxKind {
|
|
|
658
2284
|
}
|
|
659
2285
|
}
|
|
660
2286
|
|
|
2287
|
+
/** A replaced box's intrinsic size from what its content says, in CSS
|
|
2288
|
+
* pixels: an axis it does not give takes the default object size. */
|
|
2289
|
+
function setIntrinsics(box: Box, size: IntrinsicSize, scale: number): void {
|
|
2290
|
+
box.intrinsic = {
|
|
2291
|
+
width: (size.width ?? 300) * scale,
|
|
2292
|
+
height: (size.height ?? 150) * scale,
|
|
2293
|
+
missing: (size.width === null ? 1 : 0) | (size.height === null ? 2 : 0),
|
|
2294
|
+
ratio: size.ratio,
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
|
|
661
2298
|
function replacedKind(el: Element, tag: string): ReplacedKind {
|
|
662
2299
|
switch (tag) {
|
|
663
2300
|
case 'img':
|
|
664
2301
|
return 'image';
|
|
2302
|
+
case 'svg':
|
|
2303
|
+
// XHTML's `<svg:svg>` too, which `tagOf` names `svg`
|
|
2304
|
+
return 'svg';
|
|
665
2305
|
case 'hr':
|
|
666
2306
|
return 'hr';
|
|
2307
|
+
case 'iframe':
|
|
2308
|
+
case 'video':
|
|
2309
|
+
case 'embed':
|
|
2310
|
+
case 'canvas':
|
|
2311
|
+
return 'frame';
|
|
667
2312
|
case 'textarea':
|
|
668
2313
|
return 'textarea';
|
|
669
2314
|
case 'select':
|
|
670
2315
|
return 'select';
|
|
671
|
-
|
|
672
|
-
|
|
2316
|
+
// A `<button>` is not one: its content is the document's, laid out and
|
|
2317
|
+
// drawn like any box's (the UA sheet's `button` rule), where an
|
|
2318
|
+
// `<input type=submit>` has only a value to show and is a widget.
|
|
673
2319
|
case 'input': {
|
|
674
2320
|
const type = (attr(el, 'type') ?? 'text').toLowerCase();
|
|
675
2321
|
if (type === 'checkbox') return 'checkbox';
|
|
@@ -684,87 +2330,34 @@ function replacedKind(el: Element, tag: string): ReplacedKind {
|
|
|
684
2330
|
}
|
|
685
2331
|
}
|
|
686
2332
|
|
|
687
|
-
const ROMAN: [number, string][] = [
|
|
688
|
-
[1000, 'm'],
|
|
689
|
-
[900, 'cm'],
|
|
690
|
-
[500, 'd'],
|
|
691
|
-
[400, 'cd'],
|
|
692
|
-
[100, 'c'],
|
|
693
|
-
[90, 'xc'],
|
|
694
|
-
[50, 'l'],
|
|
695
|
-
[40, 'xl'],
|
|
696
|
-
[10, 'x'],
|
|
697
|
-
[9, 'ix'],
|
|
698
|
-
[5, 'v'],
|
|
699
|
-
[4, 'iv'],
|
|
700
|
-
[1, 'i'],
|
|
701
|
-
];
|
|
702
|
-
|
|
703
2333
|
/**
|
|
704
|
-
* The marker a `list-item` draws
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
2334
|
+
* The marker a `list-item` draws: its `list-item` counter written in its
|
|
2335
|
+
* `list-style-type`, between the style's prefix and suffix, or the string
|
|
2336
|
+
* the type is. The counter is the builder's (`CounterScopes`), which is
|
|
2337
|
+
* what lets `value` on an `<li>`, `start` and `reversed` on an `<ol>`, a
|
|
2338
|
+
* list in a list and an author's own `counter-reset` and `counter-set` all
|
|
2339
|
+
* count as a browser counts them. A suffix that ends in a space — `. `, a
|
|
2340
|
+
* bullet's — is written without it, and the layout sets the marker off by
|
|
2341
|
+
* its own gap; one that does not, `、`, sets the marker against the text.
|
|
708
2342
|
*/
|
|
709
2343
|
function markerFor(
|
|
710
|
-
el: Element,
|
|
711
2344
|
style: ComputedStyle,
|
|
712
|
-
|
|
713
|
-
|
|
2345
|
+
n: number,
|
|
2346
|
+
styles: CounterStyles,
|
|
2347
|
+
): { text: string; flush: boolean } {
|
|
714
2348
|
const type = style.listStyleType;
|
|
715
|
-
if (type === 'none') return '';
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
case 'lower-alpha':
|
|
728
|
-
case 'lower-latin':
|
|
729
|
-
return `${alpha(n).toLowerCase()}.`;
|
|
730
|
-
case 'upper-alpha':
|
|
731
|
-
case 'upper-latin':
|
|
732
|
-
return `${alpha(n)}.`;
|
|
733
|
-
case 'lower-roman':
|
|
734
|
-
return `${roman(n)}.`;
|
|
735
|
-
case 'upper-roman':
|
|
736
|
-
return `${roman(n).toUpperCase()}.`;
|
|
737
|
-
case 'circle':
|
|
738
|
-
return '◦';
|
|
739
|
-
case 'square':
|
|
740
|
-
return '▪';
|
|
741
|
-
case 'disc':
|
|
742
|
-
default:
|
|
743
|
-
return '•';
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
function alpha(n: number): string {
|
|
748
|
-
let out = '';
|
|
749
|
-
let v = Math.max(1, n);
|
|
750
|
-
while (v > 0) {
|
|
751
|
-
const rem = (v - 1) % 26;
|
|
752
|
-
out = String.fromCharCode(65 + rem) + out;
|
|
753
|
-
v = Math.floor((v - 1) / 26);
|
|
754
|
-
}
|
|
755
|
-
return out;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
function roman(n: number): string {
|
|
759
|
-
let v = Math.max(1, Math.min(3999, n));
|
|
760
|
-
let out = '';
|
|
761
|
-
for (const [value, sym] of ROMAN) {
|
|
762
|
-
while (v >= value) {
|
|
763
|
-
out += sym;
|
|
764
|
-
v -= value;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
return out;
|
|
2349
|
+
if (type === 'none') return { text: '', flush: false };
|
|
2350
|
+
if (type.startsWith('"')) return { text: type.slice(1), flush: true };
|
|
2351
|
+
const { prefix, text, suffix } = styles.marker(
|
|
2352
|
+
n,
|
|
2353
|
+
type,
|
|
2354
|
+
style.direction === 'rtl',
|
|
2355
|
+
);
|
|
2356
|
+
const spaced = /\s$/.test(suffix);
|
|
2357
|
+
return {
|
|
2358
|
+
text: prefix + text + (spaced ? suffix.trimEnd() : suffix),
|
|
2359
|
+
flush: !spaced,
|
|
2360
|
+
};
|
|
768
2361
|
}
|
|
769
2362
|
|
|
770
2363
|
/**
|
|
@@ -810,23 +2403,35 @@ function assignSubtreeRanges(box: Box): { start: number; end: number } {
|
|
|
810
2403
|
* straight to `<tr>`, and CSS says the missing row group is generated
|
|
811
2404
|
* rather than the rows being dropped.
|
|
812
2405
|
*/
|
|
813
|
-
function fixUp(box: Box): void {
|
|
814
|
-
for (const child of box.children) fixUp(child);
|
|
2406
|
+
function fixUp(box: Box, anonymous: AnonymousStyle): void {
|
|
2407
|
+
for (const child of box.children) fixUp(child, anonymous);
|
|
2408
|
+
fixUpOwn(box, anonymous);
|
|
2409
|
+
}
|
|
815
2410
|
|
|
2411
|
+
/** `fixUp` of a box whose children have had theirs. */
|
|
2412
|
+
function fixUpOwn(box: Box, anonymous: AnonymousStyle): void {
|
|
816
2413
|
if (box.kind === 'table') {
|
|
817
|
-
fixUpTable(box);
|
|
2414
|
+
fixUpTable(box, anonymous);
|
|
818
2415
|
return;
|
|
819
2416
|
}
|
|
820
2417
|
if (box.kind === 'table-row-group') {
|
|
821
|
-
wrapOrphans(box, 'table-row', (k) => k === 'table-row');
|
|
2418
|
+
wrapOrphans(box, 'table-row', (k) => k === 'table-row', anonymous);
|
|
822
2419
|
return;
|
|
823
2420
|
}
|
|
824
2421
|
if (box.kind === 'table-row') {
|
|
825
|
-
wrapOrphans(box, 'table-cell', (k) => k === 'table-cell');
|
|
2422
|
+
wrapOrphans(box, 'table-cell', (k) => k === 'table-cell', anonymous);
|
|
826
2423
|
return;
|
|
827
2424
|
}
|
|
2425
|
+
// a column group holds its columns, which are where they belong, and the
|
|
2426
|
+
// builder kept nothing else in it
|
|
2427
|
+
if (box.style.display === 'table-column-group') return;
|
|
828
2428
|
|
|
829
2429
|
if (!box.children.length) return;
|
|
2430
|
+
wrapTableParts(box, anonymous);
|
|
2431
|
+
// An inline box holds no block: a block in one is its block container's,
|
|
2432
|
+
// which breaks the inline box around it
|
|
2433
|
+
if (box.kind === 'inline') return;
|
|
2434
|
+
if (box.kind !== 'flex') breakInlines(box);
|
|
830
2435
|
let hasBlockLevel = false;
|
|
831
2436
|
let hasInlineLevel = false;
|
|
832
2437
|
for (const child of box.children) {
|
|
@@ -851,7 +2456,7 @@ function fixUp(box: Box): void {
|
|
|
851
2456
|
// finds itself; it does not force an anonymous block on its own.
|
|
852
2457
|
if (isBlockLevel(child) && !child.outOfFlow && !child.isFloat) {
|
|
853
2458
|
if (run) {
|
|
854
|
-
next.push(
|
|
2459
|
+
next.push(anonymousOf(box, 'block', run, anonymous));
|
|
855
2460
|
run = null;
|
|
856
2461
|
}
|
|
857
2462
|
next.push(child);
|
|
@@ -859,27 +2464,152 @@ function fixUp(box: Box): void {
|
|
|
859
2464
|
}
|
|
860
2465
|
// Whitespace between two blocks is not content and must not generate a
|
|
861
2466
|
// line box — `<div><p>a</p> <p>b</p></div>` has no blank line in it.
|
|
862
|
-
if (!run && child.kind === 'text' &&
|
|
2467
|
+
if (!run && child.kind === 'text' && isBlank(child.text)) continue;
|
|
863
2468
|
(run ??= []).push(child);
|
|
864
2469
|
}
|
|
865
2470
|
if (run) {
|
|
866
|
-
if (run.every((c) => c.kind === 'text' &&
|
|
2471
|
+
if (run.every((c) => c.kind === 'text' && isBlank(c.text))) {
|
|
867
2472
|
// trailing whitespace after the last block: same rule
|
|
868
2473
|
} else {
|
|
869
|
-
next.push(
|
|
2474
|
+
next.push(anonymousOf(box, 'block', run, anonymous));
|
|
870
2475
|
}
|
|
871
2476
|
}
|
|
872
2477
|
box.children = next;
|
|
873
2478
|
}
|
|
874
2479
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
2480
|
+
/**
|
|
2481
|
+
* Break a block container's inline children around the blocks in them
|
|
2482
|
+
* (CSS 2.1 9.2.1.1): an inline box with an in-flow block in it — at any
|
|
2483
|
+
* depth of inline box — becomes the pieces of it before, between and after
|
|
2484
|
+
* its blocks, and the blocks stand beside them as the container's own, for
|
|
2485
|
+
* the mixed-content rule to wrap the pieces in anonymous blocks.
|
|
2486
|
+
* `<font><p>One</p><p>Two</p></font>` laid its paragraphs side by side as
|
|
2487
|
+
* inline-blocks, and a link around a card's blocks set them in a line.
|
|
2488
|
+
*/
|
|
2489
|
+
function breakInlines(box: Box): void {
|
|
2490
|
+
let next: Box[] | null = null;
|
|
2491
|
+
for (let i = 0; i < box.children.length; i += 1) {
|
|
2492
|
+
const child = box.children[i];
|
|
2493
|
+
const pieces =
|
|
2494
|
+
child.kind === 'inline' && holdsBlock(child) ? breakAround(child) : null;
|
|
2495
|
+
if (!pieces) {
|
|
2496
|
+
next?.push(child);
|
|
2497
|
+
continue;
|
|
2498
|
+
}
|
|
2499
|
+
next ??= box.children.slice(0, i);
|
|
2500
|
+
for (const piece of pieces) {
|
|
2501
|
+
piece.parent = box;
|
|
2502
|
+
next.push(piece);
|
|
2503
|
+
}
|
|
881
2504
|
}
|
|
882
|
-
|
|
2505
|
+
if (next) box.children = next;
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
/** Whether an inline box has an in-flow block in it, at any depth of
|
|
2509
|
+
* inline box: asked first, and without making anything, since nearly
|
|
2510
|
+
* every inline box has none. */
|
|
2511
|
+
function holdsBlock(inline: Box): boolean {
|
|
2512
|
+
for (const child of inline.children) {
|
|
2513
|
+
if (child.kind === 'inline') {
|
|
2514
|
+
if (holdsBlock(child)) return true;
|
|
2515
|
+
} else if (isBlockLevel(child) && !child.outOfFlow && !child.isFloat) {
|
|
2516
|
+
return true;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
return false;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
/**
|
|
2523
|
+
* An inline box broken around the in-flow blocks in it: its pieces, each a
|
|
2524
|
+
* box of its element and style holding the content between two blocks,
|
|
2525
|
+
* with the blocks between them; null where it has none, which is nearly
|
|
2526
|
+
* every inline box. A piece has no edge on a side a block cut: the start of
|
|
2527
|
+
* the first and the end of the last are the box's own.
|
|
2528
|
+
*/
|
|
2529
|
+
function breakAround(inline: Box): Box[] | null {
|
|
2530
|
+
let out: Box[] | null = null;
|
|
2531
|
+
const pieces: Box[] = [];
|
|
2532
|
+
let run: Box[] = [];
|
|
2533
|
+
const close = (): void => {
|
|
2534
|
+
const piece = new Box('inline', inline.el, inline.style);
|
|
2535
|
+
piece.pseudo = inline.pseudo;
|
|
2536
|
+
for (const child of run) piece.append(child);
|
|
2537
|
+
pieces.push(piece);
|
|
2538
|
+
(out ??= []).push(piece);
|
|
2539
|
+
run = [];
|
|
2540
|
+
};
|
|
2541
|
+
const blocks: Box[] = [];
|
|
2542
|
+
for (const child of inline.children) {
|
|
2543
|
+
if (isBlockLevel(child) && !child.outOfFlow && !child.isFloat) {
|
|
2544
|
+
close();
|
|
2545
|
+
out!.push(child);
|
|
2546
|
+
blocks.push(child);
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
const inner =
|
|
2550
|
+
child.kind === 'inline' && holdsBlock(child) ? breakAround(child) : null;
|
|
2551
|
+
if (!inner) {
|
|
2552
|
+
run.push(child);
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
for (const piece of inner) {
|
|
2556
|
+
if (piece.kind === 'inline') {
|
|
2557
|
+
run.push(piece);
|
|
2558
|
+
} else {
|
|
2559
|
+
close();
|
|
2560
|
+
out!.push(piece);
|
|
2561
|
+
blocks.push(piece);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
if (!out) return null;
|
|
2566
|
+
close();
|
|
2567
|
+
for (let i = 0; i < pieces.length; i += 1) {
|
|
2568
|
+
pieces[i].cut = (i > 0 ? 1 : 0) | (i < pieces.length - 1 ? 2 : 0);
|
|
2569
|
+
}
|
|
2570
|
+
if (isRelative(inline.style)) CUT_BLOCKS.set(pieces[0], blocks);
|
|
2571
|
+
const fade = inline.style.opacity;
|
|
2572
|
+
if (fade < 1) {
|
|
2573
|
+
for (const block of blocks) {
|
|
2574
|
+
FADED_BLOCKS.set(block, (FADED_BLOCKS.get(block) ?? 1) * fade);
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
return out;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
/**
|
|
2581
|
+
* The style of an anonymous box inside a parent: what the parent passes on
|
|
2582
|
+
* by inheritance, and every other property at its initial value, as CSS 2.1
|
|
2583
|
+
* gives anonymous boxes theirs (9.2.1.1, 17.2.1). An anonymous box that took
|
|
2584
|
+
* the parent's style itself took its height, its padding and borders, its
|
|
2585
|
+
* background, its relative offset and its opacity a second time — the text
|
|
2586
|
+
* in `<div style="padding: 20px">text<p>…</p></div>` sat 40px in, and the
|
|
2587
|
+
* paragraph after it the height of the div further down. One per parent
|
|
2588
|
+
* style, which the cascade already shares between elements.
|
|
2589
|
+
*/
|
|
2590
|
+
type AnonymousStyle = (
|
|
2591
|
+
parent: Box,
|
|
2592
|
+
display: ComputedStyle['display'],
|
|
2593
|
+
) => ComputedStyle;
|
|
2594
|
+
|
|
2595
|
+
function anonymousStyles(initial: ComputedStyle): AnonymousStyle {
|
|
2596
|
+
const made = new WeakMap<ComputedStyle, Map<string, ComputedStyle>>();
|
|
2597
|
+
return (parent, display) => {
|
|
2598
|
+
let byDisplay = made.get(parent.style);
|
|
2599
|
+
if (!byDisplay) {
|
|
2600
|
+
byDisplay = new Map();
|
|
2601
|
+
made.set(parent.style, byDisplay);
|
|
2602
|
+
}
|
|
2603
|
+
let style = byDisplay.get(display);
|
|
2604
|
+
if (!style) {
|
|
2605
|
+
// `display` is the one property it does not start from: it is the
|
|
2606
|
+
// box the fix-up made, and layout asks the style what a box is
|
|
2607
|
+
style = inherit(parent.style, initial);
|
|
2608
|
+
style.display = display;
|
|
2609
|
+
byDisplay.set(display, style);
|
|
2610
|
+
}
|
|
2611
|
+
return style;
|
|
2612
|
+
};
|
|
883
2613
|
}
|
|
884
2614
|
|
|
885
2615
|
function isBlockLevel(box: Box): boolean {
|
|
@@ -894,6 +2624,17 @@ function isBlockLevel(box: Box): boolean {
|
|
|
894
2624
|
// An `inline-block` or `inline-flex` is a block *container* with an
|
|
895
2625
|
// inline-level outer role, so it belongs to the inline run around it.
|
|
896
2626
|
return !isInlineLevelDisplay(box.style.display);
|
|
2627
|
+
case 'replaced':
|
|
2628
|
+
// an image is inline unless it is told otherwise, and then it is a
|
|
2629
|
+
// block: `img { display: block }`, which mail writes to lose the gap
|
|
2630
|
+
// under its images, stacks them. A table's part is no display it
|
|
2631
|
+
// can take, and one told to be a cell is inline (CSS Display 3,
|
|
2632
|
+
// 2.4), which is what a table wraps a cell around.
|
|
2633
|
+
return (
|
|
2634
|
+
box.style.display !== 'inline' &&
|
|
2635
|
+
!isInlineLevelDisplay(box.style.display) &&
|
|
2636
|
+
!box.style.display.startsWith('table-')
|
|
2637
|
+
);
|
|
897
2638
|
default:
|
|
898
2639
|
return false;
|
|
899
2640
|
}
|
|
@@ -912,6 +2653,7 @@ function wrapOrphans(
|
|
|
912
2653
|
box: Box,
|
|
913
2654
|
kind: BoxKind,
|
|
914
2655
|
accept: (k: BoxKind) => boolean,
|
|
2656
|
+
anonymous: AnonymousStyle,
|
|
915
2657
|
): void {
|
|
916
2658
|
let needed = false;
|
|
917
2659
|
for (const child of box.children) {
|
|
@@ -921,26 +2663,56 @@ function wrapOrphans(
|
|
|
921
2663
|
}
|
|
922
2664
|
}
|
|
923
2665
|
if (!needed) return;
|
|
2666
|
+
// White space goes only between two of the parts, or beside one at an
|
|
2667
|
+
// end (CSS 2.1 17.2.1, rule 1): beside a child the fix-up wraps, it is
|
|
2668
|
+
// that child's run's, so `<span>a</span> <span>b</span>` in a row is one
|
|
2669
|
+
// anonymous cell of `a b`, where it was `ab`
|
|
924
2670
|
const next: Box[] = [];
|
|
925
2671
|
let run: Box[] | null = null;
|
|
2672
|
+
let space: Box[] = [];
|
|
926
2673
|
for (const child of box.children) {
|
|
927
2674
|
if (accept(child.kind)) {
|
|
928
2675
|
if (run) {
|
|
929
|
-
|
|
2676
|
+
run.push(...space);
|
|
2677
|
+
next.push(anonymousOf(box, kind, run, anonymous));
|
|
930
2678
|
run = null;
|
|
931
2679
|
}
|
|
2680
|
+
space = [];
|
|
932
2681
|
next.push(child);
|
|
933
2682
|
continue;
|
|
934
2683
|
}
|
|
935
|
-
if (isDroppableWhitespace(child))
|
|
936
|
-
|
|
2684
|
+
if (isDroppableWhitespace(child)) {
|
|
2685
|
+
space.push(child);
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2688
|
+
(run ??= []).push(...space, child);
|
|
2689
|
+
space = [];
|
|
2690
|
+
}
|
|
2691
|
+
if (run) {
|
|
2692
|
+
run.push(...space);
|
|
2693
|
+
next.push(anonymousOf(box, kind, run, anonymous));
|
|
937
2694
|
}
|
|
938
|
-
if (run) next.push(anonymousOf(box, kind, run));
|
|
939
2695
|
box.children = next;
|
|
2696
|
+
// A cell made here is a block container the fix-up has not been to: its
|
|
2697
|
+
// children had theirs, as the table's, before it held them. Text beside a
|
|
2698
|
+
// block in it goes in an anonymous block as in any other (CSS 2.1
|
|
2699
|
+
// 9.2.1.1) — `<span style="display: inline-table">bcd<div>x</div>` laid
|
|
2700
|
+
// `bcd` out as nothing, and drew the table as wide as the `x`.
|
|
2701
|
+
if (kind === 'table-cell') {
|
|
2702
|
+
for (const child of next) {
|
|
2703
|
+
if (child.kind === 'table-cell' && !child.el) fixUpOwn(child, anonymous);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
940
2706
|
}
|
|
941
2707
|
|
|
942
|
-
function anonymousOf(
|
|
943
|
-
|
|
2708
|
+
function anonymousOf(
|
|
2709
|
+
parent: Box,
|
|
2710
|
+
kind: BoxKind,
|
|
2711
|
+
run: Box[],
|
|
2712
|
+
anonymous: AnonymousStyle,
|
|
2713
|
+
display: ComputedStyle['display'] = kind as ComputedStyle['display'],
|
|
2714
|
+
): Box {
|
|
2715
|
+
const box = new Box(kind, null, anonymous(parent, display));
|
|
944
2716
|
box.parent = parent;
|
|
945
2717
|
for (const child of run) {
|
|
946
2718
|
child.parent = box;
|
|
@@ -949,43 +2721,181 @@ function anonymousOf(parent: Box, kind: BoxKind, run: Box[]): Box {
|
|
|
949
2721
|
return box;
|
|
950
2722
|
}
|
|
951
2723
|
|
|
2724
|
+
/** White space alone, which the table fix-up drops between the parts of a
|
|
2725
|
+
* table whether it is kept or not (CSS 2.1 17.2.1, rule 1). Kept, under
|
|
2726
|
+
* `white-space: pre`, it made a cell of the line break before every row. */
|
|
952
2727
|
function isDroppableWhitespace(box: Box): boolean {
|
|
953
|
-
return box.kind === 'text' &&
|
|
2728
|
+
return box.kind === 'text' && isBlank(box.text);
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
/**
|
|
2732
|
+
* Whether a text is white space alone: CSS's white space, which is the
|
|
2733
|
+
* space, the tab and the three line breaks and nothing else (CSS Text 3,
|
|
2734
|
+
* 4.1). JavaScript's `\s` and `trim` take in the no-break space and the
|
|
2735
|
+
* other Unicode spaces too, so a `<p> </p>` spacer or a
|
|
2736
|
+
* `<td> </td>` beside a block was dropped as if it held nothing. Tested
|
|
2737
|
+
* rather than trimmed, which copies a paragraph's text to find out.
|
|
2738
|
+
*/
|
|
2739
|
+
export function isBlank(text: string): boolean {
|
|
2740
|
+
return BLANK.test(text);
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
const BLANK = /^[ \t\n\r\f]*$/;
|
|
2744
|
+
|
|
2745
|
+
/** White space that collapsing would change: anything but a lone space.
|
|
2746
|
+
* Most of a document's text has none, and is its own collapsed form. */
|
|
2747
|
+
const COLLAPSIBLE = /[\t\n\r\f]| {2}/;
|
|
2748
|
+
|
|
2749
|
+
function isRelative(style: ComputedStyle): boolean {
|
|
2750
|
+
return style.position === 'relative' || style.position === 'sticky';
|
|
954
2751
|
}
|
|
955
2752
|
|
|
956
|
-
|
|
2753
|
+
/** Whether `position: relative` moves a box: an inset is set. An inline box
|
|
2754
|
+
* that is moved has its text laid out apart (`collect`), so one whose
|
|
2755
|
+
* insets resolve to nothing still costs its paragraph the layout of a line
|
|
2756
|
+
* at a time, which a box with every inset `auto` does not. */
|
|
2757
|
+
export function isOffset(style: ComputedStyle): boolean {
|
|
2758
|
+
if (!isRelative(style)) return false;
|
|
2759
|
+
return (
|
|
2760
|
+
style.top !== AUTO ||
|
|
2761
|
+
style.right !== AUTO ||
|
|
2762
|
+
style.bottom !== AUTO ||
|
|
2763
|
+
style.left !== AUTO
|
|
2764
|
+
);
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
/** Whether a style may put its box under its stacking context's flow: a
|
|
2768
|
+
* negative `z-index` on a positioned box, or on a flex item, which that
|
|
2769
|
+
* makes a stacking context as it is (`stacksLayers`). */
|
|
2770
|
+
function isNegative(style: ComputedStyle): boolean {
|
|
2771
|
+
return typeof style.zIndex === 'number' && style.zIndex < 0;
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
/** A box that belongs inside a table: a row group, a row, a cell, a
|
|
2775
|
+
* caption or a column. Asked of every child of every box in a build, so
|
|
2776
|
+
* a text or an inline box is answered from its kind, without its style. */
|
|
2777
|
+
function isTablePart(box: Box): boolean {
|
|
2778
|
+
switch (box.kind) {
|
|
2779
|
+
case 'table-row-group':
|
|
2780
|
+
case 'table-row':
|
|
2781
|
+
case 'table-cell':
|
|
2782
|
+
case 'table-caption':
|
|
2783
|
+
return !box.outOfFlow && !box.isFloat;
|
|
2784
|
+
case 'block': {
|
|
2785
|
+
if (box.outOfFlow || box.isFloat) return false;
|
|
2786
|
+
const display = box.style.display;
|
|
2787
|
+
return display === 'table-column' || display === 'table-column-group';
|
|
2788
|
+
}
|
|
2789
|
+
default:
|
|
2790
|
+
return false;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
/**
|
|
2795
|
+
* Table parts outside a table get one around them (CSS 2.1 17.2.1, rule 3):
|
|
2796
|
+
* each run of them — the white space between them no part of it — is
|
|
2797
|
+
* wrapped in an anonymous table, an inline one where the parent is inline,
|
|
2798
|
+
* and the table is then completed as any other. Laid out on their own, two
|
|
2799
|
+
* cells side by side in a line were two inline-blocks with a space between
|
|
2800
|
+
* them, and in a block two blocks, one above the other.
|
|
2801
|
+
*/
|
|
2802
|
+
function wrapTableParts(box: Box, anonymous: AnonymousStyle): void {
|
|
2803
|
+
if (!box.children.some(isTablePart)) return;
|
|
2804
|
+
const display = box.kind === 'inline' ? 'inline-table' : 'table';
|
|
2805
|
+
const next: Box[] = [];
|
|
2806
|
+
let run: Box[] | null = null;
|
|
2807
|
+
let space: Box[] = [];
|
|
2808
|
+
const flush = (): void => {
|
|
2809
|
+
if (!run) return;
|
|
2810
|
+
const table = anonymousOf(box, 'table', run, anonymous, display);
|
|
2811
|
+
fixUpTable(table, anonymous);
|
|
2812
|
+
next.push(table);
|
|
2813
|
+
run = null;
|
|
2814
|
+
};
|
|
2815
|
+
for (const child of box.children) {
|
|
2816
|
+
if (isTablePart(child)) {
|
|
2817
|
+
run ??= [];
|
|
2818
|
+
run.push(child);
|
|
2819
|
+
space = [];
|
|
2820
|
+
continue;
|
|
2821
|
+
}
|
|
2822
|
+
if (run && isDroppableWhitespace(child)) {
|
|
2823
|
+
// held back: between two parts it goes, after the last it stays
|
|
2824
|
+
space.push(child);
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
flush();
|
|
2828
|
+
next.push(...space, child);
|
|
2829
|
+
space = [];
|
|
2830
|
+
}
|
|
2831
|
+
flush();
|
|
2832
|
+
next.push(...space);
|
|
2833
|
+
box.children = next;
|
|
2834
|
+
for (const child of next) child.parent = box;
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
function fixUpTable(table: Box, anonymous: AnonymousStyle): void {
|
|
957
2838
|
const groups: Box[] = [];
|
|
958
2839
|
const captions: Box[] = [];
|
|
2840
|
+
const columns: Box[] = [];
|
|
959
2841
|
let looseRows: Box[] | null = null;
|
|
2842
|
+
let looseCells: Box[] | null = null;
|
|
2843
|
+
const flushCells = (): void => {
|
|
2844
|
+
if (!looseCells) return;
|
|
2845
|
+
(looseRows ??= []).push(
|
|
2846
|
+
anonymousOf(table, 'table-row', looseCells, anonymous),
|
|
2847
|
+
);
|
|
2848
|
+
looseCells = null;
|
|
2849
|
+
};
|
|
960
2850
|
for (const child of table.children) {
|
|
961
|
-
|
|
2851
|
+
const display = child.style.display;
|
|
2852
|
+
// an image told to be a column is an inline image, as one told to be a
|
|
2853
|
+
// cell is (`flowOf`): taken for a column, it was never drawn
|
|
2854
|
+
if (
|
|
2855
|
+
(display === 'table-column' || display === 'table-column-group') &&
|
|
2856
|
+
child.kind !== 'replaced'
|
|
2857
|
+
) {
|
|
2858
|
+
// A column is the table's, beside its rows: it lays out nothing and
|
|
2859
|
+
// paints nothing (17.2.1). Taken for a stray child, it was wrapped in
|
|
2860
|
+
// a row of its own and drawn as a cell.
|
|
2861
|
+
columns.push(child);
|
|
2862
|
+
} else if (child.kind === 'table-row-group') {
|
|
2863
|
+
flushCells();
|
|
962
2864
|
if (looseRows) {
|
|
963
|
-
groups.push(
|
|
2865
|
+
groups.push(
|
|
2866
|
+
anonymousOf(table, 'table-row-group', looseRows, anonymous),
|
|
2867
|
+
);
|
|
964
2868
|
looseRows = null;
|
|
965
2869
|
}
|
|
966
2870
|
groups.push(child);
|
|
967
2871
|
} else if (child.kind === 'table-caption') {
|
|
968
2872
|
captions.push(child);
|
|
969
2873
|
} else if (isDroppableWhitespace(child)) {
|
|
2874
|
+
// after a loose child it goes with the loose children, and the cells
|
|
2875
|
+
// they are wrapped in say whether it stays (`wrapOrphans`)
|
|
2876
|
+
if (looseCells) looseCells.push(child);
|
|
970
2877
|
continue;
|
|
2878
|
+
} else if (child.kind === 'table-row') {
|
|
2879
|
+
flushCells();
|
|
2880
|
+
(looseRows ??= []).push(child);
|
|
971
2881
|
} else {
|
|
972
|
-
//
|
|
973
|
-
//
|
|
974
|
-
//
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
? child
|
|
978
|
-
: anonymousOf(table, 'table-row', [child]);
|
|
979
|
-
(looseRows ??= []).push(row);
|
|
2882
|
+
// Anything else that ended up here is a cell, or goes in one, and a
|
|
2883
|
+
// run of them shares an anonymous row — which is how a browser
|
|
2884
|
+
// rescues `<table>text</table>`, and how three cells in a table
|
|
2885
|
+
// with no row are one row of three rather than three rows of one.
|
|
2886
|
+
(looseCells ??= []).push(child);
|
|
980
2887
|
}
|
|
981
2888
|
}
|
|
982
|
-
|
|
2889
|
+
flushCells();
|
|
2890
|
+
if (looseRows) {
|
|
2891
|
+
groups.push(anonymousOf(table, 'table-row-group', looseRows, anonymous));
|
|
2892
|
+
}
|
|
983
2893
|
for (const group of groups) {
|
|
984
|
-
wrapOrphans(group, 'table-row', (k) => k === 'table-row');
|
|
2894
|
+
wrapOrphans(group, 'table-row', (k) => k === 'table-row', anonymous);
|
|
985
2895
|
for (const row of group.children) {
|
|
986
|
-
wrapOrphans(row, 'table-cell', (k) => k === 'table-cell');
|
|
2896
|
+
wrapOrphans(row, 'table-cell', (k) => k === 'table-cell', anonymous);
|
|
987
2897
|
}
|
|
988
2898
|
}
|
|
989
|
-
table.children = [...captions, ...groups];
|
|
2899
|
+
table.children = [...captions, ...columns, ...groups];
|
|
990
2900
|
for (const child of table.children) child.parent = table;
|
|
991
2901
|
}
|