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,262 @@
|
|
|
1
|
+
package fi.ranger.evg
|
|
2
|
+
|
|
3
|
+
import android.graphics.RenderEffect
|
|
4
|
+
import android.graphics.RuntimeShader
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import android.view.View
|
|
8
|
+
import fi.ranger.rgr.EVGDisplayList
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `evg-surface-effect: ripple`, on Android, as an AGSL post-process.
|
|
12
|
+
*
|
|
13
|
+
* The effect is not a command in the display list and cannot be: it is a pass
|
|
14
|
+
* over the FINISHED pixels, which is why the WebGL host renders the page to a
|
|
15
|
+
* texture and then draws that texture through a fragment shader. An
|
|
16
|
+
* `android.graphics.Canvas` has no such stage — which is why this port shipped
|
|
17
|
+
* without the ripple and said so — but a `RuntimeShader` does. At API 33 the
|
|
18
|
+
* platform hands Skia's own shading language to an app, and
|
|
19
|
+
* `RenderEffect.createRuntimeShaderEffect` wires it into exactly that place:
|
|
20
|
+
* the view is rendered into a texture, the texture is bound to a named
|
|
21
|
+
* `uniform shader`, and the program below decides every pixel on screen.
|
|
22
|
+
*
|
|
23
|
+
* So the shader is a translation of `lib/evg/gl/evg-webgl.js`'s
|
|
24
|
+
* `RIPPLE_FRAG` and not a second effect that has to be kept looking like the
|
|
25
|
+
* first one. Two things are genuinely different, and both are simplifications:
|
|
26
|
+
*
|
|
27
|
+
* * **No flips.** A GL texture's origin is the bottom left and the page's is
|
|
28
|
+
* the top left, which cost the WebGL host two sign errors worth the comments
|
|
29
|
+
* it now carries. A view's coordinate space runs y-down like the page, so
|
|
30
|
+
* nothing is negated here.
|
|
31
|
+
* * **No `dFdx`.** AGSL has no screen-space derivatives, so the height field's
|
|
32
|
+
* gradient — which is where the glint comes from — is taken by evaluating
|
|
33
|
+
* the wave one page pixel away and subtracting, rather than by asking the
|
|
34
|
+
* hardware. Two extra evaluations of a loop that runs a handful of times.
|
|
35
|
+
*
|
|
36
|
+
* Below API 33 this is a no-op and the page draws exactly as it did.
|
|
37
|
+
*/
|
|
38
|
+
class RippleEffect(private val view: View) {
|
|
39
|
+
|
|
40
|
+
private var shader: RuntimeShader? = null
|
|
41
|
+
private var applied = false
|
|
42
|
+
/** A program that will not compile is reported once, not every frame. */
|
|
43
|
+
private var broken = false
|
|
44
|
+
|
|
45
|
+
/** True when the platform can run this at all. */
|
|
46
|
+
val available: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Put the current touches on screen, or take the effect off when there are
|
|
50
|
+
* none left.
|
|
51
|
+
*
|
|
52
|
+
* `list` is only read for the effect's PARAMETERS — they come off the
|
|
53
|
+
* stylesheet and are stamped onto every display list — while the drops are
|
|
54
|
+
* passed separately, because a ripple frame must not cost a rebuilt page:
|
|
55
|
+
* ageing a touch changes nothing about what was drawn.
|
|
56
|
+
*
|
|
57
|
+
* `drops` is x, y, age per touch, in page pixels and seconds, oldest first.
|
|
58
|
+
* `scale` is device pixels per page pixel and `panX` is how far the page is
|
|
59
|
+
* scrolled sideways — together they are how a page coordinate becomes a
|
|
60
|
+
* coordinate this shader is asked about.
|
|
61
|
+
*/
|
|
62
|
+
fun update(list: EVGDisplayList, drops: FloatArray, count: Int, scale: Float, panX: Float) {
|
|
63
|
+
if (!available || broken) return
|
|
64
|
+
if (list.effectKind != "ripple" || count <= 0) {
|
|
65
|
+
clear()
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
val s = shaderOrNull() ?: return
|
|
69
|
+
val n = if (count > MAX_DROPS) MAX_DROPS else count
|
|
70
|
+
// The array is always the full eight: a uniform array is sized by its
|
|
71
|
+
// declaration, and handing it a short one is an argument error rather
|
|
72
|
+
// than a partial update.
|
|
73
|
+
val packed = FloatArray(MAX_DROPS * 3)
|
|
74
|
+
for (i in 0 until n) {
|
|
75
|
+
packed[i * 3] = drops[i * 3]
|
|
76
|
+
packed[i * 3 + 1] = drops[i * 3 + 1]
|
|
77
|
+
packed[i * 3 + 2] = drops[i * 3 + 2]
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
s.setFloatUniform("uDrops", packed)
|
|
81
|
+
s.setIntUniform("uCount", n)
|
|
82
|
+
s.setFloatUniform("uScale", if (scale > 0f) scale else 1f)
|
|
83
|
+
s.setFloatUniform("uPanX", panX)
|
|
84
|
+
s.setFloatUniform("uSpeed", list.effectSpeed.toFloat())
|
|
85
|
+
s.setFloatUniform("uWidth", maxOf(list.effectWidth.toFloat(), 0.001f))
|
|
86
|
+
s.setFloatUniform("uStrength", list.effectStrength.toFloat())
|
|
87
|
+
s.setFloatUniform("uDecay", list.effectDecay.toFloat())
|
|
88
|
+
s.setFloatUniform("uHi", list.effectHighlight.toFloat())
|
|
89
|
+
s.setIntUniform("uRings", list.effectRings.toInt().coerceIn(1, MAX_RINGS))
|
|
90
|
+
s.setFloatUniform("uStagger", list.effectStagger.toFloat())
|
|
91
|
+
s.setFloatUniform("uFalloff", list.effectFalloff.toFloat())
|
|
92
|
+
s.setFloatUniform("uShine", list.effectShine.toFloat())
|
|
93
|
+
s.setFloatUniform("uGloss", maxOf(list.effectGloss.toFloat(), 1f))
|
|
94
|
+
s.setFloatUniform("uBump", list.effectBump.toFloat())
|
|
95
|
+
s.setFloatUniform(
|
|
96
|
+
"uLight",
|
|
97
|
+
list.effectLightX.toFloat(),
|
|
98
|
+
list.effectLightY.toFloat(),
|
|
99
|
+
list.effectLightZ.toFloat(),
|
|
100
|
+
)
|
|
101
|
+
view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(s, "uContent"))
|
|
102
|
+
applied = true
|
|
103
|
+
} catch (e: Throwable) {
|
|
104
|
+
// A uniform this platform will not take is worth exactly one line
|
|
105
|
+
// in the log and then silence: the page is still drawn, it just is
|
|
106
|
+
// not rippling.
|
|
107
|
+
broken = true
|
|
108
|
+
clear()
|
|
109
|
+
Log.w(TAG, "ripple uniforms rejected, effect off: " + e)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Off, and cheaply: a view with a render effect is composited off-screen. */
|
|
114
|
+
fun clear() {
|
|
115
|
+
if (!applied) return
|
|
116
|
+
applied = false
|
|
117
|
+
view.setRenderEffect(null)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private fun shaderOrNull(): RuntimeShader? {
|
|
121
|
+
shader?.let { return it }
|
|
122
|
+
return try {
|
|
123
|
+
val s = RuntimeShader(AGSL)
|
|
124
|
+
shader = s
|
|
125
|
+
s
|
|
126
|
+
} catch (e: Throwable) {
|
|
127
|
+
broken = true
|
|
128
|
+
Log.w(TAG, "AGSL would not compile, ripple off: " + e)
|
|
129
|
+
null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private companion object {
|
|
134
|
+
const val TAG = "EvgRipple"
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Eight touches is not a number anybody reaches by tapping; it is what
|
|
138
|
+
* a finger dragged across the surface fills in a third of a second, and
|
|
139
|
+
* the oldest is retired to make room. Five rings is one touch's train.
|
|
140
|
+
*/
|
|
141
|
+
const val MAX_DROPS = 8
|
|
142
|
+
const val MAX_RINGS = 5
|
|
143
|
+
|
|
144
|
+
val AGSL = """
|
|
145
|
+
uniform shader uContent;
|
|
146
|
+
uniform float3 uDrops[8]; // x, y in PAGE pixels; z is seconds since the touch
|
|
147
|
+
uniform int uCount;
|
|
148
|
+
uniform float uScale; // device pixels per page pixel
|
|
149
|
+
uniform float uPanX; // page pixels scrolled sideways
|
|
150
|
+
uniform float uSpeed; // page px per second the ring travels
|
|
151
|
+
uniform float uWidth; // the envelope's sigma, page px
|
|
152
|
+
uniform float uStrength; // displacement at the crest, page px
|
|
153
|
+
uniform float uDecay; // per second
|
|
154
|
+
uniform float uHi; // how much the crest lightens
|
|
155
|
+
uniform int uRings;
|
|
156
|
+
uniform float uStagger; // seconds between one wavefront and the next
|
|
157
|
+
uniform float uFalloff; // what each ring behind the front is worth
|
|
158
|
+
uniform float uShine; // how strong the glint is
|
|
159
|
+
uniform float uGloss; // Blinn-Phong exponent: how tight it is
|
|
160
|
+
uniform float uBump; // what turns the height field into a slope
|
|
161
|
+
uniform float3 uLight; // where the light is, not necessarily normalised
|
|
162
|
+
|
|
163
|
+
// The wave sum at one page point: the displacement as a VECTOR, the crest, and
|
|
164
|
+
// the envelope. Summing the displacement rather than an amplitude is the whole
|
|
165
|
+
// of the interference — two rings arriving from opposite sides push the surface
|
|
166
|
+
// in opposite directions and the pixel between them stays where it was.
|
|
167
|
+
float4 waveAt(float2 p) {
|
|
168
|
+
float2 push = float2(0.0, 0.0);
|
|
169
|
+
float crest = 0.0;
|
|
170
|
+
float energy = 0.0;
|
|
171
|
+
for (int i = 0; i < 8; i++) {
|
|
172
|
+
if (i >= uCount) { break; }
|
|
173
|
+
float3 drop = uDrops[i];
|
|
174
|
+
float2 delta = p - drop.xy;
|
|
175
|
+
float d = length(delta);
|
|
176
|
+
float2 dir = delta / max(d, 0.001);
|
|
177
|
+
float amp = 1.0;
|
|
178
|
+
// The TRAIN. A drop on water does not make one ring, it makes several
|
|
179
|
+
// from the same point a moment apart, each fainter than the one in
|
|
180
|
+
// front. They cost no state: the k-th is this touch aged by k staggers,
|
|
181
|
+
// and one that has not started yet has a negative age and is skipped.
|
|
182
|
+
for (int k = 0; k < 5; k++) {
|
|
183
|
+
if (k >= uRings) { break; }
|
|
184
|
+
float t = drop.z - float(k) * uStagger;
|
|
185
|
+
if (t <= 0.0) { break; }
|
|
186
|
+
float radius = t * uSpeed;
|
|
187
|
+
// A Gaussian ring: the wave only exists near the front, so the rest
|
|
188
|
+
// of the page is sampled where it was drawn and stays sharp.
|
|
189
|
+
float e = (d - radius) / uWidth;
|
|
190
|
+
float env = exp(-e * e);
|
|
191
|
+
float fade = env * exp(-t * uDecay) * amp;
|
|
192
|
+
float w = sin((d - radius) * 0.16) * fade;
|
|
193
|
+
push = push + dir * w;
|
|
194
|
+
crest = crest + w;
|
|
195
|
+
// The envelope, kept apart from the signal: it is the amplitude
|
|
196
|
+
// with the oscillation divided out, so unlike the wave itself it
|
|
197
|
+
// does not pass through zero twice per wavelength.
|
|
198
|
+
energy = energy + fade;
|
|
199
|
+
amp = amp * uFalloff;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return float4(push.x, push.y, crest, energy);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
half4 main(float2 fragCoord) {
|
|
206
|
+
// Device pixels to page pixels. Both spaces run y DOWN — the flips the
|
|
207
|
+
// WebGL host needs are a GL texture's business, not a view's.
|
|
208
|
+
float2 p = float2(fragCoord.x / uScale + uPanX, fragCoord.y / uScale);
|
|
209
|
+
float4 w = waveAt(p);
|
|
210
|
+
|
|
211
|
+
// NOTHING HAPPENING HERE, AND THAT IS MOST OF THE SCREEN.
|
|
212
|
+
//
|
|
213
|
+
// A ring is a thin annulus travelling across a page that is otherwise
|
|
214
|
+
// exactly what was drawn. Answering those pixels immediately skips the two
|
|
215
|
+
// extra wave sums the glint's gradient costs and the three extra texture
|
|
216
|
+
// reads the aberration costs — the difference between an effect that costs
|
|
217
|
+
// a ring and one that costs a screen, which on a software-rendered emulator
|
|
218
|
+
// is the difference between an effect and a hang.
|
|
219
|
+
if (w.w < 0.0015 && abs(w.z) < 0.0015) {
|
|
220
|
+
return uContent.eval(fragCoord);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Back to device pixels to sample with.
|
|
224
|
+
float2 offset = w.xy * uStrength * uScale;
|
|
225
|
+
|
|
226
|
+
// A whisper of chromatic aberration along the crest. 1.08 and 0.92 rather
|
|
227
|
+
// than anything bolder: past about a tenth the page stops looking like
|
|
228
|
+
// water and starts looking like a filter.
|
|
229
|
+
float r = uContent.eval(fragCoord + offset * 1.08).r;
|
|
230
|
+
float g = uContent.eval(fragCoord + offset).g;
|
|
231
|
+
float b = uContent.eval(fragCoord + offset * 0.92).b;
|
|
232
|
+
float a = uContent.eval(fragCoord + offset).a;
|
|
233
|
+
|
|
234
|
+
// THE SURFACE'S OWN NORMAL. The wave sum is a height field, its gradient is
|
|
235
|
+
// the slope, and the slope is the normal — so nothing has to be stored to
|
|
236
|
+
// light this that the displacement did not already need. AGSL has no
|
|
237
|
+
// dFdx/dFdy, so the gradient is a finite difference one PAGE pixel away,
|
|
238
|
+
// divided by the scale to be the per-device-pixel slope the WebGL host's
|
|
239
|
+
// derivative gives it.
|
|
240
|
+
float wx = waveAt(p + float2(1.0, 0.0)).z - w.z;
|
|
241
|
+
float wy = waveAt(p + float2(0.0, 1.0)).z - w.z;
|
|
242
|
+
float2 grad = float2(wx, wy) * uBump / max(uScale, 0.001);
|
|
243
|
+
float3 N = normalize(float3(-grad.x, -grad.y, 1.0));
|
|
244
|
+
|
|
245
|
+
// Blinn-Phong. The eye looks straight down at a flat page, so V is +Z.
|
|
246
|
+
float3 L = normalize(uLight);
|
|
247
|
+
float3 V = float3(0.0, 0.0, 1.0);
|
|
248
|
+
float3 H = normalize(L + V);
|
|
249
|
+
float spec = pow(max(dot(N, H), 0.0), uGloss) * uShine;
|
|
250
|
+
|
|
251
|
+
// The specular lives on the FLANK of a wave rather than its top, which is
|
|
252
|
+
// why the glint slides along the ring as the ring travels. It is faded out
|
|
253
|
+
// where there is no ring — by the ENVELOPE, not by the wave, which crosses
|
|
254
|
+
// zero twice per wavelength and would cut a dark seam through every glint.
|
|
255
|
+
float alive = clamp(w.w * 3.0, 0.0, 1.0);
|
|
256
|
+
|
|
257
|
+
float3 col = float3(r, g, b) + float3(w.z * uHi + spec * alive);
|
|
258
|
+
return half4(half3(col), half(a));
|
|
259
|
+
}
|
|
260
|
+
""".trimIndent()
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
package fi.ranger.evg
|
|
2
|
+
|
|
3
|
+
import java.awt.AlphaComposite
|
|
4
|
+
import java.awt.BasicStroke
|
|
5
|
+
import java.awt.Color
|
|
6
|
+
import java.awt.GradientPaint
|
|
7
|
+
import java.awt.Graphics2D
|
|
8
|
+
import java.awt.RenderingHints
|
|
9
|
+
import java.awt.Shape
|
|
10
|
+
import java.awt.geom.AffineTransform
|
|
11
|
+
import java.awt.geom.Path2D
|
|
12
|
+
import java.awt.geom.RoundRectangle2D
|
|
13
|
+
import java.awt.image.BufferedImage
|
|
14
|
+
import java.io.ByteArrayInputStream
|
|
15
|
+
import java.io.File
|
|
16
|
+
import javax.imageio.ImageIO
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The same display list, painted with Java2D.
|
|
20
|
+
*
|
|
21
|
+
* This exists so the port can be *checked*. An `android.graphics.Canvas`
|
|
22
|
+
* painter can only be run on a device or an emulator, and neither is available
|
|
23
|
+
* where this repository's tests run; Java2D is in every JDK. Both surfaces
|
|
24
|
+
* implement the same interface and are driven by the same [EvgPainter], so a
|
|
25
|
+
* page that renders correctly here is evidence about the walk, the geometry,
|
|
26
|
+
* the colours, the clip stack and the text placement — everything except the
|
|
27
|
+
* last page of platform delegation.
|
|
28
|
+
*
|
|
29
|
+
* It is also a usable desktop host in its own right: `RenderDeck` turns a
|
|
30
|
+
* `.pptx` into PNGs with it, and `CheckDashboard` does the same for the ui
|
|
31
|
+
* gallery's dashboard.
|
|
32
|
+
*/
|
|
33
|
+
class AwtEvgSurface(
|
|
34
|
+
private val g: Graphics2D,
|
|
35
|
+
/** The deck's own picture parts, by package path. */
|
|
36
|
+
private val images: Map<String, ByteArray>,
|
|
37
|
+
/**
|
|
38
|
+
* The faces to draw with — the SAME [AwtFaces] the measurer was installed
|
|
39
|
+
* with ([AwtTextMeasurer.install]), so what the layout measured is what
|
|
40
|
+
* is painted. The secondary constructor takes the files and makes its
|
|
41
|
+
* own, for a host that measures with the table.
|
|
42
|
+
*/
|
|
43
|
+
private val faces: AwtFaces,
|
|
44
|
+
) : EvgSurface {
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
g: Graphics2D,
|
|
48
|
+
images: Map<String, ByteArray>,
|
|
49
|
+
/** TrueType files to draw with, by the family name EVG measured under. */
|
|
50
|
+
fontFiles: Map<String, File>,
|
|
51
|
+
) : this(g, images, AwtFaces(fontFiles))
|
|
52
|
+
|
|
53
|
+
private val stack = ArrayDeque<State>()
|
|
54
|
+
private val imageCache = HashMap<String, BufferedImage?>()
|
|
55
|
+
|
|
56
|
+
private data class State(val clip: Shape?, val transform: AffineTransform)
|
|
57
|
+
|
|
58
|
+
init {
|
|
59
|
+
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
|
60
|
+
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
|
|
61
|
+
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
|
|
62
|
+
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE)
|
|
63
|
+
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun save() {
|
|
67
|
+
stack.addLast(State(g.clip, AffineTransform(g.transform)))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun restore() {
|
|
71
|
+
val s = stack.removeLastOrNull() ?: return
|
|
72
|
+
g.transform = s.transform
|
|
73
|
+
g.clip = s.clip
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override fun clipRect(x: Float, y: Float, w: Float, h: Float) {
|
|
77
|
+
g.clipRect(x.toInt(), y.toInt(), Math.ceil(w.toDouble()).toInt(), Math.ceil(h.toDouble()).toInt())
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override fun rotate(degrees: Float, px: Float, py: Float) {
|
|
81
|
+
g.rotate(Math.toRadians(degrees.toDouble()), px.toDouble(), py.toDouble())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
override fun fillRect(
|
|
85
|
+
x: Float, y: Float, w: Float, h: Float,
|
|
86
|
+
radius: Float, color: Int, gradient: EvgGradient?, shadow: EvgShadow?,
|
|
87
|
+
) {
|
|
88
|
+
if (w <= 0f || h <= 0f) return
|
|
89
|
+
val shape = roundRect(x, y, w, h, radius)
|
|
90
|
+
shadow?.let { paintShadow(shape, it) }
|
|
91
|
+
if (gradient != null) {
|
|
92
|
+
g.paint = if (gradient.vertical) {
|
|
93
|
+
GradientPaint(x, y, awt(gradient.color1), x, y + h, awt(gradient.color2))
|
|
94
|
+
} else {
|
|
95
|
+
GradientPaint(x, y, awt(gradient.color1), x + w, y, awt(gradient.color2))
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
if (alphaOf(color) == 0) return
|
|
99
|
+
g.paint = awt(color)
|
|
100
|
+
}
|
|
101
|
+
g.fill(shape)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
override fun strokeRect(
|
|
105
|
+
x: Float, y: Float, w: Float, h: Float,
|
|
106
|
+
radius: Float, thickness: Float, color: Int,
|
|
107
|
+
) {
|
|
108
|
+
if (w <= 0f || h <= 0f) return
|
|
109
|
+
// EVG's border is a band of `thickness` kept INSIDE the box, so the
|
|
110
|
+
// stroke is centred half a thickness in from the edge.
|
|
111
|
+
val half = thickness / 2f
|
|
112
|
+
val shape = roundRect(x + half, y + half, w - thickness, h - thickness, (radius - half).coerceAtLeast(0f))
|
|
113
|
+
g.paint = awt(color)
|
|
114
|
+
g.stroke = BasicStroke(thickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)
|
|
115
|
+
g.draw(shape)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
override fun fillPath(
|
|
119
|
+
pts: List<Double>, ringEnds: List<Int>, evenOdd: Boolean,
|
|
120
|
+
color: Int, shadow: EvgShadow?,
|
|
121
|
+
) {
|
|
122
|
+
val path = buildPath(pts, ringEnds, evenOdd, close = true) ?: return
|
|
123
|
+
shadow?.let { paintShadow(path, it) }
|
|
124
|
+
g.paint = awt(color)
|
|
125
|
+
g.fill(path)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override fun strokePath(pts: List<Double>, ringEnds: List<Int>, thickness: Float, color: Int) {
|
|
129
|
+
val path = buildPath(pts, ringEnds, evenOdd = false, close = false) ?: return
|
|
130
|
+
g.paint = awt(color)
|
|
131
|
+
g.stroke = BasicStroke(thickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)
|
|
132
|
+
g.draw(path)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
override fun drawImage(
|
|
136
|
+
src: String, x: Float, y: Float, w: Float, h: Float,
|
|
137
|
+
radius: Float, flipH: Boolean, flipV: Boolean,
|
|
138
|
+
) {
|
|
139
|
+
if (w <= 0f || h <= 0f) return
|
|
140
|
+
val img = imageCache.getOrPut(src) {
|
|
141
|
+
images[src]?.let { bytes -> runCatching { ImageIO.read(ByteArrayInputStream(bytes)) }.getOrNull() }
|
|
142
|
+
}
|
|
143
|
+
if (img == null) {
|
|
144
|
+
// A missing texture and an empty slide must not look the same.
|
|
145
|
+
g.paint = Color(180, 198, 230)
|
|
146
|
+
g.fill(roundRect(x, y, w, h, radius))
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
val old = g.clip
|
|
150
|
+
if (radius > 0.5f) g.clip(roundRect(x, y, w, h, radius))
|
|
151
|
+
val sx = if (flipH) -1.0 else 1.0
|
|
152
|
+
val sy = if (flipV) -1.0 else 1.0
|
|
153
|
+
val at = AffineTransform()
|
|
154
|
+
at.translate((x + if (flipH) w else 0f).toDouble(), (y + if (flipV) h else 0f).toDouble())
|
|
155
|
+
at.scale(sx * w / img.width, sy * h / img.height)
|
|
156
|
+
g.drawImage(img, at, null)
|
|
157
|
+
g.clip = old
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
override fun drawTextRun(
|
|
161
|
+
text: String, x: Float, top: Float, sizePx: Float,
|
|
162
|
+
family: String, bold: Boolean, italic: Boolean, color: Int,
|
|
163
|
+
) {
|
|
164
|
+
if (sizePx <= 0f) return
|
|
165
|
+
val font = fontFor(family, sizePx, bold, italic)
|
|
166
|
+
g.font = font
|
|
167
|
+
g.paint = awt(color)
|
|
168
|
+
// `top` is the top of the line box; the baseline is one face-ascent
|
|
169
|
+
// below it. This is the same rule the WebGL atlas and the software
|
|
170
|
+
// rasteriser place a run by.
|
|
171
|
+
val baseline = top + g.fontMetrics.ascent
|
|
172
|
+
g.drawString(text, x, baseline)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// --- the parts that are only arithmetic ----------------------------------
|
|
176
|
+
|
|
177
|
+
private fun roundRect(x: Float, y: Float, w: Float, h: Float, radius: Float): Shape {
|
|
178
|
+
if (radius <= 0.5f) {
|
|
179
|
+
return java.awt.geom.Rectangle2D.Float(x, y, w, h)
|
|
180
|
+
}
|
|
181
|
+
val r = radius.coerceAtMost(minOf(w, h) / 2f) * 2f
|
|
182
|
+
return RoundRectangle2D.Float(x, y, w, h, r, r)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private fun buildPath(pts: List<Double>, ringEnds: List<Int>, evenOdd: Boolean, close: Boolean): Path2D.Float? {
|
|
186
|
+
if (pts.size < 4) return null
|
|
187
|
+
val path = Path2D.Float(if (evenOdd) Path2D.WIND_EVEN_ODD else Path2D.WIND_NON_ZERO)
|
|
188
|
+
var start = 0
|
|
189
|
+
for (end in ringEnds) {
|
|
190
|
+
val stop = end.coerceAtMost(pts.size)
|
|
191
|
+
if (stop - start < 4) { start = stop; continue }
|
|
192
|
+
path.moveTo(pts[start], pts[start + 1])
|
|
193
|
+
var i = start + 2
|
|
194
|
+
while (i + 1 < stop) {
|
|
195
|
+
path.lineTo(pts[i], pts[i + 1])
|
|
196
|
+
i += 2
|
|
197
|
+
}
|
|
198
|
+
if (close) path.closePath()
|
|
199
|
+
start = stop
|
|
200
|
+
}
|
|
201
|
+
return if (path.currentPoint == null) null else path
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Java2D has no blur, so the soft edge is faked the way a shadow was faked
|
|
206
|
+
* before anyone had one: the silhouette drawn a few times, growing and
|
|
207
|
+
* fading. Cheap, and close enough to tell a shadowed shape from a flat one
|
|
208
|
+
* — which is all this surface is asked to prove.
|
|
209
|
+
*/
|
|
210
|
+
private fun paintShadow(shape: Shape, shadow: EvgShadow) {
|
|
211
|
+
val steps = if (shadow.blur > 0.5f) 4 else 1
|
|
212
|
+
val old = g.composite
|
|
213
|
+
val at = AffineTransform.getTranslateInstance(shadow.dx.toDouble(), shadow.dy.toDouble())
|
|
214
|
+
val moved = at.createTransformedShape(shape)
|
|
215
|
+
val base = alphaOf(shadow.color) / 255f
|
|
216
|
+
for (s in 0 until steps) {
|
|
217
|
+
val grow = shadow.blur * s / steps
|
|
218
|
+
val a = base * (1f - s.toFloat() / steps) / steps * 2f
|
|
219
|
+
g.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, a.coerceIn(0f, 1f))
|
|
220
|
+
g.paint = Color(shadow.color and 0xFFFFFF, false)
|
|
221
|
+
if (grow <= 0f) {
|
|
222
|
+
g.fill(moved)
|
|
223
|
+
} else {
|
|
224
|
+
g.stroke = BasicStroke(grow * 2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)
|
|
225
|
+
g.fill(moved)
|
|
226
|
+
g.draw(moved)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
g.composite = old
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private fun fontFor(family: String, sizePx: Float, bold: Boolean, italic: Boolean): Font =
|
|
233
|
+
faces.font(family, sizePx, bold, italic)
|
|
234
|
+
|
|
235
|
+
private fun awt(argb: Int): Color = Color(argb, true)
|
|
236
|
+
|
|
237
|
+
private fun alphaOf(argb: Int): Int = (argb ushr 24) and 0xFF
|
|
238
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
package fi.ranger.evg
|
|
3
|
+
|
|
4
|
+
import fi.ranger.rgr.EVGDefaultMeasurer
|
|
5
|
+
import fi.ranger.rgr.EVGHostTextMeasurer
|
|
6
|
+
import java.awt.Font
|
|
7
|
+
import java.awt.font.FontRenderContext
|
|
8
|
+
import java.io.File
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The faces a Java2D host draws with, resolved ONE way.
|
|
12
|
+
*
|
|
13
|
+
* [AwtEvgSurface] used to resolve a family to a `Font` privately, per
|
|
14
|
+
* surface — and a surface is made per `Graphics2D`, so per frame. With a
|
|
15
|
+
* measurer that has to resolve the same family to the same face without a
|
|
16
|
+
* `Graphics2D` in hand, the resolution moves here and both ask it: a family
|
|
17
|
+
* named in [fontFiles] is that file, anything else is the first file or the
|
|
18
|
+
* JDK's sans, and bold and italic are derived styles of it. Whatever the
|
|
19
|
+
* rule is, there is one of it.
|
|
20
|
+
*/
|
|
21
|
+
class AwtFaces(fontFiles: Map<String, File>) {
|
|
22
|
+
|
|
23
|
+
private data class FontKey(val family: String, val size: Int, val bold: Boolean, val italic: Boolean)
|
|
24
|
+
|
|
25
|
+
private val fontCache = HashMap<FontKey, Font>()
|
|
26
|
+
private val baseFonts = HashMap<String, Font>()
|
|
27
|
+
private var fallback: Font = Font(Font.SANS_SERIF, Font.PLAIN, 12)
|
|
28
|
+
|
|
29
|
+
init {
|
|
30
|
+
for ((family, file) in fontFiles) {
|
|
31
|
+
val f = runCatching { Font.createFont(Font.TRUETYPE_FONT, file) }.getOrNull() ?: continue
|
|
32
|
+
baseFonts[family.lowercase()] = f
|
|
33
|
+
if (baseFonts.size == 1) fallback = f
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun font(family: String, sizePx: Float, bold: Boolean, italic: Boolean): Font {
|
|
38
|
+
val key = FontKey(family.lowercase(), Math.round(sizePx * 4), bold, italic)
|
|
39
|
+
synchronized(fontCache) {
|
|
40
|
+
return fontCache.getOrPut(key) {
|
|
41
|
+
val base = baseFonts[family.lowercase()] ?: fallback
|
|
42
|
+
var style = Font.PLAIN
|
|
43
|
+
if (bold) style = style or Font.BOLD
|
|
44
|
+
if (italic) style = style or Font.ITALIC
|
|
45
|
+
base.deriveFont(style, sizePx)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Java2D measures, EVG breaks the lines — the desktop twin of
|
|
53
|
+
* [AndroidTextMeasurer], for the same reason [AwtEvgSurface] is the twin of
|
|
54
|
+
* [AndroidEvgSurface]: so the port can be CHECKED where there is no device.
|
|
55
|
+
* A check that lays out with the table and paints with Java2D is asserting
|
|
56
|
+
* about two faces; with this installed it asserts about one.
|
|
57
|
+
*
|
|
58
|
+
* Widths come from `Font.getStringBounds` with a fractional-metrics render
|
|
59
|
+
* context, which is the advance Java2D lays the string out to when the
|
|
60
|
+
* surface's anti-aliasing hints are on; the vertical numbers from
|
|
61
|
+
* `getLineMetrics`, which is the face's, not the string's.
|
|
62
|
+
*/
|
|
63
|
+
object AwtTextMeasurer {
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Make the measurer, attach Java2D to it through [faces] and make it the
|
|
67
|
+
* default every `EVGLayout` and `EVGTextEngine` in the process starts
|
|
68
|
+
* from. Hand the SAME [AwtFaces] to every [AwtEvgSurface] the check makes.
|
|
69
|
+
*/
|
|
70
|
+
fun install(faces: AwtFaces): EVGHostTextMeasurer {
|
|
71
|
+
val frc = FontRenderContext(null, true, true)
|
|
72
|
+
val measurer = EVGHostTextMeasurer()
|
|
73
|
+
measurer.attach({ kind, text, family, size, bold, italic ->
|
|
74
|
+
val font = faces.font(family, size.toFloat(), bold, italic)
|
|
75
|
+
when (kind) {
|
|
76
|
+
0 -> if (text.isEmpty()) 0.0 else font.getStringBounds(text, frc).width
|
|
77
|
+
1 -> font.getLineMetrics("Hg", frc).ascent.toDouble()
|
|
78
|
+
2 -> font.getLineMetrics("Hg", frc).descent.toDouble()
|
|
79
|
+
else -> font.getLineMetrics("Hg", frc).leading.toDouble()
|
|
80
|
+
}
|
|
81
|
+
}, "awt")
|
|
82
|
+
EVGDefaultMeasurer.install(measurer)
|
|
83
|
+
return measurer
|
|
84
|
+
}
|
|
85
|
+
}
|