ranger-compiler 3.5.1 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1064 -20
- package/LICENSE +3 -1
- package/README.md +123 -28
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79923 -56921
- package/dist/lib/JSON.rgr +102 -91
- package/dist/lib/Shell.rgr +4 -4
- package/dist/lib/apple/AppleToolchain.rgr +4 -4
- package/dist/lib/apple/README.md +5 -4
- package/dist/lib/apple/apple_test.rgr +1 -1
- package/dist/lib/core/README.md +1 -1
- package/dist/lib/evg/EVG.rgr +12 -0
- package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
- package/dist/lib/evg/EVGA11yTree.rgr +894 -0
- package/dist/lib/evg/EVGBox.rgr +267 -0
- package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
- package/dist/lib/evg/EVGCodepoint.rgr +316 -0
- package/dist/lib/evg/EVGColor.rgr +700 -0
- package/dist/lib/evg/EVGCommands.rgr +177 -0
- package/dist/lib/evg/EVGComponent.rgr +331 -0
- package/dist/lib/evg/EVGComponentTest.rgr +387 -0
- package/dist/lib/evg/EVGConnector.rgr +541 -0
- package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
- package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
- package/dist/lib/evg/EVGEasing.rgr +370 -0
- package/dist/lib/evg/EVGEffectTest.rgr +253 -0
- package/dist/lib/evg/EVGElement.rgr +4914 -0
- package/dist/lib/evg/EVGFixedTest.rgr +289 -0
- package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
- package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
- package/dist/lib/evg/EVGFling.rgr +244 -0
- package/dist/lib/evg/EVGFocus.rgr +400 -0
- package/dist/lib/evg/EVGFocusTest.rgr +399 -0
- package/dist/lib/evg/EVGGradient.rgr +322 -0
- package/dist/lib/evg/EVGGrapheme.rgr +190 -0
- package/dist/lib/evg/EVGGrid.rgr +975 -0
- package/dist/lib/evg/EVGHitTest.rgr +208 -0
- package/dist/lib/evg/EVGHoles.rgr +294 -0
- package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
- package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
- package/dist/lib/evg/EVGHostTree.rgr +834 -0
- package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
- package/dist/lib/evg/EVGImageDecode.rgr +117 -0
- package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
- package/dist/lib/evg/EVGInspect.rgr +883 -0
- package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
- package/dist/lib/evg/EVGJsonTest.rgr +535 -0
- package/dist/lib/evg/EVGLayout.rgr +4220 -0
- package/dist/lib/evg/EVGMeasure.rgr +1267 -0
- package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
- package/dist/lib/evg/EVGPatch.rgr +1721 -0
- package/dist/lib/evg/EVGPatchTest.rgr +692 -0
- package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
- package/dist/lib/evg/EVGReconcile.rgr +226 -0
- package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
- package/dist/lib/evg/EVGReject.rgr +126 -0
- package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
- package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
- package/dist/lib/evg/EVGRuler.rgr +232 -0
- package/dist/lib/evg/EVGRulerTest.rgr +172 -0
- package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
- package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
- package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
- package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
- package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
- package/dist/lib/evg/EVGText.rgr +105 -0
- package/dist/lib/evg/EVGTextEngine.rgr +536 -0
- package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
- package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
- package/dist/lib/evg/EVGToolbar.rgr +1369 -0
- package/dist/lib/evg/EVGTransition.rgr +728 -0
- package/dist/lib/evg/EVGTreeJson.rgr +721 -0
- package/dist/lib/evg/EVGUnit.rgr +554 -0
- package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
- package/dist/lib/evg/EvgApp.rgr +189 -0
- package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
- package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
- package/dist/lib/evg/EvgHost.rgr +451 -0
- package/dist/lib/evg/EvgTest.rgr +91 -0
- package/dist/lib/evg/EvgTraceColor.rgr +236 -0
- package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
- package/dist/lib/evg/EvgTraceFit.rgr +767 -0
- package/dist/lib/evg/EvgTracePath.rgr +435 -0
- package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
- package/dist/lib/evg/EvgViewport.rgr +313 -0
- package/dist/lib/evg/FxDemoDoc.rgr +185 -0
- package/dist/lib/evg/HOSTS.md +227 -0
- package/dist/lib/evg/ISSUES.md +835 -0
- package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
- package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
- package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
- package/dist/lib/evg/PLAN_EVG.md +518 -0
- package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
- package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
- package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
- package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
- package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
- package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
- package/dist/lib/evg/PathBuilder.rgr +333 -0
- package/dist/lib/evg/README.md +1236 -0
- package/dist/lib/evg/SPEC.md +1252 -0
- package/dist/lib/evg/SVGPathParser.rgr +1199 -0
- package/dist/lib/evg/SvgParser.rgr +1736 -0
- package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
- package/dist/lib/evg/VectorShapes.rgr +331 -0
- package/dist/lib/evg/VectorStroke.rgr +107 -0
- package/dist/lib/evg/VectorViewBox.rgr +380 -0
- package/dist/lib/evg/agent/README.md +290 -0
- package/dist/lib/evg/agent/evg_agent.rgr +899 -0
- package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
- package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
- package/dist/lib/evg/agent/fixtures/connector.css +63 -0
- package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
- package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
- package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
- package/dist/lib/evg/agent/fixtures/popover.css +99 -0
- package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
- package/dist/lib/evg/agent/roundtrip.sh +74 -0
- package/dist/lib/evg/agent/smoke.sh +230 -0
- package/dist/lib/evg/android/README.md +97 -0
- package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
- package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
- package/dist/lib/evg/android/androidstubs/App.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
- package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
- package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
- package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
- package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
- package/dist/lib/evg/android/androidstubs/View.kt +113 -0
- package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
- package/dist/lib/evg/apple/README.md +128 -0
- package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
- package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
- package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
- package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
- package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
- package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
- package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
- package/dist/lib/evg/bench/README.md +64 -0
- package/dist/lib/evg/bench/layout-bench.mjs +316 -0
- package/dist/lib/evg/bench/layout-cases.mjs +510 -0
- package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
- package/dist/lib/evg/bin/.gitignore +17 -0
- package/dist/lib/evg/evg_test.rgr +313 -0
- package/dist/lib/evg/gl/README.md +198 -0
- package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
- package/dist/lib/evg/gl/blur-check.mjs +399 -0
- package/dist/lib/evg/gl/boxmodel.json +1 -0
- package/dist/lib/evg/gl/demo.html +46 -0
- package/dist/lib/evg/gl/effect-presets.css +285 -0
- package/dist/lib/evg/gl/effect-presets.js +63 -0
- package/dist/lib/evg/gl/effect-shots.mjs +135 -0
- package/dist/lib/evg/gl/evg-a11y.js +566 -0
- package/dist/lib/evg/gl/evg-binary.js +160 -0
- package/dist/lib/evg/gl/evg-engine.js +296 -0
- package/dist/lib/evg/gl/evg-fx.js +237 -0
- package/dist/lib/evg/gl/evg-gestures.js +209 -0
- package/dist/lib/evg/gl/evg-list.js +167 -0
- package/dist/lib/evg/gl/evg-measure.js +186 -0
- package/dist/lib/evg/gl/evg-textinput.js +303 -0
- package/dist/lib/evg/gl/evg-view.js +144 -0
- package/dist/lib/evg/gl/evg-webgl.js +3942 -0
- package/dist/lib/evg/gl/fx-check.mjs +998 -0
- package/dist/lib/evg/gl/fx-demo.html +166 -0
- package/dist/lib/evg/gl/fx-demo.js +2 -0
- package/dist/lib/evg/gl/fx-serve.mjs +47 -0
- package/dist/lib/evg/gl/gestures-check.mjs +189 -0
- package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
- package/dist/lib/evg/gl/measure-check.mjs +135 -0
- package/dist/lib/evg/gl/rotation-check.mjs +207 -0
- package/dist/lib/evg/gl/shift-check.mjs +113 -0
- package/dist/lib/evg/gl/stroke-check.mjs +168 -0
- package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
- package/dist/lib/evg/gl/view-check.mjs +343 -0
- package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
- package/dist/lib/evg/html/evg-dom.js +318 -0
- package/dist/lib/evg/html/evg-html.js +600 -0
- package/dist/lib/evg/inspect/README.md +415 -0
- package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
- package/dist/lib/evg/inspect/evg-inspect.js +947 -0
- package/dist/lib/evg/inspect/shots/css.png +0 -0
- package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
- package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
- package/dist/lib/evg/inspect/shots/state.png +0 -0
- package/dist/lib/evg/inspect/shots.mjs +226 -0
- package/dist/lib/evg/oracle/css-blur.json +576 -0
- package/dist/lib/evg/oracle/css-box.json +157 -0
- package/dist/lib/evg/oracle/css-timing.json +709 -0
- package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
- package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
- package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
- package/dist/lib/evg/original/EVGColor.clj +310 -0
- package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
- package/dist/lib/evg/original/SVGPath.rgr +627 -0
- package/dist/lib/evg/original/Vec2.crgr +103 -0
- package/dist/lib/evg/ranger.json +9 -0
- package/dist/lib/evg/showcase/README.md +246 -0
- package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
- package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
- package/dist/lib/evg/showcase/build.mjs +651 -0
- package/dist/lib/evg/showcase/pages/album.tsx +30 -0
- package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
- package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
- package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
- package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
- package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
- package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
- package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
- package/dist/lib/evg/showcase/pages/more.tsx +332 -0
- package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
- package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
- package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
- package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
- package/dist/lib/evg/showcase/pages/units.tsx +35 -0
- package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
- package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
- package/dist/lib/evg/showcase/pages/views.tsx +223 -0
- package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
- package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
- package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
- package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
- package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
- package/dist/lib/evg/showcase/themes/more-default.css +128 -0
- package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
- package/dist/lib/evg/showcase/themes/showcase.css +508 -0
- package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
- package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
- package/dist/lib/evg/showcase/themes/views-default.css +15 -0
- package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
- package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
- package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
- package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
- package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
- package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
- package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
- package/dist/lib/evg/web/responsive/README.md +97 -0
- package/dist/lib/evg/web/responsive/build.mjs +90 -0
- package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
- package/dist/lib/evg/web/responsive/index.html +177 -0
- package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
- package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
- package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
- package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
- package/dist/lib/evg/web/tools/minify.mjs +60 -0
- package/dist/lib/evg/web/tracer/build.mjs +87 -0
- package/dist/lib/evg/web/tracer/index.html +2805 -0
- package/dist/lib/evg/web/tracer/sample.png +0 -0
- package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
- package/dist/lib/evgr/Cargo.lock +21 -0
- package/dist/lib/evgr/Cargo.toml +19 -0
- package/dist/lib/evgr/README.md +140 -0
- package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
- package/dist/lib/evgr/bench/compare.mjs +252 -0
- package/dist/lib/evgr/bench/speed.mjs +277 -0
- package/dist/lib/evgr/bin/.gitignore +3 -0
- package/dist/lib/evgr/src/bin/bench.rs +106 -0
- package/dist/lib/evgr/src/bin/smoke.rs +13 -0
- package/dist/lib/evgr/src/grid.rs +730 -0
- package/dist/lib/evgr/src/lib.rs +1003 -0
- package/dist/lib/evgr/src/style.rs +468 -0
- package/dist/lib/evgr/src/text.rs +83 -0
- package/dist/lib/image/BitReader.rgr +171 -0
- package/dist/lib/image/Buffer.rgr +173 -0
- package/dist/lib/image/DCT.rgr +283 -0
- package/dist/lib/image/Deflate.rgr +339 -0
- package/dist/lib/image/HuffmanDecoder.rgr +181 -0
- package/dist/lib/image/ImageBuffer.rgr +706 -0
- package/dist/lib/image/JPEGDecoder.rgr +808 -0
- package/dist/lib/image/PNGDecoder.rgr +544 -0
- package/dist/lib/image/PNGEncoder.rgr +388 -0
- package/dist/lib/image/PPMImage.rgr +224 -0
- package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
- package/dist/lib/image/README.md +33 -0
- package/dist/lib/image/RasterBuffer.rgr +294 -0
- package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
- package/dist/lib/image/WebPDecoder.rgr +433 -0
- package/dist/lib/image/WebPLossless.rgr +1035 -0
- package/dist/lib/image/WebPLossy.rgr +1929 -0
- package/dist/lib/image/ranger.json +9 -0
- package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
- package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
- package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
- package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
- package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
- package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
- package/dist/lib/image/tests/run_webp_tests.sh +39 -0
- package/dist/lib/rust/RsJson.rgr +468 -0
- package/dist/lib/rust/RsPrelude.rgr +1663 -0
- package/dist/lib/shell_test.rgr +7 -7
- package/dist/lib/stdlib.rgr +21 -5
- package/dist/lib/zip/ranger.json +6 -0
- package/dist/rgrc.js +87318 -67610
- package/package.json +1010 -1040
- package/dist/README.md +0 -117
- package/dist/package.json +0 -47
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
package fi.ranger.evg
|
|
3
|
+
|
|
4
|
+
import java.util.concurrent.Executors
|
|
5
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The engine off the UI thread — Android, and the JVM.
|
|
9
|
+
*
|
|
10
|
+
* PLAN_NATIVE_HOSTS.md S1. A Ranger app compiled to Kotlin has no `android.*`
|
|
11
|
+
* in it, so nothing about it has to run on the UI thread; what has to is the
|
|
12
|
+
* `View` that receives touches and the `Canvas` that paints. So the app lives
|
|
13
|
+
* on ONE thread of its own, every call the host makes runs there in the order
|
|
14
|
+
* the host made it, and a call that changed the page produces a frame — laid
|
|
15
|
+
* out and listed on that thread — that is handed to the UI thread, which
|
|
16
|
+
* paints it and nothing else.
|
|
17
|
+
*
|
|
18
|
+
* Three verbs, and the discipline is that a host uses them and never touches
|
|
19
|
+
* the app directly:
|
|
20
|
+
*
|
|
21
|
+
* post { app -> … } run there; return true if a frame is owed
|
|
22
|
+
* ask({ app -> … }) { v -> … } run there; answer on the UI thread
|
|
23
|
+
* sync { app -> … } run there and WAIT — for the one read the
|
|
24
|
+
* platform insists on synchronously
|
|
25
|
+
* (`onCheckIsTextEditor`)
|
|
26
|
+
*
|
|
27
|
+
* Frames are coalesced: a burst of posts that each say "a frame is owed"
|
|
28
|
+
* produces one build after the last of them, and a build asked for while one
|
|
29
|
+
* is running produces exactly one more when it finishes — never a queue of
|
|
30
|
+
* stale frames the UI thread has to draw through.
|
|
31
|
+
*
|
|
32
|
+
* This file is in the platform-free source set on purpose: `onMain` is how
|
|
33
|
+
* the host reaches its UI thread (`view::post` on Android, `SwingUtilities`
|
|
34
|
+
* or a plain call on a JVM check), so the same class serves both.
|
|
35
|
+
*
|
|
36
|
+
* @param app the Ranger app; make it BEFORE this, and never read it again
|
|
37
|
+
* from the UI thread
|
|
38
|
+
* @param build the frame, run on the engine thread — `app.frame()` and
|
|
39
|
+
* whatever the painter needs beside it
|
|
40
|
+
* @param onMain run a block on the UI thread
|
|
41
|
+
* @param deliver called on the UI thread with each built frame; keep it and
|
|
42
|
+
* `invalidate()`
|
|
43
|
+
*/
|
|
44
|
+
class EvgEngineThread<App, Frame>(
|
|
45
|
+
private val app: App,
|
|
46
|
+
private val build: (App) -> Frame,
|
|
47
|
+
private val onMain: (Runnable) -> Unit,
|
|
48
|
+
private val deliver: (Frame) -> Unit,
|
|
49
|
+
) {
|
|
50
|
+
private val exec = Executors.newSingleThreadExecutor { r ->
|
|
51
|
+
Thread(r, "evg-engine").apply { isDaemon = true }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Touched on the engine thread only.
|
|
55
|
+
private var building = false
|
|
56
|
+
private var wanted = false
|
|
57
|
+
private val closed = AtomicBoolean(false)
|
|
58
|
+
|
|
59
|
+
/** Run `work` on the engine thread. A `true` result asks for a frame. */
|
|
60
|
+
fun post(work: (App) -> Boolean) {
|
|
61
|
+
if (closed.get()) return
|
|
62
|
+
exec.execute {
|
|
63
|
+
if (work(app)) requestFrameOnThread()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Run `work` on the engine thread and hand its answer to `then` on the UI thread. */
|
|
68
|
+
fun <T> ask(work: (App) -> T, then: (T) -> Unit) {
|
|
69
|
+
if (closed.get()) return
|
|
70
|
+
exec.execute {
|
|
71
|
+
val v = work(app)
|
|
72
|
+
onMain(Runnable { then(v) })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Run `work` on the engine thread and wait for it. For the reads the
|
|
78
|
+
* platform asks for synchronously and nothing else: it holds the caller
|
|
79
|
+
* for as long as whatever is already queued takes.
|
|
80
|
+
*/
|
|
81
|
+
fun <T> sync(work: (App) -> T): T = exec.submit<T> { work(app) }.get()
|
|
82
|
+
|
|
83
|
+
/** A frame is owed — the clock ticked, the view resized. */
|
|
84
|
+
fun invalidate() {
|
|
85
|
+
if (closed.get()) return
|
|
86
|
+
exec.execute { requestFrameOnThread() }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fun close() {
|
|
90
|
+
closed.set(true)
|
|
91
|
+
exec.shutdown()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// On the engine thread.
|
|
95
|
+
private fun requestFrameOnThread() {
|
|
96
|
+
if (building) {
|
|
97
|
+
wanted = true
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
building = true
|
|
101
|
+
// Not inline: the posts already queued behind this one run first, so
|
|
102
|
+
// a press and the release that followed it are both in the frame.
|
|
103
|
+
exec.execute { buildOnThread() }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private fun buildOnThread() {
|
|
107
|
+
val f = build(app)
|
|
108
|
+
onMain(Runnable { deliver(f) })
|
|
109
|
+
building = false
|
|
110
|
+
if (wanted) {
|
|
111
|
+
wanted = false
|
|
112
|
+
requestFrameOnThread()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
package fi.ranger.evg
|
|
2
|
+
|
|
3
|
+
import fi.ranger.rgr.EVGDisplayList
|
|
4
|
+
import fi.ranger.rgr.EVGDrawCmd
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The walk: an `EVGDisplayList` in, calls on an [EvgSurface] out.
|
|
8
|
+
*
|
|
9
|
+
* The display list's own comment says a sixth backend should not be a sixth
|
|
10
|
+
* copy of the traversal, and this is that rule applied one level down: Android
|
|
11
|
+
* and AWT differ in how they fill a rounded box, not in what a command *means*,
|
|
12
|
+
* so what a command means is decided exactly once, here.
|
|
13
|
+
*
|
|
14
|
+
* Two things this painter honours that the existing backends do not:
|
|
15
|
+
*
|
|
16
|
+
* * **Gradients and shadows.** `EVGDisplayList.toJson` carries neither, so the
|
|
17
|
+
* WebGL host cannot draw them and only the CPU rasteriser does. Reading the
|
|
18
|
+
* objects directly — which is what a Kotlin host can do and a browser cannot
|
|
19
|
+
* — brings both back.
|
|
20
|
+
* * **Clipping and multi-ring paths.** The SoftCanvas painter ignores
|
|
21
|
+
* `PUSH_CLIP` / `POP_CLIP` and flattens every path to one polygon, so a
|
|
22
|
+
* ring-with-a-hole comes out solid. `save`/`clipRect`/`restore` and a real
|
|
23
|
+
* even-odd fill are one line each on both surfaces here.
|
|
24
|
+
*/
|
|
25
|
+
object EvgPainter {
|
|
26
|
+
|
|
27
|
+
private const val RECT = 0
|
|
28
|
+
private const val BORDER = 1
|
|
29
|
+
private const val IMAGE = 2
|
|
30
|
+
private const val TEXT = 3
|
|
31
|
+
private const val PUSH_CLIP = 4
|
|
32
|
+
private const val POP_CLIP = 5
|
|
33
|
+
private const val PATH = 6
|
|
34
|
+
private const val STROKE = 7
|
|
35
|
+
|
|
36
|
+
/** Anything below this is invisible; drawing it is work for nothing. */
|
|
37
|
+
private const val MIN_ALPHA = 0.001
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Paint every command in order. Returns how many were actually drawn, which
|
|
41
|
+
* is what a host puts in a debug overlay — a frame that draws 0 of 300
|
|
42
|
+
* commands is a bug you want to see rather than a black screen.
|
|
43
|
+
*/
|
|
44
|
+
fun paint(list: EVGDisplayList, surface: EvgSurface): Int {
|
|
45
|
+
var drawn = 0
|
|
46
|
+
// A list that pushes more clips than it pops must not leave the surface
|
|
47
|
+
// with a clip nobody asked for, and one that pops more than it pushes
|
|
48
|
+
// must not restore past the state the host handed us. The depth counter
|
|
49
|
+
// is the whole of both guarantees.
|
|
50
|
+
var clipDepth = 0
|
|
51
|
+
val n = list.count()
|
|
52
|
+
var i = 0
|
|
53
|
+
while (i < n) {
|
|
54
|
+
val c = list.at(i)
|
|
55
|
+
i++
|
|
56
|
+
when (c.kind) {
|
|
57
|
+
PUSH_CLIP -> {
|
|
58
|
+
surface.save()
|
|
59
|
+
surface.clipRect(c.x.toFloat(), c.y.toFloat(), c.w.toFloat(), c.h.toFloat())
|
|
60
|
+
clipDepth++
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
POP_CLIP -> {
|
|
64
|
+
if (clipDepth > 0) {
|
|
65
|
+
surface.restore()
|
|
66
|
+
clipDepth--
|
|
67
|
+
}
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// A rotation turns the command about the point the display list
|
|
73
|
+
// names, and about the command's own box centre only when it names
|
|
74
|
+
// none. `EVGDisplayList` resolves `transform-origin` into
|
|
75
|
+
// rotOriginX/Y and sets hasRotOrigin, exactly because the centre is
|
|
76
|
+
// "right for a lone rotated label and wrong for everything else: a
|
|
77
|
+
// box, its text and its children have to turn about ONE point or
|
|
78
|
+
// they come apart".
|
|
79
|
+
//
|
|
80
|
+
// Ignoring it is what made RealTrainer's loading spinner a single
|
|
81
|
+
// small bar. Its twelve blades are one 8x26 box each, fanned into a
|
|
82
|
+
// ring by `transform: rotate(Ndeg)` about `transform-origin: 4px
|
|
83
|
+
// 54px` -- a point 41px BELOW each blade's own centre. Turned about
|
|
84
|
+
// their centres they all spin in place and land on top of each
|
|
85
|
+
// other. Wrapping it in save/restore keeps the clip stack above
|
|
86
|
+
// untouched.
|
|
87
|
+
val turned = c.rotate > 0.001 || c.rotate < -0.001
|
|
88
|
+
if (turned) {
|
|
89
|
+
surface.save()
|
|
90
|
+
surface.rotate(
|
|
91
|
+
c.rotate.toFloat(),
|
|
92
|
+
(if (c.hasRotOrigin) c.rotOriginX else c.x + c.w * 0.5).toFloat(),
|
|
93
|
+
(if (c.hasRotOrigin) c.rotOriginY else c.y + c.h * 0.5).toFloat(),
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (drawOne(c, surface)) drawn++
|
|
98
|
+
|
|
99
|
+
if (turned) surface.restore()
|
|
100
|
+
}
|
|
101
|
+
while (clipDepth > 0) {
|
|
102
|
+
surface.restore()
|
|
103
|
+
clipDepth--
|
|
104
|
+
}
|
|
105
|
+
return drawn
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private fun drawOne(c: EVGDrawCmd, surface: EvgSurface): Boolean {
|
|
109
|
+
when (c.kind) {
|
|
110
|
+
RECT -> {
|
|
111
|
+
val shadow = shadowOf(c)
|
|
112
|
+
val gradient = gradientOf(c)
|
|
113
|
+
if (c.a < MIN_ALPHA && gradient == null && shadow == null) return false
|
|
114
|
+
surface.fillRect(
|
|
115
|
+
c.x.toFloat(), c.y.toFloat(), c.w.toFloat(), c.h.toFloat(),
|
|
116
|
+
c.radius.toFloat(),
|
|
117
|
+
colorOf(c),
|
|
118
|
+
gradient,
|
|
119
|
+
shadow,
|
|
120
|
+
)
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
BORDER -> {
|
|
124
|
+
if (c.a < MIN_ALPHA) return false
|
|
125
|
+
// A border with no thickness is still a border: the display
|
|
126
|
+
// list leaves the default at 0 for a hairline, and a stroke of
|
|
127
|
+
// width 0 is invisible on both surfaces.
|
|
128
|
+
val t = if (c.thickness > 0.0) c.thickness.toFloat() else 1f
|
|
129
|
+
surface.strokeRect(
|
|
130
|
+
c.x.toFloat(), c.y.toFloat(), c.w.toFloat(), c.h.toFloat(),
|
|
131
|
+
c.radius.toFloat(), t,
|
|
132
|
+
colorOf(c),
|
|
133
|
+
)
|
|
134
|
+
return true
|
|
135
|
+
}
|
|
136
|
+
IMAGE -> {
|
|
137
|
+
if (c.src.isEmpty()) return false
|
|
138
|
+
surface.drawImage(
|
|
139
|
+
c.src,
|
|
140
|
+
c.x.toFloat(), c.y.toFloat(), c.w.toFloat(), c.h.toFloat(),
|
|
141
|
+
c.radius.toFloat(),
|
|
142
|
+
c.flipH, c.flipV,
|
|
143
|
+
)
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
146
|
+
TEXT -> {
|
|
147
|
+
if (c.text.isEmpty()) return false
|
|
148
|
+
if (c.a < MIN_ALPHA) return false
|
|
149
|
+
surface.drawTextRun(
|
|
150
|
+
c.text,
|
|
151
|
+
c.x.toFloat(), c.y.toFloat(),
|
|
152
|
+
c.fontSize.toFloat(),
|
|
153
|
+
c.fontFamily,
|
|
154
|
+
isBold(c.fontWeight),
|
|
155
|
+
// `addText` has nowhere else to record a slanted face, so
|
|
156
|
+
// it writes "italic" into textAlign and `toJson` reads it
|
|
157
|
+
// back out the same way. Not lovely, but it is the format.
|
|
158
|
+
c.textAlign == "italic",
|
|
159
|
+
colorOf(c),
|
|
160
|
+
)
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
163
|
+
PATH -> {
|
|
164
|
+
if (c.a < MIN_ALPHA) return false
|
|
165
|
+
if (c.pts.size < 6) return false
|
|
166
|
+
surface.fillPath(c.pts, ringsOf(c), c.evenOdd, colorOf(c), shadowOf(c))
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
STROKE -> {
|
|
170
|
+
if (c.a < MIN_ALPHA) return false
|
|
171
|
+
if (c.pts.size < 4) return false
|
|
172
|
+
val t = if (c.thickness > 0.0) c.thickness.toFloat() else 1f
|
|
173
|
+
surface.strokePath(c.pts, ringsOf(c), t, colorOf(c))
|
|
174
|
+
return true
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return false
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A path emitted by the SVG walk carries its ring boundaries; one emitted
|
|
182
|
+
* by `addPolygon` / `addPolyline` may not. A single implicit ring covering
|
|
183
|
+
* every point is the right reading of an empty `ringEnds` — it is what the
|
|
184
|
+
* polygon filler assumes.
|
|
185
|
+
*/
|
|
186
|
+
private fun ringsOf(c: EVGDrawCmd): List<Int> =
|
|
187
|
+
if (c.ringEnds.isEmpty()) listOf(c.pts.size) else c.ringEnds
|
|
188
|
+
|
|
189
|
+
private fun gradientOf(c: EVGDrawCmd): EvgGradient? {
|
|
190
|
+
if (!c.hasGrad) return null
|
|
191
|
+
return EvgGradient(
|
|
192
|
+
color1 = argb(c.a, c.r, c.g, c.b),
|
|
193
|
+
color2 = argb(c.a2, c.r2, c.g2, c.b2),
|
|
194
|
+
// dir 0 = vertical (top to bottom), 1 = horizontal (left to right)
|
|
195
|
+
vertical = c.gradDir != 1,
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private fun shadowOf(c: EVGDrawCmd): EvgShadow? {
|
|
200
|
+
if (!c.hasShadow) return null
|
|
201
|
+
if (c.shadowA < MIN_ALPHA) return null
|
|
202
|
+
return EvgShadow(
|
|
203
|
+
dx = c.shadowX.toFloat(),
|
|
204
|
+
dy = c.shadowY.toFloat(),
|
|
205
|
+
blur = c.shadowBlur.toFloat(),
|
|
206
|
+
color = argb(c.shadowA, c.shadowR, c.shadowG, c.shadowB),
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private fun colorOf(c: EVGDrawCmd): Int = argb(c.a, c.r, c.g, c.b)
|
|
211
|
+
|
|
212
|
+
/** 0-255 channels and a 0-1 alpha, the way every command carries them. */
|
|
213
|
+
fun argb(alpha: Double, r: Int, g: Int, b: Int): Int {
|
|
214
|
+
val a = clamp255((alpha * 255.0 + 0.5).toInt())
|
|
215
|
+
return (a shl 24) or (clamp255(r) shl 16) or (clamp255(g) shl 8) or clamp255(b)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private fun clamp255(v: Int): Int = if (v < 0) 0 else if (v > 255) 255 else v
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The display list writes the word, not the number — `addText` only ever
|
|
222
|
+
* says "bold" — but a stylesheet path could put a CSS weight here, so a
|
|
223
|
+
* number is read as one.
|
|
224
|
+
*/
|
|
225
|
+
private fun isBold(weight: String): Boolean {
|
|
226
|
+
if (weight.isEmpty()) return false
|
|
227
|
+
if (weight == "bold" || weight == "bolder") return true
|
|
228
|
+
val n = weight.toIntOrNull() ?: return false
|
|
229
|
+
return n >= 600
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
package fi.ranger.evg
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Everything a backend has to be able to draw, and nothing else.
|
|
5
|
+
*
|
|
6
|
+
* `EVGDisplayList` is eight command kinds of absolute pixels and resolved
|
|
7
|
+
* colours — no tree, no units, no fonts to resolve. That is small enough that
|
|
8
|
+
* a backend is a page of delegation rather than a renderer, which is the point:
|
|
9
|
+
* `AndroidEvgSurface` paints with `android.graphics.Canvas`, `AwtEvgSurface`
|
|
10
|
+
* paints the same list with `java.awt.Graphics2D`, and the walk that decides
|
|
11
|
+
* what each command *means* ([EvgPainter]) is written once and shared.
|
|
12
|
+
*
|
|
13
|
+
* That second implementation is not decoration. An Android painter can only be
|
|
14
|
+
* checked on a device or an emulator; the same painter driven through AWT
|
|
15
|
+
* renders a deck to a PNG on any JVM, which is what makes the port testable in
|
|
16
|
+
* CI at all.
|
|
17
|
+
*
|
|
18
|
+
* Geometry is in the app's own pixels. A host that wants device pixels scales
|
|
19
|
+
* its canvas and leaves these numbers alone — text then rasterises at the
|
|
20
|
+
* panel's real resolution instead of being blown up from a small bitmap.
|
|
21
|
+
*/
|
|
22
|
+
interface EvgSurface {
|
|
23
|
+
/** Push the clip/transform stack. Paired with [restore]. */
|
|
24
|
+
fun save()
|
|
25
|
+
|
|
26
|
+
/** Pop back to the state the matching [save] recorded. */
|
|
27
|
+
fun restore()
|
|
28
|
+
|
|
29
|
+
/** Intersect the clip with this rectangle. Only ever inside a [save]. */
|
|
30
|
+
fun clipRect(x: Float, y: Float, w: Float, h: Float)
|
|
31
|
+
|
|
32
|
+
/** Turn the coordinate system `degrees` about (`px`, `py`). */
|
|
33
|
+
fun rotate(degrees: Float, px: Float, py: Float)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A filled box, possibly rounded, possibly a two-stop gradient, possibly
|
|
37
|
+
* under a drop shadow. The shadow is painted first and is the shape's own
|
|
38
|
+
* silhouette — a rounded box casts a rounded shadow.
|
|
39
|
+
*/
|
|
40
|
+
fun fillRect(
|
|
41
|
+
x: Float, y: Float, w: Float, h: Float,
|
|
42
|
+
radius: Float,
|
|
43
|
+
color: Int,
|
|
44
|
+
gradient: EvgGradient?,
|
|
45
|
+
shadow: EvgShadow?,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
/** A stroked outline, drawn as a band of `thickness` INSIDE the box. */
|
|
49
|
+
fun strokeRect(
|
|
50
|
+
x: Float, y: Float, w: Float, h: Float,
|
|
51
|
+
radius: Float, thickness: Float,
|
|
52
|
+
color: Int,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A filled vector outline. The rings are already flattened and already in
|
|
57
|
+
* page coordinates; `ringEnds` gives the index in `pts` one past each
|
|
58
|
+
* ring's last coordinate, so a shape with a hole arrives as two rings.
|
|
59
|
+
*/
|
|
60
|
+
fun fillPath(
|
|
61
|
+
pts: List<Double>, ringEnds: List<Int>, evenOdd: Boolean,
|
|
62
|
+
color: Int,
|
|
63
|
+
shadow: EvgShadow?,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
/** The same rings, stroked rather than filled. */
|
|
67
|
+
fun strokePath(
|
|
68
|
+
pts: List<Double>, ringEnds: List<Int>,
|
|
69
|
+
thickness: Float,
|
|
70
|
+
color: Int,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A picture, by the package part name the deck gave it. A surface that has
|
|
75
|
+
* no bytes for `src` should draw the placeholder rather than nothing — a
|
|
76
|
+
* missing texture and a blank slide look identical otherwise.
|
|
77
|
+
*/
|
|
78
|
+
fun drawImage(
|
|
79
|
+
src: String,
|
|
80
|
+
x: Float, y: Float, w: Float, h: Float,
|
|
81
|
+
radius: Float,
|
|
82
|
+
flipH: Boolean, flipV: Boolean,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* One run, on one line, in one face.
|
|
87
|
+
*
|
|
88
|
+
* `top` is the top of the LINE BOX, not the baseline: EVG measured the run
|
|
89
|
+
* with the same TrueType face the surface is about to draw it in, and the
|
|
90
|
+
* baseline sits one face-ascent below. Placing the ink at `top` instead
|
|
91
|
+
* lifts every run by the empty space above its capitals — a couple of
|
|
92
|
+
* pixels on a caption, most of a line on a heading. The surface owns that
|
|
93
|
+
* conversion because only it knows what its own font metrics say.
|
|
94
|
+
*/
|
|
95
|
+
fun drawTextRun(
|
|
96
|
+
text: String,
|
|
97
|
+
x: Float, top: Float,
|
|
98
|
+
sizePx: Float,
|
|
99
|
+
family: String, bold: Boolean, italic: Boolean,
|
|
100
|
+
color: Int,
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A two-stop linear gradient. `vertical` is EVG's `gradDir == 0`. */
|
|
105
|
+
data class EvgGradient(
|
|
106
|
+
val color1: Int,
|
|
107
|
+
val color2: Int,
|
|
108
|
+
val vertical: Boolean,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
/** An outer drop shadow: offset, blur radius, colour. */
|
|
112
|
+
data class EvgShadow(
|
|
113
|
+
val dx: Float,
|
|
114
|
+
val dy: Float,
|
|
115
|
+
val blur: Float,
|
|
116
|
+
val color: Int,
|
|
117
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
package fi.ranger.evg
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A surface that counts what it was asked to draw, and then draws it.
|
|
6
|
+
*
|
|
7
|
+
* "Every slide produced some ink" is a weak thing to check: a deck of nothing
|
|
8
|
+
* but grey boxes passes it, and so does a painter that has quietly stopped
|
|
9
|
+
* dispatching text. What a coverage run wants to know is which *kinds* of
|
|
10
|
+
* command reached a surface at all — whether the corpus exercises paths and
|
|
11
|
+
* clips and gradients and mirrored pictures, and whether the walk delivers
|
|
12
|
+
* them — so this wraps the real surface and keeps a tally on the way through.
|
|
13
|
+
*/
|
|
14
|
+
class RecordingSurface(private val inner: EvgSurface) : EvgSurface {
|
|
15
|
+
|
|
16
|
+
val calls = HashMap<String, Int>()
|
|
17
|
+
|
|
18
|
+
private fun bump(what: String) {
|
|
19
|
+
calls[what] = (calls[what] ?: 0) + 1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fun count(what: String): Int = calls[what] ?: 0
|
|
23
|
+
|
|
24
|
+
override fun save() {
|
|
25
|
+
bump("save"); inner.save()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override fun restore() {
|
|
29
|
+
bump("restore"); inner.restore()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun clipRect(x: Float, y: Float, w: Float, h: Float) {
|
|
33
|
+
bump("clipRect"); inner.clipRect(x, y, w, h)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun rotate(degrees: Float, px: Float, py: Float) {
|
|
37
|
+
bump("rotate"); inner.rotate(degrees, px, py)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun fillRect(
|
|
41
|
+
x: Float, y: Float, w: Float, h: Float,
|
|
42
|
+
radius: Float, color: Int, gradient: EvgGradient?, shadow: EvgShadow?,
|
|
43
|
+
) {
|
|
44
|
+
bump("fillRect")
|
|
45
|
+
if (radius > 0.5f) bump("roundedRect")
|
|
46
|
+
if (gradient != null) bump("gradient")
|
|
47
|
+
if (shadow != null) bump("shadow")
|
|
48
|
+
inner.fillRect(x, y, w, h, radius, color, gradient, shadow)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun strokeRect(
|
|
52
|
+
x: Float, y: Float, w: Float, h: Float,
|
|
53
|
+
radius: Float, thickness: Float, color: Int,
|
|
54
|
+
) {
|
|
55
|
+
bump("strokeRect"); inner.strokeRect(x, y, w, h, radius, thickness, color)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override fun fillPath(
|
|
59
|
+
pts: List<Double>, ringEnds: List<Int>, evenOdd: Boolean,
|
|
60
|
+
color: Int, shadow: EvgShadow?,
|
|
61
|
+
) {
|
|
62
|
+
bump("fillPath")
|
|
63
|
+
if (ringEnds.size > 1) bump("multiRingPath")
|
|
64
|
+
if (evenOdd) bump("evenOddPath")
|
|
65
|
+
if (shadow != null) bump("shadow")
|
|
66
|
+
inner.fillPath(pts, ringEnds, evenOdd, color, shadow)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override fun strokePath(pts: List<Double>, ringEnds: List<Int>, thickness: Float, color: Int) {
|
|
70
|
+
bump("strokePath"); inner.strokePath(pts, ringEnds, thickness, color)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
override fun drawImage(
|
|
74
|
+
src: String, x: Float, y: Float, w: Float, h: Float,
|
|
75
|
+
radius: Float, flipH: Boolean, flipV: Boolean,
|
|
76
|
+
) {
|
|
77
|
+
bump("drawImage")
|
|
78
|
+
if (flipH || flipV) bump("mirroredImage")
|
|
79
|
+
inner.drawImage(src, x, y, w, h, radius, flipH, flipV)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
override fun drawTextRun(
|
|
83
|
+
text: String, x: Float, top: Float, sizePx: Float,
|
|
84
|
+
family: String, bold: Boolean, italic: Boolean, color: Int,
|
|
85
|
+
) {
|
|
86
|
+
bump("drawTextRun")
|
|
87
|
+
if (bold) bump("boldText")
|
|
88
|
+
if (italic) bump("italicText")
|
|
89
|
+
// A run outside the Latin block is what tells you the TrueType reader
|
|
90
|
+
// and the face pool are doing something rather than getting lucky.
|
|
91
|
+
if (text.any { it.code > 127 }) bump("nonAsciiText")
|
|
92
|
+
inner.drawTextRun(text, x, top, sizePx, family, bold, italic, color)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# `lib/evg/apple` — the EVG display list, painted with CoreGraphics
|
|
2
|
+
|
|
3
|
+
**License: AGPL-3.0-or-later** (Gallery).
|
|
4
|
+
|
|
5
|
+
The Apple sibling of [`lib/evg/android`](../android/README.md), and
|
|
6
|
+
deliberately the same shape: an `EvgSurface` protocol of eight drawing calls,
|
|
7
|
+
one `EvgPainter` that decides what each display-list command *means*, and a
|
|
8
|
+
backend per drawing API.
|
|
9
|
+
|
|
10
|
+
There is only one backend here, and that is the point. A `UIView` hands out a
|
|
11
|
+
`CGContext` from `UIGraphicsGetCurrentContext()`; a SwiftUI `Canvas` hands one
|
|
12
|
+
out from `GraphicsContext.withCGContext`; a bitmap or a PDF context is the same
|
|
13
|
+
object again. So an iPhone, an iPad, an Apple Watch and an off-screen render all
|
|
14
|
+
paint through `CoreGraphicsEvgSurface`, not through four surfaces that have to
|
|
15
|
+
be kept in step.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
EVGDisplayList (Ranger, compiled to Swift)
|
|
19
|
+
│
|
|
20
|
+
EvgPainter.paint(list, into:) what a command MEANS — written once
|
|
21
|
+
│
|
|
22
|
+
EvgSurface eight calls, and nothing else
|
|
23
|
+
│
|
|
24
|
+
├── CoreGraphicsEvgSurface CGContext ← UIKit, SwiftUI, watchOS
|
|
25
|
+
└── RecordingSurface counts what was dispatched
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| File | Lines | What it is |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `Sources/EvgSurface.swift` | 163 | The protocol, and the colour/gradient/shadow types it takes |
|
|
31
|
+
| `Sources/EvgPainter.swift` | 213 | The walk: a display list in, surface calls out |
|
|
32
|
+
| `Sources/CoreGraphicsEvgSurface.swift` | 291 | Boxes, borders, paths, images and CoreText, on a `CGContext` |
|
|
33
|
+
| `Sources/RecordingSurface.swift` | 110 | Draws nothing, counts everything |
|
|
34
|
+
| `Sources/CoreTextMeasurer.swift` | 70 | CoreText measures the text the layout is made with — the same `CTFont` the surface draws with |
|
|
35
|
+
| `Sources/EvgEngineQueue.swift` | 110 | The engine on its own serial queue: `post`, `ask`, `sync`, and frames coalesced and delivered to the main thread |
|
|
36
|
+
|
|
37
|
+
## What the painter is careful about
|
|
38
|
+
|
|
39
|
+
`EvgPainter` is a transliteration of the Kotlin one rather than a second reading
|
|
40
|
+
of the format. Where the two disagree, one of them is wrong. Four things it
|
|
41
|
+
carries that a JSON-fed backend cannot:
|
|
42
|
+
|
|
43
|
+
* **Gradients and shadows.** `EVGDisplayList.toJson` carries neither, so a
|
|
44
|
+
browser host cannot draw them. Reading the objects directly brings both back.
|
|
45
|
+
* **Clipping.** `save`/`clipRect`/`restore`, with a depth counter: a list that
|
|
46
|
+
pushes more clips than it pops must not leave the surface clipped for the rest
|
|
47
|
+
of the frame, and one that pops more than it pushes must not restore past the
|
|
48
|
+
state the host handed over. On CoreGraphics the first of those is a page with
|
|
49
|
+
most of it missing.
|
|
50
|
+
* **Multi-ring paths.** `ringEnds` gives the index one past each ring's last
|
|
51
|
+
coordinate, so a shape with a hole is one path with two subpaths and a real
|
|
52
|
+
even-odd fill.
|
|
53
|
+
* **Rotation about a command's own box centre**, wrapped in save/restore so the
|
|
54
|
+
clip stack above is untouched.
|
|
55
|
+
|
|
56
|
+
## Three things CoreGraphics needs that Skia did not
|
|
57
|
+
|
|
58
|
+
**Text is upside down unless you say otherwise.** The context is flipped —
|
|
59
|
+
origin top left, y down, which is what UIKit and SwiftUI both hand you and what
|
|
60
|
+
the display list is already in — and CoreText draws glyphs the other way up.
|
|
61
|
+
`ctx.textMatrix = CGAffineTransform(scaleX: 1, y: -1)` is the fix, and it is one
|
|
62
|
+
line in exactly one place.
|
|
63
|
+
|
|
64
|
+
**`top` is not the baseline.** EVG measured the run and the baseline sits one
|
|
65
|
+
face-ascent below the top of the line box. `CTFontGetAscent` is where that comes
|
|
66
|
+
from, and the surface owns the conversion because only it knows what its own
|
|
67
|
+
metrics say.
|
|
68
|
+
|
|
69
|
+
**A border is a band INSIDE the box.** A CoreGraphics stroke straddles its path,
|
|
70
|
+
so `strokeRect` insets by half the thickness and shrinks the corner radius to
|
|
71
|
+
match. Without it a one-point hairline spills over the edge of a clipped scroll
|
|
72
|
+
region.
|
|
73
|
+
|
|
74
|
+
## Fonts
|
|
75
|
+
|
|
76
|
+
`CTFont`, not `UIFont`: CoreText exists on every Apple platform including
|
|
77
|
+
watchOS, so this is the same file there. The family name from the display list
|
|
78
|
+
picks between a monospace face, a serif and the platform's own sans; bold and
|
|
79
|
+
italic go through `CTFontCreateCopyWithSymbolicTraits`.
|
|
80
|
+
|
|
81
|
+
No font files are bundled, and none are needed for the layout to be honest:
|
|
82
|
+
`CoreTextMeasurer.install()` — one stored property in each host, declared
|
|
83
|
+
above the app so it runs first — hands EVG's `EVGHostTextMeasurer` a function
|
|
84
|
+
that measures a run with the SAME `CTFont` `EvgFontCache` gives the surface,
|
|
85
|
+
`CTLineGetTypographicBounds` and all. So the page is laid out with the face it
|
|
86
|
+
is drawn with, rather than with the table it used to guess from. The pptx port
|
|
87
|
+
still bundles the deck's own faces, for the opposite reason: a deck is measured
|
|
88
|
+
against the faces it was authored in.
|
|
89
|
+
|
|
90
|
+
## What is checked, and where
|
|
91
|
+
|
|
92
|
+
Nothing in this directory is compiled by this repository's CI, because Swift for
|
|
93
|
+
Apple platforms needs a Mac. What IS checked, on any machine, is everything the
|
|
94
|
+
painter is handed:
|
|
95
|
+
[`gallery/ui/ios/ranger/check_ios.rgr`](../../../gallery/ui/ios/ranger/check_ios.rgr)
|
|
96
|
+
asserts that the page reaches text, filled boxes, borders, rounded corners,
|
|
97
|
+
clipping, a filled path and stroked paths — and that every clip pushed is
|
|
98
|
+
popped, which is the invariant this painter exists to keep.
|
|
99
|
+
|
|
100
|
+
`RecordingSurface` is the Mac-side half of that: wrap the real surface in one,
|
|
101
|
+
paint a frame, and the tally says what the **painter** dispatched rather than
|
|
102
|
+
what the page produced.
|
|
103
|
+
|
|
104
|
+
## Scroll layers, and what this painter does not do yet
|
|
105
|
+
|
|
106
|
+
A clip that can scroll arrives with `layer` set on its PUSH_CLIP — see
|
|
107
|
+
`EVGDisplayList.refreshLayers`. An app that keeps its list across a scroll
|
|
108
|
+
hands the painter the SAME list, with the commands inside that clip already
|
|
109
|
+
moved to where the scroll put them, so this painter draws the right frame
|
|
110
|
+
without knowing a layer from any other clip. What the host saves is the
|
|
111
|
+
layout and the list build; what it still pays is rasterising every command
|
|
112
|
+
in the layer, every frame.
|
|
113
|
+
|
|
114
|
+
The next step, when a long page on a phone asks for it, is the one the
|
|
115
|
+
browser painter took: keep what was made from the layer. Here that is a
|
|
116
|
+
`CGLayer` (or a bitmap) the size of the layer's range, painted once per
|
|
117
|
+
`buildSeq`, and blitted per frame at the layer's clip with
|
|
118
|
+
`layerShiftX/Y` subtracted from the shift it was painted at — a scroll
|
|
119
|
+
frame is then one `draw(layer, at:)` under the clip and the commands
|
|
120
|
+
outside the layer. The list's `layerFirst`/`layerLast` give the command
|
|
121
|
+
range; nothing else in the list has to change.
|
|
122
|
+
|
|
123
|
+
## Related
|
|
124
|
+
|
|
125
|
+
* [`lib/evg/android`](../android/README.md) — the same interface over
|
|
126
|
+
`android.graphics.Canvas` and `java.awt.Graphics2D`
|
|
127
|
+
* [`gallery/ui/ios`](../../../gallery/ui/ios/README.md) — the dashboard demo through this
|
|
128
|
+
painter, on iPhone, iPad and Apple Watch
|