ranger-compiler 3.5.0 → 3.5.2
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/CHANGELOG.md +1160 -19
- package/LICENSE +3 -1
- package/README.md +124 -29
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79924 -56922
- package/dist/lib/JSON.rgr +102 -91
- package/dist/lib/Shell.rgr +4 -4
- package/dist/lib/apple/AppleToolchain.rgr +4 -4
- package/dist/lib/apple/README.md +5 -4
- package/dist/lib/apple/apple_test.rgr +1 -1
- package/dist/lib/core/README.md +1 -1
- package/dist/lib/evg/EVG.rgr +12 -0
- package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
- package/dist/lib/evg/EVGA11yTree.rgr +894 -0
- package/dist/lib/evg/EVGBox.rgr +267 -0
- package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
- package/dist/lib/evg/EVGCodepoint.rgr +316 -0
- package/dist/lib/evg/EVGColor.rgr +700 -0
- package/dist/lib/evg/EVGCommands.rgr +177 -0
- package/dist/lib/evg/EVGComponent.rgr +331 -0
- package/dist/lib/evg/EVGComponentTest.rgr +387 -0
- package/dist/lib/evg/EVGConnector.rgr +541 -0
- package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
- package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
- package/dist/lib/evg/EVGEasing.rgr +370 -0
- package/dist/lib/evg/EVGEffectTest.rgr +253 -0
- package/dist/lib/evg/EVGElement.rgr +4914 -0
- package/dist/lib/evg/EVGFixedTest.rgr +289 -0
- package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
- package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
- package/dist/lib/evg/EVGFling.rgr +244 -0
- package/dist/lib/evg/EVGFocus.rgr +400 -0
- package/dist/lib/evg/EVGFocusTest.rgr +399 -0
- package/dist/lib/evg/EVGGradient.rgr +322 -0
- package/dist/lib/evg/EVGGrapheme.rgr +190 -0
- package/dist/lib/evg/EVGGrid.rgr +975 -0
- package/dist/lib/evg/EVGHitTest.rgr +208 -0
- package/dist/lib/evg/EVGHoles.rgr +294 -0
- package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
- package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
- package/dist/lib/evg/EVGHostTree.rgr +834 -0
- package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
- package/dist/lib/evg/EVGImageDecode.rgr +117 -0
- package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
- package/dist/lib/evg/EVGInspect.rgr +883 -0
- package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
- package/dist/lib/evg/EVGJsonTest.rgr +535 -0
- package/dist/lib/evg/EVGLayout.rgr +4220 -0
- package/dist/lib/evg/EVGMeasure.rgr +1267 -0
- package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
- package/dist/lib/evg/EVGPatch.rgr +1721 -0
- package/dist/lib/evg/EVGPatchTest.rgr +692 -0
- package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
- package/dist/lib/evg/EVGReconcile.rgr +226 -0
- package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
- package/dist/lib/evg/EVGReject.rgr +126 -0
- package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
- package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
- package/dist/lib/evg/EVGRuler.rgr +232 -0
- package/dist/lib/evg/EVGRulerTest.rgr +172 -0
- package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
- package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
- package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
- package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
- package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
- package/dist/lib/evg/EVGText.rgr +105 -0
- package/dist/lib/evg/EVGTextEngine.rgr +536 -0
- package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
- package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
- package/dist/lib/evg/EVGToolbar.rgr +1369 -0
- package/dist/lib/evg/EVGTransition.rgr +728 -0
- package/dist/lib/evg/EVGTreeJson.rgr +721 -0
- package/dist/lib/evg/EVGUnit.rgr +554 -0
- package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
- package/dist/lib/evg/EvgApp.rgr +189 -0
- package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
- package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
- package/dist/lib/evg/EvgHost.rgr +451 -0
- package/dist/lib/evg/EvgTest.rgr +91 -0
- package/dist/lib/evg/EvgTraceColor.rgr +236 -0
- package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
- package/dist/lib/evg/EvgTraceFit.rgr +767 -0
- package/dist/lib/evg/EvgTracePath.rgr +435 -0
- package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
- package/dist/lib/evg/EvgViewport.rgr +313 -0
- package/dist/lib/evg/FxDemoDoc.rgr +185 -0
- package/dist/lib/evg/HOSTS.md +227 -0
- package/dist/lib/evg/ISSUES.md +835 -0
- package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
- package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
- package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
- package/dist/lib/evg/PLAN_EVG.md +518 -0
- package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
- package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
- package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
- package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
- package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
- package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
- package/dist/lib/evg/PathBuilder.rgr +333 -0
- package/dist/lib/evg/README.md +1236 -0
- package/dist/lib/evg/SPEC.md +1252 -0
- package/dist/lib/evg/SVGPathParser.rgr +1199 -0
- package/dist/lib/evg/SvgParser.rgr +1736 -0
- package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
- package/dist/lib/evg/VectorShapes.rgr +331 -0
- package/dist/lib/evg/VectorStroke.rgr +107 -0
- package/dist/lib/evg/VectorViewBox.rgr +380 -0
- package/dist/lib/evg/agent/README.md +290 -0
- package/dist/lib/evg/agent/evg_agent.rgr +899 -0
- package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
- package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
- package/dist/lib/evg/agent/fixtures/connector.css +63 -0
- package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
- package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
- package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
- package/dist/lib/evg/agent/fixtures/popover.css +99 -0
- package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
- package/dist/lib/evg/agent/roundtrip.sh +74 -0
- package/dist/lib/evg/agent/smoke.sh +230 -0
- package/dist/lib/evg/android/README.md +97 -0
- package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
- package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
- package/dist/lib/evg/android/androidstubs/App.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
- package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
- package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
- package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
- package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
- package/dist/lib/evg/android/androidstubs/View.kt +113 -0
- package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
- package/dist/lib/evg/apple/README.md +128 -0
- package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
- package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
- package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
- package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
- package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
- package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
- package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
- package/dist/lib/evg/bench/README.md +64 -0
- package/dist/lib/evg/bench/layout-bench.mjs +316 -0
- package/dist/lib/evg/bench/layout-cases.mjs +510 -0
- package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
- package/dist/lib/evg/bin/.gitignore +17 -0
- package/dist/lib/evg/evg_test.rgr +313 -0
- package/dist/lib/evg/gl/README.md +198 -0
- package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
- package/dist/lib/evg/gl/blur-check.mjs +399 -0
- package/dist/lib/evg/gl/boxmodel.json +1 -0
- package/dist/lib/evg/gl/demo.html +46 -0
- package/dist/lib/evg/gl/effect-presets.css +285 -0
- package/dist/lib/evg/gl/effect-presets.js +63 -0
- package/dist/lib/evg/gl/effect-shots.mjs +135 -0
- package/dist/lib/evg/gl/evg-a11y.js +566 -0
- package/dist/lib/evg/gl/evg-binary.js +160 -0
- package/dist/lib/evg/gl/evg-engine.js +296 -0
- package/dist/lib/evg/gl/evg-fx.js +237 -0
- package/dist/lib/evg/gl/evg-gestures.js +209 -0
- package/dist/lib/evg/gl/evg-list.js +167 -0
- package/dist/lib/evg/gl/evg-measure.js +186 -0
- package/dist/lib/evg/gl/evg-textinput.js +303 -0
- package/dist/lib/evg/gl/evg-view.js +144 -0
- package/dist/lib/evg/gl/evg-webgl.js +3942 -0
- package/dist/lib/evg/gl/fx-check.mjs +998 -0
- package/dist/lib/evg/gl/fx-demo.html +166 -0
- package/dist/lib/evg/gl/fx-demo.js +2 -0
- package/dist/lib/evg/gl/fx-serve.mjs +47 -0
- package/dist/lib/evg/gl/gestures-check.mjs +189 -0
- package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
- package/dist/lib/evg/gl/measure-check.mjs +135 -0
- package/dist/lib/evg/gl/rotation-check.mjs +207 -0
- package/dist/lib/evg/gl/shift-check.mjs +113 -0
- package/dist/lib/evg/gl/stroke-check.mjs +168 -0
- package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
- package/dist/lib/evg/gl/view-check.mjs +343 -0
- package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
- package/dist/lib/evg/html/evg-dom.js +318 -0
- package/dist/lib/evg/html/evg-html.js +600 -0
- package/dist/lib/evg/inspect/README.md +415 -0
- package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
- package/dist/lib/evg/inspect/evg-inspect.js +947 -0
- package/dist/lib/evg/inspect/shots/css.png +0 -0
- package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
- package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
- package/dist/lib/evg/inspect/shots/state.png +0 -0
- package/dist/lib/evg/inspect/shots.mjs +226 -0
- package/dist/lib/evg/oracle/css-blur.json +576 -0
- package/dist/lib/evg/oracle/css-box.json +157 -0
- package/dist/lib/evg/oracle/css-timing.json +709 -0
- package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
- package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
- package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
- package/dist/lib/evg/original/EVGColor.clj +310 -0
- package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
- package/dist/lib/evg/original/SVGPath.rgr +627 -0
- package/dist/lib/evg/original/Vec2.crgr +103 -0
- package/dist/lib/evg/ranger.json +9 -0
- package/dist/lib/evg/showcase/README.md +246 -0
- package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
- package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
- package/dist/lib/evg/showcase/build.mjs +651 -0
- package/dist/lib/evg/showcase/pages/album.tsx +30 -0
- package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
- package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
- package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
- package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
- package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
- package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
- package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
- package/dist/lib/evg/showcase/pages/more.tsx +332 -0
- package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
- package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
- package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
- package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
- package/dist/lib/evg/showcase/pages/units.tsx +35 -0
- package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
- package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
- package/dist/lib/evg/showcase/pages/views.tsx +223 -0
- package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
- package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
- package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
- package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
- package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
- package/dist/lib/evg/showcase/themes/more-default.css +128 -0
- package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
- package/dist/lib/evg/showcase/themes/showcase.css +508 -0
- package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
- package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
- package/dist/lib/evg/showcase/themes/views-default.css +15 -0
- package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
- package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
- package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
- package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
- package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
- package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
- package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
- package/dist/lib/evg/web/responsive/README.md +97 -0
- package/dist/lib/evg/web/responsive/build.mjs +90 -0
- package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
- package/dist/lib/evg/web/responsive/index.html +177 -0
- package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
- package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
- package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
- package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
- package/dist/lib/evg/web/tools/minify.mjs +60 -0
- package/dist/lib/evg/web/tracer/build.mjs +87 -0
- package/dist/lib/evg/web/tracer/index.html +2805 -0
- package/dist/lib/evg/web/tracer/sample.png +0 -0
- package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
- package/dist/lib/evgr/Cargo.lock +21 -0
- package/dist/lib/evgr/Cargo.toml +19 -0
- package/dist/lib/evgr/README.md +140 -0
- package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
- package/dist/lib/evgr/bench/compare.mjs +252 -0
- package/dist/lib/evgr/bench/speed.mjs +277 -0
- package/dist/lib/evgr/bin/.gitignore +3 -0
- package/dist/lib/evgr/src/bin/bench.rs +106 -0
- package/dist/lib/evgr/src/bin/smoke.rs +13 -0
- package/dist/lib/evgr/src/grid.rs +730 -0
- package/dist/lib/evgr/src/lib.rs +1003 -0
- package/dist/lib/evgr/src/style.rs +468 -0
- package/dist/lib/evgr/src/text.rs +83 -0
- package/dist/lib/image/BitReader.rgr +171 -0
- package/dist/lib/image/Buffer.rgr +173 -0
- package/dist/lib/image/DCT.rgr +283 -0
- package/dist/lib/image/Deflate.rgr +339 -0
- package/dist/lib/image/HuffmanDecoder.rgr +181 -0
- package/dist/lib/image/ImageBuffer.rgr +706 -0
- package/dist/lib/image/JPEGDecoder.rgr +808 -0
- package/dist/lib/image/PNGDecoder.rgr +544 -0
- package/dist/lib/image/PNGEncoder.rgr +388 -0
- package/dist/lib/image/PPMImage.rgr +224 -0
- package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
- package/dist/lib/image/README.md +33 -0
- package/dist/lib/image/RasterBuffer.rgr +294 -0
- package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
- package/dist/lib/image/WebPDecoder.rgr +433 -0
- package/dist/lib/image/WebPLossless.rgr +1035 -0
- package/dist/lib/image/WebPLossy.rgr +1929 -0
- package/dist/lib/image/ranger.json +9 -0
- package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
- package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
- package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
- package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
- package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
- package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
- package/dist/lib/image/tests/run_webp_tests.sh +39 -0
- package/dist/lib/rust/RsJson.rgr +468 -0
- package/dist/lib/rust/RsPrelude.rgr +1663 -0
- package/dist/lib/shell_test.rgr +7 -7
- package/dist/lib/stdlib.rgr +21 -5
- package/dist/lib/zip/ranger.json +6 -0
- package/dist/rgrc.js +87318 -67610
- package/package.json +1010 -1040
- package/dist/README.md +0 -117
- package/dist/package.json +0 -47
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
# Native hosts — a spike
|
|
2
|
+
|
|
3
|
+
How far EVG can move from "a native 2D API paints our list" towards "the
|
|
4
|
+
platform's own presentation tree shows our document", on the three platforms
|
|
5
|
+
that have a painter today: the browser, Apple (UIKit / SwiftUI) and Android
|
|
6
|
+
(View / Compose). What the platform can genuinely do better than a canvas,
|
|
7
|
+
where the wall is, and what to try first.
|
|
8
|
+
|
|
9
|
+
Status: **S0–S3 built** (§8) — the platform measurers; the engine off the UI
|
|
10
|
+
thread on all three platforms; `EVGHostTree`, the diff channel; and the
|
|
11
|
+
retained DOM painter on it, on the responsive page and on RealTrainer — both
|
|
12
|
+
live as `?painter=dom` at
|
|
13
|
+
[/evg/responsive/](https://terotests.github.io/Ranger/evg/responsive/?painter=dom)
|
|
14
|
+
and [/realtrainer/](https://terotests.github.io/Ranger/realtrainer/?painter=dom).
|
|
15
|
+
S4–S6 are design. The numbers
|
|
16
|
+
in it are measured from the repository as it is (`npm run rt:bench 40`, the
|
|
17
|
+
stylesheet tallies in §3.3), not projected.
|
|
18
|
+
|
|
19
|
+
Related: [`README.md`](README.md) (the pipeline and the seam),
|
|
20
|
+
[`gl/README.md`](gl/README.md) (the display list as a GPU input),
|
|
21
|
+
[`html/evg-html.js`](html/evg-html.js) (the SVG painter and its header on
|
|
22
|
+
identity), [`PLAN_ACCESSIBILITY.md`](PLAN_ACCESSIBILITY.md) (the second list),
|
|
23
|
+
[`inspect/README.md`](inspect/README.md) (the path identity),
|
|
24
|
+
[`apple/README.md`](apple/README.md), [`android/README.md`](android/README.md),
|
|
25
|
+
[`../../gallery/ui/PLAN_INPUTS.md`](../../gallery/ui/PLAN_INPUTS.md) (the text-input bridge).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 0. The short version
|
|
30
|
+
|
|
31
|
+
Three levels of "native", and the middle one is the one to build:
|
|
32
|
+
|
|
33
|
+
| | What the platform gets | What EVG keeps | Print parity |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| **L0 — painter** (today) | a flat list of quads, runs, paths, clips | everything: cascade, layout, lines, hit test, a11y | exact |
|
|
36
|
+
| **L1 — native nodes, EVG geometry** | one real node per element (a `div`, a `View`, a composable), placed at the rectangle EVG computed | cascade, layout, line breaks, hit test, a11y *content* | exact, same lines |
|
|
37
|
+
| **L2 — native layout** | the platform lays the boxes out itself (CSS flex/grid, SwiftUI stacks, Compose Row/Column) | the tree, the stylesheet, the controllers | broken unless test-gated |
|
|
38
|
+
|
|
39
|
+
The claim the engine makes everywhere — *the same tree, styled by the same
|
|
40
|
+
sheet, comes out the same on every target* — is exactly what L2 gives up.
|
|
41
|
+
Every platform lays flex out slightly differently (§3.3, §4.3, §5.3 list the
|
|
42
|
+
deltas), and none of them can be asked to lay a page out the way the PDF was.
|
|
43
|
+
So L2 is an **opt-in mode behind a conformance oracle**, not the default, and
|
|
44
|
+
only the web has an oracle for it today (`PLAN_CSS_LAYOUT_AND_FONTS.md` §4.1
|
|
45
|
+
rule 9: the box model and 20 grid fixtures are already checked against
|
|
46
|
+
Chromium).
|
|
47
|
+
|
|
48
|
+
L1 is where the platform's real advantages live and none of the parity is
|
|
49
|
+
lost: element identity that survives a frame, the platform's text-input
|
|
50
|
+
session, its scrolling and compositor, its accessibility tree *as the tree*
|
|
51
|
+
rather than a mirror, its animations, and — on every platform — the engine
|
|
52
|
+
running **off the UI thread**. What L1 needs from EVG is one new thing, and it
|
|
53
|
+
is the same thing on all three platforms: a derived channel that says *what
|
|
54
|
+
changed*, keyed by an identity that survives a rebuild (§6, D2).
|
|
55
|
+
|
|
56
|
+
**Does this fight the engine's design?** Only at L2. The design has two
|
|
57
|
+
halves — *one layout, so every target agrees*, and *a dumb seam, so a painter
|
|
58
|
+
is small* — and L1 keeps the first and adds a second seam beside the second:
|
|
59
|
+
a host tree next to the display list, derived on the same pass by the same
|
|
60
|
+
rule the a11y tree and the inspector already follow. A painter stays a
|
|
61
|
+
painter. What L1 gives up is a slogan, not an invariant: the display list is
|
|
62
|
+
no longer the *only* way out of the engine. L2 is the one that trades the
|
|
63
|
+
first half away, which is why it is a mode and not a default.
|
|
64
|
+
|
|
65
|
+
The order to try it in is **web first** (the platform already has everything
|
|
66
|
+
L1 needs and the parity harness exists), **Compose second** (its `Layout`
|
|
67
|
+
composable is the cleanest fit any of the three has for "EVG measures, the
|
|
68
|
+
platform hosts"), **SwiftUI third** (the same fit through the `Layout`
|
|
69
|
+
protocol, but with a view-count ceiling that decides which apps qualify).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 1. Where things stand
|
|
74
|
+
|
|
75
|
+
The pipeline is `tree → cascade → layout → display list → painter`, and the
|
|
76
|
+
seam below the display list is load-bearing (`README.md`, "The display list").
|
|
77
|
+
The three platforms in question are all painters:
|
|
78
|
+
|
|
79
|
+
| Platform | Host | Painter | Per frame |
|
|
80
|
+
| --- | --- | --- | --- |
|
|
81
|
+
| Browser | `<canvas>` + WebGL 2 (`gl/evg-webgl.js`) or `<svg>` (`html/evg-html.js`) | instanced quads / SVG nodes rebuilt | list → GPU buffers, or list → new SVG tree |
|
|
82
|
+
| Apple | `UIView` (`ui/ios`, `realtrainer/ios`), SwiftUI `Canvas` (watch — SwiftUI is the only toolkit there, §7.5) | `EvgPainter.swift` over `CGContext` | every command re-rasterised |
|
|
83
|
+
| Android | `View` (`ui/android`, `realtrainer/android`) | `EvgPainter.kt` over `android.graphics.Canvas` | every command re-rasterised |
|
|
84
|
+
|
|
85
|
+
What the engine already does that an L1 host would lean on:
|
|
86
|
+
|
|
87
|
+
* **Retention.** `EVGReconcile` keeps element objects across a rebuild by
|
|
88
|
+
`key`; the style cache reports `layoutClean()` / `nothingChanged()`; a
|
|
89
|
+
scroll container's clip is a **layer** whose commands are moved in place
|
|
90
|
+
(`EVGDisplayList.refreshLayers`); a `keepLayout` subtree is a **fragment**
|
|
91
|
+
whose commands are reused, not rebuilt.
|
|
92
|
+
* **Identity that exists but is not on the seam.** `EVGElement.key`
|
|
93
|
+
(sibling-scoped), `EVGInspect`'s structural path (`0/3/k:share`),
|
|
94
|
+
`inspectSlot` on the element and `EVGDrawCmd.node` on the command — the
|
|
95
|
+
last two only while the inspector is attached.
|
|
96
|
+
* **Four derived lists from one walk:** the display list, the hit test, the
|
|
97
|
+
a11y tree, the inspector tree. The rule they share ("derived from the
|
|
98
|
+
laid-out tree, same pass, recomputes nothing") is the rule a fifth list
|
|
99
|
+
would follow.
|
|
100
|
+
* **The platform already owns text input on the web.**
|
|
101
|
+
`gl/evg-textinput.js` parks a real `<input>` over the drawn field and
|
|
102
|
+
Ranger mirrors it. RealTrainer's Android port commits through an
|
|
103
|
+
`InputConnection`; its iOS port becomes first responder and types through
|
|
104
|
+
the same bridge.
|
|
105
|
+
* **A binary scene format built for a thread hop.** `toBinary()` writes an
|
|
106
|
+
`EVGSceneBinary` whose `cmds`, `pts` and `ends` are `int_buffer`s — an
|
|
107
|
+
`Int32Array` on the JavaScript target, and therefore transferable to and
|
|
108
|
+
from a Worker with no copy. Its header records why: 12 ms of layout was
|
|
109
|
+
followed by 62 ms of `toJson` and 19 ms of `JSON.parse` on a 10 084-command
|
|
110
|
+
slide.
|
|
111
|
+
|
|
112
|
+
And what a scroll frame costs on the engine side, on the app that is most
|
|
113
|
+
like a phone app (`npm run rt:bench 40`: a 40× diary, 390×844, 200 scroll
|
|
114
|
+
frames):
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
scrollDocument 0.00 ms
|
|
118
|
+
+ display() 0.04 ms
|
|
119
|
+
+ displayListJson() 0.34 ms
|
|
120
|
+
+ JSON.parse 0.64 ms
|
|
121
|
+
hitId() (per pointer move) 0.03 ms
|
|
122
|
+
a11yJson() + parse (settled only) 3.96 ms
|
|
123
|
+
177 draw commands, 24185 bytes
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The engine's share of a scroll frame is well under a millisecond. Whatever a
|
|
127
|
+
scroll frame costs on a device today, it is being spent **below the seam** —
|
|
128
|
+
in the painter re-rasterising 177 commands, and on the browser in the DOM
|
|
129
|
+
mirror the a11y tree is rebuilt into once the page settles. That is the
|
|
130
|
+
budget an L1 host is competing for, and it is why "native" here means
|
|
131
|
+
*hosting*, not a faster layout.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 2. The three levels, and what decides between them
|
|
136
|
+
|
|
137
|
+
**L0 — the painter.** What exists. The platform contributes glyph
|
|
138
|
+
rasterisation, a 2D API and nothing else. Text is a picture; a screen reader
|
|
139
|
+
gets a mirror; a text field is a proxy; the compositor never sees a layer.
|
|
140
|
+
|
|
141
|
+
**L1 — native nodes, EVG geometry.** One host node per element, or per kept
|
|
142
|
+
subtree, placed at `calculatedX/Y/Width/Height`. The host does what a canvas
|
|
143
|
+
cannot:
|
|
144
|
+
|
|
145
|
+
* keep a node's **identity** — so a CSS transition, a focus ring, a caret,
|
|
146
|
+
a `UITextField` or a `BasicTextField` has something to attach to;
|
|
147
|
+
* run **text input** in the field itself rather than over it;
|
|
148
|
+
* **scroll and animate on the compositor** — a scroll layer becomes a
|
|
149
|
+
`transform: translate`, a `CALayer` position, a `graphicsLayer`
|
|
150
|
+
translation, and the UI thread is not in the loop;
|
|
151
|
+
* publish **accessibility as the tree itself**: a `<button>` is a button,
|
|
152
|
+
`Modifier.semantics` is the a11y node, `.accessibilityLabel` is the name —
|
|
153
|
+
and the separate mirror (3.96 ms a frame, on settle) goes away on the web;
|
|
154
|
+
* let the engine run **off the UI thread**, because what crosses to the UI
|
|
155
|
+
thread is a diff of nodes and rectangles, not a call into the engine.
|
|
156
|
+
|
|
157
|
+
Geometry stays EVG's, so the hit test, the a11y rectangles, the inspector and
|
|
158
|
+
the PDF still agree with the picture. The one thing that moves is **who
|
|
159
|
+
measures text** (D3): the platform's own metrics, so that the glyphs the host
|
|
160
|
+
draws are the glyphs EVG wrapped with — but the *breaks* stay EVG's.
|
|
161
|
+
|
|
162
|
+
**L2 — native layout.** Emit `display: flex`, an `HStack`, a `Row`, and let
|
|
163
|
+
the platform place the children. This is what the question in the title is
|
|
164
|
+
really asking, and the honest answer is that it works for the common subset
|
|
165
|
+
on each platform and stops at a different place on each:
|
|
166
|
+
|
|
167
|
+
* CSS is the only one of the three that has EVG's model *natively* — flex
|
|
168
|
+
with grow/shrink/basis, wrap, grid with `fr` and `minmax` and areas.
|
|
169
|
+
The mapping is small (§3.3) and the deltas are known and testable.
|
|
170
|
+
* SwiftUI has stacks and `Grid`, no `flex-shrink`, no `flex-basis`, no
|
|
171
|
+
wrapping `HStack`, and a `Layout` protocol for everything else — which is
|
|
172
|
+
where EVG's own algorithm would go (§4.3).
|
|
173
|
+
* Compose has `Row`/`Column` with `weight`, `FlowRow`, no CSS grid, and a
|
|
174
|
+
`Layout` composable that is the cleanest custom-layout API of the three —
|
|
175
|
+
and, again, EVG's algorithm is what would go in it (§5.3).
|
|
176
|
+
|
|
177
|
+
So on the two mobile platforms "native layout" collapses into "EVG's layout
|
|
178
|
+
running inside the platform's layout protocol", which is L1 with extra steps.
|
|
179
|
+
On the web it is a real option — and it is the option that breaks print
|
|
180
|
+
parity, because a browser's line breaking is not `EVGTextEngine`'s and its
|
|
181
|
+
sub-pixel flex arithmetic is not `EVGLayout`'s (ISSUES #8 exists because the
|
|
182
|
+
two were compared). L2 on the web is therefore a **mode**: the page becomes a
|
|
183
|
+
DOM application whose hit testing is DOM events and whose a11y is the DOM,
|
|
184
|
+
driven by Ranger controllers, and it is checked against the existing
|
|
185
|
+
Chromium oracles rather than trusted.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 3. Web
|
|
190
|
+
|
|
191
|
+
### 3.1 What exists
|
|
192
|
+
|
|
193
|
+
* `html/evg-html.js` — the display list as `<svg>`. Rebuilt every frame; the
|
|
194
|
+
header says why it cannot do otherwise: *"a draw command carries no id … so
|
|
195
|
+
every frame rebuilds every node, and a CSS transition, a focus ring or a
|
|
196
|
+
native `<input>` has nothing stable to attach to. That is a property of
|
|
197
|
+
the SEAM, not of this file."* Differenced against WebGL at 0.022 % on the
|
|
198
|
+
feature sheet, 0.000 % on the pptx deck.
|
|
199
|
+
* `EVGHTMLRenderer.rgr` — the print preview: absolutely positioned `<div>`s
|
|
200
|
+
from `calculatedX/Y`, one per element, with real `@font-face`s. Not
|
|
201
|
+
retained, not interactive, but it is already the L1 *shape*.
|
|
202
|
+
* `gl/evg-a11y.js` — the a11y tree as DOM over the canvas.
|
|
203
|
+
* `gl/evg-textinput.js` — the `<input>` proxy.
|
|
204
|
+
* `inspect/` — a structural path per element and `EVGDrawCmd.node`.
|
|
205
|
+
|
|
206
|
+
Every piece of an L1 web host is on the shelf. What is missing is the one
|
|
207
|
+
that ties them: a painter that walks the **tree**, keeps its nodes, and is
|
|
208
|
+
told what changed.
|
|
209
|
+
|
|
210
|
+
### 3.2 L1 — a retained DOM painter (`html/evg-dom.js`)
|
|
211
|
+
|
|
212
|
+
One DOM node per element, created once, patched thereafter:
|
|
213
|
+
|
|
214
|
+
| `elementType` | Host node | Notes |
|
|
215
|
+
| --- | --- | --- |
|
|
216
|
+
| container | `<div>` `position:absolute; left/top/width/height` from EVG; background, border, radius, shadow, opacity as CSS | `overflow` other than `visible` → `overflow:hidden` and a child layer `<div>` that carries `transform: translate(-scrollLeft, -scrollTop)`; a scroll is then a compositor-only style write |
|
|
217
|
+
| text | one `<div>` per **EVG line**, `white-space: pre`, the run's font, at the line box EVG placed | the browser must not wrap: EVG already broke the paragraph, and the PDF broke it in the same place. Bidi forced off as `evg-html.js` does (U+202D … U+202C) |
|
|
218
|
+
| image | `<img>` inside a clipping `<div>` | the `object-fit: cover` crop as `EVGHTMLRenderer` already does it |
|
|
219
|
+
| path | an inline `<svg>` with one `<path d>` per element | rings and `fill-rule="evenodd"` verbatim, exactly as `evg-html.js` — this is the one place SVG is chosen inside a DOM tree, and the reason the file's own header gives for SVG |
|
|
220
|
+
| a control (`role` set, or `a11yFocusable`) | the semantic element: `<button>`, `<input>`, `<div role=…>` | the a11y attributes go on the node itself; `evg-a11y.js`'s mirror is not needed for a tree that IS the DOM |
|
|
221
|
+
| a field (`InputCtl`) | a real `<input>` / `<textarea>` styled from the element | the proxy becomes the field; `applyEdit(value, selStart, selEnd)` is unchanged |
|
|
222
|
+
|
|
223
|
+
**Ordering.** DOM paint order is tree order plus stacking; EVG's is the
|
|
224
|
+
display list's, which the hit test walks backwards. Overlays and absolutes
|
|
225
|
+
are already placed after the flow (README, "Overlays"), so an overlay's node
|
|
226
|
+
appended last in its stacking context draws last. `z-index` is not in the
|
|
227
|
+
CSS subset and need not be: the tree order the list already emits is the
|
|
228
|
+
order the nodes are appended in.
|
|
229
|
+
|
|
230
|
+
**Transitions.** Two choices, and the recommendation is the boring one: keep
|
|
231
|
+
`EVGTransition` as the clock and write the *showing* value to the node, so
|
|
232
|
+
`ui:test`'s timing gates still hold and a transition on iOS and on the web
|
|
233
|
+
are the same code. CSS `transition` is available as an optimisation for
|
|
234
|
+
paint-only properties once the identity exists — it is the thing the current
|
|
235
|
+
painter cannot do at all.
|
|
236
|
+
|
|
237
|
+
**Fonts.** The demos lay out with `SimpleTextMeasurer` — a measured advance
|
|
238
|
+
table from a browser's sans fallback — and draw with the system sans. With a
|
|
239
|
+
DOM host the draw face is the same face; the table is a snapshot of it. An
|
|
240
|
+
`EVGTextMeasurer` backed by `CanvasRenderingContext2D.measureText` (the
|
|
241
|
+
metrics `evg-html.js` already caches per face) closes the loop: the wrap the
|
|
242
|
+
engine computes is the wrap the browser draws (D3).
|
|
243
|
+
|
|
244
|
+
**What it is checked against.** The same `parity.mjs` that holds the SVG
|
|
245
|
+
painter to the WebGL one, run on this painter; `ui:demo:page` for the text
|
|
246
|
+
session; the a11y gates with the mirror switched off and the tree asked
|
|
247
|
+
directly. The number to beat is the SVG painter's 0.022 %.
|
|
248
|
+
|
|
249
|
+
### 3.3 L2 — native CSS layout, as a mode
|
|
250
|
+
|
|
251
|
+
The mapping surface is small. Across `gallery/realtrainer/web/realtrainer.css`
|
|
252
|
+
and the eighteen `gallery/ui/demo/*.css` sheets:
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
2410 lengths in px, 44 in %, 6 in vh, 4 in rem, 1 in fill — no hp or em in these sheets
|
|
256
|
+
457 display: flex 3 display: grid 3 display: none 1 display: block
|
|
257
|
+
339 flex-wrap: nowrap (explicit) 15 flex-wrap: wrap
|
|
258
|
+
16 position: absolute 13 position: relative
|
|
259
|
+
81 lines that name something CSS does not have: overlay-*, evg-*, scrollbar-*, hp, fill, @vars
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
| EVG | CSS | Delta |
|
|
263
|
+
| --- | --- | --- |
|
|
264
|
+
| `display: flex` and the flex properties | the same | `align-items` defaults to `flex-start` here and `stretch` in CSS; `flex-wrap` initialises to `wrap` here and `nowrap` in CSS — so the emitter **writes both explicitly on every flex box** |
|
|
265
|
+
| `gap`, `justify-content`, `align-content` | the same | — |
|
|
266
|
+
| `display: grid`, tracks, `repeat`, `minmax`, areas, spans, `subgrid` | the same | no `auto-fit` / `auto-fill` on this side; the browser has it, so nothing to lose |
|
|
267
|
+
| `%` | the same | percent padding on the vertical axis was fixed to resolve against width, as CSS does (`PLAN_CSS_LAYOUT_AND_FONTS.md` 4.1 rule 9) |
|
|
268
|
+
| `hp` | `calc()` against the parent height, or `%` on a height property | not expressible on a width property without `calc()`; unused in the sheets above |
|
|
269
|
+
| `fill` | `flex: 1` in a flex parent | used once, as a spacer in a row |
|
|
270
|
+
| `vw` / `vh` | the same | on paper EVG says the page area; on the web it is the viewport, which is what the browser says too |
|
|
271
|
+
| `@media`, `@vars`, `var()`, themes | the same, or resolved by `EVGStyleSheet` before emission | resolving first keeps the palette sheet-level, which is the point of `@vars` |
|
|
272
|
+
| overlays (`overlay`, `overlay-side`, …) | **none** | EVG places them; the node gets `position:absolute` at the computed rectangle. CSS anchor positioning is not a target |
|
|
273
|
+
| `line-height: normal` = `1.15em` for the sans fallback | the face's own | the same face, so the same answer — but the browser's, not the table's |
|
|
274
|
+
| `evg-surface-effect`, `scrollbar-*`, `backdrop-filter` | dropped, native scrollbars, `backdrop-filter` | the ripple is a GPU pass with no DOM equivalent |
|
|
275
|
+
|
|
276
|
+
Where this stops being a mapping is **text**: the browser wraps a `<span>`
|
|
277
|
+
itself, its heights feed its own flex pass, and `calculatedHeight` is then a
|
|
278
|
+
number nobody drew. In this mode EVG's layout is not run at all for the
|
|
279
|
+
screen; the hit test is DOM events, the a11y tree is the DOM, the inspector is
|
|
280
|
+
the browser's, and the PDF is laid out separately by EVG from the same tree
|
|
281
|
+
and sheet. The check is the one `PLAN_CSS_LAYOUT_AND_FONTS.md` §6.3 already
|
|
282
|
+
names as option B: *"emit real CSS flex and diff against EVG frames in
|
|
283
|
+
tests"* — the box-model and grid oracles, extended with the demo pages.
|
|
284
|
+
|
|
285
|
+
This is a legitimate product shape — a Ranger-controlled DOM application —
|
|
286
|
+
and it is the shape that gives up the engine's claim. It should be built
|
|
287
|
+
after L1, if at all, and named as a different mode rather than as a faster
|
|
288
|
+
painter.
|
|
289
|
+
|
|
290
|
+
### 3.4 Threads on the web
|
|
291
|
+
|
|
292
|
+
Today: everything on the main thread, `requestAnimationFrame` calls
|
|
293
|
+
`app.tick`, `app.frame()`, and the painter, in that order.
|
|
294
|
+
|
|
295
|
+
Proposed: the compiled engine in a **Worker**. The main thread owns the DOM
|
|
296
|
+
(or the WebGL context via `OffscreenCanvas`), forwards pointer, wheel and
|
|
297
|
+
keyboard events to the worker, and receives one message per frame:
|
|
298
|
+
|
|
299
|
+
* for the WebGL painter, the `EVGSceneBinary` — three `Int32Array`s and a
|
|
300
|
+
string pool, **transferred**, so the cost is the hop and not a copy;
|
|
301
|
+
* for the DOM painter, the host-tree diff (D2) in the same fixed-point
|
|
302
|
+
record shape.
|
|
303
|
+
|
|
304
|
+
Text measurement in the worker: `SimpleTextMeasurer` is a table and needs
|
|
305
|
+
nothing; `OffscreenCanvas.getContext("2d").measureText` is available in
|
|
306
|
+
workers, so the canvas-backed measurer of §3.2 runs there too. Fonts must be
|
|
307
|
+
loaded in the worker's `FontFaceSet` as well as the page's, or the worker
|
|
308
|
+
measures the fallback — the same trap `evg-html.js`'s `clearFontMetrics`
|
|
309
|
+
exists for.
|
|
310
|
+
|
|
311
|
+
Latency: hit testing is 0.03 ms, so a press costs one `postMessage` round
|
|
312
|
+
trip, which is well under a frame. What this buys is that a 12 ms layout on
|
|
313
|
+
a chart-heavy slide no longer blocks input or the compositor, and a scroll
|
|
314
|
+
on a kept list is a style write on the main thread with nothing computed
|
|
315
|
+
there.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## 4. Apple
|
|
320
|
+
|
|
321
|
+
### 4.1 What exists
|
|
322
|
+
|
|
323
|
+
`apple/EvgPainter.swift` over a `CGContext`; a `UIView` host for iPhone and
|
|
324
|
+
iPad, a SwiftUI `Canvas` host on the watch. `frame()` is cached and repainted;
|
|
325
|
+
every command is rasterised every frame. The page draws its own text field
|
|
326
|
+
and caret; the view becomes first responder so the keyboard rises. The
|
|
327
|
+
dashboard lays out with EVG's estimate and paints with the platform's sans.
|
|
328
|
+
`apple/README.md` already names the next step for scroll: a `CGLayer` per
|
|
329
|
+
scroll layer, painted once per `buildSeq`, blitted per frame.
|
|
330
|
+
|
|
331
|
+
### 4.2 L1 on UIKit and on SwiftUI
|
|
332
|
+
|
|
333
|
+
**UIKit** is the direct translation of §3.2: a `UIView` (or, for cost, a
|
|
334
|
+
`CALayer`) per element or per kept subtree, `frame` from EVG, a `UITextField`
|
|
335
|
+
where the tree has a field — the web proxy pattern, with `UITextInput` doing
|
|
336
|
+
what `beforeinput`/`input` do — and `UIAccessibilityElement`s from
|
|
337
|
+
`EVGA11yTree`, which `PLAN_ACCESSIBILITY.md` §13 already sketches for macOS.
|
|
338
|
+
Scroll layers are `CALayer.position` writes, off the engine entirely.
|
|
339
|
+
`UIScrollView` stays out for the reason `DashboardView.swift` gives: the page
|
|
340
|
+
has its own scroll container and a second owner makes two.
|
|
341
|
+
|
|
342
|
+
**SwiftUI** is the more interesting host and the more constrained one. The
|
|
343
|
+
fit is the `Layout` protocol (iOS 16, macOS 13, watchOS 9): a container whose
|
|
344
|
+
`placeSubviews` reads `calculatedX/Y` off EVG and whose `sizeThatFits`
|
|
345
|
+
returns the root's size. Each element becomes a small `EvgNodeView` switching
|
|
346
|
+
on `elementType`: `Text` (one per EVG line, `.fixedSize()` so SwiftUI does
|
|
347
|
+
not wrap it again), `TextField`, `Image`, a `Path` for vectors, a `ZStack`
|
|
348
|
+
for a container. What SwiftUI then gives:
|
|
349
|
+
|
|
350
|
+
* identity through `.id(key)` and `ForEach` over keyed children, so a
|
|
351
|
+
`withAnimation` on a rectangle change is a real animation and
|
|
352
|
+
`EVGTransition`'s work can be handed over per property;
|
|
353
|
+
* native `TextField` with the system keyboard, selection, dictation and
|
|
354
|
+
autofill — the P3 bridge that `PLAN_INPUTS.md` could not attach anywhere;
|
|
355
|
+
* `.accessibilityLabel`, `.accessibilityAddTraits`, `.accessibilityValue`
|
|
356
|
+
from the `a11y*` fields — VoiceOver on the tree, not a mirror;
|
|
357
|
+
* `Material` for `backdrop-filter`, `.shadow`, `.clipShape` for radii.
|
|
358
|
+
|
|
359
|
+
And the two costs that decide which apps qualify:
|
|
360
|
+
|
|
361
|
+
* **View count.** SwiftUI diffs the whole view tree on a state change and is
|
|
362
|
+
known to fall over in the low thousands of live views. RealTrainer's phone
|
|
363
|
+
frame is 177 commands; the dashboard's tablet frame is 352; a pptx slide
|
|
364
|
+
can be 10 000. The line is roughly *chrome-shaped apps yes, document apps
|
|
365
|
+
no* (§7), and the fallback inside one app is a `Canvas` subview for a
|
|
366
|
+
subtree the host marks as a picture — a chart, a slide.
|
|
367
|
+
* **Text.** `Text` measures itself with CoreText; EVG must measure with the
|
|
368
|
+
same thing or the runs will not fit their boxes. A CoreText-backed
|
|
369
|
+
`EVGTextMeasurer` (`CTLineGetTypographicBounds` on a `CTLine` from the same
|
|
370
|
+
`CTFont` the surface picks) is the piece to build first, and it improves
|
|
371
|
+
the **existing** CoreGraphics painter on the same day — today `ui/ios` lays
|
|
372
|
+
out with the estimate and draws with the platform face, which the README
|
|
373
|
+
calls "the honest choice" only because nothing better was wired.
|
|
374
|
+
|
|
375
|
+
### 4.3 L2 — SwiftUI's own layout
|
|
376
|
+
|
|
377
|
+
For completeness, what maps and what does not:
|
|
378
|
+
|
|
379
|
+
| EVG | SwiftUI | |
|
|
380
|
+
| --- | --- | --- |
|
|
381
|
+
| `flex-direction: row` / `column` | `HStack` / `VStack` | ✓ |
|
|
382
|
+
| `gap` | `spacing:` | ✓ |
|
|
383
|
+
| `justify-content: space-between` / `-around` / `-evenly` | `Spacer()`s between children | ✓, by construction |
|
|
384
|
+
| `justify-content: center` / `flex-end` | leading/trailing `Spacer()` | ✓ |
|
|
385
|
+
| `align-items` | `alignment:` on the stack | ✓, no `stretch` on the cross axis without `.frame(maxWidth: .infinity)` |
|
|
386
|
+
| `flex-grow` | `.frame(maxWidth: .infinity)` + `layoutPriority` | approximate — SwiftUI divides remaining space equally among flexible children, not by weight |
|
|
387
|
+
| `flex-shrink`, `flex-basis` | none | ✗ |
|
|
388
|
+
| `flex-wrap: wrap` | none in `HStack`; a custom `Layout` | ✗ natively |
|
|
389
|
+
| `min/max-width/height` | `.frame(minWidth:…)` | ✓ |
|
|
390
|
+
| `display: grid` | `Grid` (iOS 16) with `gridCellColumns`; `LazyVGrid` with `GridItem(.flexible)` | fixed columns and spans ✓; `fr` weights, `minmax`, named areas, `subgrid` ✗ |
|
|
391
|
+
| `position: absolute` | `.offset` / `.position` in a `ZStack` | ✓ |
|
|
392
|
+
| overlays | `.overlay` / `.popover` | ✓ for the anchor, placement rules differ |
|
|
393
|
+
|
|
394
|
+
A SwiftUI `Layout` that fills the gaps is EVG's flex algorithm, in Swift,
|
|
395
|
+
which the compiler already emits. So the recommendation for Apple is the
|
|
396
|
+
`Layout`-protocol host of §4.2 and not a stack emitter: the same numbers, one
|
|
397
|
+
implementation, and print parity kept.
|
|
398
|
+
|
|
399
|
+
### 4.4 Threads on Apple
|
|
400
|
+
|
|
401
|
+
The engine is generated Swift with no UIKit in it. Layout, the display list
|
|
402
|
+
and the host diff run on a background queue or an actor; CoreText measurement
|
|
403
|
+
is thread-safe; what crosses to the main actor is the `EVGDisplayList` (as
|
|
404
|
+
today) or the host-tree diff. `CADisplayLink` drives painting only. The
|
|
405
|
+
scroll layer as a `CGLayer` from `apple/README.md` is unchanged by any of
|
|
406
|
+
this and is the cheaper first step on the CoreGraphics painter.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 5. Android
|
|
411
|
+
|
|
412
|
+
### 5.1 What exists
|
|
413
|
+
|
|
414
|
+
`android/EvgPainter.kt` over `android.graphics.Canvas`, a Java2D twin for
|
|
415
|
+
checks without a device, a `View` host in `ui/android` and
|
|
416
|
+
`realtrainer/android`, an `InputConnection` for the soft keyboard, and the
|
|
417
|
+
ripple as an AGSL `RuntimeShader` post-pass. Fonts: the platform's sans in
|
|
418
|
+
four styles; layout with the estimate, as on iOS.
|
|
419
|
+
|
|
420
|
+
### 5.2 L1 on Compose
|
|
421
|
+
|
|
422
|
+
Of the three platforms this is the cleanest fit, because Compose's layout
|
|
423
|
+
protocol is single-pass measure-and-place, which is what EVG produces:
|
|
424
|
+
|
|
425
|
+
```kotlin
|
|
426
|
+
Layout(content = { children of the EVG node as composables }) { measurables, constraints ->
|
|
427
|
+
// EVG has already laid the tree out; the placeable is told its size and put at its rectangle
|
|
428
|
+
layout(w, h) { placeables.forEachIndexed { i, p -> p.place(x[i], y[i]) } }
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
* **Identity:** `key(el.key)` inside the children lambda, so a reordered
|
|
433
|
+
keyed list moves composables rather than recreating them; unkeyed children
|
|
434
|
+
get the inspect path.
|
|
435
|
+
* **Text:** `BasicText` for each EVG line with `softWrap = false`, and an
|
|
436
|
+
`EVGTextMeasurer` backed by Compose's `TextMeasurer` (`rememberTextMeasurer`
|
|
437
|
+
/ `TextMeasurer(...)`, foundation 1.5+), which is usable outside
|
|
438
|
+
composition — one instance owned by the engine thread, since nothing
|
|
439
|
+
documents it as shareable across threads — the D3 measurer for this
|
|
440
|
+
platform.
|
|
441
|
+
* **Fields:** `BasicTextField` where the tree has a field; the P3 bridge
|
|
442
|
+
that `PLAN_INPUTS.md` left blocked gets a host that has a field, and the
|
|
443
|
+
`InputConnection` written for RealTrainer becomes the field's own.
|
|
444
|
+
* **Semantics:** `Modifier.semantics { role, contentDescription, stateDescription, … }`
|
|
445
|
+
from the `a11y*` fields — TalkBack on the tree itself.
|
|
446
|
+
* **Scroll layers:** `Modifier.graphicsLayer { translationY = -scrollTop }`
|
|
447
|
+
on the layer's content composable, a `RenderNode` property write with no
|
|
448
|
+
recomposition; or, where the app is happy to hand scrolling over,
|
|
449
|
+
`verticalScroll` with the offset fed back to `EVGElement.scrollTop`.
|
|
450
|
+
* **Ripple:** `Modifier.clickable` with the platform's indication replaces
|
|
451
|
+
the AGSL pass; `backdrop-filter` stays a `RenderEffect`.
|
|
452
|
+
* **Paths:** `Canvas {}` with a `Path` from the rings, even-odd fill —
|
|
453
|
+
Compose's `Canvas` is a composable, so a chart subtree is one node.
|
|
454
|
+
|
|
455
|
+
No view-count ceiling of SwiftUI's kind: Compose skips unchanged composables
|
|
456
|
+
by parameter equality, and `LazyColumn` exists for the document case, though
|
|
457
|
+
EVG's own culling already answers that (177 commands for a 40× diary).
|
|
458
|
+
|
|
459
|
+
### 5.3 L2 — Compose's own layout
|
|
460
|
+
|
|
461
|
+
| EVG | Compose | |
|
|
462
|
+
| --- | --- | --- |
|
|
463
|
+
| `flex-direction` | `Row` / `Column` | ✓ |
|
|
464
|
+
| `gap` | `Arrangement.spacedBy` | ✓ |
|
|
465
|
+
| `justify-content` | `Arrangement.SpaceBetween` / `SpaceAround` / `SpaceEvenly` / `Center` / `End` | ✓ |
|
|
466
|
+
| `align-items` | `verticalAlignment` / `horizontalAlignment`; `Modifier.fillMaxHeight` for `stretch` | ✓ |
|
|
467
|
+
| `flex-grow` | `Modifier.weight(n)` | ✓, weighted — closer than SwiftUI |
|
|
468
|
+
| `flex-shrink`, `flex-basis` | none | ✗ |
|
|
469
|
+
| `flex-wrap: wrap` | `FlowRow` / `FlowColumn` (foundation-layout 1.4+) | ✓ for `wrap`, `align-content` partial |
|
|
470
|
+
| `min/max-*` | `Modifier.sizeIn` | ✓ |
|
|
471
|
+
| `display: grid` | none — `LazyVerticalGrid` is a virtualised list, not CSS grid | ✗ (`fr`, `minmax`, areas, `subgrid`) |
|
|
472
|
+
| `position: absolute` | `Box` with `Modifier.offset` / `Modifier.absoluteOffset` | ✓ |
|
|
473
|
+
| overlays | `Popup` / `DropdownMenu` anchoring | ✓ for the anchor, placement rules differ |
|
|
474
|
+
|
|
475
|
+
Same conclusion as SwiftUI, with a wider native subset: the gaps (`shrink`,
|
|
476
|
+
`basis`, grid) are exactly the places a `Layout` with EVG's numbers would go,
|
|
477
|
+
so the L1 host is the recommendation and a `Row`/`Column` emitter is not.
|
|
478
|
+
The old `View` system with Google's `FlexboxLayout` is closer to CSS than
|
|
479
|
+
Compose is, and is not where Android is going; not recommended.
|
|
480
|
+
|
|
481
|
+
### 5.4 Threads on Android
|
|
482
|
+
|
|
483
|
+
The engine is generated Kotlin with no `android.*` in it (the stubs in
|
|
484
|
+
`androidstubs/` exist so the host type-checks without an SDK). Layout, list
|
|
485
|
+
and diff run on `Dispatchers.Default`; the `TextMeasurer` measures there;
|
|
486
|
+
the result is posted to the main thread as a `State` the `Layout` reads.
|
|
487
|
+
Choreographer drives painting only. On the existing `View` host the same
|
|
488
|
+
split applies with the `EVGDisplayList` as the message, which is a smaller
|
|
489
|
+
change than Compose and worth doing first.
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## 6. The four decisions, shared by all three
|
|
494
|
+
|
|
495
|
+
**D1 — who owns geometry.** EVG (L1), by default. The platform (L2) only as a
|
|
496
|
+
mode with an oracle, and only on the web today.
|
|
497
|
+
|
|
498
|
+
**D2 — identity across frames, and a diff.** The seam is a list of commands
|
|
499
|
+
that "carries no id" on purpose. An L1 host needs, per element, a handle that
|
|
500
|
+
survives a rebuild and a statement of what changed. Proposal: a fifth derived
|
|
501
|
+
list beside the display list, the hit test, the a11y tree and the inspector —
|
|
502
|
+
`EVGHostTree` — that walks the laid-out tree on the same pass and emits
|
|
503
|
+
**ops** rather than a picture:
|
|
504
|
+
|
|
505
|
+
```
|
|
506
|
+
CREATE path parentPath index elementType (paint, geometry, text lines, a11y)
|
|
507
|
+
UPDATE path bits: GEOMETRY | PAINT | TEXT | A11Y | SCROLL
|
|
508
|
+
MOVE path parentPath index
|
|
509
|
+
REMOVE path
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
* The key is `EVGInspect`'s path — `0/3/k:share` — so a keyed child keeps its
|
|
513
|
+
node across a reorder, and an unkeyed one is what it is today: structural.
|
|
514
|
+
`EVGReconcile` decides what "the same element" means; this reads the answer.
|
|
515
|
+
The path is an address, not an identity: an element of another type, tag,
|
|
516
|
+
id or clipping at a described address (a scene change that puts a scroller
|
|
517
|
+
where a card was) is a `REMOVE` and a `CREATE`, its subtree made with it,
|
|
518
|
+
so a host never has to turn one kind of node into another in place.
|
|
519
|
+
* The `UPDATE` bits come from what the engine already tracks: the style
|
|
520
|
+
cache's `layoutClean()` / `nothingChanged()`, `paintStamp` on the element,
|
|
521
|
+
the layer's `layerShiftX/Y`, and a text-lines stamp from `EVGTextEngine`.
|
|
522
|
+
* It is pure Ranger, like the display list, and so it compiles to ES6 for
|
|
523
|
+
the DOM host, Swift for SwiftUI and Kotlin for Compose. Written once.
|
|
524
|
+
* It has the same binary form as the scene (`int_buffer` records, a string
|
|
525
|
+
pool), so it crosses a thread the way the scene does.
|
|
526
|
+
|
|
527
|
+
Nothing below the seam changes: the painters keep reading the display list.
|
|
528
|
+
A host chooses one or the other, and a host can mix — a SwiftUI page with a
|
|
529
|
+
`Canvas` subtree for the chart reads both.
|
|
530
|
+
|
|
531
|
+
**D3 — text: the platform measures, EVG breaks.** An `EVGTextMeasurer` per
|
|
532
|
+
platform, backed by what the host draws with: `measureText` on a canvas,
|
|
533
|
+
CoreText, Compose's `TextMeasurer`. EVG keeps breaking the lines and emitting
|
|
534
|
+
one run per line, so a screen and a PDF that share a face still break in the
|
|
535
|
+
same place — the invariant `PLAN_CSS_LAYOUT_AND_FONTS.md` §4 is built on. A
|
|
536
|
+
host that wants the platform to wrap (Dynamic Type, hyphenation) is asking
|
|
537
|
+
for L2 on that subtree, and says so.
|
|
538
|
+
|
|
539
|
+
**D4 — the engine off the UI thread.** Worker + transferables on the web, a
|
|
540
|
+
background queue on Apple, `Dispatchers.Default` on Android. The message is
|
|
541
|
+
the display list (binary) or the host-tree diff (binary). Input goes the
|
|
542
|
+
other way; the hit test is 0.03 ms, so the hop is the latency. Two things
|
|
543
|
+
have to move to the engine thread with the engine: the text measurer (all
|
|
544
|
+
three are usable there) and the clock (`app.tick(dt)`), which the hosts
|
|
545
|
+
already hand in rather than read.
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## 7. Which apps
|
|
550
|
+
|
|
551
|
+
The examples in the question — `npm run ui:demo:web`, `npm run rt:web`, and
|
|
552
|
+
their `ui:ios` / `ui:android` / `rt:ios` / `rt:android` ports — are all
|
|
553
|
+
chrome-shaped: a few hundred elements, a stylesheet with `@media`, controls
|
|
554
|
+
with state, one or two scroll containers. That is the L1 shape.
|
|
555
|
+
|
|
556
|
+
| App | Frame | Shape | L1 host | L2 |
|
|
557
|
+
| --- | --- | --- | --- | --- |
|
|
558
|
+
| `gallery/realtrainer` (`rt:*`) | 177 cmds, phone | responsive page, fields, dialogs, scroll | **yes** — the reference case on all three platforms; it already has the input bridge on each | web mode, maybe |
|
|
559
|
+
| `gallery/ui/demo` (`ui:demo:*`) | 352 cmds, tablet | dashboard, menus, tables, a chart | **yes**; the chart subtree stays a picture | web mode, maybe |
|
|
560
|
+
| `gallery/pptx`, `odp` | up to 10 000 cmds | a document, one slide at a time, a deck laid out against its own faces | no — painter; the SVG one for print | no |
|
|
561
|
+
| `gallery/datagrid`, `rangerdb viewer` | virtualised, immediate-mode | no element tree (`PLAN_ACCESSIBILITY.md` §4) | no — painter | no |
|
|
562
|
+
| `gallery/book`, `docx_viewer`, `pdf_writer` | pages | print-first | no — painter, PDF | no |
|
|
563
|
+
|
|
564
|
+
The split is the one `PLAN_ACCESSIBILITY.md` §4 already draws between
|
|
565
|
+
declarative and immediate-mode EVG: a host tree needs a tree.
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## 7.5 Watches
|
|
570
|
+
|
|
571
|
+
Apple Watch and Wear OS are not small phones, and the levels above land on
|
|
572
|
+
them differently. `gallery/watch_evg/WATCH_PERFORMANCE.md` has the numbers
|
|
573
|
+
this section leans on: on the slowest Wear parts a full declarative rebuild
|
|
574
|
+
of a watch face is 9–14 ms of a 16.7 ms frame, a retained frame is 1.2 ms,
|
|
575
|
+
and on watchOS (AOT) the same face rebuilds in about 2.6 ms.
|
|
576
|
+
|
|
577
|
+
**What the platforms allow.**
|
|
578
|
+
|
|
579
|
+
| | Apple Watch | Wear OS |
|
|
580
|
+
| --- | --- | --- |
|
|
581
|
+
| UI toolkit | **SwiftUI only** — no `UIView`, no `CALayer` of your own. The painter host is SwiftUI `Canvas` (`ui/ios/watch`), and the L1 host can only be the SwiftUI `Layout` protocol (watchOS 9+); the UIKit route of §4.2 does not exist here | Views or Compose for Wear OS; Compose is where the platform is going, and the L1 host of §5.2 is the same `Layout` composable, with `ScalingLazyColumn` and `TimeText` around it |
|
|
582
|
+
| Input | the digital crown (a rotation, not a distance — `scrollByCrown` in the facade), a tap, a drag; no hover, no keyboard | the rotary bezel as `MotionEvent.AXIS_SCROLL` through `onGenericMotionEvent`, a tap, a swipe; the soft keyboard exists but is rarely the answer |
|
|
583
|
+
| Screen | 40–49mm, round-cornered rectangle, 198–224 pt across | 454² typical, usually round: content is inset to the circle, and a page laid out for a rectangle loses its corners |
|
|
584
|
+
| Always-on | the app's scene is dimmed and updated about once a minute; a `TimelineView` drives it | ambient mode through `AmbientLifecycleObserver`: one update a minute, no animation, a greyscale-friendly picture |
|
|
585
|
+
| Cold start | AOT: the first frame is single-digit ms | a JIT: the first frame is tens of ms on the bench and hundreds on a watch core; `dex2oat` helps, class loading does not go away |
|
|
586
|
+
|
|
587
|
+
**What this changes in the stages.**
|
|
588
|
+
|
|
589
|
+
* **S0 holds as it is.** `CoreTextMeasurer` is installed in the watch model
|
|
590
|
+
above the app, and `AndroidTextMeasurer` is not phone-specific. The watch
|
|
591
|
+
bench measures with `AwtTextMeasurer`, so its `layout` column now includes
|
|
592
|
+
a real face.
|
|
593
|
+
* **S1 matters more, not less.** A 9 ms rebuild on the main thread is a
|
|
594
|
+
crown that stutters; on the queue it is a frame that arrives a moment
|
|
595
|
+
later while the crown keeps its feel. `WatchPageModel` is on
|
|
596
|
+
`EvgEngineQueue` now; a Wear host would be `RealTrainerView.kt` with the
|
|
597
|
+
rotary event posted where the drag is. Two watch rules for the queue:
|
|
598
|
+
in ambient mode nothing drives the clock, so the engine thread must be
|
|
599
|
+
idle between the once-a-minute ticks (the coalescing already makes an
|
|
600
|
+
idle engine cost nothing); and a watch app is killed often, so the
|
|
601
|
+
queue's first build should be the cheap screen (`WATCH_PERFORMANCE.md`,
|
|
602
|
+
condition 3).
|
|
603
|
+
* **S4 (Compose) is the Wear path, and S5 (SwiftUI `Layout`) is the only
|
|
604
|
+
Apple Watch path** — there is no UIKit fallback there, which makes the
|
|
605
|
+
watch the place SwiftUI's view-count ceiling is least of a problem: a
|
|
606
|
+
watch screen is 20–80 elements, not 5 000.
|
|
607
|
+
* **Accessibility is the one place the watch is easier.** VoiceOver and
|
|
608
|
+
TalkBack both run on the wrist, and an L1 host's nodes are the tree, as
|
|
609
|
+
on the phone. At L0 the mirror of §3 has no equivalent on watchOS, so
|
|
610
|
+
today's `Canvas` host is a picture to VoiceOver — the same wall as a
|
|
611
|
+
`<canvas>`, without the DOM to mirror into. That is an argument for L1 on
|
|
612
|
+
the watch before the phone, not after.
|
|
613
|
+
* **The screen is round.** EVG has no notion of a non-rectangular viewport;
|
|
614
|
+
a Wear host insets the page to the inscribed square or lays it out with
|
|
615
|
+
`padding` from the sheet's `@media`, and a curved list (`ScalingLazyColumn`)
|
|
616
|
+
is a platform effect an L1 host gets and a painter has to fake.
|
|
617
|
+
|
|
618
|
+
**Not built here.** No Wear OS host exists in the repository; the pieces a
|
|
619
|
+
port would use — the measurer, the engine thread, the painter, the surface —
|
|
620
|
+
are the phone's, and the bench is the evidence they fit. Nothing on the
|
|
621
|
+
watch side is compiled by CI, as `apple/README.md` says of the whole
|
|
622
|
+
directory.
|
|
623
|
+
|
|
624
|
+
## 8. Stages, each with its gate
|
|
625
|
+
|
|
626
|
+
Ordered so that every stage improves something that ships today, and the
|
|
627
|
+
expensive decision (D2) is made after the cheap ones have paid.
|
|
628
|
+
|
|
629
|
+
| | What | Where | Gate |
|
|
630
|
+
| --- | --- | --- | --- |
|
|
631
|
+
| **S0** ✅ | Platform text measurers behind one Ranger class, `EVGHostTextMeasurer`, that takes ONE host function and keeps the face cache, the weight convention and the cache key; `EVGDefaultMeasurer` makes it every layout's default. Hosts: canvas `measureText` (`gl/evg-measure.js`), CoreText (`apple/Sources/CoreTextMeasurer.swift`), Skia `Paint` and Java2D (`android/src/…/AndroidTextMeasurer.kt`, `AwtTextMeasurer.kt`) | `EVGContextMeasurer.rgr` was the pattern | `evg:hostmeasurer:test` (42 checks); the responsive smoke and the RealTrainer checks in Chromium; the native hosts type-check only where a Mac or `kotlinc` is |
|
|
632
|
+
| **S1** ✅ | Engine off the UI thread with the **existing** painters. Web: `gl/evg-engine.js` (Worker; frames as transferred `EVGSceneBinary`, now 36 fields with corners, layer and shadow; `gl/evg-binary.js` reads them; input batched into the frame request; hooks for the hit test beside the tree) and `realtrainer/web/main-worker.js` on it. Apple: `apple/Sources/EvgEngineQueue.swift`. Android/JVM: `android/src/main/…/EvgEngineThread.kt`. Both RealTrainer native views rewritten on the queues: `draw` paints the last delivered frame and reads the app for nothing | the engine untouched but for the wider record | `evg:binary:check` (27); `rt:frame` runs both browser hosts through one check and prints pointer-down-to-frame — 1.2 ms on the main thread, 11.2 ms through the Worker, in a headless Chromium without a GPU; the native views type-check only where a Mac or `kotlinc` is |
|
|
633
|
+
| **S2** ✅ | `EVGHostTree.rgr` — CREATE/UPDATE/MOVE/REMOVE by the inspector's path, with GEOMETRY/PAINT/TEXT/A11Y/SCROLL bits; geometry parent-relative at scroll 0; the engine's lines; JSON. No binary form yet — the DOM host reads JSON on the same thread | beside `EVGDisplayList.rgr` | `evg:hosttree:test` (61): every CREATE rectangle is one the list drew; an unchanged tree emits nothing; a colour is PAINT alone; a keyed reorder moves and remakes nothing; a scroll is one bit on the container |
|
|
634
|
+
| **S3** ◐ | `html/evg-dom.js` — one node per element, patched from the ops; text as a `<div>` per line with `white-space: pre`; borders as inset shadows so children are not shifted; scroll layers as one transform; role and label on the node; transforms with the element's origin. On the responsive page and on RealTrainer as `?painter=dom`, under the canvas that takes the pointer | web | `evg:dom:check` (14): every node is where the engine put it, within half a pixel, before and after a resize, and the resize updated the nodes rather than remaking them. `rt:dom` (14): the same of RealTrainer's loader, sign-in, dashboard and document; a scene change is creates and removes; a press through the canvas opens the dashboard; a scroll is one transform on the layer and remakes nothing. **Still open:** a pixel parity against `evg-html.js`, fields as real `<input>`s, and the mirror switched off in favour of the nodes |
|
|
635
|
+
| **S4** | The Compose host for `realtrainer/android`: `Layout` + `EVGHostTree`, `BasicTextField`, semantics | Android | `rt:android:verify` with a recording host that counts composables the way `RecordingSurface` counts draws |
|
|
636
|
+
| **S5** | The SwiftUI host for `realtrainer/ios` through the `Layout` protocol | Apple | `rt:ios:verify`; a view-count ceiling asserted per scene |
|
|
637
|
+
| **S6** (optional) | L2 web mode: the cascade resolved by EVG, layout by the browser | web | the Chromium box-model and grid oracles, extended with the demo pages; the mode is named in the URL, never the default |
|
|
638
|
+
|
|
639
|
+
S0 and S1 are independent of each other and of everything after; both are
|
|
640
|
+
worth doing whether or not the rest happens. S2 is where the design in D2 is
|
|
641
|
+
committed and is the stage to review before writing it.
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## 9. The walls, named
|
|
646
|
+
|
|
647
|
+
* **Text metrics are the whole game.** Every place a native host and EVG
|
|
648
|
+
disagree about a pixel is a place they measured a run differently. S0 is
|
|
649
|
+
first for that reason, and any host that wraps text itself is L2.
|
|
650
|
+
* **SwiftUI's view count.** A few thousand live views is where it slows; a
|
|
651
|
+
document is out, and even a chrome app needs the `Canvas`-subtree escape
|
|
652
|
+
hatch for a chart with 5 000 segments.
|
|
653
|
+
* **Unkeyed lists.** A structural path is only stable for keyed children.
|
|
654
|
+
The demos already key what they reorder; a list that does not will churn
|
|
655
|
+
nodes on every rebuild, which is what the painter does today, so it is
|
|
656
|
+
not a regression — but it is not the win either.
|
|
657
|
+
* **Mixing SVG into DOM.** A `<path>` inside a `<div>` tree is fine; a clip
|
|
658
|
+
stack that crosses between them is not. Paths are leaves, and a path
|
|
659
|
+
never clips, so the mix stays at the leaves.
|
|
660
|
+
* **Two scroll owners.** A native `ScrollView` / `UIScrollView` /
|
|
661
|
+
`verticalScroll` and EVG's own container cannot both own an offset. L1
|
|
662
|
+
keeps EVG's and writes it to a compositor transform; handing scrolling to
|
|
663
|
+
the platform is an app-level choice, per container.
|
|
664
|
+
* **The ripple and the backdrop blur** are GPU passes over finished pixels.
|
|
665
|
+
Compose has `RenderEffect`, the web has `backdrop-filter`, SwiftUI has
|
|
666
|
+
`Material`; the ripple itself becomes the platform's own indication, and
|
|
667
|
+
is not the same picture. `parity.mjs` will say so, and it should be
|
|
668
|
+
allowed to.
|
|
669
|
+
* **Print parity narrows to "same lines, native glyphs".** That is the
|
|
670
|
+
contract L1 keeps and the one L2 does not, and it is worth writing into
|
|
671
|
+
`README.md`'s first paragraph the day an L1 host lands.
|