@react-x11/components 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/charts/node.d.ts +11 -1
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +44 -14
- package/dist/charts/node.js.map +1 -1
- package/dist/charts/render.d.ts +12 -3
- package/dist/charts/render.d.ts.map +1 -1
- package/dist/charts/render.js +94 -25
- package/dist/charts/render.js.map +1 -1
- package/dist/code/index.d.ts.map +1 -1
- package/dist/code/index.js +27 -24
- package/dist/code/index.js.map +1 -1
- package/dist/code-editor/completion.d.ts.map +1 -1
- package/dist/code-editor/completion.js +3 -2
- package/dist/code-editor/completion.js.map +1 -1
- package/dist/code-editor/index.d.ts.map +1 -1
- package/dist/code-editor/index.js +19 -0
- package/dist/code-editor/index.js.map +1 -1
- package/dist/code-editor/node.d.ts +177 -7
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +1329 -164
- package/dist/code-editor/node.js.map +1 -1
- package/dist/code-language/index.d.ts +1 -1
- package/dist/code-language/index.d.ts.map +1 -1
- package/dist/code-language/index.js +1 -1
- package/dist/code-language/index.js.map +1 -1
- package/dist/code-language/languages/glsl.d.ts.map +1 -1
- package/dist/code-language/languages/glsl.js +10 -2
- package/dist/code-language/languages/glsl.js.map +1 -1
- package/dist/code-language/languages/javascript.d.ts.map +1 -1
- package/dist/code-language/languages/javascript.js +18 -3
- package/dist/code-language/languages/javascript.js.map +1 -1
- package/dist/code-language/languages/json.d.ts.map +1 -1
- package/dist/code-language/languages/json.js +10 -2
- package/dist/code-language/languages/json.js.map +1 -1
- package/dist/code-language/languages/shell.d.ts.map +1 -1
- package/dist/code-language/languages/shell.js +10 -2
- package/dist/code-language/languages/shell.js.map +1 -1
- package/dist/code-language/languages/sql.d.ts.map +1 -1
- package/dist/code-language/languages/sql.js +20 -2
- package/dist/code-language/languages/sql.js.map +1 -1
- package/dist/code-language/runs.d.ts +31 -0
- package/dist/code-language/runs.d.ts.map +1 -1
- package/dist/code-language/runs.js +105 -0
- package/dist/code-language/runs.js.map +1 -1
- package/dist/code-language/stream.d.ts +22 -0
- package/dist/code-language/stream.d.ts.map +1 -1
- package/dist/code-language/stream.js +259 -9
- package/dist/code-language/stream.js.map +1 -1
- package/dist/code-language/types.d.ts +5 -4
- package/dist/code-language/types.d.ts.map +1 -1
- package/dist/codeblock/index.d.ts +26 -2
- package/dist/codeblock/index.d.ts.map +1 -1
- package/dist/codeblock/index.js +57 -4
- package/dist/codeblock/index.js.map +1 -1
- package/dist/flow/draw.d.ts +50 -0
- package/dist/flow/draw.d.ts.map +1 -1
- package/dist/flow/draw.js +132 -33
- package/dist/flow/draw.js.map +1 -1
- package/dist/flow/gl/color.d.ts +13 -0
- package/dist/flow/gl/color.d.ts.map +1 -0
- package/dist/flow/gl/color.js +82 -0
- package/dist/flow/gl/color.js.map +1 -0
- package/dist/flow/gl/index.d.ts +60 -0
- package/dist/flow/gl/index.d.ts.map +1 -0
- package/dist/flow/gl/index.js +283 -0
- package/dist/flow/gl/index.js.map +1 -0
- package/dist/flow/gl/pack.d.ts +133 -0
- package/dist/flow/gl/pack.d.ts.map +1 -0
- package/dist/flow/gl/pack.js +408 -0
- package/dist/flow/gl/pack.js.map +1 -0
- package/dist/flow/gl/renderer.d.ts +95 -0
- package/dist/flow/gl/renderer.d.ts.map +1 -0
- package/dist/flow/gl/renderer.js +459 -0
- package/dist/flow/gl/renderer.js.map +1 -0
- package/dist/flow/gl/shaders.d.ts +79 -0
- package/dist/flow/gl/shaders.d.ts.map +1 -0
- package/dist/flow/gl/shaders.js +346 -0
- package/dist/flow/gl/shaders.js.map +1 -0
- package/dist/flow/gl/text.d.ts +318 -0
- package/dist/flow/gl/text.d.ts.map +1 -0
- package/dist/flow/gl/text.js +1103 -0
- package/dist/flow/gl/text.js.map +1 -0
- package/dist/flow/index.d.ts +1 -1
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +619 -64
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/node.d.ts +621 -72
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +2253 -1018
- package/dist/flow/node.js.map +1 -1
- package/dist/flow/paint.d.ts +35 -0
- package/dist/flow/paint.d.ts.map +1 -0
- package/dist/flow/paint.js +393 -0
- package/dist/flow/paint.js.map +1 -0
- package/dist/flow/paths.d.ts +14 -0
- package/dist/flow/paths.d.ts.map +1 -1
- package/dist/flow/paths.js +45 -19
- package/dist/flow/paths.js.map +1 -1
- package/dist/flow/scene.d.ts +584 -0
- package/dist/flow/scene.d.ts.map +1 -0
- package/dist/flow/scene.js +1453 -0
- package/dist/flow/scene.js.map +1 -0
- package/dist/flow/types.d.ts +111 -1
- package/dist/flow/types.d.ts.map +1 -1
- package/dist/html/controls.d.ts +28 -0
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +52 -2
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/calc.d.ts +14 -0
- package/dist/html/css/calc.d.ts.map +1 -0
- package/dist/html/css/calc.js +344 -0
- package/dist/html/css/calc.js.map +1 -0
- package/dist/html/css/cascade.d.ts +262 -6
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +1373 -67
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +23 -0
- package/dist/html/css/color.d.ts.map +1 -0
- package/dist/html/css/color.js +740 -0
- package/dist/html/css/color.js.map +1 -0
- package/dist/html/css/content.d.ts +92 -0
- package/dist/html/css/content.d.ts.map +1 -0
- package/dist/html/css/content.js +388 -0
- package/dist/html/css/content.js.map +1 -0
- package/dist/html/css/counter-styles.d.ts +76 -0
- package/dist/html/css/counter-styles.d.ts.map +1 -0
- package/dist/html/css/counter-styles.js +893 -0
- package/dist/html/css/counter-styles.js.map +1 -0
- package/dist/html/css/decode.d.ts +12 -0
- package/dist/html/css/decode.d.ts.map +1 -0
- package/dist/html/css/decode.js +116 -0
- package/dist/html/css/decode.js.map +1 -0
- package/dist/html/css/parse.d.ts +110 -10
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +1473 -180
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +501 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +3729 -206
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +65 -16
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +37 -3
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +122 -48
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/css/vars.d.ts +52 -0
- package/dist/html/css/vars.d.ts.map +1 -0
- package/dist/html/css/vars.js +358 -0
- package/dist/html/css/vars.js.map +1 -0
- package/dist/html/dom.d.ts +30 -4
- package/dist/html/dom.d.ts.map +1 -1
- package/dist/html/dom.js +278 -17
- package/dist/html/dom.js.map +1 -1
- package/dist/html/fonts.d.ts +106 -0
- package/dist/html/fonts.d.ts.map +1 -0
- package/dist/html/fonts.js +565 -0
- package/dist/html/fonts.js.map +1 -0
- package/dist/html/gif.d.ts +15 -0
- package/dist/html/gif.d.ts.map +1 -0
- package/dist/html/gif.js +229 -0
- package/dist/html/gif.js.map +1 -0
- package/dist/html/index.d.ts +50 -11
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +115 -9
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +226 -3
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +3077 -219
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +367 -17
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +1742 -182
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +58 -0
- package/dist/html/layout/cache.d.ts.map +1 -0
- package/dist/html/layout/cache.js +224 -0
- package/dist/html/layout/cache.js.map +1 -0
- package/dist/html/layout/collapse.d.ts +45 -0
- package/dist/html/layout/collapse.d.ts.map +1 -0
- package/dist/html/layout/collapse.js +246 -0
- package/dist/html/layout/collapse.js.map +1 -0
- package/dist/html/layout/css-grid.d.ts +7 -0
- package/dist/html/layout/css-grid.d.ts.map +1 -0
- package/dist/html/layout/css-grid.js +900 -0
- package/dist/html/layout/css-grid.js.map +1 -0
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +1025 -89
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/floats.d.ts +45 -6
- package/dist/html/layout/floats.d.ts.map +1 -1
- package/dist/html/layout/floats.js +113 -14
- package/dist/html/layout/floats.js.map +1 -1
- package/dist/html/layout/grid-lines.d.ts +39 -0
- package/dist/html/layout/grid-lines.d.ts.map +1 -0
- package/dist/html/layout/grid-lines.js +119 -0
- package/dist/html/layout/grid-lines.js.map +1 -0
- package/dist/html/layout/grid.d.ts +53 -0
- package/dist/html/layout/grid.d.ts.map +1 -0
- package/dist/html/layout/grid.js +153 -0
- package/dist/html/layout/grid.js.map +1 -0
- package/dist/html/layout/inline.d.ts +154 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +3598 -175
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/shaping.d.ts +6 -0
- package/dist/html/layout/shaping.d.ts.map +1 -0
- package/dist/html/layout/shaping.js +100 -0
- package/dist/html/layout/shaping.js.map +1 -0
- package/dist/html/layout/table.d.ts +3 -0
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +681 -175
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/layout/tracks.d.ts +44 -0
- package/dist/html/layout/tracks.d.ts.map +1 -0
- package/dist/html/layout/tracks.js +349 -0
- package/dist/html/layout/tracks.js.map +1 -0
- package/dist/html/node.d.ts +169 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +1429 -137
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +92 -3
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +3635 -107
- package/dist/html/paint.js.map +1 -1
- package/dist/html/resources.d.ts +60 -14
- package/dist/html/resources.d.ts.map +1 -1
- package/dist/html/resources.js +122 -36
- package/dist/html/resources.js.map +1 -1
- package/dist/html/surfaces.d.ts +23 -0
- package/dist/html/surfaces.d.ts.map +1 -0
- package/dist/html/surfaces.js +68 -0
- package/dist/html/surfaces.js.map +1 -0
- package/dist/html/svg.d.ts +80 -0
- package/dist/html/svg.d.ts.map +1 -0
- package/dist/html/svg.js +442 -0
- package/dist/html/svg.js.map +1 -0
- package/dist/html/url.d.ts +23 -0
- package/dist/html/url.d.ts.map +1 -0
- package/dist/html/url.js +80 -0
- package/dist/html/url.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/codelines.d.ts +42 -0
- package/dist/internal/codelines.d.ts.map +1 -0
- package/dist/internal/codelines.js +170 -0
- package/dist/internal/codelines.js.map +1 -0
- package/dist/internal/collate.d.ts +3 -0
- package/dist/internal/collate.d.ts.map +1 -0
- package/dist/internal/collate.js +11 -0
- package/dist/internal/collate.js.map +1 -0
- package/dist/internal/markdown/ast.d.ts +11 -0
- package/dist/internal/markdown/ast.d.ts.map +1 -1
- package/dist/internal/markdown/parse.d.ts.map +1 -1
- package/dist/internal/markdown/parse.js +109 -8
- package/dist/internal/markdown/parse.js.map +1 -1
- package/dist/internal/prewarm.d.ts +18 -0
- package/dist/internal/prewarm.d.ts.map +1 -0
- package/dist/internal/prewarm.js +42 -0
- package/dist/internal/prewarm.js.map +1 -0
- package/dist/internal/sdf.d.ts +20 -0
- package/dist/internal/sdf.d.ts.map +1 -0
- package/dist/internal/sdf.js +216 -0
- package/dist/internal/sdf.js.map +1 -0
- package/dist/internal/splice.d.ts +12 -0
- package/dist/internal/splice.d.ts.map +1 -0
- package/dist/internal/splice.js +23 -0
- package/dist/internal/splice.js.map +1 -0
- package/dist/internal/window.d.ts +12 -0
- package/dist/internal/window.d.ts.map +1 -1
- package/dist/internal/window.js +23 -6
- package/dist/internal/window.js.map +1 -1
- package/dist/maps/anchors.d.ts +6 -1
- package/dist/maps/anchors.d.ts.map +1 -1
- package/dist/maps/anchors.js +260 -1
- package/dist/maps/anchors.js.map +1 -1
- package/dist/maps/controller.d.ts +57 -0
- package/dist/maps/controller.d.ts.map +1 -1
- package/dist/maps/controller.js +97 -18
- package/dist/maps/controller.js.map +1 -1
- package/dist/maps/gl/buckets.d.ts +5 -1
- package/dist/maps/gl/buckets.d.ts.map +1 -1
- package/dist/maps/gl/buckets.js +5 -2
- package/dist/maps/gl/buckets.js.map +1 -1
- package/dist/maps/gl/build-worker.d.ts +2 -0
- package/dist/maps/gl/build-worker.d.ts.map +1 -1
- package/dist/maps/gl/build-worker.js +1 -1
- package/dist/maps/gl/build-worker.js.map +1 -1
- package/dist/maps/gl/cover.d.ts +23 -4
- package/dist/maps/gl/cover.d.ts.map +1 -1
- package/dist/maps/gl/cover.js +61 -48
- package/dist/maps/gl/cover.js.map +1 -1
- package/dist/maps/gl/placement.d.ts +21 -8
- package/dist/maps/gl/placement.d.ts.map +1 -1
- package/dist/maps/gl/placement.js +133 -58
- package/dist/maps/gl/placement.js.map +1 -1
- package/dist/maps/gl/renderer.d.ts +36 -4
- package/dist/maps/gl/renderer.d.ts.map +1 -1
- package/dist/maps/gl/renderer.js +81 -24
- package/dist/maps/gl/renderer.js.map +1 -1
- package/dist/maps/gl/shaders.d.ts +18 -17
- package/dist/maps/gl/shaders.d.ts.map +1 -1
- package/dist/maps/gl/shaders.js +34 -32
- package/dist/maps/gl/shaders.js.map +1 -1
- package/dist/maps/gl/store.d.ts +10 -0
- package/dist/maps/gl/store.d.ts.map +1 -1
- package/dist/maps/gl/store.js +47 -4
- package/dist/maps/gl/store.js.map +1 -1
- package/dist/maps/gl/text.d.ts +153 -24
- package/dist/maps/gl/text.d.ts.map +1 -1
- package/dist/maps/gl/text.js +379 -63
- package/dist/maps/gl/text.js.map +1 -1
- package/dist/maps/gl/view.d.ts +293 -2
- package/dist/maps/gl/view.d.ts.map +1 -1
- package/dist/maps/gl/view.js +317 -82
- package/dist/maps/gl/view.js.map +1 -1
- package/dist/maps/icons.d.ts +32 -0
- package/dist/maps/icons.d.ts.map +1 -0
- package/dist/maps/icons.js +151 -0
- package/dist/maps/icons.js.map +1 -0
- package/dist/maps/index.d.ts +2 -0
- package/dist/maps/index.d.ts.map +1 -1
- package/dist/maps/index.js +2 -0
- package/dist/maps/index.js.map +1 -1
- package/dist/maps/labels.d.ts +56 -4
- package/dist/maps/labels.d.ts.map +1 -1
- package/dist/maps/labels.js +213 -46
- package/dist/maps/labels.js.map +1 -1
- package/dist/maps/node.d.ts +17 -0
- package/dist/maps/node.d.ts.map +1 -1
- package/dist/maps/node.js +64 -30
- package/dist/maps/node.js.map +1 -1
- package/dist/maps/proj.d.ts +48 -0
- package/dist/maps/proj.d.ts.map +1 -1
- package/dist/maps/proj.js +73 -0
- package/dist/maps/proj.js.map +1 -1
- package/dist/maps/sources.d.ts +5 -3
- package/dist/maps/sources.d.ts.map +1 -1
- package/dist/maps/sources.js +5 -3
- package/dist/maps/sources.js.map +1 -1
- package/dist/maps/style.d.ts +35 -0
- package/dist/maps/style.d.ts.map +1 -1
- package/dist/maps/style.js.map +1 -1
- package/dist/maps/styles.d.ts +12 -0
- package/dist/maps/styles.d.ts.map +1 -1
- package/dist/maps/styles.js +120 -6
- package/dist/maps/styles.js.map +1 -1
- package/dist/maps/tiles.d.ts +31 -11
- package/dist/maps/tiles.d.ts.map +1 -1
- package/dist/maps/tiles.js +61 -27
- package/dist/maps/tiles.js.map +1 -1
- package/dist/maps/types.d.ts +28 -4
- package/dist/maps/types.d.ts.map +1 -1
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +209 -68
- package/dist/markdown/index.js.map +1 -1
- package/dist/markdown/spans.d.ts.map +1 -1
- package/dist/markdown/spans.js +6 -0
- package/dist/markdown/spans.js.map +1 -1
- package/dist/qml/containers.d.ts.map +1 -1
- package/dist/qml/containers.js +6 -1
- package/dist/qml/containers.js.map +1 -1
- package/dist/qml/interaction.d.ts.map +1 -1
- package/dist/qml/interaction.js +38 -5
- package/dist/qml/interaction.js.map +1 -1
- package/dist/qml/layouts.d.ts.map +1 -1
- package/dist/qml/layouts.js +21 -6
- package/dist/qml/layouts.js.map +1 -1
- package/dist/qml/qtquick.d.ts.map +1 -1
- package/dist/qml/qtquick.js +9 -2
- package/dist/qml/qtquick.js.map +1 -1
- package/dist/qml/react.d.ts.map +1 -1
- package/dist/qml/react.js +9 -7
- package/dist/qml/react.js.map +1 -1
- package/dist/reorder/clock.d.ts +7 -0
- package/dist/reorder/clock.d.ts.map +1 -0
- package/dist/reorder/clock.js +26 -0
- package/dist/reorder/clock.js.map +1 -0
- package/dist/reorder/index.d.ts +25 -3
- package/dist/reorder/index.d.ts.map +1 -1
- package/dist/reorder/index.js +64 -19
- package/dist/reorder/index.js.map +1 -1
- package/dist/rich-text-editor/index.d.ts +0 -11
- package/dist/rich-text-editor/index.d.ts.map +1 -1
- package/dist/rich-text-editor/index.js +38 -9
- package/dist/rich-text-editor/index.js.map +1 -1
- package/dist/rich-text-editor/keymap.d.ts.map +1 -1
- package/dist/rich-text-editor/keymap.js +2 -1
- package/dist/rich-text-editor/keymap.js.map +1 -1
- package/dist/rich-text-editor/keys.d.ts +17 -6
- package/dist/rich-text-editor/keys.d.ts.map +1 -1
- package/dist/rich-text-editor/keys.js +135 -26
- package/dist/rich-text-editor/keys.js.map +1 -1
- package/dist/rich-text-editor/markdown.d.ts.map +1 -1
- package/dist/rich-text-editor/markdown.js +30 -1
- package/dist/rich-text-editor/markdown.js.map +1 -1
- package/dist/rich-text-editor/marks.d.ts +9 -0
- package/dist/rich-text-editor/marks.d.ts.map +1 -0
- package/dist/rich-text-editor/marks.js +83 -0
- package/dist/rich-text-editor/marks.js.map +1 -0
- package/dist/rich-text-editor/render.d.ts.map +1 -1
- package/dist/rich-text-editor/render.js +28 -10
- package/dist/rich-text-editor/render.js.map +1 -1
- package/dist/rich-text-editor/replace.d.ts +13 -0
- package/dist/rich-text-editor/replace.d.ts.map +1 -0
- package/dist/rich-text-editor/replace.js +64 -0
- package/dist/rich-text-editor/replace.js.map +1 -0
- package/dist/rich-text-editor/toolbar.d.ts.map +1 -1
- package/dist/rich-text-editor/toolbar.js +1 -1
- package/dist/rich-text-editor/toolbar.js.map +1 -1
- package/dist/rich-text-editor/virtual.d.ts.map +1 -1
- package/dist/rich-text-editor/virtual.js +17 -0
- package/dist/rich-text-editor/virtual.js.map +1 -1
- package/dist/richtext/node.d.ts +46 -2
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +187 -4
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +13 -8
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +18 -8
- package/dist/richtext/runs.js.map +1 -1
- package/dist/table/index.d.ts.map +1 -1
- package/dist/table/index.js +23 -0
- package/dist/table/index.js.map +1 -1
- package/dist/table/rows.d.ts +3 -2
- package/dist/table/rows.d.ts.map +1 -1
- package/dist/table/rows.js +20 -6
- package/dist/table/rows.js.map +1 -1
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +4 -0
- package/dist/terminal/index.js.map +1 -1
- package/dist/terminal/vt/pty.d.ts +13 -2
- package/dist/terminal/vt/pty.d.ts.map +1 -1
- package/dist/terminal/vt/pty.js +18 -3
- package/dist/terminal/vt/pty.js.map +1 -1
- package/dist/terminal-output/index.d.ts.map +1 -1
- package/dist/terminal-output/index.js +103 -23
- package/dist/terminal-output/index.js.map +1 -1
- package/dist/terminal-output/runs.d.ts +4 -1
- package/dist/terminal-output/runs.d.ts.map +1 -1
- package/dist/terminal-output/runs.js +5 -1
- package/dist/terminal-output/runs.js.map +1 -1
- package/dist/three/canvas.d.ts.map +1 -1
- package/dist/three/canvas.js +85 -7
- package/dist/three/canvas.js.map +1 -1
- package/dist/three/events.d.ts +22 -4
- package/dist/three/events.d.ts.map +1 -1
- package/dist/three/events.js +34 -4
- package/dist/three/events.js.map +1 -1
- package/dist/tree/index.d.ts.map +1 -1
- package/dist/tree/index.js +17 -0
- package/dist/tree/index.js.map +1 -1
- package/package.json +42 -33
- package/src/charts/node.ts +62 -26
- package/src/charts/render.ts +120 -30
- package/src/code/index.ts +36 -32
- package/src/code-editor/completion.ts +3 -2
- package/src/code-editor/index.ts +19 -0
- package/src/code-editor/node.ts +1574 -184
- package/src/code-language/index.ts +1 -1
- package/src/code-language/languages/glsl.ts +12 -2
- package/src/code-language/languages/javascript.ts +21 -4
- package/src/code-language/languages/json.ts +12 -2
- package/src/code-language/languages/shell.ts +12 -2
- package/src/code-language/languages/sql.ts +22 -2
- package/src/code-language/runs.ts +111 -1
- package/src/code-language/stream.ts +257 -17
- package/src/code-language/types.ts +5 -4
- package/src/codeblock/index.ts +68 -4
- package/src/flow/draw.ts +167 -41
- package/src/flow/gl/color.ts +85 -0
- package/src/flow/gl/index.ts +342 -0
- package/src/flow/gl/pack.ts +585 -0
- package/src/flow/gl/renderer.ts +679 -0
- package/src/flow/gl/shaders.ts +371 -0
- package/src/flow/gl/text.ts +1295 -0
- package/src/flow/index.ts +794 -67
- package/src/flow/node.ts +2496 -1256
- package/src/flow/paint.ts +511 -0
- package/src/flow/paths.ts +80 -15
- package/src/flow/scene.ts +2199 -0
- package/src/flow/types.ts +113 -1
- package/src/html/controls.ts +73 -2
- package/src/html/css/calc.ts +385 -0
- package/src/html/css/cascade.ts +1481 -62
- package/src/html/css/color.ts +755 -0
- package/src/html/css/content.ts +417 -0
- package/src/html/css/counter-styles.ts +973 -0
- package/src/html/css/decode.ts +130 -0
- package/src/html/css/parse.ts +1514 -160
- package/src/html/css/style.ts +4131 -223
- package/src/html/css/ua.ts +66 -17
- package/src/html/css/values.ts +150 -50
- package/src/html/css/vars.ts +348 -0
- package/src/html/dom.ts +284 -17
- package/src/html/fonts.ts +641 -0
- package/src/html/gif.ts +233 -0
- package/src/html/index.ts +180 -20
- package/src/html/layout/block.ts +3560 -208
- package/src/html/layout/boxes.ts +2141 -190
- package/src/html/layout/cache.ts +289 -0
- package/src/html/layout/collapse.ts +317 -0
- package/src/html/layout/css-grid.ts +1063 -0
- package/src/html/layout/flex.ts +1115 -106
- package/src/html/layout/floats.ts +128 -14
- package/src/html/layout/grid-lines.ts +141 -0
- package/src/html/layout/grid.ts +181 -0
- package/src/html/layout/inline.ts +4187 -192
- package/src/html/layout/shaping.ts +140 -0
- package/src/html/layout/table.ts +696 -186
- package/src/html/layout/tracks.ts +437 -0
- package/src/html/node.ts +1585 -156
- package/src/html/paint.ts +4582 -267
- package/src/html/resources.ts +161 -50
- package/src/html/surfaces.ts +81 -0
- package/src/html/svg.ts +504 -0
- package/src/html/url.ts +86 -0
- package/src/index.ts +2 -0
- package/src/internal/codelines.ts +200 -0
- package/src/internal/collate.ts +12 -0
- package/src/internal/markdown/ast.ts +11 -0
- package/src/internal/markdown/parse.ts +154 -11
- package/src/internal/prewarm.ts +45 -0
- package/src/internal/sdf.ts +227 -0
- package/src/internal/splice.ts +25 -0
- package/src/internal/window.ts +36 -5
- package/src/maps/anchors.ts +307 -1
- package/src/maps/controller.ts +116 -31
- package/src/maps/gl/buckets.ts +5 -1
- package/src/maps/gl/build-worker.ts +13 -2
- package/src/maps/gl/cover.ts +79 -44
- package/src/maps/gl/placement.ts +221 -69
- package/src/maps/gl/renderer.ts +104 -20
- package/src/maps/gl/shaders.ts +35 -32
- package/src/maps/gl/store.ts +66 -17
- package/src/maps/gl/text.ts +444 -71
- package/src/maps/gl/view.ts +361 -96
- package/src/maps/icons.ts +183 -0
- package/src/maps/index.ts +4 -0
- package/src/maps/labels.ts +269 -49
- package/src/maps/node.ts +82 -49
- package/src/maps/proj.ts +87 -0
- package/src/maps/sources.ts +5 -3
- package/src/maps/style.ts +32 -0
- package/src/maps/styles.ts +215 -4
- package/src/maps/tiles.ts +64 -31
- package/src/maps/types.ts +28 -4
- package/src/markdown/index.ts +293 -92
- package/src/markdown/spans.ts +5 -0
- package/src/qml/containers.tsx +6 -1
- package/src/qml/interaction.tsx +42 -5
- package/src/qml/layouts.tsx +21 -6
- package/src/qml/qtquick.tsx +9 -2
- package/src/qml/react.tsx +7 -5
- package/src/reorder/clock.ts +27 -0
- package/src/reorder/index.ts +100 -27
- package/src/rich-text-editor/index.ts +37 -12
- package/src/rich-text-editor/keymap.ts +1 -1
- package/src/rich-text-editor/keys.ts +135 -26
- package/src/rich-text-editor/markdown.ts +36 -1
- package/src/rich-text-editor/marks.ts +101 -0
- package/src/rich-text-editor/render.ts +49 -12
- package/src/rich-text-editor/replace.ts +85 -0
- package/src/rich-text-editor/toolbar.ts +1 -1
- package/src/rich-text-editor/virtual.ts +25 -0
- package/src/richtext/node.ts +235 -14
- package/src/richtext/runs.ts +23 -12
- package/src/table/index.ts +29 -0
- package/src/table/rows.ts +18 -8
- package/src/terminal/index.ts +4 -0
- package/src/terminal/vt/pty.ts +16 -2
- package/src/terminal-output/index.ts +129 -31
- package/src/terminal-output/runs.ts +16 -2
- package/src/three/canvas.ts +102 -8
- package/src/three/events.ts +49 -9
- package/src/tree/index.ts +19 -0
|
@@ -0,0 +1,1295 @@
|
|
|
1
|
+
// Labels on the GPU: strings set by the platform's text engine, read back as
|
|
2
|
+
// coverage, kept as signed distance fields in one texture, and drawn as boxes
|
|
3
|
+
// that sample it.
|
|
4
|
+
//
|
|
5
|
+
// A `<glarea>` is stacked over everything 2D in its window, so a label on a
|
|
6
|
+
// GL graph has to be *in* the GL frame. The glyphs come from where every other
|
|
7
|
+
// string in the app does: `app.fonts.layout()` shapes it (CoreText on Cocoa,
|
|
8
|
+
// ntk's shaper on X11, fallback and all), the layout draws into an offscreen
|
|
9
|
+
// `Surface`, and the pixels come back — one readback per *batch*, not per
|
|
10
|
+
// label. What is kept is not that coverage but its distance field
|
|
11
|
+
// (`src/internal/sdf.ts`, the one `<Map>`'s labels are drawn from): a byte a
|
|
12
|
+
// texel, each texel's distance to the glyphs' edge, which the shader turns
|
|
13
|
+
// back into ink at whatever size the label is drawn (`./shaders.ts`). Shaped
|
|
14
|
+
// in white, because the colour is the shader's too, so one field serves
|
|
15
|
+
// every palette.
|
|
16
|
+
//
|
|
17
|
+
// **One field per string, at every size.** A graph zooms continuously and
|
|
18
|
+
// every label changes size with it. Coverage is exact for one size, so this
|
|
19
|
+
// atlas used to set every label again at each size a zoom came to rest at:
|
|
20
|
+
// ~580 strings after an eight-notch wheel zoom over a lattice, 80-120 ms of
|
|
21
|
+
// shaping, drawing and readback and 3.6 MB of texture, the labels drawn soft
|
|
22
|
+
// from the nearest size until the last batch landed ~170 ms after the wheel
|
|
23
|
+
// stopped. A field is set once, at `base`, and sharp at any size — every
|
|
24
|
+
// frame of a zoom gesture included — so a zoom asks the atlas for nothing.
|
|
25
|
+
// What that costs is the engine's hinting at the drawn size: the type is its
|
|
26
|
+
// outline, scaled, antialiased across one device pixel.
|
|
27
|
+
//
|
|
28
|
+
// So a label is also *measured* once, at the base size, and scaled: its box,
|
|
29
|
+
// and where it is cut to fit its card — `fitText`, the 2D painter's own, at
|
|
30
|
+
// the width the label's size allows. One string at every zoom is one field;
|
|
31
|
+
// a cut that moved by a character between two zooms would be a second.
|
|
32
|
+
// Advances are linear in size, so the cut is where the 2D painter makes it.
|
|
33
|
+
//
|
|
34
|
+
// Three things a graph needs that a map does not:
|
|
35
|
+
//
|
|
36
|
+
// - **Strings arrive a slice at a time, nearest the middle first.** A pan
|
|
37
|
+
// to an unseen part of the graph asks for hundreds at once. The world
|
|
38
|
+
// packs a box for every label, the ones still being set drawn as
|
|
39
|
+
// nothing (`GlyphQuad.ready`), and each frame writes in the fields that
|
|
40
|
+
// landed since the last (`FlowGlRenderer.landLabels`) — where the labels
|
|
41
|
+
// used to wait for the last of them and a world packed again.
|
|
42
|
+
// - **Nothing new while a zoom gesture moves** (`admit`). A zoom asks for
|
|
43
|
+
// nothing it has drawn before, but zooming out uncovers strings; they
|
|
44
|
+
// are set when it stops, as maps admit labels at rest. A single step —
|
|
45
|
+
// `setCenter`, a button — is not a gesture, and its labels are set at
|
|
46
|
+
// once.
|
|
47
|
+
// - **The atlas fills.** A world is built for three panes each way round
|
|
48
|
+
// the view, so a big graph zoomed out asks for many more strings than
|
|
49
|
+
// are on screen — 2,000 titles in the 2,000-node lattice at 0.6, where
|
|
50
|
+
// one page holds 660 at 2x. The texture's four channels are four pages,
|
|
51
|
+
// alpha first, so an atlas under the old limit is what it was. Past
|
|
52
|
+
// them, a field is dropped in place for a label nearer the view: first
|
|
53
|
+
// what no pack draws, then what is drawn farthest off screen, never
|
|
54
|
+
// what is on it (`room`). A box drawn from a dropped field is drawn as
|
|
55
|
+
// nothing until its field is back (`slotOf`), with no world packed
|
|
56
|
+
// again; a label that finds no room waits until the view moves.
|
|
57
|
+
import { Surface } from 'react-x11/ntk';
|
|
58
|
+
|
|
59
|
+
import { distanceField } from '../../internal/sdf.js';
|
|
60
|
+
import { fitText, shape } from '../draw.js';
|
|
61
|
+
import type { PainterOptions } from '../draw.js';
|
|
62
|
+
import type { SceneText } from '../scene.js';
|
|
63
|
+
import type { FlowRect, TextOptions } from '../types.js';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A label's quad and where its field sits in the atlas, all at the scene's
|
|
67
|
+
* zoom. `x`/`y` are the **text's** top-left, logical pixels in the scene's
|
|
68
|
+
* space — what the shader puts on the device grid, as the 2D painter puts
|
|
69
|
+
* the corner it draws a layout from — and the quad starts `margin` before
|
|
70
|
+
* it in both axes and is `w` by `h`. `texel` is the logical pixels one texel
|
|
71
|
+
* of the field covers; `u0`..`v1` are texture coordinates.
|
|
72
|
+
*
|
|
73
|
+
* A label whose field is still being set is a quad too, `ready` false and
|
|
74
|
+
* no place in the atlas: its size is known from the measurement before its
|
|
75
|
+
* field exists, so it is packed where it will be drawn, and its field is
|
|
76
|
+
* written in when it lands (`landed`) — without packing the world again.
|
|
77
|
+
*/
|
|
78
|
+
export interface GlyphQuad {
|
|
79
|
+
/** The field it draws from: its weight and the string, as cut. */
|
|
80
|
+
key: string;
|
|
81
|
+
ready: boolean;
|
|
82
|
+
x: number;
|
|
83
|
+
y: number;
|
|
84
|
+
w: number;
|
|
85
|
+
h: number;
|
|
86
|
+
margin: number;
|
|
87
|
+
texel: number;
|
|
88
|
+
/** The page its field is on: which channel of the texture holds it. */
|
|
89
|
+
page: number;
|
|
90
|
+
/** Its field's placement (`LabelAtlas.slotOf`); 0 when not ready. */
|
|
91
|
+
slot: number;
|
|
92
|
+
u0: number;
|
|
93
|
+
v0: number;
|
|
94
|
+
u1: number;
|
|
95
|
+
v1: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** What the atlas needs of the pane: its app, for the staging surface, and
|
|
99
|
+
* the options its 2D painter measures with. */
|
|
100
|
+
export interface TextSource {
|
|
101
|
+
app: unknown;
|
|
102
|
+
options: PainterOptions;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface Entry {
|
|
106
|
+
/** Place in the atlas, texels, margin included. */
|
|
107
|
+
x: number;
|
|
108
|
+
y: number;
|
|
109
|
+
width: number;
|
|
110
|
+
height: number;
|
|
111
|
+
/** The distance field, one byte a texel — kept to upload again, after a
|
|
112
|
+
* compaction moves it or into the texture of a new context. */
|
|
113
|
+
field: Uint8Array;
|
|
114
|
+
/** The last world pack that drew from it (`LabelAtlas.beginPack`). */
|
|
115
|
+
used: number;
|
|
116
|
+
/** Where that pack draws it — x, y, width, height, the world's units —
|
|
117
|
+
* for how far off screen it is when room is wanted. */
|
|
118
|
+
at: number[];
|
|
119
|
+
/** Where a lifted layer draws it (`quad`'s `lifted`), and the pack that
|
|
120
|
+
* was current then: one place, written over by every step of a drag
|
|
121
|
+
* rather than added to, since the layer is packed again each step. */
|
|
122
|
+
live: number[] | null;
|
|
123
|
+
liveAt: number;
|
|
124
|
+
page: number;
|
|
125
|
+
shelf: Shelf;
|
|
126
|
+
/** Which placement this is: a new one each time a field lands. */
|
|
127
|
+
slot: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** A row of one height on a page, filled left to right, with the gaps the
|
|
131
|
+
* fields dropped from it left. */
|
|
132
|
+
interface Shelf {
|
|
133
|
+
page: number;
|
|
134
|
+
y: number;
|
|
135
|
+
height: number;
|
|
136
|
+
/** Where the next field goes, past every gap. */
|
|
137
|
+
end: number;
|
|
138
|
+
gaps: { x: number; w: number }[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface Place {
|
|
142
|
+
shelf: Shelf;
|
|
143
|
+
x: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** A field that may be dropped, and how far from the view it is drawn —
|
|
147
|
+
* Infinity for one no pack draws. */
|
|
148
|
+
interface Victim {
|
|
149
|
+
key: string;
|
|
150
|
+
d: number;
|
|
151
|
+
used: number;
|
|
152
|
+
entry: Entry;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface ImageLike {
|
|
156
|
+
data: Uint8ClampedArray | Uint8Array;
|
|
157
|
+
width: number;
|
|
158
|
+
height: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface StagingContext {
|
|
162
|
+
getImageData(x: number, y: number, w: number, h: number): Promise<ImageLike>;
|
|
163
|
+
destroy?(): void;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface SurfaceLike {
|
|
167
|
+
getContext(name: '2d'): StagingContext;
|
|
168
|
+
clear(): void;
|
|
169
|
+
destroy(): void;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The atlas texture's side, texels: 16 MB of RGBA, four pages of fields —
|
|
173
|
+
* some 8,000 titles at 1x and 2,600 at 2x. */
|
|
174
|
+
export const ATLAS_SIZE = 2048;
|
|
175
|
+
/** Pages in the texture: its four channels, each a field a texel. */
|
|
176
|
+
const PAGES = 4;
|
|
177
|
+
/** The channel each page is in: alpha first, which is where one page's
|
|
178
|
+
* fields went before there were pages. */
|
|
179
|
+
const CHANNELS = [3, 0, 1, 2];
|
|
180
|
+
/** Shelf heights round up to this many texels. */
|
|
181
|
+
const SHELF_STEP = 4;
|
|
182
|
+
/** The surface a batch is set on. A readback has to stay inside it. */
|
|
183
|
+
const STAGING_WIDTH = 1024;
|
|
184
|
+
const STAGING_HEIGHT = 512;
|
|
185
|
+
/** Strings per batch at most, and milliseconds of drawing one may take: a
|
|
186
|
+
* batch runs between frames. */
|
|
187
|
+
const BATCH = 48;
|
|
188
|
+
const DRAW_BUDGET_MS = 2;
|
|
189
|
+
/** Milliseconds of fields one slice may make — about a dozen strings at
|
|
190
|
+
* 1x — before the frames have the thread back. */
|
|
191
|
+
const FIELD_BUDGET_MS = 3;
|
|
192
|
+
/** Milliseconds of measuring new strings a world pack may spend, past the
|
|
193
|
+
* first: what is left is measured between frames. */
|
|
194
|
+
const SHAPE_BUDGET_MS = 4;
|
|
195
|
+
|
|
196
|
+
/** What is wanted: the string to set, where the first label to ask for it
|
|
197
|
+
* is — and, for a request the pack did not measure, the request, and the
|
|
198
|
+
* width its string is cut to. */
|
|
199
|
+
interface Want {
|
|
200
|
+
text: string;
|
|
201
|
+
weight: SceneText['weight'];
|
|
202
|
+
x: number;
|
|
203
|
+
y: number;
|
|
204
|
+
request?: string;
|
|
205
|
+
fit?: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A label's request: its weight, its string, and the width it is cut to in
|
|
210
|
+
* device pixels at the base size — the same at every zoom, since a card's
|
|
211
|
+
* width and its label's size scale together, so one request resolves once.
|
|
212
|
+
*/
|
|
213
|
+
function requestOf(
|
|
214
|
+
t: SceneText,
|
|
215
|
+
texel: number,
|
|
216
|
+
): { key: string; fit: number | undefined } {
|
|
217
|
+
const fit = t.maxWidth
|
|
218
|
+
? Math.round((t.maxWidth / texel) * 64) / 64
|
|
219
|
+
: undefined;
|
|
220
|
+
return { key: `${t.weight ?? 400}|${fit ?? ''}|${t.text}`, fit };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Texels the drawn quad leaves off each side of a field: a bilinear sample
|
|
225
|
+
* at its edge then stays inside the field's own margin, never reaching a
|
|
226
|
+
* neighbour's.
|
|
227
|
+
*/
|
|
228
|
+
export const QUAD_INSET = 1;
|
|
229
|
+
|
|
230
|
+
/** The device size a display scale sets fields at: 16 at 1x, 32 at 2x. A
|
|
231
|
+
* card's title is 13 at zoom 1, so a field is mostly drawn at or under the
|
|
232
|
+
* size it was set at, where it is sharpest, and at twice it at the
|
|
233
|
+
* default `maxZoom` of 2.5. */
|
|
234
|
+
export const fieldBase = (scale: number): number =>
|
|
235
|
+
Math.round(16 * Math.min(2, Math.max(1, scale)));
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* A field's margin and spread at a base size, texels. A graph's labels have
|
|
239
|
+
* no halo, so the field only has to reach as far as the antialiasing ramp
|
|
240
|
+
* of the smallest label drawn: a handle's 10 at `LABEL_ZOOM`, under a
|
|
241
|
+
* third of the base, is a ramp of 2.3 texels, and a sixth of the base
|
|
242
|
+
* reaches three. A quarter, which the first cut used, drew the same pixels
|
|
243
|
+
* to within 5/255 from fields 12% larger, whose every texel was made.
|
|
244
|
+
*/
|
|
245
|
+
export const fieldPad = (base: number): number => Math.ceil(base / 6);
|
|
246
|
+
|
|
247
|
+
/** The clock, through `globalThis`: `src/` compiles with `types: []`. */
|
|
248
|
+
const globals = globalThis as {
|
|
249
|
+
performance?: { now(): number };
|
|
250
|
+
setTimeout?(fn: () => void, ms: number): unknown;
|
|
251
|
+
setImmediate?(fn: () => void): unknown;
|
|
252
|
+
};
|
|
253
|
+
const now = (): number => globals.performance?.now() ?? Date.now();
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* The next task: what lets a frame in between two slices. `setImmediate`,
|
|
257
|
+
* after the loop has polled for what came in — a frame's callback among it
|
|
258
|
+
* — where a timeout of 0 is a millisecond at least, idle, between every
|
|
259
|
+
* slice of a first appearance.
|
|
260
|
+
*/
|
|
261
|
+
const nextTask = (): Promise<void> =>
|
|
262
|
+
new Promise((resolve) => {
|
|
263
|
+
if (globals.setImmediate) globals.setImmediate(resolve);
|
|
264
|
+
else if (globals.setTimeout) globals.setTimeout(resolve, 0);
|
|
265
|
+
else resolve();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
export class LabelAtlas {
|
|
269
|
+
private readonly source: TextSource;
|
|
270
|
+
/** Measured and shaped in white, at device size — the scale is 1 here,
|
|
271
|
+
* and a size is the base in device pixels — and cached apart from the
|
|
272
|
+
* pane's own text: nothing measured here is a size the pane draws. */
|
|
273
|
+
private readonly white: PainterOptions;
|
|
274
|
+
/** The device size every field is set at: a label drawn at `size`
|
|
275
|
+
* scales its field by `size / base`. */
|
|
276
|
+
readonly base: number;
|
|
277
|
+
/** The margin set round every field, texels, which is also the distance
|
|
278
|
+
* its values span — the shader's `u_spread`. */
|
|
279
|
+
readonly pad: number;
|
|
280
|
+
private readonly entries = new Map<string, Entry>();
|
|
281
|
+
/** Wanted and not yet set: key → what to set it from, and where the
|
|
282
|
+
* label nearest the view that asks for it is. */
|
|
283
|
+
private readonly missing = new Map<string, Want>();
|
|
284
|
+
/**
|
|
285
|
+
* Wanted, and waiting for room: the atlas is full of fields at least as
|
|
286
|
+
* near the view as these, so nothing is set for them until the view
|
|
287
|
+
* moves (`setView`) or a pack starts. Every key here is in `missing` too;
|
|
288
|
+
* what `wanting` counts is the rest.
|
|
289
|
+
*/
|
|
290
|
+
private readonly stalled = new Set<string>();
|
|
291
|
+
/**
|
|
292
|
+
* Requests measured: request → the string as cut. Measuring is what a
|
|
293
|
+
* label's first appearance costs the frame that packs it — 60-80 µs a
|
|
294
|
+
* string, on DirectWrite and on ntk alike, so a view of three hundred new
|
|
295
|
+
* labels was one frame 20 ms long before any of them could arrive. A pack
|
|
296
|
+
* measures for `shapeBudgetMs` and leaves the rest to the slices between
|
|
297
|
+
* frames; the ones it measured no pack measures again.
|
|
298
|
+
*/
|
|
299
|
+
private readonly resolved = new Map<string, string>();
|
|
300
|
+
/**
|
|
301
|
+
* What the pack under way has spent measuring, and how many strings it
|
|
302
|
+
* has measured — at least one a pack. Measuring alone is charged: a pack
|
|
303
|
+
* writes every edge before its first label, and a deadline from its start
|
|
304
|
+
* put off, after a zoom, labels whose cut had moved a fraction of a pixel
|
|
305
|
+
* and whose every measurement was cached.
|
|
306
|
+
*/
|
|
307
|
+
private spent = 0;
|
|
308
|
+
private measured = 0;
|
|
309
|
+
/** Read back and waiting for their fields, oldest first — as wanted as
|
|
310
|
+
* anything in `missing`, so a pack does not ask for them again. */
|
|
311
|
+
private fieldQueue: {
|
|
312
|
+
key: string;
|
|
313
|
+
width: number;
|
|
314
|
+
height: number;
|
|
315
|
+
pixels: Uint8Array;
|
|
316
|
+
want: Want;
|
|
317
|
+
}[] = [];
|
|
318
|
+
private readonly queued = new Set<string>();
|
|
319
|
+
/** The pages' shelves, and how far down each page they reach. */
|
|
320
|
+
private shelves: Shelf[] = [];
|
|
321
|
+
private readonly tops: number[];
|
|
322
|
+
/** Pages in the texture: one per channel, alpha first. */
|
|
323
|
+
private readonly pages: number;
|
|
324
|
+
/**
|
|
325
|
+
* The whole texture, four bytes a texel, made when the first field goes
|
|
326
|
+
* on a page past the first. An upload writes every channel of the rect
|
|
327
|
+
* it covers, so from then on a field is uploaded from here, with the
|
|
328
|
+
* fields of the other pages under it. An atlas that never fills its first
|
|
329
|
+
* page never makes it: 16 MB, paid only past the old limit.
|
|
330
|
+
*/
|
|
331
|
+
private mirror: Uint8Array | null = null;
|
|
332
|
+
/** The last placement found no room, and nothing has been freed since:
|
|
333
|
+
* a want that cannot evict is stalled before it is shaped. */
|
|
334
|
+
private full = false;
|
|
335
|
+
/** Fields that may be dropped for a nearer one, farthest from the view
|
|
336
|
+
* first — made when first needed in a slice, and consumed in order. */
|
|
337
|
+
private victims: Victim[] | null = null;
|
|
338
|
+
private victimAt = 0;
|
|
339
|
+
/** Placements, counted: a field's `slot` changes whenever it lands, so a
|
|
340
|
+
* box drawn from an old one is found by comparing the two. */
|
|
341
|
+
private nextSlot = 1;
|
|
342
|
+
private staging: SurfaceLike | null = null;
|
|
343
|
+
private stagingCtx: StagingContext | null = null;
|
|
344
|
+
private busy = false;
|
|
345
|
+
/** Whether the layouts answer their own coverage (react-x11#673): unknown
|
|
346
|
+
* until the first string asks, and false for good once one does not. */
|
|
347
|
+
private coverage: boolean | null = null;
|
|
348
|
+
/** Bumped whenever a field lands or is dropped — the renderer writes in
|
|
349
|
+
* what landed, and uploads, when it moves. */
|
|
350
|
+
generation = 0;
|
|
351
|
+
/** Bumped whenever a field is dropped: the renderer finds the boxes that
|
|
352
|
+
* drew from it (`slotOf`) and draws them as nothing until it is back. */
|
|
353
|
+
moves = 0;
|
|
354
|
+
/** Fields set are uploaded when the renderer next binds the atlas. */
|
|
355
|
+
private readonly pending = new Set<string>();
|
|
356
|
+
/** Whether new strings may be set: false while the zoom is moving. */
|
|
357
|
+
admit = true;
|
|
358
|
+
/** Milliseconds of fields one slice may make, past the first field. */
|
|
359
|
+
fieldBudgetMs = FIELD_BUDGET_MS;
|
|
360
|
+
/** Milliseconds of measuring new strings a world pack may spend. */
|
|
361
|
+
shapeBudgetMs = SHAPE_BUDGET_MS;
|
|
362
|
+
private texture: unknown = null;
|
|
363
|
+
private textureGeneration = -1;
|
|
364
|
+
/** A field's rect as uploaded: widened to RGBA — the shader reads one
|
|
365
|
+
* channel, and a one-channel texture is a format GLES 2 and a core
|
|
366
|
+
* profile do not share. Grown to the largest field. */
|
|
367
|
+
private scratch = new Uint8Array(0);
|
|
368
|
+
/** Counts world packs; an entry stamped with the current one is drawn. */
|
|
369
|
+
private epoch = 0;
|
|
370
|
+
|
|
371
|
+
/** The texture's side, texels. */
|
|
372
|
+
private readonly side: number;
|
|
373
|
+
|
|
374
|
+
/** `side` and `pages` are for tests, which fill an atlas without setting
|
|
375
|
+
* thousands of labels. */
|
|
376
|
+
constructor(source: TextSource, side = ATLAS_SIZE, pages = PAGES) {
|
|
377
|
+
this.source = source;
|
|
378
|
+
this.side = side;
|
|
379
|
+
this.pages = Math.max(1, Math.min(PAGES, pages));
|
|
380
|
+
this.tops = new Array<number>(this.pages).fill(0);
|
|
381
|
+
this.base = fieldBase(source.options.scale);
|
|
382
|
+
this.pad = fieldPad(this.base);
|
|
383
|
+
this.white = {
|
|
384
|
+
...source.options,
|
|
385
|
+
color: '#ffffff',
|
|
386
|
+
scale: 1,
|
|
387
|
+
cache: new Map(),
|
|
388
|
+
approximateText: false,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* A world pack is starting: the labels it asks for are the ones drawn
|
|
394
|
+
* until the next — and **all** that is wanted. What earlier packs asked
|
|
395
|
+
* for and nothing has set is forgotten here: the strings a pan went past
|
|
396
|
+
* are not worth a batch once it has.
|
|
397
|
+
*/
|
|
398
|
+
beginPack(): void {
|
|
399
|
+
this.epoch++;
|
|
400
|
+
this.missing.clear();
|
|
401
|
+
this.stalled.clear();
|
|
402
|
+
this.victims = null;
|
|
403
|
+
this.spent = 0;
|
|
404
|
+
this.measured = 0;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* The view's centre, in the world's coordinates. What is wanted is set
|
|
409
|
+
* nearest it first: on a first appearance the labels arrive over a
|
|
410
|
+
* hundred milliseconds or more, and the ones in the middle of the view
|
|
411
|
+
* are the ones being read.
|
|
412
|
+
*/
|
|
413
|
+
focus: { x: number; y: number } | null = null;
|
|
414
|
+
/** The view, in the world's coordinates — the surface says where each
|
|
415
|
+
* frame. Null draws everything as if it were on screen. */
|
|
416
|
+
private view: FlowRect | null = null;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Where the view is now. A field on screen is never dropped for another;
|
|
420
|
+
* one off it is dropped for a label nearer the view, and a move of the
|
|
421
|
+
* view is what can make a stalled label nearer than one that is set.
|
|
422
|
+
*/
|
|
423
|
+
setView(view: FlowRect | null): void {
|
|
424
|
+
const v = this.view;
|
|
425
|
+
if (
|
|
426
|
+
v === view ||
|
|
427
|
+
(v &&
|
|
428
|
+
view &&
|
|
429
|
+
v.x === view.x &&
|
|
430
|
+
v.y === view.y &&
|
|
431
|
+
v.width === view.width &&
|
|
432
|
+
v.height === view.height)
|
|
433
|
+
) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
this.view = view;
|
|
437
|
+
if (view) {
|
|
438
|
+
this.focus = { x: view.x + view.width / 2, y: view.y + view.height / 2 };
|
|
439
|
+
}
|
|
440
|
+
this.stalled.clear();
|
|
441
|
+
this.victims = null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** How far a rect is from the view, in the world's units: 0 on it. */
|
|
445
|
+
private far(x: number, y: number, w: number, h: number): number {
|
|
446
|
+
const v = this.view;
|
|
447
|
+
if (!v) return 0;
|
|
448
|
+
const dx = Math.max(v.x - (x + w), x - (v.x + v.width), 0);
|
|
449
|
+
const dy = Math.max(v.y - (y + h), y - (v.y + v.height), 0);
|
|
450
|
+
return dx > dy ? dx + dy / 2 : dy + dx / 2;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** How near a label is, for ordering what is wanted: from the view,
|
|
454
|
+
* then from its middle. */
|
|
455
|
+
private nearness(x: number, y: number): [number, number] {
|
|
456
|
+
const f = this.focus;
|
|
457
|
+
const d = f ? (x - f.x) * (x - f.x) + (y - f.y) * (y - f.y) : 0;
|
|
458
|
+
return [this.far(x, y, 0, 0), d];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** A want moved to the label asking for it nearest the view. */
|
|
462
|
+
private nearer(want: Want, t: { x: number; y: number }): void {
|
|
463
|
+
const [a, b] = this.nearness(want.x, want.y);
|
|
464
|
+
const [c, d] = this.nearness(t.x, t.y);
|
|
465
|
+
if (c < a || (c === a && d < b)) {
|
|
466
|
+
want.x = t.x;
|
|
467
|
+
want.y = t.y;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* A label's quad, or null when there is nothing to draw it from yet —
|
|
473
|
+
* which asks for its field.
|
|
474
|
+
*/
|
|
475
|
+
quad(t: SceneText, lifted = false): GlyphQuad | null {
|
|
476
|
+
// Logical pixels, at the scene's zoom, per device pixel at the base
|
|
477
|
+
// size: one texel of the field.
|
|
478
|
+
const texel = t.size / this.base;
|
|
479
|
+
if (!(texel > 0)) return null;
|
|
480
|
+
const request = requestOf(t, texel);
|
|
481
|
+
let shown = this.resolved.get(request.key);
|
|
482
|
+
if (shown === undefined) {
|
|
483
|
+
if (this.measured > 0 && this.spent > this.shapeBudgetMs) {
|
|
484
|
+
// Past the pack's budget: measured between frames, and the whole
|
|
485
|
+
// quad written in once its field lands (`landing`).
|
|
486
|
+
const key = `?${request.key}`;
|
|
487
|
+
const had = this.missing.get(key);
|
|
488
|
+
if (had) this.nearer(had, t);
|
|
489
|
+
else {
|
|
490
|
+
this.missing.set(key, {
|
|
491
|
+
text: t.text,
|
|
492
|
+
weight: t.weight,
|
|
493
|
+
x: t.x,
|
|
494
|
+
y: t.y,
|
|
495
|
+
request: request.key,
|
|
496
|
+
fit: request.fit,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
key,
|
|
501
|
+
ready: false,
|
|
502
|
+
x: t.x,
|
|
503
|
+
y: t.y,
|
|
504
|
+
w: 0,
|
|
505
|
+
h: 0,
|
|
506
|
+
margin: 0,
|
|
507
|
+
texel,
|
|
508
|
+
page: 0,
|
|
509
|
+
slot: 0,
|
|
510
|
+
u0: 0,
|
|
511
|
+
v0: 0,
|
|
512
|
+
u1: 0,
|
|
513
|
+
v1: 0,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
this.measured++;
|
|
517
|
+
const started = now();
|
|
518
|
+
shown = this.cut(t.text, t.weight, request.fit);
|
|
519
|
+
this.remember(request.key, shown);
|
|
520
|
+
if (shown) {
|
|
521
|
+
shape(this.white, shown, {
|
|
522
|
+
size: this.base,
|
|
523
|
+
weight: t.weight,
|
|
524
|
+
color: '#ffffff',
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
this.spent += now() - started;
|
|
528
|
+
}
|
|
529
|
+
if (!shown) return null;
|
|
530
|
+
const style: TextOptions = {
|
|
531
|
+
size: this.base,
|
|
532
|
+
weight: t.weight,
|
|
533
|
+
color: '#ffffff',
|
|
534
|
+
};
|
|
535
|
+
const shaped = shape(this.white, shown, style);
|
|
536
|
+
if (!shaped) return null;
|
|
537
|
+
const width = shaped.width * texel;
|
|
538
|
+
const height = shaped.height * texel;
|
|
539
|
+
// Placed as `Painter.text` places it: anchored by `align` and
|
|
540
|
+
// `baseline`, its top-left put on the device grid — by the shader here,
|
|
541
|
+
// after the pan's offset, which is where the grid is.
|
|
542
|
+
const dx =
|
|
543
|
+
t.align === 'center' ? -width / 2 : t.align === 'right' ? -width : 0;
|
|
544
|
+
const dy = t.baseline === 'middle' ? -height / 2 : 0;
|
|
545
|
+
const key = `${t.weight ?? 400}|${shown}`;
|
|
546
|
+
const entry = this.entries.get(key);
|
|
547
|
+
const inset = QUAD_INSET;
|
|
548
|
+
if (!entry) {
|
|
549
|
+
if (!this.queued.has(key)) {
|
|
550
|
+
const had = this.missing.get(key);
|
|
551
|
+
if (had) this.nearer(had, t);
|
|
552
|
+
else {
|
|
553
|
+
this.missing.set(key, {
|
|
554
|
+
text: shown,
|
|
555
|
+
weight: t.weight,
|
|
556
|
+
x: t.x,
|
|
557
|
+
y: t.y,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// Its field will be the layout box in whole pixels and the margin,
|
|
562
|
+
// which is known now: packed where it will be drawn, and drawn once
|
|
563
|
+
// its field lands.
|
|
564
|
+
return {
|
|
565
|
+
key,
|
|
566
|
+
ready: false,
|
|
567
|
+
x: t.x + dx,
|
|
568
|
+
y: t.y + dy,
|
|
569
|
+
w: (Math.ceil(shaped.width) + (this.pad - inset) * 2) * texel,
|
|
570
|
+
h: (Math.ceil(shaped.height) + (this.pad - inset) * 2) * texel,
|
|
571
|
+
margin: (this.pad - inset) * texel,
|
|
572
|
+
texel,
|
|
573
|
+
page: 0,
|
|
574
|
+
slot: 0,
|
|
575
|
+
u0: 0,
|
|
576
|
+
v0: 0,
|
|
577
|
+
u1: 0,
|
|
578
|
+
v1: 0,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
const q: GlyphQuad = {
|
|
582
|
+
key,
|
|
583
|
+
ready: true,
|
|
584
|
+
x: t.x + dx,
|
|
585
|
+
y: t.y + dy,
|
|
586
|
+
w: (entry.width - inset * 2) * texel,
|
|
587
|
+
h: (entry.height - inset * 2) * texel,
|
|
588
|
+
margin: (this.pad - inset) * texel,
|
|
589
|
+
texel,
|
|
590
|
+
page: entry.page,
|
|
591
|
+
slot: entry.slot,
|
|
592
|
+
u0: (entry.x + inset) / this.side,
|
|
593
|
+
v0: (entry.y + inset) / this.side,
|
|
594
|
+
u1: (entry.x + entry.width - inset) / this.side,
|
|
595
|
+
v1: (entry.y + entry.height - inset) / this.side,
|
|
596
|
+
};
|
|
597
|
+
// Where it is drawn this pack, for how far from the view it is.
|
|
598
|
+
if (entry.used !== this.epoch) {
|
|
599
|
+
entry.used = this.epoch;
|
|
600
|
+
entry.at.length = 0;
|
|
601
|
+
}
|
|
602
|
+
if (lifted) {
|
|
603
|
+
entry.live = [q.x - q.margin, q.y - q.margin, q.w, q.h];
|
|
604
|
+
entry.liveAt = this.epoch;
|
|
605
|
+
} else {
|
|
606
|
+
entry.at.push(q.x - q.margin, q.y - q.margin, q.w, q.h);
|
|
607
|
+
}
|
|
608
|
+
return q;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* The quad a label packed before its field existed draws from, now that
|
|
613
|
+
* it can be drawn — null until then. One the pack did not measure (a `?`
|
|
614
|
+
* key) is placed now, from the string its request resolved to: the pack
|
|
615
|
+
* gave it a box of no size, and this is its whole quad.
|
|
616
|
+
*/
|
|
617
|
+
landing(key: string, t: SceneText, lifted = false): GlyphQuad | null {
|
|
618
|
+
if (key.startsWith('?')) {
|
|
619
|
+
const shown = this.resolved.get(key.slice(1));
|
|
620
|
+
if (shown === undefined) return null;
|
|
621
|
+
if (!this.entries.has(`${t.weight ?? 400}|${shown}`)) return null;
|
|
622
|
+
} else if (!this.entries.has(key)) {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
// measured and set: `quad` does no work but place it, and marks it
|
|
626
|
+
const q = this.quad(t, lifted);
|
|
627
|
+
return q?.ready ? q : null;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** The placement a field is at now — -1 when it is not set — for a box
|
|
631
|
+
* to tell whether the field it was drawn from is still there. */
|
|
632
|
+
slotOf(key: string): number {
|
|
633
|
+
return this.entries.get(key)?.slot ?? -1;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/** A field a drawn label lost, wanted again from where that label is. */
|
|
637
|
+
want(key: string, t: SceneText): void {
|
|
638
|
+
if (this.entries.has(key) || this.queued.has(key)) return;
|
|
639
|
+
const had = this.missing.get(key);
|
|
640
|
+
if (had) {
|
|
641
|
+
this.nearer(had, t);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
const bar = key.indexOf('|');
|
|
645
|
+
this.missing.set(key, {
|
|
646
|
+
text: key.slice(bar + 1),
|
|
647
|
+
weight: t.weight,
|
|
648
|
+
x: t.x,
|
|
649
|
+
y: t.y,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** A string cut to the width it fits, measured at the base size. */
|
|
654
|
+
private cut(
|
|
655
|
+
text: string,
|
|
656
|
+
weight: SceneText['weight'],
|
|
657
|
+
fit: number | undefined,
|
|
658
|
+
): string {
|
|
659
|
+
if (fit === undefined) return text;
|
|
660
|
+
const style: TextOptions = { size: this.base, weight, color: '#ffffff' };
|
|
661
|
+
return fitText(this.white, text, style, fit);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private remember(request: string, shown: string): void {
|
|
665
|
+
if (this.resolved.size > 20000) this.resolved.clear();
|
|
666
|
+
this.resolved.set(request, shown);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** No longer wanted: set, or given up on. */
|
|
670
|
+
private unwant(key: string): void {
|
|
671
|
+
this.missing.delete(key);
|
|
672
|
+
this.stalled.delete(key);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* A request the pack left unmeasured, measured: its string cut and
|
|
677
|
+
* shaped, and wanted under its field's key from then on. Null when that
|
|
678
|
+
* field is set, being set or waiting for room already — the request is
|
|
679
|
+
* answered, and a frame writes its labels in.
|
|
680
|
+
*/
|
|
681
|
+
private resolve(
|
|
682
|
+
key: string,
|
|
683
|
+
want: Want,
|
|
684
|
+
): { key: string; text: string; want: Want } | null {
|
|
685
|
+
if (want.request === undefined) return { key, text: want.text, want };
|
|
686
|
+
this.unwant(key);
|
|
687
|
+
const shown = this.cut(want.text, want.weight, want.fit);
|
|
688
|
+
this.remember(want.request, shown);
|
|
689
|
+
if (!shown) return null;
|
|
690
|
+
const field = `${want.weight ?? 400}|${shown}`;
|
|
691
|
+
if (
|
|
692
|
+
this.entries.has(field) ||
|
|
693
|
+
this.queued.has(field) ||
|
|
694
|
+
this.stalled.has(field)
|
|
695
|
+
) {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
let wanted = this.missing.get(field);
|
|
699
|
+
if (wanted) this.nearer(wanted, want);
|
|
700
|
+
else {
|
|
701
|
+
wanted = { ...want, text: shown, request: undefined };
|
|
702
|
+
this.missing.set(field, wanted);
|
|
703
|
+
}
|
|
704
|
+
return { key: field, text: shown, want: wanted };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** What is wanted and not waiting for room, nearest the view first. */
|
|
708
|
+
private wanted(): [string, Want][] {
|
|
709
|
+
const list: [string, Want, number, number][] = [];
|
|
710
|
+
for (const [key, want] of this.missing) {
|
|
711
|
+
if (this.stalled.has(key)) continue;
|
|
712
|
+
const [a, b] = this.nearness(want.x, want.y);
|
|
713
|
+
list.push([key, want, a, b]);
|
|
714
|
+
}
|
|
715
|
+
if (list.length > 1) list.sort((p, q) => p[2] - q[2] || p[3] - q[3]);
|
|
716
|
+
return list.map(([key, want]) => [key, want]);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** Whether any label drawn so far is still waiting for a field that can
|
|
720
|
+
* be set — not counting those waiting for room. */
|
|
721
|
+
get wanting(): boolean {
|
|
722
|
+
return this.fieldQueue.length > 0 || this.missing.size > this.stalled.size;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Whether a want this far from the view is worth shaping: not when the
|
|
727
|
+
* atlas is full and holds nothing farther to drop for it.
|
|
728
|
+
*/
|
|
729
|
+
private hopeful(distance: number): boolean {
|
|
730
|
+
return !this.full || this.victim(distance) !== null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* One slice of setting what was asked for: fields for what was read back
|
|
735
|
+
* already, in a task of their own, or else one batch drawn and read back
|
|
736
|
+
* and the first of its fields. Resolves true when any field landed — the
|
|
737
|
+
* caller asks for a frame then, which draws what landed — and does
|
|
738
|
+
* nothing while one is already in flight,
|
|
739
|
+
* while the zoom is moving, or with nothing to set.
|
|
740
|
+
*/
|
|
741
|
+
async pump(): Promise<boolean> {
|
|
742
|
+
if (this.busy || !this.admit) return false;
|
|
743
|
+
const fonts = this.source.options.fonts;
|
|
744
|
+
if (this.fieldQueue.length === 0 && (!this.wanting || !fonts)) {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
this.busy = true;
|
|
748
|
+
try {
|
|
749
|
+
// A frame first, if one is waiting: every slice is a task of its own.
|
|
750
|
+
// A readback can answer at once (the Windows surface does), so a
|
|
751
|
+
// batch that set its fields in the same slice left nothing to wait
|
|
752
|
+
// on, and a whole appearance ran as one task — every label at the
|
|
753
|
+
// end of it, and no frame in between.
|
|
754
|
+
await nextTask();
|
|
755
|
+
// what may be dropped is worked out again: the view may have moved
|
|
756
|
+
this.victims = null;
|
|
757
|
+
if (this.fieldQueue.length > 0) return this.makeFields();
|
|
758
|
+
if (!this.wanting) return false;
|
|
759
|
+
if (this.coverage !== false) {
|
|
760
|
+
const landed = this.coverageSlice();
|
|
761
|
+
if (landed !== null) return landed;
|
|
762
|
+
}
|
|
763
|
+
return await this.readBack();
|
|
764
|
+
} finally {
|
|
765
|
+
this.busy = false;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* One batch drawn onto the staging surface and read back, and the first
|
|
771
|
+
* of its fields: the way a string is set on an engine whose layouts do not
|
|
772
|
+
* answer their own coverage.
|
|
773
|
+
*/
|
|
774
|
+
private async readBack(): Promise<boolean> {
|
|
775
|
+
if (!this.staging) {
|
|
776
|
+
this.staging = new Surface(this.source.app as never, {
|
|
777
|
+
width: STAGING_WIDTH,
|
|
778
|
+
height: STAGING_HEIGHT,
|
|
779
|
+
}) as unknown as SurfaceLike;
|
|
780
|
+
this.stagingCtx = this.staging.getContext('2d');
|
|
781
|
+
}
|
|
782
|
+
const staging = this.staging;
|
|
783
|
+
const ctx = this.stagingCtx!;
|
|
784
|
+
staging.clear();
|
|
785
|
+
// Drawing and measuring are budgeted apart: a batch read back costs
|
|
786
|
+
// what it costs however few strings are in it, so measuring the ones
|
|
787
|
+
// the pack left out of the drawing's budget made the batches small and
|
|
788
|
+
// the readbacks many.
|
|
789
|
+
let drawing = 0;
|
|
790
|
+
let measuring = 0;
|
|
791
|
+
const pad = this.pad;
|
|
792
|
+
const placed: {
|
|
793
|
+
key: string;
|
|
794
|
+
want: Want;
|
|
795
|
+
x: number;
|
|
796
|
+
y: number;
|
|
797
|
+
w: number;
|
|
798
|
+
h: number;
|
|
799
|
+
}[] = [];
|
|
800
|
+
let x = 0;
|
|
801
|
+
let y = 0;
|
|
802
|
+
let row = 0;
|
|
803
|
+
let answered = false;
|
|
804
|
+
for (const [asked, wanted] of this.wanted()) {
|
|
805
|
+
if (
|
|
806
|
+
placed.length >= BATCH ||
|
|
807
|
+
drawing > DRAW_BUDGET_MS ||
|
|
808
|
+
measuring > this.shapeBudgetMs
|
|
809
|
+
) {
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
if (!this.hopeful(this.far(wanted.x, wanted.y, 0, 0))) {
|
|
813
|
+
this.stalled.add(asked);
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const measured = now();
|
|
817
|
+
const want = this.resolve(asked, wanted);
|
|
818
|
+
if (!want) {
|
|
819
|
+
measuring += now() - measured;
|
|
820
|
+
answered = true;
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
const key = want.key;
|
|
824
|
+
const shaped = shape(this.white, want.text, {
|
|
825
|
+
size: this.base,
|
|
826
|
+
weight: wanted.weight,
|
|
827
|
+
color: '#ffffff',
|
|
828
|
+
});
|
|
829
|
+
measuring += now() - measured;
|
|
830
|
+
if (!shaped) break;
|
|
831
|
+
const layout = shaped.layout;
|
|
832
|
+
const w = Math.ceil(layout.width) + pad * 2;
|
|
833
|
+
const h = Math.ceil(layout.height || this.base * 1.3) + pad * 2;
|
|
834
|
+
if (w > STAGING_WIDTH || h > STAGING_HEIGHT) {
|
|
835
|
+
// wider than anything can set: never drawn, and never asked again
|
|
836
|
+
this.unwant(key);
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (x + w > STAGING_WIDTH) {
|
|
840
|
+
x = 0;
|
|
841
|
+
y += row;
|
|
842
|
+
row = 0;
|
|
843
|
+
}
|
|
844
|
+
if (y + h > STAGING_HEIGHT) break;
|
|
845
|
+
const drawn = now();
|
|
846
|
+
layout.draw(ctx, x + pad, y + pad);
|
|
847
|
+
drawing += now() - drawn;
|
|
848
|
+
placed.push({ key, want: want.want, x, y, w, h });
|
|
849
|
+
x += w;
|
|
850
|
+
row = Math.max(row, h);
|
|
851
|
+
}
|
|
852
|
+
if (placed.length === 0) {
|
|
853
|
+
if (!answered) return false;
|
|
854
|
+
this.generation++;
|
|
855
|
+
return true;
|
|
856
|
+
}
|
|
857
|
+
const used = Math.max(...placed.map((p) => p.y + p.h));
|
|
858
|
+
const image = await ctx.getImageData(0, 0, STAGING_WIDTH, used);
|
|
859
|
+
for (const p of placed) {
|
|
860
|
+
this.unwant(p.key);
|
|
861
|
+
// Row by row, as the readback has them; the field reads alpha.
|
|
862
|
+
const rowBytes = p.w * 4;
|
|
863
|
+
const pixels = new Uint8Array(rowBytes * p.h);
|
|
864
|
+
for (let r = 0; r < p.h; r++) {
|
|
865
|
+
const src = ((p.y + r) * image.width + p.x) * 4;
|
|
866
|
+
pixels.set(image.data.subarray(src, src + rowBytes), r * rowBytes);
|
|
867
|
+
}
|
|
868
|
+
this.fieldQueue.push({
|
|
869
|
+
key: p.key,
|
|
870
|
+
width: p.w,
|
|
871
|
+
height: p.h,
|
|
872
|
+
pixels,
|
|
873
|
+
want: p.want,
|
|
874
|
+
});
|
|
875
|
+
this.queued.add(p.key);
|
|
876
|
+
}
|
|
877
|
+
if (this.makeFields()) return true;
|
|
878
|
+
if (!answered) return false;
|
|
879
|
+
this.generation++;
|
|
880
|
+
return true;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* One slice of strings set from their layouts' own coverage, where the
|
|
885
|
+
* engine answers it: no staging surface and no readback, so a string
|
|
886
|
+
* shaped this slice is a field this slice, until the budget is spent — at
|
|
887
|
+
* least one. Null when the layouts cannot answer, and the batches read
|
|
888
|
+
* back instead from then on.
|
|
889
|
+
*/
|
|
890
|
+
private coverageSlice(): boolean | null {
|
|
891
|
+
const started = now();
|
|
892
|
+
let landed = false;
|
|
893
|
+
let answered = false;
|
|
894
|
+
for (const [asked, wanted] of this.wanted()) {
|
|
895
|
+
if ((landed || answered) && now() - started >= this.fieldBudgetMs) break;
|
|
896
|
+
const distance = this.far(wanted.x, wanted.y, 0, 0);
|
|
897
|
+
if (!this.hopeful(distance)) {
|
|
898
|
+
// Full of fields at least as near: not shaped, and not asked for
|
|
899
|
+
// again until the view moves.
|
|
900
|
+
this.stalled.add(asked);
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
const want = this.resolve(asked, wanted);
|
|
904
|
+
if (!want) {
|
|
905
|
+
answered = true;
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
const key = want.key;
|
|
909
|
+
const shaped = shape(this.white, want.text, {
|
|
910
|
+
size: this.base,
|
|
911
|
+
weight: wanted.weight,
|
|
912
|
+
color: '#ffffff',
|
|
913
|
+
});
|
|
914
|
+
const coverage = shaped?.layout.coverage?.({ pad: this.pad }) ?? null;
|
|
915
|
+
if (!coverage) {
|
|
916
|
+
if (landed || answered || this.coverage) break;
|
|
917
|
+
this.coverage = false;
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
this.coverage = true;
|
|
921
|
+
if (coverage.width > STAGING_WIDTH || coverage.height > STAGING_HEIGHT) {
|
|
922
|
+
// wider than a batch could set: never drawn, as there
|
|
923
|
+
this.unwant(key);
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const place = this.room(coverage.width, coverage.height, distance);
|
|
927
|
+
if (!place) {
|
|
928
|
+
this.stalled.add(key);
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
this.unwant(key);
|
|
932
|
+
const field = distanceField(
|
|
933
|
+
coverage.data,
|
|
934
|
+
coverage.width,
|
|
935
|
+
coverage.height,
|
|
936
|
+
this.pad,
|
|
937
|
+
1,
|
|
938
|
+
0,
|
|
939
|
+
);
|
|
940
|
+
landed = true;
|
|
941
|
+
this.put(key, place, coverage.width, coverage.height, field, want.want);
|
|
942
|
+
}
|
|
943
|
+
if (landed || answered) this.generation++;
|
|
944
|
+
return landed || answered;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Fields for what was read back, oldest first, until the slice's budget
|
|
949
|
+
* is spent — at least one. A field is 0.1-0.4 ms (more texels at 2x);
|
|
950
|
+
* a batch's worth made as it landed was a frame held back.
|
|
951
|
+
*/
|
|
952
|
+
private makeFields(): boolean {
|
|
953
|
+
const started = now();
|
|
954
|
+
let landed = false;
|
|
955
|
+
while (
|
|
956
|
+
this.fieldQueue.length > 0 &&
|
|
957
|
+
(!landed || now() - started < this.fieldBudgetMs)
|
|
958
|
+
) {
|
|
959
|
+
const { key, width, height, pixels, want } = this.fieldQueue.shift()!;
|
|
960
|
+
this.queued.delete(key);
|
|
961
|
+
const place = this.room(width, height, this.far(want.x, want.y, 0, 0));
|
|
962
|
+
if (!place) {
|
|
963
|
+
// no room nearer than it: wanted again once the view moves
|
|
964
|
+
this.missing.set(key, want);
|
|
965
|
+
this.stalled.add(key);
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
const field = distanceField(pixels, width, height, this.pad);
|
|
969
|
+
landed = true;
|
|
970
|
+
this.put(key, place, width, height, field, want);
|
|
971
|
+
}
|
|
972
|
+
if (landed) this.generation++;
|
|
973
|
+
return landed;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* Room for a field of a label this far from the view: free room, or room
|
|
978
|
+
* made by dropping fields — first the ones no pack draws, then the ones
|
|
979
|
+
* drawn farthest from the view, so long as they are farther than it.
|
|
980
|
+
* Nothing on screen is dropped for anything. Null when there is none, and
|
|
981
|
+
* the atlas is `full` until something is freed.
|
|
982
|
+
*/
|
|
983
|
+
private room(w: number, h: number, distance: number): Place | null {
|
|
984
|
+
let place = this.allot(w, h) ?? this.squeeze(w, h);
|
|
985
|
+
while (!place) {
|
|
986
|
+
const v = this.victim(distance);
|
|
987
|
+
if (!v) {
|
|
988
|
+
this.full = true;
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
this.evict(v.key);
|
|
992
|
+
this.victimAt++;
|
|
993
|
+
place = this.allot(w, h) ?? this.squeeze(w, h);
|
|
994
|
+
}
|
|
995
|
+
return place;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Room made by sliding a shelf's fields together over its gaps: fields
|
|
1000
|
+
* of one height are not one width — `node 7` and `node 1234` — and a
|
|
1001
|
+
* narrow one dropped leaves a gap a wide one does not fit, so gaps apart
|
|
1002
|
+
* are room nothing uses. The fields moved are new placements: a box
|
|
1003
|
+
* drawn from one is written again from where it is now, in the same
|
|
1004
|
+
* frame (`slotOf`).
|
|
1005
|
+
*/
|
|
1006
|
+
private squeeze(w: number, h: number): Place | null {
|
|
1007
|
+
const height = Math.ceil(h / SHELF_STEP) * SHELF_STEP;
|
|
1008
|
+
const shelf = this.shelves.find(
|
|
1009
|
+
(s) =>
|
|
1010
|
+
s.height === height &&
|
|
1011
|
+
s.gaps.length > 0 &&
|
|
1012
|
+
s.gaps.reduce((n, g) => n + g.w, this.side - s.end) >= w,
|
|
1013
|
+
);
|
|
1014
|
+
if (!shelf) return null;
|
|
1015
|
+
const on = [...this.entries]
|
|
1016
|
+
.filter(([, e]) => e.shelf === shelf)
|
|
1017
|
+
.sort((a, b) => a[1].x - b[1].x);
|
|
1018
|
+
let x = 0;
|
|
1019
|
+
for (const [key, e] of on) {
|
|
1020
|
+
if (e.x !== x) {
|
|
1021
|
+
e.x = x;
|
|
1022
|
+
e.slot = this.nextSlot++;
|
|
1023
|
+
this.pending.add(key);
|
|
1024
|
+
if (this.mirror) this.paint(e);
|
|
1025
|
+
}
|
|
1026
|
+
x += e.width;
|
|
1027
|
+
}
|
|
1028
|
+
shelf.gaps = [];
|
|
1029
|
+
shelf.end = x;
|
|
1030
|
+
this.moves++;
|
|
1031
|
+
this.generation++;
|
|
1032
|
+
return this.allot(w, h);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/** The next field that may go for a label this far from the view, or
|
|
1036
|
+
* null — skipping the ones already gone, or set again since. */
|
|
1037
|
+
private victim(distance: number): Victim | null {
|
|
1038
|
+
if (!this.victims) {
|
|
1039
|
+
const list: Victim[] = [];
|
|
1040
|
+
for (const [key, e] of this.entries) {
|
|
1041
|
+
const d = e.used < this.epoch ? Infinity : this.reach(e);
|
|
1042
|
+
if (d > 0) list.push({ key, d, used: e.used, entry: e });
|
|
1043
|
+
}
|
|
1044
|
+
list.sort((a, b) => b.d - a.d || a.used - b.used);
|
|
1045
|
+
this.victims = list;
|
|
1046
|
+
this.victimAt = 0;
|
|
1047
|
+
}
|
|
1048
|
+
const list = this.victims;
|
|
1049
|
+
while (this.victimAt < list.length) {
|
|
1050
|
+
const v = list[this.victimAt];
|
|
1051
|
+
if (this.entries.get(v.key) !== v.entry) {
|
|
1052
|
+
this.victimAt++;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
return v.d > distance ? v : null;
|
|
1056
|
+
}
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/** How far from the view the nearest label drawn from a field is. */
|
|
1061
|
+
private reach(e: Entry): number {
|
|
1062
|
+
let d =
|
|
1063
|
+
e.live && e.liveAt === this.epoch
|
|
1064
|
+
? this.far(e.live[0], e.live[1], e.live[2], e.live[3])
|
|
1065
|
+
: Infinity;
|
|
1066
|
+
for (let i = 0; i < e.at.length && d > 0; i += 4) {
|
|
1067
|
+
d = Math.min(d, this.far(e.at[i], e.at[i + 1], e.at[i + 2], e.at[i + 3]));
|
|
1068
|
+
}
|
|
1069
|
+
return d === Infinity ? 0 : d;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/** A field into the place made for it — drawn, until a pack says where,
|
|
1073
|
+
* where the label that asked for it is. */
|
|
1074
|
+
private put(
|
|
1075
|
+
key: string,
|
|
1076
|
+
place: Place,
|
|
1077
|
+
width: number,
|
|
1078
|
+
height: number,
|
|
1079
|
+
field: Uint8Array,
|
|
1080
|
+
want: Want,
|
|
1081
|
+
): void {
|
|
1082
|
+
const entry: Entry = {
|
|
1083
|
+
x: place.x,
|
|
1084
|
+
y: place.shelf.y,
|
|
1085
|
+
width,
|
|
1086
|
+
height,
|
|
1087
|
+
field,
|
|
1088
|
+
// drawn as soon as its labels are written in
|
|
1089
|
+
used: this.epoch,
|
|
1090
|
+
at: [want.x, want.y, 0, 0],
|
|
1091
|
+
live: null,
|
|
1092
|
+
liveAt: -1,
|
|
1093
|
+
page: place.shelf.page,
|
|
1094
|
+
shelf: place.shelf,
|
|
1095
|
+
slot: this.nextSlot++,
|
|
1096
|
+
};
|
|
1097
|
+
this.entries.set(key, entry);
|
|
1098
|
+
if (entry.page > 0 && !this.mirror) {
|
|
1099
|
+
this.mirror = new Uint8Array(this.side * this.side * 4);
|
|
1100
|
+
for (const e of this.entries.values()) this.paint(e);
|
|
1101
|
+
} else if (this.mirror) {
|
|
1102
|
+
this.paint(entry);
|
|
1103
|
+
}
|
|
1104
|
+
this.pending.add(key);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/** A field written into its channel of the mirror. */
|
|
1108
|
+
private paint(e: Entry): void {
|
|
1109
|
+
const m = this.mirror!;
|
|
1110
|
+
const channel = CHANNELS[e.page];
|
|
1111
|
+
const side = this.side;
|
|
1112
|
+
const field = e.field;
|
|
1113
|
+
for (let r = 0; r < e.height; r++) {
|
|
1114
|
+
let at = ((e.y + r) * side + e.x) * 4 + channel;
|
|
1115
|
+
const row = r * e.width;
|
|
1116
|
+
for (let c = 0; c < e.width; c++, at += 4) m[at] = field[row + c];
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/** A field dropped, and its room given back to its shelf. */
|
|
1121
|
+
private evict(key: string): void {
|
|
1122
|
+
const e = this.entries.get(key);
|
|
1123
|
+
if (!e) return;
|
|
1124
|
+
this.entries.delete(key);
|
|
1125
|
+
this.pending.delete(key);
|
|
1126
|
+
this.free(e);
|
|
1127
|
+
this.full = false;
|
|
1128
|
+
this.moves++;
|
|
1129
|
+
this.generation++;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Shelves by height, in steps: a label's field is as tall as the base
|
|
1134
|
+
* size's line and the margin, so nearly every field is one height, and a
|
|
1135
|
+
* field dropped leaves a gap the next one fits. The first fit in a gap,
|
|
1136
|
+
* then the end of a shelf, then a new shelf — on the first page with
|
|
1137
|
+
* room, alpha first.
|
|
1138
|
+
*/
|
|
1139
|
+
private allot(w: number, h: number): Place | null {
|
|
1140
|
+
if (w > this.side || h > this.side) return null;
|
|
1141
|
+
const height = Math.ceil(h / SHELF_STEP) * SHELF_STEP;
|
|
1142
|
+
for (const shelf of this.shelves) {
|
|
1143
|
+
if (shelf.height !== height) continue;
|
|
1144
|
+
const gaps = shelf.gaps;
|
|
1145
|
+
for (let i = 0; i < gaps.length; i++) {
|
|
1146
|
+
const gap = gaps[i];
|
|
1147
|
+
if (gap.w < w) continue;
|
|
1148
|
+
const x = gap.x;
|
|
1149
|
+
gap.x += w;
|
|
1150
|
+
gap.w -= w;
|
|
1151
|
+
if (gap.w === 0) gaps.splice(i, 1);
|
|
1152
|
+
return { shelf, x };
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
for (const shelf of this.shelves) {
|
|
1156
|
+
if (shelf.height !== height || shelf.end + w > this.side) continue;
|
|
1157
|
+
const x = shelf.end;
|
|
1158
|
+
shelf.end += w;
|
|
1159
|
+
return { shelf, x };
|
|
1160
|
+
}
|
|
1161
|
+
for (let page = 0; page < this.pages; page++) {
|
|
1162
|
+
const y = this.tops[page];
|
|
1163
|
+
if (y + height > this.side) continue;
|
|
1164
|
+
const shelf: Shelf = { page, y, height, end: w, gaps: [] };
|
|
1165
|
+
this.tops[page] = y + height;
|
|
1166
|
+
this.shelves.push(shelf);
|
|
1167
|
+
return { shelf, x: 0 };
|
|
1168
|
+
}
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/** A field's room back to its shelf, merged with the gaps beside it; a
|
|
1173
|
+
* shelf left empty at the bottom of its page goes back to the page. */
|
|
1174
|
+
private free(e: Entry): void {
|
|
1175
|
+
const shelf = e.shelf;
|
|
1176
|
+
const gaps = shelf.gaps;
|
|
1177
|
+
gaps.push({ x: e.x, w: e.width });
|
|
1178
|
+
gaps.sort((a, b) => a.x - b.x);
|
|
1179
|
+
let n = 0;
|
|
1180
|
+
for (const gap of gaps) {
|
|
1181
|
+
const last = n > 0 ? gaps[n - 1] : null;
|
|
1182
|
+
if (last && last.x + last.w === gap.x) last.w += gap.w;
|
|
1183
|
+
else gaps[n++] = gap;
|
|
1184
|
+
}
|
|
1185
|
+
gaps.length = n;
|
|
1186
|
+
const last = gaps[n - 1];
|
|
1187
|
+
if (last && last.x + last.w === shelf.end) {
|
|
1188
|
+
shelf.end = last.x;
|
|
1189
|
+
gaps.pop();
|
|
1190
|
+
}
|
|
1191
|
+
if (
|
|
1192
|
+
shelf.end === 0 &&
|
|
1193
|
+
gaps.length === 0 &&
|
|
1194
|
+
shelf.y + shelf.height === this.tops[shelf.page]
|
|
1195
|
+
) {
|
|
1196
|
+
this.tops[shelf.page] = shelf.y;
|
|
1197
|
+
this.shelves.splice(this.shelves.indexOf(shelf), 1);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* The atlas texture, bound to unit 0, with every field set since the last
|
|
1203
|
+
* call uploaded into it. Made on first use — and made again for a new
|
|
1204
|
+
* context, into which every field is uploaded again from the copies kept
|
|
1205
|
+
* here.
|
|
1206
|
+
*/
|
|
1207
|
+
bind(gl: GLLike, fresh: boolean): void {
|
|
1208
|
+
if (!this.texture || fresh) {
|
|
1209
|
+
this.texture = gl.createTexture();
|
|
1210
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
1211
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
1212
|
+
gl.texImage2D(
|
|
1213
|
+
gl.TEXTURE_2D,
|
|
1214
|
+
0,
|
|
1215
|
+
gl.RGBA,
|
|
1216
|
+
this.side,
|
|
1217
|
+
this.side,
|
|
1218
|
+
0,
|
|
1219
|
+
gl.RGBA,
|
|
1220
|
+
gl.UNSIGNED_BYTE,
|
|
1221
|
+
this.mirror,
|
|
1222
|
+
);
|
|
1223
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
1224
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
1225
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1226
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1227
|
+
// the mirror went up whole; without one, every field goes up again
|
|
1228
|
+
if (this.mirror) this.pending.clear();
|
|
1229
|
+
else for (const key of this.entries.keys()) this.pending.add(key);
|
|
1230
|
+
}
|
|
1231
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1232
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
1233
|
+
if (this.textureGeneration === this.generation && this.pending.size === 0) {
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
1237
|
+
const side = this.side;
|
|
1238
|
+
for (const key of this.pending) {
|
|
1239
|
+
const entry = this.entries.get(key);
|
|
1240
|
+
if (!entry) continue;
|
|
1241
|
+
const texels = entry.width * entry.height;
|
|
1242
|
+
if (this.scratch.length < texels * 4) {
|
|
1243
|
+
this.scratch = new Uint8Array(texels * 4).fill(255);
|
|
1244
|
+
}
|
|
1245
|
+
const rgba = this.scratch.subarray(0, texels * 4);
|
|
1246
|
+
const mirror = this.mirror;
|
|
1247
|
+
if (mirror) {
|
|
1248
|
+
// the rect as the mirror has it: this field, and the other pages'
|
|
1249
|
+
for (let r = 0; r < entry.height; r++) {
|
|
1250
|
+
const src = ((entry.y + r) * side + entry.x) * 4;
|
|
1251
|
+
rgba.set(
|
|
1252
|
+
mirror.subarray(src, src + entry.width * 4),
|
|
1253
|
+
r * entry.width * 4,
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
} else {
|
|
1257
|
+
// the first page alone: white, the field in alpha
|
|
1258
|
+
const field = entry.field;
|
|
1259
|
+
for (let i = 0; i < texels; i++) {
|
|
1260
|
+
rgba[i * 4] = 255;
|
|
1261
|
+
rgba[i * 4 + 1] = 255;
|
|
1262
|
+
rgba[i * 4 + 2] = 255;
|
|
1263
|
+
rgba[i * 4 + 3] = field[i];
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
gl.texSubImage2D(
|
|
1267
|
+
gl.TEXTURE_2D,
|
|
1268
|
+
0,
|
|
1269
|
+
entry.x,
|
|
1270
|
+
entry.y,
|
|
1271
|
+
entry.width,
|
|
1272
|
+
entry.height,
|
|
1273
|
+
gl.RGBA,
|
|
1274
|
+
gl.UNSIGNED_BYTE,
|
|
1275
|
+
rgba,
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
this.pending.clear();
|
|
1279
|
+
this.textureGeneration = this.generation;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
dispose(gl?: GLLike): void {
|
|
1283
|
+
if (gl && this.texture) gl.deleteTexture(this.texture);
|
|
1284
|
+
this.texture = null;
|
|
1285
|
+
this.fieldQueue = [];
|
|
1286
|
+
this.queued.clear();
|
|
1287
|
+
this.stagingCtx?.destroy?.();
|
|
1288
|
+
this.stagingCtx = null;
|
|
1289
|
+
this.staging?.destroy();
|
|
1290
|
+
this.staging = null;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1295
|
+
type GLLike = any;
|