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,329 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// The display list, painted with CoreGraphics.
|
|
4
|
+
//
|
|
5
|
+
// One backend for every Apple platform, on purpose. A `UIView` hands out a
|
|
6
|
+
// `CGContext` from `UIGraphicsGetCurrentContext()`, a SwiftUI `Canvas` hands
|
|
7
|
+
// one out from `GraphicsContext.withCGContext`, and a PDF or a bitmap context
|
|
8
|
+
// is the same object again — so an iPhone, an iPad, an Apple Watch and an
|
|
9
|
+
// off-screen render all go through this file rather than through four.
|
|
10
|
+
//
|
|
11
|
+
// The context is assumed FLIPPED: origin top left, y increasing downwards,
|
|
12
|
+
// which is what UIKit and SwiftUI both hand you and what the display list is
|
|
13
|
+
// already in. The one place that has to know is the text, because CoreText
|
|
14
|
+
// draws glyphs the other way up unless the text matrix says otherwise.
|
|
15
|
+
//
|
|
16
|
+
// Text is CoreText rather than UIKit for one reason: `CTFont` exists on every
|
|
17
|
+
// Apple platform including watchOS, and this file is the same file there.
|
|
18
|
+
|
|
19
|
+
import CoreGraphics
|
|
20
|
+
import CoreText
|
|
21
|
+
import Foundation
|
|
22
|
+
|
|
23
|
+
/// Faces, kept for the life of the process rather than the life of a surface.
|
|
24
|
+
///
|
|
25
|
+
/// A surface wraps the `CGContext` the system hands to `draw(_:)`, and that is
|
|
26
|
+
/// a different object every frame — so a surface is made per frame, and a cache
|
|
27
|
+
/// owned by one is a cache thrown away sixty times a second. That is exactly
|
|
28
|
+
/// what the cache exists to prevent: a page draws around 190 text runs a frame
|
|
29
|
+
/// and asks for about six distinct faces, and `CTFontCreateUIFontForLanguage`
|
|
30
|
+
/// plus `CTFontCreateCopyWithSymbolicTraits` are not cheap enough to run per
|
|
31
|
+
/// run. Tapping a tab took most of a second on a phone with this cache being
|
|
32
|
+
/// discarded.
|
|
33
|
+
///
|
|
34
|
+
/// A `CTFont` is immutable and safe to share. The lock is here because the type
|
|
35
|
+
/// is `Sendable` by assertion and a future host may paint off the main thread;
|
|
36
|
+
/// it is uncontended in the one-thread case that actually happens.
|
|
37
|
+
final class EvgFontCache: @unchecked Sendable {
|
|
38
|
+
|
|
39
|
+
static let shared = EvgFontCache()
|
|
40
|
+
|
|
41
|
+
private var faces: [String: CTFont] = [:]
|
|
42
|
+
private let lock = NSLock()
|
|
43
|
+
|
|
44
|
+
func face(size: CGFloat, family: String, bold: Bool, italic: Bool) -> CTFont {
|
|
45
|
+
let key = "\(family)|\(size)|\(bold)|\(italic)"
|
|
46
|
+
lock.lock()
|
|
47
|
+
if let cached = faces[key] {
|
|
48
|
+
lock.unlock()
|
|
49
|
+
return cached
|
|
50
|
+
}
|
|
51
|
+
lock.unlock()
|
|
52
|
+
|
|
53
|
+
let made = EvgFontCache.make(size: size, family: family, bold: bold, italic: italic)
|
|
54
|
+
lock.lock()
|
|
55
|
+
faces[key] = made
|
|
56
|
+
lock.unlock()
|
|
57
|
+
return made
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static func make(size: CGFloat, family: String, bold: Bool, italic: Bool) -> CTFont {
|
|
61
|
+
let lower = family.lowercased()
|
|
62
|
+
var base: CTFont
|
|
63
|
+
if lower.contains("mono") || lower.contains("courier") || lower.contains("consol") {
|
|
64
|
+
base = CTFontCreateWithName("Menlo" as CFString, size, nil)
|
|
65
|
+
} else if lower.contains("serif") && !lower.contains("sans") {
|
|
66
|
+
base = CTFontCreateWithName("Georgia" as CFString, size, nil)
|
|
67
|
+
} else {
|
|
68
|
+
// The platform's own sans. `gallery/ui` lays out with EVG's width
|
|
69
|
+
// estimate rather than a face, exactly as the browser build does,
|
|
70
|
+
// so bundling a TrueType file here would make the picture *less*
|
|
71
|
+
// like the one the demo is checked against, not more.
|
|
72
|
+
base = CTFontCreateUIFontForLanguage(.system, size, nil)
|
|
73
|
+
?? CTFontCreateWithName("Helvetica" as CFString, size, nil)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
var traits: CTFontSymbolicTraits = []
|
|
77
|
+
if bold { traits.insert(.traitBold) }
|
|
78
|
+
if italic { traits.insert(.traitItalic) }
|
|
79
|
+
if !traits.isEmpty {
|
|
80
|
+
if let styled = CTFontCreateCopyWithSymbolicTraits(base, size, nil, traits, traits) {
|
|
81
|
+
base = styled
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return base
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
final class CoreGraphicsEvgSurface: EvgSurface {
|
|
89
|
+
|
|
90
|
+
private let ctx: CGContext
|
|
91
|
+
|
|
92
|
+
init(context: CGContext) {
|
|
93
|
+
self.ctx = context
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// MARK: - state
|
|
97
|
+
|
|
98
|
+
func save() {
|
|
99
|
+
ctx.saveGState()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func restore() {
|
|
103
|
+
ctx.restoreGState()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func clipRect(x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat) {
|
|
107
|
+
ctx.clip(to: CGRect(x: x, y: y, width: w, height: h))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func rotate(degrees: CGFloat, px: CGFloat, py: CGFloat) {
|
|
111
|
+
ctx.translateBy(x: px, y: py)
|
|
112
|
+
ctx.rotate(by: degrees * .pi / 180.0)
|
|
113
|
+
ctx.translateBy(x: -px, y: -py)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// MARK: - boxes
|
|
117
|
+
|
|
118
|
+
private func boxPath(x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat, radius: CGFloat) -> CGPath {
|
|
119
|
+
let rect = CGRect(x: x, y: y, width: w, height: h)
|
|
120
|
+
if radius <= 0.0 {
|
|
121
|
+
return CGPath(rect: rect, transform: nil)
|
|
122
|
+
}
|
|
123
|
+
// A radius larger than half the box is a pill, and CoreGraphics draws
|
|
124
|
+
// something wrong rather than a pill if it is not clamped.
|
|
125
|
+
let r = min(radius, min(w, h) * 0.5)
|
|
126
|
+
return CGPath(roundedRect: rect, cornerWidth: r, cornerHeight: r, transform: nil)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private func applyShadow(_ shadow: EvgShadow?) {
|
|
130
|
+
guard let s = shadow else { return }
|
|
131
|
+
ctx.setShadow(offset: CGSize(width: s.dx, height: s.dy), blur: s.blur, color: s.color.cgColor)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func fillRect(
|
|
135
|
+
x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat,
|
|
136
|
+
radius: CGFloat,
|
|
137
|
+
color: EvgColor,
|
|
138
|
+
gradient: EvgGradient?,
|
|
139
|
+
shadow: EvgShadow?
|
|
140
|
+
) {
|
|
141
|
+
if w <= 0 || h <= 0 { return }
|
|
142
|
+
let path = boxPath(x: x, y: y, w: w, h: h, radius: radius)
|
|
143
|
+
|
|
144
|
+
guard let grad = gradient else {
|
|
145
|
+
ctx.saveGState()
|
|
146
|
+
applyShadow(shadow)
|
|
147
|
+
ctx.addPath(path)
|
|
148
|
+
ctx.setFillColor(color.cgColor)
|
|
149
|
+
ctx.fillPath()
|
|
150
|
+
ctx.restoreGState()
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// A gradient cannot be a fill colour, so the shape becomes a clip and
|
|
155
|
+
// the gradient is drawn through it. The shadow is the SHAPE's, not the
|
|
156
|
+
// gradient's, so it is painted first as a flat fill of the first stop —
|
|
157
|
+
// a gradient box under a shadow casts the box's silhouette.
|
|
158
|
+
if shadow != nil {
|
|
159
|
+
ctx.saveGState()
|
|
160
|
+
applyShadow(shadow)
|
|
161
|
+
ctx.addPath(path)
|
|
162
|
+
ctx.setFillColor(grad.color1.cgColor)
|
|
163
|
+
ctx.fillPath()
|
|
164
|
+
ctx.restoreGState()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
ctx.saveGState()
|
|
168
|
+
ctx.addPath(path)
|
|
169
|
+
ctx.clip()
|
|
170
|
+
let space = CGColorSpaceCreateDeviceRGB()
|
|
171
|
+
let colors = [grad.color1.cgColor, grad.color2.cgColor] as CFArray
|
|
172
|
+
if let cg = CGGradient(colorsSpace: space, colors: colors, locations: [0.0, 1.0]) {
|
|
173
|
+
let start = CGPoint(x: x, y: y)
|
|
174
|
+
let end = grad.vertical ? CGPoint(x: x, y: y + h) : CGPoint(x: x + w, y: y)
|
|
175
|
+
ctx.drawLinearGradient(cg, start: start, end: end, options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
|
|
176
|
+
}
|
|
177
|
+
ctx.restoreGState()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
func strokeRect(
|
|
181
|
+
x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat,
|
|
182
|
+
radius: CGFloat, thickness: CGFloat,
|
|
183
|
+
color: EvgColor
|
|
184
|
+
) {
|
|
185
|
+
if w <= 0 || h <= 0 { return }
|
|
186
|
+
// EVG's border is a band INSIDE the box, and a CoreGraphics stroke
|
|
187
|
+
// straddles the path. Insetting by half the thickness puts the band
|
|
188
|
+
// where the layout said it was — which is what keeps a one-point
|
|
189
|
+
// hairline from spilling over the edge of a clipped scroll region.
|
|
190
|
+
let half = thickness * 0.5
|
|
191
|
+
let inset = CGRect(x: x + half, y: y + half, width: max(0, w - thickness), height: max(0, h - thickness))
|
|
192
|
+
let r = radius > 0 ? max(0, min(radius - half, min(inset.width, inset.height) * 0.5)) : 0
|
|
193
|
+
let path = r > 0
|
|
194
|
+
? CGPath(roundedRect: inset, cornerWidth: r, cornerHeight: r, transform: nil)
|
|
195
|
+
: CGPath(rect: inset, transform: nil)
|
|
196
|
+
ctx.saveGState()
|
|
197
|
+
ctx.addPath(path)
|
|
198
|
+
ctx.setStrokeColor(color.cgColor)
|
|
199
|
+
ctx.setLineWidth(thickness)
|
|
200
|
+
ctx.strokePath()
|
|
201
|
+
ctx.restoreGState()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// MARK: - vector outlines
|
|
205
|
+
|
|
206
|
+
/// `pts` is flat — x, y, x, y — and `ringEnds` holds the index one past
|
|
207
|
+
/// each ring's last coordinate, so a shape with a hole arrives as two
|
|
208
|
+
/// rings and is filled as one path with two subpaths.
|
|
209
|
+
private func buildPath(pts: [Double], ringEnds: [Int], close: Bool) -> CGPath {
|
|
210
|
+
let path = CGMutablePath()
|
|
211
|
+
var start = 0
|
|
212
|
+
for rawEnd in ringEnds {
|
|
213
|
+
let end = min(rawEnd, pts.count)
|
|
214
|
+
if end - start >= 4 {
|
|
215
|
+
path.move(to: CGPoint(x: pts[start], y: pts[start + 1]))
|
|
216
|
+
var i = start + 2
|
|
217
|
+
while i + 1 < end {
|
|
218
|
+
path.addLine(to: CGPoint(x: pts[i], y: pts[i + 1]))
|
|
219
|
+
i += 2
|
|
220
|
+
}
|
|
221
|
+
if close { path.closeSubpath() }
|
|
222
|
+
}
|
|
223
|
+
start = max(start, end)
|
|
224
|
+
}
|
|
225
|
+
return path
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
func fillPath(
|
|
229
|
+
pts: [Double], ringEnds: [Int], evenOdd: Bool,
|
|
230
|
+
color: EvgColor,
|
|
231
|
+
shadow: EvgShadow?
|
|
232
|
+
) {
|
|
233
|
+
let path = buildPath(pts: pts, ringEnds: ringEnds, close: true)
|
|
234
|
+
if path.isEmpty { return }
|
|
235
|
+
ctx.saveGState()
|
|
236
|
+
applyShadow(shadow)
|
|
237
|
+
ctx.addPath(path)
|
|
238
|
+
ctx.setFillColor(color.cgColor)
|
|
239
|
+
if evenOdd {
|
|
240
|
+
ctx.fillPath(using: .evenOdd)
|
|
241
|
+
} else {
|
|
242
|
+
ctx.fillPath(using: .winding)
|
|
243
|
+
}
|
|
244
|
+
ctx.restoreGState()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
func strokePath(
|
|
248
|
+
pts: [Double], ringEnds: [Int],
|
|
249
|
+
thickness: CGFloat,
|
|
250
|
+
color: EvgColor
|
|
251
|
+
) {
|
|
252
|
+
// Not closed: a stroked ring in this page is an icon outline or the
|
|
253
|
+
// chart's line, and closing it would draw a chord from the last point
|
|
254
|
+
// back to the first one.
|
|
255
|
+
let path = buildPath(pts: pts, ringEnds: ringEnds, close: false)
|
|
256
|
+
if path.isEmpty { return }
|
|
257
|
+
ctx.saveGState()
|
|
258
|
+
ctx.addPath(path)
|
|
259
|
+
ctx.setStrokeColor(color.cgColor)
|
|
260
|
+
ctx.setLineWidth(thickness)
|
|
261
|
+
ctx.setLineJoin(.round)
|
|
262
|
+
ctx.setLineCap(.round)
|
|
263
|
+
ctx.strokePath()
|
|
264
|
+
ctx.restoreGState()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// MARK: - images
|
|
268
|
+
|
|
269
|
+
/// This page reaches no images, and a surface that drew nothing for one
|
|
270
|
+
/// would be indistinguishable from a surface that drew it wrong. A visible
|
|
271
|
+
/// placeholder is the honest answer until an image cache is wired in.
|
|
272
|
+
func drawImage(
|
|
273
|
+
src: String,
|
|
274
|
+
x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat,
|
|
275
|
+
radius: CGFloat,
|
|
276
|
+
flipH: Bool, flipV: Bool
|
|
277
|
+
) {
|
|
278
|
+
if w <= 0 || h <= 0 { return }
|
|
279
|
+
let path = boxPath(x: x, y: y, w: w, h: h, radius: radius)
|
|
280
|
+
ctx.saveGState()
|
|
281
|
+
ctx.addPath(path)
|
|
282
|
+
ctx.setFillColor(EvgColor(r: 220, g: 220, b: 226, a: 1.0).cgColor)
|
|
283
|
+
ctx.fillPath()
|
|
284
|
+
ctx.addPath(path)
|
|
285
|
+
ctx.setStrokeColor(EvgColor(r: 150, g: 150, b: 160, a: 1.0).cgColor)
|
|
286
|
+
ctx.setLineWidth(1.0)
|
|
287
|
+
ctx.strokePath()
|
|
288
|
+
ctx.move(to: CGPoint(x: x, y: y))
|
|
289
|
+
ctx.addLine(to: CGPoint(x: x + w, y: y + h))
|
|
290
|
+
ctx.strokePath()
|
|
291
|
+
ctx.restoreGState()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// MARK: - text
|
|
295
|
+
|
|
296
|
+
private func fontFor(size: CGFloat, family: String, bold: Bool, italic: Bool) -> CTFont {
|
|
297
|
+
EvgFontCache.shared.face(size: size, family: family, bold: bold, italic: italic)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
func drawTextRun(
|
|
301
|
+
text: String,
|
|
302
|
+
x: CGFloat, top: CGFloat,
|
|
303
|
+
sizePx: CGFloat,
|
|
304
|
+
family: String, bold: Bool, italic: Bool,
|
|
305
|
+
color: EvgColor
|
|
306
|
+
) {
|
|
307
|
+
if text.isEmpty || sizePx <= 0 { return }
|
|
308
|
+
let font = fontFor(size: sizePx, family: family, bold: bold, italic: italic)
|
|
309
|
+
// CoreText's own attribute names, spelled the long way round.
|
|
310
|
+
// `kCTFontAttributeName` is a `CFString`; `NSAttributedString.Key` is
|
|
311
|
+
// not, and UIKit's `.foregroundColor` is a DIFFERENT string that
|
|
312
|
+
// CoreText does not read.
|
|
313
|
+
let attributes: [NSAttributedString.Key: Any] = [
|
|
314
|
+
NSAttributedString.Key(kCTFontAttributeName as String): font,
|
|
315
|
+
NSAttributedString.Key(kCTForegroundColorAttributeName as String): color.cgColor,
|
|
316
|
+
]
|
|
317
|
+
let line = CTLineCreateWithAttributedString(
|
|
318
|
+
NSAttributedString(string: text, attributes: attributes)
|
|
319
|
+
)
|
|
320
|
+
ctx.saveGState()
|
|
321
|
+
// `top` is the top of the line box; the baseline is one ascent below
|
|
322
|
+
// it. And the text matrix is flipped because the context is: without
|
|
323
|
+
// it CoreText draws every glyph upside down in a UIKit context.
|
|
324
|
+
ctx.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0)
|
|
325
|
+
ctx.textPosition = CGPoint(x: x, y: top + CTFontGetAscent(font))
|
|
326
|
+
CTLineDraw(line, ctx)
|
|
327
|
+
ctx.restoreGState()
|
|
328
|
+
}
|
|
329
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// CoreText measures, EVG breaks the lines.
|
|
4
|
+
//
|
|
5
|
+
// PLAN_NATIVE_HOSTS.md S0, the Apple half. `EVGHostTextMeasurer` (Ranger,
|
|
6
|
+
// compiled into the app's generated Swift) takes ONE function from the
|
|
7
|
+
// platform — a run's width, and a face's ascent, descent and leading — and
|
|
8
|
+
// every layout the app builds then measures with it. Before this the layout
|
|
9
|
+
// measured with the advance table, a snapshot of one browser's sans, and
|
|
10
|
+
// `CoreGraphicsEvgSurface` drew with whatever `EvgFontCache` resolved: two
|
|
11
|
+
// faces, and a caret or a label's box placed from the wrong one.
|
|
12
|
+
//
|
|
13
|
+
// The face is the SAME `CTFont` the surface draws with — `EvgFontCache.shared`
|
|
14
|
+
// is asked with the same four arguments `drawTextRun` asks it with — so the
|
|
15
|
+
// width the layout measured is the width `CTLineDraw` puts on the screen.
|
|
16
|
+
// `CTLineGetTypographicBounds` is the advance CoreText itself lays the line
|
|
17
|
+
// out to, kerning and all, which is what makes this exact rather than close.
|
|
18
|
+
//
|
|
19
|
+
// Install once, before the app's `start`, on any thread: `CTFont` is
|
|
20
|
+
// immutable and the cache is locked, so the layout may run off the main
|
|
21
|
+
// actor (S1) and measure where it lays out.
|
|
22
|
+
//
|
|
23
|
+
// Nothing here is compiled by this repository's CI — Swift for Apple needs a
|
|
24
|
+
// Mac — which is the same standing as the rest of this directory.
|
|
25
|
+
|
|
26
|
+
import CoreGraphics
|
|
27
|
+
import CoreText
|
|
28
|
+
import Foundation
|
|
29
|
+
|
|
30
|
+
enum CoreTextMeasurer {
|
|
31
|
+
|
|
32
|
+
/// Make the measurer, attach CoreText to it and make it the default every
|
|
33
|
+
/// `EVGLayout` and `EVGTextEngine` in the process starts from.
|
|
34
|
+
///
|
|
35
|
+
/// Idempotent in effect: a second call installs a second measurer, which
|
|
36
|
+
/// answers the same numbers from the same cache.
|
|
37
|
+
@discardableResult
|
|
38
|
+
static func install() -> EVGHostTextMeasurer {
|
|
39
|
+
let measurer = EVGHostTextMeasurer()
|
|
40
|
+
measurer.attach(f: metric, name: "coretext")
|
|
41
|
+
EVGDefaultMeasurer.install(m: measurer)
|
|
42
|
+
return measurer
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// The one function. `kind` 0 is the width of `text`; 1, 2 and 3 are the
|
|
46
|
+
/// face's ascent, descent and leading, for which `text` is empty.
|
|
47
|
+
static func metric(_ kind: Int, _ text: String, _ family: String,
|
|
48
|
+
_ size: Double, _ bold: Bool, _ italic: Bool) -> Double {
|
|
49
|
+
let font = EvgFontCache.shared.face(size: CGFloat(size), family: family, bold: bold, italic: italic)
|
|
50
|
+
switch kind {
|
|
51
|
+
case 0:
|
|
52
|
+
if text.isEmpty { return 0 }
|
|
53
|
+
let attributes: [NSAttributedString.Key: Any] = [
|
|
54
|
+
NSAttributedString.Key(kCTFontAttributeName as String): font,
|
|
55
|
+
]
|
|
56
|
+
let line = CTLineCreateWithAttributedString(
|
|
57
|
+
NSAttributedString(string: text, attributes: attributes)
|
|
58
|
+
)
|
|
59
|
+
return CTLineGetTypographicBounds(line, nil, nil, nil)
|
|
60
|
+
case 1:
|
|
61
|
+
return Double(CTFontGetAscent(font))
|
|
62
|
+
case 2:
|
|
63
|
+
return Double(CTFontGetDescent(font))
|
|
64
|
+
default:
|
|
65
|
+
// The face's own line gap. `line-height: normal` is ascent +
|
|
66
|
+
// descent + this, which is what CoreText's own layout uses too.
|
|
67
|
+
return Double(CTFontGetLeading(font))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// The engine off the main thread — Apple.
|
|
4
|
+
//
|
|
5
|
+
// PLAN_NATIVE_HOSTS.md S1. A Ranger app compiled to Swift is a value: no
|
|
6
|
+
// UIKit in it, no view, nothing that has to be on the main actor. What has
|
|
7
|
+
// to be on the main actor is the view that receives touches and the context
|
|
8
|
+
// that paints. So the app lives on ONE serial queue, every call the host
|
|
9
|
+
// makes runs there in the order the host made it, and a call that changed
|
|
10
|
+
// the page produces a frame — laid out and listed on the queue — that is
|
|
11
|
+
// handed to the main thread, which paints it and nothing else.
|
|
12
|
+
//
|
|
13
|
+
// Three verbs, and the discipline is that a host uses them and never touches
|
|
14
|
+
// the app directly:
|
|
15
|
+
//
|
|
16
|
+
// post { app in … } run there; return true if a frame is owed
|
|
17
|
+
// ask({ app in … }) { v in … } run there; answer on the main thread
|
|
18
|
+
// sync { app in … } run there and WAIT — for the one read UIKit
|
|
19
|
+
// insists on synchronously (`canBecomeFirstResponder`)
|
|
20
|
+
//
|
|
21
|
+
// Frames are coalesced: a burst of posts that each say "a frame is owed"
|
|
22
|
+
// produces one build after the last of them, and a build that is asked for
|
|
23
|
+
// while one is running produces exactly one more when it finishes — never a
|
|
24
|
+
// queue of stale frames the main thread has to draw through.
|
|
25
|
+
//
|
|
26
|
+
// `Frame` is whatever the host paints from: the display list alone, or the
|
|
27
|
+
// list with the viewport numbers the painter needs beside it, read on the
|
|
28
|
+
// queue so the main thread reads the app for nothing.
|
|
29
|
+
|
|
30
|
+
import Foundation
|
|
31
|
+
|
|
32
|
+
final class EvgEngineQueue<App, Frame> {
|
|
33
|
+
|
|
34
|
+
private let queue = DispatchQueue(label: "fi.ranger.evg.engine", qos: .userInteractive)
|
|
35
|
+
private let app: App
|
|
36
|
+
private let build: (App) -> Frame
|
|
37
|
+
private let deliver: (Frame) -> Void
|
|
38
|
+
|
|
39
|
+
// Touched on the queue only.
|
|
40
|
+
private var building = false
|
|
41
|
+
private var wanted = false
|
|
42
|
+
|
|
43
|
+
/// - Parameters:
|
|
44
|
+
/// - app: the Ranger app; make it BEFORE this, and never read it again
|
|
45
|
+
/// from the main thread
|
|
46
|
+
/// - build: the frame, run on the queue — `app.frame()` and whatever
|
|
47
|
+
/// the painter needs beside it
|
|
48
|
+
/// - deliver: called on the MAIN thread with each built frame; keep it
|
|
49
|
+
/// and `setNeedsDisplay`
|
|
50
|
+
init(app: App, build: @escaping (App) -> Frame, deliver: @escaping (Frame) -> Void) {
|
|
51
|
+
self.app = app
|
|
52
|
+
self.build = build
|
|
53
|
+
self.deliver = deliver
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Run `work` on the engine queue. A `true` result asks for a frame.
|
|
57
|
+
func post(_ work: @escaping (App) -> Bool) {
|
|
58
|
+
queue.async { [self] in
|
|
59
|
+
if work(app) { requestFrameOnQueue() }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Run `work` on the engine queue and hand its answer to `then` on the
|
|
64
|
+
/// main thread.
|
|
65
|
+
func ask<T>(_ work: @escaping (App) -> T, then: @escaping (T) -> Void) {
|
|
66
|
+
queue.async { [self] in
|
|
67
|
+
let v = work(app)
|
|
68
|
+
DispatchQueue.main.async { then(v) }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Run `work` on the engine queue and wait for it. For the reads UIKit
|
|
73
|
+
/// asks for synchronously and nothing else: it holds the caller for as
|
|
74
|
+
/// long as whatever is already on the queue takes.
|
|
75
|
+
func sync<T>(_ work: (App) -> T) -> T {
|
|
76
|
+
return queue.sync { work(app) }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// A frame is owed — the clock ticked, the window resized.
|
|
80
|
+
func invalidate() {
|
|
81
|
+
queue.async { [self] in requestFrameOnQueue() }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// On the queue.
|
|
85
|
+
private func requestFrameOnQueue() {
|
|
86
|
+
if building {
|
|
87
|
+
wanted = true
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
building = true
|
|
91
|
+
// Not inline: the posts already queued behind this one run first, so
|
|
92
|
+
// a press and the release that followed it are both in the frame.
|
|
93
|
+
queue.async { [self] in buildOnQueue() }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private func buildOnQueue() {
|
|
97
|
+
let f = build(app)
|
|
98
|
+
DispatchQueue.main.async { [self] in deliver(f) }
|
|
99
|
+
building = false
|
|
100
|
+
if wanted {
|
|
101
|
+
wanted = false
|
|
102
|
+
requestFrameOnQueue()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|