ranger-compiler 3.5.1 → 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 +1064 -20
- package/LICENSE +3 -1
- package/README.md +123 -28
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79923 -56921
- 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,322 @@
|
|
|
1
|
+
# The view on the GPU — a design
|
|
2
|
+
|
|
3
|
+
A canvas that pans and zooms rebuilds its whole display list for every frame,
|
|
4
|
+
because the pan and the zoom are baked into the coordinates that list carries.
|
|
5
|
+
They do not have to be. The painter already applies a translate in the vertex
|
|
6
|
+
shader — `uShift`, what a scroll layer moves by — and a *view* is that with a
|
|
7
|
+
scale and no layer. Keep the frame, change two uniforms.
|
|
8
|
+
|
|
9
|
+
Status: **S0 through S3 are built** (§11 says what each one is and what gate it
|
|
10
|
+
passed); S4 is not started. Every number in the design was measured on this
|
|
11
|
+
repository as it stood when it was written, on the machine it was written on,
|
|
12
|
+
and the measurement is named beside it.
|
|
13
|
+
|
|
14
|
+
Related: [`gl/README.md`](gl/README.md) (the display list as a GPU input),
|
|
15
|
+
[`README.md`](README.md) (the pipeline and the seam),
|
|
16
|
+
[`PLAN_NATIVE_HOSTS.md`](PLAN_NATIVE_HOSTS.md) (the engine off the UI thread —
|
|
17
|
+
this is orthogonal to it and composes with it),
|
|
18
|
+
[`../../gallery/figma/README.md`](../../gallery/figma/README.md) (the canvas that found this).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 0. The short version
|
|
23
|
+
|
|
24
|
+
One pan frame of a FigJam board of 3,565 nodes — 744 commands, 767,000 points
|
|
25
|
+
— in Chromium, after the flattening and bridge work already on this branch:
|
|
26
|
+
|
|
27
|
+
| per frame | today | with a view |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| build the display list (Ranger) | 37 ms | — |
|
|
30
|
+
| decode it in the page | 9 ms | — |
|
|
31
|
+
| build the GPU frame: atlas, buffers, upload | 18 ms | — |
|
|
32
|
+
| draw it | 18 ms | 18 ms |
|
|
33
|
+
| **a pan** | **87 ms** (11 fps) | **~18 ms** (50 fps) |
|
|
34
|
+
|
|
35
|
+
The last row is not a projection. `prepareDisplayList` already returns a frame
|
|
36
|
+
whose `draw()` can be called again, and drawing the same frame a second time
|
|
37
|
+
costs 18.4 ms and rebuilds nothing — measured. What is missing is a way to
|
|
38
|
+
draw that same frame *somewhere else*: a scale beside the translate the shader
|
|
39
|
+
already has, and a host that keeps the frame while the view moves.
|
|
40
|
+
|
|
41
|
+
**A zoom is not free the way a pan is.** Two things in a built frame have the
|
|
42
|
+
scale baked into them — the glyph atlas is rasterized at a size, and a curve
|
|
43
|
+
was flattened for a size — so a view may stretch a frame only so far before it
|
|
44
|
+
has to be built again (§4). That band is the whole of the design's honesty:
|
|
45
|
+
pans are exact and free, zoom is free within about ±40% and costs one rebuild
|
|
46
|
+
outside it.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 1. What is already there
|
|
51
|
+
|
|
52
|
+
Nothing in this design is a new mechanism. Every piece exists and is in use:
|
|
53
|
+
|
|
54
|
+
| | where | what it does |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| a translate applied by the shader | `gl/evg-webgl.js`, `uShift` | a scroll layer that moved since the frame was built draws moved, without rebuilding |
|
|
57
|
+
| a frame that outlives its draw | `prepareDisplayList` → `frame.draw(shifts)` | RealTrainer's fling: 266 scroll frames, none of them laid out again (`npm run rt:scroll`) |
|
|
58
|
+
| a clip carried into a transformed subtree | `EVGDisplayList.cullThroughTransform` | culling against the region a view will actually show |
|
|
59
|
+
| subdivision chosen from the drawn size | `EVGDisplayList.drawScale` | a curve knows how big it will be after the transform |
|
|
60
|
+
| a flattened outline kept on its element | `EVGElement.ringsCache` | the same path is not re-parsed for the next frame |
|
|
61
|
+
| the frame as typed arrays | `EVGDisplayList.toBinary` | no text between the engine and the painter |
|
|
62
|
+
|
|
63
|
+
The design is one step further along the line those five are already on: stop
|
|
64
|
+
baking the camera into the coordinates at all.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 2. The contract
|
|
69
|
+
|
|
70
|
+
Today a display list is in **device space**: a command's `x` is where it lands
|
|
71
|
+
on the canvas, and `transformFrom` has already multiplied every point of it by
|
|
72
|
+
whatever transforms its element sat under. With a view, a list is in **scene
|
|
73
|
+
space** and carries the camera beside it:
|
|
74
|
+
|
|
75
|
+
```ranger
|
|
76
|
+
list.setView(x y scale) ; the camera, not an element property
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
* The walk does not bake that transform. Nothing else changes: element
|
|
80
|
+
transforms *inside* the scene (a rotated label, a scaled badge) are baked as
|
|
81
|
+
they are today, because they belong to the picture rather than to the
|
|
82
|
+
camera.
|
|
83
|
+
* `toJson` and `toBinary` carry `view` beside `width`/`height`. The binary
|
|
84
|
+
record is unchanged — the view is three numbers on the envelope, not a field
|
|
85
|
+
per command — and `sceneStride` already derives the record width, so an
|
|
86
|
+
older reader meets a list it can still read (it would draw it unpanned,
|
|
87
|
+
which is why `view` is also reported in `stats`).
|
|
88
|
+
* No view means an identity view, which is exactly the list every consumer
|
|
89
|
+
gets today. **This is opt-in from the first line.** A PDF, a PNG, an SVG
|
|
90
|
+
export and every golden test see byte-identical output unless a host asks
|
|
91
|
+
for a camera.
|
|
92
|
+
|
|
93
|
+
The host stops putting the pan/zoom on a world element. The figma viewer's
|
|
94
|
+
`SceneToEVG.buildPage` would no longer write `transform: translate(…) scale(…)`
|
|
95
|
+
on the world div; it would call `list.setView(panX, panY, zoom)` instead. The
|
|
96
|
+
element tree then no longer changes when the view does — which is the whole
|
|
97
|
+
point: a tree that does not change does not need to be walked.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 3. What multiplies, and where
|
|
102
|
+
|
|
103
|
+
In the vertex shader, `uShift` (vec2) becomes `uView` (vec4: `sx, sy, tx, ty`):
|
|
104
|
+
|
|
105
|
+
```glsl
|
|
106
|
+
vec2 p = aRect.xy * uView.xy + uView.zw + aCorner * aRect.zw * uView.xy;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
and with it, in the same shader: the half-size that feeds the distance field,
|
|
110
|
+
the corner radii, the border thickness, and the rotation origin. A gradient is
|
|
111
|
+
computed in the quad's own 0..1 space and is unaffected; an image's UV crop is
|
|
112
|
+
computed from aspect ratios and is unaffected. The path program
|
|
113
|
+
(`PATH_VERT`) takes the same uniform, applied to `aPos`.
|
|
114
|
+
|
|
115
|
+
Outside the shader, three things are in device pixels and must be mapped by
|
|
116
|
+
the host before they are handed to GL:
|
|
117
|
+
|
|
118
|
+
| | today | with a view |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| a clip (scissor rectangle) | device pixels | multiply by the view when the scissor is set |
|
|
121
|
+
| the stroke expansion (a quad per segment, CPU) | device pixels | expand in scene units, let the shader scale — or keep a device-width stroke by dividing the width by the scale, which is what a hairline wants |
|
|
122
|
+
| a blur/shadow radius | device pixels | scale it; a shadow that does not grow with the picture reads as a different shadow |
|
|
123
|
+
|
|
124
|
+
Rotation in the view is possible (`uView` becomes a `mat3`) and nothing in the
|
|
125
|
+
repository needs it. Leave it out; say so where the field is declared.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 4. The two things a scale cannot carry
|
|
130
|
+
|
|
131
|
+
A view stretches a *built* frame, and two things in a built frame were built
|
|
132
|
+
for a size.
|
|
133
|
+
|
|
134
|
+
**Glyphs.** The atlas is a 2-D canvas rasterization of each run at its size.
|
|
135
|
+
Scaled up on the GPU it blurs; scaled down it aliases. `atlasFor` already
|
|
136
|
+
keys its slots by run and size, so the fix is a policy and not a mechanism:
|
|
137
|
+
rebuild the atlas — not the list — when the view's scale leaves the band the
|
|
138
|
+
atlas was built for. That is a text-only rebuild, and on the board above it is
|
|
139
|
+
part of the 18 ms build, not all of it. (The permanent fix is SDF glyphs,
|
|
140
|
+
which is its own plan and its own quality argument. Not this one.)
|
|
141
|
+
|
|
142
|
+
**Curves.** A path was flattened into points for a drawn size (`drawScale`),
|
|
143
|
+
so a frame stretched far enough shows facets — and one shrunk far enough
|
|
144
|
+
carries points nobody can see. Same policy, same band.
|
|
145
|
+
|
|
146
|
+
So the rule, and it is the whole risk of the design:
|
|
147
|
+
|
|
148
|
+
> A frame may be drawn at any **pan**. It may be drawn at a scale between
|
|
149
|
+
> `1/√2` and `√2` of the scale it was built at. Outside that, or outside the
|
|
150
|
+
> region it was built for (§5), it is built again.
|
|
151
|
+
|
|
152
|
+
A pinch crosses a √2 band about every three notches, so a zoom gesture costs a
|
|
153
|
+
rebuild every third frame or so and pans between them cost nothing — against
|
|
154
|
+
every frame costing one today. The tolerance is a number, not a law: a viewer
|
|
155
|
+
that cares more about sharpness than about frames picks 1.15 instead.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 5. Culling a list you mean to keep
|
|
160
|
+
|
|
161
|
+
Culling today asks *what is in the viewport*, and a kept frame is drawn at
|
|
162
|
+
views the viewport did not have. So a kept list is built for a **region**:
|
|
163
|
+
the viewport grown by an overscan, in scene units.
|
|
164
|
+
|
|
165
|
+
The scroll layers already do this vertically — `setOverscan(before, after)`,
|
|
166
|
+
defaulting to one clip height each way — and `refreshLayers` already decides
|
|
167
|
+
when a shift has left the range the overscan covered. A view needs the same
|
|
168
|
+
decision in two dimensions:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
rebuild when the view's scale left the band (§4)
|
|
172
|
+
or the viewport is no longer inside the built region
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Sizing the region is a memory argument, not a correctness one: one viewport of
|
|
176
|
+
overscan in each direction is 9× the commands in the worst case and usually
|
|
177
|
+
far less (a board at fit-zoom is mostly on screen already; a board at 8× has
|
|
178
|
+
almost nothing on it). A cap — "grow the region until it would exceed N
|
|
179
|
+
commands, then stop" — keeps the worst case bounded and degrades to today's
|
|
180
|
+
behaviour, one build per frame, rather than to a stall.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 6. What each painter has to do
|
|
185
|
+
|
|
186
|
+
| painter | work | notes |
|
|
187
|
+
| --- | --- | --- |
|
|
188
|
+
| WebGL (`gl/evg-webgl.js`) | `uShift` → `uView` in two shaders, the scissor multiply, `frame.draw(view, shifts)` | the frame already survives its draw |
|
|
189
|
+
| DOM (`html/evg-html.js`) | one CSS `transform` on the root group | the natural form; arguably already free |
|
|
190
|
+
| Apple (`apple/`) | a CTM before the frame is drawn | `CGContext`/`CALayer` take one |
|
|
191
|
+
| Android (`android/`) | `Canvas.setMatrix` before the frame | same |
|
|
192
|
+
| SDL2 / C++ (RealTrainer native) | the same uniform in the same shader | the shader is shared source |
|
|
193
|
+
| PDF, PNG, SVG exporters | nothing | there is no camera in a printed page: they get an identity view and bake as they do now |
|
|
194
|
+
| the a11y mirror (`gl/evg-a11y.js`) | multiply the rectangles it positions the DOM at | it mirrors *painted* geometry, so it follows the camera |
|
|
195
|
+
| hit testing | nothing, but it becomes honest | every host already maps a click through `(screen − pan) / zoom` by hand; the view is that number in one place |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 7. Who it helps, measured as it is today
|
|
200
|
+
|
|
201
|
+
| | today | what the view removes |
|
|
202
|
+
| --- | --- | --- |
|
|
203
|
+
| **figma viewer** — pan/zoom a board, 3,565 nodes | 87 ms a frame (§0) | the list build, the decode and the GPU build: ~69 of the 87 |
|
|
204
|
+
| **rangerflow** — drag, wheel, pinch a diagram, 2,516 commands | 12.1 ms to build and serialize a frame (`loadSqlFixture`, node) | the same: its `FlowView.build()` walks and culls the whole graph per frame, and a pan does not change the graph |
|
|
205
|
+
| **markdown** — scroll a document, 3,365 commands | 5.1 ms a frame: `offsetBy` over every command, a copy of the list, then JSON | `offsetBy` and the copy disappear — the scroll IS the view |
|
|
206
|
+
| **RealTrainer / ui** — flings and scroll containers | already kept; `refreshLayers` moves the commands on the CPU | a layer shift becomes a view with a translate: the CPU move goes too |
|
|
207
|
+
| **pptx viewer** | a slide is static | little per frame; the win is at fit/zoom and on a zoomed presenter view |
|
|
208
|
+
| **evg inspect, the responsive demo** | rebuild per view change | the same as any canvas |
|
|
209
|
+
|
|
210
|
+
Two of those — rangerflow and markdown — are already fast enough that nobody
|
|
211
|
+
would rewrite them for this alone. They are in the table because the change is
|
|
212
|
+
in the engine and they get it for the price of deleting code: `offsetBy` and
|
|
213
|
+
the per-frame list copy in markdown's `frame()`, the camera arithmetic spread
|
|
214
|
+
through `FlowView`'s painting in rangerflow.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 8. Where it gives nothing
|
|
219
|
+
|
|
220
|
+
* A page that draws once. PDF, PNG, SVG, the pptx export: there is no second
|
|
221
|
+
frame to keep.
|
|
222
|
+
* The first frame of a file. A build is a build; this is about the second one.
|
|
223
|
+
* Content that changes every frame — an animation, a simulation tick, a drag
|
|
224
|
+
that moves a node. The view keeps a frame whose *scene* is unchanged. (A
|
|
225
|
+
drag that moves one node is the fragments mechanism's problem, and §10 says
|
|
226
|
+
why the view is what would finally make fragments work.)
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 9. What could go wrong
|
|
231
|
+
|
|
232
|
+
| risk | what it looks like | what holds it |
|
|
233
|
+
| --- | --- | --- |
|
|
234
|
+
| text softens while zooming | a pinch looks blurry until it settles | the band (§4), and a rebuild on gesture end; the check is a pixel diff of a kept frame against a rebuilt one at the band's edge |
|
|
235
|
+
| a golden test moves | a PDF or a conformance oracle shifts by a rounding | identity view is the default and bakes exactly as today; any consumer that does not call `setView` must be byte-identical, and that is the gate on S0 |
|
|
236
|
+
| two mechanisms disagree | a scroll layer inside a view drifts | one composition, written once: `view ∘ layerShift`, the layer's shift in scene units; `rt:scroll` already holds the layer half to a full rebuild, and the same check gains a view |
|
|
237
|
+
| the region policy thrashes | every second frame is a rebuild after all | the policy is one function in `gl/`, with the region and the band as its inputs, so it is testable without a browser (`evg:gestures:check` is the precedent) |
|
|
238
|
+
| the binary record grows | an older reader misreads a frame | it does not grow: the view rides on the envelope. `sceneStride` derives the width and refuses a narrower record with both numbers in the message |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 10. Alternatives, and why this one
|
|
243
|
+
|
|
244
|
+
* **Move the engine to a Worker** (`gl/evg-engine.js`, PLAN_NATIVE_HOSTS S1).
|
|
245
|
+
Takes the cost off the UI thread; does not remove it. A 87 ms frame stays 87
|
|
246
|
+
ms of somebody's CPU, and the pan still lags by one frame. Composes with
|
|
247
|
+
this design rather than competing: a Worker that need not rebuild has
|
|
248
|
+
nothing to send.
|
|
249
|
+
* **Render to a texture and scale that** (a tile cache). Cheap to draw, and
|
|
250
|
+
every zoom is a resample: text goes soft immediately rather than at the band
|
|
251
|
+
edge, and the memory is the whole board at device resolution.
|
|
252
|
+
* **Keep the per-element commands and re-emit them** (the fragments mechanism,
|
|
253
|
+
already in `EVGDisplayList`). It cannot survive a view change today,
|
|
254
|
+
because what it keeps is in device space — the commands it re-emits would be
|
|
255
|
+
transformed a second time. In scene space that objection disappears, which
|
|
256
|
+
makes the view the thing that unlocks fragments for canvases, and fragments
|
|
257
|
+
the answer to "one node moved, nothing else did".
|
|
258
|
+
* **Do nothing and flatten less.** That was the last three commits, and it
|
|
259
|
+
bought 20×. It runs out here: what is left per frame is the frame itself.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 11. Stages
|
|
264
|
+
|
|
265
|
+
| | what | gate | state |
|
|
266
|
+
| --- | --- | --- | --- |
|
|
267
|
+
| **S0** | `setView` on `EVGDisplayList`, carried by `toJson`/`toBinary`; `uView` in the two WebGL shaders; `frame.draw(shifts, view)` | every existing consumer byte-identical (identity view); a kept frame drawn at a new view matches a rebuilt list pixel for pixel (`evg:binary:check` gains the view; a pixel diff on the figma board) | **done** — `evg:view:check`, 31 checks, worst channel difference 0 |
|
|
268
|
+
| **S1** | the keep/rebuild policy as a host helper in `gl/` — region, band, and "rebuild now" — with the figma viewer on it | a headless check of the policy alone (no browser), and the board's pan frame at ~18 ms with the same pixels | **done** — `gl/evg-view.js` and `evg:view:policy` (30 checks); the figma viewer pans on a kept frame |
|
|
269
|
+
| **S2** | rangerflow on it: `FlowView` builds in world space, the camera leaves its painting code | its smoke test, and the minimap (which draws the *same* scene at another view — a second view, free) | **done** — in three frames rather than one, see below |
|
|
270
|
+
| **S3** | markdown's scroll on it: `frame()` stops copying and offsetting the document list | `markdown:web:test`, and the caret still lands where it is clicked | **done** — and the caret is now a list of its own |
|
|
271
|
+
| **S4** | layer shifts expressed as views; the native painters (Apple, Android, SDL) take a CTM | `rt:scroll` and `rt:frame` on both hosts | not started |
|
|
272
|
+
|
|
273
|
+
**S2 came out as THREE frames, not one.** The design assumed a canvas is a
|
|
274
|
+
scene with a camera on it. RangerFlow is three things at three different rates,
|
|
275
|
+
and the split is the interesting part of the stage:
|
|
276
|
+
|
|
277
|
+
| | what | in | rebuilt when |
|
|
278
|
+
| --- | --- | --- | --- |
|
|
279
|
+
| the pattern | the paper and the dot grid | screen pixels | the zoom or the window changes — a pan SLIDES it, because a pattern moved by one of its own periods is itself |
|
|
280
|
+
| the diagram | lanes, edges, nodes, labels | flow units, with the camera beside them | the graph changes, or the view leaves the band or the region |
|
|
281
|
+
| the chrome | rulers, minimap, ports, grips, buttons | screen pixels | every frame; it is a few dozen commands and all of it moves when the view does |
|
|
282
|
+
|
|
283
|
+
Two things had to be said out loud that the baked walk got for free, and they
|
|
284
|
+
are `FlowView.drawn` and `FlowView.hair`: **whether a thing is worth drawing is
|
|
285
|
+
a question about screen size** (a label at 1.2 pixels is mush whatever the model
|
|
286
|
+
says, so every level-of-detail test asks `drawn(len)`), and **a hairline is a
|
|
287
|
+
pixel, not a flow unit** (`hair(px)` divides by the zoom the frame is built at).
|
|
288
|
+
Everything else — a border, a row height, a font size — was already a flow
|
|
289
|
+
length going through `sl`, and those needed no change at all.
|
|
290
|
+
|
|
291
|
+
The grid is the reason for the third frame. Built into the diagram's list it
|
|
292
|
+
would cover the region rather than the window: nine times the dots, rebuilt on
|
|
293
|
+
every click — 1.2 MB of display list for one PlantUML diagram, measured. As its
|
|
294
|
+
own periodic frame it costs what it always did and a pan does not touch it.
|
|
295
|
+
|
|
296
|
+
Not done in S2: **the minimap is still drawn the long way**, as its own dots
|
|
297
|
+
rather than as the diagram's frame at a second view. The frames are there now,
|
|
298
|
+
so it is a small change, but it is a change to the minimap rather than to the
|
|
299
|
+
camera and nothing depends on it.
|
|
300
|
+
|
|
301
|
+
**Two bugs only a pixel could find**, both caught by the page's own
|
|
302
|
+
camera-against-baked comparison and both fixed here:
|
|
303
|
+
|
|
304
|
+
* **A camera written to two decimals.** `EVGDisplayList.toJson` wrote the view
|
|
305
|
+
through `num`, which rounds to a hundredth — right for a coordinate, wrong
|
|
306
|
+
for a SCALE that multiplies every coordinate in the list. A zoom of 0.4053
|
|
307
|
+
came out as 0.41 and drew the diagram 1.2% too large: eight pixels of slip at
|
|
308
|
+
the bottom of the canvas, and invisible at zoom 1, which is where every check
|
|
309
|
+
had been looking. `EVGDisplayList.fine` writes six decimals, and the view is
|
|
310
|
+
the only thing that uses it. (The binary bridge was never affected: it carries
|
|
311
|
+
doubles.)
|
|
312
|
+
* **A hairline floor measured in the wrong space.** The painter widens a stroke
|
|
313
|
+
to three quarters of a PIXEL so a thin line does not fall between samples —
|
|
314
|
+
but it applied that floor to the list's own units, and a list with a camera is
|
|
315
|
+
in scene units. A 1.5-unit edge on a diagram at 0.17 is a quarter of a pixel:
|
|
316
|
+
the floor never applied, and every line came out a third as dark as the same
|
|
317
|
+
diagram drawn without a camera. `strokeTriangles` now takes the scale the
|
|
318
|
+
frame is built at.
|
|
319
|
+
|
|
320
|
+
S0 is the only stage that touches the engine's contract, and it is additive.
|
|
321
|
+
S1 is where the frames come from. Everything after it is a host choosing to
|
|
322
|
+
stop doing arithmetic it no longer has to do.
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
Import "SVGPathParser.rgr"
|
|
4
|
+
Import "VectorShapes.rgr"
|
|
5
|
+
|
|
6
|
+
; ==============================================================================
|
|
7
|
+
; PathBuilder.rgr — build a path from data, the way the parser reads one
|
|
8
|
+
; ==============================================================================
|
|
9
|
+
; PLAN_VECTOR_IR.md Stage 3.5.
|
|
10
|
+
;
|
|
11
|
+
; SVGPathParser turns `d` into commands. This is the other direction, and it is
|
|
12
|
+
; the direction a chart, a diagram or any generated drawing actually needs: the
|
|
13
|
+
; shape comes from data, not from a string somebody typed.
|
|
14
|
+
;
|
|
15
|
+
; Without it the only way to draw computed geometry is to concatenate `d` by
|
|
16
|
+
; hand —
|
|
17
|
+
;
|
|
18
|
+
; d = d + "L" + (to_string x) + "," + (to_string y) + " "
|
|
19
|
+
;
|
|
20
|
+
; — which puts number formatting, separator rules and command syntax into every
|
|
21
|
+
; caller, and hands the result straight back to the parser to be taken apart
|
|
22
|
+
; again. Building commands directly skips the round trip entirely: a renderer
|
|
23
|
+
; can consume `getCommands()` as-is, and `asPathData()` is there only for the
|
|
24
|
+
; targets that speak `d`, like an inline <svg>.
|
|
25
|
+
;
|
|
26
|
+
; The basic shapes are here too, so a whole scene — axes, gridlines, a series,
|
|
27
|
+
; its markers — can accumulate into ONE path with one fill and one stroke,
|
|
28
|
+
; rather than one element per line.
|
|
29
|
+
; ==============================================================================
|
|
30
|
+
|
|
31
|
+
class PathBuilder {
|
|
32
|
+
def commands:[PathCommand]
|
|
33
|
+
|
|
34
|
+
; The pen, so the relative moves know where they are starting from.
|
|
35
|
+
def curX:double 0.0
|
|
36
|
+
def curY:double 0.0
|
|
37
|
+
; Where the current subpath began, which is where `close` returns to.
|
|
38
|
+
def startX:double 0.0
|
|
39
|
+
def startY:double 0.0
|
|
40
|
+
; Whether anything has been drawn yet. A relative move as the very first
|
|
41
|
+
; command has no pen position to be relative TO, so it is treated as
|
|
42
|
+
; absolute rather than silently measured from (0, 0).
|
|
43
|
+
def started:boolean false
|
|
44
|
+
|
|
45
|
+
Constructor () {
|
|
46
|
+
def c:[PathCommand]
|
|
47
|
+
commands = c
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn reset:void () {
|
|
51
|
+
def c:[PathCommand]
|
|
52
|
+
commands = c
|
|
53
|
+
curX = 0.0
|
|
54
|
+
curY = 0.0
|
|
55
|
+
startX = 0.0
|
|
56
|
+
startY = 0.0
|
|
57
|
+
started = false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn isEmpty:boolean () {
|
|
61
|
+
return ((array_length commands) == 0)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn commandCount:int () {
|
|
65
|
+
return (array_length commands)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn getCommands:[PathCommand] () {
|
|
69
|
+
return commands
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
; --------------------------------------------------------------------------
|
|
73
|
+
; Absolute moves
|
|
74
|
+
; --------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
fn moveTo:void (x:double y:double) {
|
|
77
|
+
push commands (VectorShapes.moveTo(x y))
|
|
78
|
+
curX = x
|
|
79
|
+
curY = y
|
|
80
|
+
startX = x
|
|
81
|
+
startY = y
|
|
82
|
+
started = true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn lineTo:void (x:double y:double) {
|
|
86
|
+
; A lineto with no preceding moveto has no start point. Promoting it to
|
|
87
|
+
; a moveto is what the path grammar would require anyway, and it keeps
|
|
88
|
+
; a caller that loops over data from producing an unusable path when
|
|
89
|
+
; the first point arrives through the same code as the rest.
|
|
90
|
+
if (started == false) {
|
|
91
|
+
this.moveTo(x y)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
push commands (VectorShapes.lineTo(x y))
|
|
95
|
+
curX = x
|
|
96
|
+
curY = y
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn cubicTo:void (x1:double y1:double x2:double y2:double x:double y:double) {
|
|
100
|
+
if (started == false) {
|
|
101
|
+
this.moveTo(x1 y1)
|
|
102
|
+
}
|
|
103
|
+
push commands (VectorShapes.cubicTo(x1 y1 x2 y2 x y))
|
|
104
|
+
curX = x
|
|
105
|
+
curY = y
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn quadTo:void (x1:double y1:double x:double y:double) {
|
|
109
|
+
if (started == false) {
|
|
110
|
+
this.moveTo(x1 y1)
|
|
111
|
+
}
|
|
112
|
+
def c (new PathCommand())
|
|
113
|
+
c.type = "Q"
|
|
114
|
+
c.x1 = x1
|
|
115
|
+
c.y1 = y1
|
|
116
|
+
c.x = x
|
|
117
|
+
c.y = y
|
|
118
|
+
push commands c
|
|
119
|
+
curX = x
|
|
120
|
+
curY = y
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fn close:void () {
|
|
124
|
+
if (started == false) {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
push commands (VectorShapes.closePath())
|
|
128
|
+
curX = startX
|
|
129
|
+
curY = startY
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
; --------------------------------------------------------------------------
|
|
133
|
+
; Relative moves
|
|
134
|
+
; --------------------------------------------------------------------------
|
|
135
|
+
; Convenient for stepping along a series, and for the small offsets that
|
|
136
|
+
; make up a tick or a marker.
|
|
137
|
+
|
|
138
|
+
fn moveBy:void (dx:double dy:double) {
|
|
139
|
+
if (started == false) {
|
|
140
|
+
this.moveTo(dx dy)
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
this.moveTo((curX + dx) (curY + dy))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fn lineBy:void (dx:double dy:double) {
|
|
147
|
+
if (started == false) {
|
|
148
|
+
this.moveTo(dx dy)
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
this.lineTo((curX + dx) (curY + dy))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
; --------------------------------------------------------------------------
|
|
155
|
+
; Whole shapes
|
|
156
|
+
; --------------------------------------------------------------------------
|
|
157
|
+
; Each of these is its own subpath, so they accumulate into one path that
|
|
158
|
+
; is filled and stroked once. That is the difference between a chart with
|
|
159
|
+
; forty gridlines as forty elements and one with a single gridline path.
|
|
160
|
+
|
|
161
|
+
; Turn everything built so far about a point. A symbol that carries an
|
|
162
|
+
; angle — a wind vector, an arrow along a route — is the same shape drawn
|
|
163
|
+
; the same way and then rotated, and doing it to the GEOMETRY rather than
|
|
164
|
+
; in a renderer's transform means every target draws it turned: SVG, PDF,
|
|
165
|
+
; the raster and the GPU all read the commands and none of them read a
|
|
166
|
+
; transform attribute.
|
|
167
|
+
fn rotateAbout:void (cx:double cy:double degrees:double) {
|
|
168
|
+
if (degrees == 0.0) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
def rad:double (degrees * 0.017453292519943295)
|
|
172
|
+
def c:double (cos rad)
|
|
173
|
+
def s:double (sin rad)
|
|
174
|
+
def n:int (array_length commands)
|
|
175
|
+
def k:int 0
|
|
176
|
+
while (k < n) {
|
|
177
|
+
def one:PathCommand (itemAt commands k)
|
|
178
|
+
k = (k + 1)
|
|
179
|
+
def px:double (one.x - cx)
|
|
180
|
+
def py:double (one.y - cy)
|
|
181
|
+
one.x = (cx + ((px * c) - (py * s)))
|
|
182
|
+
one.y = (cy + ((px * s) + (py * c)))
|
|
183
|
+
def p1x:double (one.x1 - cx)
|
|
184
|
+
def p1y:double (one.y1 - cy)
|
|
185
|
+
one.x1 = (cx + ((p1x * c) - (p1y * s)))
|
|
186
|
+
one.y1 = (cy + ((p1x * s) + (p1y * c)))
|
|
187
|
+
def p2x:double (one.x2 - cx)
|
|
188
|
+
def p2y:double (one.y2 - cy)
|
|
189
|
+
one.x2 = (cx + ((p2x * c) - (p2y * s)))
|
|
190
|
+
one.y2 = (cy + ((p2x * s) + (p2y * c)))
|
|
191
|
+
; An elliptical arc keeps its radii and turns its own axis with
|
|
192
|
+
; the rest of the shape.
|
|
193
|
+
if (one.type == "A") {
|
|
194
|
+
one.rotation = (one.rotation + degrees)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fn addCommands:void (cmds:[PathCommand]) {
|
|
200
|
+
def n:int (array_length cmds)
|
|
201
|
+
def k:int 0
|
|
202
|
+
while (k < n) {
|
|
203
|
+
def c:PathCommand (itemAt cmds k)
|
|
204
|
+
push commands c
|
|
205
|
+
; Keep the pen in step with what was appended, so a relative move
|
|
206
|
+
; after an appended shape starts from the right place.
|
|
207
|
+
if ((c.type == "M")) {
|
|
208
|
+
startX = c.x
|
|
209
|
+
startY = c.y
|
|
210
|
+
started = true
|
|
211
|
+
}
|
|
212
|
+
if ((c.type == "Z") == false) {
|
|
213
|
+
curX = c.x
|
|
214
|
+
curY = c.y
|
|
215
|
+
} {
|
|
216
|
+
curX = startX
|
|
217
|
+
curY = startY
|
|
218
|
+
}
|
|
219
|
+
k = (k + 1)
|
|
220
|
+
}
|
|
221
|
+
if (n > 0) {
|
|
222
|
+
started = true
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fn addRect:void (x:double y:double w:double h:double) {
|
|
227
|
+
def negOne:double (0.0 - 1.0)
|
|
228
|
+
def cmds:[PathCommand] (VectorShapes.rect(x y w h negOne negOne))
|
|
229
|
+
this.addCommands(cmds)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn addRoundedRect:void (x:double y:double w:double h:double rx:double ry:double) {
|
|
233
|
+
def cmds:[PathCommand] (VectorShapes.rect(x y w h rx ry))
|
|
234
|
+
this.addCommands(cmds)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
fn addCircle:void (cx:double cy:double r:double) {
|
|
238
|
+
def cmds:[PathCommand] (VectorShapes.circle(cx cy r))
|
|
239
|
+
this.addCommands(cmds)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn addEllipse:void (cx:double cy:double rx:double ry:double) {
|
|
243
|
+
def cmds:[PathCommand] (VectorShapes.ellipse(cx cy rx ry))
|
|
244
|
+
this.addCommands(cmds)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
fn addLine:void (x1:double y1:double x2:double y2:double) {
|
|
248
|
+
def cmds:[PathCommand] (VectorShapes.line(x1 y1 x2 y2))
|
|
249
|
+
this.addCommands(cmds)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fn addPolyline:void (pts:[double]) {
|
|
253
|
+
def cmds:[PathCommand] (VectorShapes.polyline(pts))
|
|
254
|
+
this.addCommands(cmds)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fn addPolygon:void (pts:[double]) {
|
|
258
|
+
def cmds:[PathCommand] (VectorShapes.polygon(pts))
|
|
259
|
+
this.addCommands(cmds)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
; Append a path written as `d`. The bridge back from the string form, for
|
|
263
|
+
; an icon or a marker glyph that already exists as path data. Malformed
|
|
264
|
+
; data follows the parser's rule: what parses is appended, and the caller
|
|
265
|
+
; can ask the parser itself for the diagnostic.
|
|
266
|
+
fn addPathData:void (d:string) {
|
|
267
|
+
def p (new SVGPathParser())
|
|
268
|
+
p.parse(d)
|
|
269
|
+
def cmds:[PathCommand] (p.getCommands())
|
|
270
|
+
this.addCommands(cmds)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
; --------------------------------------------------------------------------
|
|
274
|
+
; Output
|
|
275
|
+
; --------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
fn asPathData:string () {
|
|
278
|
+
return (VectorShapes.asPathData(commands))
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
; The extent of everything built so far, for callers that need to frame it
|
|
282
|
+
; — a viewBox, a legend box, a hit region.
|
|
283
|
+
fn bounds:PathBounds () {
|
|
284
|
+
def b (new PathBounds())
|
|
285
|
+
def n:int (array_length commands)
|
|
286
|
+
def seen:boolean false
|
|
287
|
+
def k:int 0
|
|
288
|
+
while (k < n) {
|
|
289
|
+
def c:PathCommand (itemAt commands k)
|
|
290
|
+
if ((c.type == "Z") == false) {
|
|
291
|
+
; Control points are included: a curve can bulge past its
|
|
292
|
+
; endpoints, and a box that cropped it would be wrong for
|
|
293
|
+
; exactly the shapes curves are used for.
|
|
294
|
+
def xs:[double]
|
|
295
|
+
def ys:[double]
|
|
296
|
+
push xs c.x
|
|
297
|
+
push ys c.y
|
|
298
|
+
if (c.type == "C") {
|
|
299
|
+
push xs c.x1
|
|
300
|
+
push ys c.y1
|
|
301
|
+
push xs c.x2
|
|
302
|
+
push ys c.y2
|
|
303
|
+
}
|
|
304
|
+
if (c.type == "Q") {
|
|
305
|
+
push xs c.x1
|
|
306
|
+
push ys c.y1
|
|
307
|
+
}
|
|
308
|
+
def j:int 0
|
|
309
|
+
while (j < (array_length xs)) {
|
|
310
|
+
def vx:double (itemAt xs j)
|
|
311
|
+
def vy:double (itemAt ys j)
|
|
312
|
+
if (seen == false) {
|
|
313
|
+
b.minX = vx
|
|
314
|
+
b.maxX = vx
|
|
315
|
+
b.minY = vy
|
|
316
|
+
b.maxY = vy
|
|
317
|
+
seen = true
|
|
318
|
+
} {
|
|
319
|
+
if (vx < b.minX) { b.minX = vx }
|
|
320
|
+
if (vx > b.maxX) { b.maxX = vx }
|
|
321
|
+
if (vy < b.minY) { b.minY = vy }
|
|
322
|
+
if (vy > b.maxY) { b.maxY = vy }
|
|
323
|
+
}
|
|
324
|
+
j = (j + 1)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
k = (k + 1)
|
|
328
|
+
}
|
|
329
|
+
b.width = (b.maxX - b.minX)
|
|
330
|
+
b.height = (b.maxY - b.minY)
|
|
331
|
+
return b
|
|
332
|
+
}
|
|
333
|
+
}
|