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,664 @@
|
|
|
1
|
+
# EVG Vector IR & SVG Import Plan
|
|
2
|
+
|
|
3
|
+
**Status:** Draft for discussion
|
|
4
|
+
**Scope:** `lib/evg/` (renderer-neutral) + the three renderers in
|
|
5
|
+
`gallery/pdf_writer/src/` and the WASM UI in `gallery/game_engine/v2/ui/`
|
|
6
|
+
|
|
7
|
+
## 1. Premise
|
|
8
|
+
|
|
9
|
+
The proposal is: *do not make EVG an SVG engine — give EVG a renderer-neutral
|
|
10
|
+
vector layer and make SVG one importer into it.* That direction is right, and
|
|
11
|
+
this document adapts it to what is already in the tree.
|
|
12
|
+
|
|
13
|
+
The important correction to the original sketch: **this is not a greenfield
|
|
14
|
+
build.** `lib/evg/SVGPathParser.rgr` already exists (568 lines) and already
|
|
15
|
+
contains `PathCommand`, an SVG path-data parser, bounds calculation, scaling and
|
|
16
|
+
a curve `flatten()`. It already has three independent consumers:
|
|
17
|
+
|
|
18
|
+
| Consumer | File | Uses |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| PDF renderer | `pdf_writer/src/core/EVGPDFRenderer.rgr:1762` | `getScaledCommands()` → PDF `m`/`l`/`c`/`h` |
|
|
21
|
+
| WASM UI | `game_engine/v2/ui/WasmUiSelect.rgr:538` | `flatten(12)` → `UIContext.fillPolygon` |
|
|
22
|
+
| EVG tests | `game_engine/v2/evg/evg_test.rgr:2210` | parse + bounds |
|
|
23
|
+
|
|
24
|
+
The HTML renderer is a fourth consumer in spirit: it does not parse at all, it
|
|
25
|
+
forwards `el.svgPath` and `el.viewBox` straight into an inline `<svg>` and lets
|
|
26
|
+
the browser do the work (`EVGHTMLRenderer.rgr:1505`).
|
|
27
|
+
|
|
28
|
+
So the work is **not** "add a vector layer". It is "the vector layer exists,
|
|
29
|
+
is under-specified, and every consumer interprets it differently". That is a
|
|
30
|
+
stronger argument for the plan than the original sketch made, because the
|
|
31
|
+
divergence is already producing wrong output today.
|
|
32
|
+
|
|
33
|
+
## 2. What is actually broken today
|
|
34
|
+
|
|
35
|
+
These are the concrete defects that a shared Vector IR is meant to eliminate.
|
|
36
|
+
Each one is a real behavioural difference, not a hypothetical.
|
|
37
|
+
|
|
38
|
+
### 2.1 PDF and HTML disagree about `viewBox`
|
|
39
|
+
|
|
40
|
+
For the same `<Path>` element:
|
|
41
|
+
|
|
42
|
+
* **PDF** calls `getScaledCommands(w, h)`, which maps the path's *bounding box*
|
|
43
|
+
onto the element box with **independent X and Y scale factors**, and ignores
|
|
44
|
+
`viewBox` completely.
|
|
45
|
+
* **HTML** emits `<svg viewBox="…" width="w" height="h"><path d="…">` and the
|
|
46
|
+
browser applies real SVG semantics: viewBox → viewport with
|
|
47
|
+
`preserveAspectRatio="xMidYMid meet"` (uniform scale, centred, letterboxed).
|
|
48
|
+
|
|
49
|
+
Consequences: a non-square icon is stretched in PDF and letterboxed in HTML;
|
|
50
|
+
intentional padding inside the viewBox (very common in 24×24 icon sets) is
|
|
51
|
+
silently cropped away in PDF because bbox-fitting removes it; and a path that
|
|
52
|
+
does not start at the viewBox origin lands in a different place in each output.
|
|
53
|
+
|
|
54
|
+
This one issue justifies the whole plan on its own: the same document does not
|
|
55
|
+
render the same way, and no amount of per-renderer patching fixes it because
|
|
56
|
+
there is no shared definition of what the coordinates *mean*.
|
|
57
|
+
|
|
58
|
+
### 2.2 The path parser silently drops what it does not understand
|
|
59
|
+
|
|
60
|
+
`SVGPathParser.parseCommand` handles `M m L l H h V v C c Q q Z z`. It does
|
|
61
|
+
**not** handle:
|
|
62
|
+
|
|
63
|
+
* `A`/`a` — elliptical arc. `PathCommand` has `rx`, `ry`, `rotation`,
|
|
64
|
+
`largeArc`, `sweep` fields and `flatten()` has an `"A"` branch, but the parser
|
|
65
|
+
never emits one, so those are dead code.
|
|
66
|
+
* `S`/`s`, `T`/`t` — smooth cubic/quadratic. Common in optimizer output (SVGO)
|
|
67
|
+
and in hand-written paths.
|
|
68
|
+
* **Implicit command repetition** — `M 10 10 20 20 30 30`, `C … … …` with
|
|
69
|
+
repeated coordinate sets. The main loop reads one letter, `parseCommand`
|
|
70
|
+
consumes exactly one coordinate set and returns.
|
|
71
|
+
|
|
72
|
+
The failure mode is the problem: unrecognised letters and leftover numbers are
|
|
73
|
+
skipped one character at a time by the `i = i + 1` branch in `parse()`. Nothing
|
|
74
|
+
raises, nothing warns. The path just quietly loses segments and the shape closes
|
|
75
|
+
across the gap. For hand-authored icon paths this has been survivable; for
|
|
76
|
+
imported `.svg` files it is not.
|
|
77
|
+
|
|
78
|
+
### 2.3 `Q` → cubic conversion in the PDF renderer is mathematically wrong
|
|
79
|
+
|
|
80
|
+
`EVGPDFRenderer.rgr:1804` emits, for a quadratic segment with control `C`:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
x1 y1 x1 y1 x y c
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
i.e. it uses the quadratic control point as *both* cubic control points. The
|
|
87
|
+
correct degree elevation is:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
C1 = P0 + 2/3 (C - P0)
|
|
91
|
+
C2 = P3 + 2/3 (C - P3)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The current form pulls the curve toward the control point far too weakly, so
|
|
95
|
+
quadratic curves render visibly flatter in PDF than in HTML (where the browser
|
|
96
|
+
does it correctly). The comment in the source (`; For simplicity, approximate
|
|
97
|
+
with cubic`) shows this was known to be an approximation; it is a
|
|
98
|
+
one-line-per-coordinate exact fix, and in a shared IR it is fixed once.
|
|
99
|
+
|
|
100
|
+
### 2.4 `flatten()` loses subpath structure, so holes are impossible
|
|
101
|
+
|
|
102
|
+
`flatten(steps)` returns a flat `[x0,y0,x1,y1,…]` list with no ring boundaries —
|
|
103
|
+
an `M` in the middle of the path just pushes another point. `UIContext.fillPolygon`
|
|
104
|
+
then treats the whole list as one closed polygon (it wraps last → first) and
|
|
105
|
+
fills by counting scanline crossings.
|
|
106
|
+
|
|
107
|
+
So any path with more than one subpath renders wrong: a donut, the letter "O",
|
|
108
|
+
a check mark inside a ring, any icon with a counter. Every real icon set hits
|
|
109
|
+
this.
|
|
110
|
+
|
|
111
|
+
### 2.5 Raster has no path support at all
|
|
112
|
+
|
|
113
|
+
`RasterPrimitives` has line/rect/rounded-rect/circle/ellipse. There is no path
|
|
114
|
+
entry point, which is exactly what `TODO_EVG.md` tracks as **PNG 0.9 SVG path
|
|
115
|
+
rasterization — TODO**. A `<Path>` in a TSX document renders in PDF and HTML and
|
|
116
|
+
is simply absent from PNG output.
|
|
117
|
+
|
|
118
|
+
## 3. The one big asset the sketch under-valued
|
|
119
|
+
|
|
120
|
+
The original sketch treats the raster side as "the actual work" and proposes a
|
|
121
|
+
new `VectorRasterizer`. In fact **a general anti-aliased path rasterizer already
|
|
122
|
+
exists in the tree** — it is just hard-wired to font glyphs. In
|
|
123
|
+
`pdf_writer/src/raster/RasterText.rgr`:
|
|
124
|
+
|
|
125
|
+
* `GlyphContour` — a closed contour of points (`RasterText.rgr:31`)
|
|
126
|
+
* `GlyphEdge` — an edge carrying a winding direction (`:71`)
|
|
127
|
+
* `flattenContour` — quadratic curve subdivision into edges (`:570`)
|
|
128
|
+
* `scanlineFillAA` — scanline fill, **non-zero winding rule**, 4×4 subpixel
|
|
129
|
+
supersampling (`:908`)
|
|
130
|
+
|
|
131
|
+
That is a complete polygon rasterizer. Nothing about the fill stage is
|
|
132
|
+
font-specific; only the *input* is (TrueType contours, quadratic-only, on/off
|
|
133
|
+
curve points).
|
|
134
|
+
|
|
135
|
+
This changes the cost estimate substantially. The raster milestone is mostly an
|
|
136
|
+
**extraction**, not a new algorithm:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
RasterText VectorRasterizer RasterText
|
|
140
|
+
GlyphContour ──► Contour ◄── (feeds glyph contours)
|
|
141
|
+
GlyphEdge ──► Edge ◄── SVGPathParser (flattened)
|
|
142
|
+
scanlineFillAA ──► fillAA(rule) ◄── UIContext.fillPolygon
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Three wins from one refactor: PNG 0.9 gets done, the WASM UI's `fillPolygon`
|
|
146
|
+
gains anti-aliasing and hole support for free, and glyph rendering and path
|
|
147
|
+
rendering can no longer drift apart. The only genuinely new capability needed is
|
|
148
|
+
an **even-odd** fill rule alongside the existing non-zero.
|
|
149
|
+
|
|
150
|
+
## 4. Recommended architecture
|
|
151
|
+
|
|
152
|
+
Agreed with the sketch on the central decision:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
SVG file ──► SvgParser ──┐
|
|
156
|
+
├──► Vector IR ──┬──► PDF (native path operators)
|
|
157
|
+
TSX <Path>/<Vector> ─────┤ ├──► HTML (inline <svg>)
|
|
158
|
+
│ ├──► Raster(VectorRasterizer)
|
|
159
|
+
programmatic (charts) ───┘ └──► WASM UI (UIContext)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Two refinements.
|
|
163
|
+
|
|
164
|
+
### 4.1 Do not introduce a second node tree — until imports need one
|
|
165
|
+
|
|
166
|
+
The sketch proposes `VectorGroup` / `VectorPath` / `VectorRect` / `VectorEllipse`
|
|
167
|
+
as a parallel node tree. EVG already has an element tree (`EVGElement`, with
|
|
168
|
+
children, `transform`, `viewBox`, `fillColor`, `strokeColor`, `strokeWidth`) plus
|
|
169
|
+
`EVGDisplayList` as a flattened draw-command stream. A third tree means a third
|
|
170
|
+
traversal, a third transform resolution and a third paint resolution.
|
|
171
|
+
|
|
172
|
+
Recommended split, decided by **where the nodes come from**:
|
|
173
|
+
|
|
174
|
+
* **Authored vector** (`<Path>`, `<Vector>` in TSX): stays `EVGElement`. There
|
|
175
|
+
are few nodes, they benefit from layout, props and data binding, and this is
|
|
176
|
+
already how `<Path>` works.
|
|
177
|
+
* **Imported SVG** (`<Svg src="logo.svg">`): collapses into **one** `EVGElement`
|
|
178
|
+
carrying a lightweight, layout-free vector display list. An Illustrator export
|
|
179
|
+
can be thousands of nodes; giving each one a full `EVGElement` layout box would
|
|
180
|
+
be very expensive for something that never participates in layout.
|
|
181
|
+
|
|
182
|
+
This is consistent with the sketch's own observation that EVG should see an
|
|
183
|
+
imported SVG as a single layout element. It also keeps the symmetry the sketch
|
|
184
|
+
wants — `SVG → EVG` next to `TSX → EVG` — with `EVGElement` as the shared target.
|
|
185
|
+
|
|
186
|
+
The value types the IR does need are small and uncontroversial: `PathCommand`
|
|
187
|
+
(exists), `Matrix2D`, `Paint`, `FillRule`.
|
|
188
|
+
|
|
189
|
+
### 4.2 Put it in `lib/evg/`, not a new tree
|
|
190
|
+
|
|
191
|
+
`lib/evg/` is already the renderer-neutral module (`EVGElement`, `EVGLayout`,
|
|
192
|
+
`EVGColor`, `EVGGradient`, `EVGDisplayList`, `SVGPathParser`). The renderers live
|
|
193
|
+
in `gallery/pdf_writer/src/{core,raster}` and `game_engine/v2/ui`. The vector
|
|
194
|
+
files belong next to `SVGPathParser.rgr`:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
lib/evg/
|
|
198
|
+
SVGPathParser.rgr (exists — extend, keep the name: 3 importers + tests)
|
|
199
|
+
VectorPath.rgr (contours, fill rule, paint, bounds)
|
|
200
|
+
Matrix2D.rgr
|
|
201
|
+
VectorViewBox.rgr (viewBox + preserveAspectRatio → element-box transform)
|
|
202
|
+
SvgParser.rgr (XML subset → EVGElement / vector display list)
|
|
203
|
+
EvgBitmapTracer.rgr (bitmap → path; Potrace-style params for benchmarking)
|
|
204
|
+
EvgTraceFit.rgr (optimal polygon: calcLon / bestPolygon / adjustVertices)
|
|
205
|
+
EvgTraceCurve.rgr (alphamax smooth + optiCurve)
|
|
206
|
+
EvgTraceTypes.rgr (shared bitmap/options/point types)
|
|
207
|
+
|
|
208
|
+
gallery/pdf_writer/src/raster/
|
|
209
|
+
VectorRasterizer.rgr (extracted from RasterText)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Keeping the `SVGPathParser.rgr` filename matters: three files import it by
|
|
213
|
+
relative path and one is outside `pdf_writer`. Renaming it is a separate,
|
|
214
|
+
mechanical commit if wanted at all.
|
|
215
|
+
|
|
216
|
+
## 5. Staging
|
|
217
|
+
|
|
218
|
+
Ordered so that each stage lands something visible and nothing depends on a
|
|
219
|
+
stage that has not shipped.
|
|
220
|
+
|
|
221
|
+
### Stage 0 — Make the renderers agree — **DONE**
|
|
222
|
+
|
|
223
|
+
The highest value per line of code in the whole plan.
|
|
224
|
+
|
|
225
|
+
1. ✅ `Q` → cubic in `EVGPDFRenderer` now uses exact degree elevation (§2.3).
|
|
226
|
+
2. ✅ `lib/evg/VectorViewBox.rgr` — `Matrix2D` plus one implementation of
|
|
227
|
+
the SVG viewBox / `preserveAspectRatio` rule.
|
|
228
|
+
3. ✅ PDF resolves through it and emits the result as a `cm` matrix, with path
|
|
229
|
+
commands left in user units; `getScaledCommands(w, h)` is no longer on the
|
|
230
|
+
render path.
|
|
231
|
+
4. ✅ HTML asks the same class which viewBox applies and emits it, so the
|
|
232
|
+
browser applies the identical rule.
|
|
233
|
+
5. ✅ `bash gallery/pdf_writer/test/run_vector.sh` (`npm run test:evg:vector`).
|
|
234
|
+
|
|
235
|
+
**One behaviour change worth knowing about.** No document in the repo sets
|
|
236
|
+
`viewBox` on a `<Path>` — every one relies on the art being fitted to
|
|
237
|
+
`width`/`height`. Dropping that would have broken all of them, so the fallback
|
|
238
|
+
is now: *explicit viewBox wins; otherwise synthesise one from the path bounds*,
|
|
239
|
+
and both renderers use the same fallback. Two consequences:
|
|
240
|
+
|
|
241
|
+
* PDF no longer stretches non-square art — the fit is uniform (`meet`), so a
|
|
242
|
+
path drawn into a box of a different aspect ratio is letterboxed rather than
|
|
243
|
+
distorted.
|
|
244
|
+
* HTML now emits the synthesised viewBox where it previously emitted none, so
|
|
245
|
+
the browser fits the art instead of drawing it at 1:1 in the corner.
|
|
246
|
+
|
|
247
|
+
Both moved toward each other, and toward what the documents were clearly asking
|
|
248
|
+
for. Verified end to end: on the same fixture, PDF emits `2 0 0 2 -20 -20 cm`
|
|
249
|
+
where HTML emits `viewBox="10 10 30 30"` — the same transform expressed twice.
|
|
250
|
+
|
|
251
|
+
### Stage 1 — Path completeness — **DONE**
|
|
252
|
+
|
|
253
|
+
1. `S/s`, `T/t` (smooth cubic/quad, reflected control point).
|
|
254
|
+
2. Implicit command repetition in the parse loop.
|
|
255
|
+
3. **Fail loudly**: unknown command letters and trailing garbage must be reported
|
|
256
|
+
(a warning list on the parser), never silently skipped. Today's behaviour is
|
|
257
|
+
the worst kind — wrong output, no signal.
|
|
258
|
+
4. `A/a` arcs: parse, then normalise endpoint → centre parameterisation and emit
|
|
259
|
+
cubics **in the IR**, so PDF, raster, HTML and WASM all get arcs from one
|
|
260
|
+
implementation. (The PDF renderer currently has no `"A"` branch at all, so an
|
|
261
|
+
arc would vanish silently.)
|
|
262
|
+
|
|
263
|
+
### Stage 2 — Contours and the shared rasterizer — **DONE**
|
|
264
|
+
|
|
265
|
+
1. ✅ `flattenRings` returns **rings** and applies the transform while
|
|
266
|
+
flattening, so curves are subdivided at the size they are drawn.
|
|
267
|
+
`PathRing` is the unit; `flatten()` is kept for its existing callers and
|
|
268
|
+
documented as unable to express more than one contour.
|
|
269
|
+
2. ✅ `VectorRasterizer` extracted from `RasterText`: `VectorEdge`, `addRing`,
|
|
270
|
+
`fillAA(rule)`, with even-odd alongside non-zero.
|
|
271
|
+
3. ✅ `RasterText` feeds glyph contours into it. Verified by rendering a text
|
|
272
|
+
page before and after the refactor: **byte-identical PNG**. The duplicate
|
|
273
|
+
non-AA fill path in `RasterText` (`renderGlyphFast`, `scanlineFill` and
|
|
274
|
+
their helpers, all unreachable) went with it — leaving it would have left
|
|
275
|
+
two fills to drift apart, which is what the stage exists to prevent.
|
|
276
|
+
4. ✅ Raster `<Path>` support, fill and stroke → closes **PNG 0.9**.
|
|
277
|
+
5. ❌ `UIContext.fillPolygon` — see the note below.
|
|
278
|
+
|
|
279
|
+
Note on scope: **fill was nearly free, stroke was not.** As predicted, fill came
|
|
280
|
+
out of the extraction almost unchanged, while stroking needed its own answer.
|
|
281
|
+
Shipped as the approximation described above: one quad per flattened segment
|
|
282
|
+
plus a disc at each joint, merged by the non-zero rule, giving round joins and
|
|
283
|
+
butt caps. It is indistinguishable from a real stroke at icon weights and
|
|
284
|
+
visibly an approximation for very thick strokes with sharp corners, where a
|
|
285
|
+
mitre would run to a point. PDF and HTML stroke natively, so only raster carries
|
|
286
|
+
it. Real offset curves remain the eventual answer.
|
|
287
|
+
|
|
288
|
+
**What did not land: the WASM UI still has its own fill.** `UIContext` paints
|
|
289
|
+
into `SoftCanvas` (`game_engine/v2/framebuffer.rgr`) while the rasterizer works
|
|
290
|
+
on `RasterBuffer` (`pdf_writer/src/raster`). Sharing the code needs either a
|
|
291
|
+
buffer abstraction or a cross-gallery dependency, and that is a design decision
|
|
292
|
+
worth making deliberately rather than as a side effect of this stage. The
|
|
293
|
+
shapes `WasmUiSelect` draws today are single-ring chevrons and check marks, so
|
|
294
|
+
it has no live defect from the missing hole support — but it does still lack
|
|
295
|
+
anti-aliasing, and `flatten()` is still what it calls.
|
|
296
|
+
|
|
297
|
+
### Stage 3 — Shape normalisation — **DONE**
|
|
298
|
+
|
|
299
|
+
`lib/evg/VectorShapes.rgr`: `rect` (with SVG's rx/ry rules), `circle`,
|
|
300
|
+
`ellipse`, `line`, `polyline`, `polygon` → `PathCommand[]`, plus `asPathData`
|
|
301
|
+
to go back to a `d` string. Pure functions, no renderer changes, no state.
|
|
302
|
+
|
|
303
|
+
Curves come out as cubics using the exact kappa rather than arcs, so consumers
|
|
304
|
+
need no arc conversion. Checked geometrically — sampled points on a circle must
|
|
305
|
+
lie on that circle, which is what would catch a wrong kappa where endpoint
|
|
306
|
+
comparisons never would — and round-tripped: emit a shape as path data, parse
|
|
307
|
+
it back with `SVGPathParser`, confirm the geometry survived. That last one
|
|
308
|
+
exercises the writer and the reader against each other and is what makes the
|
|
309
|
+
emitted `d` safe to hand to a browser.
|
|
310
|
+
|
|
311
|
+
**What this makes redundant, as a follow-up.** Three hand-rolled versions of
|
|
312
|
+
the same geometry now exist alongside the derived one:
|
|
313
|
+
|
|
314
|
+
| Duplicate | Where |
|
|
315
|
+
| --- | --- |
|
|
316
|
+
| `drawRoundedRectPath` | `EVGPDFRenderer.rgr:1966`, 7 call sites, with kappa truncated to `0.5523` |
|
|
317
|
+
| `fillRoundedRect` / `drawRoundedRect` | `RasterPrimitives.rgr:93`, `:129` |
|
|
318
|
+
| `fillCircle` / `fillEllipse` | `RasterPrimitives.rgr:170`, `:326` |
|
|
319
|
+
|
|
320
|
+
Routing them through `VectorShapes` is the obvious cleanup, and it is
|
|
321
|
+
deliberately NOT part of this stage: `drawRoundedRectPath` works in PDF's
|
|
322
|
+
y-up space and is used for clipping as well as filling, so the winding
|
|
323
|
+
direction of the replacement matters, and no golden test currently covers
|
|
324
|
+
border-radius output. Worth doing with a rendering comparison in hand rather
|
|
325
|
+
than on the way past.
|
|
326
|
+
|
|
327
|
+
A language note found here, recorded because it will bite again: **`to_int`
|
|
328
|
+
floors rather than truncating toward zero.** Splitting a negative number into
|
|
329
|
+
whole and fractional parts directly gives the wrong pair — -2.5 comes apart as
|
|
330
|
+
whole -3 and fraction 0.4999.
|
|
331
|
+
|
|
332
|
+
### Stage 3.5 — Chart primitives — **DONE**
|
|
333
|
+
|
|
334
|
+
Not in the original plan. These came out of asking what a chart engine would
|
|
335
|
+
need on top of the vector layer, and each turned out to be a gap the layer had
|
|
336
|
+
rather than something a chart library could paper over.
|
|
337
|
+
|
|
338
|
+
1. ✅ `PathBuilder` — the counterpart to the parser. A chart's geometry comes
|
|
339
|
+
from data, and without this the only way to draw it is to concatenate `d` by
|
|
340
|
+
hand and hand the result straight back to the parser. The basic shapes are
|
|
341
|
+
included, so a whole scene accumulates into one path with one fill.
|
|
342
|
+
2. ✅ `stroke-dasharray` / `stroke-dashoffset` in all three targets, parsed
|
|
343
|
+
once in `VectorStroke`. Gridlines.
|
|
344
|
+
3. ✅ `rotate` rendered. It had been parsed into `EVGElement` all along with no
|
|
345
|
+
renderer reading it, so a rotated axis label was silently upright. The
|
|
346
|
+
rasterizer takes a transform, which is what makes it work for glyphs and
|
|
347
|
+
paths through one implementation.
|
|
348
|
+
4. ✅ Rectangular clipping in the raster target, so a series stops at the plot
|
|
349
|
+
area. PDF and HTML already clipped; only raster leaked.
|
|
350
|
+
5. ✅ `opacity` in the raster target, per element rather than per group.
|
|
351
|
+
|
|
352
|
+
**Found on the way, and worth knowing.** `to_string` reaches for exponential
|
|
353
|
+
notation on very small magnitudes — `cos 90°` is `6.123233995736766e-17` — and
|
|
354
|
+
**PDF real numbers have no exponential form**. That was going into content
|
|
355
|
+
streams, where a viewer is within its rights to reject the whole thing. Now
|
|
356
|
+
clamped, with the gate asserting no exponent ever reaches the stream.
|
|
357
|
+
|
|
358
|
+
**What is left of this group.** Opacity in the PDF target needs an ExtGState
|
|
359
|
+
resource threaded into the page dictionary in two code paths. It is the most
|
|
360
|
+
delicate part of the PDF writer and nothing golden-tests its structure, so it
|
|
361
|
+
was left rather than done in a hurry.
|
|
362
|
+
|
|
363
|
+
Also still per-target rather than shared: rotation applies in raster to what
|
|
364
|
+
goes through the rasterizer — text and paths. Rectangular backgrounds come from
|
|
365
|
+
`RasterPrimitives` and stay upright. Routing those through `VectorShapes` is the
|
|
366
|
+
same follow-up Stage 3 already records.
|
|
367
|
+
|
|
368
|
+
### Stage 4 — `SvgParser` and the `<Svg>` element — **DONE**
|
|
369
|
+
|
|
370
|
+
`lib/evg/SvgParser.rgr` reads an SVG document into the vector layer, and
|
|
371
|
+
`<Svg src="logo.svg">` draws one in a TSX document. It is the only file in the
|
|
372
|
+
tree that knows any XML.
|
|
373
|
+
|
|
374
|
+
**What comes out is a flat list, not a node tree.** `SvgVectorItem` is resolved
|
|
375
|
+
geometry plus resolved paint, and the whole document is a `[SvgVectorItem]`.
|
|
376
|
+
Two decisions make that possible and both are worth stating, because they are
|
|
377
|
+
the reason the renderers needed almost no new code:
|
|
378
|
+
|
|
379
|
+
* **Transforms are baked.** Every enclosing `transform` is applied to the
|
|
380
|
+
commands as they are produced, so an item's geometry is absolute in the
|
|
381
|
+
document's own user space. An affine map takes a cubic to a cubic, so this
|
|
382
|
+
approximates nothing — and it means a renderer needs no transform stack, no
|
|
383
|
+
group traversal and no state machine. It draws a list.
|
|
384
|
+
* **Paint is resolved.** Inheritance down groups, `fill="none"`, the three
|
|
385
|
+
opacity properties, `style` outranking presentation attributes: all settled
|
|
386
|
+
once, in the importer, so the three renderers cannot come to different
|
|
387
|
+
conclusions about what colour a shape is.
|
|
388
|
+
|
|
389
|
+
**The element reuses the path type rather than adding one.** An imported
|
|
390
|
+
drawing is laid out, sized and positioned exactly like a `<Path>`, so a new
|
|
391
|
+
`elementType` would have meant a second copy of that in the layout engine and in
|
|
392
|
+
all three renderers with no behavioural difference. `EVGElement.svgSource`
|
|
393
|
+
carries the markup; `<Svg src>` is resolved relative to the document that names
|
|
394
|
+
it, in `JSXToEVG.parseFile`, which is the one place that touches the filesystem
|
|
395
|
+
— `SvgParser` itself never opens anything, which is what keeps §8's "no network
|
|
396
|
+
or filesystem access from inside the parser" true by construction rather than by
|
|
397
|
+
review.
|
|
398
|
+
|
|
399
|
+
**HTML goes through the IR too, and this is the load-bearing decision.** It
|
|
400
|
+
would have been *less* code to hand the original markup to the browser inside an
|
|
401
|
+
inline `<svg>` — and it would have reintroduced exactly the class of defect this
|
|
402
|
+
plan exists to remove, because the browser would happily draw the filters,
|
|
403
|
+
gradients, clip paths and live text that PDF and PNG cannot. The HTML target
|
|
404
|
+
therefore emits one `<path>` per resolved item, and a construct outside the
|
|
405
|
+
profile is missing from the preview in the same way it is missing from the
|
|
406
|
+
print. Out of profile has to mean out of profile everywhere, or the profile is
|
|
407
|
+
not real.
|
|
408
|
+
|
|
409
|
+
**In the profile:** `svg`, `g`, `a`, `defs`, `use` (same-document fragments,
|
|
410
|
+
including forward references and `xlink:href`), `path`, `rect`, `circle`,
|
|
411
|
+
`ellipse`, `line`, `polyline`, `polygon`; `transform` with `matrix`,
|
|
412
|
+
`translate`, `scale`, `rotate` (both forms), `skewX`, `skewY`; `fill`, `stroke`,
|
|
413
|
+
`stroke-width`, `fill-rule`, `stroke-dasharray`, `stroke-dashoffset`,
|
|
414
|
+
`opacity`, `fill-opacity`, `stroke-opacity`, and the `style` attribute carrying
|
|
415
|
+
any of them; the root's `viewBox` and `width`/`height`; comments, CDATA,
|
|
416
|
+
processing instructions and namespace prefixes.
|
|
417
|
+
|
|
418
|
+
**Refused, and reported (§7.4):** `text` — with its own sentence, because a logo
|
|
419
|
+
that quietly loses its wordmark is the most likely "SVG support is broken"
|
|
420
|
+
report — plus `script`, `style`, `image`, `filter`, `mask`, `clipPath`,
|
|
421
|
+
`pattern`, `marker`, `symbol`, the animation elements, gradient elements (§6),
|
|
422
|
+
`switch`, nested `<svg>` viewports, external `href`, unknown elements, unknown
|
|
423
|
+
transform functions, unreadable colours and unreadable lengths. Warnings are
|
|
424
|
+
deduplicated by kind, so a file with four hundred gradient fills says it once.
|
|
425
|
+
|
|
426
|
+
**Refused by construction, not by mitigation:** there is no entity machinery at
|
|
427
|
+
all, and a DOCTYPE carrying an internal subset — the only place an entity
|
|
428
|
+
declaration can appear — is a parse error rather than something parsed with the
|
|
429
|
+
entities left as text. That removes billion-laughs and XXE by there being
|
|
430
|
+
nothing to expand. Node count, nesting depth, path-command count and `<use>`
|
|
431
|
+
expansion depth are all capped and reported as errors rather than as an
|
|
432
|
+
out-of-memory.
|
|
433
|
+
|
|
434
|
+
**Found on the way, and worth knowing.** `str2double` reads `"100%"` as `100`.
|
|
435
|
+
A root `width="100%"` is the SVG default and means "fill the viewport" — which
|
|
436
|
+
for an imported drawing is a decision the hosting element makes — so reading it
|
|
437
|
+
as user units would have sized every default-width document by a number that
|
|
438
|
+
means something else entirely. Lengths are now checked to be numbers before they
|
|
439
|
+
are read as numbers.
|
|
440
|
+
|
|
441
|
+
**Fixed on the way past.** Two divergences the imported case walked straight
|
|
442
|
+
into:
|
|
443
|
+
|
|
444
|
+
* The PDF renderer had no even-odd fill: it always emitted `f`/`B`, never
|
|
445
|
+
`f*`/`B*`, so `fill-rule="evenodd"` — which HTML hands to the browser and the
|
|
446
|
+
raster target has honoured since Stage 2 — filled the middle of a ring in PDF
|
|
447
|
+
and left it hollow in the other two.
|
|
448
|
+
* The HTML renderer did not rotate a `<Path>`. PDF emitted a rotation matrix and
|
|
449
|
+
the raster target turned the shape on the shared rasterizer, but this renderer
|
|
450
|
+
builds the inline `<svg>`'s style itself rather than going through the builder
|
|
451
|
+
that carries `transform`, so `rotate` never reached it and the shape stayed
|
|
452
|
+
upright.
|
|
453
|
+
|
|
454
|
+
**The element's fill is the document's initial paint.** SVG's initial `fill` is
|
|
455
|
+
black, and that is still the default — but when the hosting element has a fill,
|
|
456
|
+
that becomes the value any shape inherits when neither it nor anything above it
|
|
457
|
+
names one. A file that sets its own colours is untouched, because its own value
|
|
458
|
+
wins. This is what lets an icon set follow a theme without editing the files,
|
|
459
|
+
and it is the only sensible answer for `currentColor`, which otherwise refers to
|
|
460
|
+
a CSS property an imported document cannot see. It is also what keeps the
|
|
461
|
+
showcase's rule — geometry in the tree, appearance in the stylesheet — true for
|
|
462
|
+
files the gallery does not own.
|
|
463
|
+
|
|
464
|
+
**Four consumers, not three.** `EVGDisplayList` carries imported documents too,
|
|
465
|
+
so the GL/WebGL backend gets them. Leaving it out would have made a `<Svg>`
|
|
466
|
+
present in three targets and absent from the fourth, which is the exact failure
|
|
467
|
+
that file was written to prevent.
|
|
468
|
+
|
|
469
|
+
**Not in this stage.** Stroke joins and caps are not represented (the raster
|
|
470
|
+
stroke is butt caps and round joins, Stage 2), `preserveAspectRatio` inside an
|
|
471
|
+
imported document is ignored because the hosting element decides the fit, and
|
|
472
|
+
group `opacity` is multiplied into each child's alpha rather than composited as
|
|
473
|
+
a unit — overlapping children inside a faded group show through each other here
|
|
474
|
+
and would not in a browser. Each is reported when a document uses it.
|
|
475
|
+
|
|
476
|
+
**Showcase.** A `svg` page joins the gallery with two files that make the
|
|
477
|
+
distinction visible: `rosette.svg` names no colours and is painted by the theme
|
|
478
|
+
(and is one petal referenced eight times, so `<use>` and the baked rotations are
|
|
479
|
+
what is on the page), while `emblem.svg` brings its own palette, its own group
|
|
480
|
+
transform and an even-odd counter, and keeps all three.
|
|
481
|
+
|
|
482
|
+
### Stage 5 — Paint (flat colour only) — **DONE**
|
|
483
|
+
|
|
484
|
+
Landed with Stage 4, because paint resolution is not separable from the walk
|
|
485
|
+
that produces the items: `fill`, `stroke`, `stroke-width`, `fill-rule`, the dash
|
|
486
|
+
properties and the three opacity properties, inherited down groups and resolved
|
|
487
|
+
into each item. Gradient paint remains deliberately **out of scope for this
|
|
488
|
+
plan** — see §6 — and a `url(#…)` reference is dropped with a diagnostic rather
|
|
489
|
+
than rendered differently in each target.
|
|
490
|
+
|
|
491
|
+
## 6. Gradients are deferred, on purpose
|
|
492
|
+
|
|
493
|
+
`linearGradient` / `radialGradient` are dropped from the first vector profile.
|
|
494
|
+
Everything else in this plan comes first. The reasoning is worth recording,
|
|
495
|
+
because it is not just a scheduling call.
|
|
496
|
+
|
|
497
|
+
**Gradients are already the worst parity offender in the codebase.** Today the
|
|
498
|
+
same `background-gradient` produces three different results:
|
|
499
|
+
|
|
500
|
+
| Target | Behaviour |
|
|
501
|
+
| --- | --- |
|
|
502
|
+
| HTML | `EVGHTMLRenderer.rgr:827` — the raw CSS string is passed through verbatim; the browser interpolates, with all stops |
|
|
503
|
+
| Raster | `evg_png_tool.rgr:490` — only `getStartColor()` and `getEndColor()` are read; **every intermediate stop is discarded**, and `RasterGradient` does its own interpolation |
|
|
504
|
+
| PDF | Not implemented at all (`TODO_EVG.md` still lists Type 2 / Type 3 shading as upcoming) |
|
|
505
|
+
|
|
506
|
+
So a three-stop gradient is correct in HTML, silently reduced to two stops in
|
|
507
|
+
PNG, and absent from PDF. Extending that into the vector layer would multiply an
|
|
508
|
+
existing inconsistency across every path, and the whole point of §2 is to *remove*
|
|
509
|
+
this class of divergence.
|
|
510
|
+
|
|
511
|
+
**Print makes the guarantee unkeepable anyway.** Even with PDF shading
|
|
512
|
+
implemented, gradients are the one construct where identical output cannot be
|
|
513
|
+
promised: many RIPs and printer drivers rasterize smooth shading rather than
|
|
514
|
+
executing it, at a resolution and dither of their own choosing, and banding
|
|
515
|
+
behaviour then depends on the device. A flat fill survives that pipeline
|
|
516
|
+
predictably; a gradient does not.
|
|
517
|
+
|
|
518
|
+
**Colour management is a separate, larger axis — and it affects flat colour
|
|
519
|
+
too.** Worth stating explicitly so it is not mistaken for a gradient problem:
|
|
520
|
+
|
|
521
|
+
* `EVGColor` is RGBA doubles with no colour-space tag (`EVGColor.rgr:5`).
|
|
522
|
+
* The PDF renderer writes `rg` / `RG` (DeviceRGB) everywhere, and images as
|
|
523
|
+
`/DeviceRGB`. There is no ICC profile, no `/OutputIntent`, no CMYK path.
|
|
524
|
+
|
|
525
|
+
For screen output this is fine. For print, DeviceRGB means the conversion to the
|
|
526
|
+
press's CMYK happens somewhere downstream, outside EVG's control — brand colours
|
|
527
|
+
are the usual casualty. That is a genuine gap, but it is a **document-level
|
|
528
|
+
colour pipeline** question (output intent, ICC, spot colours), not something to
|
|
529
|
+
solve as a side effect of adding gradients. Deferring gradients keeps the two
|
|
530
|
+
questions from getting entangled.
|
|
531
|
+
|
|
532
|
+
**When to revisit.** Gradients are visually valuable, particularly for
|
|
533
|
+
application UI where the raster and HTML targets dominate and print parity is
|
|
534
|
+
not a constraint. The natural time to come back is after Stage 2, once
|
|
535
|
+
`VectorRasterizer` exists: gradient paint then becomes "which colour does this
|
|
536
|
+
covered pixel get", a property of the shared rasterizer rather than a
|
|
537
|
+
per-renderer special case. Prerequisites when that happens: N-stop support in
|
|
538
|
+
`RasterGradient` (removing the two-stop truncation), and PDF Type 2/3 shading,
|
|
539
|
+
so all three targets start from the same model.
|
|
540
|
+
|
|
541
|
+
## 7. Conformance: lean on the standard, and on the browser
|
|
542
|
+
|
|
543
|
+
The profile is deliberately small, but **everything inside it should follow
|
|
544
|
+
SVG 1.1 semantics exactly**. Adopt the spec's *semantics*, not its *scope*.
|
|
545
|
+
This matters more than it sounds: it is what turns "the renderers disagree"
|
|
546
|
+
from a design debate into a defect with a right answer. In §2.1, PDF is not an
|
|
547
|
+
alternative convention — it is wrong, and the spec says so.
|
|
548
|
+
|
|
549
|
+
Three levels of conformance backing, in order of value per effort.
|
|
550
|
+
|
|
551
|
+
### 7.1 Spec-derived unit assertions (do this from Stage 0)
|
|
552
|
+
|
|
553
|
+
The parts of SVG 1.1 that are literally formulas or grammar need no images, no
|
|
554
|
+
browser and no rendering — they are pure functions with published expected
|
|
555
|
+
values:
|
|
556
|
+
|
|
557
|
+
| What | Spec source | Test shape |
|
|
558
|
+
| --- | --- | --- |
|
|
559
|
+
| Path data grammar | §8.3 BNF | path string → `PathCommand[]`, incl. implicit repeats and `S`/`T` reflection |
|
|
560
|
+
| Path error handling | §8.3 | malformed `d` → commands up to the error, plus a diagnostic |
|
|
561
|
+
| Elliptical arc | Appendix F.6.5 (endpoint → centre) and F.6.6 (out-of-range radii correction) | arc params → centre/angles → cubics, numeric comparison |
|
|
562
|
+
| `preserveAspectRatio` | viewBox/viewport section | (viewBox, viewport, align, meet\|slice) → `Matrix2D`, all nine align values |
|
|
563
|
+
| Fill rules | `fill-rule` property | self-intersecting star → nonzero vs even-odd coverage |
|
|
564
|
+
| Shape → path | shape sections (incl. the arc-magic-number for `rect` corners) | `rect`/`circle`/`ellipse`/`polygon` → `PathCommand[]` |
|
|
565
|
+
|
|
566
|
+
One refinement this forces on Stage 1: the spec **defines** path-data error
|
|
567
|
+
handling — render up to, but not including, the command containing the first
|
|
568
|
+
error. That is better than either today's silent skip or a hard failure. Do
|
|
569
|
+
both halves: truncate per spec *and* surface a diagnostic, and assert both.
|
|
570
|
+
|
|
571
|
+
### 7.2 The browser as oracle — the harness already exists
|
|
572
|
+
|
|
573
|
+
`test/browser_parity_snapshot.js` + `browser_parity_test.rgr` already implement
|
|
574
|
+
exactly the right pattern for this, for font metrics: Chromium establishes the
|
|
575
|
+
numbers **once** into a committed snapshot, and the offline gate then checks EVG
|
|
576
|
+
against that snapshot with no browser and no network — because, as that file
|
|
577
|
+
puts it, a correctness gate that can be skipped stops being one.
|
|
578
|
+
|
|
579
|
+
Extend the same mechanism to vector geometry. The browser is already in the loop
|
|
580
|
+
as a conformance-grade SVG implementation (the HTML renderer delegates to it),
|
|
581
|
+
and the useful outputs are *numbers*, not images:
|
|
582
|
+
|
|
583
|
+
* `getScreenCTM()` on a `<svg>` with a given `viewBox`/size → the exact
|
|
584
|
+
viewBox transform `VectorViewBox` must reproduce.
|
|
585
|
+
* `getBBox()` on a `<path>` → geometry after arc/smooth-curve resolution.
|
|
586
|
+
* `getPointAtLength()` sampled along `getTotalLength()` → a curve fingerprint
|
|
587
|
+
that catches a wrong `Q`→cubic conversion (§2.3) immediately.
|
|
588
|
+
|
|
589
|
+
This fits the existing snapshot design directly: same recorder shape, same
|
|
590
|
+
committed-fixture gate, no image diffing, no fuzzy comparison. `playwright-core`
|
|
591
|
+
is already a root dependency and Chromium is already pinned by path in the
|
|
592
|
+
recorder.
|
|
593
|
+
|
|
594
|
+
### 7.3 The W3C SVG 1.1 test suite — later, and curated
|
|
595
|
+
|
|
596
|
+
Tempting, but not the right backbone right now:
|
|
597
|
+
|
|
598
|
+
* It is **reference-PNG based**, so it needs a working rasterizer plus fuzzy
|
|
599
|
+
image comparison — i.e. it cannot help until after Stage 2, and it brings a
|
|
600
|
+
whole image-diff tolerance problem with it.
|
|
601
|
+
* Most of it exercises features deliberately outside the profile (text, fonts,
|
|
602
|
+
filters, masks, animation). Those tests would fail for reasons that are not
|
|
603
|
+
defects, so raw pass rate is meaningless.
|
|
604
|
+
* It carries its own licensing/vendoring question for the repo.
|
|
605
|
+
|
|
606
|
+
If it is adopted later, adopt it as a **curated subset with an explicit
|
|
607
|
+
out-of-profile expected-fail list**, so the number the suite reports means
|
|
608
|
+
something.
|
|
609
|
+
|
|
610
|
+
### 7.4 Conformance also defines what "unsupported" must look like
|
|
611
|
+
|
|
612
|
+
A restricted profile is only coherent if *not implemented* is a defined,
|
|
613
|
+
detectable state rather than silently wrong output. So the profile in §8 should
|
|
614
|
+
be enforced with the same rigour as the features: for anything outside it, the
|
|
615
|
+
assertion is that a **diagnostic is produced**, not that something renders. That
|
|
616
|
+
is what makes it safe to ship a small profile and grow it.
|
|
617
|
+
|
|
618
|
+
## 8. Profile for the first SVG version
|
|
619
|
+
|
|
620
|
+
Agreed with the sketch's subset. Explicitly out, and enforced by the parser
|
|
621
|
+
rather than by convention:
|
|
622
|
+
|
|
623
|
+
```
|
|
624
|
+
script animation foreignObject
|
|
625
|
+
external CSS filters masks
|
|
626
|
+
external refs <use> href to external documents
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Additions worth making explicit, because imported SVG is untrusted input:
|
|
630
|
+
|
|
631
|
+
* **No XML entity expansion.** Simply do not implement entities — that removes
|
|
632
|
+
billion-laughs and XXE by construction rather than by mitigation.
|
|
633
|
+
* **No network or filesystem access from inside the parser.** `href` /
|
|
634
|
+
`xlink:href` to anything other than a same-document fragment is dropped.
|
|
635
|
+
* **Hard caps** on node count, nesting depth and path-command count, reported as
|
|
636
|
+
a parse error rather than an OOM.
|
|
637
|
+
* **`<text>` must warn, not silently drop.** Illustrator and Figma exports very
|
|
638
|
+
often contain `<text>`, and a logo that quietly loses its wordmark is the most
|
|
639
|
+
likely "SVG support is broken" report. Detect it and say so.
|
|
640
|
+
|
|
641
|
+
## 9. Summary
|
|
642
|
+
|
|
643
|
+
The plan's core decision — *general Vector IR, SVG as an importer* — is correct
|
|
644
|
+
and is the right next big EVG feature. Three adjustments to the sketch:
|
|
645
|
+
|
|
646
|
+
1. **It is half-built already.** `SVGPathParser` + `PathCommand` exist with three
|
|
647
|
+
consumers. The job is consolidation and correctness, not new construction.
|
|
648
|
+
2. **The immediate win is agreement, not capability.** PDF and HTML currently
|
|
649
|
+
disagree about `viewBox`, and the quadratic conversion in PDF is wrong. Stage 0
|
|
650
|
+
is small and fixes visible defects.
|
|
651
|
+
3. **The raster rasterizer already exists inside `RasterText`.** Extracting it is
|
|
652
|
+
cheaper than writing one and pays out in three places. The genuinely new work
|
|
653
|
+
on the raster side is stroking, not filling.
|
|
654
|
+
|
|
655
|
+
Conformance backing (§7): follow SVG 1.1 semantics exactly inside the profile,
|
|
656
|
+
starting with spec-derived unit assertions on the pure functions, and extend the
|
|
657
|
+
existing browser-parity snapshot harness to vector geometry. The W3C test suite
|
|
658
|
+
is a later, curated addition — not the backbone.
|
|
659
|
+
|
|
660
|
+
Gradients are explicitly deferred (§6): they are the one construct where
|
|
661
|
+
cross-target parity cannot be promised — print pipelines rasterize them at their
|
|
662
|
+
own discretion — and the existing implementations already disagree with each
|
|
663
|
+
other. Flat colour first; revisit once `VectorRasterizer` makes gradient paint a
|
|
664
|
+
property of one shared fill stage rather than three separate ones.
|