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,209 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// Pan and zoom on an EVG canvas, from the pointers the browser reports.
|
|
4
|
+
//
|
|
5
|
+
// Every page that draws a scene bigger than its canvas needs the same four
|
|
6
|
+
// gestures, and each of them had been written again in its own standalone:
|
|
7
|
+
// drag to pan, wheel to zoom, a press that does not travel is a click, and —
|
|
8
|
+
// missing everywhere — two fingers to pinch. This is that handling, once.
|
|
9
|
+
//
|
|
10
|
+
// WHAT IT DOES NOT DO. It never touches the scene: it reads the view the host
|
|
11
|
+
// gives it (`view()`) and hands back another (`setView()`), so the host keeps
|
|
12
|
+
// deciding what a view is, when to paint one, and how to coalesce them. The
|
|
13
|
+
// host's own `setView` is expected to schedule rather than paint — a pinch
|
|
14
|
+
// reports far more moves than a display can show.
|
|
15
|
+
//
|
|
16
|
+
// THE ANCHOR is what makes a zoom feel like a zoom: the point under the
|
|
17
|
+
// cursor, or under the midpoint of two fingers, stays where it is. The world
|
|
18
|
+
// point below it is `(screen - pan) / scale`, and keeping it there after the
|
|
19
|
+
// scale changes gives `pan' = screen - (screen - pan) * (scale' / scale)` —
|
|
20
|
+
// which is also what moves the picture when the fingers move, so a pinch pans
|
|
21
|
+
// and zooms in one expression rather than two that fight.
|
|
22
|
+
//
|
|
23
|
+
// A trackpad pinch is not a touch: browsers report it as a wheel with `ctrl`
|
|
24
|
+
// held, at a fraction of a notch per event. It gets its own factor, or a
|
|
25
|
+
// pinch crawls where a wheel flies. Safari's own `gesture*` events are read
|
|
26
|
+
// too, because a Safari trackpad may send those instead.
|
|
27
|
+
|
|
28
|
+
/** How far a press may travel and still be a click, in CSS pixels. */
|
|
29
|
+
const DRAG_SLOP = 4;
|
|
30
|
+
|
|
31
|
+
const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
32
|
+
const mid = (a, b) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Attach pan and zoom to a canvas.
|
|
36
|
+
*
|
|
37
|
+
* attachViewGestures(canvas, {
|
|
38
|
+
* view: () => ({ x, y, sc }), // the pan and scale in force
|
|
39
|
+
* setView: (x, y, sc) => {}, // schedule this one
|
|
40
|
+
* minZoom, maxZoom, // optional clamps
|
|
41
|
+
* onTap: (clientX, clientY) => {}, // a press that did not travel
|
|
42
|
+
* onCursor: (name) => {}, // "grab" / "grabbing", for the host's CSS
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* Returns a function that removes every listener again.
|
|
46
|
+
*/
|
|
47
|
+
export function attachViewGestures(el, opts) {
|
|
48
|
+
const view = opts.view;
|
|
49
|
+
const setView = opts.setView;
|
|
50
|
+
const minZoom = opts.minZoom ?? 0.05;
|
|
51
|
+
const maxZoom = opts.maxZoom ?? 16;
|
|
52
|
+
const onTap = opts.onTap || (() => {});
|
|
53
|
+
const onCursor = opts.onCursor || (() => {});
|
|
54
|
+
|
|
55
|
+
// Every pointer currently down on the canvas, in canvas coordinates.
|
|
56
|
+
const points = new Map();
|
|
57
|
+
let drag = null; // one pointer: a pan, or a click that has not moved yet
|
|
58
|
+
let pinch = null; // two pointers: the scale and the midpoint they started at
|
|
59
|
+
let gestureScale = 1;
|
|
60
|
+
|
|
61
|
+
const at = (ev) => {
|
|
62
|
+
const r = el.getBoundingClientRect();
|
|
63
|
+
return { x: ev.clientX - r.left, y: ev.clientY - r.top };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Scale by `factor` about a point on the canvas, which stays put. */
|
|
67
|
+
function zoomAbout(factor, px, py) {
|
|
68
|
+
const now = view();
|
|
69
|
+
const next = Math.min(maxZoom, Math.max(minZoom, now.sc * factor));
|
|
70
|
+
if (next === now.sc) return;
|
|
71
|
+
const k = next / now.sc;
|
|
72
|
+
setView(px - (px - now.x) * k, py - (py - now.y) * k, next);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function beginPinch() {
|
|
76
|
+
const [a, b] = [...points.values()];
|
|
77
|
+
pinch = { d0: dist(a, b), m0: mid(a, b), view: { ...view() } };
|
|
78
|
+
drag = null;
|
|
79
|
+
onCursor("grabbing");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function movePinch() {
|
|
83
|
+
const [a, b] = [...points.values()];
|
|
84
|
+
const d = dist(a, b);
|
|
85
|
+
if (!pinch.d0 || !d) return;
|
|
86
|
+
const m = mid(a, b);
|
|
87
|
+
const want = pinch.view.sc * (d / pinch.d0);
|
|
88
|
+
const sc = Math.min(maxZoom, Math.max(minZoom, want));
|
|
89
|
+
const k = sc / pinch.view.sc;
|
|
90
|
+
// The world point under the fingers' midpoint when the pinch began is
|
|
91
|
+
// put back under the midpoint where they are now: the spread scales,
|
|
92
|
+
// the travel pans.
|
|
93
|
+
setView(m.x - (pinch.m0.x - pinch.view.x) * k, m.y - (pinch.m0.y - pinch.view.y) * k, sc);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Capture keeps the moves coming when a finger leaves the canvas, and is
|
|
97
|
+
// not worth a broken gesture when the browser will not give it: a pointer
|
|
98
|
+
// the host synthesised has no capture to take.
|
|
99
|
+
const capture = (id) => { try { el.setPointerCapture(id); } catch (_) { /* no capture */ } };
|
|
100
|
+
const release = (id) => { try { if (el.hasPointerCapture(id)) el.releasePointerCapture(id); } catch (_) { /* gone */ } };
|
|
101
|
+
|
|
102
|
+
const onDown = (ev) => {
|
|
103
|
+
points.set(ev.pointerId, at(ev));
|
|
104
|
+
capture(ev.pointerId);
|
|
105
|
+
if (points.size === 2) {
|
|
106
|
+
beginPinch();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (points.size > 2) return;
|
|
110
|
+
const now = view();
|
|
111
|
+
drag = { id: ev.pointerId, from: at(ev), ox: now.x, oy: now.y, moved: false, button: ev.button };
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const onMove = (ev) => {
|
|
115
|
+
if (!points.has(ev.pointerId)) return;
|
|
116
|
+
points.set(ev.pointerId, at(ev));
|
|
117
|
+
if (pinch && points.size >= 2) {
|
|
118
|
+
movePinch();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!drag || ev.pointerId !== drag.id) return;
|
|
122
|
+
const p = at(ev);
|
|
123
|
+
const dx = p.x - drag.from.x;
|
|
124
|
+
const dy = p.y - drag.from.y;
|
|
125
|
+
if (!drag.moved && Math.abs(dx) + Math.abs(dy) < DRAG_SLOP) return;
|
|
126
|
+
if (!drag.moved) onCursor("grabbing");
|
|
127
|
+
drag.moved = true;
|
|
128
|
+
// The scene is laid out in CSS pixels and so is the pan: the pointer's
|
|
129
|
+
// travel goes in as it comes. Scaling it by the device pixel ratio makes
|
|
130
|
+
// the page slide at twice the cursor's speed on a HiDPI screen.
|
|
131
|
+
setView(drag.ox + dx, drag.oy + dy, view().sc);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const onUp = (ev) => {
|
|
135
|
+
const had = points.delete(ev.pointerId);
|
|
136
|
+
release(ev.pointerId);
|
|
137
|
+
if (!had) return;
|
|
138
|
+
onCursor("grab");
|
|
139
|
+
if (pinch) {
|
|
140
|
+
pinch = null;
|
|
141
|
+
// One finger left on the glass goes on panning from where it is,
|
|
142
|
+
// rather than the picture stopping until it is lifted and put down.
|
|
143
|
+
if (points.size === 1) {
|
|
144
|
+
const [id] = [...points.keys()];
|
|
145
|
+
const now = view();
|
|
146
|
+
drag = { id, from: points.get(id), ox: now.x, oy: now.y, moved: true, button: 0 };
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!drag || ev.pointerId !== drag.id) return;
|
|
151
|
+
const wasDrag = drag.moved;
|
|
152
|
+
const button = drag.button;
|
|
153
|
+
drag = null;
|
|
154
|
+
if (!wasDrag && button === 0) onTap(ev.clientX, ev.clientY);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const onWheel = (ev) => {
|
|
158
|
+
ev.preventDefault();
|
|
159
|
+
// Firefox reports wheel deltas in lines and Chrome in pixels: one notch
|
|
160
|
+
// of the same wheel arrives as 3 there and as 100 here. Counting a line
|
|
161
|
+
// as 33 pixels makes a notch a notch in both, which is the point — a
|
|
162
|
+
// line's real height would make Firefox scroll at half speed.
|
|
163
|
+
const perUnit = ev.deltaMode === 1 ? 33 : ev.deltaMode === 2 ? 400 : 1;
|
|
164
|
+
const dy = Math.max(-240, Math.min(240, ev.deltaY * perUnit));
|
|
165
|
+
// A trackpad pinch is a wheel with ctrl held, and it arrives a fraction
|
|
166
|
+
// of a notch at a time: on the wheel's own scale a pinch would crawl.
|
|
167
|
+
const rate = ev.ctrlKey ? 0.01 : 0.0015;
|
|
168
|
+
const p = at(ev);
|
|
169
|
+
zoomAbout(Math.exp(-dy * rate), p.x, p.y);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Safari's trackpad pinch, which is not a wheel. Preventing the default is
|
|
173
|
+
// what keeps it from zooming the whole page instead of the canvas.
|
|
174
|
+
const onGestureStart = (ev) => { ev.preventDefault(); gestureScale = ev.scale || 1; };
|
|
175
|
+
const onGestureChange = (ev) => {
|
|
176
|
+
ev.preventDefault();
|
|
177
|
+
const s = ev.scale || 1;
|
|
178
|
+
if (!gestureScale) gestureScale = s;
|
|
179
|
+
const p = at(ev);
|
|
180
|
+
zoomAbout(s / gestureScale, p.x, p.y);
|
|
181
|
+
gestureScale = s;
|
|
182
|
+
};
|
|
183
|
+
const onGestureEnd = (ev) => { ev.preventDefault(); gestureScale = 1; };
|
|
184
|
+
|
|
185
|
+
el.addEventListener("pointerdown", onDown);
|
|
186
|
+
el.addEventListener("pointermove", onMove);
|
|
187
|
+
el.addEventListener("pointerup", onUp);
|
|
188
|
+
el.addEventListener("pointercancel", onUp);
|
|
189
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
190
|
+
el.addEventListener("gesturestart", onGestureStart);
|
|
191
|
+
el.addEventListener("gesturechange", onGestureChange);
|
|
192
|
+
el.addEventListener("gestureend", onGestureEnd);
|
|
193
|
+
|
|
194
|
+
return function detach() {
|
|
195
|
+
el.removeEventListener("pointerdown", onDown);
|
|
196
|
+
el.removeEventListener("pointermove", onMove);
|
|
197
|
+
el.removeEventListener("pointerup", onUp);
|
|
198
|
+
el.removeEventListener("pointercancel", onUp);
|
|
199
|
+
el.removeEventListener("wheel", onWheel);
|
|
200
|
+
el.removeEventListener("gesturestart", onGestureStart);
|
|
201
|
+
el.removeEventListener("gesturechange", onGestureChange);
|
|
202
|
+
el.removeEventListener("gestureend", onGestureEnd);
|
|
203
|
+
points.clear();
|
|
204
|
+
drag = null;
|
|
205
|
+
pinch = null;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { DRAG_SLOP };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// The display list as the painter wants it, read straight off the Ranger
|
|
4
|
+
// object — no JSON in between.
|
|
5
|
+
//
|
|
6
|
+
// A host that shares a process with the app used to ask for `toJson()` and
|
|
7
|
+
// parse it back: a string of every coordinate on the page written and read
|
|
8
|
+
// on every frame, and a heap of garbage to collect after. That is the whole
|
|
9
|
+
// of what this file removes. It has to produce exactly what `toJson` would,
|
|
10
|
+
// key for key — `rt:scroll` holds the two against each other on every frame
|
|
11
|
+
// it draws — because the painter was written against the JSON and nothing in
|
|
12
|
+
// it should have to know which way the list arrived.
|
|
13
|
+
//
|
|
14
|
+
// Numbers come through as they are rather than rounded to the hundredth the
|
|
15
|
+
// JSON writer spells, which is the one difference: the painter gets what the
|
|
16
|
+
// layout computed, not its decimal form.
|
|
17
|
+
|
|
18
|
+
/** The commands of an `EVGDisplayList` in the JSON writer's shape. */
|
|
19
|
+
/** The camera the list carries, or null. Mirrors `viewOfBinary`. */
|
|
20
|
+
export function viewOf(dl) {
|
|
21
|
+
if (!dl || !dl.hasView) return null;
|
|
22
|
+
return { x: dl.viewX || 0, y: dl.viewY || 0, scale: dl.viewScale === undefined ? 1 : dl.viewScale };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function cmdsOf(dl) {
|
|
26
|
+
const attribute = !!dl.attribute;
|
|
27
|
+
const out = new Array(dl.cmds.length);
|
|
28
|
+
for (let i = 0; i < dl.cmds.length; i += 1) {
|
|
29
|
+
const c = dl.cmds[i];
|
|
30
|
+
const o = { k: c.kind };
|
|
31
|
+
if (attribute) o.n = c.node;
|
|
32
|
+
if (c.layer > 0) o.layer = c.layer;
|
|
33
|
+
o.x = c.x;
|
|
34
|
+
o.y = c.y;
|
|
35
|
+
o.w = c.w;
|
|
36
|
+
o.h = c.h;
|
|
37
|
+
if (c.perCorner) o.rc = [c.radius, c.radiusTR, c.radiusBR, c.radiusBL];
|
|
38
|
+
if (c.radius > 0) o.r = c.radius;
|
|
39
|
+
if (c.thickness > 0) {
|
|
40
|
+
o.t = c.thickness;
|
|
41
|
+
// Guarded, not just compared: this reads the Ranger object off a
|
|
42
|
+
// BUILT bundle, and a bundle compiled before a field existed has no
|
|
43
|
+
// such property. `undefined !== 0` is true, so an unguarded test
|
|
44
|
+
// would hand the painter `cap: undefined` rather than no cap.
|
|
45
|
+
if (c.strokeCap) o.cap = c.strokeCap;
|
|
46
|
+
if (c.strokeJoin) o.join = c.strokeJoin;
|
|
47
|
+
if (c.strokeDash) {
|
|
48
|
+
o.dash = c.strokeDash;
|
|
49
|
+
if (c.strokeDashOffset) o.dashoff = c.strokeDashOffset;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
o.c = [c.r, c.g, c.b, c.a];
|
|
53
|
+
if (c.hasGrad) {
|
|
54
|
+
o.gd = c.gradDir;
|
|
55
|
+
o.c2 = [c.r2, c.g2, c.b2, c.a2];
|
|
56
|
+
}
|
|
57
|
+
if (c.text.length > 0) {
|
|
58
|
+
o.text = c.text;
|
|
59
|
+
o.font = c.fontFamily;
|
|
60
|
+
o.size = c.fontSize;
|
|
61
|
+
if (c.fontWeight.length > 0) o.weight = c.fontWeight;
|
|
62
|
+
if (c.letterSpacing !== 0) o.ls = c.letterSpacing;
|
|
63
|
+
if (c.textAlign === "italic") o.italic = true;
|
|
64
|
+
}
|
|
65
|
+
if (c.src.length > 0) o.src = c.src;
|
|
66
|
+
if (c.flipH) o.fx = true;
|
|
67
|
+
if (c.flipV) o.fy = true;
|
|
68
|
+
// The crop window into the source: a picture is not always shown whole,
|
|
69
|
+
// and a reader that drops this draws the whole frame where a detail
|
|
70
|
+
// belongs.
|
|
71
|
+
if (c.hasCrop) o.cu = [c.cropX, c.cropY, c.cropX + c.cropW, c.cropY + c.cropH];
|
|
72
|
+
if (c.rotate !== 0) {
|
|
73
|
+
o.rot = c.rotate;
|
|
74
|
+
if (c.hasRotOrigin) {
|
|
75
|
+
o.rox = c.rotOriginX;
|
|
76
|
+
o.roy = c.rotOriginY;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (c.backdropBlur > 0) o.bb = c.backdropBlur;
|
|
80
|
+
// The effect instance this rectangle carries, by name — the display list's
|
|
81
|
+
// `efx`. Guarded like the stroke fields above: a bundle compiled before
|
|
82
|
+
// the field existed has no such property.
|
|
83
|
+
if (c.effectId) o.efx = c.effectId;
|
|
84
|
+
// The shadow, which the JSON never carried and the binary now does
|
|
85
|
+
// (`evg-binary.js`): written here too so the two readers agree key for
|
|
86
|
+
// key, and so a painter that wants it has it whichever way the list came.
|
|
87
|
+
if (c.hasShadow) {
|
|
88
|
+
o.sh = { x: c.shadowX, y: c.shadowY, blur: c.shadowBlur, c: [c.shadowR, c.shadowG, c.shadowB, c.shadowA] };
|
|
89
|
+
}
|
|
90
|
+
if (c.pts.length > 0) {
|
|
91
|
+
o.pts = c.pts.slice();
|
|
92
|
+
o.ends = c.ringEnds.length === 0 ? [c.pts.length] : c.ringEnds.slice();
|
|
93
|
+
if (c.evenOdd) o.eo = 1;
|
|
94
|
+
}
|
|
95
|
+
out[i] = o;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The surface effect, or undefined — the JSON's `effect`. */
|
|
101
|
+
export function effectOf(dl) {
|
|
102
|
+
if (!dl.effectKind || dl.effectKind.length === 0) return undefined;
|
|
103
|
+
const drops = [];
|
|
104
|
+
for (let i = 0; i < dl.effectAges.length; i += 1) {
|
|
105
|
+
drops.push([dl.effectXs[i], dl.effectYs[i], dl.effectAges[i]]);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
kind: dl.effectKind,
|
|
109
|
+
drops,
|
|
110
|
+
speed: dl.effectSpeed,
|
|
111
|
+
width: dl.effectWidth,
|
|
112
|
+
strength: dl.effectStrength,
|
|
113
|
+
decay: dl.effectDecay,
|
|
114
|
+
highlight: dl.effectHighlight,
|
|
115
|
+
rings: dl.effectRings,
|
|
116
|
+
stagger: dl.effectStagger,
|
|
117
|
+
falloff: dl.effectFalloff,
|
|
118
|
+
shine: dl.effectShine,
|
|
119
|
+
gloss: dl.effectGloss,
|
|
120
|
+
bump: dl.effectBump,
|
|
121
|
+
light: [dl.effectLightX, dl.effectLightY, dl.effectLightZ],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The element-scoped effects — the JSON's `effects`. One per element that
|
|
126
|
+
* declared `evg-surface-effect`, with the box the layout gave it, the
|
|
127
|
+
* trigger the sheet wrote and whatever `evg-fx-*` parameters came with it.
|
|
128
|
+
* Undefined when the page declares none, which keeps a list that has no
|
|
129
|
+
* effects on it exactly the object it was. */
|
|
130
|
+
export function effectsOf(dl) {
|
|
131
|
+
const ids = dl.fxIds;
|
|
132
|
+
if (!ids || ids.length === 0) return undefined;
|
|
133
|
+
const out = new Array(ids.length);
|
|
134
|
+
for (let i = 0; i < ids.length; i += 1) {
|
|
135
|
+
const p = {};
|
|
136
|
+
const start = dl.fxParamStart[i];
|
|
137
|
+
for (let k = 0; k < dl.fxParamCount[i]; k += 1) p[dl.fxParamNames[start + k]] = dl.fxParamValues[start + k];
|
|
138
|
+
// Written in the order `toJson` writes them, so the two readers produce
|
|
139
|
+
// objects a person can diff as well as a comparison can.
|
|
140
|
+
const e = { id: ids[i], kind: dl.fxKinds[i] };
|
|
141
|
+
if (dl.fxTriggers[i]) e.on = dl.fxTriggers[i];
|
|
142
|
+
e.box = [dl.fxX[i], dl.fxY[i], dl.fxW[i], dl.fxH[i]];
|
|
143
|
+
if (dl.fxRadius[i] > 0) e.r = dl.fxRadius[i];
|
|
144
|
+
e.p = p;
|
|
145
|
+
out[i] = e;
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** How far each scroll layer's content has moved since the list was built —
|
|
151
|
+
* the JSON's `shifts`. Read on every frame: it is the one thing about a kept
|
|
152
|
+
* list that changes. */
|
|
153
|
+
export function shiftsOf(dl) {
|
|
154
|
+
const out = new Array(dl.layerShiftX.length);
|
|
155
|
+
for (let i = 0; i < dl.layerShiftX.length; i += 1) out[i] = [dl.layerShiftX[i], dl.layerShiftY[i]];
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The whole list, as `JSON.parse(dl.toJson())` would give it. */
|
|
160
|
+
export function listOf(dl) {
|
|
161
|
+
const out = { seq: dl.buildSeq, shifts: shiftsOf(dl), cmds: cmdsOf(dl) };
|
|
162
|
+
const effect = effectOf(dl);
|
|
163
|
+
if (effect) out.effect = effect;
|
|
164
|
+
const effects = effectsOf(dl);
|
|
165
|
+
if (effects) out.effects = effects;
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// The browser measures, EVG breaks the lines.
|
|
4
|
+
//
|
|
5
|
+
// PLAN_NATIVE_HOSTS.md S0, the web half. `EVGHostTextMeasurer` (Ranger) takes
|
|
6
|
+
// ONE function from the platform — width of a run, and a face's ascent,
|
|
7
|
+
// descent and line gap — and every layout in the compiled module then
|
|
8
|
+
// measures with it. This file is that function for a browser: a 2D canvas
|
|
9
|
+
// context and `measureText`, with the font shorthand built EXACTLY as the
|
|
10
|
+
// painters build theirs (`evg-webgl.js` and `evg-html.js` `fontSpec`), so the
|
|
11
|
+
// face the layout measured with is the face the atlas or the `<text>` node
|
|
12
|
+
// draws with. Before this, the layout measured with the advance table — one
|
|
13
|
+
// browser's sans, snapshotted once — and the painter drew with whatever this
|
|
14
|
+
// browser resolved, which is the gap S0 exists to close.
|
|
15
|
+
//
|
|
16
|
+
// It works in a Worker too (`OffscreenCanvas`), which is what S1 wants: the
|
|
17
|
+
// engine off the main thread, measuring where it lays out.
|
|
18
|
+
//
|
|
19
|
+
// WHAT IT DOES NOT DO. It does not decide which module to install into: a
|
|
20
|
+
// page that bundles several generated modules has several `EVGDefaultMeasurer`
|
|
21
|
+
// singletons — two copies of a class are two classes — and installs into
|
|
22
|
+
// each. `installCanvasMeasurer(modules)` takes the list.
|
|
23
|
+
//
|
|
24
|
+
// ONE FACT WORTH KNOWING. Canvas has no line gap. `fontBoundingBoxAscent` and
|
|
25
|
+
// `Descent` are the face's, but `line-height: normal` in CSS is ascent +
|
|
26
|
+
// descent + the face's lineGap, and the sans fallback's gap is real: 1.15em
|
|
27
|
+
// of line box against 1.117em of ascent + descent. So in a document the gap
|
|
28
|
+
// is measured off a hidden element with `line-height: normal`, once per face;
|
|
29
|
+
// in a worker there is no element, and the host says "unknown" (a negative
|
|
30
|
+
// number), which the Ranger side reads as zero.
|
|
31
|
+
|
|
32
|
+
const LRO = "\u202D", PDF = "\u202C";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The family a command names, stripped of the `-Bold` suffix
|
|
36
|
+
* `EVGElement.effectiveFontFamily` adds for a bold element. The suffix is a
|
|
37
|
+
* convention for the TTF measurers (`FontManager` reads `Family-Bold` as the
|
|
38
|
+
* bold face); a browser reads it as a family nobody has and falls to the
|
|
39
|
+
* stack. The weight arrives separately, and that is what the browser uses.
|
|
40
|
+
*/
|
|
41
|
+
export function familyOf(name) {
|
|
42
|
+
const f = name || "";
|
|
43
|
+
return f.endsWith("-Bold") ? f.slice(0, -5) : f;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Make a measurer for one compiled module and attach the browser to it.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} mod the generated module: `EVGHostTextMeasurer` and
|
|
50
|
+
* `EVGDefaultMeasurer` are read off it
|
|
51
|
+
* @param {object} [opts]
|
|
52
|
+
* @param {string[]} [opts.fallback] the families the browser may fall back
|
|
53
|
+
* to, in order — the same list handed to the painter's
|
|
54
|
+
* `setFontFallback`, so the two walks agree
|
|
55
|
+
* @param {boolean} [opts.install=true] make it the module's default
|
|
56
|
+
* @returns {{ measurer: object, refresh: () => void, detach: () => void }}
|
|
57
|
+
*/
|
|
58
|
+
export function createCanvasMeasurer(mod, opts = {}) {
|
|
59
|
+
if (!mod || typeof mod.EVGHostTextMeasurer !== "function") {
|
|
60
|
+
throw new Error("evg-measure: the module has no EVGHostTextMeasurer — import EVGLayout.rgr (or EVGTextEngine.rgr) in the Ranger program");
|
|
61
|
+
}
|
|
62
|
+
const stackList = (opts.fallback || []).filter((f) => f && f.length).map((f) => `"${f}"`);
|
|
63
|
+
stackList.push("sans-serif");
|
|
64
|
+
const fallbackStack = stackList.join(", ");
|
|
65
|
+
|
|
66
|
+
const ctx = makeContext();
|
|
67
|
+
// Face metrics by font spec: the vertical numbers are a property of the
|
|
68
|
+
// face at a size, not of any string, so three answers serve every run.
|
|
69
|
+
const faces = new Map();
|
|
70
|
+
let currentFont = "";
|
|
71
|
+
|
|
72
|
+
const spec = (family, size, bold, italic) => {
|
|
73
|
+
const fam = family && family.length ? `"${family}", ${fallbackStack}` : fallbackStack;
|
|
74
|
+
return `${italic ? "italic " : ""}${bold ? "bold " : ""}${size}px ${fam}`;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const setFont = (s) => {
|
|
78
|
+
if (s !== currentFont) {
|
|
79
|
+
ctx.font = s;
|
|
80
|
+
currentFont = s;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const faceOf = (s, size) => {
|
|
85
|
+
let f = faces.get(s);
|
|
86
|
+
if (f) return f;
|
|
87
|
+
setFont(s);
|
|
88
|
+
// "Hg" rather than any run: the face's box is the same for every string.
|
|
89
|
+
const m = ctx.measureText("Hg");
|
|
90
|
+
const asc = m.fontBoundingBoxAscent || 0;
|
|
91
|
+
const desc = m.fontBoundingBoxDescent || 0;
|
|
92
|
+
f = { asc, desc, gap: normalGap(s, asc + desc, size) };
|
|
93
|
+
faces.set(s, f);
|
|
94
|
+
return f;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const metric = (kind, text, family, size, bold, italic) => {
|
|
98
|
+
const s = spec(family, size, bold, italic);
|
|
99
|
+
if (kind === 0) {
|
|
100
|
+
if (!text) return 0;
|
|
101
|
+
setFont(s);
|
|
102
|
+
// The same bidi override the painters apply: EVG has already ordered
|
|
103
|
+
// the runs, and a measurement that reorders inside one is a different
|
|
104
|
+
// width from the one that will be drawn.
|
|
105
|
+
return ctx.measureText(LRO + text + PDF).width;
|
|
106
|
+
}
|
|
107
|
+
const f = faceOf(s, size);
|
|
108
|
+
if (kind === 1) return f.asc;
|
|
109
|
+
if (kind === 2) return f.desc;
|
|
110
|
+
return f.gap;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const measurer = new mod.EVGHostTextMeasurer();
|
|
114
|
+
measurer.attach(metric, "canvas");
|
|
115
|
+
if (opts.install !== false) mod.EVGDefaultMeasurer.install(measurer);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
measurer,
|
|
119
|
+
/** A face finished loading (`document.fonts.ready`): forget what was
|
|
120
|
+
* measured with the fallback and move the key, so every engine holding
|
|
121
|
+
* this measurer measures again. */
|
|
122
|
+
refresh() {
|
|
123
|
+
faces.clear();
|
|
124
|
+
currentFont = "";
|
|
125
|
+
measurer.invalidate();
|
|
126
|
+
},
|
|
127
|
+
/** Back to the table. */
|
|
128
|
+
detach() {
|
|
129
|
+
measurer.detach();
|
|
130
|
+
if (opts.install !== false) mod.EVGDefaultMeasurer.uninstall();
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The same, for every module a page bundles. Returns one handle whose
|
|
137
|
+
* `refresh` refreshes them all — a page has one `document.fonts.ready`.
|
|
138
|
+
*/
|
|
139
|
+
export function installCanvasMeasurer(modules, opts = {}) {
|
|
140
|
+
const list = Array.isArray(modules) ? modules : [modules];
|
|
141
|
+
const made = list.map((m) => createCanvasMeasurer(m, opts));
|
|
142
|
+
return {
|
|
143
|
+
measurers: made.map((h) => h.measurer),
|
|
144
|
+
refresh() { for (const h of made) h.refresh(); },
|
|
145
|
+
detach() { for (const h of made) h.detach(); },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
function makeContext() {
|
|
152
|
+
if (typeof document !== "undefined" && document.createElement) {
|
|
153
|
+
const canvas = document.createElement("canvas");
|
|
154
|
+
canvas.width = 8;
|
|
155
|
+
canvas.height = 8;
|
|
156
|
+
const ctx = canvas.getContext("2d");
|
|
157
|
+
if (ctx) return ctx;
|
|
158
|
+
}
|
|
159
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
160
|
+
const ctx = new OffscreenCanvas(8, 8).getContext("2d");
|
|
161
|
+
if (ctx) return ctx;
|
|
162
|
+
}
|
|
163
|
+
throw new Error("evg-measure: no 2D canvas context available here");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// The line gap, from the one place a browser states it: the height of a
|
|
167
|
+
// line box at `line-height: normal`. Measured at the face's own size — the
|
|
168
|
+
// gap scales with the em, and rounding at a small size is what it is.
|
|
169
|
+
let probe = null;
|
|
170
|
+
function normalGap(fontSpec, ascPlusDesc, size) {
|
|
171
|
+
if (typeof document === "undefined" || !document.body) return -1;
|
|
172
|
+
if (!probe) {
|
|
173
|
+
probe = document.createElement("div");
|
|
174
|
+
probe.setAttribute("aria-hidden", "true");
|
|
175
|
+
probe.style.cssText = "position:absolute;left:-10000px;top:0;visibility:hidden;white-space:nowrap;line-height:normal;padding:0;border:0;margin:0;pointer-events:none";
|
|
176
|
+
probe.textContent = "Hg";
|
|
177
|
+
document.body.appendChild(probe);
|
|
178
|
+
}
|
|
179
|
+
probe.style.font = fontSpec;
|
|
180
|
+
const h = probe.getBoundingClientRect().height;
|
|
181
|
+
if (!(h > 0) || !(ascPlusDesc > 0)) return -1;
|
|
182
|
+
const gap = h - ascPlusDesc;
|
|
183
|
+
// A negative gap is a rounding artefact of a box measured in whole pixels
|
|
184
|
+
// against metrics measured in fractions; a face has no negative gap.
|
|
185
|
+
return gap > 0 ? gap : 0;
|
|
186
|
+
}
|