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,600 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evg-html.js — the same display list, painted as DOM instead of pixels.
|
|
3
|
+
*
|
|
4
|
+
* `evg-webgl.js` is the reference for this file. Both are handed the SAME
|
|
5
|
+
* `{width, height, list:{cmds}}` that `EVGDisplayList` emits, and both are
|
|
6
|
+
* expected to put the same picture on the screen; only the mechanism differs.
|
|
7
|
+
* Nothing above the seam knows which one is running, which is the whole reason
|
|
8
|
+
* the seam is there — see lib/evg/gl/README.md.
|
|
9
|
+
*
|
|
10
|
+
* EVGDisplayList ──┬─► evg-webgl.js ─► quads, glyph atlas, GLSL
|
|
11
|
+
* └─► THIS FILE ─► <svg>: rect, path, text, image
|
|
12
|
+
*
|
|
13
|
+
* WHY SVG AND NOT DIVS. The list has eight command kinds and SVG has a native
|
|
14
|
+
* answer for seven of them, including the two a stack of absolutely positioned
|
|
15
|
+
* `<div>`s cannot express at all:
|
|
16
|
+
*
|
|
17
|
+
* * PATH / STROKE are flattened rings in page coordinates — `<path d=…>` and
|
|
18
|
+
* `fill-rule="evenodd"` take them verbatim, with no tessellation and no
|
|
19
|
+
* stencil buffer. The GL backend needs both.
|
|
20
|
+
* * PUSH_CLIP / POP_CLIP is a STACK, and a stack of nested `<g clip-path>`
|
|
21
|
+
* intersects by construction. The GL backend has one scissor rectangle and
|
|
22
|
+
* has to intersect the stack by hand; `EVGListToElements`, which converts a
|
|
23
|
+
* list into an element tree for the PDF writer, gives up on clipping
|
|
24
|
+
* entirely and says so in its header.
|
|
25
|
+
*
|
|
26
|
+
* So this backend is not a degraded one. It is missing the ripple post-pass,
|
|
27
|
+
* which is a GPU effect with no DOM equivalent, and it gains selectable text,
|
|
28
|
+
* resolution independence, and a picture that survives being printed.
|
|
29
|
+
*
|
|
30
|
+
* (That sibling now exists: `evg-dom.js` reads `EVGHostTree`, the list that
|
|
31
|
+
* carries identity, and keeps its nodes. This file stays what it is.)
|
|
32
|
+
*
|
|
33
|
+
* WHAT IS DELIBERATELY NOT HERE. Element identity. A draw command carries no
|
|
34
|
+
* id — `EVGDrawCmd` has geometry and paint and nothing else — so every frame
|
|
35
|
+
* rebuilds every node, and a CSS transition, a focus ring or a native `<input>`
|
|
36
|
+
* has nothing stable to attach to. That is a property of the SEAM, not of this
|
|
37
|
+
* file: getting it would mean an id on the command or a second walk over the
|
|
38
|
+
* element tree. A painter is what this is.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const KIND = {
|
|
42
|
+
RECT: 0, BORDER: 1, IMAGE: 2, TEXT: 3, PUSH_CLIP: 4, POP_CLIP: 5,
|
|
43
|
+
PATH: 6, STROKE: 7,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Text, and the one number that decides where every run sits
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Bidi, forced off — the same rule `evg-webgl.js` applies, for the same reason.
|
|
54
|
+
*
|
|
55
|
+
* EVG has already decided the order of a line: a line that changes colour
|
|
56
|
+
* partway along arrives as several TEXT commands at computed x positions. A
|
|
57
|
+
* renderer that reorders each of them in isolation reverses Arabic inside its
|
|
58
|
+
* own run and then places the runs left to right anyway. U+202D forces every
|
|
59
|
+
* character to level 0 and U+202C pops it.
|
|
60
|
+
*
|
|
61
|
+
* It matters MORE here than it does on the GL side. There the string is
|
|
62
|
+
* rasterised into an atlas and the result is a picture; here it is a live
|
|
63
|
+
* `<text>` node that the browser is still free to shape and reorder at paint
|
|
64
|
+
* time.
|
|
65
|
+
*/
|
|
66
|
+
const LRO = "\u202D", PDF = "\u202C";
|
|
67
|
+
export const verbatim = (t) => LRO + t + PDF;
|
|
68
|
+
|
|
69
|
+
let fallbackStack = "sans-serif";
|
|
70
|
+
|
|
71
|
+
/** The families the browser may fall back to, after the one a command names.
|
|
72
|
+
*
|
|
73
|
+
* Same contract as the GL backend: our FontManager falls back per CODEPOINT
|
|
74
|
+
* across every loaded face and every width in the layout was measured through
|
|
75
|
+
* that walk, so a stack that ends at `sans-serif` answers those codepoints
|
|
76
|
+
* from whatever the system has — right-looking glyphs at widths nobody
|
|
77
|
+
* measured. A page sets this to its own pool, in load order.
|
|
78
|
+
*/
|
|
79
|
+
export function setFontFallback(families) {
|
|
80
|
+
const list = (families || []).filter((f) => f && f.length).map((f) => `"${f}"`);
|
|
81
|
+
list.push("sans-serif");
|
|
82
|
+
fallbackStack = list.join(", ");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** One place that turns a TEXT command into a CSS font shorthand. */
|
|
86
|
+
// The family a command names, less the `-Bold` suffix `effectiveFontFamily`
|
|
87
|
+
// writes for a bold element: a convention the TTF measurers read as "the
|
|
88
|
+
// bold face", and a browser reads as a family nobody has. The weight arrives
|
|
89
|
+
// on its own and is what the browser is given. `gl/evg-measure.js` strips
|
|
90
|
+
// the same suffix, so the layout and this painter ask for one face.
|
|
91
|
+
const familyOf = (c) => (c.font && c.font.endsWith("-Bold") ? c.font.slice(0, -5) : c.font || "");
|
|
92
|
+
|
|
93
|
+
function fontSpec(c) {
|
|
94
|
+
return `${c.italic ? "italic " : ""}${c.weight ? c.weight + " " : ""}${c.size}px "${familyOf(c)}", ${fallbackStack}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Face metrics per font spec. `fontBoundingBox*` is a property of the FACE at
|
|
98
|
+
// a size, not of the string, so one measurement answers every run that shares
|
|
99
|
+
// a spec — which on a slide is nearly all of them.
|
|
100
|
+
const FACE_METRICS = new Map();
|
|
101
|
+
let metricCtx = null;
|
|
102
|
+
|
|
103
|
+
function faceMetrics(c) {
|
|
104
|
+
const spec = fontSpec(c);
|
|
105
|
+
const hit = FACE_METRICS.get(spec);
|
|
106
|
+
if (hit) return hit;
|
|
107
|
+
if (!metricCtx) {
|
|
108
|
+
const canvas = document.createElement("canvas");
|
|
109
|
+
canvas.width = 8;
|
|
110
|
+
canvas.height = 8;
|
|
111
|
+
metricCtx = canvas.getContext("2d");
|
|
112
|
+
}
|
|
113
|
+
let asc = c.size * 1.05, desc = c.size * 0.212;
|
|
114
|
+
if (metricCtx) {
|
|
115
|
+
metricCtx.font = spec;
|
|
116
|
+
// "Hg" rather than the run: the ink of a particular string is irrelevant
|
|
117
|
+
// here and measuring per run would defeat the cache.
|
|
118
|
+
const m = metricCtx.measureText("Hg");
|
|
119
|
+
if (m.fontBoundingBoxAscent) asc = m.fontBoundingBoxAscent;
|
|
120
|
+
if (m.fontBoundingBoxDescent) desc = m.fontBoundingBoxDescent;
|
|
121
|
+
}
|
|
122
|
+
const out = { asc, desc };
|
|
123
|
+
FACE_METRICS.set(spec, out);
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Drop the cache when the page has registered new faces.
|
|
128
|
+
*
|
|
129
|
+
* A metric measured before `document.fonts.add()` resolves is the FALLBACK
|
|
130
|
+
* face's, and it is then cached forever — every run in the deck placed by the
|
|
131
|
+
* system sans's ascent while being drawn in Open Sans. A page that loads fonts
|
|
132
|
+
* after this module calls this.
|
|
133
|
+
*/
|
|
134
|
+
export function clearFontMetrics() {
|
|
135
|
+
FACE_METRICS.clear();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Where the baseline of a TEXT command goes.
|
|
140
|
+
*
|
|
141
|
+
* Transcribed from `evg-webgl.js`, which is the definition:
|
|
142
|
+
*
|
|
143
|
+
* baseline = y + (lineBox - (faceAsc + faceDesc)) / 2 + faceAsc
|
|
144
|
+
*
|
|
145
|
+
* A TEXT command's `y` is the top of the LINE BOX and `h` is its height, so
|
|
146
|
+
* the baseline is one half-leading plus one face ascent below it. The
|
|
147
|
+
* half-leading term is not decoration — a line box is normally taller than the
|
|
148
|
+
* face it holds, so leaving it out draws every run high, in one direction
|
|
149
|
+
* only.
|
|
150
|
+
*
|
|
151
|
+
* SVG's default `dominant-baseline` is alphabetic, so a `<text y=…>` placed
|
|
152
|
+
* here needs no further adjustment: the two conventions already agree.
|
|
153
|
+
*/
|
|
154
|
+
function baselineOf(c) {
|
|
155
|
+
const { asc, desc } = faceMetrics(c);
|
|
156
|
+
const halfLeading = c.h ? (c.h - (asc + desc)) / 2 : 0;
|
|
157
|
+
return c.y + halfLeading + asc;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Markup
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
const XML_ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
165
|
+
const esc = (s) => String(s).replace(/[&<>"']/g, (ch) => XML_ESCAPES[ch]);
|
|
166
|
+
|
|
167
|
+
/** Two decimals, and no exponent.
|
|
168
|
+
*
|
|
169
|
+
* The display list is already written to hundredths — `EVGDisplayList.num`
|
|
170
|
+
* rounds there — so this loses nothing. What it prevents is `toString`
|
|
171
|
+
* reaching for scientific notation on a very small number: `1e-7` is not a
|
|
172
|
+
* valid SVG length, and one of them in a path's `d` invalidates the whole
|
|
173
|
+
* attribute rather than the one coordinate.
|
|
174
|
+
*/
|
|
175
|
+
function n(v) {
|
|
176
|
+
if (!Number.isFinite(v)) return "0";
|
|
177
|
+
const r = Math.round(v * 100) / 100;
|
|
178
|
+
return Object.is(r, -0) ? "0" : String(r);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const rgba = (c) => {
|
|
182
|
+
const col = c || [0, 0, 0, 1];
|
|
183
|
+
const a = col[3] === undefined ? 1 : col[3];
|
|
184
|
+
return a >= 1 ? `rgb(${col[0] | 0},${col[1] | 0},${col[2] | 0})`
|
|
185
|
+
: `rgba(${col[0] | 0},${col[1] | 0},${col[2] | 0},${n(a)})`;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/** The four corner radii of a command, clamped the way CSS clamps them.
|
|
189
|
+
*
|
|
190
|
+
* `rc` is present only when the four differ; a box with one radius carries a
|
|
191
|
+
* single `r`, which is the shape every backend but the GL one reads.
|
|
192
|
+
*/
|
|
193
|
+
function radiiOf(c) {
|
|
194
|
+
const r = c.rc ? c.rc.slice(0, 4) : [c.r || 0, c.r || 0, c.r || 0, c.r || 0];
|
|
195
|
+
const lim = Math.min(c.w, c.h) / 2;
|
|
196
|
+
return r.map((v) => Math.max(0, Math.min(v || 0, lim)));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const uniform = (r) => r[0] === r[1] && r[1] === r[2] && r[2] === r[3];
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* A rounded rectangle as a path, for the case `rx` cannot express.
|
|
203
|
+
*
|
|
204
|
+
* Corners are TL, TR, BR, BL — the order `border-radius` writes them in, which
|
|
205
|
+
* is the order the display list carries and the order the GL shader reads.
|
|
206
|
+
*/
|
|
207
|
+
function roundedPath(x, y, w, h, r) {
|
|
208
|
+
const [tl, tr, br, bl] = r;
|
|
209
|
+
return [
|
|
210
|
+
`M${n(x + tl)},${n(y)}`,
|
|
211
|
+
`H${n(x + w - tr)}`, tr ? `A${n(tr)},${n(tr)} 0 0 1 ${n(x + w)},${n(y + tr)}` : "",
|
|
212
|
+
`V${n(y + h - br)}`, br ? `A${n(br)},${n(br)} 0 0 1 ${n(x + w - br)},${n(y + h)}` : "",
|
|
213
|
+
`H${n(x + bl)}`, bl ? `A${n(bl)},${n(bl)} 0 0 1 ${n(x)},${n(y + h - bl)}` : "",
|
|
214
|
+
`V${n(y + tl)}`, tl ? `A${n(tl)},${n(tl)} 0 0 1 ${n(x + tl)},${n(y)}` : "",
|
|
215
|
+
"Z",
|
|
216
|
+
].filter(Boolean).join("");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The rings of a PATH / STROKE command as one `d`.
|
|
220
|
+
*
|
|
221
|
+
* `ends` gives the index one past each ring's last COORDINATE, so a shape with
|
|
222
|
+
* a hole survives as two subpaths rather than one confused outline. A command
|
|
223
|
+
* that set `pts` and forgot `ends` is treated as one ring covering everything,
|
|
224
|
+
* which is what the JSON writer does too.
|
|
225
|
+
*/
|
|
226
|
+
function ringsPath(c, close) {
|
|
227
|
+
const pts = c.pts;
|
|
228
|
+
if (!pts || pts.length < 4) return "";
|
|
229
|
+
const ends = c.ends && c.ends.length ? c.ends : [pts.length];
|
|
230
|
+
let d = "", at = 0;
|
|
231
|
+
for (const end of ends) {
|
|
232
|
+
if (end - at < 4) { at = end; continue; }
|
|
233
|
+
d += `M${n(pts[at])},${n(pts[at + 1])}`;
|
|
234
|
+
for (let i = at + 2; i + 1 < end; i += 2) d += `L${n(pts[i])},${n(pts[i + 1])}`;
|
|
235
|
+
if (close) d += "Z";
|
|
236
|
+
at = end;
|
|
237
|
+
}
|
|
238
|
+
return d;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* What a command turns about.
|
|
243
|
+
*
|
|
244
|
+
* An explicit origin is the easy half. Without one the rule is "the command's
|
|
245
|
+
* own centre", and for TEXT that is not the centre of the line box: the GL
|
|
246
|
+
* backend draws a run in a quad sized to its INK, so the pivot it falls back to
|
|
247
|
+
* is the ink's centre. The two are a few pixels apart on a 22px label — enough
|
|
248
|
+
* that the parity check sees it, which is how this was found.
|
|
249
|
+
*
|
|
250
|
+
* `EVGDisplayList` says the same thing from the other side: `rotOriginX/Y` were
|
|
251
|
+
* added precisely because turning a box and its own words about their separate
|
|
252
|
+
* centres pulls them apart, and anything that cares emits an origin. This is
|
|
253
|
+
* the case that does not — a lone turned label — and there the ink centre is
|
|
254
|
+
* both what the GPU does and what looks right.
|
|
255
|
+
*
|
|
256
|
+
* Three values are needed to say "unset" rather than two, because (0, 0) is a
|
|
257
|
+
* legal pivot and cannot be told apart from "none" by its coordinates.
|
|
258
|
+
*/
|
|
259
|
+
function rotAttr(c) {
|
|
260
|
+
if (!c.rot) return "";
|
|
261
|
+
let cx, cy;
|
|
262
|
+
if (c.rox !== undefined) {
|
|
263
|
+
cx = c.rox;
|
|
264
|
+
cy = c.roy === undefined ? c.y + c.h / 2 : c.roy;
|
|
265
|
+
} else if (c.k === KIND.TEXT) {
|
|
266
|
+
const ink = inkBox(c);
|
|
267
|
+
cx = ink.cx;
|
|
268
|
+
cy = ink.cy;
|
|
269
|
+
} else {
|
|
270
|
+
cx = c.x + c.w / 2;
|
|
271
|
+
cy = c.y + c.h / 2;
|
|
272
|
+
}
|
|
273
|
+
return ` transform="rotate(${n(c.rot)} ${n(cx)} ${n(cy)})"`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The centre of the quad the GL backend would have drawn this run in.
|
|
278
|
+
*
|
|
279
|
+
* Transcribed from `buildTextAtlas`: a slot is the run's measured width and its
|
|
280
|
+
* INK height — `actualBoundingBox*`, the ascent and descent of these particular
|
|
281
|
+
* letters — with two pixels of padding, placed so the baseline lines up. The
|
|
282
|
+
* padding cancels out of the centre, so what is left is the ink box.
|
|
283
|
+
*
|
|
284
|
+
* Measured per run rather than per face, which the cached `faceMetrics` cannot
|
|
285
|
+
* be: the ink of "moon" and of "Ág" are different boxes in the same font. It is
|
|
286
|
+
* only reached for a turned run with no origin, which is rare enough that the
|
|
287
|
+
* measurement does not need a cache of its own.
|
|
288
|
+
*/
|
|
289
|
+
function inkBox(c) {
|
|
290
|
+
const base = baselineOf(c);
|
|
291
|
+
if (!metricCtx) faceMetrics(c);
|
|
292
|
+
if (!metricCtx) return { cx: c.x + c.w / 2, cy: c.y + c.h / 2 };
|
|
293
|
+
metricCtx.font = fontSpec(c);
|
|
294
|
+
const m = metricCtx.measureText(verbatim(c.text));
|
|
295
|
+
const asc = m.actualBoundingBoxAscent || c.size * 0.8;
|
|
296
|
+
const desc = m.actualBoundingBoxDescent || c.size * 0.25;
|
|
297
|
+
return {
|
|
298
|
+
cx: c.x + Math.ceil(m.width) / 2,
|
|
299
|
+
cy: base - asc + Math.ceil(asc + desc) / 2,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
// Images
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Load every distinct image the list names. Same signature as the GL backend's,
|
|
309
|
+
* so a host that already has one needs no second code path.
|
|
310
|
+
*/
|
|
311
|
+
export async function loadImages(doc, opts = {}) {
|
|
312
|
+
const base = opts.base || "";
|
|
313
|
+
const srcs = [...new Set(doc.list.cmds.filter((c) => c.k === KIND.IMAGE && c.src).map((c) => c.src))];
|
|
314
|
+
const out = new Map();
|
|
315
|
+
await Promise.all(srcs.map((src) => new Promise((resolve) => {
|
|
316
|
+
const img = new Image();
|
|
317
|
+
img.crossOrigin = "anonymous";
|
|
318
|
+
img.onload = () => { out.set(src, img); resolve(); };
|
|
319
|
+
img.onerror = () => { out.set(src, null); resolve(); };
|
|
320
|
+
img.src = base + src;
|
|
321
|
+
})));
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// The paint
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Paint a display list into an `<svg>`.
|
|
331
|
+
*
|
|
332
|
+
* `target` is the element to paint into: an `<svg>` is used directly, anything
|
|
333
|
+
* else gets one created inside it and reused across frames.
|
|
334
|
+
*
|
|
335
|
+
* The markup is assembled as a STRING and handed over in one `innerHTML`. The
|
|
336
|
+
* obvious alternative — `createElementNS` per command, appended as it goes —
|
|
337
|
+
* is between two and four times slower on a slide-sized list, because each
|
|
338
|
+
* append is a live mutation of a rendered tree; a string is parsed once with
|
|
339
|
+
* nothing on screen depending on it.
|
|
340
|
+
*
|
|
341
|
+
* Returns the same shape of stats the GL backend returns, so a page can report
|
|
342
|
+
* what it drew without knowing which backend drew it.
|
|
343
|
+
*/
|
|
344
|
+
export function renderDisplayList(target, doc, opts = {}) {
|
|
345
|
+
const images = opts.images || new Map();
|
|
346
|
+
const cmds = doc.list.cmds;
|
|
347
|
+
const svg = svgFor(target);
|
|
348
|
+
|
|
349
|
+
const defs = [];
|
|
350
|
+
const body = [];
|
|
351
|
+
let openClips = 0;
|
|
352
|
+
let drawn = 0, textRuns = 0, paths = 0, drawnImages = 0, missingImages = 0, clips = 0;
|
|
353
|
+
let uid = 0;
|
|
354
|
+
|
|
355
|
+
for (const c of cmds) {
|
|
356
|
+
switch (c.k) {
|
|
357
|
+
case KIND.PUSH_CLIP: {
|
|
358
|
+
// A nested <g clip-path> intersects with everything above it, which is
|
|
359
|
+
// exactly what the stack means — and is the part the GL backend has to
|
|
360
|
+
// compute by hand because a scissor is not a stack.
|
|
361
|
+
const id = `evgclip${uid++}`;
|
|
362
|
+
defs.push(`<clipPath id="${id}"><rect x="${n(c.x)}" y="${n(c.y)}" width="${n(Math.max(0, c.w))}" height="${n(Math.max(0, c.h))}"/></clipPath>`);
|
|
363
|
+
body.push(`<g clip-path="url(#${id})">`);
|
|
364
|
+
openClips += 1;
|
|
365
|
+
clips += 1;
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case KIND.POP_CLIP:
|
|
369
|
+
// A pop with nothing pushed is a producer's bug, not this file's: close
|
|
370
|
+
// nothing rather than emitting a stray `</g>` that would swallow the
|
|
371
|
+
// rest of the frame into whatever encloses it.
|
|
372
|
+
if (openClips > 0) { body.push("</g>"); openClips -= 1; }
|
|
373
|
+
break;
|
|
374
|
+
|
|
375
|
+
case KIND.RECT: {
|
|
376
|
+
if (c.w <= 0 || c.h <= 0) break;
|
|
377
|
+
let fill = rgba(c.c);
|
|
378
|
+
if (c.gd !== undefined && c.c2) {
|
|
379
|
+
// `gd` is 1 for across the box and anything else for down it — the
|
|
380
|
+
// two directions the display list carries.
|
|
381
|
+
const id = `evggrad${uid++}`;
|
|
382
|
+
const across = c.gd === 1;
|
|
383
|
+
defs.push(
|
|
384
|
+
`<linearGradient id="${id}" x1="0" y1="0" x2="${across ? 1 : 0}" y2="${across ? 0 : 1}">` +
|
|
385
|
+
`<stop offset="0" stop-color="${rgba(c.c)}"/><stop offset="1" stop-color="${rgba(c.c2)}"/>` +
|
|
386
|
+
`</linearGradient>`);
|
|
387
|
+
fill = `url(#${id})`;
|
|
388
|
+
}
|
|
389
|
+
if (c.bb > 0) body.push(backdrop(c));
|
|
390
|
+
const r = radiiOf(c);
|
|
391
|
+
// The drop shadow, under the box: the same rounded rectangle, moved
|
|
392
|
+
// and softened. A Gaussian of sigma = blur/2 is what CSS means by a
|
|
393
|
+
// blur radius, and the GL shader's falloff spans the same width.
|
|
394
|
+
if (c.sh) {
|
|
395
|
+
const b = Math.max(c.sh.blur || 0, 0);
|
|
396
|
+
const sx = c.x + (c.sh.x || 0), sy = c.y + (c.sh.y || 0);
|
|
397
|
+
let attrs = "";
|
|
398
|
+
if (b > 0) {
|
|
399
|
+
const id = `evgshadow${uid++}`;
|
|
400
|
+
// The filter region has to be wide enough for the kernel or the
|
|
401
|
+
// blur is clipped square at the edge of the shape's own box.
|
|
402
|
+
defs.push(`<filter id="${id}" x="-50%" y="-50%" width="200%" height="200%">` +
|
|
403
|
+
`<feGaussianBlur stdDeviation="${n(b / 2)}"/></filter>`);
|
|
404
|
+
attrs = ` filter="url(#${id})"`;
|
|
405
|
+
}
|
|
406
|
+
body.push(uniform(r)
|
|
407
|
+
? `<rect x="${n(sx)}" y="${n(sy)}" width="${n(c.w)}" height="${n(c.h)}"${r[0] ? ` rx="${n(r[0])}"` : ""} fill="${rgba(c.sh.c)}"${attrs}${rotAttr(c)}/>`
|
|
408
|
+
: `<path d="${roundedPath(sx, sy, c.w, c.h, r)}" fill="${rgba(c.sh.c)}"${attrs}${rotAttr(c)}/>`);
|
|
409
|
+
}
|
|
410
|
+
body.push(uniform(r)
|
|
411
|
+
? `<rect x="${n(c.x)}" y="${n(c.y)}" width="${n(c.w)}" height="${n(c.h)}"${r[0] ? ` rx="${n(r[0])}"` : ""} fill="${fill}"${rotAttr(c)}/>`
|
|
412
|
+
: `<path d="${roundedPath(c.x, c.y, c.w, c.h, r)}" fill="${fill}"${rotAttr(c)}/>`);
|
|
413
|
+
drawn += 1;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
case KIND.BORDER: {
|
|
418
|
+
// The GL shader keeps a band from d = -thickness to d = 0: the border
|
|
419
|
+
// sits INSIDE the box, not centred on its edge. An SVG stroke is
|
|
420
|
+
// centred, so the rectangle is inset by half the thickness and the
|
|
421
|
+
// radius comes down by the same amount — otherwise a 2px border on a
|
|
422
|
+
// 100px box covers 101px of it and the corner is a hair too round.
|
|
423
|
+
const t = c.t || 1;
|
|
424
|
+
if (c.w <= t || c.h <= t) break;
|
|
425
|
+
const r = radiiOf(c).map((v) => Math.max(0, v - t / 2));
|
|
426
|
+
const x = c.x + t / 2, y = c.y + t / 2, w = c.w - t, h = c.h - t;
|
|
427
|
+
body.push(uniform(r)
|
|
428
|
+
? `<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}"${r[0] ? ` rx="${n(r[0])}"` : ""} fill="none" stroke="${rgba(c.c)}" stroke-width="${n(t)}"${rotAttr(c)}/>`
|
|
429
|
+
: `<path d="${roundedPath(x, y, w, h, r)}" fill="none" stroke="${rgba(c.c)}" stroke-width="${n(t)}"${rotAttr(c)}/>`);
|
|
430
|
+
drawn += 1;
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
case KIND.IMAGE: {
|
|
435
|
+
const img = c.src ? images.get(c.src) : null;
|
|
436
|
+
if (!img) { missingImages += 1; break; }
|
|
437
|
+
// `preserveAspectRatio="…slice"` IS object-fit: cover — fill the box and
|
|
438
|
+
// crop the overflow, centred, rather than distort the picture. The GL
|
|
439
|
+
// backend computes the same crop as a UV rectangle because a quad has
|
|
440
|
+
// nowhere else to put it.
|
|
441
|
+
// A crop window (`cu`, in the bitmap's own 0..1 space) is drawn by
|
|
442
|
+
// placing the WHOLE picture on the larger rectangle that puts that
|
|
443
|
+
// window over the box, and clipping to the box. `none` because the
|
|
444
|
+
// window is stretched to fill the box exactly — Figma's crop tool
|
|
445
|
+
// already keeps it the shape of the frame, so nothing is distorted.
|
|
446
|
+
const cu = c.cu;
|
|
447
|
+
const cw = cu ? cu[2] - cu[0] : 1, chh = cu ? cu[3] - cu[1] : 1;
|
|
448
|
+
const dw = cu ? c.w / cw : c.w, dh = cu ? c.h / chh : c.h;
|
|
449
|
+
const dx = cu ? c.x - cu[0] * dw : c.x, dy = cu ? c.y - cu[1] * dh : c.y;
|
|
450
|
+
let attrs = `x="${n(dx)}" y="${n(dy)}" width="${n(dw)}" height="${n(dh)}"` +
|
|
451
|
+
` preserveAspectRatio="${cu ? "none" : "xMidYMid slice"}" href="${esc(img.src)}"`;
|
|
452
|
+
const transforms = [];
|
|
453
|
+
if (c.rot) {
|
|
454
|
+
const cx = c.rox === undefined ? c.x + c.w / 2 : c.rox;
|
|
455
|
+
const cy = c.roy === undefined ? c.y + c.h / 2 : c.roy;
|
|
456
|
+
transforms.push(`rotate(${n(c.rot)} ${n(cx)} ${n(cy)})`);
|
|
457
|
+
}
|
|
458
|
+
if (c.fx || c.fy) {
|
|
459
|
+
// Mirroring is a scale about the box's own centre, which is what
|
|
460
|
+
// reading the texture backwards amounts to on the GL side.
|
|
461
|
+
const cx = c.x + c.w / 2, cy = c.y + c.h / 2;
|
|
462
|
+
transforms.push(`translate(${n(cx)} ${n(cy)}) scale(${c.fx ? -1 : 1} ${c.fy ? -1 : 1}) translate(${n(-cx)} ${n(-cy)})`);
|
|
463
|
+
}
|
|
464
|
+
if (transforms.length) attrs += ` transform="${transforms.join(" ")}"`;
|
|
465
|
+
const r = radiiOf(c);
|
|
466
|
+
if (r.some((v) => v > 0) || cu) {
|
|
467
|
+
// A photo in a rounded box is clipped by the same shape the box is
|
|
468
|
+
// drawn with — the GL backend runs it through the same distance
|
|
469
|
+
// field for the same reason. A cropped one is clipped to the box
|
|
470
|
+
// whatever its corners: the picture is drawn larger than the box
|
|
471
|
+
// on purpose and the rest of it must not show.
|
|
472
|
+
const id = `evgimgclip${uid++}`;
|
|
473
|
+
defs.push(`<clipPath id="${id}"><path d="${roundedPath(c.x, c.y, c.w, c.h, r)}"/></clipPath>`);
|
|
474
|
+
body.push(`<g clip-path="url(#${id})"><image ${attrs}/></g>`);
|
|
475
|
+
} else {
|
|
476
|
+
body.push(`<image ${attrs}/>`);
|
|
477
|
+
}
|
|
478
|
+
drawnImages += 1;
|
|
479
|
+
drawn += 1;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
case KIND.TEXT: {
|
|
484
|
+
if (!c.text) break;
|
|
485
|
+
// `xml:space="preserve"` is not optional: SVG collapses runs of spaces
|
|
486
|
+
// by default, and EVG emits a run's leading and trailing space as part
|
|
487
|
+
// of the run it measured. Without it an indented line starts flush.
|
|
488
|
+
let a = `x="${n(c.x)}" y="${n(baselineOf(c))}" fill="${rgba(c.c)}"` +
|
|
489
|
+
` font-family=""${esc(familyOf(c))}", ${esc(fallbackStack)}"` +
|
|
490
|
+
` font-size="${n(c.size)}"`;
|
|
491
|
+
if (c.weight) a += ` font-weight="${esc(c.weight)}"`;
|
|
492
|
+
if (c.italic) a += ` font-style="italic"`;
|
|
493
|
+
body.push(`<text ${a} xml:space="preserve"${rotAttr(c)}>${esc(verbatim(c.text))}</text>`);
|
|
494
|
+
textRuns += 1;
|
|
495
|
+
drawn += 1;
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
case KIND.PATH: {
|
|
500
|
+
const d = ringsPath(c, true);
|
|
501
|
+
if (!d) break;
|
|
502
|
+
// A gradient on a shape that is not its box. The stops run across the
|
|
503
|
+
// command's own rectangle, which is what the GL side hands its path
|
|
504
|
+
// shader as a uniform; `userSpaceOnUse` is how SVG says the same.
|
|
505
|
+
let pfill = rgba(c.c);
|
|
506
|
+
if (c.c2) {
|
|
507
|
+
const id = `evgpgrad${uid++}`;
|
|
508
|
+
const across = c.gd === 1;
|
|
509
|
+
const x0 = c.x, y0 = c.y, x1 = c.x + (across ? c.w : 0), y1 = c.y + (across ? 0 : c.h);
|
|
510
|
+
defs.push(
|
|
511
|
+
`<linearGradient id="${id}" gradientUnits="userSpaceOnUse"` +
|
|
512
|
+
` x1="${n(x0)}" y1="${n(y0)}" x2="${n(x1)}" y2="${n(y1)}">` +
|
|
513
|
+
`<stop offset="0" stop-color="${rgba(c.c)}"/><stop offset="1" stop-color="${rgba(c.c2)}"/>` +
|
|
514
|
+
`</linearGradient>`);
|
|
515
|
+
pfill = `url(#${id})`;
|
|
516
|
+
}
|
|
517
|
+
body.push(`<path d="${d}" fill="${pfill}"${c.eo ? ` fill-rule="evenodd"` : ""}${rotAttr(c)}/>`);
|
|
518
|
+
paths += 1;
|
|
519
|
+
drawn += 1;
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
case KIND.STROKE: {
|
|
524
|
+
const d = ringsPath(c, false);
|
|
525
|
+
if (!d) break;
|
|
526
|
+
body.push(`<path d="${d}" fill="none" stroke="${rgba(c.c)}" stroke-width="${n(c.t || 1)}"` +
|
|
527
|
+
` stroke-linejoin="round" stroke-linecap="round"${rotAttr(c)}/>`);
|
|
528
|
+
paths += 1;
|
|
529
|
+
drawn += 1;
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
default:
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// A producer that pushed more clips than it popped would otherwise leave the
|
|
539
|
+
// markup unbalanced, and an unbalanced tree is repaired by the HTML parser in
|
|
540
|
+
// whatever way it likes.
|
|
541
|
+
while (openClips > 0) { body.push("</g>"); openClips -= 1; }
|
|
542
|
+
|
|
543
|
+
svg.setAttribute("viewBox", `0 0 ${n(doc.width)} ${n(doc.height)}`);
|
|
544
|
+
svg.setAttribute("width", n(doc.width));
|
|
545
|
+
svg.setAttribute("height", n(doc.height));
|
|
546
|
+
svg.innerHTML = (defs.length ? `<defs>${defs.join("")}</defs>` : "") + body.join("");
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
drawn, textRuns, paths, images: drawnImages, missingImages, clips,
|
|
550
|
+
// Every element, not `childElementCount`: a clipped run lives inside a
|
|
551
|
+
// <g>, so the top-level count drops the whole of a scrolling panel and
|
|
552
|
+
// reports a frame as nearly empty when it is nothing of the kind.
|
|
553
|
+
nodes: svg.getElementsByTagName("*").length,
|
|
554
|
+
// The ripple is a GPU post-pass over the finished frame. There is no DOM
|
|
555
|
+
// equivalent, and saying so is better than silently dropping it.
|
|
556
|
+
// `doc.list.effect`, not `doc.effect`: the effect rides the LIST, the way
|
|
557
|
+
// the commands do, and read off the envelope this was null on every page
|
|
558
|
+
// that has ever had one — including the one it was written for. Nothing
|
|
559
|
+
// read it, which is why it stayed wrong.
|
|
560
|
+
unsupportedEffect: (doc.list && doc.list.effect) ? doc.list.effect.kind : null,
|
|
561
|
+
// And the same for the element-scoped ones: a starfield is a shader, and a
|
|
562
|
+
// backend with no render target draws the page without it. Named rather
|
|
563
|
+
// than counted, because the useful question here is WHICH effect a page
|
|
564
|
+
// loses when it is exported as SVG.
|
|
565
|
+
unsupportedEffects: (doc.list && doc.list.effects ? doc.list.effects : []).map((e) => e.kind),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function svgFor(target) {
|
|
570
|
+
if (target && target.namespaceURI === SVG_NS && target.tagName.toLowerCase() === "svg") return target;
|
|
571
|
+
let svg = target.querySelector(":scope > svg");
|
|
572
|
+
if (!svg) {
|
|
573
|
+
svg = document.createElementNS(SVG_NS, "svg");
|
|
574
|
+
svg.setAttribute("xmlns", SVG_NS);
|
|
575
|
+
target.appendChild(svg);
|
|
576
|
+
}
|
|
577
|
+
return svg;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* `backdrop-filter: blur(Npx)` — softening what is already painted behind a
|
|
582
|
+
* rectangle before drawing it.
|
|
583
|
+
*
|
|
584
|
+
* The GL backend needed a render target, a separable blur and a second shader
|
|
585
|
+
* for this. Here it is the CSS property it was named after, reached through a
|
|
586
|
+
* `<foreignObject>`, because SVG filters do not see through to what is under
|
|
587
|
+
* the element.
|
|
588
|
+
*
|
|
589
|
+
* A browser that does not support `backdrop-filter` draws the rectangle and
|
|
590
|
+
* loses only the softening, which is the right failure — the same one the
|
|
591
|
+
* display list's own header asks a backend to have.
|
|
592
|
+
*/
|
|
593
|
+
function backdrop(c) {
|
|
594
|
+
const r = radiiOf(c);
|
|
595
|
+
const radius = uniform(r) ? `${n(r[0])}px` : r.map((v) => `${n(v)}px`).join(" ");
|
|
596
|
+
return `<foreignObject x="${n(c.x)}" y="${n(c.y)}" width="${n(c.w)}" height="${n(c.h)}">` +
|
|
597
|
+
`<div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;` +
|
|
598
|
+
`border-radius:${radius};backdrop-filter:blur(${n(c.bb)}px);-webkit-backdrop-filter:blur(${n(c.bb)}px)"></div>` +
|
|
599
|
+
`</foreignObject>`;
|
|
600
|
+
}
|