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,835 @@
|
|
|
1
|
+
# EVG Layout Engine Known Issues
|
|
2
|
+
|
|
3
|
+
## Issue #1: Text/Label Elements Don't Auto-Size Width Based on Content
|
|
4
|
+
|
|
5
|
+
**Status:** Resolved
|
|
6
|
+
**Severity:** Medium
|
|
7
|
+
**Found:** December 19, 2025
|
|
8
|
+
**Resolved:** August 14, 2026
|
|
9
|
+
**Component:** EVGLayout.rgr
|
|
10
|
+
|
|
11
|
+
### Resolution
|
|
12
|
+
|
|
13
|
+
Text leaf nodes shrink-wrap to their measured content in `EVGLayout.layoutElement`
|
|
14
|
+
and `estimateChildWidth`, so a `<Label>` no longer claims the full parent width
|
|
15
|
+
in a `flexDirection="row"`.
|
|
16
|
+
|
|
17
|
+
Two follow-ups were needed before the fix could be trusted, both landed with the
|
|
18
|
+
font-correctness work:
|
|
19
|
+
|
|
20
|
+
- The measurement passed a hardcoded `"Helvetica"` for every string, so the
|
|
21
|
+
shrink-wrapped width was right in shape but wrong in size for any other face.
|
|
22
|
+
It now measures with the element's own `fontFamily`.
|
|
23
|
+
- The measurement itself was a `fontSize * 0.55` guess. Layout now measures with
|
|
24
|
+
the same TTF the output embeds, checked against a browser in
|
|
25
|
+
`gallery/pdf_writer/test/font_parity.js`.
|
|
26
|
+
|
|
27
|
+
Covered by `evg_test`: "text label shrink-wraps (not full width)",
|
|
28
|
+
"sibling stays on the same row", "sibling sits right after the label", and
|
|
29
|
+
"layout measured with the element's family". The original report below is kept
|
|
30
|
+
for history and no longer describes current behavior.
|
|
31
|
+
|
|
32
|
+
### Description
|
|
33
|
+
|
|
34
|
+
Text and Label elements in the EVG layout engine do not calculate their width based on text content. Instead, they default to taking the full parent width, which causes layout problems when using `flexDirection="row"`.
|
|
35
|
+
|
|
36
|
+
### Problem
|
|
37
|
+
|
|
38
|
+
When laying out elements horizontally with `flexDirection="row"`, text elements without an explicit width will:
|
|
39
|
+
|
|
40
|
+
1. Take the full available parent width
|
|
41
|
+
2. Push subsequent elements to the next row
|
|
42
|
+
3. Ignore their actual text content width
|
|
43
|
+
|
|
44
|
+
This makes it impossible to have inline layouts like:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<View flexDirection="row">
|
|
48
|
+
<Label>Some text</Label> {/* Takes full width */}
|
|
49
|
+
<Image src="icon.jpg" /> {/* Wraps to next line */}
|
|
50
|
+
</View>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Root Cause
|
|
54
|
+
|
|
55
|
+
In `EVGLayout.rgr`, the `layoutElement` function (lines 150-172) only calculates **height** for text elements based on text wrapping and line count. It does not calculate **width** based on text content.
|
|
56
|
+
|
|
57
|
+
Additionally, in `layoutChildren` (lines 240-245), when an element has:
|
|
58
|
+
|
|
59
|
+
- No explicit width (`width.isSet == false`)
|
|
60
|
+
- No flex value (`flex == 0`)
|
|
61
|
+
|
|
62
|
+
The layout engine treats it as "taking full width":
|
|
63
|
+
|
|
64
|
+
```ranger
|
|
65
|
+
; No width and no flex - treat as taking full width (will wrap)
|
|
66
|
+
fixedWidth = fixedWidth + innerWidth + c.box.marginLeftPx + c.box.marginRightPx
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This assumption works for block-level elements but fails for inline text.
|
|
70
|
+
|
|
71
|
+
### Current Workaround
|
|
72
|
+
|
|
73
|
+
## Issue #2: JSX Tokenizer Creates Separate Tokens for Words
|
|
74
|
+
|
|
75
|
+
**Status:** Resolved (December 19, 2025)
|
|
76
|
+
**Severity:** High
|
|
77
|
+
**Component:** ts_parser_simple.rgr, ComponentEngine.rgr
|
|
78
|
+
|
|
79
|
+
### Description
|
|
80
|
+
|
|
81
|
+
The JSX text tokenizer was splitting multi-word text content into separate tokens (one per word), losing whitespace between words. This caused text like "Showcasing custom fonts" to render as "Showcasingcustomfonts".
|
|
82
|
+
|
|
83
|
+
### Root Cause
|
|
84
|
+
|
|
85
|
+
The TypeScript parser's lexer tokenizes based on whitespace, creating separate tokens for each word. When the JSX parser processes text content, each word becomes a separate `JSXText` node without the original spacing.
|
|
86
|
+
|
|
87
|
+
The `ComponentEngine.evaluateTextContent` function was normalizing and trimming each individual token before concatenating, which removed the information about word boundaries.
|
|
88
|
+
|
|
89
|
+
### Solution
|
|
90
|
+
|
|
91
|
+
Modified `ComponentEngine.evaluateTextContent` to:
|
|
92
|
+
|
|
93
|
+
1. Accumulate all JSXText tokens with spaces between them (since they were originally separated by whitespace)
|
|
94
|
+
2. Concatenate raw token values first: `result = result + " " + rawText`
|
|
95
|
+
3. Apply normalization and trimming to the **complete** accumulated text
|
|
96
|
+
4. This preserves word boundaries while still handling newlines and extra whitespace correctly
|
|
97
|
+
|
|
98
|
+
**Fixed in:** ComponentEngine.rgr, `evaluateTextContent` function (lines 738-780)
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Issue #3: SVG Path Elements Not Implemented
|
|
103
|
+
|
|
104
|
+
**Status:** Open
|
|
105
|
+
**Severity:** Medium
|
|
106
|
+
**Found:** December 19, 2025 (via test_features.tsx)
|
|
107
|
+
**Component:** ComponentEngine.rgr, EVGElement.rgr, EVGPDFRenderer.rgr
|
|
108
|
+
|
|
109
|
+
### Description
|
|
110
|
+
|
|
111
|
+
SVG `<Path>` elements are not implemented in the EVG component system. When used in TSX files, they are treated as unknown components and rendered as empty `<div>` elements.
|
|
112
|
+
|
|
113
|
+
### Problem
|
|
114
|
+
|
|
115
|
+
When attempting to use SVG paths for icons or vector graphics:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<Path
|
|
119
|
+
d="M10,6.5c-2.2,0-4,1.8-4,4s1.8,4,4,4s4-1.8,4-4S12.2,6.5,10,6.5"
|
|
120
|
+
width="80"
|
|
121
|
+
height="80"
|
|
122
|
+
viewBox="0 0 20 20"
|
|
123
|
+
backgroundColor="#27ae60"
|
|
124
|
+
/>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The system outputs:
|
|
128
|
+
|
|
129
|
+
- **Warning:** "Unknown component: Path"
|
|
130
|
+
- Renders as: `<div>` with no visual output
|
|
131
|
+
|
|
132
|
+
### Impact
|
|
133
|
+
|
|
134
|
+
- Cannot render vector icons or SVG graphics
|
|
135
|
+
- Must use raster images instead (PNG/JPG)
|
|
136
|
+
- Limits design flexibility for scalable icons and shapes
|
|
137
|
+
|
|
138
|
+
### Required Implementation
|
|
139
|
+
|
|
140
|
+
1. **Add Path to evg_types.tsx:**
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
export function Path(props: PathProps): JSX.Element;
|
|
144
|
+
|
|
145
|
+
interface PathProps extends EVGStyle {
|
|
146
|
+
d: string; // SVG path data
|
|
147
|
+
svgPath?: string; // Alias for d
|
|
148
|
+
viewBox?: string; // ViewBox for scaling
|
|
149
|
+
fill?: Color; // Fill color
|
|
150
|
+
stroke?: Color; // Stroke color
|
|
151
|
+
strokeWidth?: number; // Stroke width
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
2. **Update ComponentEngine.rgr:** Add "path" to known element types (currently recognizes: View, Label, Image, Section, Page, Print)
|
|
156
|
+
|
|
157
|
+
3. **Implement path rendering in EVGPDFRenderer.rgr:** Parse SVG path commands (M, L, C, Z, etc.) and render using PDF drawing primitives
|
|
158
|
+
|
|
159
|
+
4. **Add path rendering to EVGElement.rgr:** Store path data (d attribute, viewBox) as element properties
|
|
160
|
+
|
|
161
|
+
### Workaround
|
|
162
|
+
|
|
163
|
+
Use raster image formats (PNG, JPG) for icons and graphics instead of vector SVG paths.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
Explicitly set a width percentage or fixed width on text elements in row layouts:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<View flexDirection="row">
|
|
171
|
+
<Label width="80%">Some text</Label>
|
|
172
|
+
<Image src="icon.jpg" width={20} height={20} />
|
|
173
|
+
</View>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Or use flex values:
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
<View flexDirection="row">
|
|
180
|
+
<Label flex={1}>Some text</Label>
|
|
181
|
+
<Image src="icon.jpg" width={20} height={20} />
|
|
182
|
+
</View>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Proper Solution
|
|
186
|
+
|
|
187
|
+
Text/Label elements should calculate their intrinsic width based on:
|
|
188
|
+
|
|
189
|
+
1. Text content length
|
|
190
|
+
2. Font size and family
|
|
191
|
+
3. Font metrics from the text measurer
|
|
192
|
+
|
|
193
|
+
The layout algorithm should:
|
|
194
|
+
|
|
195
|
+
1. Check if element is a text/label type
|
|
196
|
+
2. If no explicit width is set, measure the text content
|
|
197
|
+
3. Use the measured width instead of defaulting to parent width
|
|
198
|
+
4. Still respect `maxWidth` constraints for wrapping
|
|
199
|
+
|
|
200
|
+
### Suggested Code Changes
|
|
201
|
+
|
|
202
|
+
In `EVGLayout.rgr`, around line 290-300, add text width measurement:
|
|
203
|
+
|
|
204
|
+
```ranger
|
|
205
|
+
; Calculate child dimensions
|
|
206
|
+
def childWidth:double innerWidth
|
|
207
|
+
if child.width.isSet {
|
|
208
|
+
childWidth = child.width.pixels
|
|
209
|
+
} {
|
|
210
|
+
; NEW: For text elements, measure content width
|
|
211
|
+
if ((child.tagName == "text") || (child.tagName == "span")) {
|
|
212
|
+
def fontSize:double child.inheritedFontSize
|
|
213
|
+
if child.fontSize.isSet {
|
|
214
|
+
fontSize = child.fontSize.pixels
|
|
215
|
+
}
|
|
216
|
+
if (fontSize <= 0.0) {
|
|
217
|
+
fontSize = 14.0
|
|
218
|
+
}
|
|
219
|
+
def fontFamily:string child.inheritedFontFamily
|
|
220
|
+
childWidth = (measurer.measureTextWidth(child.textContent fontFamily fontSize))
|
|
221
|
+
; Add some padding for safety
|
|
222
|
+
childWidth = childWidth + 4.0
|
|
223
|
+
} {
|
|
224
|
+
; Check if this child has a calculated flex width
|
|
225
|
+
if (child.calculatedFlexWidth > 0.0) {
|
|
226
|
+
childWidth = child.calculatedFlexWidth
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Impact
|
|
233
|
+
|
|
234
|
+
- **High**: Affects all horizontal layouts with text
|
|
235
|
+
- **Workaround Available**: Yes (explicit width or flex)
|
|
236
|
+
- **Breaking Change**: Potentially, as existing layouts may rely on current behavior
|
|
237
|
+
|
|
238
|
+
### Related Code
|
|
239
|
+
|
|
240
|
+
- `lib/evg/EVGLayout.rgr` - Lines 200-400 (layoutChildren function)
|
|
241
|
+
- `lib/evg/EVGTextMeasurer.rgr` - Text measurement utilities
|
|
242
|
+
- `gallery/pdf_writer/FontManager.rgr` - Font metrics for accurate measurement
|
|
243
|
+
|
|
244
|
+
### Test Case
|
|
245
|
+
|
|
246
|
+
See `gallery/pdf_writer/components/ListItem.tsx` for a component that demonstrates this issue.
|
|
247
|
+
|
|
248
|
+
### Notes
|
|
249
|
+
|
|
250
|
+
- The current behavior may be intentional for some use cases (e.g., full-width text blocks)
|
|
251
|
+
- A proper fix should distinguish between "inline" and "block" text elements
|
|
252
|
+
- Consider adding a `display: inline` or similar property to control this behavior
|
|
253
|
+
- Text measurement requires access to font metrics (FontManager in PDF writer context)
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Issue #4: The scene binary's record width was agreed in advance, not published
|
|
258
|
+
|
|
259
|
+
**Status:** Resolved
|
|
260
|
+
**Severity:** High — every field after the first command read from the wrong offset
|
|
261
|
+
**Found:** August 30, 2026 (in CI, on master)
|
|
262
|
+
**Resolved:** August 30, 2026
|
|
263
|
+
**Component:** `EVGDisplayList.rgr`, `gallery/pptx/web/host/pptx-host.mjs`
|
|
264
|
+
|
|
265
|
+
### What happened
|
|
266
|
+
|
|
267
|
+
`EVGDisplayList` publishes a frame as three `Int32Array`s: one fixed-size record
|
|
268
|
+
per draw command, then the ring coordinates and the strings those records index
|
|
269
|
+
into. It is the fast path — the JSON one costs 1.5 MB of text a frame — and it
|
|
270
|
+
is POSITIONAL. Nothing in it says how wide a record is.
|
|
271
|
+
|
|
272
|
+
The record grew from 24 ints to 26 when `transform: rotate()` needed an origin
|
|
273
|
+
to turn about. `EVGDisplayList.stride()` was updated, and its comment even says
|
|
274
|
+
"read it through this function and never inline the number". The decoder on the
|
|
275
|
+
other side of the bridge had inlined it:
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
export const SCENE_STRIDE = 24; // pptx-host.mjs
|
|
279
|
+
const b = i * SCENE_STRIDE;
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
So from the second command on, every field was read two ints early. Colours
|
|
283
|
+
became coordinates, coordinates became flags, and the frame was nonsense that
|
|
284
|
+
still *looked* like numbers. Nothing threw until a ring count read out of
|
|
285
|
+
somebody's colour reached `new Array(eCount)`:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
RangeError: Invalid array length
|
|
289
|
+
at decodeScene (pptx-host.mjs:104)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
— in the WebAssembly parity job, which needs an Emscripten toolchain, runs late
|
|
293
|
+
in the deploy workflow, and points a hundred fields downstream of the mistake.
|
|
294
|
+
|
|
295
|
+
### Resolution
|
|
296
|
+
|
|
297
|
+
**The shape is derived from the bytes, not agreed in advance.** `cmds` is
|
|
298
|
+
allocated as exactly `count * stride`, so `sceneStride(bin)` recovers the number
|
|
299
|
+
the writer used by dividing. That answer cannot drift, and it needs no new
|
|
300
|
+
export — which matters, because three producers publish this frame by three
|
|
301
|
+
different routes (the Ranger engine, the Emscripten build and the Rust one) and
|
|
302
|
+
only one of them is in a position to export a constant.
|
|
303
|
+
|
|
304
|
+
What the decoder now names is the FLOOR: `SCENE_FIELDS_READ = 23`, the number
|
|
305
|
+
of fields it actually reads. A record wider than that is fine — the extra
|
|
306
|
+
fields are not its business — and one narrower throws with both numbers in the
|
|
307
|
+
message.
|
|
308
|
+
|
|
309
|
+
### Why it was not caught sooner
|
|
310
|
+
|
|
311
|
+
The claim that the two paths agree was written in a comment and checked
|
|
312
|
+
nowhere. `gallery/pptx/web/host/scene-binary-check.mjs` now asks the engine for
|
|
313
|
+
both the JSON and the binary, for every slide of every fixture, and compares
|
|
314
|
+
them field by field: 37 decks, 45 slides, 8,658 commands, no browser, no
|
|
315
|
+
toolchain, one second. It is in `scripts/run-gallery-editor-tests.sh` and runs
|
|
316
|
+
in CI *before* the WebAssembly build rather than after it.
|
|
317
|
+
|
|
318
|
+
Two other checks would also have caught this and neither was wired up: the
|
|
319
|
+
standalone smoke test (`npm run pptx:web:test`) fails outright, and the frame
|
|
320
|
+
is visibly empty in the playground. Both were reachable the whole time.
|
|
321
|
+
|
|
322
|
+
### The general lesson
|
|
323
|
+
|
|
324
|
+
A positional binary format needs its shape carried with it or derivable from
|
|
325
|
+
it. "Both sides know the layout" is not a property anything enforces, and the
|
|
326
|
+
failure mode is not a crash at the boundary — it is plausible-looking garbage
|
|
327
|
+
that surfaces somewhere unrecognisable.
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Issue #5: `backdrop-filter` sampled the page, not the element
|
|
333
|
+
|
|
334
|
+
**Status:** Resolved
|
|
335
|
+
**Severity:** Medium — a scrim showed the page bleeding through its own border
|
|
336
|
+
**Found:** August 30, 2026, while implementing it
|
|
337
|
+
**Resolved:** the same day
|
|
338
|
+
**Component:** `lib/evg/gl/evg-webgl.js`
|
|
339
|
+
|
|
340
|
+
### What happened
|
|
341
|
+
|
|
342
|
+
The first implementation grew the copied region by the kernel's reach and
|
|
343
|
+
blurred that, on the reading that `backdrop-filter` samples past the element's
|
|
344
|
+
edge. The evidence for it was a flat grey behind a pane coming out flat to the
|
|
345
|
+
border, with no rim.
|
|
346
|
+
|
|
347
|
+
That is not evidence. **A uniform field is uniform under either rule** — edge
|
|
348
|
+
clamping and sampling-past give the same answer when there is nothing outside
|
|
349
|
+
worth sampling. The observation ruled out only a third possibility, that edge
|
|
350
|
+
samples read transparent and the border fades.
|
|
351
|
+
|
|
352
|
+
The case that decides is a feature that straddles the border. White page, black
|
|
353
|
+
stripe starting exactly at the pane's left edge:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
x: 95 96 97 98 99 | 100 101 102 ...
|
|
357
|
+
browser 255 255 255 255 255| 0 0 0
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
No ramp at the border at all — while the same black/white boundary a hundred
|
|
361
|
+
pixels further in, inside the pane, gets the full smooth curve. Outside content
|
|
362
|
+
does not enter.
|
|
363
|
+
|
|
364
|
+
### Resolution
|
|
365
|
+
|
|
366
|
+
Copy exactly the element's box and let `CLAMP_TO_EDGE` do the rest, which is
|
|
367
|
+
the measured rule expressed in one place.
|
|
368
|
+
|
|
369
|
+
### The general lesson
|
|
370
|
+
|
|
371
|
+
Three checks passed against the wrong implementation, and all three were scenes
|
|
372
|
+
where the backdrop did not change across the element's border. A test that
|
|
373
|
+
cannot distinguish the two candidate rules is not weak evidence for one of
|
|
374
|
+
them; it is no evidence at all. Before trusting a case, ask what the WRONG
|
|
375
|
+
implementation would print — and if the answer is "the same thing", the case is
|
|
376
|
+
not about the question.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Issue #6: a text run's reported width changes between the first frame and the rest
|
|
381
|
+
|
|
382
|
+
**Status:** open, pre-existing, cosmetic in the display list only.
|
|
383
|
+
|
|
384
|
+
Found while proving that moving `TreeDemo`'s rows onto component instances
|
|
385
|
+
changed nothing observable. It did not — but the display list on frame 7 is not
|
|
386
|
+
the display list on frame 1, and that is true with or without the change.
|
|
387
|
+
|
|
388
|
+
Two text commands in the tree demo differ between the first paint and any later
|
|
389
|
+
one:
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
frame 1 {"k":3,"x":105.14,"y":261,"w":58.1, …,"text":"Jane Doe"}
|
|
393
|
+
frame 7 {"k":3,"x":105.57,"y":261,"w":340, …,"text":"Jane Doe"}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`w` goes from the MEASURED run width (58.1) to the label's declared box width
|
|
397
|
+
(340), and `x` shifts by 0.43px. Only the deepest rows are affected — the ones
|
|
398
|
+
whose label sits after an indent spacer whose width is set on the element
|
|
399
|
+
rather than by the sheet.
|
|
400
|
+
|
|
401
|
+
Nothing on screen moves: `k:3` is a text draw and the painter positions the run
|
|
402
|
+
from `x` and the glyphs, not from `w`. So this is a display-list fidelity bug
|
|
403
|
+
rather than a rendering one, and it was invisible until something compared two
|
|
404
|
+
frames of the same state.
|
|
405
|
+
|
|
406
|
+
### Why it is recorded rather than fixed here
|
|
407
|
+
|
|
408
|
+
It is not the component work's to fix, and it was verified as pre-existing by
|
|
409
|
+
running the identical probe on both sides of that change — same two commands,
|
|
410
|
+
same numbers. Fixing it means understanding why a second layout pass over the
|
|
411
|
+
same tree resolves the label's width differently, which is `EVGLayout`'s
|
|
412
|
+
business and wants its own measurement.
|
|
413
|
+
|
|
414
|
+
### The general lesson
|
|
415
|
+
|
|
416
|
+
"Nothing changed" is only checkable if the thing you compare is stable to begin
|
|
417
|
+
with. A baseline captured on frame 1 and a candidate settled over six frames
|
|
418
|
+
are not the same experiment, and the first version of that comparison reported a
|
|
419
|
+
difference my change had not caused. Compare like with like, and when a
|
|
420
|
+
comparison fails, check the baseline before the change.
|
|
421
|
+
|
|
422
|
+
## Issue #7: shrink-to-fit text can wrap inside the box its own width produced
|
|
423
|
+
|
|
424
|
+
**Status:** Resolved (September 5, 2026)
|
|
425
|
+
**Severity:** Low (cosmetic; a label that fits is drawn on two lines)
|
|
426
|
+
**Found:** September 2, 2026
|
|
427
|
+
**Component:** `EVGLayout.rgr` / `EVGTextEngine.rgr`
|
|
428
|
+
|
|
429
|
+
### Resolution
|
|
430
|
+
|
|
431
|
+
`EVGTextEngine.breakLines` allows a line `EVGTextEngine.fitEpsilon()` — a
|
|
432
|
+
millionth of a pixel — over the width it is breaking into. The error a
|
|
433
|
+
shrink-wrap round trip introduces is about seven parts in a quadrillion, so
|
|
434
|
+
the tolerance is a hundred million times it and a millionth of the smallest
|
|
435
|
+
thing anyone can see: it cannot change a wrap a person asked for, and it can
|
|
436
|
+
only undo a round trip that should have been the identity.
|
|
437
|
+
|
|
438
|
+
Covered by `evg:textbox:check` — "the round trip a shrink-wrapped box makes",
|
|
439
|
+
which sweeps twenty-one strings across eight sizes, eight paddings and four
|
|
440
|
+
border widths, asserts that some of those round trips really do come back
|
|
441
|
+
short (or the check would be proving nothing), and that not one of the 5632
|
|
442
|
+
labels breaks inside its own box. Before the fix, 310 of them did.
|
|
443
|
+
|
|
444
|
+
The report below is kept for history.
|
|
445
|
+
|
|
446
|
+
### What happens
|
|
447
|
+
|
|
448
|
+
A text element with no stated width shrink-wraps: layout measures the run, adds
|
|
449
|
+
the padding and border, and that sum is the box. The line breaker then works
|
|
450
|
+
with the content width, which is that sum *minus* the same padding — and for
|
|
451
|
+
some strings the round trip does not land back on the number it started from,
|
|
452
|
+
so the breaker sees a width a fraction narrower than the text and breaks it.
|
|
453
|
+
|
|
454
|
+
Reproduced in `lib/evg/web/responsive`, whose sidebar is six labels in a
|
|
455
|
+
column. Five sit on one line and `Display list` does not:
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
measure 'Display list' = 62.12453 'Text engine' = 67.93241
|
|
459
|
+
item 'Display list' w=86.12453 h=47.9 <- 24px padding, two lines
|
|
460
|
+
item 'Text engine' w=91.93241 h=32.95 <- 24px padding, one line
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Both boxes are exactly `measured + 24`. Both are therefore exactly on the
|
|
464
|
+
threshold. One wraps and the other does not, which is what says this is a
|
|
465
|
+
floating-point artifact of `(w + pad) - pad` and not a measurement disagreement:
|
|
466
|
+
the run measurement and the sum of the per-word measurements agree to the last
|
|
467
|
+
digit for both strings.
|
|
468
|
+
|
|
469
|
+
### Why it is recorded rather than fixed here
|
|
470
|
+
|
|
471
|
+
The fix belongs in the line breaker — a shrink-wrapped box should not be able to
|
|
472
|
+
break its own content, so the comparison there wants an epsilon (or the content
|
|
473
|
+
width wants to be carried alongside the box width rather than recomputed from
|
|
474
|
+
it). Either is a change to how every EVG document breaks lines, and it needs
|
|
475
|
+
its own measurement against the conformance oracles before it lands.
|
|
476
|
+
|
|
477
|
+
### Workaround
|
|
478
|
+
|
|
479
|
+
State a `min-width`, which takes the box off the threshold. The responsive
|
|
480
|
+
demo's `.nav-item` does exactly that, and says so.
|
|
481
|
+
|
|
482
|
+
## Issue #8: a flex row wrapped because of its own arithmetic
|
|
483
|
+
|
|
484
|
+
**Status:** Resolved
|
|
485
|
+
**Severity:** High (visible flicker while a window is resized)
|
|
486
|
+
**Found:** September 2, 2026
|
|
487
|
+
**Resolved:** September 2, 2026
|
|
488
|
+
**Component:** `EVGLayout.rgr`
|
|
489
|
+
|
|
490
|
+
### Resolution
|
|
491
|
+
|
|
492
|
+
The row-wrap test in `EVGLayout.layoutFlexChildren` compares the child's total
|
|
493
|
+
width against the space left on the line. It now allows a hundredth of a pixel
|
|
494
|
+
of overflow before it wraps. `lib/evg/EVGFlexWrapTest.rgr`
|
|
495
|
+
(`npm run evg:flexwrap:test`) sweeps a 240px sidebar beside a `flex: 1` panel
|
|
496
|
+
across every width from 400 to 1800 in quarter-pixel steps and asserts the two
|
|
497
|
+
stay on one line — and, in the same file, that a row which genuinely does not
|
|
498
|
+
fit still wraps and that `flex-wrap: nowrap` still means nowrap. Without the
|
|
499
|
+
tolerance the first two of those five checks fail.
|
|
500
|
+
|
|
501
|
+
`lib/evg/web/responsive` carries the same sweep for the page the defect was
|
|
502
|
+
found on.
|
|
503
|
+
|
|
504
|
+
### What happened
|
|
505
|
+
|
|
506
|
+
The commonest two-column layout there is:
|
|
507
|
+
|
|
508
|
+
```css
|
|
509
|
+
.body { display: flex; gap: 24px } /* wrap allowed — EVG's default */
|
|
510
|
+
.side { width: 240px }
|
|
511
|
+
.main { flex: 1 }
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
`.main`'s width is computed **by the layout** out of the same line it is then
|
|
515
|
+
measured against: the flex pass takes the inner width, subtracts the sidebar and
|
|
516
|
+
the gap, and hands back the remainder. Adding those three back up is not
|
|
517
|
+
guaranteed to return the number they were subtracted from — in binary floating
|
|
518
|
+
point `(a - b - c) + b + c` can land one ulp above `a` — and a bare `>` then
|
|
519
|
+
reads the row as too wide for itself.
|
|
520
|
+
|
|
521
|
+
At a window width of 823px: `4vw` padding either side leaves an inner width of
|
|
522
|
+
757.16, the sidebar is 240, the gap 24, and `.main` came out 493.15999999999997.
|
|
523
|
+
The three sum to 757.1600000000001.
|
|
524
|
+
|
|
525
|
+
### Why it mattered more than one pixel
|
|
526
|
+
|
|
527
|
+
The failing widths are **scattered**, not a band. Of the 680 integer widths
|
|
528
|
+
between 821 and 1500, 127 wrapped — 823, 826, 836, 842, 845, 848, 851, 861 …
|
|
529
|
+
So dragging a window edge did not cross a threshold once; it dropped the content
|
|
530
|
+
below the sidebar and pulled it back up again every few pixels, which reads as a
|
|
531
|
+
flickering page rather than as a layout decision.
|
|
532
|
+
|
|
533
|
+
### The general lesson
|
|
534
|
+
|
|
535
|
+
Same root as issue #7, one stage further up: a number this engine **derived from
|
|
536
|
+
a bound** must not then be tested against that bound with an exact comparison.
|
|
537
|
+
Subtraction and addition do not cancel in floating point, so any "does what I
|
|
538
|
+
just computed still fit in what I computed it from" test needs a tolerance —
|
|
539
|
+
sized below what a painter can draw and above the error being cancelled.
|
|
540
|
+
|
|
541
|
+
## Issue #9: `line-height` written as a length was read as a multiplier
|
|
542
|
+
|
|
543
|
+
**Status:** Resolved (September 5, 2026)
|
|
544
|
+
**Severity:** High (text drawn far outside its own box)
|
|
545
|
+
**Found:** September 5, 2026 — reported as "vertical align sometimes goes to
|
|
546
|
+
the bottom", from a phone screenshot of a duration pill
|
|
547
|
+
**Component:** `EVGElement.rgr` / `EVGLayout.rgr` / `EVGDisplayList.rgr`
|
|
548
|
+
|
|
549
|
+
### What happened
|
|
550
|
+
|
|
551
|
+
CSS gives `line-height` three ways to be written and keeps them apart:
|
|
552
|
+
`normal` is the face's own line box, a NUMBER is that many times the font
|
|
553
|
+
size, and a LENGTH is itself. `EVGElement` held one double and put every
|
|
554
|
+
value through `to_double`, which stops at the first character it cannot use —
|
|
555
|
+
so `24px` came back `24` and was used as a multiplier.
|
|
556
|
+
|
|
557
|
+
Measured, on a 12px pill with `height: 20px` and `line-height: 16px`:
|
|
558
|
+
|
|
559
|
+
```
|
|
560
|
+
before line box 192px baseline 102.2 — the text drawn 82px BELOW the pill
|
|
561
|
+
after line box 16px baseline 14.2 — inside it
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
A line box twelve times too tall does not look twelve times too tall, because
|
|
565
|
+
the painter puts the baseline a half-leading below its top: it looks like a
|
|
566
|
+
line that has fallen to the bottom of everything near it, and only in the
|
|
567
|
+
places whose stylesheet writes a length. Which is what "sometimes" meant.
|
|
568
|
+
|
|
569
|
+
### The fix
|
|
570
|
+
|
|
571
|
+
`EVGElement.lineHeightUnit` holds a stated LENGTH as an `EVGUnit`, beside the
|
|
572
|
+
number; `EVGElement.lineBoxFor` is the single place that tells the three
|
|
573
|
+
apart, and both the layout (which reserves the height) and the display list
|
|
574
|
+
(which steps the lines) call it rather than each computing their own. A
|
|
575
|
+
percentage resolves against the element's own font size — CSS 2.1 §10.8.1,
|
|
576
|
+
the one percentage in the box model that does not look at the containing
|
|
577
|
+
block — `em` against its own size and `rem` against the root's.
|
|
578
|
+
|
|
579
|
+
Covered by `evg:textbox:check`, "line-height as a number, a length and a
|
|
580
|
+
percentage": eight cases across `normal`, unset, `1.5`, `150%`, `24px`,
|
|
581
|
+
`2em`, `1.5rem` and `0.8`.
|
|
582
|
+
|
|
583
|
+
## Issue #10: a text element drew its text on top of its own border
|
|
584
|
+
|
|
585
|
+
**Status:** Resolved (September 5, 2026)
|
|
586
|
+
**Severity:** Medium
|
|
587
|
+
**Found:** September 5, 2026, while measuring the above
|
|
588
|
+
**Component:** `EVGDisplayList.rgr` / `EVGLayout.rgr`
|
|
589
|
+
|
|
590
|
+
### What happened
|
|
591
|
+
|
|
592
|
+
`EVGDisplayList` started a text run at `x + paddingLeft`, `y + paddingTop` —
|
|
593
|
+
the border was missing from both. But the width it broke the run into is
|
|
594
|
+
`EVGBox.getInnerWidth`, which takes the border off, and the layout's own
|
|
595
|
+
`calculatedBaseline` is measured from the border edge and adds border AND
|
|
596
|
+
padding. So a text element with a border drew its text one border-width high
|
|
597
|
+
and one left, over its own top border, and measured against a width that
|
|
598
|
+
assumed it started inside it.
|
|
599
|
+
|
|
600
|
+
EVG disagreeing with EVG, in three places that each looked right alone. The
|
|
601
|
+
layout's wrap width had the mirror-image of the same slip: it subtracted the
|
|
602
|
+
paddings and not the border, so a bordered element counted its lines at one
|
|
603
|
+
width and was broken at another — the height reserved for a wrap the layout
|
|
604
|
+
imagined, and the painter drawing a different one.
|
|
605
|
+
|
|
606
|
+
Covered by `evg:textbox:check`, "a border on a text element", which asserts
|
|
607
|
+
the run starts inside both and — the one that matters — that the layout's
|
|
608
|
+
baseline and the painter's are the same point.
|
|
609
|
+
|
|
610
|
+
## Issue #11: a flex container's own text ignored `align-items`
|
|
611
|
+
|
|
612
|
+
**Status:** Resolved (September 5, 2026)
|
|
613
|
+
**Severity:** Medium (a pill's label sits against its top edge)
|
|
614
|
+
**Found:** September 5, 2026 — reported as "the minute still shows wrong",
|
|
615
|
+
from a phone screenshot of a `10min` pill
|
|
616
|
+
**Component:** `EVGLayout.rgr` / `EVGElement.rgr` / `EVGDisplayList.rgr`
|
|
617
|
+
|
|
618
|
+
### What happened
|
|
619
|
+
|
|
620
|
+
An element that is a flex container and carries text of its OWN does not lay
|
|
621
|
+
that text out as a block. CSS wraps it in an ANONYMOUS FLEX ITEM, and from
|
|
622
|
+
then on `align-items` and `justify-content` place it like any other item.
|
|
623
|
+
|
|
624
|
+
That is the pill idiom, and it is written that way everywhere:
|
|
625
|
+
|
|
626
|
+
```css
|
|
627
|
+
.pill { display: flex; align-items: center; height: 34px; padding: 0 12px }
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
with the label as the element's own text. EVG placed the line box at the top
|
|
631
|
+
of the content box and never consulted `align-items`. Measured on that pill,
|
|
632
|
+
the space above and below the digits:
|
|
633
|
+
|
|
634
|
+
```
|
|
635
|
+
before above 3.0 below 20.2 the label against the top edge
|
|
636
|
+
after above 11.4 below 11.8 where the same text in a child sits
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
`gallery/realtrainer`'s stylesheet has a hundred and nine rules with
|
|
640
|
+
`align-items: center`, so the shape is not rare.
|
|
641
|
+
|
|
642
|
+
### The fix
|
|
643
|
+
|
|
644
|
+
`EVGLayout` writes `EVGElement.textShiftY` once the box's height is final —
|
|
645
|
+
the offset is a share of the slack and there is no slack until then — and the
|
|
646
|
+
display list adds it to every line. `calculatedBaseline` includes it too, or
|
|
647
|
+
an `align-items: baseline` row around the pill would line the pill up by a
|
|
648
|
+
baseline the painter does not draw at.
|
|
649
|
+
|
|
650
|
+
`align-items` across a row, `justify-content` along a column, and a BLOCK is
|
|
651
|
+
untouched: its line boxes still start at the top of its content box whatever
|
|
652
|
+
`align-items` says.
|
|
653
|
+
|
|
654
|
+
### The check
|
|
655
|
+
|
|
656
|
+
`evg:textbox:check`, "a flex container's own text", asserts the EQUIVALENCE
|
|
657
|
+
rather than any number: the element's own text has to land exactly where the
|
|
658
|
+
same text in a child of the same container lands. A browser cannot tell those
|
|
659
|
+
two apart and neither may EVG. Six alignments, plus that the slack really is
|
|
660
|
+
there to be shared, that the layout's baseline is still the painter's, and
|
|
661
|
+
that a block ignores `align-items`.
|
|
662
|
+
|
|
663
|
+
## Issue #12: the accessibility mirror does not hide the page behind a dialog
|
|
664
|
+
|
|
665
|
+
Open, and NOT fixed here. The drawn half is fixed — see `EVGFocus`, the focus
|
|
666
|
+
trap — and this is the other half, recorded so it is not rediscovered.
|
|
667
|
+
|
|
668
|
+
### What happens
|
|
669
|
+
|
|
670
|
+
`evg-a11y.js` already knows how: `applyModal` looks for a node in the
|
|
671
|
+
accessibility tree that carries `modal` (`aria-modal`), walks up to the
|
|
672
|
+
top-level region holding it, and puts `aria-hidden="true"` on every other
|
|
673
|
+
top-level region. That is the right mechanism and it is written.
|
|
674
|
+
|
|
675
|
+
It never fires in realtrainer, because there is no such node. The element that
|
|
676
|
+
declares `a11yModal` is the overlay — the scrim, which is what covers the page
|
|
677
|
+
— and an element with no role and no name is not published to the tree at all.
|
|
678
|
+
So `tree.nodes.find(n => n.modal)` finds nothing, nothing is hidden, and a
|
|
679
|
+
screen reader walks straight out of an open dialog into the page behind it,
|
|
680
|
+
even though a sighted keyboard user can no longer do that.
|
|
681
|
+
|
|
682
|
+
### Why it is not a one-line fix
|
|
683
|
+
|
|
684
|
+
The node has to exist, which means the overlay or the sheet has to carry a
|
|
685
|
+
role — `dialog` is the right one — and that adds a node to the accessibility
|
|
686
|
+
tree of every screen that has a sheet on it. Those trees are recorded:
|
|
687
|
+
`gallery/realtrainer/traces/*.json` is the Ranger side's own transcript, and
|
|
688
|
+
`traces/reference/*.json` is the app being ported. Adding the role is very
|
|
689
|
+
likely a step TOWARDS the reference — a real dialog there is a
|
|
690
|
+
`<div role="dialog" aria-modal="true">` — but "very likely" is not "checked",
|
|
691
|
+
and the check needs the private frontend the reference recorder runs against.
|
|
692
|
+
|
|
693
|
+
So the shape of the fix is known and the cost is a re-record plus a look at
|
|
694
|
+
the reference diff, which is a machine this repository's CI does not have.
|
|
695
|
+
|
|
696
|
+
### What holds in the meantime
|
|
697
|
+
|
|
698
|
+
The keyboard is trapped (`EVGFocus`, `rt:keys`), Escape closes the topmost
|
|
699
|
+
dialog, and the mirror still reports the dialog's own controls correctly — it
|
|
700
|
+
just also reports the page behind them.
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
## Issue #13: a kept frame moved the page and left the focus ring behind
|
|
705
|
+
|
|
706
|
+
Fixed. Recorded because the first half of the fix was not enough and the
|
|
707
|
+
second half was in a place nobody would look.
|
|
708
|
+
|
|
709
|
+
### What happened
|
|
710
|
+
|
|
711
|
+
The ring is drawn LAST and outside every clip, so that a button inside a panel
|
|
712
|
+
is not half-ringed by the panel's edge. Outside every clip is also outside
|
|
713
|
+
every scroll LAYER — and a host does not re-read the display list for a frame
|
|
714
|
+
that only scrolled. It moves the kept frame by a per-layer offset: `uShift` in
|
|
715
|
+
the WebGL painter, the same arithmetic on the worker page. The ring belonged to
|
|
716
|
+
no layer, so it got no offset: the page moved and the ring stayed.
|
|
717
|
+
|
|
718
|
+
### Why the first fix did not show it
|
|
719
|
+
|
|
720
|
+
`EVGDisplayList.refreshRing` (Issue #12's neighbour, landed earlier) puts the
|
|
721
|
+
ring back against its own box whenever the kept LIST is refreshed, and every
|
|
722
|
+
check that reads `displayListJson()` therefore passes — the list was right all
|
|
723
|
+
along. What was wrong was the FRAME, which is built from the list once and
|
|
724
|
+
then moved by uniforms. A check that reads the list cannot see it; a
|
|
725
|
+
screenshot can.
|
|
726
|
+
|
|
727
|
+
### The fix
|
|
728
|
+
|
|
729
|
+
`ringAround` marks the ring command with the layer its element scrolls in
|
|
730
|
+
(`layerAround`, the innermost layer container that holds it, 1-based as the
|
|
731
|
+
painter numbers them), and the painter reads `layer` off a command that
|
|
732
|
+
carries one instead of taking it from the clip nesting alone. A control that
|
|
733
|
+
scrolls with nothing — a header button — is in layer 0 and stays put, which is
|
|
734
|
+
also checked.
|
|
735
|
+
|
|
736
|
+
### What holds it
|
|
737
|
+
|
|
738
|
+
`rt:keys` — "the ring is round its element, whatever the page is doing": the
|
|
739
|
+
invariant across wheels, a throw moved by the clock, a rebuild under the ring
|
|
740
|
+
and a page turn, plus the two assertions on the layer the ring declares. The
|
|
741
|
+
invariant is what was missing: a delta test ("it moved by 160") passes for a
|
|
742
|
+
ring that lags a frame and for one that leads one.
|
|
743
|
+
|
|
744
|
+
## Issue #11: a gradient drew on the GPU and nowhere else
|
|
745
|
+
|
|
746
|
+
**Status:** Resolved (September 13, 2026)
|
|
747
|
+
**Component:** `evg_png_tool`, `EVGPDFRenderer`, `EVGDisplayList`
|
|
748
|
+
|
|
749
|
+
EVG has two ways to say "gradient" and the painters did not agree on which one
|
|
750
|
+
they read:
|
|
751
|
+
|
|
752
|
+
| | `gradient-from` / `-to` / `-dir` | `background-gradient` (the CSS string) |
|
|
753
|
+
| --- | --- | --- |
|
|
754
|
+
| display list → WebGL, SVG, GL | **yes** | no |
|
|
755
|
+
| raster (PNG) | no | **yes** |
|
|
756
|
+
| PDF | no | **yes** |
|
|
757
|
+
|
|
758
|
+
So a gradient written in the pair — which is the display list's own vocabulary,
|
|
759
|
+
and what `SceneToEVG` emits for a Figma fill — drew on the GPU and was silently
|
|
760
|
+
absent from the image and the print. That is the opposite of the claim this
|
|
761
|
+
engine makes about its targets, and nothing reported it: the element laid out,
|
|
762
|
+
measured and composited exactly as it should, and the fill was simply not
|
|
763
|
+
painted.
|
|
764
|
+
|
|
765
|
+
Found by converting a Figma page with a gradient and looking at the PNG.
|
|
766
|
+
|
|
767
|
+
### The fix, and the trap inside it
|
|
768
|
+
|
|
769
|
+
Both painters now take the pair. The trap is that they do not share an angle
|
|
770
|
+
convention: the rasteriser builds a direction vector from `(cos, sin)` with 0 to
|
|
771
|
+
the right, while the PDF renderer parses CSS where 0 is to the top. One `dir`
|
|
772
|
+
code, two correct angles, and using either one in the other place turns every
|
|
773
|
+
gradient a quarter turn in one target only.
|
|
774
|
+
|
|
775
|
+
Both inverses therefore live in `EVGDisplayList`, beside the `applyGradient`
|
|
776
|
+
that reduces an angle to the code — `rasterAngleOfDir` and `cssAngleOfDir`,
|
|
777
|
+
named so the difference is visible rather than discovered. `EVGJsonTest` checks
|
|
778
|
+
them against that reduction on the same axes rather than trusting two constants
|
|
779
|
+
to stay in step.
|
|
780
|
+
|
|
781
|
+
Cost: one boolean test per element. The GPU path is untouched, since it already
|
|
782
|
+
read the right fields.
|
|
783
|
+
|
|
784
|
+
## Issue #12: the rasteriser knew one image format out of three
|
|
785
|
+
|
|
786
|
+
**Status:** Resolved (September 13, 2026)
|
|
787
|
+
**Component:** `evg_png_tool`, `EVGImageDecode`, `ProgressiveJPEGDecoder`
|
|
788
|
+
|
|
789
|
+
`evg_png_tool` chose between the baseline and progressive JPEG decoders itself
|
|
790
|
+
and had never heard of PNG, so a document with one rendered
|
|
791
|
+
`Not a JPEG file (missing SOI)` and stopped. A Figma archive is full of PNGs.
|
|
792
|
+
|
|
793
|
+
`EVGImageDecode` already sniffed all three formats — the tool was simply not
|
|
794
|
+
asking it — but it had the mirror gap: no progressive branch, because
|
|
795
|
+
`ProgressiveJPEGDecoder` had only `decode(dir, name)` and the sniffing decoder
|
|
796
|
+
has bytes. That one now has `decodeBytes`, and a `quiet` flag for the same
|
|
797
|
+
reason `JPEGDecoder` has one: twenty-four lines of narration per photograph.
|
|
798
|
+
|
|
799
|
+
### The smaller defect underneath
|
|
800
|
+
|
|
801
|
+
`PNGDecoder.decodeBytes` returns a 1x1 image when it gives up, and
|
|
802
|
+
`EVGImageDecode` judged success by the returned buffer. A genuine 1x1 PNG — a
|
|
803
|
+
spacer, a placeholder, the thumbnail inside a `.fig` — was therefore
|
|
804
|
+
indistinguishable from a failure and was rejected. It now asks the decoder what
|
|
805
|
+
its header said: those dimensions stay 0 when nothing parsed, and say 1x1 when
|
|
806
|
+
the file really is that.
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
## #13 A negative resolved size drew mirrored rather than empty — FIXED
|
|
810
|
+
|
|
811
|
+
`width: -40%` is not a width: CSS throws the declaration away. EVG kept the
|
|
812
|
+
number, and the raster painter drew the box mirrored through its own left edge
|
|
813
|
+
— so RealTrainer's progress bar, whose app-side arithmetic produced "week -41
|
|
814
|
+
of 104", came out as a FULL blue bar where an empty one was correct. The same
|
|
815
|
+
document exported to Figma showed nothing there, which is how the disagreement
|
|
816
|
+
was found: two renderers of the same box, one drawing a bar and one drawing
|
|
817
|
+
nothing.
|
|
818
|
+
|
|
819
|
+
`EVGLayout` clamps a calculated width or height to zero now. The clamp is on
|
|
820
|
+
the SIZE and not on `EVGUnit.resolve`, because a negative offset is perfectly
|
|
821
|
+
ordinary — `left: -10px` moves a box left — and clamping there would break it.
|
|
822
|
+
|
|
823
|
+
## #14 `to_double` disagrees with itself across targets — OPEN
|
|
824
|
+
|
|
825
|
+
`(to_double "10 ")` is 10 in JavaScript and Python and NOTHING in Go, whose
|
|
826
|
+
parse is strict about surrounding whitespace. A number scanned up to the next
|
|
827
|
+
operator carries a trailing space more often than not, so a recursive-descent
|
|
828
|
+
parser written the obvious way returns the right answer on two targets and zero
|
|
829
|
+
on the third — from one source, which is the one thing the compiler promises
|
|
830
|
+
not to do.
|
|
831
|
+
|
|
832
|
+
The portable form is `(to_double (trim text))`, and the plugin's `calc` example
|
|
833
|
+
says so in a comment because it was found by running the example on all three.
|
|
834
|
+
Whether the fix belongs in the Go writer (trim before parsing) or in the
|
|
835
|
+
documented contract (callers trim) is open; the disagreement is not.
|