ranger-compiler 3.5.1 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1064 -20
- package/LICENSE +3 -1
- package/README.md +123 -28
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79923 -56921
- package/dist/lib/JSON.rgr +102 -91
- package/dist/lib/Shell.rgr +4 -4
- package/dist/lib/apple/AppleToolchain.rgr +4 -4
- package/dist/lib/apple/README.md +5 -4
- package/dist/lib/apple/apple_test.rgr +1 -1
- package/dist/lib/core/README.md +1 -1
- package/dist/lib/evg/EVG.rgr +12 -0
- package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
- package/dist/lib/evg/EVGA11yTree.rgr +894 -0
- package/dist/lib/evg/EVGBox.rgr +267 -0
- package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
- package/dist/lib/evg/EVGCodepoint.rgr +316 -0
- package/dist/lib/evg/EVGColor.rgr +700 -0
- package/dist/lib/evg/EVGCommands.rgr +177 -0
- package/dist/lib/evg/EVGComponent.rgr +331 -0
- package/dist/lib/evg/EVGComponentTest.rgr +387 -0
- package/dist/lib/evg/EVGConnector.rgr +541 -0
- package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
- package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
- package/dist/lib/evg/EVGEasing.rgr +370 -0
- package/dist/lib/evg/EVGEffectTest.rgr +253 -0
- package/dist/lib/evg/EVGElement.rgr +4914 -0
- package/dist/lib/evg/EVGFixedTest.rgr +289 -0
- package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
- package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
- package/dist/lib/evg/EVGFling.rgr +244 -0
- package/dist/lib/evg/EVGFocus.rgr +400 -0
- package/dist/lib/evg/EVGFocusTest.rgr +399 -0
- package/dist/lib/evg/EVGGradient.rgr +322 -0
- package/dist/lib/evg/EVGGrapheme.rgr +190 -0
- package/dist/lib/evg/EVGGrid.rgr +975 -0
- package/dist/lib/evg/EVGHitTest.rgr +208 -0
- package/dist/lib/evg/EVGHoles.rgr +294 -0
- package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
- package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
- package/dist/lib/evg/EVGHostTree.rgr +834 -0
- package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
- package/dist/lib/evg/EVGImageDecode.rgr +117 -0
- package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
- package/dist/lib/evg/EVGInspect.rgr +883 -0
- package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
- package/dist/lib/evg/EVGJsonTest.rgr +535 -0
- package/dist/lib/evg/EVGLayout.rgr +4220 -0
- package/dist/lib/evg/EVGMeasure.rgr +1267 -0
- package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
- package/dist/lib/evg/EVGPatch.rgr +1721 -0
- package/dist/lib/evg/EVGPatchTest.rgr +692 -0
- package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
- package/dist/lib/evg/EVGReconcile.rgr +226 -0
- package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
- package/dist/lib/evg/EVGReject.rgr +126 -0
- package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
- package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
- package/dist/lib/evg/EVGRuler.rgr +232 -0
- package/dist/lib/evg/EVGRulerTest.rgr +172 -0
- package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
- package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
- package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
- package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
- package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
- package/dist/lib/evg/EVGText.rgr +105 -0
- package/dist/lib/evg/EVGTextEngine.rgr +536 -0
- package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
- package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
- package/dist/lib/evg/EVGToolbar.rgr +1369 -0
- package/dist/lib/evg/EVGTransition.rgr +728 -0
- package/dist/lib/evg/EVGTreeJson.rgr +721 -0
- package/dist/lib/evg/EVGUnit.rgr +554 -0
- package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
- package/dist/lib/evg/EvgApp.rgr +189 -0
- package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
- package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
- package/dist/lib/evg/EvgHost.rgr +451 -0
- package/dist/lib/evg/EvgTest.rgr +91 -0
- package/dist/lib/evg/EvgTraceColor.rgr +236 -0
- package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
- package/dist/lib/evg/EvgTraceFit.rgr +767 -0
- package/dist/lib/evg/EvgTracePath.rgr +435 -0
- package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
- package/dist/lib/evg/EvgViewport.rgr +313 -0
- package/dist/lib/evg/FxDemoDoc.rgr +185 -0
- package/dist/lib/evg/HOSTS.md +227 -0
- package/dist/lib/evg/ISSUES.md +835 -0
- package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
- package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
- package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
- package/dist/lib/evg/PLAN_EVG.md +518 -0
- package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
- package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
- package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
- package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
- package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
- package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
- package/dist/lib/evg/PathBuilder.rgr +333 -0
- package/dist/lib/evg/README.md +1236 -0
- package/dist/lib/evg/SPEC.md +1252 -0
- package/dist/lib/evg/SVGPathParser.rgr +1199 -0
- package/dist/lib/evg/SvgParser.rgr +1736 -0
- package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
- package/dist/lib/evg/VectorShapes.rgr +331 -0
- package/dist/lib/evg/VectorStroke.rgr +107 -0
- package/dist/lib/evg/VectorViewBox.rgr +380 -0
- package/dist/lib/evg/agent/README.md +290 -0
- package/dist/lib/evg/agent/evg_agent.rgr +899 -0
- package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
- package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
- package/dist/lib/evg/agent/fixtures/connector.css +63 -0
- package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
- package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
- package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
- package/dist/lib/evg/agent/fixtures/popover.css +99 -0
- package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
- package/dist/lib/evg/agent/roundtrip.sh +74 -0
- package/dist/lib/evg/agent/smoke.sh +230 -0
- package/dist/lib/evg/android/README.md +97 -0
- package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
- package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
- package/dist/lib/evg/android/androidstubs/App.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
- package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
- package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
- package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
- package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
- package/dist/lib/evg/android/androidstubs/View.kt +113 -0
- package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
- package/dist/lib/evg/apple/README.md +128 -0
- package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
- package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
- package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
- package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
- package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
- package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
- package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
- package/dist/lib/evg/bench/README.md +64 -0
- package/dist/lib/evg/bench/layout-bench.mjs +316 -0
- package/dist/lib/evg/bench/layout-cases.mjs +510 -0
- package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
- package/dist/lib/evg/bin/.gitignore +17 -0
- package/dist/lib/evg/evg_test.rgr +313 -0
- package/dist/lib/evg/gl/README.md +198 -0
- package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
- package/dist/lib/evg/gl/blur-check.mjs +399 -0
- package/dist/lib/evg/gl/boxmodel.json +1 -0
- package/dist/lib/evg/gl/demo.html +46 -0
- package/dist/lib/evg/gl/effect-presets.css +285 -0
- package/dist/lib/evg/gl/effect-presets.js +63 -0
- package/dist/lib/evg/gl/effect-shots.mjs +135 -0
- package/dist/lib/evg/gl/evg-a11y.js +566 -0
- package/dist/lib/evg/gl/evg-binary.js +160 -0
- package/dist/lib/evg/gl/evg-engine.js +296 -0
- package/dist/lib/evg/gl/evg-fx.js +237 -0
- package/dist/lib/evg/gl/evg-gestures.js +209 -0
- package/dist/lib/evg/gl/evg-list.js +167 -0
- package/dist/lib/evg/gl/evg-measure.js +186 -0
- package/dist/lib/evg/gl/evg-textinput.js +303 -0
- package/dist/lib/evg/gl/evg-view.js +144 -0
- package/dist/lib/evg/gl/evg-webgl.js +3942 -0
- package/dist/lib/evg/gl/fx-check.mjs +998 -0
- package/dist/lib/evg/gl/fx-demo.html +166 -0
- package/dist/lib/evg/gl/fx-demo.js +2 -0
- package/dist/lib/evg/gl/fx-serve.mjs +47 -0
- package/dist/lib/evg/gl/gestures-check.mjs +189 -0
- package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
- package/dist/lib/evg/gl/measure-check.mjs +135 -0
- package/dist/lib/evg/gl/rotation-check.mjs +207 -0
- package/dist/lib/evg/gl/shift-check.mjs +113 -0
- package/dist/lib/evg/gl/stroke-check.mjs +168 -0
- package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
- package/dist/lib/evg/gl/view-check.mjs +343 -0
- package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
- package/dist/lib/evg/html/evg-dom.js +318 -0
- package/dist/lib/evg/html/evg-html.js +600 -0
- package/dist/lib/evg/inspect/README.md +415 -0
- package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
- package/dist/lib/evg/inspect/evg-inspect.js +947 -0
- package/dist/lib/evg/inspect/shots/css.png +0 -0
- package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
- package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
- package/dist/lib/evg/inspect/shots/state.png +0 -0
- package/dist/lib/evg/inspect/shots.mjs +226 -0
- package/dist/lib/evg/oracle/css-blur.json +576 -0
- package/dist/lib/evg/oracle/css-box.json +157 -0
- package/dist/lib/evg/oracle/css-timing.json +709 -0
- package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
- package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
- package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
- package/dist/lib/evg/original/EVGColor.clj +310 -0
- package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
- package/dist/lib/evg/original/SVGPath.rgr +627 -0
- package/dist/lib/evg/original/Vec2.crgr +103 -0
- package/dist/lib/evg/ranger.json +9 -0
- package/dist/lib/evg/showcase/README.md +246 -0
- package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
- package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
- package/dist/lib/evg/showcase/build.mjs +651 -0
- package/dist/lib/evg/showcase/pages/album.tsx +30 -0
- package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
- package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
- package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
- package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
- package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
- package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
- package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
- package/dist/lib/evg/showcase/pages/more.tsx +332 -0
- package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
- package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
- package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
- package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
- package/dist/lib/evg/showcase/pages/units.tsx +35 -0
- package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
- package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
- package/dist/lib/evg/showcase/pages/views.tsx +223 -0
- package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
- package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
- package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
- package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
- package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
- package/dist/lib/evg/showcase/themes/more-default.css +128 -0
- package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
- package/dist/lib/evg/showcase/themes/showcase.css +508 -0
- package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
- package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
- package/dist/lib/evg/showcase/themes/views-default.css +15 -0
- package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
- package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
- package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
- package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
- package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
- package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
- package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
- package/dist/lib/evg/web/responsive/README.md +97 -0
- package/dist/lib/evg/web/responsive/build.mjs +90 -0
- package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
- package/dist/lib/evg/web/responsive/index.html +177 -0
- package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
- package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
- package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
- package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
- package/dist/lib/evg/web/tools/minify.mjs +60 -0
- package/dist/lib/evg/web/tracer/build.mjs +87 -0
- package/dist/lib/evg/web/tracer/index.html +2805 -0
- package/dist/lib/evg/web/tracer/sample.png +0 -0
- package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
- package/dist/lib/evgr/Cargo.lock +21 -0
- package/dist/lib/evgr/Cargo.toml +19 -0
- package/dist/lib/evgr/README.md +140 -0
- package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
- package/dist/lib/evgr/bench/compare.mjs +252 -0
- package/dist/lib/evgr/bench/speed.mjs +277 -0
- package/dist/lib/evgr/bin/.gitignore +3 -0
- package/dist/lib/evgr/src/bin/bench.rs +106 -0
- package/dist/lib/evgr/src/bin/smoke.rs +13 -0
- package/dist/lib/evgr/src/grid.rs +730 -0
- package/dist/lib/evgr/src/lib.rs +1003 -0
- package/dist/lib/evgr/src/style.rs +468 -0
- package/dist/lib/evgr/src/text.rs +83 -0
- package/dist/lib/image/BitReader.rgr +171 -0
- package/dist/lib/image/Buffer.rgr +173 -0
- package/dist/lib/image/DCT.rgr +283 -0
- package/dist/lib/image/Deflate.rgr +339 -0
- package/dist/lib/image/HuffmanDecoder.rgr +181 -0
- package/dist/lib/image/ImageBuffer.rgr +706 -0
- package/dist/lib/image/JPEGDecoder.rgr +808 -0
- package/dist/lib/image/PNGDecoder.rgr +544 -0
- package/dist/lib/image/PNGEncoder.rgr +388 -0
- package/dist/lib/image/PPMImage.rgr +224 -0
- package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
- package/dist/lib/image/README.md +33 -0
- package/dist/lib/image/RasterBuffer.rgr +294 -0
- package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
- package/dist/lib/image/WebPDecoder.rgr +433 -0
- package/dist/lib/image/WebPLossless.rgr +1035 -0
- package/dist/lib/image/WebPLossy.rgr +1929 -0
- package/dist/lib/image/ranger.json +9 -0
- package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
- package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
- package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
- package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
- package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
- package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
- package/dist/lib/image/tests/run_webp_tests.sh +39 -0
- package/dist/lib/rust/RsJson.rgr +468 -0
- package/dist/lib/rust/RsPrelude.rgr +1663 -0
- package/dist/lib/shell_test.rgr +7 -7
- package/dist/lib/stdlib.rgr +21 -5
- package/dist/lib/zip/ranger.json +6 -0
- package/dist/rgrc.js +87318 -67610
- package/package.json +1010 -1040
- package/dist/README.md +0 -117
- package/dist/package.json +0 -47
|
@@ -0,0 +1,4914 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
; EVGElement.rgr - Base element class for EVG layout
|
|
4
|
+
|
|
5
|
+
Import "EVGUnit.rgr"
|
|
6
|
+
Import "EVGReject.rgr"
|
|
7
|
+
Import "EVGColor.rgr"
|
|
8
|
+
Import "EVGEasing.rgr"
|
|
9
|
+
Import "EVGBox.rgr"
|
|
10
|
+
Import "EVGGradient.rgr"
|
|
11
|
+
Import "SvgParser.rgr"
|
|
12
|
+
Import "SVGPathParser.rgr"
|
|
13
|
+
|
|
14
|
+
; Element type constants
|
|
15
|
+
; 0 = CONTAINER (div)
|
|
16
|
+
; 1 = TEXT (span)
|
|
17
|
+
; 2 = IMAGE (img)
|
|
18
|
+
; 3 = PATH (svg path)
|
|
19
|
+
|
|
20
|
+
; One property on its way from one value to another.
|
|
21
|
+
;
|
|
22
|
+
; Colours and numbers are both here rather than in two classes: a flight is a
|
|
23
|
+
; start, an end and a clock, and the only difference is how the two ends are
|
|
24
|
+
; read back out. Splitting them would double the bookkeeping to save one field.
|
|
25
|
+
class EVGFlight {
|
|
26
|
+
def property:string ""
|
|
27
|
+
; The duration this flight is actually running for. Not the one the sheet
|
|
28
|
+
; declared — a reversal shortens it, and `reversingFactor` is the record of
|
|
29
|
+
; by how much.
|
|
30
|
+
def durationMs:double 0.0
|
|
31
|
+
def delayMs:double 0.0
|
|
32
|
+
def elapsedMs:double 0.0
|
|
33
|
+
def easing:EVGEasing (new EVGEasing())
|
|
34
|
+
; Colours, when the property is one.
|
|
35
|
+
def fromColor:EVGColor
|
|
36
|
+
def toColor:EVGColor
|
|
37
|
+
; A plain number, when it is not.
|
|
38
|
+
def fromNumber:double 0.0
|
|
39
|
+
def toNumber:double 0.0
|
|
40
|
+
def isColor:boolean false
|
|
41
|
+
; The EVGUnit type this number is expressed in, for the flights where that
|
|
42
|
+
; can differ between the two ends. 25 per cent and 25 pixels are the same
|
|
43
|
+
; number and not the same place, so a flight whose unit has changed is not
|
|
44
|
+
; a flight at all.
|
|
45
|
+
def unitCode:int 0
|
|
46
|
+
|
|
47
|
+
; --- what a reversal needs to remember ------------------------------------
|
|
48
|
+
;
|
|
49
|
+
; CSS calls this the "reversing-adjusted start value": the value that, if
|
|
50
|
+
; the property is asked to go back to it, means this is a REVERSAL rather
|
|
51
|
+
; than a new journey somewhere else. For a fresh transition it is where the
|
|
52
|
+
; flight left from; after a reversal it is the target the flight was
|
|
53
|
+
; heading for before it turned round.
|
|
54
|
+
;
|
|
55
|
+
; The distinction matters. A hover that leaves half way is a reversal and
|
|
56
|
+
; must come back in half the time; a hover that leaves and lands on a THIRD
|
|
57
|
+
; colour is not, and gets the full duration. Comparing against the start
|
|
58
|
+
; value alone gets the second case right and the first case wrong as soon
|
|
59
|
+
; as the pointer changes its mind twice.
|
|
60
|
+
def reversingStartColor:EVGColor
|
|
61
|
+
def reversingStartNumber:double 0.0
|
|
62
|
+
def reversingFactor:double 1.0
|
|
63
|
+
|
|
64
|
+
; --- what this flight last put on the element -----------------------------
|
|
65
|
+
;
|
|
66
|
+
; `reconcile` reads the TARGET off the element and `writeBack` writes the
|
|
67
|
+
; SHOWING value to the same field, so the two can be told apart only if the
|
|
68
|
+
; flight remembers what it wrote. Without that, a second `reconcile` in the
|
|
69
|
+
; same frame reads its own output back, decides the target has moved to
|
|
70
|
+
; wherever the animation has got to, and retargets there — which collapses
|
|
71
|
+
; the flight to a standstill.
|
|
72
|
+
;
|
|
73
|
+
; That is not hypothetical. `laidOut()` runs once per question a host asks
|
|
74
|
+
; — what to draw, what is under the pointer, what it means — so three
|
|
75
|
+
; reconciles a frame is ordinary. It stayed hidden because the stylesheet
|
|
76
|
+
; rewrites its own values at the top of every one of them, which re-declares
|
|
77
|
+
; the target each time. Anything the stylesheet does NOT write — an inline
|
|
78
|
+
; attribute set by the author — had no such protection, and simply refused
|
|
79
|
+
; to animate.
|
|
80
|
+
def wroteNumber:double 0.0
|
|
81
|
+
def wroteColor:EVGColor
|
|
82
|
+
def hasWrote:boolean false
|
|
83
|
+
|
|
84
|
+
Constructor () {
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
; Where the clock is, 0 to 1, before the timing function has its say.
|
|
88
|
+
; The delay is part of the clock — a flight in its delay has not started
|
|
89
|
+
; moving and reads 0 — which is what the browser does and what makes
|
|
90
|
+
; `transition: opacity 200ms 100ms` hold still for its first tenth of a
|
|
91
|
+
; second instead of easing very slowly through it.
|
|
92
|
+
fn progress:double () {
|
|
93
|
+
if (durationMs <= 0.0) {
|
|
94
|
+
return 1.0
|
|
95
|
+
}
|
|
96
|
+
def t:double ((elapsedMs - delayMs) / durationMs)
|
|
97
|
+
if (t < 0.0) {
|
|
98
|
+
return 0.0
|
|
99
|
+
}
|
|
100
|
+
if (t > 1.0) {
|
|
101
|
+
return 1.0
|
|
102
|
+
}
|
|
103
|
+
return t
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
; What the value is actually interpolated by. Every reader wants this one;
|
|
107
|
+
; `progress()` is only the clock.
|
|
108
|
+
fn eased:double () {
|
|
109
|
+
return (easing.ease((this.progress())))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn done:boolean () {
|
|
113
|
+
return (elapsedMs >= (delayMs + durationMs))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class EVGElement {
|
|
118
|
+
def id:string ""
|
|
119
|
+
; The RECONCILER's name for this element, and not the application's.
|
|
120
|
+
;
|
|
121
|
+
; `id` is global and outward-facing: the hit test reports it, the
|
|
122
|
+
; accessibility tree carries it, and a page addresses a control by it.
|
|
123
|
+
; `key` is sibling-scoped and inward-facing: it says "this new child is the
|
|
124
|
+
; same logical child as that old one", so a rebuild can keep the element
|
|
125
|
+
; instead of replacing it. Two lists on one page may both use the key
|
|
126
|
+
; "video"; two elements may not share an id.
|
|
127
|
+
;
|
|
128
|
+
; "" means unkeyed, and an unkeyed child is matched by POSITION among its
|
|
129
|
+
; unkeyed siblings — the same rule every library that has this uses, and
|
|
130
|
+
; the reason a reordered list needs keys to animate.
|
|
131
|
+
def key:string ""
|
|
132
|
+
; Where this box goes when it is followed: a URL, or `#name` for a place
|
|
133
|
+
; in this same document.
|
|
134
|
+
;
|
|
135
|
+
; A destination is not application logic — PDF has `/Annots`, SVG has
|
|
136
|
+
; `<a>`, HTML has `<a>` — and a renderer that cannot say it is the one
|
|
137
|
+
; missing something. What it deliberately does NOT say is what following
|
|
138
|
+
; it means: the PDF writes an annotation and stops, and an interactive
|
|
139
|
+
; host asks the display list what is under the pointer and decides for
|
|
140
|
+
; itself. Nothing here navigates.
|
|
141
|
+
;
|
|
142
|
+
; It applies to the subtree, the way `<a>` does, so a link may be a word,
|
|
143
|
+
; a row or a card without the thing inside it knowing.
|
|
144
|
+
def href:string ""
|
|
145
|
+
def tagName:string "div"
|
|
146
|
+
def elementType:int 0
|
|
147
|
+
|
|
148
|
+
; Print/Book format properties
|
|
149
|
+
def format:string "" ; Book format: "a4", "trade-5x8", "large-square", etc.
|
|
150
|
+
def orientation:string "" ; "portrait" or "landscape"
|
|
151
|
+
def pageWidth:double 0.0 ; Resolved page width in points
|
|
152
|
+
def pageHeight:double 0.0 ; Resolved page height in points
|
|
153
|
+
|
|
154
|
+
; Tree structure
|
|
155
|
+
def parent@(optional):EVGElement
|
|
156
|
+
def children:[EVGElement]
|
|
157
|
+
|
|
158
|
+
; Dimensions
|
|
159
|
+
def width:EVGUnit
|
|
160
|
+
def height:EVGUnit
|
|
161
|
+
def minWidth:EVGUnit
|
|
162
|
+
def minHeight:EVGUnit
|
|
163
|
+
def maxWidth:EVGUnit
|
|
164
|
+
def maxHeight:EVGUnit
|
|
165
|
+
|
|
166
|
+
; Position (for absolute positioning)
|
|
167
|
+
def left:EVGUnit
|
|
168
|
+
def top:EVGUnit
|
|
169
|
+
def right:EVGUnit
|
|
170
|
+
def bottom:EVGUnit
|
|
171
|
+
def x:EVGUnit
|
|
172
|
+
def y:EVGUnit
|
|
173
|
+
|
|
174
|
+
; Box model
|
|
175
|
+
def box:EVGBox
|
|
176
|
+
|
|
177
|
+
; Visual properties
|
|
178
|
+
def backgroundColor:EVGColor
|
|
179
|
+
def opacity:double 1.0
|
|
180
|
+
; `backdrop-filter: blur(Npx)` — how far to blur what is BEHIND this
|
|
181
|
+
; element, in pixels, before painting the element over it. 0 is no filter.
|
|
182
|
+
;
|
|
183
|
+
; Behind, not itself: this does nothing to the element's own pixels. A
|
|
184
|
+
; modal's scrim wants the page under it softened while the scrim's own
|
|
185
|
+
; text stays sharp, and `filter: blur()` would do the opposite.
|
|
186
|
+
;
|
|
187
|
+
; The number is CSS's radius, which is the Gaussian's SIGMA and not its
|
|
188
|
+
; half-width — measured in `oracle/css-blur.json`, along with the more
|
|
189
|
+
; surprising half: browsers do not compute a Gaussian at all but the SVG
|
|
190
|
+
; spec's three-box approximation of one, and the difference is up to 14
|
|
191
|
+
; luminance levels at a hard edge.
|
|
192
|
+
def backdropBlur:double 0.0
|
|
193
|
+
|
|
194
|
+
; Linear-gradient background (RGU1 GRAD_FROM/GRAD_TO/GRAD_DIR). When gradientSet
|
|
195
|
+
; the renderer fills the (rounded) box with a 2-stop linear gradient instead of
|
|
196
|
+
; the solid backgroundColor. gradientDir: 0 = vertical (top->bottom), 1 =
|
|
197
|
+
; horizontal (left->right).
|
|
198
|
+
def gradientSet:boolean false
|
|
199
|
+
def gradientFrom:EVGColor
|
|
200
|
+
def gradientTo:EVGColor
|
|
201
|
+
def gradientDir:int 0
|
|
202
|
+
|
|
203
|
+
; Absolute placement (RGU1 ABS_X/ABS_Y): when absPosSet the element is laid out
|
|
204
|
+
; at (absX, absY) in page pixels instead of participating in flex flow — used
|
|
205
|
+
; for coord-reported overlay effects.
|
|
206
|
+
def absPosSet:boolean false
|
|
207
|
+
def absX:double 0.0
|
|
208
|
+
def absY:double 0.0
|
|
209
|
+
|
|
210
|
+
; Animated glow strength 0..1 (RGU1 GLOW prop). The guest drives this over
|
|
211
|
+
; time (a procedural effect animation); the renderer draws a soft halo behind
|
|
212
|
+
; the element scaled by it. 0 = no glow.
|
|
213
|
+
def glowIntensity:double 0.0
|
|
214
|
+
|
|
215
|
+
; Background image clipped to the (rounded) box (RGU1 BG_IMAGE prop). When
|
|
216
|
+
; bgImageSet the renderer paints bgImagePath inside the rounded rect instead of
|
|
217
|
+
; the solid/gradient fill. The path is host-resolvable (the guest is sandboxed;
|
|
218
|
+
; the host loads the pixels), so an .as menu can show real image tiles.
|
|
219
|
+
def bgImageSet:boolean false
|
|
220
|
+
def bgImagePath:string ""
|
|
221
|
+
|
|
222
|
+
; Layout properties
|
|
223
|
+
|
|
224
|
+
; CSS `direction`, which is NOT the `direction` below it.
|
|
225
|
+
;
|
|
226
|
+
; The property under that name here has always meant the flow AXIS —
|
|
227
|
+
; "row" or "column" — which predates this and is still what those two
|
|
228
|
+
; values mean. CSS's own `direction` takes "ltr" and "rtl" and means
|
|
229
|
+
; something else entirely: which end of a container its children start
|
|
230
|
+
; from. Two properties cannot share a name, and renaming the older one
|
|
231
|
+
; would break every element that sets it, so `setStyle` decides by VALUE:
|
|
232
|
+
; "ltr" and "rtl" land here, everything else lands on the axis.
|
|
233
|
+
;
|
|
234
|
+
; "" means the element said nothing and inherits, which is what `direction`
|
|
235
|
+
; does in CSS and what makes one setting on the root turn a whole UI
|
|
236
|
+
; around.
|
|
237
|
+
def textDir:string ""
|
|
238
|
+
; What that resolves to after inheritance. This is what the layout reads.
|
|
239
|
+
def resolvedRtl:boolean false
|
|
240
|
+
|
|
241
|
+
def direction:string "row"
|
|
242
|
+
def align:string "left"
|
|
243
|
+
def verticalAlign:string "top"
|
|
244
|
+
def isInline:boolean false
|
|
245
|
+
def lineBreak:boolean false
|
|
246
|
+
def overflow:string "visible"
|
|
247
|
+
|
|
248
|
+
; `cursor`, and it INHERITS, like the CSS property it is: a title bar says
|
|
249
|
+
; `cursor: move` once and the words on it do not have to say it again.
|
|
250
|
+
; Empty means nobody has said anything, which is what makes inheriting
|
|
251
|
+
; distinguishable from an author writing `default` on purpose.
|
|
252
|
+
def cursor:string ""
|
|
253
|
+
; The scroll container's bar, as CSS styles it: `scrollbar-width` (auto,
|
|
254
|
+
; thin, none), `scrollbar-color` (the thumb's colour, then the track's;
|
|
255
|
+
; `auto` for either is ink on whatever the bar is over), and the EVG
|
|
256
|
+
; extension `evg-scrollbar-label` (percent, none). Read by
|
|
257
|
+
; `EVGDisplayList.addScrollbar`, which draws the bar when an app asks
|
|
258
|
+
; the list to.
|
|
259
|
+
def scrollbarWidth:string ""
|
|
260
|
+
def scrollbarThumb:EVGColor
|
|
261
|
+
def scrollbarTrack:EVGColor
|
|
262
|
+
def scrollbarLabel:string ""
|
|
263
|
+
|
|
264
|
+
; --- A SURFACE EFFECT ------------------------------------------------------
|
|
265
|
+
;
|
|
266
|
+
; `evg-surface-effect: ripple`, and the family of `evg-ripple-*` numbers
|
|
267
|
+
; under it. The `evg-` prefix is not decoration: this is an EVG EXTENSION
|
|
268
|
+
; and not a CSS property, so nothing in the gallery should ever measure it
|
|
269
|
+
; against a browser and find a divergence — there is nothing to diverge
|
|
270
|
+
; from. Every other property in this file is CSS and is measured; these are
|
|
271
|
+
; ours, and they say so in their names.
|
|
272
|
+
;
|
|
273
|
+
; The effect belongs to a SURFACE, not to a box. It names a post-process
|
|
274
|
+
; over everything that was drawn: the display list carries it beside the
|
|
275
|
+
; commands rather than as one of them, and a backend that has never heard
|
|
276
|
+
; of it draws the page and ignores the field.
|
|
277
|
+
def surfaceEffect:string ""
|
|
278
|
+
; How fast the ring travels, in pixels a second.
|
|
279
|
+
def rippleSpeed:double 220.0
|
|
280
|
+
; How wide the ring is — the Gaussian envelope's sigma, in pixels.
|
|
281
|
+
def rippleWidth:double 28.0
|
|
282
|
+
; How far the surface is pushed at the crest, in pixels.
|
|
283
|
+
def rippleStrength:double 7.0
|
|
284
|
+
; How fast the whole thing dies away, per second.
|
|
285
|
+
def rippleDecay:double 1.8
|
|
286
|
+
; How much the crest lightens. Small: this is the difference between water
|
|
287
|
+
; and a filter somebody put on a video.
|
|
288
|
+
def rippleHighlight:double 0.08
|
|
289
|
+
|
|
290
|
+
; --- how many rings ONE touch sends -----------------------------------------
|
|
291
|
+
;
|
|
292
|
+
; A drop on water does not make one ring. It makes a TRAIN of them, born at
|
|
293
|
+
; the same point a moment apart and travelling out together, each fainter
|
|
294
|
+
; than the one in front: the shape is a single expanding target rather than
|
|
295
|
+
; a single circle.
|
|
296
|
+
;
|
|
297
|
+
; This costs no state at all. The rings of one touch differ only in when
|
|
298
|
+
; they started, and that is the touch's age minus a multiple of the stagger
|
|
299
|
+
; — so the shader works them out from the one age it already has, and a
|
|
300
|
+
; ring whose start is still in the future simply has not been born yet.
|
|
301
|
+
def rippleRings:double 3.0
|
|
302
|
+
; Seconds between one wavefront and the next.
|
|
303
|
+
def rippleStagger:double 0.09
|
|
304
|
+
; What each ring behind the front is worth. Below about a half the train
|
|
305
|
+
; reads as one ring with a smudge behind it.
|
|
306
|
+
def rippleFalloff:double 0.62
|
|
307
|
+
|
|
308
|
+
; --- and what makes it look WET ---------------------------------------------
|
|
309
|
+
;
|
|
310
|
+
; A brightness proportional to the wave's height is an AMBIENT term: every
|
|
311
|
+
; part of a crest lights up by the same amount however it is tilted, which
|
|
312
|
+
; is exactly what a matte surface does. Water is not matte. What makes it
|
|
313
|
+
; read as water is a SPECULAR: a highlight that depends on the angle
|
|
314
|
+
; between the light, the eye and the surface's own normal, so it lives on
|
|
315
|
+
; the FLANK of a wave rather than on its top and slides along as the wave
|
|
316
|
+
; moves.
|
|
317
|
+
;
|
|
318
|
+
; The normal comes from the height field the rings already sum to — its
|
|
319
|
+
; screen-space gradient is the slope, and the slope is the normal. Nothing
|
|
320
|
+
; new has to be computed or stored.
|
|
321
|
+
;
|
|
322
|
+
; How strong the glint is. 0 turns it off and leaves the ambient term.
|
|
323
|
+
def rippleShine:double 0.45
|
|
324
|
+
; The Blinn-Phong exponent: how TIGHT the glint is. Low is a broad sheen
|
|
325
|
+
; like wet stone, high is a sharp point like a bead of water. Past about
|
|
326
|
+
; 200 the glint stops being an arc along the ring and breaks into
|
|
327
|
+
; separate specks, because the surviving band gets narrower than a pixel.
|
|
328
|
+
def rippleGloss:double 120.0
|
|
329
|
+
; How much the wave's height tilts the normal. This is the one number with
|
|
330
|
+
; no physical meaning — the height field is in arbitrary units, so this is
|
|
331
|
+
; what turns it into a slope.
|
|
332
|
+
def rippleBump:double 70.0
|
|
333
|
+
; Where the light is. The default is over the viewer's left shoulder,
|
|
334
|
+
; which is where every UI in this gallery already pretends its light is.
|
|
335
|
+
def rippleLightX:double -0.45
|
|
336
|
+
def rippleLightY:double -0.65
|
|
337
|
+
def rippleLightZ:double 0.62
|
|
338
|
+
|
|
339
|
+
; --- and the state it needs, which is not styling ---------------------------
|
|
340
|
+
;
|
|
341
|
+
; Where the surface was touched and how long ago — for EVERY touch still in
|
|
342
|
+
; flight, not just the last one. Set by the APPLICATION, like `scrollTop`:
|
|
343
|
+
; a stylesheet has no business owning where somebody clicked.
|
|
344
|
+
;
|
|
345
|
+
; Three parallel arrays rather than an array of triples, because that is
|
|
346
|
+
; what goes into a `vec3` uniform array without a copy at the other end.
|
|
347
|
+
; Empty means nothing is in flight.
|
|
348
|
+
;
|
|
349
|
+
; MANY of them, and that is the whole difference between an effect and a
|
|
350
|
+
; surface: two rings that cross ADD, so the crests reinforce where they
|
|
351
|
+
; meet and cancel a quarter-wavelength away. Interference is not something
|
|
352
|
+
; the shader implements — it is what summing waves does.
|
|
353
|
+
def rippleXs:[double]
|
|
354
|
+
def rippleYs:[double]
|
|
355
|
+
def rippleAges:[double]
|
|
356
|
+
|
|
357
|
+
; --- WHICH BOX, AND WHAT STARTS IT ----------------------------------------
|
|
358
|
+
;
|
|
359
|
+
; `evg-surface-effect` above says WHAT to run. These two say WHERE and
|
|
360
|
+
; WHEN, and they are the whole difference between an effect a page has and
|
|
361
|
+
; an effect a page's code has to remember to drive.
|
|
362
|
+
;
|
|
363
|
+
; WHERE is this element's own border box: an effect declared on a card is
|
|
364
|
+
; the card's, and the display list carries the box the layout gave it, so
|
|
365
|
+
; nothing downstream has to be told a rectangle somebody typed.
|
|
366
|
+
;
|
|
367
|
+
; WHEN is `evg-effect-on`, a list of the pointer things that feed it —
|
|
368
|
+
; `press`, `drag`, `hover` — or `always` for one that runs on its own
|
|
369
|
+
; clock and needs no pointer at all. The default is EMPTY, which means the
|
|
370
|
+
; application drives it, and that is what the original ripple did from the
|
|
371
|
+
; page's own `pointerdown`: the property is how a page stops having to.
|
|
372
|
+
;
|
|
373
|
+
; Nothing in EVG reads this. It is written into the display list and read
|
|
374
|
+
; by the host's effect driver, for the same reason `cursor` is: what a
|
|
375
|
+
; pointer is belongs to whatever owns the pointer.
|
|
376
|
+
def effectTrigger:string ""
|
|
377
|
+
|
|
378
|
+
; --- THE PARAMETERS, FOR AN EFFECT THIS FILE HAS NEVER HEARD OF -----------
|
|
379
|
+
;
|
|
380
|
+
; `evg-ripple-speed` above is a field because the ripple was the only
|
|
381
|
+
; effect there was. The second effect cannot be — a starfield's density
|
|
382
|
+
; and a glitch's jitter are not fields on every element in the document,
|
|
383
|
+
; and a renderer plugin nobody has written yet cannot have its fields
|
|
384
|
+
; declared here in advance.
|
|
385
|
+
;
|
|
386
|
+
; So: `evg-fx-<name>: <number>` lands in this pair of parallel arrays
|
|
387
|
+
; under `<name>`, travels through the display list untouched, and means
|
|
388
|
+
; whatever the plugin that reads it says it means. Two arrays rather than
|
|
389
|
+
; a map because the order is the order they were written in, and a JSON
|
|
390
|
+
; writer that reorders keys between two runs makes every golden file a
|
|
391
|
+
; coin toss.
|
|
392
|
+
; THE DOCUMENT'S STYLESHEET, on the root element and nowhere else.
|
|
393
|
+
;
|
|
394
|
+
; A `.evg.json` could only ever say things inline: every node carried its
|
|
395
|
+
; own copy of the same six properties, and an agent asked to restyle a
|
|
396
|
+
; screen had to touch forty nodes to change one colour. EVG has had a CSS
|
|
397
|
+
; engine the whole time — `EVGStyleSheet`, with classes, pseudo-classes,
|
|
398
|
+
; themes and media queries — and the editable format simply had nowhere to
|
|
399
|
+
; put it.
|
|
400
|
+
;
|
|
401
|
+
; It is a STRING here, not a parsed sheet, because parsing it would make
|
|
402
|
+
; every element depend on the stylesheet and the stylesheet already depends
|
|
403
|
+
; on the element. `EVGLayout` reads it, parses it once and applies it; an
|
|
404
|
+
; inline property still outranks it, which is what makes the two mix.
|
|
405
|
+
def documentCss:string ""
|
|
406
|
+
|
|
407
|
+
def fxNames:[string]
|
|
408
|
+
def fxValues:[double]
|
|
409
|
+
|
|
410
|
+
; --- SCROLLING ------------------------------------------------------------
|
|
411
|
+
;
|
|
412
|
+
; How far the content inside this element is pushed up and left. Set by the
|
|
413
|
+
; APPLICATION, not by a stylesheet — a scroll position is state, and the
|
|
414
|
+
; sheet has no business owning it — and clamped by the layout to what there
|
|
415
|
+
; actually is to scroll, the way a browser clamps `scrollTop`.
|
|
416
|
+
def scrollTop:double 0.0
|
|
417
|
+
def scrollLeft:double 0.0
|
|
418
|
+
; How big the content turned out to be, written by the layout. These are
|
|
419
|
+
; the numbers a scrollbar is sized from and the reason a scroll container
|
|
420
|
+
; is not the same thing as a clipped box: a clipped box throws the overflow
|
|
421
|
+
; away, a scroll container remembers how much of it there was.
|
|
422
|
+
;
|
|
423
|
+
; Measured the way the DOM measures them, PADDING BOX and not content box:
|
|
424
|
+
; eight 60-pixel rows in a box with 10 of padding gives 500, not 480. The
|
|
425
|
+
; two definitions produce the same `maxScrollTop` — the paddings cancel —
|
|
426
|
+
; so this could have been either, and it is the browser's, because a number
|
|
427
|
+
; that is only right after you subtract something is a number somebody
|
|
428
|
+
; will forget to subtract.
|
|
429
|
+
def scrollWidth:double 0.0
|
|
430
|
+
def scrollHeight:double 0.0
|
|
431
|
+
; The offset the LAYOUT has actually translated this subtree by, which is
|
|
432
|
+
; not the same thing as the offset the application asked for. A wheel event
|
|
433
|
+
; moves `scrollTop` and nothing else; these say where the boxes are now, so
|
|
434
|
+
; `EVGLayout.scrollOnly` can move them by the difference instead of laying
|
|
435
|
+
; the document out again to arrive at the same widths.
|
|
436
|
+
def appliedScrollTop:double 0.0
|
|
437
|
+
def appliedScrollLeft:double 0.0
|
|
438
|
+
|
|
439
|
+
; --- WHERE THIS SUBTREE DRAWS ---------------------------------------------
|
|
440
|
+
;
|
|
441
|
+
; The union of this element's own box and every box under it, written by
|
|
442
|
+
; the layout and moved with the subtree. It is what lets the display list
|
|
443
|
+
; skip a subtree that is nowhere near the scroll viewport WITHOUT walking
|
|
444
|
+
; into it first: an element's own box says nothing about where its
|
|
445
|
+
; children are — a zero-height wrapper is at the top of the document and
|
|
446
|
+
; its rows are ten thousand pixels down — so the box alone is not a safe
|
|
447
|
+
; thing to cull against, and this is.
|
|
448
|
+
;
|
|
449
|
+
; `paintUnbounded` is the honest answer for a subtree whose drawn output
|
|
450
|
+
; is NOT its layout boxes: a transform moves the pixels after the fact and
|
|
451
|
+
; a rotation moves them somewhere no rectangle here predicts. Such a
|
|
452
|
+
; subtree is never culled, and the flag rises to its ancestors — which
|
|
453
|
+
; costs those ancestors their own culling and nothing else, because their
|
|
454
|
+
; other children are still tested on their own bounds.
|
|
455
|
+
def paintLeft:double 0.0
|
|
456
|
+
def paintTop:double 0.0
|
|
457
|
+
def paintRight:double 0.0
|
|
458
|
+
def paintBottom:double 0.0
|
|
459
|
+
def paintUnbounded:boolean false
|
|
460
|
+
; A SCROLL OWED TO THE SUBTREE. This element's own box and paint bounds
|
|
461
|
+
; are where they should be; everything beneath it still has to move by
|
|
462
|
+
; this much. `EVGLayout.scrollOnly` writes it on the rows a scroll moved
|
|
463
|
+
; that are far from the viewport instead of walking their thousands of
|
|
464
|
+
; descendants — on a long diary that walk was the largest self-time in a
|
|
465
|
+
; profile of a fling, and it moved boxes nobody would look at. It is
|
|
466
|
+
; paid by `settleShift` the moment anything descends into the subtree:
|
|
467
|
+
; the display list, the hit test and the accessibility walk each settle
|
|
468
|
+
; a node before they look at its children, so a consumer reads a stale
|
|
469
|
+
; box only if it reaches one without going through those, and the
|
|
470
|
+
; whole-tree readers call `settleAll` first. A full layout resets it
|
|
471
|
+
; with the rest of the layout state.
|
|
472
|
+
def shiftDx:double 0.0
|
|
473
|
+
def shiftDy:double 0.0
|
|
474
|
+
; A LAYOUT KEPT. An app that knows a subtree has not changed — a card
|
|
475
|
+
; built from an entry and kept across rebuilds — says so with
|
|
476
|
+
; `keepLayout`, and the layout then answers for that subtree with what
|
|
477
|
+
; it computed last time, moved to where the flow puts it now, when
|
|
478
|
+
; three things hold: it was laid out before (`hasLayout`), the sheet's
|
|
479
|
+
; last pass wrote no layout property anywhere in it (`layoutClean`,
|
|
480
|
+
; which the sheet computes bottom-up for every element), and the flow
|
|
481
|
+
; hands it the same inputs — the width and height it is laid out in,
|
|
482
|
+
; and the flex width its parent gave it. The flow has already put the
|
|
483
|
+
; element's own box where it goes; what is under it follows by the
|
|
484
|
+
; difference, read off where the first child sat relative to the box
|
|
485
|
+
; when the subtree was last measured (`kidOffX`, `kidOffY`). A subtree
|
|
486
|
+
; skipped this way is `layoutSkipped`, so the paint-bounds pass leaves
|
|
487
|
+
; its bounds, which moved with it, alone. Anything else lays it out as
|
|
488
|
+
; before.
|
|
489
|
+
def keepLayout:boolean false
|
|
490
|
+
def hasLayout:boolean false
|
|
491
|
+
def layoutClean:boolean false
|
|
492
|
+
def layoutSkipped:boolean false
|
|
493
|
+
def kidOffX:double 0.0
|
|
494
|
+
def kidOffY:double 0.0
|
|
495
|
+
; Whether `paintTop` and the rest were measured since the subtree was
|
|
496
|
+
; last laid out for real: a subtree skipped in a later pass of the same
|
|
497
|
+
; layout — a parent's second pass — moved bounds that were never
|
|
498
|
+
; measured, and the measuring pass must not take a skipped subtree's
|
|
499
|
+
; word for it until they were.
|
|
500
|
+
def boundsFresh:boolean false
|
|
501
|
+
; …and the same question for the overlay pass: does this subtree hold a
|
|
502
|
+
; surface that has to be placed against an anchor, and has it been asked?
|
|
503
|
+
; A subtree the layout kept moved WHOLE — the surface and the anchor it
|
|
504
|
+
; sits against went together — so its placement still holds and the pass
|
|
505
|
+
; can take the answer it gave last time instead of walking it. See
|
|
506
|
+
; `EVGLayout.placeOverlaysIn`.
|
|
507
|
+
def hasOverlayBelow:boolean false
|
|
508
|
+
def overlayScanned:boolean false
|
|
509
|
+
def lastParentW:double 0.0
|
|
510
|
+
def lastParentH:double 0.0
|
|
511
|
+
def lastFlex:boolean false
|
|
512
|
+
def lastFlexW:double 0.0
|
|
513
|
+
; …and the height the parent handed down (grown, stretched or shrunk).
|
|
514
|
+
def lastFlexH:double 0.0
|
|
515
|
+
def hasIntrinsicMin:boolean false
|
|
516
|
+
def hasIntrinsicMax:boolean false
|
|
517
|
+
def intrinsicMin:double 0.0
|
|
518
|
+
def intrinsicMax:double 0.0
|
|
519
|
+
; THE COMMANDS KEPT. A kept subtree draws the same commands each time,
|
|
520
|
+
; so the display list keeps what it emitted for it and emits it again.
|
|
521
|
+
; `paintStamp` says whether they still hold: the sheet bumps it when a
|
|
522
|
+
; pass wrote paint anywhere in the subtree (`paintClean` is that pass's
|
|
523
|
+
; answer), and the layout bumps it when it lays the subtree out for
|
|
524
|
+
; real. `EVGDisplayList.walk` has the rest.
|
|
525
|
+
def paintClean:boolean false
|
|
526
|
+
def paintStamp:int 0
|
|
527
|
+
; Does anything in this subtree carry a `surfaceEffect`? Written by the
|
|
528
|
+
; same pass, and read by `EVGDisplayList.readEffect` — which used to walk
|
|
529
|
+
; the WHOLE document on every frame looking for the one element that
|
|
530
|
+
; might, and in a page that has none walked all of it and found nothing.
|
|
531
|
+
; It was 11% of a scroll frame doing exactly that.
|
|
532
|
+
def paintHasEffect:boolean false
|
|
533
|
+
; The name the display list's effect pass gave this element's instance on
|
|
534
|
+
; the last build — the element's own `id` when it has one. Written there
|
|
535
|
+
; and read by the walk a moment later, so the rectangle that carries the
|
|
536
|
+
; effect and the instance it refers to cannot disagree about its name.
|
|
537
|
+
;
|
|
538
|
+
; IN-FLIGHT STATE, and `adoptFrom` deliberately does not carry it across a
|
|
539
|
+
; reconcile: `EVGDisplayList.build` names every instance again before the
|
|
540
|
+
; walk that reads them, so a kept element's stale name and a fresh
|
|
541
|
+
; element's empty one come to exactly the same thing one build later.
|
|
542
|
+
; `scripts/check-evg-adopt.mjs` holds that decision on the record.
|
|
543
|
+
def effectRuntimeId:string ""
|
|
544
|
+
|
|
545
|
+
; --- WHERE THE ELEMENT'S OWN TEXT SITS INSIDE IT --------------------------
|
|
546
|
+
;
|
|
547
|
+
; Normally nowhere: a block's line boxes start at the top of its content
|
|
548
|
+
; box and that is the end of it. But an element that is a FLEX CONTAINER
|
|
549
|
+
; and carries text of its own does not lay that text out as a block —
|
|
550
|
+
; CSS wraps it in an ANONYMOUS FLEX ITEM, and from then on `align-items`
|
|
551
|
+
; and `justify-content` place it like any other item.
|
|
552
|
+
;
|
|
553
|
+
; That is the pill idiom, and it is written that way everywhere:
|
|
554
|
+
;
|
|
555
|
+
; .pill { display: flex; align-items: center; height: 34px; padding: 0 12px }
|
|
556
|
+
;
|
|
557
|
+
; with the label as the element's own text. EVG placed the line at the top
|
|
558
|
+
; of the content box and never consulted `align-items`, so the label sat
|
|
559
|
+
; against the pill's top edge instead of in the middle of it.
|
|
560
|
+
;
|
|
561
|
+
; The layout writes the offset here and the display list adds it to every
|
|
562
|
+
; line, so the box's height and the painter's baseline agree — as they
|
|
563
|
+
; must, because `calculatedBaseline` includes it too.
|
|
564
|
+
def textShiftY:double 0.0
|
|
565
|
+
|
|
566
|
+
; Typography (inherited)
|
|
567
|
+
def fontSize:EVGUnit
|
|
568
|
+
; fontSize starts UNSET: an element is born without one so that "the author
|
|
569
|
+
; asked for this size" stays distinguishable from "nobody said". Layout
|
|
570
|
+
; fills it in from the inherited size for every element that does not set
|
|
571
|
+
; one, so by the time a renderer reads it, it is always set — and remembers
|
|
572
|
+
; that it did so, or a re-layout would mistake its own fill-in for an
|
|
573
|
+
; authored size and stop inheriting.
|
|
574
|
+
def fontSizeInherited:boolean false
|
|
575
|
+
; The size inherited from the parent, kept apart from the resolved one so
|
|
576
|
+
; that an `em` font-size resolved twice does not compound.
|
|
577
|
+
def fontSizeBase:double 14.0
|
|
578
|
+
; The font-size of the tree's root, which is what `rem` resolves against.
|
|
579
|
+
; Inherited unchanged all the way down.
|
|
580
|
+
def rootFontSize:double 14.0
|
|
581
|
+
; What `vw` and `vh` on this element resolve against: the layout's page
|
|
582
|
+
; size, published onto the root by `EVGLayout.layout` and inherited from
|
|
583
|
+
; there like the root font size. Zero until a layout says otherwise — see
|
|
584
|
+
; the note at the top of EVGUnit.
|
|
585
|
+
def viewportW:double 0.0
|
|
586
|
+
def viewportH:double 0.0
|
|
587
|
+
; A BOX THAT IS A VIEWPORT OF ITS OWN. `fixed` is positioned against the
|
|
588
|
+
; viewport, and until now there was exactly one of those per layout — the
|
|
589
|
+
; page. A stage that shows the same document at 1440, 768 and 390 in one
|
|
590
|
+
; tree has three, and a modal in the phone frame belongs over the phone
|
|
591
|
+
; frame and not over the whole stage. An element with this set keeps its
|
|
592
|
+
; own `viewportW/H` and `viewportX/Y` instead of inheriting them, and
|
|
593
|
+
; every `fixed` box under it resolves against that rectangle. Off, the
|
|
594
|
+
; viewport is the page's, as it always was.
|
|
595
|
+
def viewportRoot:boolean false
|
|
596
|
+
; Where that viewport's own (0,0) is, in the coordinates the layout works
|
|
597
|
+
; in. Zero for the page, which is where it starts.
|
|
598
|
+
def viewportX:double 0.0
|
|
599
|
+
def viewportY:double 0.0
|
|
600
|
+
def fontFamily:string "Noto Sans"
|
|
601
|
+
def fontWeight:string "normal"
|
|
602
|
+
; Extra advance after every character, in pixels — CSS `letter-spacing`,
|
|
603
|
+
; and negative is the tracking a designer tightens a heading with.
|
|
604
|
+
; ZERO is the font's own spacing and every measurement and every painter
|
|
605
|
+
; takes the same path it always did, so nothing that does not set this
|
|
606
|
+
; can be moved by it.
|
|
607
|
+
def letterSpacing:double 0.0
|
|
608
|
+
; Extra leading above every paragraph after the first, in pixels. CSS
|
|
609
|
+
; has no such property — it puts paragraphs in elements and gives them
|
|
610
|
+
; margins — but a text element here holds the whole block, newlines
|
|
611
|
+
; included, so the gap has to be a property of the block. ZERO is the
|
|
612
|
+
; single-spaced block every caller had before.
|
|
613
|
+
def paragraphSpacing:double 0.0
|
|
614
|
+
; SVG's `stroke-linecap` and `stroke-linejoin` on a stroked path.
|
|
615
|
+
; "butt" and "miter" are what the painter drew before either existed,
|
|
616
|
+
; so a caller that says nothing keeps the picture it had.
|
|
617
|
+
def strokeLineCap:string "butt"
|
|
618
|
+
def strokeLineJoin:string "miter"
|
|
619
|
+
; ZERO means `normal`, which is the FACE's own line box and is asked of
|
|
620
|
+
; the text engine. It used to default to 1.2 — a number people type, not
|
|
621
|
+
; one a browser computes: measured, the sans fallback's normal is 1.15em
|
|
622
|
+
; and monospace's 1.164, so every text box in the gallery was four per
|
|
623
|
+
; cent taller than the same declarations give in a browser.
|
|
624
|
+
def lineHeight:double 0.0
|
|
625
|
+
; `line-height` WRITTEN AS A LENGTH, which is a different thing from
|
|
626
|
+
; written as a number and CSS keeps them apart on purpose: `1.5` is one
|
|
627
|
+
; and a half times whatever font size the element ends up with, `24px` is
|
|
628
|
+
; twenty-four pixels whatever the font size is, and `150%` is computed
|
|
629
|
+
; once against this element's own size and then inherited as that
|
|
630
|
+
; computed value.
|
|
631
|
+
;
|
|
632
|
+
; It used to be one double, so `line-height: 24px` went through
|
|
633
|
+
; `to_double`, came out 24, and was used as a MULTIPLIER: a 14px run got a
|
|
634
|
+
; 336-pixel line box. The text then sat at the top of a line box twelve
|
|
635
|
+
; times too tall — which reads, on the screen, as a line that has fallen
|
|
636
|
+
; to the bottom of everything around it.
|
|
637
|
+
;
|
|
638
|
+
; Unset means the number above (or `normal`, which is zero there). See
|
|
639
|
+
; `lineBoxFor`, which is the only thing that should read either.
|
|
640
|
+
def lineHeightUnit:EVGUnit
|
|
641
|
+
def textAlign:string "left"
|
|
642
|
+
; `white-space`, of which two values matter here: `normal`, which wraps at
|
|
643
|
+
; the box's width, and `nowrap`, which does not wrap at all — the line is
|
|
644
|
+
; as long as the text and the box clips it if it clips anything.
|
|
645
|
+
;
|
|
646
|
+
; A ONE-LINE FIELD IS THE REASON. A text input is one line whatever is in
|
|
647
|
+
; it: an `<input>` never wraps, it scrolls. Without this, a placeholder
|
|
648
|
+
; longer than its box broke onto a second line and fell out of the bottom
|
|
649
|
+
; of a 40-pixel row — which is what the quick entry on Home did with the
|
|
650
|
+
; longer of its two placeholders.
|
|
651
|
+
;
|
|
652
|
+
; INHERITED, as CSS has it, so `white-space: nowrap` on the field reaches
|
|
653
|
+
; the run inside it without every child having to say so.
|
|
654
|
+
def whiteSpace:string "normal"
|
|
655
|
+
def color:EVGColor
|
|
656
|
+
; What to paint glyphs that came from a FALLBACK face — emoji, in
|
|
657
|
+
; practice — when they should not be the colour of the text around them.
|
|
658
|
+
;
|
|
659
|
+
; Unset by default, which means "the same as `color`", so nothing changes
|
|
660
|
+
; for a document that does not ask. It exists because a monochrome emoji
|
|
661
|
+
; face can be filled with any colour at all, and the one thing the engine
|
|
662
|
+
; could not do was fill it with a DIFFERENT one from the sentence it sits
|
|
663
|
+
; in — there are no inline spans here to hang a second colour on.
|
|
664
|
+
def emojiColor:EVGColor
|
|
665
|
+
def textContent:string ""
|
|
666
|
+
|
|
667
|
+
; CSS Flexbox properties
|
|
668
|
+
def display:string "block"
|
|
669
|
+
def flex:double 0.0
|
|
670
|
+
; flex-basis: the main-axis size a flex item starts from, before grow or
|
|
671
|
+
; shrink. Unset means "use width". The `flex: N` shorthand sets it to 0,
|
|
672
|
+
; which is what makes `flex: 1` share space evenly even when the item also
|
|
673
|
+
; carries a width — without it an explicit width made `flex` a no-op.
|
|
674
|
+
; flex-shrink: how readily this item gives up space when the line
|
|
675
|
+
; overflows, relative to its siblings. CSS default is 1; 0 means "never
|
|
676
|
+
; shrink me", which is how a logo or a fixed label keeps its size while the
|
|
677
|
+
; rest of the row compresses.
|
|
678
|
+
def flexShrink:double 1.0
|
|
679
|
+
def flexBasis:EVGUnit
|
|
680
|
+
def flexDirection:string "column"
|
|
681
|
+
def justifyContent:string "flex-start"
|
|
682
|
+
def alignItems:string "flex-start"
|
|
683
|
+
; The item's own answer to its container's `align-items`. Empty means "did
|
|
684
|
+
; not say" — CSS spells that `auto`, and both are accepted — so an item
|
|
685
|
+
; that never mentions it costs one string comparison and nothing else.
|
|
686
|
+
def alignSelf:string ""
|
|
687
|
+
; align-content: how WRAPPED LINES are distributed along the cross axis,
|
|
688
|
+
; as opposed to alignItems which positions children within one line. Only
|
|
689
|
+
; meaningful when the content wraps and the container has room to spare.
|
|
690
|
+
def alignContent:string "flex-start"
|
|
691
|
+
; flexWrap: "wrap" (row auto-wraps — historical EVG default) or "nowrap"
|
|
692
|
+
; (CSS default; children never wrap, they overflow / shrink instead).
|
|
693
|
+
def flexWrap:string "wrap"
|
|
694
|
+
def gap:EVGUnit
|
|
695
|
+
; Axis-specific gaps. When unset, `gap` applies to both axes.
|
|
696
|
+
def rowGap:EVGUnit
|
|
697
|
+
def columnGap:EVGUnit
|
|
698
|
+
; CSS Grid subset (see EVGGrid.rgr). Empty means "not a grid track spec";
|
|
699
|
+
; a grid container with no column template gets a single 1fr column.
|
|
700
|
+
def gridTemplateColumns:string ""
|
|
701
|
+
def gridTemplateRows:string ""
|
|
702
|
+
; Per-item placement, e.g. "span 2" or "2 / 4". Empty means auto-flow.
|
|
703
|
+
; grid-template-areas: rows of names, e.g.
|
|
704
|
+
; "hero hero" "left right"
|
|
705
|
+
; An item claims a region with grid-area.
|
|
706
|
+
; Track sizes handed down by an enclosing grid when this element declares
|
|
707
|
+
; `grid-template-columns: subgrid` / `grid-template-rows: subgrid`. Filled
|
|
708
|
+
; in by EVGLayout just before the child is laid out, because the parent is
|
|
709
|
+
; the only thing that knows its own tracks.
|
|
710
|
+
def subgridColumnSizes:[double]
|
|
711
|
+
def subgridRowSizes:[double]
|
|
712
|
+
; The row heights this element's own grid worked out, left behind for an
|
|
713
|
+
; enclosing grid to read. A row subgrid always spans several of its
|
|
714
|
+
; parent's rows, and with subgrid it is the GRANDCHILDREN that have to size
|
|
715
|
+
; those rows — so the parent measures the subgrid once and adopts the rows
|
|
716
|
+
; it came up with, rather than trying to see through it.
|
|
717
|
+
def computedRowSizes:[double]
|
|
718
|
+
; Set by the enclosing grid as soon as it has claimed this element, which
|
|
719
|
+
; is well before it can size its tracks. It is what tells a subgrid child
|
|
720
|
+
; laid out during the parent's content-measuring pass — where the sizes do
|
|
721
|
+
; not exist yet — apart from one with no enclosing grid at all. Without it
|
|
722
|
+
; the first kind reported "no enclosing grid", which was simply untrue.
|
|
723
|
+
def subgridPending:boolean false
|
|
724
|
+
def gridTemplateAreas:string ""
|
|
725
|
+
; grid-auto-flow: "row" (default) or "row dense" — dense backfills earlier
|
|
726
|
+
; holes instead of only moving forward.
|
|
727
|
+
def gridAutoFlow:string "row"
|
|
728
|
+
; Full-bleed: this element should run past the trim edge into the bleed
|
|
729
|
+
; area, so the cut falls inside its ink rather than at its edge. Only has
|
|
730
|
+
; an effect when the output was asked for a bleed, and only on the edges
|
|
731
|
+
; the element already touches.
|
|
732
|
+
def fullBleed:boolean false
|
|
733
|
+
def gridArea:string ""
|
|
734
|
+
def gridColumn:string ""
|
|
735
|
+
def gridRow:string ""
|
|
736
|
+
def position:string "relative"
|
|
737
|
+
|
|
738
|
+
; CSS Margin/Padding (shorthand expansion handled at parse time)
|
|
739
|
+
def marginTop:EVGUnit
|
|
740
|
+
def marginRight:EVGUnit
|
|
741
|
+
def marginBottom:EVGUnit
|
|
742
|
+
def marginLeft:EVGUnit
|
|
743
|
+
def paddingTop:EVGUnit
|
|
744
|
+
def paddingRight:EVGUnit
|
|
745
|
+
def paddingBottom:EVGUnit
|
|
746
|
+
def paddingLeft:EVGUnit
|
|
747
|
+
|
|
748
|
+
; CSS Border
|
|
749
|
+
def borderWidth:EVGUnit
|
|
750
|
+
def borderTopWidth:EVGUnit
|
|
751
|
+
def borderRightWidth:EVGUnit
|
|
752
|
+
def borderBottomWidth:EVGUnit
|
|
753
|
+
def borderLeftWidth:EVGUnit
|
|
754
|
+
def borderColor:EVGColor
|
|
755
|
+
; NOTE: border-radius lives on `box`, not here. A duplicate field on the
|
|
756
|
+
; element used to shadow it — setAttribute wrote box.borderRadius while the
|
|
757
|
+
; raster renderer read the element's, so rounded corners silently came out
|
|
758
|
+
; square in PNG and correct in PDF. Do not reintroduce it.
|
|
759
|
+
|
|
760
|
+
; Image properties
|
|
761
|
+
def src:string ""
|
|
762
|
+
def alt:string ""
|
|
763
|
+
; The part of the SOURCE the box shows: "x y w h" as fractions of the
|
|
764
|
+
; bitmap, or the same four as percentages. It is the window, not the
|
|
765
|
+
; destination: "0 0 0.5 1" shows the left half of the picture stretched
|
|
766
|
+
; across the whole box. Numbers outside 0..1 are legal and mean the
|
|
767
|
+
; window reaches past the bitmap — the picture then does not cover the
|
|
768
|
+
; box and the rest of it stays empty, which is what a Figma crop zoomed
|
|
769
|
+
; out past the image does.
|
|
770
|
+
def imageViewBox:string ""
|
|
771
|
+
; Parsed image viewBox values, as fractions of the source
|
|
772
|
+
def imageViewBoxX:double 0.0
|
|
773
|
+
def imageViewBoxY:double 0.0
|
|
774
|
+
def imageViewBoxW:double 1.0
|
|
775
|
+
def imageViewBoxH:double 1.0
|
|
776
|
+
def imageViewBoxSet:boolean false
|
|
777
|
+
; Image offset for positioning within container (can be negative)
|
|
778
|
+
; Works with object-position CSS property
|
|
779
|
+
def imageOffsetX:EVGUnit
|
|
780
|
+
def imageOffsetY:EVGUnit
|
|
781
|
+
; Image sizing: "cover", "contain", "fill", "none"
|
|
782
|
+
; Default is "cover" - preserves aspect ratio, fills container, clips overflow
|
|
783
|
+
def objectFit:string "cover"
|
|
784
|
+
; Original image dimensions (set by image loader)
|
|
785
|
+
def sourceWidth:double 0.0
|
|
786
|
+
def sourceHeight:double 0.0
|
|
787
|
+
|
|
788
|
+
; SVG Path properties
|
|
789
|
+
def svgPath:string ""
|
|
790
|
+
; How the outline is fitted into the box, in SVG's own words. `meet`
|
|
791
|
+
; keeps the shape and letterboxes it; `none` stretches. Everything here
|
|
792
|
+
; is authored at its own size and so fits either way — until a shape is
|
|
793
|
+
; RESIZED, and then the two are the difference between a wider shape and
|
|
794
|
+
; the same shape in a wider frame.
|
|
795
|
+
def preserveAspectRatio:string "xMidYMid meet"
|
|
796
|
+
; --- the flattened outline, kept ------------------------------------------
|
|
797
|
+
;
|
|
798
|
+
; `d` is a string, and the display list wants points: parsing it and
|
|
799
|
+
; flattening its curves is the walk's largest cost on anything vector —
|
|
800
|
+
; a page of text drawn as glyph outlines is thousands of paths, and a
|
|
801
|
+
; Figma board panned at 60 Hz re-parsed every one of them for every
|
|
802
|
+
; frame, 1.2 seconds a frame on a board of 3,565 nodes.
|
|
803
|
+
;
|
|
804
|
+
; The result depends on the path and on the box it is drawn in, and
|
|
805
|
+
; neither changes while the view moves — a transform moves the pixels
|
|
806
|
+
; after the boxes are placed. So it is kept here, and re-flattened when
|
|
807
|
+
; either changes. `EVGDisplayList.copyRings` copies out of this into the
|
|
808
|
+
; command, so what is cached is never what a painter mutates.
|
|
809
|
+
def ringsCache:[PathRing]
|
|
810
|
+
def ringsHave:boolean false
|
|
811
|
+
def ringsPath:string ""
|
|
812
|
+
def ringsX:double 0.0
|
|
813
|
+
def ringsY:double 0.0
|
|
814
|
+
def ringsW:double 0.0
|
|
815
|
+
def ringsH:double 0.0
|
|
816
|
+
def ringsSteps:int 0
|
|
817
|
+
; The viewBox scale the outline was flattened at, which is what a stroke
|
|
818
|
+
; width is in user units of.
|
|
819
|
+
def ringsScale:double 1.0
|
|
820
|
+
def ringsViewBox:string ""
|
|
821
|
+
def ringsFit:string ""
|
|
822
|
+
; An entire imported SVG document, as markup. Where `svgPath` is one `d`
|
|
823
|
+
; string with the element's own fill and stroke, this is a drawing with its
|
|
824
|
+
; own shapes, groups, transforms and paint, which SvgParser resolves into a
|
|
825
|
+
; flat list of drawables (PLAN_VECTOR_IR.md Stage 4). The two are mutually
|
|
826
|
+
; exclusive on one element and an import wins, because a document that set
|
|
827
|
+
; both meant the document.
|
|
828
|
+
;
|
|
829
|
+
; It lives on the path element rather than on a new element type: an
|
|
830
|
+
; imported drawing is laid out, sized and positioned exactly like a `<Path>`
|
|
831
|
+
; is, so a separate type would mean a second copy of that in the layout
|
|
832
|
+
; engine and in all three renderers for no behavioural difference.
|
|
833
|
+
def svgSource:string ""
|
|
834
|
+
; `svgSource`, parsed — by the display list, the first time it draws this
|
|
835
|
+
; element, and kept for every frame after. The markup is the same string
|
|
836
|
+
; on every frame and parsing it again per frame was a sixth of the
|
|
837
|
+
; display-list build on a scrolled page with an icon per row. The key is
|
|
838
|
+
; what the parse depends on: the source and the fill the element gives it
|
|
839
|
+
; (`SvgParser.setInitialFill`), so a changed icon or a recoloured one is
|
|
840
|
+
; parsed again and a cached document can never be the wrong drawing. A
|
|
841
|
+
; rebuilt tree has fresh elements and parses once more; that is a press,
|
|
842
|
+
; not a frame.
|
|
843
|
+
def svgDoc@(optional):SvgDocument
|
|
844
|
+
def svgDocKey:string ""
|
|
845
|
+
def viewBox:string ""
|
|
846
|
+
def fillColor:EVGColor
|
|
847
|
+
def strokeColor:EVGColor
|
|
848
|
+
def strokeWidth:double 0.0
|
|
849
|
+
; "nonzero" (the SVG default) or "evenodd". They differ where a shape
|
|
850
|
+
; overlaps or nests inside itself, which is exactly what decides whether the
|
|
851
|
+
; middle of a ring is a hole or is filled.
|
|
852
|
+
def fillRule:string "nonzero"
|
|
853
|
+
; Dash pattern as SVG writes it: alternating on/off lengths in user
|
|
854
|
+
; units, e.g. "4 2". Empty means a solid stroke. Gridlines and target
|
|
855
|
+
; lines are the reason this exists.
|
|
856
|
+
def strokeDashArray:string ""
|
|
857
|
+
def strokeDashOffset:double 0.0
|
|
858
|
+
|
|
859
|
+
; ---------------------------------------------------------------------
|
|
860
|
+
; Anchors and connectors
|
|
861
|
+
; ---------------------------------------------------------------------
|
|
862
|
+
;
|
|
863
|
+
; `anchor-name` is CSS Anchor Positioning's own property: a name other
|
|
864
|
+
; elements can point at, written `--orders` by convention. It is kept as
|
|
865
|
+
; written, so `--orders` and `orders` are two different names, the way the
|
|
866
|
+
; CSS one is.
|
|
867
|
+
def anchorName:string ""
|
|
868
|
+
|
|
869
|
+
; A `connector` is a path whose `d` the LAYOUT writes. The author says
|
|
870
|
+
; which two boxes it joins and how it should look; where those boxes end
|
|
871
|
+
; up is the layout's business, and it is re-solved on every layout pass,
|
|
872
|
+
; which is the whole point — a hand-placed `left: 171px` arrow is wrong
|
|
873
|
+
; the moment the grid it was measured against reflows.
|
|
874
|
+
;
|
|
875
|
+
; `from`/`to` name an anchor (`--orders`) or an id (`#orders`).
|
|
876
|
+
def connectorFrom:string ""
|
|
877
|
+
def connectorTo:string ""
|
|
878
|
+
; Which edge of the box to leave from and arrive at: left, right, top,
|
|
879
|
+
; bottom, center — or `auto`, which picks the pair facing each other. Auto
|
|
880
|
+
; is what makes the same connector correct in both a two-column desktop
|
|
881
|
+
; layout and a stacked phone one.
|
|
882
|
+
def connectorFromSide:string "auto"
|
|
883
|
+
def connectorToSide:string "auto"
|
|
884
|
+
; straight | orthogonal | bezier
|
|
885
|
+
def connectorRouting:string "straight"
|
|
886
|
+
; A gap left between the box edge and the line's end, along the side's
|
|
887
|
+
; own normal.
|
|
888
|
+
def connectorFromOffset:double 0.0
|
|
889
|
+
def connectorToOffset:double 0.0
|
|
890
|
+
; none | triangle | open. `triangle` is filled, `open` is two strokes;
|
|
891
|
+
; both follow the direction of the segment they sit on.
|
|
892
|
+
def arrowStart:string "none"
|
|
893
|
+
def arrowEnd:string "none"
|
|
894
|
+
def arrowSize:double 10.0
|
|
895
|
+
; The heads, as their own `d`. They are kept OUT of `svgPath` because a
|
|
896
|
+
; fill fills every subpath of the path it is on, closing each one
|
|
897
|
+
; implicitly — an orthogonal shaft and its head on one path would come
|
|
898
|
+
; back as a filled triangle of shaft. So the shaft is stroked and this is
|
|
899
|
+
; filled, which is also what the two of them are.
|
|
900
|
+
def arrowPath:string ""
|
|
901
|
+
; Set by the layout when it could not resolve `from` or `to`; the draw is
|
|
902
|
+
; skipped rather than pointed at the origin.
|
|
903
|
+
def connectorResolved:boolean false
|
|
904
|
+
|
|
905
|
+
; Clip path (for any element)
|
|
906
|
+
def clipPath:string ""
|
|
907
|
+
|
|
908
|
+
; CSS class — selects stylesheet rules (see EVGStyleSheet.rgr). Space
|
|
909
|
+
; separated, like HTML: "photo span-2".
|
|
910
|
+
def className:string ""
|
|
911
|
+
; Theme name for stylesheet resolution, read from the document root.
|
|
912
|
+
def theme:string ""
|
|
913
|
+
; Property names the authoring layer set directly on this element, in
|
|
914
|
+
; kebab-case. A stylesheet must not overwrite these — inline wins the
|
|
915
|
+
; cascade — so front-ends call markInline() for every attribute they apply.
|
|
916
|
+
def inlineProps:[string]
|
|
917
|
+
|
|
918
|
+
; WHICH PROPERTIES CAME FROM THE DOCUMENT'S STYLESHEET.
|
|
919
|
+
;
|
|
920
|
+
; The sheet is applied by writing values onto elements, which makes a
|
|
921
|
+
; cascaded value indistinguishable from an authored one the moment layout
|
|
922
|
+
; has run — and anything that serializes the tree afterwards writes the
|
|
923
|
+
; whole cascade into every node, turning a stylesheet into a one-time
|
|
924
|
+
; macro. It happened: a page with six `.glass` cards came back with the
|
|
925
|
+
; rule copied onto all six, class and all.
|
|
926
|
+
;
|
|
927
|
+
; So the sheet says so. `EVGStyleSheet` marks what it set, `EVGLayout`
|
|
928
|
+
; clears the marks before re-applying, and `EVGTreeJson` leaves a marked
|
|
929
|
+
; property out — the document keeps saying `class-name`, and the rule
|
|
930
|
+
; stays one place. An inline property is never marked, because the sheet
|
|
931
|
+
; does not touch one.
|
|
932
|
+
def cssProps:[string]
|
|
933
|
+
|
|
934
|
+
; PDF/Print settings (for Print element)
|
|
935
|
+
def imageQuality:int 0 ; JPEG quality 1-100 (0 = use default)
|
|
936
|
+
def maxImageSize:int 0 ; Max image dimension in pixels (0 = use default)
|
|
937
|
+
|
|
938
|
+
; --- Transform --------------------------------------------------------------
|
|
939
|
+
;
|
|
940
|
+
; `transform: rotate(15deg) scale(1.05) translate(4px, 0)` is composed at
|
|
941
|
+
; parse time into ONE similarity — an angle, a uniform scale and an offset —
|
|
942
|
+
; because that is the largest family closed under composition that a display
|
|
943
|
+
; list of axis-aligned boxes can still represent exactly. Rotation, uniform
|
|
944
|
+
; scale and translation compose to another rotation, uniform scale and
|
|
945
|
+
; translation, in any order and to any depth. `scale(2, 1)` and `skew()` do
|
|
946
|
+
; not; they are reported rather than approximated.
|
|
947
|
+
;
|
|
948
|
+
; Composing here rather than in the painter is what makes the four numbers
|
|
949
|
+
; INTERPOLABLE: `transition: transform 200ms` is then four ordinary number
|
|
950
|
+
; flights, and a rotation that is also being scaled does the right thing
|
|
951
|
+
; without a matrix decomposition on every frame.
|
|
952
|
+
;
|
|
953
|
+
def rotate:double 0.0
|
|
954
|
+
def scale:double 1.0
|
|
955
|
+
; A REFLECTION, which a rotation and a factor cannot say between them.
|
|
956
|
+
; `scale(1, -1)` and `scale(-1, 1)` have the same magnitude on both axes
|
|
957
|
+
; and differ only in sign, so the similarity survives — it is a turn, a
|
|
958
|
+
; factor and a flip, not a non-uniform scale. Figma writes a mirrored
|
|
959
|
+
; layer exactly this way (its matrix has a negative determinant), and
|
|
960
|
+
; read as a plain rotation a mirrored arrow points the wrong way.
|
|
961
|
+
def flipY:boolean false
|
|
962
|
+
def translateX:double 0.0
|
|
963
|
+
def translateY:double 0.0
|
|
964
|
+
; Kept verbatim so the transform survives a round trip and so a reader can
|
|
965
|
+
; see what was written, not only what it reduced to.
|
|
966
|
+
def transformSpec:string ""
|
|
967
|
+
|
|
968
|
+
; --- transform-origin ----------------------------------------------------
|
|
969
|
+
;
|
|
970
|
+
; What the rotation and the scale happen ABOUT, as a length or a percentage
|
|
971
|
+
; of the border box on each axis. Unset means CSS's default of 50% 50%,
|
|
972
|
+
; which is the element's own centre — measured, not assumed: a 100x40 box
|
|
973
|
+
; turned a quarter turn lands at (30, -30, 40, 100), whose centre is where
|
|
974
|
+
; it started.
|
|
975
|
+
;
|
|
976
|
+
; Resolved LATE, in the display list, where the laid-out box is known.
|
|
977
|
+
; Resolving at parse time would need a size nobody has yet, and resolving
|
|
978
|
+
; when the stylesheet is applied would use the previous frame's box.
|
|
979
|
+
def transformOriginX:EVGUnit (EVGUnit.unset())
|
|
980
|
+
def transformOriginY:EVGUnit (EVGUnit.unset())
|
|
981
|
+
def transformOriginSpec:string ""
|
|
982
|
+
|
|
983
|
+
; Shadow
|
|
984
|
+
def shadowRadius:EVGUnit
|
|
985
|
+
def shadowColor:EVGColor
|
|
986
|
+
def shadowOffsetX:EVGUnit
|
|
987
|
+
def shadowOffsetY:EVGUnit
|
|
988
|
+
|
|
989
|
+
; Gradient (CSS gradient string like "linear-gradient(90deg, #ff0000, #0000ff)")
|
|
990
|
+
def backgroundGradient:string ""
|
|
991
|
+
; Parsed gradient (for renderers to use)
|
|
992
|
+
def gradient:EVGGradient (new EVGGradient())
|
|
993
|
+
|
|
994
|
+
; Calculated values (output from layout)
|
|
995
|
+
def calculatedX:double 0.0
|
|
996
|
+
def calculatedY:double 0.0
|
|
997
|
+
def calculatedWidth:double 0.0
|
|
998
|
+
def calculatedHeight:double 0.0
|
|
999
|
+
def calculatedInnerWidth:double 0.0
|
|
1000
|
+
def calculatedInnerHeight:double 0.0
|
|
1001
|
+
def calculatedFlexWidth:double 0.0
|
|
1002
|
+
; Whether the flex pass DECIDED that width, as opposed to it merely being
|
|
1003
|
+
; zero. Its own flag because the pass used to record the decision by
|
|
1004
|
+
; clearing `width.isSet` — which destroyed the author's declaration, so the
|
|
1005
|
+
; second frame laid out from a tree the stylesheet no longer described.
|
|
1006
|
+
; Layout output is layout state; it does not get to edit the style.
|
|
1007
|
+
def hasFlexWidth:boolean false
|
|
1008
|
+
; Column-axis flex grow (and row alignItems:stretch) target height, resolved
|
|
1009
|
+
; by EVGLayout and consumed by layoutElement when height is not explicitly set.
|
|
1010
|
+
def calculatedFlexHeight:double 0.0
|
|
1011
|
+
; Whether the column's flex-shrink DECIDED that height, over a declared
|
|
1012
|
+
; one — the same flag `hasFlexWidth` is along a row, for the same reason:
|
|
1013
|
+
; the pass used to say so by clearing `height.isSet`.
|
|
1014
|
+
def hasFlexHeight:boolean false
|
|
1015
|
+
; Distance from this element's top border edge down to the baseline of its
|
|
1016
|
+
; FIRST line of text, and the matching descent. Filled in by EVGLayout for
|
|
1017
|
+
; text nodes from their real font metrics, and propagated up from the first
|
|
1018
|
+
; in-flow child for containers, so `align-items: baseline` has something to
|
|
1019
|
+
; line up. 0 means "no text baseline" — CSS then uses the bottom margin edge.
|
|
1020
|
+
def calculatedBaseline:double 0.0
|
|
1021
|
+
def calculatedDescent:double 0.0
|
|
1022
|
+
def hasBaseline:boolean false
|
|
1023
|
+
; True when this element's height was definite (explicit, page-sized, or
|
|
1024
|
+
; already resolved by flex) at the moment its children were laid out, which
|
|
1025
|
+
; is the only time `calculatedInnerHeight` is meaningful as a constraint.
|
|
1026
|
+
; Auto-height elements size to their content, so main-axis grow/shrink must
|
|
1027
|
+
; not run against them — there is nothing to grow into or shrink to.
|
|
1028
|
+
def hasDefiniteHeight:boolean false
|
|
1029
|
+
def calculatedPage:int 0
|
|
1030
|
+
def isAbsolute:boolean false
|
|
1031
|
+
|
|
1032
|
+
; --- overlays -------------------------------------------------------------
|
|
1033
|
+
; An overlay surface stays exactly where it is in the tree: still its
|
|
1034
|
+
; parent's child, still the owner of its subtree, still where an event or an
|
|
1035
|
+
; accessibility walk finds it. Only LAYOUT and PAINT treat it differently —
|
|
1036
|
+
; it takes no space in its parent's flow, and it is drawn after the whole
|
|
1037
|
+
; normal tree, above it.
|
|
1038
|
+
;
|
|
1039
|
+
; That is the difference between this and a portal. Moving the element into
|
|
1040
|
+
; some overlay root would move its ownership with it, and every question
|
|
1041
|
+
; that follows the tree — who handles this click, what does a reader read
|
|
1042
|
+
; next, when is this freed — would then have the wrong answer.
|
|
1043
|
+
def isOverlay:boolean false
|
|
1044
|
+
; What it is positioned against. Left empty by the author: the layout pass
|
|
1045
|
+
; fills it from the surface's own parent, by finding the sibling that
|
|
1046
|
+
; declared itself the anchor.
|
|
1047
|
+
def overlayAnchor@(weak optional):EVGElement
|
|
1048
|
+
def isOverlayAnchor:boolean false
|
|
1049
|
+
; "bottom", "top", "right", "left", or "center" for a modal that has no
|
|
1050
|
+
; anchor. Words rather than an enum because a tree literal assigns these as
|
|
1051
|
+
; ordinary typed fields — `(overlaySide "bottom")` type-checks, and a code
|
|
1052
|
+
; would have been a magic number at every call site.
|
|
1053
|
+
def overlaySide:string "bottom"
|
|
1054
|
+
; "start", "center" or "end", along the side's cross axis.
|
|
1055
|
+
def overlayAlign:string "start"
|
|
1056
|
+
def overlayGap:double 4.0
|
|
1057
|
+
; Where the overlay pass PUT it. Deliberately not written back into `left`
|
|
1058
|
+
; and `top`: those are the author's inputs, and a layout that writes its
|
|
1059
|
+
; output into its own input reads last frame's answer on the next pass.
|
|
1060
|
+
; That is not hypothetical — `resolveUnits` had exactly this shape and
|
|
1061
|
+
; froze every percentage after the first layout.
|
|
1062
|
+
def overlayX:double 0.0
|
|
1063
|
+
def overlayY:double 0.0
|
|
1064
|
+
; Which side the surface ENDED UP on, which is not always the one it asked
|
|
1065
|
+
; for: near an edge it flips. Radix publishes the same thing as `data-side`
|
|
1066
|
+
; and stylesheets use it — an arrow points the other way when a menu opens
|
|
1067
|
+
; upwards — so it is a placement result, not an internal.
|
|
1068
|
+
def overlayPlacedSide:string ""
|
|
1069
|
+
; …and which alignment, for the same reason: a fallback may change both.
|
|
1070
|
+
def overlayPlacedAlign:string ""
|
|
1071
|
+
; anchored | sheet | fullscreen — what the surface ended up being. A menu
|
|
1072
|
+
; that turned into a bottom sheet on a phone is a different widget, and a
|
|
1073
|
+
; stylesheet, a test and an accessibility tree all want to know.
|
|
1074
|
+
def overlayPlacedPresentation:string ""
|
|
1075
|
+
; The placement had to make it smaller than it asked to be, so it clips and
|
|
1076
|
+
; has somewhere to scroll — `scrollHeight` is the extent, as everywhere.
|
|
1077
|
+
def overlayClamped:boolean false
|
|
1078
|
+
|
|
1079
|
+
; --- popover: anchoring by name, fallbacks, presentation ------------------
|
|
1080
|
+
;
|
|
1081
|
+
; CSS Anchor Positioning's own vocabulary, as far as this engine carries
|
|
1082
|
+
; it. The alternative was a second EVG-shaped spelling of the same idea,
|
|
1083
|
+
; which costs everyone who already knows the CSS one.
|
|
1084
|
+
;
|
|
1085
|
+
; `position-anchor` names the box to position against — an `anchor-name`
|
|
1086
|
+
; (`--file-menu`) or an `#id`. It also MARKS the element as a surface:
|
|
1087
|
+
; anything positioned against another box wants to be out of flow and in
|
|
1088
|
+
; the top layer, and making that follow from one property is the whole
|
|
1089
|
+
; ergonomic difference between this and writing four.
|
|
1090
|
+
def positionAnchor:string ""
|
|
1091
|
+
; A comma-separated list of position-areas to try when the declared one
|
|
1092
|
+
; does not fit: "top start, right start, left start". CSS spells it
|
|
1093
|
+
; `position-try-fallbacks`; the shorter `position-try` is accepted too.
|
|
1094
|
+
def positionTryFallbacks:string ""
|
|
1095
|
+
; "" tries them in order and takes the first that fits. "most-space" scores
|
|
1096
|
+
; them all and takes the roomiest, which is CSS's `position-try-order`
|
|
1097
|
+
; heading in the same direction.
|
|
1098
|
+
def positionTryOrder:string ""
|
|
1099
|
+
; anchored | sheet | fullscreen. The one thing here that is not CSS: on a
|
|
1100
|
+
; phone an anchored menu is the wrong widget, not a badly placed one, and
|
|
1101
|
+
; no amount of fallback placement fixes that.
|
|
1102
|
+
def presentation:string ""
|
|
1103
|
+
; Become a sheet at or below this page width. A `@media` block setting
|
|
1104
|
+
; `presentation` is the general route; this is the one line that covers the
|
|
1105
|
+
; case everybody has.
|
|
1106
|
+
def sheetBelow:double 0.0
|
|
1107
|
+
; Clamp the surface to the room it actually has, rather than letting it run
|
|
1108
|
+
; off the page. With `overflow` other than `visible` it then scrolls.
|
|
1109
|
+
def fitViewport:boolean false
|
|
1110
|
+
|
|
1111
|
+
; CSS's `anchor()` in an inset property: `left: anchor(right)` puts this
|
|
1112
|
+
; element's left edge on the anchor's right edge, and
|
|
1113
|
+
; `calc(anchor(right) + 8px)` puts it eight past. Each edge keeps which
|
|
1114
|
+
; edge of the ANCHOR it was given and how far off it sits; empty means the
|
|
1115
|
+
; edge was an ordinary length and `left`/`top` hold it as usual.
|
|
1116
|
+
;
|
|
1117
|
+
; This is the other half of `position-anchor`. `position-area` places a
|
|
1118
|
+
; whole surface on a side of its anchor, which is what a menu wants;
|
|
1119
|
+
; `anchor()` places one EDGE, which is what a badge hanging off a corner
|
|
1120
|
+
; wants, and there is no way to say that with an area.
|
|
1121
|
+
def anchorLeftSide:string ""
|
|
1122
|
+
def anchorLeftOffset:double 0.0
|
|
1123
|
+
def anchorTopSide:string ""
|
|
1124
|
+
def anchorTopOffset:double 0.0
|
|
1125
|
+
def anchorRightSide:string ""
|
|
1126
|
+
def anchorRightOffset:double 0.0
|
|
1127
|
+
def anchorBottomSide:string ""
|
|
1128
|
+
def anchorBottomOffset:double 0.0
|
|
1129
|
+
|
|
1130
|
+
; --- interaction state -------------------------------------------------------
|
|
1131
|
+
; What is happening to this element right now, so a STYLESHEET can say what
|
|
1132
|
+
; that should look like. `.btn:hover` is a rule that asks for the first of
|
|
1133
|
+
; these; without them the only way to change a look on hover is for the
|
|
1134
|
+
; controller to write a second class, which makes every controller know
|
|
1135
|
+
; about presentation.
|
|
1136
|
+
;
|
|
1137
|
+
; Set by whoever routes input — the host that hit-tests a pointer already
|
|
1138
|
+
; knows which element is under it, and the one that owns focus already
|
|
1139
|
+
; knows which is focused.
|
|
1140
|
+
def isHovered:boolean false
|
|
1141
|
+
def isFocused:boolean false
|
|
1142
|
+
; The pointer is down on it: CSS's `:active`.
|
|
1143
|
+
def isPressed:boolean false
|
|
1144
|
+
|
|
1145
|
+
; --- transitions --------------------------------------------------------------
|
|
1146
|
+
; `transition: background-color 150ms` as the stylesheet declared it, and
|
|
1147
|
+
; the flights currently in the air. Both live on the element because a
|
|
1148
|
+
; transition is a fact about THIS box moving, not about the rule that
|
|
1149
|
+
; started it — the rule may not even match any more by the time it lands.
|
|
1150
|
+
def transitionSpec:string ""
|
|
1151
|
+
def transitions:[EVGFlight]
|
|
1152
|
+
|
|
1153
|
+
; --- accessibility ---------------------------------------------------------
|
|
1154
|
+
; What this element MEANS, next to what it looks like. A canvas contributes
|
|
1155
|
+
; one empty graphic to the browser's accessibility tree no matter what was
|
|
1156
|
+
; drawn into it, so an EVG app has to publish the meaning separately —
|
|
1157
|
+
; and the only place that will not drift out of step with the picture is
|
|
1158
|
+
; the declaration that built the picture.
|
|
1159
|
+
;
|
|
1160
|
+
; The role is the ARIA spelling ("menuitem", "toolbar", "separator"),
|
|
1161
|
+
; because that is the vocabulary the platform mirrors speak and inventing a
|
|
1162
|
+
; second one buys nothing. Empty means "not in the accessibility tree",
|
|
1163
|
+
; which is the right default: most elements in a UI are boxes.
|
|
1164
|
+
def role:string ""
|
|
1165
|
+
; The accessible name, when it is not simply what the element says. Left
|
|
1166
|
+
; empty, the name is taken from the text of the descendants that have no
|
|
1167
|
+
; role of their own — the same rule the HTML name calculation uses, and the
|
|
1168
|
+
; reason a row of Label + Shortcut announces as one thing.
|
|
1169
|
+
def a11yLabel:string ""
|
|
1170
|
+
; What a text field CONTAINS, as opposed to what it is called.
|
|
1171
|
+
;
|
|
1172
|
+
; A reader that is told "Full name, text box" and not the name in it is
|
|
1173
|
+
; being told half of what is on screen — and this was missing entirely:
|
|
1174
|
+
; the tree carried the label and the role, the DOM mirror asked for
|
|
1175
|
+
; `node.value`, and nothing on this side ever set it. The conformance
|
|
1176
|
+
; trace has had the value all along through `UiRow.text`, which is the
|
|
1177
|
+
; CONTROLLER's path; a demo that draws its own tree went without.
|
|
1178
|
+
;
|
|
1179
|
+
; A password field must leave this EMPTY. `value` is the text and
|
|
1180
|
+
; `glyphText()` is the bullets, and neither belongs to a reader: a browser
|
|
1181
|
+
; exposes no value for `type=password` either.
|
|
1182
|
+
def a11yValue:string ""
|
|
1183
|
+
; `aria-required`, `aria-invalid` and `aria-readonly`, as the DOM spells
|
|
1184
|
+
; them: "true", "false", or "" for a control that says nothing.
|
|
1185
|
+
;
|
|
1186
|
+
; STRINGS AND NOT BOOLEANS, and that is the whole point of them. Absent
|
|
1187
|
+
; and "false" are different claims — a field that says
|
|
1188
|
+
; `aria-required="false"` has been asked and answered, and one that says
|
|
1189
|
+
; nothing has not. The controller path has spelled them this way since the
|
|
1190
|
+
; `input_required` spec recorded the rule against a real <input>
|
|
1191
|
+
; ("aria-required is published only when it was asked for"); the element
|
|
1192
|
+
; path could not say either at all, so a demo-built form drew a red ring
|
|
1193
|
+
; and a message for a person and told a reader nothing.
|
|
1194
|
+
def a11yRequired:string ""
|
|
1195
|
+
def a11yInvalid:string ""
|
|
1196
|
+
def a11yReadOnly:string ""
|
|
1197
|
+
; What a reader says INSTEAD of the role — "sortable" on a draggable row.
|
|
1198
|
+
def a11yRoleDescription:string ""
|
|
1199
|
+
; `aria-describedby`, ALREADY RESOLVED to the sentence it points at — a
|
|
1200
|
+
; form field's hint, or its error message once there is one.
|
|
1201
|
+
;
|
|
1202
|
+
; The text and not an id, for the reason the conformance trace carries the
|
|
1203
|
+
; text: an id is a fact about a document and the sentence is what a reader
|
|
1204
|
+
; hears, and this tree has no document to hold ids in. A control with no
|
|
1205
|
+
; description says nothing, which is what an absent attribute means.
|
|
1206
|
+
def a11yDescription:string ""
|
|
1207
|
+
; `aria-describedby` as a REFERENCE: the id of the element in this tree
|
|
1208
|
+
; that carries the sentence — a field's visible error message. A host that
|
|
1209
|
+
; has a document (the DOM mirror) points at that element, so a reader hears
|
|
1210
|
+
; the message it can also walk to; `a11yDescription` keeps the resolved
|
|
1211
|
+
; text for hosts that have no ids.
|
|
1212
|
+
def a11yDescribedBy:string ""
|
|
1213
|
+
; `aria-controls` and `aria-labelledby`, as REFERENCES to other elements'
|
|
1214
|
+
; ids in this tree: a tab names the panel it shows, and the panel is named
|
|
1215
|
+
; by its tab. Hosts with a document (the DOM mirror) resolve them.
|
|
1216
|
+
def a11yControls:string ""
|
|
1217
|
+
def a11yLabelledBy:string ""
|
|
1218
|
+
; `aria-haspopup`: "menu", "listbox", "dialog". The one thing a reader
|
|
1219
|
+
; cannot see about a trigger — that pressing it opens something — and a
|
|
1220
|
+
; select's closed trigger is a box with a word in it without it.
|
|
1221
|
+
def a11yHasPopup:string ""
|
|
1222
|
+
; `aria-rowcount` and `aria-rowindex`. A virtualised table has a dozen rows
|
|
1223
|
+
; in the tree and ten thousand in the data, and these two are the only
|
|
1224
|
+
; thing that says so — 0 for a table that publishes neither, which is what
|
|
1225
|
+
; a non-virtualised one does.
|
|
1226
|
+
def a11yRowCount:int 0
|
|
1227
|
+
def a11yRowIndex:int 0
|
|
1228
|
+
; `aria-hidden`: this element and everything under it is DECORATION and
|
|
1229
|
+
; contributes nothing to the accessible tree — no node, and no words to an
|
|
1230
|
+
; ancestor's name.
|
|
1231
|
+
;
|
|
1232
|
+
; The chevron on a submenu row is the case that asked for it. It is a ">"
|
|
1233
|
+
; drawn beside the label, and without this the row announces as "Share
|
|
1234
|
+
; greater-than" — the glyph read aloud as if it were part of the name, when
|
|
1235
|
+
; `aria-haspopup` has already said the same thing properly.
|
|
1236
|
+
def a11yHidden:boolean false
|
|
1237
|
+
; `aria-modal`: while this surface is up, the rest of the page is not
|
|
1238
|
+
; there — a reader does not walk it and the host hides it. A menu that
|
|
1239
|
+
; takes the wheel and the arrow keys says so here, so the tree the
|
|
1240
|
+
; hosts read agrees with what the app does with the keys.
|
|
1241
|
+
def a11yModal:boolean false
|
|
1242
|
+
; `aria-sort` on a column header: 0 none, 1 ascending, 2 descending.
|
|
1243
|
+
def a11ySorted:int 0
|
|
1244
|
+
; `aria-orientation`. Its own field for the reason `aria-sort` is one: on a
|
|
1245
|
+
; SPLITTER it decides what a key means — ArrowLeft moves a horizontal one
|
|
1246
|
+
; and does nothing at all to a vertical one — and every other observation
|
|
1247
|
+
; about the two is identical. A toolbar, a slider, a menubar and a
|
|
1248
|
+
; separator all publish it too.
|
|
1249
|
+
def a11yOrientation:string ""
|
|
1250
|
+
; `aria-current`. "page" on the crumb you are on.
|
|
1251
|
+
def a11yCurrent:string ""
|
|
1252
|
+
; `aria-valuenow` / `valuemin` / `valuemax`. Absent until a page built out
|
|
1253
|
+
; of ELEMENTS needed to publish a splitter, and the gap was invisible
|
|
1254
|
+
; because a page built out of a CONTROLLER publishes them through `UiRow`
|
|
1255
|
+
; instead — two paths to the same tree and only one of them complete. Axe
|
|
1256
|
+
; found it: a focusable `separator` without a value is not a splitter, it
|
|
1257
|
+
; is a decoration that steals a tab stop.
|
|
1258
|
+
;
|
|
1259
|
+
; `hasValue` and `hasRange` are separate from the numbers for the reason
|
|
1260
|
+
; `EVGA11yNode` keeps them separate: a progress bar with a range and NO
|
|
1261
|
+
; position is how every platform spells "busy, length unknown", and 0 is a
|
|
1262
|
+
; different claim.
|
|
1263
|
+
def a11yHasValue:boolean false
|
|
1264
|
+
def a11yValueNow:int 0
|
|
1265
|
+
def a11yHasRange:boolean false
|
|
1266
|
+
def a11yValueMin:int 0
|
|
1267
|
+
def a11yValueMax:int 0
|
|
1268
|
+
; Tri-states, EVGA11yTri: 0 not applicable, 1 no, 2 yes, 3 mixed. Not
|
|
1269
|
+
; booleans — `aria-checked="false"` on something that is not checkable makes
|
|
1270
|
+
; a reader announce a checkbox that does not exist.
|
|
1271
|
+
def a11yChecked:int 0
|
|
1272
|
+
; `aria-pressed`, tri-state. A carried sortable row and a show/hide toggle
|
|
1273
|
+
; are both buttons that are ON, and neither is CHECKED — a reader says
|
|
1274
|
+
; "pressed" for one and "checked" for the other, and they are not
|
|
1275
|
+
; interchangeable.
|
|
1276
|
+
def a11yPressed:int 0
|
|
1277
|
+
def a11yExpanded:int 0
|
|
1278
|
+
def a11ySelected:int 0
|
|
1279
|
+
def a11yDisabled:boolean false
|
|
1280
|
+
def a11yFocusable:boolean false
|
|
1281
|
+
; Where this row sits in a set the tree does not fully contain. A submenu
|
|
1282
|
+
; that is closed is not in the tree, so its parent still has to say how many
|
|
1283
|
+
; rows there are.
|
|
1284
|
+
def a11yPosInSet:int 0
|
|
1285
|
+
def a11ySetSize:int 0
|
|
1286
|
+
; `aria-level`, for a treeitem in a flat tree. 0 means unset.
|
|
1287
|
+
def a11yLevel:int 0
|
|
1288
|
+
|
|
1289
|
+
; --- what the stylesheet last wrote here ---------------------------------
|
|
1290
|
+
;
|
|
1291
|
+
; The cascade's answer for an element is a function of four things, and
|
|
1292
|
+
; these remember which four produced what the element is holding now. When
|
|
1293
|
+
; they all still agree on the next pass, the element already contains
|
|
1294
|
+
; exactly what the sheet would write, and writing it again is pure cost —
|
|
1295
|
+
; on a 1600-row table a hover changes ONE row and the other 22,402 elements
|
|
1296
|
+
; were being re-declared regardless.
|
|
1297
|
+
;
|
|
1298
|
+
; `styleGen` is the sheet's own counter, so re-parsing or resizing
|
|
1299
|
+
; invalidates every element at once without touching any of them.
|
|
1300
|
+
; `EVGTransition.writeBack` sets it to -1, because after a flight has
|
|
1301
|
+
; written a showing value the element no longer holds what the sheet
|
|
1302
|
+
; declared — and the sheet has to re-declare it or the flight stops dead.
|
|
1303
|
+
def styleClass:string ""
|
|
1304
|
+
def styleTheme:string ""
|
|
1305
|
+
def styleBits:int 0
|
|
1306
|
+
def styleGen:int 0
|
|
1307
|
+
; Which plan produced it, so two passes can be compared for whether
|
|
1308
|
+
; anything LAYOUT-relevant actually differs. -1 is "no plan yet".
|
|
1309
|
+
def styleSlot:int (0 - 1)
|
|
1310
|
+
; How many children it had when it was styled.
|
|
1311
|
+
;
|
|
1312
|
+
; The stylesheet cannot see a child being added or removed — that is not a
|
|
1313
|
+
; property and no rule mentions it — so without this an element whose class
|
|
1314
|
+
; never changed would be skipped, the pass would report itself clean, layout
|
|
1315
|
+
; would be skipped, and the remaining children would be drawn at the
|
|
1316
|
+
; positions they held before the removal. Comparing one integer removes a
|
|
1317
|
+
; whole class of host mistake, and a removal is the case that has no other
|
|
1318
|
+
; symptom: an ADDED child is caught anyway, because a new element has never
|
|
1319
|
+
; been styled.
|
|
1320
|
+
def styleKids:int (0 - 1)
|
|
1321
|
+
|
|
1322
|
+
; Where this element sits in the last inspect walk, or -1 when the
|
|
1323
|
+
; inspector has never run. `EVGInspect` stamps it, and two things read it:
|
|
1324
|
+
; the display list, to say which element emitted a command, and the hit
|
|
1325
|
+
; test, to turn a point into a node path without comparing object
|
|
1326
|
+
; identity — which Ranger has no operator for. One int, untouched and
|
|
1327
|
+
; meaningless while nothing inspects anything.
|
|
1328
|
+
def inspectSlot:int (0 - 1)
|
|
1329
|
+
|
|
1330
|
+
def isLayoutComplete:boolean false
|
|
1331
|
+
def unitsResolved:boolean false
|
|
1332
|
+
|
|
1333
|
+
; For statement evaluation: indicates if a return was executed
|
|
1334
|
+
def hasReturn:boolean false
|
|
1335
|
+
def hasBreak:boolean false
|
|
1336
|
+
def hasContinue:boolean false
|
|
1337
|
+
|
|
1338
|
+
; Inherited font size in pixels
|
|
1339
|
+
def inheritedFontSize:double 14.0
|
|
1340
|
+
|
|
1341
|
+
Constructor () {
|
|
1342
|
+
tagName = "div"
|
|
1343
|
+
elementType = 0
|
|
1344
|
+
|
|
1345
|
+
width = (EVGUnit.unset())
|
|
1346
|
+
height = (EVGUnit.unset())
|
|
1347
|
+
minWidth = (EVGUnit.unset())
|
|
1348
|
+
minHeight = (EVGUnit.unset())
|
|
1349
|
+
maxWidth = (EVGUnit.unset())
|
|
1350
|
+
maxHeight = (EVGUnit.unset())
|
|
1351
|
+
|
|
1352
|
+
left = (EVGUnit.unset())
|
|
1353
|
+
top = (EVGUnit.unset())
|
|
1354
|
+
right = (EVGUnit.unset())
|
|
1355
|
+
bottom = (EVGUnit.unset())
|
|
1356
|
+
x = (EVGUnit.unset())
|
|
1357
|
+
y = (EVGUnit.unset())
|
|
1358
|
+
gap = (EVGUnit.unset())
|
|
1359
|
+
flexBasis = (EVGUnit.unset())
|
|
1360
|
+
rowGap = (EVGUnit.unset())
|
|
1361
|
+
columnGap = (EVGUnit.unset())
|
|
1362
|
+
|
|
1363
|
+
def newBox (new EVGBox())
|
|
1364
|
+
box = newBox
|
|
1365
|
+
|
|
1366
|
+
backgroundColor = (EVGColor.noColor())
|
|
1367
|
+
color = (EVGColor.black())
|
|
1368
|
+
emojiColor = (EVGColor.noColor())
|
|
1369
|
+
|
|
1370
|
+
fontSize = (EVGUnit.unset())
|
|
1371
|
+
lineHeightUnit = (EVGUnit.unset())
|
|
1372
|
+
|
|
1373
|
+
shadowRadius = (EVGUnit.unset())
|
|
1374
|
+
shadowColor = (EVGColor.noColor())
|
|
1375
|
+
shadowOffsetX = (EVGUnit.unset())
|
|
1376
|
+
shadowOffsetY = (EVGUnit.unset())
|
|
1377
|
+
|
|
1378
|
+
imageOffsetX = (EVGUnit.unset())
|
|
1379
|
+
imageOffsetY = (EVGUnit.unset())
|
|
1380
|
+
|
|
1381
|
+
fillColor = (EVGColor.noColor())
|
|
1382
|
+
scrollbarThumb = (EVGColor.noColor())
|
|
1383
|
+
scrollbarTrack = (EVGColor.noColor())
|
|
1384
|
+
strokeColor = (EVGColor.noColor())
|
|
1385
|
+
|
|
1386
|
+
; The per-side box fields the stylesheet writes when it has a value.
|
|
1387
|
+
; They used to be left undefined until then, which gave an element a
|
|
1388
|
+
; different hidden class depending on which of them had ever been
|
|
1389
|
+
; set, and made every layout read of them polymorphic. Every field
|
|
1390
|
+
; is now present from birth, at no cost: the defaults are shared.
|
|
1391
|
+
marginTop = (EVGUnit.unset())
|
|
1392
|
+
marginRight = (EVGUnit.unset())
|
|
1393
|
+
marginBottom = (EVGUnit.unset())
|
|
1394
|
+
marginLeft = (EVGUnit.unset())
|
|
1395
|
+
paddingTop = (EVGUnit.unset())
|
|
1396
|
+
paddingRight = (EVGUnit.unset())
|
|
1397
|
+
paddingBottom = (EVGUnit.unset())
|
|
1398
|
+
paddingLeft = (EVGUnit.unset())
|
|
1399
|
+
borderWidth = (EVGUnit.unset())
|
|
1400
|
+
borderTopWidth = (EVGUnit.unset())
|
|
1401
|
+
borderRightWidth = (EVGUnit.unset())
|
|
1402
|
+
borderBottomWidth = (EVGUnit.unset())
|
|
1403
|
+
borderLeftWidth = (EVGUnit.unset())
|
|
1404
|
+
borderColor = (EVGColor.noColor())
|
|
1405
|
+
gradientFrom = (EVGColor.noColor())
|
|
1406
|
+
gradientTo = (EVGColor.noColor())
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
sfn createDiv:EVGElement () {
|
|
1410
|
+
def el (new EVGElement())
|
|
1411
|
+
el.tagName = "div"
|
|
1412
|
+
el.elementType = 0
|
|
1413
|
+
return el
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
sfn createSpan:EVGElement () {
|
|
1417
|
+
def el (new EVGElement())
|
|
1418
|
+
el.tagName = "span"
|
|
1419
|
+
el.elementType = 1
|
|
1420
|
+
return el
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
sfn createImg:EVGElement () {
|
|
1424
|
+
def el (new EVGElement())
|
|
1425
|
+
el.tagName = "img"
|
|
1426
|
+
el.elementType = 2
|
|
1427
|
+
return el
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
sfn createPath:EVGElement () {
|
|
1431
|
+
def el (new EVGElement())
|
|
1432
|
+
el.tagName = "path"
|
|
1433
|
+
el.elementType = 3
|
|
1434
|
+
return el
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
fn addChild:void (child:EVGElement) {
|
|
1438
|
+
; NOTE: no `child.parent = this` back-reference — it was write-only (never
|
|
1439
|
+
; read) and, in the C++ backend, assigning `this` to a shared_ptr field
|
|
1440
|
+
; forces enable_shared_from_this on EVGElement, which broke the native
|
|
1441
|
+
; build. Children are tracked by `children[]` (forward edges) only.
|
|
1442
|
+
push children child
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
; A subtree whose layout is being kept — see `keepLayout` — is left as
|
|
1446
|
+
; it is: the layout decides, when the flow reaches it, whether it can
|
|
1447
|
+
; be answered from what it holds, and resets it itself when not.
|
|
1448
|
+
fn resetLayoutState:void () {
|
|
1449
|
+
if (keepLayout && (hasLayout && layoutClean)) {
|
|
1450
|
+
; Except the height the parent hands down, which the parent works
|
|
1451
|
+
; out again this pass if it still holds; left in place, one that no
|
|
1452
|
+
; longer does would be read back as this box's input — see
|
|
1453
|
+
; `resetLayoutNow` — and would match `lastFlexH`.
|
|
1454
|
+
calculatedFlexHeight = 0.0
|
|
1455
|
+
hasFlexHeight = false
|
|
1456
|
+
return
|
|
1457
|
+
}
|
|
1458
|
+
this.resetLayoutNow()
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
; Nothing under here has been laid out, as far as `keepLayout` is
|
|
1462
|
+
; concerned. `EVGLayout.layout` says so when the page size changed: a
|
|
1463
|
+
; `vw` or `vh` anywhere in a kept subtree means something else now,
|
|
1464
|
+
; though nothing the flow hands the subtree's root need have changed.
|
|
1465
|
+
fn forgetLayout:void () {
|
|
1466
|
+
hasLayout = false
|
|
1467
|
+
def i:int 0
|
|
1468
|
+
while (i < (array_length children)) {
|
|
1469
|
+
def child:EVGElement (itemAt children i)
|
|
1470
|
+
child.forgetLayout()
|
|
1471
|
+
i = (i + 1)
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
fn resetLayoutNow:void () {
|
|
1476
|
+
unitsResolved = false
|
|
1477
|
+
shiftDx = 0.0
|
|
1478
|
+
shiftDy = 0.0
|
|
1479
|
+
hasIntrinsicMin = false
|
|
1480
|
+
hasIntrinsicMax = false
|
|
1481
|
+
layoutSkipped = false
|
|
1482
|
+
boundsFresh = false
|
|
1483
|
+
overlayScanned = false
|
|
1484
|
+
calculatedX = 0.0
|
|
1485
|
+
calculatedY = 0.0
|
|
1486
|
+
calculatedWidth = 0.0
|
|
1487
|
+
calculatedHeight = 0.0
|
|
1488
|
+
; Cleared with the rest of the layout state, so a pass that no longer
|
|
1489
|
+
; shrinks a child stops claiming it did.
|
|
1490
|
+
calculatedFlexWidth = 0.0
|
|
1491
|
+
hasFlexWidth = false
|
|
1492
|
+
hasDefiniteHeight = false
|
|
1493
|
+
; And the rest of what the last pass computed, which the layout reads
|
|
1494
|
+
; back as INPUT when it is still there: a flex-grown or stretched
|
|
1495
|
+
; height is adopted by `layoutElement` whenever `height` is not set,
|
|
1496
|
+
; and an auto-height row stretches its items to its inner height
|
|
1497
|
+
; before it has worked out a new one. Laid out again at another page
|
|
1498
|
+
; size, a card kept the 248px its column grew it to after its own
|
|
1499
|
+
; height went `auto`, and a row stretched its columns to the height
|
|
1500
|
+
; their text had on one line. The parent writes these again this pass
|
|
1501
|
+
; if they still hold.
|
|
1502
|
+
calculatedFlexHeight = 0.0
|
|
1503
|
+
hasFlexHeight = false
|
|
1504
|
+
calculatedInnerWidth = 0.0
|
|
1505
|
+
calculatedInnerHeight = 0.0
|
|
1506
|
+
calculatedBaseline = 0.0
|
|
1507
|
+
calculatedDescent = 0.0
|
|
1508
|
+
hasBaseline = false
|
|
1509
|
+
; Recursively reset children
|
|
1510
|
+
def i:int 0
|
|
1511
|
+
while (i < (array_length children)) {
|
|
1512
|
+
def child:EVGElement (itemAt children i)
|
|
1513
|
+
child.resetLayoutState()
|
|
1514
|
+
i = i + 1
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
fn getChildCount:int () {
|
|
1519
|
+
return (array_length children)
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
; Move this box and its paint bounds, and nothing beneath it.
|
|
1523
|
+
fn moveSelf:void (dx:double dy:double) {
|
|
1524
|
+
calculatedX = (calculatedX + dx)
|
|
1525
|
+
calculatedY = (calculatedY + dy)
|
|
1526
|
+
paintLeft = (paintLeft + dx)
|
|
1527
|
+
paintRight = (paintRight + dx)
|
|
1528
|
+
paintTop = (paintTop + dy)
|
|
1529
|
+
paintBottom = (paintBottom + dy)
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
; Move this box and every box beneath it. A descendant that itself owes
|
|
1533
|
+
; a shift keeps owing it: its own shift is relative to its own box, which
|
|
1534
|
+
; has just moved with the rest.
|
|
1535
|
+
fn moveSubtree:void (dx:double dy:double) {
|
|
1536
|
+
this.moveSelf(dx dy)
|
|
1537
|
+
def i:int 0
|
|
1538
|
+
def n:int (array_length children)
|
|
1539
|
+
while (i < n) {
|
|
1540
|
+
def kid:EVGElement (itemAt children i)
|
|
1541
|
+
kid.moveSubtree(dx dy)
|
|
1542
|
+
i = (i + 1)
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
; Pay the scroll this subtree is owed — see `shiftDx`. Cheap to ask on
|
|
1547
|
+
; a node that owes nothing, which is every node but the far rows.
|
|
1548
|
+
fn settleShift:void () {
|
|
1549
|
+
if ((shiftDx == 0.0) && (shiftDy == 0.0)) {
|
|
1550
|
+
return
|
|
1551
|
+
}
|
|
1552
|
+
def dx:double shiftDx
|
|
1553
|
+
def dy:double shiftDy
|
|
1554
|
+
shiftDx = 0.0
|
|
1555
|
+
shiftDy = 0.0
|
|
1556
|
+
def i:int 0
|
|
1557
|
+
def n:int (array_length children)
|
|
1558
|
+
while (i < n) {
|
|
1559
|
+
def kid:EVGElement (itemAt children i)
|
|
1560
|
+
kid.moveSubtree(dx dy)
|
|
1561
|
+
i = (i + 1)
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
; Every box in the tree where it should be, for a reader that is about
|
|
1566
|
+
; to look at all of them.
|
|
1567
|
+
fn settleAll:void () {
|
|
1568
|
+
this.settleShift()
|
|
1569
|
+
def i:int 0
|
|
1570
|
+
def n:int (array_length children)
|
|
1571
|
+
while (i < n) {
|
|
1572
|
+
def kid:EVGElement (itemAt children i)
|
|
1573
|
+
kid.settleAll()
|
|
1574
|
+
i = (i + 1)
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
; --- scrolling ------------------------------------------------------------
|
|
1579
|
+
;
|
|
1580
|
+
; A box that does not let its content out. `visible` is the only CSS value
|
|
1581
|
+
; that does not clip, and every other one — `hidden` included — makes a
|
|
1582
|
+
; SCROLL CONTAINER. That `hidden` scrolls is the part worth stating: it is
|
|
1583
|
+
; a container with no scrollbar, not a box that throws the overflow away,
|
|
1584
|
+
; and a program can scroll it even though a pointer cannot.
|
|
1585
|
+
fn clipsContent:boolean () {
|
|
1586
|
+
return (overflow != "visible")
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
; The visible box, the DOM's `clientHeight`/`clientWidth`: everything
|
|
1590
|
+
; inside the border, padding included.
|
|
1591
|
+
fn clientHeight:double () {
|
|
1592
|
+
return (calculatedHeight - (box.borderWidthPx * 2.0))
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
fn clientWidth:double () {
|
|
1596
|
+
return (calculatedWidth - (box.borderWidthPx * 2.0))
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
; How far there is to go. Zero when the content fits, which is also the
|
|
1600
|
+
; answer for every element that is not a scroll container at all, because
|
|
1601
|
+
; the layout only ever writes an extent for one that is.
|
|
1602
|
+
fn maxScrollTop:double () {
|
|
1603
|
+
def m:double (scrollHeight - (this.clientHeight()))
|
|
1604
|
+
if (m < 0.0) {
|
|
1605
|
+
return 0.0
|
|
1606
|
+
}
|
|
1607
|
+
return m
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
fn maxScrollLeft:double () {
|
|
1611
|
+
def m:double (scrollWidth - (this.clientWidth()))
|
|
1612
|
+
if (m < 0.0) {
|
|
1613
|
+
return 0.0
|
|
1614
|
+
}
|
|
1615
|
+
return m
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
fn getChild:EVGElement (index:int) {
|
|
1619
|
+
return (itemAt children index)
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
fn hasParent:boolean () {
|
|
1623
|
+
; Check if parent is set - use simple boolean check
|
|
1624
|
+
if parent {
|
|
1625
|
+
return true
|
|
1626
|
+
}
|
|
1627
|
+
return false
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
fn isContainer:boolean () {
|
|
1631
|
+
return (elementType == 0)
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
fn isText:boolean () {
|
|
1635
|
+
return (elementType == 1)
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
fn isImage:boolean () {
|
|
1639
|
+
return (elementType == 2)
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
fn isPath:boolean () {
|
|
1643
|
+
return (elementType == 3)
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
; A tag's props arrive as strings from a stylesheet and as whatever the
|
|
1647
|
+
; author wrote from a tree literal, so "true", "1" and a bare word all mean
|
|
1648
|
+
; the same thing here.
|
|
1649
|
+
sfn truthy:boolean (value:string) {
|
|
1650
|
+
def v:string (trim value)
|
|
1651
|
+
if (v == "true") {
|
|
1652
|
+
return true
|
|
1653
|
+
}
|
|
1654
|
+
if (v == "1") {
|
|
1655
|
+
return true
|
|
1656
|
+
}
|
|
1657
|
+
if (v == "yes") {
|
|
1658
|
+
return true
|
|
1659
|
+
}
|
|
1660
|
+
return false
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
; "true"/"false"/"mixed" as EVGA11yTri, which is 0 for anything else —
|
|
1664
|
+
; including the empty string, i.e. "this element has no such state".
|
|
1665
|
+
sfn triState:int (value:string) {
|
|
1666
|
+
def v:string (trim value)
|
|
1667
|
+
if (v == "true") {
|
|
1668
|
+
return 2
|
|
1669
|
+
}
|
|
1670
|
+
if (v == "false") {
|
|
1671
|
+
return 1
|
|
1672
|
+
}
|
|
1673
|
+
if (v == "mixed") {
|
|
1674
|
+
return 3
|
|
1675
|
+
}
|
|
1676
|
+
return 0
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
; `display: none` — the element is not there: no box, no paint, no hit,
|
|
1680
|
+
; not in the accessible tree. It is the way a stylesheet folds a bar or
|
|
1681
|
+
; a rail away at a breakpoint, and what `@media` is for.
|
|
1682
|
+
fn isHidden:boolean () {
|
|
1683
|
+
return (display == "none")
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
; THE LINE BOX, in pixels, and the only thing that should read
|
|
1687
|
+
; `lineHeight` or `lineHeightUnit`. CSS gives three ways to write it and
|
|
1688
|
+
; they are three different computations:
|
|
1689
|
+
;
|
|
1690
|
+
; `normal` the FACE's own line box — `normalPx`, which the caller got
|
|
1691
|
+
; from the text engine because only it knows about faces
|
|
1692
|
+
; a number that many times this element's font size
|
|
1693
|
+
; a length itself, with `%` and `em` against this element's OWN font
|
|
1694
|
+
; size (not its parent's, which is what a percentage means
|
|
1695
|
+
; everywhere else and does not mean here) and `rem` against
|
|
1696
|
+
; the root's
|
|
1697
|
+
;
|
|
1698
|
+
; The layout reserves the height by this and the display list steps the
|
|
1699
|
+
; lines by it, so there is one answer and both sides read it.
|
|
1700
|
+
fn lineBoxFor:double (fontSize:double normalPx:double) {
|
|
1701
|
+
if lineHeightUnit.isSet {
|
|
1702
|
+
def u:EVGUnit lineHeightUnit
|
|
1703
|
+
u.setContext(rootFontSize viewportW viewportH)
|
|
1704
|
+
; The parent size a `line-height` percentage resolves against IS
|
|
1705
|
+
; the font size. CSS 2.1 10.8.1, and it is the one percentage in
|
|
1706
|
+
; the box model that does not look at the containing block.
|
|
1707
|
+
u.resolve(fontSize fontSize)
|
|
1708
|
+
if (u.pixels > 0.0) {
|
|
1709
|
+
return u.pixels
|
|
1710
|
+
}
|
|
1711
|
+
return 0.0
|
|
1712
|
+
}
|
|
1713
|
+
if (lineHeight > 0.0) {
|
|
1714
|
+
return (fontSize * lineHeight)
|
|
1715
|
+
}
|
|
1716
|
+
return normalPx
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
; The width a line is broken into, for this element's own text: the box's
|
|
1720
|
+
; content width, or ZERO — which `EVGTextEngine` reads as "do not wrap" —
|
|
1721
|
+
; when `white-space` says the text is one line. One answer, so the layout
|
|
1722
|
+
; that reserves the height and the display list that draws the lines
|
|
1723
|
+
; cannot disagree about how many there are.
|
|
1724
|
+
fn wrapWidth:double (contentWidth:double) {
|
|
1725
|
+
if (whiteSpace == "nowrap") {
|
|
1726
|
+
return 0.0
|
|
1727
|
+
}
|
|
1728
|
+
if (whiteSpace == "pre") {
|
|
1729
|
+
return 0.0
|
|
1730
|
+
}
|
|
1731
|
+
return contentWidth
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
; Is this element's drawn output somewhere its layout box does not say?
|
|
1735
|
+
; A transform moves the pixels after the boxes are placed, so the answer
|
|
1736
|
+
; decides whether the subtree can be culled at all — see `paintUnbounded`.
|
|
1737
|
+
fn hasTransform:boolean () {
|
|
1738
|
+
if (rotate != 0.0) {
|
|
1739
|
+
return true
|
|
1740
|
+
}
|
|
1741
|
+
if flipY {
|
|
1742
|
+
return true
|
|
1743
|
+
}
|
|
1744
|
+
if ((fabs (scale - 1.0)) > 0.000001) {
|
|
1745
|
+
return true
|
|
1746
|
+
}
|
|
1747
|
+
if (translateX != 0.0) {
|
|
1748
|
+
return true
|
|
1749
|
+
}
|
|
1750
|
+
if (translateY != 0.0) {
|
|
1751
|
+
return true
|
|
1752
|
+
}
|
|
1753
|
+
return false
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
; Positioned against the VIEWPORT and not moved by a scroll — the one
|
|
1757
|
+
; thing that separates it from `absolute`, which is positioned against the
|
|
1758
|
+
; nearest container and travels with it.
|
|
1759
|
+
fn isFixedPosition:boolean () {
|
|
1760
|
+
return (position == "fixed")
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
; The same walk as `moveSubtree`, except that a FIXED box and everything
|
|
1764
|
+
; under it stays where it is. A scroll moves the content of a container;
|
|
1765
|
+
; a fixed box is not content, it is furniture over the viewport, and the
|
|
1766
|
+
; whole point of the mode is that the page slides under it.
|
|
1767
|
+
fn moveSubtreeUnfixed:void (dx:double dy:double) {
|
|
1768
|
+
this.moveSelf(dx dy)
|
|
1769
|
+
def i:int 0
|
|
1770
|
+
def n:int (array_length children)
|
|
1771
|
+
while (i < n) {
|
|
1772
|
+
def kid:EVGElement (itemAt children i)
|
|
1773
|
+
if (kid.isFixedPosition()) {
|
|
1774
|
+
} {
|
|
1775
|
+
kid.moveSubtreeUnfixed(dx dy)
|
|
1776
|
+
}
|
|
1777
|
+
i = (i + 1)
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
; --- position-area --------------------------------------------------------
|
|
1782
|
+
;
|
|
1783
|
+
; `bottom`, `bottom start`, `top end`, `right center`, `center`, `cover`.
|
|
1784
|
+
; The first word is the side; the second is where along that side's cross
|
|
1785
|
+
; axis the surface sits. CSS's own grammar writes the second word as a
|
|
1786
|
+
; physical direction as often as a logical one — `bottom left` — so both
|
|
1787
|
+
; spellings are read, and which axis a physical word means depends on the
|
|
1788
|
+
; side, exactly as it does there.
|
|
1789
|
+
sfn areaSide:string (area:string) {
|
|
1790
|
+
def first:string (EVGElement.areaWord(area 0))
|
|
1791
|
+
if (((first == "top") || (first == "bottom")) || (first == "left")) {
|
|
1792
|
+
return first
|
|
1793
|
+
}
|
|
1794
|
+
if (((first == "right") || (first == "center")) || (first == "cover")) {
|
|
1795
|
+
return first
|
|
1796
|
+
}
|
|
1797
|
+
if (first == "free") {
|
|
1798
|
+
return first
|
|
1799
|
+
}
|
|
1800
|
+
return ""
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
sfn areaAlign:string (area:string) {
|
|
1804
|
+
def side:string (EVGElement.areaWord(area 0))
|
|
1805
|
+
def word:string (EVGElement.areaWord(area 1))
|
|
1806
|
+
if ((strlen word) == 0) {
|
|
1807
|
+
return "start"
|
|
1808
|
+
}
|
|
1809
|
+
if (((word == "start") || (word == "center")) || (word == "end")) {
|
|
1810
|
+
return word
|
|
1811
|
+
}
|
|
1812
|
+
; A physical word. Along a horizontal side the cross axis is vertical,
|
|
1813
|
+
; and the other way round.
|
|
1814
|
+
if ((side == "top") || (side == "bottom")) {
|
|
1815
|
+
if (word == "left") {
|
|
1816
|
+
return "start"
|
|
1817
|
+
}
|
|
1818
|
+
if (word == "right") {
|
|
1819
|
+
return "end"
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
if ((side == "left") || (side == "right")) {
|
|
1823
|
+
if (word == "top") {
|
|
1824
|
+
return "start"
|
|
1825
|
+
}
|
|
1826
|
+
if (word == "bottom") {
|
|
1827
|
+
return "end"
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return "start"
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
; The n-th whitespace-separated word of an area, or "".
|
|
1834
|
+
sfn areaWord:string (area:string index:int) {
|
|
1835
|
+
def out:string ""
|
|
1836
|
+
def seen:int 0
|
|
1837
|
+
def cur:string ""
|
|
1838
|
+
def i:int 0
|
|
1839
|
+
def n:int (strlen area)
|
|
1840
|
+
while (i <= n) {
|
|
1841
|
+
def isBreak:boolean (i == n)
|
|
1842
|
+
if (isBreak == false) {
|
|
1843
|
+
def c:int (charAt area i)
|
|
1844
|
+
if (((c == 32) || (c == 9)) || (c == 10)) {
|
|
1845
|
+
isBreak = true
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
if isBreak {
|
|
1849
|
+
if ((strlen cur) > 0) {
|
|
1850
|
+
if (seen == index) {
|
|
1851
|
+
out = cur
|
|
1852
|
+
return out
|
|
1853
|
+
}
|
|
1854
|
+
seen = (seen + 1)
|
|
1855
|
+
cur = ""
|
|
1856
|
+
}
|
|
1857
|
+
} {
|
|
1858
|
+
cur = (cur + (substring area i (i + 1)))
|
|
1859
|
+
}
|
|
1860
|
+
i = (i + 1)
|
|
1861
|
+
}
|
|
1862
|
+
return out
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
; --- anchor() ---------------------------------------------------------------
|
|
1866
|
+
;
|
|
1867
|
+
; `anchor(right)` and `calc(anchor(right) + 8px)`. Only those two shapes:
|
|
1868
|
+
; one anchor reference and at most one length added to or subtracted from
|
|
1869
|
+
; it, which is what every real use of this is. Anything more is `calc()`,
|
|
1870
|
+
; and this engine does not have one.
|
|
1871
|
+
|
|
1872
|
+
sfn mentionsAnchor:boolean (value:string) {
|
|
1873
|
+
return ((indexOf value "anchor(") >= 0)
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
; Which edge of the anchor was named. An inset property takes an edge on
|
|
1877
|
+
; its own axis; `anchor(center)` is the middle of that axis. An edge from
|
|
1878
|
+
; the wrong axis is a mistake worth reporting rather than guessing at.
|
|
1879
|
+
sfn anchorFnSide:string (prop:string value:string) {
|
|
1880
|
+
def open:int (indexOf value "anchor(")
|
|
1881
|
+
def rest:string (substring value (open + 7) (strlen value))
|
|
1882
|
+
def close:int (indexOf rest ")")
|
|
1883
|
+
if (close < 0) {
|
|
1884
|
+
EVGReject.note("unsupported value" prop value)
|
|
1885
|
+
return ""
|
|
1886
|
+
}
|
|
1887
|
+
def side:string (trim (substring rest 0 close))
|
|
1888
|
+
def vertical:boolean ((prop == "top") || (prop == "bottom"))
|
|
1889
|
+
if (side == "center") {
|
|
1890
|
+
return "center"
|
|
1891
|
+
}
|
|
1892
|
+
if vertical {
|
|
1893
|
+
if ((side == "top") || (side == "bottom")) {
|
|
1894
|
+
return side
|
|
1895
|
+
}
|
|
1896
|
+
} {
|
|
1897
|
+
if ((side == "left") || (side == "right")) {
|
|
1898
|
+
return side
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
EVGReject.note("unsupported value" prop value)
|
|
1902
|
+
return ""
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
; What `+ 8px` or `- 8px` after the reference adds. No length, no offset.
|
|
1906
|
+
sfn anchorFnOffset:double (value:string) {
|
|
1907
|
+
def open:int (indexOf value "anchor(")
|
|
1908
|
+
def rest:string (substring value (open + 7) (strlen value))
|
|
1909
|
+
def close:int (indexOf rest ")")
|
|
1910
|
+
if (close < 0) {
|
|
1911
|
+
return 0.0
|
|
1912
|
+
}
|
|
1913
|
+
def tail:string (trim (substring rest (close + 1) (strlen rest)))
|
|
1914
|
+
if ((strlen tail) == 0) {
|
|
1915
|
+
return 0.0
|
|
1916
|
+
}
|
|
1917
|
+
def sign:double 1.0
|
|
1918
|
+
def first:string (substring tail 0 1)
|
|
1919
|
+
if (first == "-") {
|
|
1920
|
+
sign = (0.0 - 1.0)
|
|
1921
|
+
} {
|
|
1922
|
+
if ((first == "+") == false) {
|
|
1923
|
+
return 0.0
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
def num:string (trim (substring tail 1 (strlen tail)))
|
|
1927
|
+
; Drop a trailing unit: px is the only one an offset can mean here,
|
|
1928
|
+
; because there is nothing to take a percentage of.
|
|
1929
|
+
def digits:string ""
|
|
1930
|
+
def i:int 0
|
|
1931
|
+
while (i < (strlen num)) {
|
|
1932
|
+
def ch:int (charAt num i)
|
|
1933
|
+
def ok:boolean (((ch >= 48) && (ch <= 57)) || (ch == 46))
|
|
1934
|
+
if ok {
|
|
1935
|
+
digits = (digits + (substring num i (i + 1)))
|
|
1936
|
+
} {
|
|
1937
|
+
i = (strlen num)
|
|
1938
|
+
}
|
|
1939
|
+
i = (i + 1)
|
|
1940
|
+
}
|
|
1941
|
+
def v@(optional):double (to_double (trim digits))
|
|
1942
|
+
if v {
|
|
1943
|
+
return (sign * (unwrap v))
|
|
1944
|
+
}
|
|
1945
|
+
return 0.0
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
; Any inset written as `anchor(…)`. The layout then places this element
|
|
1949
|
+
; edge by edge instead of by an area.
|
|
1950
|
+
fn hasAnchorInsets:boolean () {
|
|
1951
|
+
if ((strlen anchorLeftSide) > 0) {
|
|
1952
|
+
return true
|
|
1953
|
+
}
|
|
1954
|
+
if ((strlen anchorTopSide) > 0) {
|
|
1955
|
+
return true
|
|
1956
|
+
}
|
|
1957
|
+
if ((strlen anchorRightSide) > 0) {
|
|
1958
|
+
return true
|
|
1959
|
+
}
|
|
1960
|
+
if ((strlen anchorBottomSide) > 0) {
|
|
1961
|
+
return true
|
|
1962
|
+
}
|
|
1963
|
+
return false
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
; A SURFACE: out of its parent's flow, drawn in the top layer, placed
|
|
1967
|
+
; against an anchor rather than by the flow. `overlay: true` says so, and
|
|
1968
|
+
; so does the `popover` tag, which is the web's name for the same thing and
|
|
1969
|
+
; saves the property.
|
|
1970
|
+
fn isSurface:boolean () {
|
|
1971
|
+
if isOverlay {
|
|
1972
|
+
return true
|
|
1973
|
+
}
|
|
1974
|
+
if (tagName == "popover") {
|
|
1975
|
+
return true
|
|
1976
|
+
}
|
|
1977
|
+
return false
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
; What an `arrowPath` is filled with: the stroke's own colour, because an
|
|
1981
|
+
; arrow whose head is a different colour from its shaft is a mistake far
|
|
1982
|
+
; more often than a choice. An explicit `fill` still wins.
|
|
1983
|
+
fn arrowFillColor:EVGColor () {
|
|
1984
|
+
def out:EVGColor (EVGColor.noColor())
|
|
1985
|
+
if fillColor.isSet {
|
|
1986
|
+
def f:EVGColor (unwrap fillColor)
|
|
1987
|
+
out = f
|
|
1988
|
+
return out
|
|
1989
|
+
}
|
|
1990
|
+
if strokeColor.isSet {
|
|
1991
|
+
def st:EVGColor (unwrap strokeColor)
|
|
1992
|
+
out = st
|
|
1993
|
+
}
|
|
1994
|
+
return out
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
; Drawn as a vector path by every renderer. A `connector` is one: the
|
|
1998
|
+
; difference between it and a `path` is who wrote the `d`, which no
|
|
1999
|
+
; renderer has any reason to care about.
|
|
2000
|
+
fn drawsPath:boolean () {
|
|
2001
|
+
if (tagName == "path") {
|
|
2002
|
+
return true
|
|
2003
|
+
}
|
|
2004
|
+
if (tagName == "Path") {
|
|
2005
|
+
return true
|
|
2006
|
+
}
|
|
2007
|
+
if (tagName == "connector") {
|
|
2008
|
+
return true
|
|
2009
|
+
}
|
|
2010
|
+
; An `svg` element is the same family — `EVGPatch.familyOf` has said so
|
|
2011
|
+
; since it was written, and the display list draws an inline document
|
|
2012
|
+
; in `walkPath` — but this answered no for the tag, so the one branch
|
|
2013
|
+
; that reaches that code was never taken. A node an agent could create,
|
|
2014
|
+
; address, and fill with a traced picture drew nothing at all.
|
|
2015
|
+
if (tagName == "svg") {
|
|
2016
|
+
return true
|
|
2017
|
+
}
|
|
2018
|
+
if (tagName == "Svg") {
|
|
2019
|
+
return true
|
|
2020
|
+
}
|
|
2021
|
+
return false
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
fn hasAbsolutePosition:boolean () {
|
|
2025
|
+
; Fixed is out of flow by the same rule absolute is; where it differs
|
|
2026
|
+
; is which box it is positioned against, which is the layout's business
|
|
2027
|
+
; and not this one's.
|
|
2028
|
+
if (position == "fixed") {
|
|
2029
|
+
return true
|
|
2030
|
+
}
|
|
2031
|
+
; An overlay surface is out of flow by the same rule an absolutely
|
|
2032
|
+
; positioned element is, and saying so here rather than adding a second
|
|
2033
|
+
; condition to every size and space calculation in EVGLayout is the
|
|
2034
|
+
; whole reason the flow half of this feature is one line. The overlay
|
|
2035
|
+
; pass then MOVES it; until then it sits where layoutAbsolute put it.
|
|
2036
|
+
if (this.isSurface()) {
|
|
2037
|
+
return true
|
|
2038
|
+
}
|
|
2039
|
+
; A hidden element is out of flow the same way: nothing around it
|
|
2040
|
+
; moves for it, and the gap between its neighbours is one gap.
|
|
2041
|
+
if (display == "none") {
|
|
2042
|
+
return true
|
|
2043
|
+
}
|
|
2044
|
+
; Check if element has absolute positioning
|
|
2045
|
+
; Layer elements are always absolute (overlay parent)
|
|
2046
|
+
if ((tagName == "layer") || (tagName == "Layer")) {
|
|
2047
|
+
return true
|
|
2048
|
+
}
|
|
2049
|
+
; A connector is geometry over the layout, never in it: it joins two
|
|
2050
|
+
; boxes and must not push either of them about.
|
|
2051
|
+
if (tagName == "connector") {
|
|
2052
|
+
return true
|
|
2053
|
+
}
|
|
2054
|
+
; An inset written as `anchor(…)` is still an inset.
|
|
2055
|
+
if (this.hasAnchorInsets()) {
|
|
2056
|
+
return true
|
|
2057
|
+
}
|
|
2058
|
+
if left.isSet {
|
|
2059
|
+
return true
|
|
2060
|
+
}
|
|
2061
|
+
if top.isSet {
|
|
2062
|
+
return true
|
|
2063
|
+
}
|
|
2064
|
+
if right.isSet {
|
|
2065
|
+
return true
|
|
2066
|
+
}
|
|
2067
|
+
if bottom.isSet {
|
|
2068
|
+
return true
|
|
2069
|
+
}
|
|
2070
|
+
if x.isSet {
|
|
2071
|
+
return true
|
|
2072
|
+
}
|
|
2073
|
+
if y.isSet {
|
|
2074
|
+
return true
|
|
2075
|
+
}
|
|
2076
|
+
return false
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
; Resolve book format to page dimensions in points (72 DPI)
|
|
2080
|
+
; Returns [width, height] based on format and orientation
|
|
2081
|
+
fn resolveBookFormat:void () {
|
|
2082
|
+
def w:double 595.0 ; Default A4 width
|
|
2083
|
+
def h:double 842.0 ; Default A4 height
|
|
2084
|
+
|
|
2085
|
+
; Blurb book formats (width x height in points @ 72 DPI)
|
|
2086
|
+
if (format == "a4") {
|
|
2087
|
+
w = 595.0
|
|
2088
|
+
h = 842.0
|
|
2089
|
+
}
|
|
2090
|
+
if (format == "letter") {
|
|
2091
|
+
w = 612.0
|
|
2092
|
+
h = 792.0
|
|
2093
|
+
}
|
|
2094
|
+
if (format == "trade-5x8") {
|
|
2095
|
+
w = 360.0
|
|
2096
|
+
h = 576.0
|
|
2097
|
+
}
|
|
2098
|
+
if (format == "trade-6x9") {
|
|
2099
|
+
w = 432.0
|
|
2100
|
+
h = 648.0
|
|
2101
|
+
}
|
|
2102
|
+
if (format == "trade-8x10") {
|
|
2103
|
+
w = 576.0
|
|
2104
|
+
h = 720.0
|
|
2105
|
+
}
|
|
2106
|
+
if (format == "mini-square") {
|
|
2107
|
+
w = 360.0
|
|
2108
|
+
h = 360.0
|
|
2109
|
+
}
|
|
2110
|
+
if (format == "small-square") {
|
|
2111
|
+
w = 504.0
|
|
2112
|
+
h = 504.0
|
|
2113
|
+
}
|
|
2114
|
+
if (format == "standard-portrait") {
|
|
2115
|
+
w = 576.0
|
|
2116
|
+
h = 720.0
|
|
2117
|
+
}
|
|
2118
|
+
if (format == "standard-landscape") {
|
|
2119
|
+
w = 720.0
|
|
2120
|
+
h = 576.0
|
|
2121
|
+
}
|
|
2122
|
+
if (format == "large-landscape") {
|
|
2123
|
+
w = 936.0
|
|
2124
|
+
h = 792.0
|
|
2125
|
+
}
|
|
2126
|
+
if (format == "large-square") {
|
|
2127
|
+
w = 864.0
|
|
2128
|
+
h = 864.0
|
|
2129
|
+
}
|
|
2130
|
+
if (format == "magazine") {
|
|
2131
|
+
w = 612.0
|
|
2132
|
+
h = 792.0
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
; Apply orientation (swap dimensions for landscape if not already landscape format)
|
|
2136
|
+
if (orientation == "landscape") {
|
|
2137
|
+
if (w < h) {
|
|
2138
|
+
; Swap width and height for landscape
|
|
2139
|
+
def temp:double w
|
|
2140
|
+
w = h
|
|
2141
|
+
h = temp
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
if (orientation == "portrait") {
|
|
2145
|
+
if (w > h) {
|
|
2146
|
+
; Swap width and height for portrait
|
|
2147
|
+
def temp:double w
|
|
2148
|
+
w = h
|
|
2149
|
+
h = temp
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
; If custom dimensions provided, use them
|
|
2154
|
+
if (pageWidth > 0.0) {
|
|
2155
|
+
w = pageWidth
|
|
2156
|
+
}
|
|
2157
|
+
if (pageHeight > 0.0) {
|
|
2158
|
+
h = pageHeight
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
; Store resolved dimensions
|
|
2162
|
+
pageWidth = w
|
|
2163
|
+
pageHeight = h
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
; The face this element is actually set in, weight included.
|
|
2167
|
+
;
|
|
2168
|
+
; The renderers pick a bold face by appending "-Bold" to the family at
|
|
2169
|
+
; PAINT time, while layout measured the plain family — so a bold heading
|
|
2170
|
+
; was measured in the regular cut and drawn in the wider bold one, and
|
|
2171
|
+
; wrapped a line later than the box it had been given. Both sides go
|
|
2172
|
+
; through here now, which is §4.1 rule 1: one face, one measurement.
|
|
2173
|
+
; The destination in force here: this element's own, or the nearest
|
|
2174
|
+
; ancestor's. `<a>` wrapping a row is the shape this exists for.
|
|
2175
|
+
fn effectiveHref:string () {
|
|
2176
|
+
if ((strlen href) > 0) {
|
|
2177
|
+
return href
|
|
2178
|
+
}
|
|
2179
|
+
def p@(optional):EVGElement parent
|
|
2180
|
+
def guard:int 0
|
|
2181
|
+
while ((!null? p) && (guard < 4096)) {
|
|
2182
|
+
def up:EVGElement (unwrap p)
|
|
2183
|
+
if ((strlen up.href) > 0) {
|
|
2184
|
+
return up.href
|
|
2185
|
+
}
|
|
2186
|
+
p = up.parent
|
|
2187
|
+
guard = (guard + 1)
|
|
2188
|
+
}
|
|
2189
|
+
return ""
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
fn effectiveFontFamily:string () {
|
|
2193
|
+
if (fontWeight == "bold") {
|
|
2194
|
+
return (fontFamily + "-Bold")
|
|
2195
|
+
}
|
|
2196
|
+
return fontFamily
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
; The border, from wherever it was actually written.
|
|
2200
|
+
;
|
|
2201
|
+
; There are TWO places a border can land. `setAttribute` — which is what a
|
|
2202
|
+
; stylesheet and every kebab-case JSX attribute go through — writes
|
|
2203
|
+
; `box.borderWidth` / `box.borderColor`. The camelCase style-object path in
|
|
2204
|
+
; JSXToEVG writes the element-level `borderWidth` / `borderColor`. Each
|
|
2205
|
+
; painter then picked one:
|
|
2206
|
+
;
|
|
2207
|
+
; PDF box.borderWidth + box.borderColor -> drew it
|
|
2208
|
+
; HTML the browser, from the emitted CSS -> drew it
|
|
2209
|
+
; PNG el.borderWidth + el.borderColor -> drew NOTHING
|
|
2210
|
+
; list box.borderWidthPx + el.borderColor -> drew NOTHING
|
|
2211
|
+
;
|
|
2212
|
+
; So a CSS-declared border printed and previewed correctly and was missing
|
|
2213
|
+
; from the raster and the GPU backend — the same shape of bug as the
|
|
2214
|
+
; border-radius that worked in PDF and silently not in PNG. One accessor,
|
|
2215
|
+
; and every painter reads it.
|
|
2216
|
+
fn effectiveBorderWidthPx:double () {
|
|
2217
|
+
if (box.borderWidthPx > 0.0) {
|
|
2218
|
+
return box.borderWidthPx
|
|
2219
|
+
}
|
|
2220
|
+
if borderWidth {
|
|
2221
|
+
if borderWidth.isSet {
|
|
2222
|
+
return borderWidth.pixels
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
return 0.0
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
fn effectiveBorderColor:EVGColor () {
|
|
2229
|
+
if box.borderColor {
|
|
2230
|
+
def bc:EVGColor (unwrap box.borderColor)
|
|
2231
|
+
if bc.isSet {
|
|
2232
|
+
return bc
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
if borderColor {
|
|
2236
|
+
def ec:EVGColor (unwrap borderColor)
|
|
2237
|
+
if ec.isSet {
|
|
2238
|
+
return ec
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
; A width with no colour is a black border, which is what CSS says and
|
|
2242
|
+
; what the PDF path already assumed.
|
|
2243
|
+
return (EVGColor.black())
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
fn hasBorder:boolean () {
|
|
2247
|
+
if ((this.effectiveBorderWidthPx()) <= 0.0) {
|
|
2248
|
+
return false
|
|
2249
|
+
}
|
|
2250
|
+
return true
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
; The colour fallback glyphs are painted in: the explicit one when set,
|
|
2254
|
+
; otherwise the text colour, so an undeclared document looks exactly as it
|
|
2255
|
+
; did.
|
|
2256
|
+
fn effectiveEmojiColor:EVGColor () {
|
|
2257
|
+
if emojiColor.isSet {
|
|
2258
|
+
return (unwrap emojiColor)
|
|
2259
|
+
}
|
|
2260
|
+
return (unwrap color)
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
fn inheritProperties:void (parentEl:EVGElement) {
|
|
2264
|
+
; Inherit properties from parent
|
|
2265
|
+
if (fontFamily == "Noto Sans") {
|
|
2266
|
+
fontFamily = parentEl.fontFamily
|
|
2267
|
+
}
|
|
2268
|
+
if (color.isSet == false) {
|
|
2269
|
+
color = parentEl.color
|
|
2270
|
+
}
|
|
2271
|
+
; Inherited like `color`: a deck sets it once and every caption in it
|
|
2272
|
+
; picks it up.
|
|
2273
|
+
if (emojiColor.isSet == false) {
|
|
2274
|
+
emojiColor = parentEl.emojiColor
|
|
2275
|
+
}
|
|
2276
|
+
; And so does `cursor`. This is what makes "what is the cursor here" a
|
|
2277
|
+
; question about ONE element rather than a walk up a tree that has no
|
|
2278
|
+
; parent links.
|
|
2279
|
+
if ((strlen cursor) == 0) {
|
|
2280
|
+
cursor = parentEl.cursor
|
|
2281
|
+
}
|
|
2282
|
+
; `white-space` is inherited, which is what lets a one-line field say
|
|
2283
|
+
; `nowrap` once and have the run inside it obey.
|
|
2284
|
+
if (whiteSpace == "normal") {
|
|
2285
|
+
whiteSpace = parentEl.whiteSpace
|
|
2286
|
+
}
|
|
2287
|
+
fontSizeBase = parentEl.inheritedFontSize
|
|
2288
|
+
rootFontSize = parentEl.rootFontSize
|
|
2289
|
+
; The viewport travels the same way the root font size does rather
|
|
2290
|
+
; than being looked up — except across a box that declares one of its
|
|
2291
|
+
; own, which is where the walk stops.
|
|
2292
|
+
if (viewportRoot == false) {
|
|
2293
|
+
viewportW = parentEl.viewportW
|
|
2294
|
+
viewportH = parentEl.viewportH
|
|
2295
|
+
viewportX = parentEl.viewportX
|
|
2296
|
+
viewportY = parentEl.viewportY
|
|
2297
|
+
}
|
|
2298
|
+
this.applyOwnFontSize()
|
|
2299
|
+
this.applyOwnDirection(parentEl.resolvedRtl)
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
; Settle this element's direction against the one it inherits. Inherited
|
|
2303
|
+
; like `color`, and for the same reason: a UI is turned around by saying so
|
|
2304
|
+
; ONCE, on the root, rather than by marking every panel in it.
|
|
2305
|
+
fn applyOwnDirection:void (inherited:boolean) {
|
|
2306
|
+
resolvedRtl = inherited
|
|
2307
|
+
if (textDir == "rtl") {
|
|
2308
|
+
resolvedRtl = true
|
|
2309
|
+
}
|
|
2310
|
+
if (textDir == "ltr") {
|
|
2311
|
+
resolvedRtl = false
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
; Settle this element's font-size against the one it inherits, and publish
|
|
2316
|
+
; the result as what its own `em` lengths and its descendants resolve
|
|
2317
|
+
; against.
|
|
2318
|
+
;
|
|
2319
|
+
; Two things were broken before this existed. A root has no parent, so
|
|
2320
|
+
; inheritProperties never ran on it and a font-size on a Page or Print was
|
|
2321
|
+
; dropped outright. And below the root, every element was constructed with
|
|
2322
|
+
; a 14px font-size, so an inherited size was immediately overwritten by
|
|
2323
|
+
; that default — font-size only ever applied to the element that carried it.
|
|
2324
|
+
; EVGLayout.layout calls this on the root; inheritProperties on the rest.
|
|
2325
|
+
fn applyOwnFontSize:void () {
|
|
2326
|
+
def authored:boolean fontSize.isSet
|
|
2327
|
+
if fontSizeInherited {
|
|
2328
|
+
authored = false
|
|
2329
|
+
}
|
|
2330
|
+
if authored {
|
|
2331
|
+
; `em` on font-size itself is relative to the *parent's* size,
|
|
2332
|
+
; which is why the base is kept rather than read back out of
|
|
2333
|
+
; inheritedFontSize.
|
|
2334
|
+
fontSize.setContext(rootFontSize viewportW viewportH)
|
|
2335
|
+
fontSize.resolve(fontSizeBase fontSizeBase)
|
|
2336
|
+
inheritedFontSize = fontSize.pixels
|
|
2337
|
+
} {
|
|
2338
|
+
inheritedFontSize = fontSizeBase
|
|
2339
|
+
fontSize = (EVGUnit.px(fontSizeBase))
|
|
2340
|
+
fontSizeInherited = true
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
fn resolveUnits:void (parentWidth:double parentHeight:double) {
|
|
2345
|
+
; Skip if already resolved - prevents double-resolution of percentages
|
|
2346
|
+
if unitsResolved {
|
|
2347
|
+
return
|
|
2348
|
+
}
|
|
2349
|
+
unitsResolved = true
|
|
2350
|
+
|
|
2351
|
+
; Resolve all unit values to pixels
|
|
2352
|
+
def fs:double inheritedFontSize
|
|
2353
|
+
def rfs:double rootFontSize
|
|
2354
|
+
|
|
2355
|
+
; `rem` needs the root's font size and `vw`/`vh` need the viewport,
|
|
2356
|
+
; neither of which a unit can reach on its own — hand both over before
|
|
2357
|
+
; anything is resolved.
|
|
2358
|
+
def vpw:double viewportW
|
|
2359
|
+
def vph:double viewportH
|
|
2360
|
+
width.setContext(rfs vpw vph)
|
|
2361
|
+
height.setContext(rfs vpw vph)
|
|
2362
|
+
flexBasis.setContext(rfs vpw vph)
|
|
2363
|
+
minWidth.setContext(rfs vpw vph)
|
|
2364
|
+
minHeight.setContext(rfs vpw vph)
|
|
2365
|
+
maxWidth.setContext(rfs vpw vph)
|
|
2366
|
+
maxHeight.setContext(rfs vpw vph)
|
|
2367
|
+
left.setContext(rfs vpw vph)
|
|
2368
|
+
top.setContext(rfs vpw vph)
|
|
2369
|
+
right.setContext(rfs vpw vph)
|
|
2370
|
+
bottom.setContext(rfs vpw vph)
|
|
2371
|
+
x.setContext(rfs vpw vph)
|
|
2372
|
+
y.setContext(rfs vpw vph)
|
|
2373
|
+
shadowRadius.setContext(rfs vpw vph)
|
|
2374
|
+
shadowOffsetX.setContext(rfs vpw vph)
|
|
2375
|
+
shadowOffsetY.setContext(rfs vpw vph)
|
|
2376
|
+
|
|
2377
|
+
; Dimensions - width uses parentWidth, height uses parentHeight
|
|
2378
|
+
width.resolveWithHeight(parentWidth parentHeight fs)
|
|
2379
|
+
height.resolveForHeight(parentWidth parentHeight fs)
|
|
2380
|
+
flexBasis.resolve(parentWidth fs)
|
|
2381
|
+
minWidth.resolve(parentWidth fs)
|
|
2382
|
+
minHeight.resolve(parentHeight fs)
|
|
2383
|
+
maxWidth.resolve(parentWidth fs)
|
|
2384
|
+
maxHeight.resolve(parentHeight fs)
|
|
2385
|
+
|
|
2386
|
+
; Position
|
|
2387
|
+
left.resolve(parentWidth fs)
|
|
2388
|
+
top.resolve(parentHeight fs)
|
|
2389
|
+
right.resolve(parentWidth fs)
|
|
2390
|
+
bottom.resolve(parentHeight fs)
|
|
2391
|
+
x.resolve(parentWidth fs)
|
|
2392
|
+
y.resolve(parentHeight fs)
|
|
2393
|
+
|
|
2394
|
+
; Box model
|
|
2395
|
+
box.resolveUnits(parentWidth parentHeight fs rfs vpw vph)
|
|
2396
|
+
|
|
2397
|
+
; Shadow
|
|
2398
|
+
shadowRadius.resolve(parentWidth fs)
|
|
2399
|
+
shadowOffsetX.resolve(parentWidth fs)
|
|
2400
|
+
shadowOffsetY.resolve(parentHeight fs)
|
|
2401
|
+
|
|
2402
|
+
; Determine if absolute positioning
|
|
2403
|
+
isAbsolute = (this.hasAbsolutePosition())
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
; Normalize a property spelling so `fontSize` and `font-size` are one key.
|
|
2407
|
+
sfn toKebab:string (name:string) {
|
|
2408
|
+
def out:string ""
|
|
2409
|
+
def len:int (strlen name)
|
|
2410
|
+
def i:int 0
|
|
2411
|
+
while (i < len) {
|
|
2412
|
+
def c:int (charAt name i)
|
|
2413
|
+
if ((c >= 65) && (c <= 90)) {
|
|
2414
|
+
if (i > 0) {
|
|
2415
|
+
out = out + "-"
|
|
2416
|
+
}
|
|
2417
|
+
out = out + (strfromcode (c + 32))
|
|
2418
|
+
} {
|
|
2419
|
+
out = out + (strfromcode c)
|
|
2420
|
+
}
|
|
2421
|
+
i = (i + 1)
|
|
2422
|
+
}
|
|
2423
|
+
return out
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
; --- transform ---------------------------------------------------------------
|
|
2427
|
+
|
|
2428
|
+
; Compose the function list into one similarity: angle, uniform scale,
|
|
2429
|
+
; offset. Left to right, the way CSS reads them, and the composition is the
|
|
2430
|
+
; reason order is honoured for free:
|
|
2431
|
+
;
|
|
2432
|
+
; M = (a, s, t) means p -> R(a)·s·p + t
|
|
2433
|
+
; M ∘ N means angle a+b, scale s·s2, offset t + R(a)·s·u
|
|
2434
|
+
;
|
|
2435
|
+
; which is why `rotate(90deg) translate(10px, 0)` moves DOWN the page and
|
|
2436
|
+
; `translate(10px, 0) rotate(90deg)` moves right. Applying the pieces
|
|
2437
|
+
; independently gets both of those wrong and looks correct whenever only one
|
|
2438
|
+
; of them is present, which is most of the time.
|
|
2439
|
+
fn applyTransform:void (value:string) {
|
|
2440
|
+
transformSpec = (trim value)
|
|
2441
|
+
rotate = 0.0
|
|
2442
|
+
scale = 1.0
|
|
2443
|
+
flipY = false
|
|
2444
|
+
translateX = 0.0
|
|
2445
|
+
translateY = 0.0
|
|
2446
|
+
if ((transformSpec == "none") || ((strlen transformSpec) == 0)) {
|
|
2447
|
+
return
|
|
2448
|
+
}
|
|
2449
|
+
def parts:[string] (EVGElement.splitWords(transformSpec))
|
|
2450
|
+
def a:double 0.0
|
|
2451
|
+
def sc:double 1.0
|
|
2452
|
+
def flip:boolean false
|
|
2453
|
+
def tx:double 0.0
|
|
2454
|
+
def ty:double 0.0
|
|
2455
|
+
def i:int 0
|
|
2456
|
+
while (i < (array_length parts)) {
|
|
2457
|
+
def one:string (trim (itemAt parts i))
|
|
2458
|
+
def b:double 0.0
|
|
2459
|
+
def s2:double 1.0
|
|
2460
|
+
def f2:boolean false
|
|
2461
|
+
def ux:double 0.0
|
|
2462
|
+
def uy:double 0.0
|
|
2463
|
+
def args:string (EVGElement.callArgs(one "rotate"))
|
|
2464
|
+
if ((strlen args) > 0) {
|
|
2465
|
+
b = (EVGElement.parseAngleDeg(args))
|
|
2466
|
+
} {
|
|
2467
|
+
def sargs:string (EVGElement.callArgs(one "scale"))
|
|
2468
|
+
if ((strlen sargs) > 0) {
|
|
2469
|
+
def nums:[double] (EVGElement.numberList(sargs))
|
|
2470
|
+
if ((array_length nums) > 0) {
|
|
2471
|
+
s2 = (itemAt nums 0)
|
|
2472
|
+
}
|
|
2473
|
+
; Two arguments of the same magnitude and opposite sign
|
|
2474
|
+
; are `s · diag(1, -1)`: the factor is the first of them
|
|
2475
|
+
; and the rest is a flip.
|
|
2476
|
+
if ((array_length nums) > 1) {
|
|
2477
|
+
if (((itemAt nums 0) * (itemAt nums 1)) < 0.0) {
|
|
2478
|
+
f2 = true
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
} {
|
|
2482
|
+
def targs:string (EVGElement.callArgs(one "translate"))
|
|
2483
|
+
if ((strlen targs) > 0) {
|
|
2484
|
+
def tn:[double] (EVGElement.numberList(targs))
|
|
2485
|
+
if ((array_length tn) > 0) {
|
|
2486
|
+
ux = (itemAt tn 0)
|
|
2487
|
+
}
|
|
2488
|
+
if ((array_length tn) > 1) {
|
|
2489
|
+
uy = (itemAt tn 1)
|
|
2490
|
+
}
|
|
2491
|
+
} {
|
|
2492
|
+
def xargs:string (EVGElement.callArgs(one "translateX"))
|
|
2493
|
+
if ((strlen xargs) > 0) {
|
|
2494
|
+
def xn:[double] (EVGElement.numberList(xargs))
|
|
2495
|
+
if ((array_length xn) > 0) {
|
|
2496
|
+
ux = (itemAt xn 0)
|
|
2497
|
+
}
|
|
2498
|
+
} {
|
|
2499
|
+
def yargs:string (EVGElement.callArgs(one "translateY"))
|
|
2500
|
+
if ((strlen yargs) > 0) {
|
|
2501
|
+
def yn:[double] (EVGElement.numberList(yargs))
|
|
2502
|
+
if ((array_length yn) > 0) {
|
|
2503
|
+
uy = (itemAt yn 0)
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
def rad:double ((a * 3.14159265358979) / 180.0)
|
|
2511
|
+
def cs:double (cos rad)
|
|
2512
|
+
def sn:double (sin rad)
|
|
2513
|
+
; A flip already in force turns the pieces after it around: the
|
|
2514
|
+
; offset is mirrored before it is rotated, and the angle of a
|
|
2515
|
+
; rotation the other side of a mirror is its negative.
|
|
2516
|
+
; F·R(b) = R(-b)·F
|
|
2517
|
+
; which is what keeps `scale(1,-1) rotate(30deg)` different from
|
|
2518
|
+
; `rotate(30deg) scale(1,-1)`, the way the order already matters
|
|
2519
|
+
; for a translate.
|
|
2520
|
+
def uyf:double uy
|
|
2521
|
+
def bf:double b
|
|
2522
|
+
if flip {
|
|
2523
|
+
uyf = (0.0 - uy)
|
|
2524
|
+
bf = (0.0 - b)
|
|
2525
|
+
}
|
|
2526
|
+
tx = (tx + (sc * ((ux * cs) - (uyf * sn))))
|
|
2527
|
+
ty = (ty + (sc * ((ux * sn) + (uyf * cs))))
|
|
2528
|
+
a = (a + bf)
|
|
2529
|
+
sc = (sc * s2)
|
|
2530
|
+
if f2 {
|
|
2531
|
+
flip = (flip == false)
|
|
2532
|
+
}
|
|
2533
|
+
i = (i + 1)
|
|
2534
|
+
}
|
|
2535
|
+
rotate = a
|
|
2536
|
+
scale = sc
|
|
2537
|
+
flipY = flip
|
|
2538
|
+
translateX = tx
|
|
2539
|
+
translateY = ty
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
; --- transform-origin ---------------------------------------------------------
|
|
2543
|
+
|
|
2544
|
+
; `transform-origin: left top`, `25% 10px`, `bottom`, `10px`.
|
|
2545
|
+
;
|
|
2546
|
+
; The grammar is not "first value is x". A lone LENGTH sets x and leaves y
|
|
2547
|
+
; at 50%, but a lone `top` or `bottom` is a Y keyword and sets the other
|
|
2548
|
+
; axis — so `transform-origin: 10px` is `10px 50%` while
|
|
2549
|
+
; `transform-origin: top` is `50% 0%`. And with two keywords the ORDER IS
|
|
2550
|
+
; FREE: `top left` and `left top` are the same point. Both of those are
|
|
2551
|
+
; measured, not read off a grammar: a browser was asked for the resolved
|
|
2552
|
+
; value of seventeen spellings and `oracle/css-timing.json` holds the
|
|
2553
|
+
; answers.
|
|
2554
|
+
;
|
|
2555
|
+
; A third value is the z origin. It is accepted and ignored; this is a 2-D
|
|
2556
|
+
; engine and rejecting the declaration outright would throw away the x and
|
|
2557
|
+
; y with it.
|
|
2558
|
+
fn applyTransformOrigin:void (value:string) {
|
|
2559
|
+
transformOriginSpec = (trim value)
|
|
2560
|
+
def words:[string] (EVGElement.splitWords(transformOriginSpec))
|
|
2561
|
+
def n:int (array_length words)
|
|
2562
|
+
if (n == 0) {
|
|
2563
|
+
transformOriginX = (new EVGUnit())
|
|
2564
|
+
transformOriginY = (new EVGUnit())
|
|
2565
|
+
return
|
|
2566
|
+
}
|
|
2567
|
+
def first:string (trim (itemAt words 0))
|
|
2568
|
+
if (n == 1) {
|
|
2569
|
+
if (EVGElement.isYKeyword(first)) {
|
|
2570
|
+
transformOriginX = (EVGUnit.percent(50.0))
|
|
2571
|
+
transformOriginY = (EVGElement.originUnit(first))
|
|
2572
|
+
} {
|
|
2573
|
+
transformOriginX = (EVGElement.originUnit(first))
|
|
2574
|
+
transformOriginY = (EVGUnit.percent(50.0))
|
|
2575
|
+
}
|
|
2576
|
+
return
|
|
2577
|
+
}
|
|
2578
|
+
def second:string (trim (itemAt words 1))
|
|
2579
|
+
; Swap when the pair was written the other way round. Only a keyword can
|
|
2580
|
+
; say which axis it belongs to, so only a keyword can trigger this — two
|
|
2581
|
+
; lengths are always x then y.
|
|
2582
|
+
def swap:boolean false
|
|
2583
|
+
if (EVGElement.isYKeyword(first)) { swap = true }
|
|
2584
|
+
if (EVGElement.isXKeyword(second)) { swap = true }
|
|
2585
|
+
if swap {
|
|
2586
|
+
transformOriginX = (EVGElement.originUnit(second))
|
|
2587
|
+
transformOriginY = (EVGElement.originUnit(first))
|
|
2588
|
+
} {
|
|
2589
|
+
transformOriginX = (EVGElement.originUnit(first))
|
|
2590
|
+
transformOriginY = (EVGElement.originUnit(second))
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
sfn isXKeyword:boolean (w:string) {
|
|
2595
|
+
return ((w == "left") || (w == "right"))
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
sfn isYKeyword:boolean (w:string) {
|
|
2599
|
+
return ((w == "top") || (w == "bottom"))
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
; A keyword is a percentage of the box. `center` belongs to neither axis
|
|
2603
|
+
; and means the same on both, which is why it is not in either list above.
|
|
2604
|
+
sfn originUnit:EVGUnit (w:string) {
|
|
2605
|
+
if ((w == "left") || (w == "top")) { return (EVGUnit.percent(0.0)) }
|
|
2606
|
+
if ((w == "right") || (w == "bottom")) { return (EVGUnit.percent(100.0)) }
|
|
2607
|
+
if (w == "center") { return (EVGUnit.percent(50.0)) }
|
|
2608
|
+
return (EVGUnit.parse(w))
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
; The origin in pixels, from the box's own top-left corner. `size` is that
|
|
2612
|
+
; edge of the border box.
|
|
2613
|
+
;
|
|
2614
|
+
; Unset is 50%, not 0: an element that never mentions `transform-origin`
|
|
2615
|
+
; turns about its centre, and reading an unset unit as zero would put every
|
|
2616
|
+
; untouched rotation on the top-left corner instead.
|
|
2617
|
+
sfn resolveOrigin:double (u:EVGUnit size:double) {
|
|
2618
|
+
if (u.isSet == false) {
|
|
2619
|
+
return (size / 2.0)
|
|
2620
|
+
}
|
|
2621
|
+
; 1 is PERCENT, 3 is HEIGHT_PERCENT. Both resolve against the edge
|
|
2622
|
+
; being asked about here, because the caller passes the right one.
|
|
2623
|
+
if ((u.unitType == 1) || (u.unitType == 3)) {
|
|
2624
|
+
return ((u.value / 100.0) * size)
|
|
2625
|
+
}
|
|
2626
|
+
return u.value
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
; "" when the value is one this engine can represent exactly, otherwise the
|
|
2630
|
+
; reason. A stylesheet reports it; approximating a skew as a rotation would
|
|
2631
|
+
; be worse than saying no.
|
|
2632
|
+
sfn transformProblem:string (value:string) {
|
|
2633
|
+
def v:string (trim value)
|
|
2634
|
+
if ((v == "none") || ((strlen v) == 0)) {
|
|
2635
|
+
return ""
|
|
2636
|
+
}
|
|
2637
|
+
def parts:[string] (EVGElement.splitWords(v))
|
|
2638
|
+
def i:int 0
|
|
2639
|
+
while (i < (array_length parts)) {
|
|
2640
|
+
def one:string (trim (itemAt parts i))
|
|
2641
|
+
def known:boolean false
|
|
2642
|
+
if ((strlen (EVGElement.callArgs(one "rotate"))) > 0) { known = true }
|
|
2643
|
+
if ((strlen (EVGElement.callArgs(one "scale"))) > 0) { known = true }
|
|
2644
|
+
if ((strlen (EVGElement.callArgs(one "translate"))) > 0) { known = true }
|
|
2645
|
+
if ((strlen (EVGElement.callArgs(one "translateX"))) > 0) { known = true }
|
|
2646
|
+
if ((strlen (EVGElement.callArgs(one "translateY"))) > 0) { known = true }
|
|
2647
|
+
if (known == false) {
|
|
2648
|
+
return ("Unsupported transform (rotate, scale, translate, translateX, translateY): " + one)
|
|
2649
|
+
}
|
|
2650
|
+
; A non-uniform scale is not a similarity, so the display list
|
|
2651
|
+
; cannot carry it as an angle and a factor. Two arguments whose
|
|
2652
|
+
; MAGNITUDES differ is exactly that case — a difference of sign
|
|
2653
|
+
; alone is a reflection, which is still a similarity and is
|
|
2654
|
+
; carried as a flip beside the angle and the factor.
|
|
2655
|
+
def sargs:string (EVGElement.callArgs(one "scale"))
|
|
2656
|
+
if ((strlen sargs) > 0) {
|
|
2657
|
+
def nums:[double] (EVGElement.numberList(sargs))
|
|
2658
|
+
if ((array_length nums) > 1) {
|
|
2659
|
+
if ((fabs ((fabs (itemAt nums 0)) - (fabs (itemAt nums 1)))) > 0.0001) {
|
|
2660
|
+
return ("Only a uniform scale is supported: " + one)
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
i = (i + 1)
|
|
2665
|
+
}
|
|
2666
|
+
return ""
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
; The inside of `name(...)`, or "" if `one` is not that call. The name has
|
|
2670
|
+
; to match WHOLE: `translateX(4px)` is not a `translate`, and accepting it
|
|
2671
|
+
; as one silently drops the X.
|
|
2672
|
+
sfn callArgs:string (one:string name:string) {
|
|
2673
|
+
def nl2:int (strlen name)
|
|
2674
|
+
def tl:int (strlen one)
|
|
2675
|
+
if (tl < (nl2 + 3)) {
|
|
2676
|
+
return ""
|
|
2677
|
+
}
|
|
2678
|
+
if ((substring one 0 nl2) != name) {
|
|
2679
|
+
return ""
|
|
2680
|
+
}
|
|
2681
|
+
if ((charAt one nl2) != 40) {
|
|
2682
|
+
return ""
|
|
2683
|
+
}
|
|
2684
|
+
if ((charAt one (tl - 1)) != 41) {
|
|
2685
|
+
return ""
|
|
2686
|
+
}
|
|
2687
|
+
def inner:string (substring one (nl2 + 1) (tl - 1))
|
|
2688
|
+
if ((strlen inner) == 0) {
|
|
2689
|
+
return ""
|
|
2690
|
+
}
|
|
2691
|
+
return inner
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
; Comma-separated numbers with a unit suffix that is ignored: everything
|
|
2695
|
+
; here is already in device pixels and a percentage would need the box.
|
|
2696
|
+
sfn numberList:[double] (s:string) {
|
|
2697
|
+
def out:[double]
|
|
2698
|
+
def parts:[string] (EVGElement.splitOnChar(s 44))
|
|
2699
|
+
def i:int 0
|
|
2700
|
+
while (i < (array_length parts)) {
|
|
2701
|
+
push out (EVGElement.leadingNumber((itemAt parts i)))
|
|
2702
|
+
i = (i + 1)
|
|
2703
|
+
}
|
|
2704
|
+
return out
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
sfn splitOnChar:[string] (s:string ch:int) {
|
|
2708
|
+
def out:[string]
|
|
2709
|
+
def start:int 0
|
|
2710
|
+
def i:int 0
|
|
2711
|
+
def n:int (strlen s)
|
|
2712
|
+
while (i < n) {
|
|
2713
|
+
if ((charAt s i) == ch) {
|
|
2714
|
+
push out (substring s start i)
|
|
2715
|
+
start = (i + 1)
|
|
2716
|
+
}
|
|
2717
|
+
i = (i + 1)
|
|
2718
|
+
}
|
|
2719
|
+
push out (substring s start n)
|
|
2720
|
+
return out
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
; The number at the front, unit discarded. `4px` and `4` are the same
|
|
2724
|
+
; length here; a bare `0` is legal CSS and has to parse.
|
|
2725
|
+
sfn leadingNumber:double (s:string) {
|
|
2726
|
+
def t:string (trim s)
|
|
2727
|
+
def n:int (strlen t)
|
|
2728
|
+
def stop:int 0
|
|
2729
|
+
def scanning:boolean true
|
|
2730
|
+
while (scanning && (stop < n)) {
|
|
2731
|
+
def c:int (charAt t stop)
|
|
2732
|
+
def digit:boolean ((c >= 48) && (c <= 57))
|
|
2733
|
+
def signOrDot:boolean (((c == 45) || (c == 43)) || (c == 46))
|
|
2734
|
+
if (digit || signOrDot) {
|
|
2735
|
+
stop = (stop + 1)
|
|
2736
|
+
} {
|
|
2737
|
+
scanning = false
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
if (stop == 0) {
|
|
2741
|
+
return 0.0
|
|
2742
|
+
}
|
|
2743
|
+
def v@(optional):double (to_double (substring t 0 stop))
|
|
2744
|
+
if v {
|
|
2745
|
+
return (unwrap v)
|
|
2746
|
+
}
|
|
2747
|
+
return 0.0
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
; deg, rad, turn and grad, because a stylesheet that says `0.25turn` means
|
|
2751
|
+
; a quarter circle and reading it as a quarter of a degree is a bug nobody
|
|
2752
|
+
; would think to look for. `turn` and `grad` are checked BEFORE `rad`: both
|
|
2753
|
+
; of them end in those three letters.
|
|
2754
|
+
sfn parseAngleDeg:double (text:string) {
|
|
2755
|
+
def t:string (trim text)
|
|
2756
|
+
def v:double (EVGElement.leadingNumber(t))
|
|
2757
|
+
def n:int (strlen t)
|
|
2758
|
+
if (n > 4) {
|
|
2759
|
+
def four:string (substring t (n - 4) n)
|
|
2760
|
+
if (four == "turn") {
|
|
2761
|
+
return (v * 360.0)
|
|
2762
|
+
}
|
|
2763
|
+
if (four == "grad") {
|
|
2764
|
+
return ((v * 360.0) / 400.0)
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
if (n > 3) {
|
|
2768
|
+
def three:string (substring t (n - 3) n)
|
|
2769
|
+
if (three == "rad") {
|
|
2770
|
+
return ((v * 180.0) / 3.14159265358979)
|
|
2771
|
+
}
|
|
2772
|
+
if (three == "deg") {
|
|
2773
|
+
return v
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
return v
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
; Split a CSS value on whitespace, but never inside parentheses —
|
|
2780
|
+
; `rgb(203, 213, 225)` is ONE token, and splitting it would hand the
|
|
2781
|
+
; border shorthand three pieces of nonsense.
|
|
2782
|
+
; CSS box shorthands take one to four values, and EVG used to read the
|
|
2783
|
+
; whole declaration as ONE unit: `padding: 0 16px` put nothing on the sides
|
|
2784
|
+
; at all, because the parser saw the leading `0` and applied it four times.
|
|
2785
|
+
; Every two-, three- and four-value shorthand in the gallery had been wrong
|
|
2786
|
+
; since the sheets were written, which nobody noticed because a padding
|
|
2787
|
+
; that is too small looks like a layout someone chose.
|
|
2788
|
+
;
|
|
2789
|
+
; The mapping and the failure rules are measured in `oracle/css-box.json`:
|
|
2790
|
+
; one value is all four sides, two are vertical then horizontal, three are
|
|
2791
|
+
; top/horizontal/bottom, four are top/right/bottom/left. Anything else —
|
|
2792
|
+
; five values, a component that is not a length, a negative PADDING — drops
|
|
2793
|
+
; the whole declaration rather than the offending component, so the element
|
|
2794
|
+
; keeps whatever it had. An empty result here means exactly that.
|
|
2795
|
+
sfn boxSides:[EVGUnit] (value:string isMargin:boolean) {
|
|
2796
|
+
def out:[EVGUnit]
|
|
2797
|
+
def words:[string] (EVGElement.splitWords(value))
|
|
2798
|
+
def n:int (array_length words)
|
|
2799
|
+
if (n < 1) {
|
|
2800
|
+
return out
|
|
2801
|
+
}
|
|
2802
|
+
if (n > 4) {
|
|
2803
|
+
return out
|
|
2804
|
+
}
|
|
2805
|
+
def parts:[EVGUnit]
|
|
2806
|
+
def i:int 0
|
|
2807
|
+
while (i < n) {
|
|
2808
|
+
def w:string (itemAt words i)
|
|
2809
|
+
def u:EVGUnit (EVGUnit.parse(w))
|
|
2810
|
+
if (u.isSet == false) {
|
|
2811
|
+
; `auto` is the one word that legally parses to no length, and
|
|
2812
|
+
; only on a margin. EVG has no auto-margin centring, so it lands
|
|
2813
|
+
; where an unset margin lands: at zero.
|
|
2814
|
+
if (isMargin == false) {
|
|
2815
|
+
return out
|
|
2816
|
+
}
|
|
2817
|
+
if (w != "auto") {
|
|
2818
|
+
return out
|
|
2819
|
+
}
|
|
2820
|
+
} {
|
|
2821
|
+
if (u.value < 0.0) {
|
|
2822
|
+
if (isMargin == false) {
|
|
2823
|
+
return out
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
push parts u
|
|
2828
|
+
i = (i + 1)
|
|
2829
|
+
}
|
|
2830
|
+
def top:EVGUnit (itemAt parts 0)
|
|
2831
|
+
def right:EVGUnit top
|
|
2832
|
+
def bottom:EVGUnit top
|
|
2833
|
+
def left:EVGUnit top
|
|
2834
|
+
if (n > 1) {
|
|
2835
|
+
right = (itemAt parts 1)
|
|
2836
|
+
left = right
|
|
2837
|
+
}
|
|
2838
|
+
if (n > 2) {
|
|
2839
|
+
bottom = (itemAt parts 2)
|
|
2840
|
+
}
|
|
2841
|
+
if (n > 3) {
|
|
2842
|
+
left = (itemAt parts 3)
|
|
2843
|
+
}
|
|
2844
|
+
push out top
|
|
2845
|
+
push out right
|
|
2846
|
+
push out bottom
|
|
2847
|
+
push out left
|
|
2848
|
+
return out
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
; The radius out of `blur(12px)`, or 0 for `none` and for anything this
|
|
2852
|
+
; does not understand. Called by `setStyle`, and separately by the style
|
|
2853
|
+
; sheet's validator so an unsupported filter function is REPORTED — see
|
|
2854
|
+
; `blurProblem`.
|
|
2855
|
+
; "x y w h", four numbers or four percentages, into the element's four
|
|
2856
|
+
; fractions. Anything else leaves them alone and says so, so a typo is a
|
|
2857
|
+
; picture drawn whole rather than a picture drawn nowhere.
|
|
2858
|
+
sfn parseViewBox:boolean (value:string el:EVGElement) {
|
|
2859
|
+
def v:string (trim value)
|
|
2860
|
+
if ((strlen v) == 0) {
|
|
2861
|
+
return false
|
|
2862
|
+
}
|
|
2863
|
+
def words:[string] (EVGElement.splitWords(v))
|
|
2864
|
+
if ((array_length words) != 4) {
|
|
2865
|
+
return false
|
|
2866
|
+
}
|
|
2867
|
+
def got:[double]
|
|
2868
|
+
def i:int 0
|
|
2869
|
+
while (i < 4) {
|
|
2870
|
+
def w:string (itemAt words i)
|
|
2871
|
+
def u:EVGUnit (EVGUnit.parse(w))
|
|
2872
|
+
if (u.isSet == false) {
|
|
2873
|
+
return false
|
|
2874
|
+
}
|
|
2875
|
+
def n:double u.value
|
|
2876
|
+
if (u.isPercent()) {
|
|
2877
|
+
n = (n / 100.0)
|
|
2878
|
+
}
|
|
2879
|
+
push got n
|
|
2880
|
+
i = (i + 1)
|
|
2881
|
+
}
|
|
2882
|
+
def cw:double (itemAt got 2)
|
|
2883
|
+
def ch:double (itemAt got 3)
|
|
2884
|
+
; A window with no width shows nothing; that is a typo, not a crop.
|
|
2885
|
+
if ((fabs cw) < 0.000001) {
|
|
2886
|
+
return false
|
|
2887
|
+
}
|
|
2888
|
+
if ((fabs ch) < 0.000001) {
|
|
2889
|
+
return false
|
|
2890
|
+
}
|
|
2891
|
+
el.imageViewBoxX = (itemAt got 0)
|
|
2892
|
+
el.imageViewBoxY = (itemAt got 1)
|
|
2893
|
+
el.imageViewBoxW = cw
|
|
2894
|
+
el.imageViewBoxH = ch
|
|
2895
|
+
return true
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
sfn blurRadiusOf:double (value:string) {
|
|
2899
|
+
def v:string (trim value)
|
|
2900
|
+
if (v == "none") {
|
|
2901
|
+
return 0.0
|
|
2902
|
+
}
|
|
2903
|
+
if ((strlen v) == 0) {
|
|
2904
|
+
return 0.0
|
|
2905
|
+
}
|
|
2906
|
+
def words:[string] (EVGElement.splitWords(v))
|
|
2907
|
+
; One function, and it has to be blur(). A list of filters is legal CSS
|
|
2908
|
+
; and is not supported here; saying so is the point of returning 0 and
|
|
2909
|
+
; letting `blurProblem` name it.
|
|
2910
|
+
if ((array_length words) != 1) {
|
|
2911
|
+
return 0.0
|
|
2912
|
+
}
|
|
2913
|
+
def one:string (itemAt words 0)
|
|
2914
|
+
def inner:string (EVGElement.callArgs(one "blur"))
|
|
2915
|
+
if ((strlen inner) == 0) {
|
|
2916
|
+
return 0.0
|
|
2917
|
+
}
|
|
2918
|
+
def u:EVGUnit (EVGUnit.parse(inner))
|
|
2919
|
+
if (u.isSet == false) {
|
|
2920
|
+
return 0.0
|
|
2921
|
+
}
|
|
2922
|
+
; A negative radius is not a filter, it is a typo.
|
|
2923
|
+
if (u.value < 0.0) {
|
|
2924
|
+
return 0.0
|
|
2925
|
+
}
|
|
2926
|
+
; Only absolute lengths: a percentage has nothing to resolve against
|
|
2927
|
+
; here, since a blur radius is not a fraction of anything.
|
|
2928
|
+
if (u.unitType != 0) {
|
|
2929
|
+
return 0.0
|
|
2930
|
+
}
|
|
2931
|
+
return u.pixels
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
; Why a `backdrop-filter` did nothing, or "" when it is fine. The sheet
|
|
2935
|
+
; reports this the way it reports an unknown timing function: a filter that
|
|
2936
|
+
; is quietly ignored looks exactly like a backend that has not caught up.
|
|
2937
|
+
sfn blurProblem:string (value:string) {
|
|
2938
|
+
def v:string (trim value)
|
|
2939
|
+
if (v == "none") {
|
|
2940
|
+
return ""
|
|
2941
|
+
}
|
|
2942
|
+
if ((strlen v) == 0) {
|
|
2943
|
+
return ""
|
|
2944
|
+
}
|
|
2945
|
+
def words:[string] (EVGElement.splitWords(v))
|
|
2946
|
+
if ((array_length words) != 1) {
|
|
2947
|
+
return ("backdrop-filter: only a single blur() is supported, got '" + v + "'")
|
|
2948
|
+
}
|
|
2949
|
+
def one:string (itemAt words 0)
|
|
2950
|
+
def inner:string (EVGElement.callArgs(one "blur"))
|
|
2951
|
+
if ((strlen inner) == 0) {
|
|
2952
|
+
return ("backdrop-filter: only blur() is supported, got '" + v + "'")
|
|
2953
|
+
}
|
|
2954
|
+
def u:EVGUnit (EVGUnit.parse(inner))
|
|
2955
|
+
if (u.isSet == false) {
|
|
2956
|
+
return ("backdrop-filter: '" + inner + "' is not a length")
|
|
2957
|
+
}
|
|
2958
|
+
if (u.value < 0.0) {
|
|
2959
|
+
return ("backdrop-filter: a blur radius cannot be negative")
|
|
2960
|
+
}
|
|
2961
|
+
if (u.unitType != 0) {
|
|
2962
|
+
return ("backdrop-filter: blur() needs an absolute length, got '" + inner + "'")
|
|
2963
|
+
}
|
|
2964
|
+
return ""
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
sfn splitWords:[string] (s:string) {
|
|
2968
|
+
def out:[string]
|
|
2969
|
+
def cur:string ""
|
|
2970
|
+
def depth:int 0
|
|
2971
|
+
def i:int 0
|
|
2972
|
+
def len:int (strlen s)
|
|
2973
|
+
while (i < len) {
|
|
2974
|
+
def c:int (charAt s i)
|
|
2975
|
+
if (c == 40) {
|
|
2976
|
+
depth = (depth + 1)
|
|
2977
|
+
}
|
|
2978
|
+
if (c == 41) {
|
|
2979
|
+
depth = (depth - 1)
|
|
2980
|
+
}
|
|
2981
|
+
def isSpace:boolean false
|
|
2982
|
+
if (depth == 0) {
|
|
2983
|
+
if (c == 32) {
|
|
2984
|
+
isSpace = true
|
|
2985
|
+
}
|
|
2986
|
+
if (c == 9) {
|
|
2987
|
+
isSpace = true
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
if isSpace {
|
|
2991
|
+
if ((strlen cur) > 0) {
|
|
2992
|
+
push out cur
|
|
2993
|
+
cur = ""
|
|
2994
|
+
}
|
|
2995
|
+
} {
|
|
2996
|
+
cur = cur + (strfromcode c)
|
|
2997
|
+
}
|
|
2998
|
+
i = (i + 1)
|
|
2999
|
+
}
|
|
3000
|
+
if ((strlen cur) > 0) {
|
|
3001
|
+
push out cur
|
|
3002
|
+
}
|
|
3003
|
+
return out
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
sfn isBorderStyleWord:boolean (tok:string) {
|
|
3007
|
+
if (tok == "solid") {
|
|
3008
|
+
return true
|
|
3009
|
+
}
|
|
3010
|
+
if (tok == "dashed") {
|
|
3011
|
+
return true
|
|
3012
|
+
}
|
|
3013
|
+
if (tok == "dotted") {
|
|
3014
|
+
return true
|
|
3015
|
+
}
|
|
3016
|
+
if (tok == "double") {
|
|
3017
|
+
return true
|
|
3018
|
+
}
|
|
3019
|
+
if (tok == "none") {
|
|
3020
|
+
return true
|
|
3021
|
+
}
|
|
3022
|
+
if (tok == "hidden") {
|
|
3023
|
+
return true
|
|
3024
|
+
}
|
|
3025
|
+
return false
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
; A width starts with a digit or a dot; anything else in the shorthand that
|
|
3029
|
+
; is not a style keyword is the colour — `#abc`, `rgb(...)` or a name.
|
|
3030
|
+
; A bare number, with a `px` or other unit allowed and ignored: these are
|
|
3031
|
+
; EVG's own properties and they are all in page pixels or in per-second,
|
|
3032
|
+
; so there is no unit to resolve — `28` and `28px` mean the same thing and
|
|
3033
|
+
; both are things somebody will write.
|
|
3034
|
+
sfn numberOr:double (value:string fallback:double) {
|
|
3035
|
+
def digits:string ""
|
|
3036
|
+
def i:int 0
|
|
3037
|
+
while (i < (strlen value)) {
|
|
3038
|
+
; `charAt` gives a CODEPOINT, not a one-character string, so the
|
|
3039
|
+
; comparison is against numbers: 48..57 are the digits, 46 is the
|
|
3040
|
+
; point and 45 the minus.
|
|
3041
|
+
def code:int (charAt value i)
|
|
3042
|
+
def keep:boolean ((code >= 48) && (code <= 57))
|
|
3043
|
+
if (code == 46) { keep = true }
|
|
3044
|
+
if (code == 45) { keep = true }
|
|
3045
|
+
if keep {
|
|
3046
|
+
digits = (digits + (substring value i (i + 1)))
|
|
3047
|
+
}
|
|
3048
|
+
i = (i + 1)
|
|
3049
|
+
}
|
|
3050
|
+
if ((strlen digits) == 0) {
|
|
3051
|
+
return fallback
|
|
3052
|
+
}
|
|
3053
|
+
def v@(optional):double (to_double digits)
|
|
3054
|
+
if v {
|
|
3055
|
+
return (unwrap v)
|
|
3056
|
+
}
|
|
3057
|
+
return fallback
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
sfn looksLikeColor:boolean (tok:string) {
|
|
3061
|
+
if ((strlen tok) == 0) {
|
|
3062
|
+
return false
|
|
3063
|
+
}
|
|
3064
|
+
def c:int (charAt tok 0)
|
|
3065
|
+
if ((c >= 48) && (c <= 57)) {
|
|
3066
|
+
return false
|
|
3067
|
+
}
|
|
3068
|
+
if (c == 46) {
|
|
3069
|
+
return false
|
|
3070
|
+
}
|
|
3071
|
+
return true
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
; Record that the authoring layer set `name` directly on this element.
|
|
3075
|
+
; The local is `prop` and not `key`: `key` is a field of this class now,
|
|
3076
|
+
; and a local shadowing it here would be a rename waiting to go wrong.
|
|
3077
|
+
fn markCss:void (name:string) {
|
|
3078
|
+
def prop:string (EVGElement.toKebab(name))
|
|
3079
|
+
if ((this.fromCss(prop)) == false) {
|
|
3080
|
+
push cssProps prop
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
fn fromCss:boolean (name:string) {
|
|
3085
|
+
if ((array_length cssProps) == 0) {
|
|
3086
|
+
return false
|
|
3087
|
+
}
|
|
3088
|
+
def prop:string (EVGElement.toKebab(name))
|
|
3089
|
+
def i:int 0
|
|
3090
|
+
while (i < (array_length cssProps)) {
|
|
3091
|
+
if ((itemAt cssProps i) == prop) {
|
|
3092
|
+
return true
|
|
3093
|
+
}
|
|
3094
|
+
i = (i + 1)
|
|
3095
|
+
}
|
|
3096
|
+
return false
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
fn clearCssMarks:void () {
|
|
3100
|
+
def empty:[string]
|
|
3101
|
+
cssProps = empty
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
fn markInline:void (name:string) {
|
|
3105
|
+
def prop:string (EVGElement.toKebab(name))
|
|
3106
|
+
if ((this.hasInline(prop)) == false) {
|
|
3107
|
+
push inlineProps prop
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
; Forget that the authoring layer set `name` directly.
|
|
3112
|
+
;
|
|
3113
|
+
; `markInline` had no inverse, which was fine while the only writer was a
|
|
3114
|
+
; parser reading a document once. An edit that is undone needs one: putting
|
|
3115
|
+
; the value back without clearing this leaves the property outranking every
|
|
3116
|
+
; stylesheet rule, so the tree would look restored and cascade differently.
|
|
3117
|
+
fn unmarkInline:void (name:string) {
|
|
3118
|
+
def prop:string (EVGElement.toKebab(name))
|
|
3119
|
+
def kept:[string]
|
|
3120
|
+
def i:int 0
|
|
3121
|
+
while (i < (array_length inlineProps)) {
|
|
3122
|
+
def cur:string (itemAt inlineProps i)
|
|
3123
|
+
if (cur != prop) {
|
|
3124
|
+
push kept cur
|
|
3125
|
+
}
|
|
3126
|
+
i = (i + 1)
|
|
3127
|
+
}
|
|
3128
|
+
inlineProps = kept
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
fn hasInline:boolean (name:string) {
|
|
3132
|
+
; The overwhelmingly common answer, reached without converting the
|
|
3133
|
+
; name. The stylesheet asks this once per declaration per element per
|
|
3134
|
+
; frame — about 180,000 times on a 1600-row table — and almost every
|
|
3135
|
+
; element has nothing inline at all, so paying for a kebab-case
|
|
3136
|
+
; conversion in order to look in an empty list was most of what it cost.
|
|
3137
|
+
if ((array_length inlineProps) == 0) {
|
|
3138
|
+
return false
|
|
3139
|
+
}
|
|
3140
|
+
def prop:string (EVGElement.toKebab(name))
|
|
3141
|
+
def i:int 0
|
|
3142
|
+
while (i < (array_length inlineProps)) {
|
|
3143
|
+
if ((itemAt inlineProps i) == prop) {
|
|
3144
|
+
return true
|
|
3145
|
+
}
|
|
3146
|
+
i = (i + 1)
|
|
3147
|
+
}
|
|
3148
|
+
return false
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
; CSS `flex` shorthand: "1", "1 1 auto", "2 0 120px".
|
|
3152
|
+
; one number -> grow, and basis 0 (CSS uses 0%, not auto)
|
|
3153
|
+
; number number -> grow, shrink
|
|
3154
|
+
; n n <length> -> grow, shrink, basis
|
|
3155
|
+
; A single non-numeric value is treated as a basis, matching `flex: auto`
|
|
3156
|
+
; and `flex: 120px`.
|
|
3157
|
+
fn setFlexShorthand:void (value:string) {
|
|
3158
|
+
def parts:[string] (EVGElement.splitSpaces(value))
|
|
3159
|
+
def n:int (array_length parts)
|
|
3160
|
+
if (n == 0) {
|
|
3161
|
+
return
|
|
3162
|
+
}
|
|
3163
|
+
def first:string (itemAt parts 0)
|
|
3164
|
+
; to_double("150px") happily returns 150, so a bare unit value would be
|
|
3165
|
+
; read as a grow factor. Only a token that is entirely numeric is one.
|
|
3166
|
+
def growVal@(optional):double (to_double first)
|
|
3167
|
+
if (EVGElement.isPlainNumber(first)) {
|
|
3168
|
+
flex = (unwrap growVal)
|
|
3169
|
+
; A bare `flex: N` starts from zero, so items share the line in
|
|
3170
|
+
; proportion to N regardless of any width they also carry.
|
|
3171
|
+
flexBasis = (EVGUnit.px(0.0))
|
|
3172
|
+
if (n >= 2) {
|
|
3173
|
+
def shrinkVal@(optional):double (to_double (itemAt parts 1))
|
|
3174
|
+
if shrinkVal {
|
|
3175
|
+
flexShrink = (unwrap shrinkVal)
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
if (n >= 3) {
|
|
3179
|
+
flexBasis = (EVGUnit.parse((itemAt parts 2)))
|
|
3180
|
+
}
|
|
3181
|
+
} {
|
|
3182
|
+
; `flex: auto` / `flex: 120px` — a basis with default grow/shrink.
|
|
3183
|
+
flexBasis = (EVGUnit.parse(first))
|
|
3184
|
+
flex = 1.0
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
sfn isPlainNumber:boolean (s:string) {
|
|
3189
|
+
def len:int (strlen s)
|
|
3190
|
+
if (len == 0) {
|
|
3191
|
+
return false
|
|
3192
|
+
}
|
|
3193
|
+
def digits:int 0
|
|
3194
|
+
def i:int 0
|
|
3195
|
+
while (i < len) {
|
|
3196
|
+
def c:int (charAt s i)
|
|
3197
|
+
def isDigit:boolean ((c >= 48) && (c <= 57))
|
|
3198
|
+
if isDigit {
|
|
3199
|
+
digits = (digits + 1)
|
|
3200
|
+
} {
|
|
3201
|
+
; '.', '-' and '+' may appear; anything else means a unit.
|
|
3202
|
+
if (((c != 46) && (c != 45)) && (c != 43)) {
|
|
3203
|
+
return false
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
i = (i + 1)
|
|
3207
|
+
}
|
|
3208
|
+
return (digits > 0)
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
sfn splitSpaces:[string] (s:string) {
|
|
3212
|
+
def out:[string]
|
|
3213
|
+
def len:int (strlen s)
|
|
3214
|
+
def start:int 0
|
|
3215
|
+
def inTok:boolean false
|
|
3216
|
+
def i:int 0
|
|
3217
|
+
while (i < len) {
|
|
3218
|
+
def c:int (charAt s i)
|
|
3219
|
+
def isSpace:boolean (((c == 32) || (c == 9)) || ((c == 10) || (c == 13)))
|
|
3220
|
+
if isSpace {
|
|
3221
|
+
if inTok {
|
|
3222
|
+
push out (substring s start i)
|
|
3223
|
+
inTok = false
|
|
3224
|
+
}
|
|
3225
|
+
} {
|
|
3226
|
+
if (inTok == false) {
|
|
3227
|
+
start = i
|
|
3228
|
+
inTok = true
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
i = (i + 1)
|
|
3232
|
+
}
|
|
3233
|
+
if inTok {
|
|
3234
|
+
push out (substring s start len)
|
|
3235
|
+
}
|
|
3236
|
+
return out
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
; Take on everything `other` describes, keeping this element's IDENTITY.
|
|
3240
|
+
; (The parameter is `other` and not `src` because `src` is already a field
|
|
3241
|
+
; of this class — the image source.)
|
|
3242
|
+
;
|
|
3243
|
+
; Every field of EVGElement is copied EXCEPT three:
|
|
3244
|
+
;
|
|
3245
|
+
; `parent` and `children` — structure, which `EVGReconcile` owns. Copying
|
|
3246
|
+
; them here would splice the discarded tree back in.
|
|
3247
|
+
;
|
|
3248
|
+
; `transitions` — the in-flight animations, which are the whole reason
|
|
3249
|
+
; this element is being kept rather than replaced. A flight remembers its
|
|
3250
|
+
; own target and what it last wrote (`EVGFlight.hasWrote`), so preserving
|
|
3251
|
+
; the list and overwriting everything else is exactly right: the sheet
|
|
3252
|
+
; re-declares the destination, and `EVGTransition.reconcile` compares the
|
|
3253
|
+
; two and either continues, redirects or lands.
|
|
3254
|
+
;
|
|
3255
|
+
; Object-typed fields are shared, not cloned. That is safe because `other`
|
|
3256
|
+
; is a node of a tree being DISCARDED — it has exactly one live holder after
|
|
3257
|
+
; this returns — and it is why this must never be used to copy one live
|
|
3258
|
+
; element onto another.
|
|
3259
|
+
;
|
|
3260
|
+
; A field added to this class and not added here would be silently dropped
|
|
3261
|
+
; on every rebuild, which is a bug that shows up as "it works until you
|
|
3262
|
+
; interact with it". `scripts/check-evg-adopt.mjs` fails the build when the
|
|
3263
|
+
; two lists disagree, so this cannot rot quietly.
|
|
3264
|
+
fn adoptFrom:void (other:EVGElement) {
|
|
3265
|
+
id = other.id
|
|
3266
|
+
key = other.key
|
|
3267
|
+
; The link a subtree sits inside. A reconciled element that loses it
|
|
3268
|
+
; stops being a link on the next rebuild — and in a PDF, where the
|
|
3269
|
+
; href is an annotation rather than blue ink, there is nothing left on
|
|
3270
|
+
; the page to say it ever was one.
|
|
3271
|
+
href = other.href
|
|
3272
|
+
tagName = other.tagName
|
|
3273
|
+
elementType = other.elementType
|
|
3274
|
+
format = other.format
|
|
3275
|
+
orientation = other.orientation
|
|
3276
|
+
pageWidth = other.pageWidth
|
|
3277
|
+
pageHeight = other.pageHeight
|
|
3278
|
+
width = other.width
|
|
3279
|
+
height = other.height
|
|
3280
|
+
minWidth = other.minWidth
|
|
3281
|
+
minHeight = other.minHeight
|
|
3282
|
+
maxWidth = other.maxWidth
|
|
3283
|
+
maxHeight = other.maxHeight
|
|
3284
|
+
left = other.left
|
|
3285
|
+
top = other.top
|
|
3286
|
+
right = other.right
|
|
3287
|
+
bottom = other.bottom
|
|
3288
|
+
x = other.x
|
|
3289
|
+
y = other.y
|
|
3290
|
+
box = other.box
|
|
3291
|
+
backgroundColor = other.backgroundColor
|
|
3292
|
+
opacity = other.opacity
|
|
3293
|
+
backdropBlur = other.backdropBlur
|
|
3294
|
+
gradientSet = other.gradientSet
|
|
3295
|
+
gradientFrom = other.gradientFrom
|
|
3296
|
+
gradientTo = other.gradientTo
|
|
3297
|
+
gradientDir = other.gradientDir
|
|
3298
|
+
absPosSet = other.absPosSet
|
|
3299
|
+
absX = other.absX
|
|
3300
|
+
absY = other.absY
|
|
3301
|
+
glowIntensity = other.glowIntensity
|
|
3302
|
+
bgImageSet = other.bgImageSet
|
|
3303
|
+
bgImagePath = other.bgImagePath
|
|
3304
|
+
textDir = other.textDir
|
|
3305
|
+
resolvedRtl = other.resolvedRtl
|
|
3306
|
+
direction = other.direction
|
|
3307
|
+
align = other.align
|
|
3308
|
+
verticalAlign = other.verticalAlign
|
|
3309
|
+
isInline = other.isInline
|
|
3310
|
+
lineBreak = other.lineBreak
|
|
3311
|
+
overflow = other.overflow
|
|
3312
|
+
cursor = other.cursor
|
|
3313
|
+
surfaceEffect = other.surfaceEffect
|
|
3314
|
+
rippleSpeed = other.rippleSpeed
|
|
3315
|
+
rippleWidth = other.rippleWidth
|
|
3316
|
+
rippleStrength = other.rippleStrength
|
|
3317
|
+
rippleDecay = other.rippleDecay
|
|
3318
|
+
rippleHighlight = other.rippleHighlight
|
|
3319
|
+
rippleRings = other.rippleRings
|
|
3320
|
+
rippleStagger = other.rippleStagger
|
|
3321
|
+
rippleFalloff = other.rippleFalloff
|
|
3322
|
+
rippleShine = other.rippleShine
|
|
3323
|
+
rippleGloss = other.rippleGloss
|
|
3324
|
+
rippleBump = other.rippleBump
|
|
3325
|
+
rippleLightX = other.rippleLightX
|
|
3326
|
+
rippleLightY = other.rippleLightY
|
|
3327
|
+
rippleLightZ = other.rippleLightZ
|
|
3328
|
+
rippleXs = other.rippleXs
|
|
3329
|
+
rippleYs = other.rippleYs
|
|
3330
|
+
rippleAges = other.rippleAges
|
|
3331
|
+
effectTrigger = other.effectTrigger
|
|
3332
|
+
fxNames = other.fxNames
|
|
3333
|
+
fxValues = other.fxValues
|
|
3334
|
+
documentCss = other.documentCss
|
|
3335
|
+
; The marks go with the values. An element that took another's
|
|
3336
|
+
; cascaded properties and not the record of where they came from is
|
|
3337
|
+
; an element that writes the whole stylesheet into itself the next
|
|
3338
|
+
; time the tree is serialized.
|
|
3339
|
+
cssProps = other.cssProps
|
|
3340
|
+
scrollTop = other.scrollTop
|
|
3341
|
+
scrollLeft = other.scrollLeft
|
|
3342
|
+
scrollWidth = other.scrollWidth
|
|
3343
|
+
scrollHeight = other.scrollHeight
|
|
3344
|
+
fontSize = other.fontSize
|
|
3345
|
+
fontSizeInherited = other.fontSizeInherited
|
|
3346
|
+
fontSizeBase = other.fontSizeBase
|
|
3347
|
+
rootFontSize = other.rootFontSize
|
|
3348
|
+
viewportW = other.viewportW
|
|
3349
|
+
viewportH = other.viewportH
|
|
3350
|
+
viewportRoot = other.viewportRoot
|
|
3351
|
+
viewportX = other.viewportX
|
|
3352
|
+
viewportY = other.viewportY
|
|
3353
|
+
fontFamily = other.fontFamily
|
|
3354
|
+
fontWeight = other.fontWeight
|
|
3355
|
+
letterSpacing = other.letterSpacing
|
|
3356
|
+
paragraphSpacing = other.paragraphSpacing
|
|
3357
|
+
strokeLineCap = other.strokeLineCap
|
|
3358
|
+
strokeLineJoin = other.strokeLineJoin
|
|
3359
|
+
lineHeight = other.lineHeight
|
|
3360
|
+
lineHeightUnit = other.lineHeightUnit
|
|
3361
|
+
textAlign = other.textAlign
|
|
3362
|
+
; Written by the layout, not by the author — but adopted with the
|
|
3363
|
+
; rest, because a reconciled element that lost it would draw its own
|
|
3364
|
+
; text against the top of a box it was centred in until something
|
|
3365
|
+
; forced a fresh layout.
|
|
3366
|
+
textShiftY = other.textShiftY
|
|
3367
|
+
whiteSpace = other.whiteSpace
|
|
3368
|
+
color = other.color
|
|
3369
|
+
emojiColor = other.emojiColor
|
|
3370
|
+
textContent = other.textContent
|
|
3371
|
+
display = other.display
|
|
3372
|
+
flex = other.flex
|
|
3373
|
+
flexShrink = other.flexShrink
|
|
3374
|
+
flexBasis = other.flexBasis
|
|
3375
|
+
flexDirection = other.flexDirection
|
|
3376
|
+
justifyContent = other.justifyContent
|
|
3377
|
+
alignItems = other.alignItems
|
|
3378
|
+
alignSelf = other.alignSelf
|
|
3379
|
+
alignContent = other.alignContent
|
|
3380
|
+
flexWrap = other.flexWrap
|
|
3381
|
+
gap = other.gap
|
|
3382
|
+
rowGap = other.rowGap
|
|
3383
|
+
columnGap = other.columnGap
|
|
3384
|
+
gridTemplateColumns = other.gridTemplateColumns
|
|
3385
|
+
gridTemplateRows = other.gridTemplateRows
|
|
3386
|
+
subgridColumnSizes = other.subgridColumnSizes
|
|
3387
|
+
subgridRowSizes = other.subgridRowSizes
|
|
3388
|
+
computedRowSizes = other.computedRowSizes
|
|
3389
|
+
subgridPending = other.subgridPending
|
|
3390
|
+
gridTemplateAreas = other.gridTemplateAreas
|
|
3391
|
+
gridAutoFlow = other.gridAutoFlow
|
|
3392
|
+
fullBleed = other.fullBleed
|
|
3393
|
+
gridArea = other.gridArea
|
|
3394
|
+
gridColumn = other.gridColumn
|
|
3395
|
+
gridRow = other.gridRow
|
|
3396
|
+
position = other.position
|
|
3397
|
+
marginTop = other.marginTop
|
|
3398
|
+
marginRight = other.marginRight
|
|
3399
|
+
marginBottom = other.marginBottom
|
|
3400
|
+
marginLeft = other.marginLeft
|
|
3401
|
+
paddingTop = other.paddingTop
|
|
3402
|
+
paddingRight = other.paddingRight
|
|
3403
|
+
paddingBottom = other.paddingBottom
|
|
3404
|
+
paddingLeft = other.paddingLeft
|
|
3405
|
+
borderWidth = other.borderWidth
|
|
3406
|
+
borderTopWidth = other.borderTopWidth
|
|
3407
|
+
borderRightWidth = other.borderRightWidth
|
|
3408
|
+
borderBottomWidth = other.borderBottomWidth
|
|
3409
|
+
borderLeftWidth = other.borderLeftWidth
|
|
3410
|
+
borderColor = other.borderColor
|
|
3411
|
+
src = other.src
|
|
3412
|
+
alt = other.alt
|
|
3413
|
+
preserveAspectRatio = other.preserveAspectRatio
|
|
3414
|
+
imageViewBox = other.imageViewBox
|
|
3415
|
+
imageViewBoxX = other.imageViewBoxX
|
|
3416
|
+
imageViewBoxY = other.imageViewBoxY
|
|
3417
|
+
imageViewBoxW = other.imageViewBoxW
|
|
3418
|
+
imageViewBoxH = other.imageViewBoxH
|
|
3419
|
+
imageViewBoxSet = other.imageViewBoxSet
|
|
3420
|
+
imageOffsetX = other.imageOffsetX
|
|
3421
|
+
imageOffsetY = other.imageOffsetY
|
|
3422
|
+
objectFit = other.objectFit
|
|
3423
|
+
sourceWidth = other.sourceWidth
|
|
3424
|
+
sourceHeight = other.sourceHeight
|
|
3425
|
+
svgPath = other.svgPath
|
|
3426
|
+
svgSource = other.svgSource
|
|
3427
|
+
svgDoc = other.svgDoc
|
|
3428
|
+
svgDocKey = other.svgDocKey
|
|
3429
|
+
; What the layout wrote and what it keeps — see `keepLayout` — come
|
|
3430
|
+
; from the fresh element, which is to say they are reset: a reconciled
|
|
3431
|
+
; element is laid out, measured and drawn again. `paintStamp` is this
|
|
3432
|
+
; element's own count of paint changes, and adopting is one.
|
|
3433
|
+
appliedScrollTop = other.appliedScrollTop
|
|
3434
|
+
appliedScrollLeft = other.appliedScrollLeft
|
|
3435
|
+
paintLeft = other.paintLeft
|
|
3436
|
+
paintTop = other.paintTop
|
|
3437
|
+
paintRight = other.paintRight
|
|
3438
|
+
paintBottom = other.paintBottom
|
|
3439
|
+
paintUnbounded = other.paintUnbounded
|
|
3440
|
+
paintHasEffect = other.paintHasEffect
|
|
3441
|
+
shiftDx = other.shiftDx
|
|
3442
|
+
shiftDy = other.shiftDy
|
|
3443
|
+
keepLayout = other.keepLayout
|
|
3444
|
+
hasOverlayBelow = other.hasOverlayBelow
|
|
3445
|
+
overlayScanned = other.overlayScanned
|
|
3446
|
+
hasLayout = other.hasLayout
|
|
3447
|
+
layoutClean = other.layoutClean
|
|
3448
|
+
layoutSkipped = other.layoutSkipped
|
|
3449
|
+
kidOffX = other.kidOffX
|
|
3450
|
+
kidOffY = other.kidOffY
|
|
3451
|
+
boundsFresh = other.boundsFresh
|
|
3452
|
+
lastParentW = other.lastParentW
|
|
3453
|
+
lastParentH = other.lastParentH
|
|
3454
|
+
lastFlex = other.lastFlex
|
|
3455
|
+
lastFlexW = other.lastFlexW
|
|
3456
|
+
lastFlexH = other.lastFlexH
|
|
3457
|
+
hasIntrinsicMin = other.hasIntrinsicMin
|
|
3458
|
+
hasIntrinsicMax = other.hasIntrinsicMax
|
|
3459
|
+
intrinsicMin = other.intrinsicMin
|
|
3460
|
+
intrinsicMax = other.intrinsicMax
|
|
3461
|
+
paintClean = other.paintClean
|
|
3462
|
+
paintStamp = (paintStamp + 1)
|
|
3463
|
+
scrollbarWidth = other.scrollbarWidth
|
|
3464
|
+
scrollbarThumb = other.scrollbarThumb
|
|
3465
|
+
scrollbarTrack = other.scrollbarTrack
|
|
3466
|
+
scrollbarLabel = other.scrollbarLabel
|
|
3467
|
+
viewBox = other.viewBox
|
|
3468
|
+
fillColor = other.fillColor
|
|
3469
|
+
strokeColor = other.strokeColor
|
|
3470
|
+
strokeWidth = other.strokeWidth
|
|
3471
|
+
fillRule = other.fillRule
|
|
3472
|
+
strokeDashArray = other.strokeDashArray
|
|
3473
|
+
strokeDashOffset = other.strokeDashOffset
|
|
3474
|
+
anchorName = other.anchorName
|
|
3475
|
+
connectorFrom = other.connectorFrom
|
|
3476
|
+
connectorTo = other.connectorTo
|
|
3477
|
+
connectorFromSide = other.connectorFromSide
|
|
3478
|
+
connectorToSide = other.connectorToSide
|
|
3479
|
+
connectorRouting = other.connectorRouting
|
|
3480
|
+
connectorFromOffset = other.connectorFromOffset
|
|
3481
|
+
connectorToOffset = other.connectorToOffset
|
|
3482
|
+
arrowStart = other.arrowStart
|
|
3483
|
+
arrowEnd = other.arrowEnd
|
|
3484
|
+
arrowSize = other.arrowSize
|
|
3485
|
+
arrowPath = other.arrowPath
|
|
3486
|
+
connectorResolved = other.connectorResolved
|
|
3487
|
+
clipPath = other.clipPath
|
|
3488
|
+
className = other.className
|
|
3489
|
+
theme = other.theme
|
|
3490
|
+
inlineProps = other.inlineProps
|
|
3491
|
+
imageQuality = other.imageQuality
|
|
3492
|
+
maxImageSize = other.maxImageSize
|
|
3493
|
+
rotate = other.rotate
|
|
3494
|
+
scale = other.scale
|
|
3495
|
+
flipY = other.flipY
|
|
3496
|
+
translateX = other.translateX
|
|
3497
|
+
translateY = other.translateY
|
|
3498
|
+
transformSpec = other.transformSpec
|
|
3499
|
+
transformOriginX = other.transformOriginX
|
|
3500
|
+
transformOriginY = other.transformOriginY
|
|
3501
|
+
transformOriginSpec = other.transformOriginSpec
|
|
3502
|
+
shadowRadius = other.shadowRadius
|
|
3503
|
+
shadowColor = other.shadowColor
|
|
3504
|
+
shadowOffsetX = other.shadowOffsetX
|
|
3505
|
+
shadowOffsetY = other.shadowOffsetY
|
|
3506
|
+
backgroundGradient = other.backgroundGradient
|
|
3507
|
+
gradient = other.gradient
|
|
3508
|
+
calculatedX = other.calculatedX
|
|
3509
|
+
calculatedY = other.calculatedY
|
|
3510
|
+
calculatedWidth = other.calculatedWidth
|
|
3511
|
+
calculatedHeight = other.calculatedHeight
|
|
3512
|
+
calculatedInnerWidth = other.calculatedInnerWidth
|
|
3513
|
+
calculatedInnerHeight = other.calculatedInnerHeight
|
|
3514
|
+
calculatedFlexWidth = other.calculatedFlexWidth
|
|
3515
|
+
hasFlexWidth = other.hasFlexWidth
|
|
3516
|
+
calculatedFlexHeight = other.calculatedFlexHeight
|
|
3517
|
+
hasFlexHeight = other.hasFlexHeight
|
|
3518
|
+
calculatedBaseline = other.calculatedBaseline
|
|
3519
|
+
calculatedDescent = other.calculatedDescent
|
|
3520
|
+
hasBaseline = other.hasBaseline
|
|
3521
|
+
hasDefiniteHeight = other.hasDefiniteHeight
|
|
3522
|
+
calculatedPage = other.calculatedPage
|
|
3523
|
+
isAbsolute = other.isAbsolute
|
|
3524
|
+
isOverlay = other.isOverlay
|
|
3525
|
+
positionAnchor = other.positionAnchor
|
|
3526
|
+
positionTryFallbacks = other.positionTryFallbacks
|
|
3527
|
+
positionTryOrder = other.positionTryOrder
|
|
3528
|
+
presentation = other.presentation
|
|
3529
|
+
sheetBelow = other.sheetBelow
|
|
3530
|
+
fitViewport = other.fitViewport
|
|
3531
|
+
anchorLeftSide = other.anchorLeftSide
|
|
3532
|
+
anchorLeftOffset = other.anchorLeftOffset
|
|
3533
|
+
anchorTopSide = other.anchorTopSide
|
|
3534
|
+
anchorTopOffset = other.anchorTopOffset
|
|
3535
|
+
anchorRightSide = other.anchorRightSide
|
|
3536
|
+
anchorRightOffset = other.anchorRightOffset
|
|
3537
|
+
anchorBottomSide = other.anchorBottomSide
|
|
3538
|
+
anchorBottomOffset = other.anchorBottomOffset
|
|
3539
|
+
overlayPlacedAlign = other.overlayPlacedAlign
|
|
3540
|
+
overlayPlacedPresentation = other.overlayPlacedPresentation
|
|
3541
|
+
overlayClamped = other.overlayClamped
|
|
3542
|
+
overlayAnchor = other.overlayAnchor
|
|
3543
|
+
isOverlayAnchor = other.isOverlayAnchor
|
|
3544
|
+
overlaySide = other.overlaySide
|
|
3545
|
+
overlayAlign = other.overlayAlign
|
|
3546
|
+
overlayGap = other.overlayGap
|
|
3547
|
+
overlayX = other.overlayX
|
|
3548
|
+
overlayY = other.overlayY
|
|
3549
|
+
overlayPlacedSide = other.overlayPlacedSide
|
|
3550
|
+
isHovered = other.isHovered
|
|
3551
|
+
isFocused = other.isFocused
|
|
3552
|
+
isPressed = other.isPressed
|
|
3553
|
+
transitionSpec = other.transitionSpec
|
|
3554
|
+
role = other.role
|
|
3555
|
+
a11yLabel = other.a11yLabel
|
|
3556
|
+
a11yValue = other.a11yValue
|
|
3557
|
+
a11yRequired = other.a11yRequired
|
|
3558
|
+
a11yInvalid = other.a11yInvalid
|
|
3559
|
+
a11yReadOnly = other.a11yReadOnly
|
|
3560
|
+
a11yRoleDescription = other.a11yRoleDescription
|
|
3561
|
+
a11yDescription = other.a11yDescription
|
|
3562
|
+
a11yDescribedBy = other.a11yDescribedBy
|
|
3563
|
+
a11yControls = other.a11yControls
|
|
3564
|
+
a11yLabelledBy = other.a11yLabelledBy
|
|
3565
|
+
a11yHasPopup = other.a11yHasPopup
|
|
3566
|
+
a11yRowCount = other.a11yRowCount
|
|
3567
|
+
a11yRowIndex = other.a11yRowIndex
|
|
3568
|
+
a11yHidden = other.a11yHidden
|
|
3569
|
+
a11yModal = other.a11yModal
|
|
3570
|
+
a11ySorted = other.a11ySorted
|
|
3571
|
+
a11yOrientation = other.a11yOrientation
|
|
3572
|
+
a11yCurrent = other.a11yCurrent
|
|
3573
|
+
a11yHasValue = other.a11yHasValue
|
|
3574
|
+
a11yValueNow = other.a11yValueNow
|
|
3575
|
+
a11yHasRange = other.a11yHasRange
|
|
3576
|
+
a11yValueMin = other.a11yValueMin
|
|
3577
|
+
a11yValueMax = other.a11yValueMax
|
|
3578
|
+
a11yChecked = other.a11yChecked
|
|
3579
|
+
a11yPressed = other.a11yPressed
|
|
3580
|
+
a11yExpanded = other.a11yExpanded
|
|
3581
|
+
a11ySelected = other.a11ySelected
|
|
3582
|
+
a11yDisabled = other.a11yDisabled
|
|
3583
|
+
a11yFocusable = other.a11yFocusable
|
|
3584
|
+
a11yPosInSet = other.a11yPosInSet
|
|
3585
|
+
a11ySetSize = other.a11ySetSize
|
|
3586
|
+
a11yLevel = other.a11yLevel
|
|
3587
|
+
; Copied, and the values copied are the FRESH element's — which has
|
|
3588
|
+
; never been styled. So a reconciled element always reads as
|
|
3589
|
+
; "the sheet has not written here", and re-styles. That is the honest
|
|
3590
|
+
; answer: `adoptFrom` has just overwritten every value the sheet had
|
|
3591
|
+
; put there.
|
|
3592
|
+
styleClass = other.styleClass
|
|
3593
|
+
styleTheme = other.styleTheme
|
|
3594
|
+
styleBits = other.styleBits
|
|
3595
|
+
styleGen = other.styleGen
|
|
3596
|
+
styleSlot = other.styleSlot
|
|
3597
|
+
styleKids = other.styleKids
|
|
3598
|
+
; Where the inspector last saw this element. Declared with the field
|
|
3599
|
+
; and not copied here, so `evg:adopt:check` — which reads the field
|
|
3600
|
+
; list and this function and compares them — named it the moment the
|
|
3601
|
+
; suite could run again. A reconciled element that loses its slot is
|
|
3602
|
+
; an element the display list and the hit test can no longer name.
|
|
3603
|
+
inspectSlot = other.inspectSlot
|
|
3604
|
+
isLayoutComplete = other.isLayoutComplete
|
|
3605
|
+
unitsResolved = other.unitsResolved
|
|
3606
|
+
hasReturn = other.hasReturn
|
|
3607
|
+
hasBreak = other.hasBreak
|
|
3608
|
+
hasContinue = other.hasContinue
|
|
3609
|
+
inheritedFontSize = other.inheritedFontSize
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
; `scrollbar-color: <thumb> <track>` — two colours, split on the space
|
|
3613
|
+
; between them and not on the spaces inside an `rgba( )`. One colour
|
|
3614
|
+
; is the thumb alone; `auto` for either is no colour of its own.
|
|
3615
|
+
fn setScrollbarColor:void (value:string) {
|
|
3616
|
+
def parts:[string]
|
|
3617
|
+
def cur@(mutable):string ""
|
|
3618
|
+
def depth@(mutable):int 0
|
|
3619
|
+
def i:int 0
|
|
3620
|
+
def n:int (strlen value)
|
|
3621
|
+
while (i < n) {
|
|
3622
|
+
def ch:string (substring value i (i + 1))
|
|
3623
|
+
if (ch == "(") {
|
|
3624
|
+
depth = (depth + 1)
|
|
3625
|
+
}
|
|
3626
|
+
if (ch == ")") {
|
|
3627
|
+
depth = (depth - 1)
|
|
3628
|
+
}
|
|
3629
|
+
if (((ch == " ") || (ch == "\t")) && (depth == 0)) {
|
|
3630
|
+
if ((strlen cur) > 0) {
|
|
3631
|
+
push parts cur
|
|
3632
|
+
cur = ""
|
|
3633
|
+
}
|
|
3634
|
+
} {
|
|
3635
|
+
cur = (cur + ch)
|
|
3636
|
+
}
|
|
3637
|
+
i = (i + 1)
|
|
3638
|
+
}
|
|
3639
|
+
if ((strlen cur) > 0) {
|
|
3640
|
+
push parts cur
|
|
3641
|
+
}
|
|
3642
|
+
scrollbarThumb = (EVGColor.noColor())
|
|
3643
|
+
scrollbarTrack = (EVGColor.noColor())
|
|
3644
|
+
if ((array_length parts) > 0) {
|
|
3645
|
+
def t:string (itemAt parts 0)
|
|
3646
|
+
if (t != "auto") {
|
|
3647
|
+
scrollbarThumb = (EVGColor.parse(t))
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
if ((array_length parts) > 1) {
|
|
3651
|
+
def tr:string (itemAt parts 1)
|
|
3652
|
+
if (tr != "auto") {
|
|
3653
|
+
scrollbarTrack = (EVGColor.parse(tr))
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
; Parse a length, and SAY SO when it could not be parsed.
|
|
3659
|
+
;
|
|
3660
|
+
; `EVGUnit` deliberately leaves an unrecognised value unset, which is what
|
|
3661
|
+
; a browser does with a declaration it cannot read — but a browser then
|
|
3662
|
+
; shows you the struck-through line in its inspector, and this engine used
|
|
3663
|
+
; to show you nothing. An unset width is not neutral here either: in a row
|
|
3664
|
+
; it fills the parent, so `width: calc(100% - 40px)` did not fail to apply,
|
|
3665
|
+
; it applied as `width: 100%`.
|
|
3666
|
+
;
|
|
3667
|
+
; The cost is one `isSet` test on a path that already parsed a string.
|
|
3668
|
+
;
|
|
3669
|
+
; `fit-content` is the one value that is understood AND left unset — the
|
|
3670
|
+
; layout reads its unit type (6), not `isSet` — so it is not a rejection.
|
|
3671
|
+
; Rave's strip counted it as one for every button on the page.
|
|
3672
|
+
fn unitOf:EVGUnit (name:string value:string) {
|
|
3673
|
+
def u:EVGUnit (EVGUnit.parse(value))
|
|
3674
|
+
if ((u.isSet == false) && (u.unitType != 6)) {
|
|
3675
|
+
if ((EVGReject.isAbsent(value)) == false) {
|
|
3676
|
+
EVGReject.note("unsupported length" name value)
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
return u
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
; --- the generic effect parameters ---------------------------------------
|
|
3683
|
+
;
|
|
3684
|
+
; Last write wins, in place, so a rule that restates a parameter replaces
|
|
3685
|
+
; it rather than appending a second one the reader would have to know to
|
|
3686
|
+
; ignore.
|
|
3687
|
+
fn setFx:void (key:string value:double) {
|
|
3688
|
+
def i:int 0
|
|
3689
|
+
while (i < (array_length fxNames)) {
|
|
3690
|
+
if ((itemAt fxNames i) == key) {
|
|
3691
|
+
set fxValues i value
|
|
3692
|
+
return
|
|
3693
|
+
}
|
|
3694
|
+
i = (i + 1)
|
|
3695
|
+
}
|
|
3696
|
+
push fxNames key
|
|
3697
|
+
push fxValues value
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
; What was written under `key`, or the fallback. The plugin's defaults are
|
|
3701
|
+
; the plugin's business — this only answers whether the page said anything.
|
|
3702
|
+
fn fxValue:double (key:string fallback:double) {
|
|
3703
|
+
def i:int 0
|
|
3704
|
+
while (i < (array_length fxNames)) {
|
|
3705
|
+
if ((itemAt fxNames i) == key) {
|
|
3706
|
+
return (itemAt fxValues i)
|
|
3707
|
+
}
|
|
3708
|
+
i = (i + 1)
|
|
3709
|
+
}
|
|
3710
|
+
return fallback
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
fn setAttribute:void (name:string value:string) {
|
|
3714
|
+
; Set attribute by name (for parser)
|
|
3715
|
+
|
|
3716
|
+
if ((name == "className") || (name == "class-name")) {
|
|
3717
|
+
className = value
|
|
3718
|
+
return
|
|
3719
|
+
}
|
|
3720
|
+
if (name == "theme") {
|
|
3721
|
+
theme = value
|
|
3722
|
+
return
|
|
3723
|
+
}
|
|
3724
|
+
if (name == "id") {
|
|
3725
|
+
id = value
|
|
3726
|
+
return
|
|
3727
|
+
}
|
|
3728
|
+
; `src` and `alt` are set directly by the JSX layer AND forwarded here,
|
|
3729
|
+
; so they have to mean something here too or every image on the page
|
|
3730
|
+
; reports an unknown property. They are also the right home for them:
|
|
3731
|
+
; an element built without a JSX layer should be able to say `src`.
|
|
3732
|
+
if (name == "src") {
|
|
3733
|
+
src = value
|
|
3734
|
+
return
|
|
3735
|
+
}
|
|
3736
|
+
if (name == "alt") {
|
|
3737
|
+
alt = value
|
|
3738
|
+
return
|
|
3739
|
+
}
|
|
3740
|
+
if (name == "href") {
|
|
3741
|
+
href = value
|
|
3742
|
+
return
|
|
3743
|
+
}
|
|
3744
|
+
if (name == "key") {
|
|
3745
|
+
key = value
|
|
3746
|
+
return
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
; Book format properties
|
|
3750
|
+
if (name == "format") {
|
|
3751
|
+
format = (to_lowercase value)
|
|
3752
|
+
return
|
|
3753
|
+
}
|
|
3754
|
+
if (name == "orientation") {
|
|
3755
|
+
orientation = (to_lowercase value)
|
|
3756
|
+
return
|
|
3757
|
+
}
|
|
3758
|
+
if (name == "pageWidth") {
|
|
3759
|
+
def pw@(optional):double (to_double value)
|
|
3760
|
+
if pw {
|
|
3761
|
+
pageWidth = (unwrap pw)
|
|
3762
|
+
}
|
|
3763
|
+
return
|
|
3764
|
+
}
|
|
3765
|
+
if (name == "pageHeight") {
|
|
3766
|
+
def ph@(optional):double (to_double value)
|
|
3767
|
+
if ph {
|
|
3768
|
+
pageHeight = (unwrap ph)
|
|
3769
|
+
}
|
|
3770
|
+
return
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
; Dimensions
|
|
3774
|
+
if (name == "width") {
|
|
3775
|
+
width = (this.unitOf(name value))
|
|
3776
|
+
return
|
|
3777
|
+
}
|
|
3778
|
+
if (name == "height") {
|
|
3779
|
+
height = (this.unitOf(name value))
|
|
3780
|
+
return
|
|
3781
|
+
}
|
|
3782
|
+
if ((name == "min-width") || (name == "minWidth")) {
|
|
3783
|
+
minWidth = (this.unitOf(name value))
|
|
3784
|
+
return
|
|
3785
|
+
}
|
|
3786
|
+
if ((name == "min-height") || (name == "minHeight")) {
|
|
3787
|
+
minHeight = (this.unitOf(name value))
|
|
3788
|
+
return
|
|
3789
|
+
}
|
|
3790
|
+
if ((name == "max-width") || (name == "maxWidth")) {
|
|
3791
|
+
maxWidth = (this.unitOf(name value))
|
|
3792
|
+
return
|
|
3793
|
+
}
|
|
3794
|
+
if ((name == "max-height") || (name == "maxHeight")) {
|
|
3795
|
+
maxHeight = (this.unitOf(name value))
|
|
3796
|
+
return
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
; Overlays
|
|
3800
|
+
if ((name == "overlay") || (name == "isOverlay")) {
|
|
3801
|
+
isOverlay = (EVGElement.truthy(value))
|
|
3802
|
+
return
|
|
3803
|
+
}
|
|
3804
|
+
if ((name == "overlay-anchor-role") || (name == "overlayAnchorRole")) {
|
|
3805
|
+
isOverlayAnchor = (EVGElement.truthy(value))
|
|
3806
|
+
return
|
|
3807
|
+
}
|
|
3808
|
+
if ((name == "overlay-side") || (name == "overlaySide")) {
|
|
3809
|
+
overlaySide = (trim value)
|
|
3810
|
+
return
|
|
3811
|
+
}
|
|
3812
|
+
if ((name == "overlay-align") || (name == "overlayAlign")) {
|
|
3813
|
+
overlayAlign = (trim value)
|
|
3814
|
+
return
|
|
3815
|
+
}
|
|
3816
|
+
; `position-area: bottom start` is `overlay-side` and `overlay-align`
|
|
3817
|
+
; in one declaration, which is how CSS writes it and how a fallback
|
|
3818
|
+
; list has to write it — a fallback changes both at once.
|
|
3819
|
+
if ((name == "position-area") || (name == "positionArea")) {
|
|
3820
|
+
def pa:string (trim value)
|
|
3821
|
+
def paSide:string (EVGElement.areaSide(pa))
|
|
3822
|
+
if ((strlen paSide) == 0) {
|
|
3823
|
+
EVGReject.note("unsupported value" "position-area" pa)
|
|
3824
|
+
return
|
|
3825
|
+
}
|
|
3826
|
+
overlaySide = paSide
|
|
3827
|
+
overlayAlign = (EVGElement.areaAlign(pa))
|
|
3828
|
+
return
|
|
3829
|
+
}
|
|
3830
|
+
if ((name == "position-anchor") || (name == "positionAnchor")) {
|
|
3831
|
+
positionAnchor = (trim value)
|
|
3832
|
+
; Positioned against another box: out of flow, top layer. The
|
|
3833
|
+
; property says the whole thing.
|
|
3834
|
+
if ((strlen positionAnchor) > 0) {
|
|
3835
|
+
isOverlay = true
|
|
3836
|
+
}
|
|
3837
|
+
return
|
|
3838
|
+
}
|
|
3839
|
+
if (((name == "position-try-fallbacks") || (name == "positionTryFallbacks")) || (name == "position-try")) {
|
|
3840
|
+
positionTryFallbacks = (trim value)
|
|
3841
|
+
return
|
|
3842
|
+
}
|
|
3843
|
+
if ((name == "position-try-order") || (name == "positionTryOrder")) {
|
|
3844
|
+
positionTryOrder = (trim value)
|
|
3845
|
+
return
|
|
3846
|
+
}
|
|
3847
|
+
if (name == "presentation") {
|
|
3848
|
+
def pr:string (trim value)
|
|
3849
|
+
if ((((pr == "anchored") || (pr == "sheet")) || (pr == "fullscreen")) == false) {
|
|
3850
|
+
EVGReject.note("unsupported value" "presentation" pr)
|
|
3851
|
+
return
|
|
3852
|
+
}
|
|
3853
|
+
presentation = pr
|
|
3854
|
+
return
|
|
3855
|
+
}
|
|
3856
|
+
if ((name == "sheet-below") || (name == "sheetBelow")) {
|
|
3857
|
+
def sb:EVGUnit (this.unitOf(name value))
|
|
3858
|
+
if sb.isSet {
|
|
3859
|
+
sheetBelow = sb.pixels
|
|
3860
|
+
}
|
|
3861
|
+
return
|
|
3862
|
+
}
|
|
3863
|
+
if ((name == "fit-viewport") || (name == "fitViewport")) {
|
|
3864
|
+
fitViewport = (EVGElement.truthy(value))
|
|
3865
|
+
return
|
|
3866
|
+
}
|
|
3867
|
+
if ((name == "overlay-gap") || (name == "overlayGap")) {
|
|
3868
|
+
def g@(optional):double (to_double value)
|
|
3869
|
+
if g {
|
|
3870
|
+
overlayGap = (unwrap g)
|
|
3871
|
+
}
|
|
3872
|
+
return
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
if (name == "transition") {
|
|
3876
|
+
transitionSpec = (trim value)
|
|
3877
|
+
return
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
; Accessibility. The ARIA spellings, so a tree literal and a stylesheet
|
|
3881
|
+
; and a hand-written element all say it the same way.
|
|
3882
|
+
if ((name == "role") || (name == "a11yRole")) {
|
|
3883
|
+
role = (trim value)
|
|
3884
|
+
return
|
|
3885
|
+
}
|
|
3886
|
+
; A BARE `return`, like every other branch here: `setAttribute` is
|
|
3887
|
+
; declared `:void`. These four said `return true`, which JavaScript
|
|
3888
|
+
; ignores and C++ refuses — `return-statement with a value, in function
|
|
3889
|
+
; returning 'void'` — so the whole surface compiled and every JS suite
|
|
3890
|
+
; passed while the C++ build of the deck editor was broken.
|
|
3891
|
+
if ((name == "a11yRequired") || (name == "aria-required")) {
|
|
3892
|
+
a11yRequired = value
|
|
3893
|
+
return
|
|
3894
|
+
}
|
|
3895
|
+
if ((name == "a11yInvalid") || (name == "aria-invalid")) {
|
|
3896
|
+
a11yInvalid = value
|
|
3897
|
+
return
|
|
3898
|
+
}
|
|
3899
|
+
if ((name == "a11yReadOnly") || (name == "aria-readonly")) {
|
|
3900
|
+
a11yReadOnly = value
|
|
3901
|
+
return
|
|
3902
|
+
}
|
|
3903
|
+
if ((name == "a11yValue") || (name == "aria-valuetext")) {
|
|
3904
|
+
a11yValue = value
|
|
3905
|
+
return
|
|
3906
|
+
}
|
|
3907
|
+
if ((name == "aria-label") || (name == "a11yLabel")) {
|
|
3908
|
+
a11yLabel = value
|
|
3909
|
+
return
|
|
3910
|
+
}
|
|
3911
|
+
if ((name == "aria-current") || (name == "a11yCurrent")) {
|
|
3912
|
+
a11yCurrent = value
|
|
3913
|
+
return
|
|
3914
|
+
}
|
|
3915
|
+
if ((name == "aria-orientation") || (name == "a11yOrientation")) {
|
|
3916
|
+
a11yOrientation = (to_lowercase value)
|
|
3917
|
+
return
|
|
3918
|
+
}
|
|
3919
|
+
if ((name == "aria-sort") || (name == "a11ySorted")) {
|
|
3920
|
+
if (value == "none") { a11ySorted = 1 }
|
|
3921
|
+
if (value == "ascending") { a11ySorted = 2 }
|
|
3922
|
+
if (value == "descending") { a11ySorted = 3 }
|
|
3923
|
+
return
|
|
3924
|
+
}
|
|
3925
|
+
if ((name == "aria-hidden") || (name == "a11yHidden")) {
|
|
3926
|
+
a11yHidden = (EVGElement.truthy(value))
|
|
3927
|
+
return
|
|
3928
|
+
}
|
|
3929
|
+
; `to_int` on a string is OPTIONAL here — a value that is not a number
|
|
3930
|
+
; has no integer to be — so it is unwrapped through a default rather
|
|
3931
|
+
; than assigned straight across. Zero is the right default: it is what
|
|
3932
|
+
; "this node publishes no row count" already means everywhere else.
|
|
3933
|
+
if ((name == "aria-rowcount") || (name == "a11yRowCount")) {
|
|
3934
|
+
def rc@(optional):int (to_int value)
|
|
3935
|
+
a11yRowCount = 0
|
|
3936
|
+
if rc {
|
|
3937
|
+
a11yRowCount = (unwrap rc)
|
|
3938
|
+
}
|
|
3939
|
+
return
|
|
3940
|
+
}
|
|
3941
|
+
if ((name == "aria-rowindex") || (name == "a11yRowIndex")) {
|
|
3942
|
+
def ri@(optional):int (to_int value)
|
|
3943
|
+
a11yRowIndex = 0
|
|
3944
|
+
if ri {
|
|
3945
|
+
a11yRowIndex = (unwrap ri)
|
|
3946
|
+
}
|
|
3947
|
+
return
|
|
3948
|
+
}
|
|
3949
|
+
if ((name == "aria-haspopup") || (name == "a11yHasPopup")) {
|
|
3950
|
+
a11yHasPopup = value
|
|
3951
|
+
return
|
|
3952
|
+
}
|
|
3953
|
+
if ((name == "aria-describedby") || (name == "a11yDescription")) {
|
|
3954
|
+
a11yDescription = value
|
|
3955
|
+
return
|
|
3956
|
+
}
|
|
3957
|
+
if (name == "a11yDescribedBy") {
|
|
3958
|
+
a11yDescribedBy = value
|
|
3959
|
+
return
|
|
3960
|
+
}
|
|
3961
|
+
if ((name == "aria-controls") || (name == "a11yControls")) {
|
|
3962
|
+
a11yControls = value
|
|
3963
|
+
return
|
|
3964
|
+
}
|
|
3965
|
+
if ((name == "aria-labelledby") || (name == "a11yLabelledBy")) {
|
|
3966
|
+
a11yLabelledBy = value
|
|
3967
|
+
return
|
|
3968
|
+
}
|
|
3969
|
+
if ((name == "aria-roledescription") || (name == "a11yRoleDescription")) {
|
|
3970
|
+
a11yRoleDescription = value
|
|
3971
|
+
return
|
|
3972
|
+
}
|
|
3973
|
+
if ((name == "aria-pressed") || (name == "a11yPressed")) {
|
|
3974
|
+
a11yPressed = (EVGElement.triState(value))
|
|
3975
|
+
return
|
|
3976
|
+
}
|
|
3977
|
+
if ((name == "aria-checked") || (name == "a11yChecked")) {
|
|
3978
|
+
a11yChecked = (EVGElement.triState(value))
|
|
3979
|
+
return
|
|
3980
|
+
}
|
|
3981
|
+
if ((name == "aria-expanded") || (name == "a11yExpanded")) {
|
|
3982
|
+
a11yExpanded = (EVGElement.triState(value))
|
|
3983
|
+
return
|
|
3984
|
+
}
|
|
3985
|
+
if ((name == "aria-selected") || (name == "a11ySelected")) {
|
|
3986
|
+
a11ySelected = (EVGElement.triState(value))
|
|
3987
|
+
return
|
|
3988
|
+
}
|
|
3989
|
+
if ((name == "aria-disabled") || (name == "a11yDisabled")) {
|
|
3990
|
+
a11yDisabled = (EVGElement.truthy(value))
|
|
3991
|
+
return
|
|
3992
|
+
}
|
|
3993
|
+
if ((name == "aria-focusable") || (name == "a11yFocusable")) {
|
|
3994
|
+
a11yFocusable = (EVGElement.truthy(value))
|
|
3995
|
+
return
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
; Position
|
|
3999
|
+
if (name == "left") {
|
|
4000
|
+
if (EVGElement.mentionsAnchor(value)) {
|
|
4001
|
+
anchorLeftSide = (EVGElement.anchorFnSide(name value))
|
|
4002
|
+
anchorLeftOffset = (EVGElement.anchorFnOffset(value))
|
|
4003
|
+
return
|
|
4004
|
+
}
|
|
4005
|
+
left = (this.unitOf(name value))
|
|
4006
|
+
return
|
|
4007
|
+
}
|
|
4008
|
+
if (name == "top") {
|
|
4009
|
+
if (EVGElement.mentionsAnchor(value)) {
|
|
4010
|
+
anchorTopSide = (EVGElement.anchorFnSide(name value))
|
|
4011
|
+
anchorTopOffset = (EVGElement.anchorFnOffset(value))
|
|
4012
|
+
return
|
|
4013
|
+
}
|
|
4014
|
+
top = (this.unitOf(name value))
|
|
4015
|
+
return
|
|
4016
|
+
}
|
|
4017
|
+
if (name == "right") {
|
|
4018
|
+
if (EVGElement.mentionsAnchor(value)) {
|
|
4019
|
+
anchorRightSide = (EVGElement.anchorFnSide(name value))
|
|
4020
|
+
anchorRightOffset = (EVGElement.anchorFnOffset(value))
|
|
4021
|
+
return
|
|
4022
|
+
}
|
|
4023
|
+
right = (this.unitOf(name value))
|
|
4024
|
+
return
|
|
4025
|
+
}
|
|
4026
|
+
if (name == "bottom") {
|
|
4027
|
+
if (EVGElement.mentionsAnchor(value)) {
|
|
4028
|
+
anchorBottomSide = (EVGElement.anchorFnSide(name value))
|
|
4029
|
+
anchorBottomOffset = (EVGElement.anchorFnOffset(value))
|
|
4030
|
+
return
|
|
4031
|
+
}
|
|
4032
|
+
bottom = (this.unitOf(name value))
|
|
4033
|
+
return
|
|
4034
|
+
}
|
|
4035
|
+
if (name == "x") {
|
|
4036
|
+
x = (this.unitOf(name value))
|
|
4037
|
+
return
|
|
4038
|
+
}
|
|
4039
|
+
if (name == "y") {
|
|
4040
|
+
y = (this.unitOf(name value))
|
|
4041
|
+
return
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
; Margin
|
|
4045
|
+
if (name == "margin") {
|
|
4046
|
+
def ms:[EVGUnit] (EVGElement.boxSides(value true))
|
|
4047
|
+
if ((array_length ms) == 4) {
|
|
4048
|
+
box.setMarginValues((itemAt ms 0) (itemAt ms 1) (itemAt ms 2) (itemAt ms 3))
|
|
4049
|
+
}
|
|
4050
|
+
return
|
|
4051
|
+
}
|
|
4052
|
+
if ((name == "margin-left") || (name == "marginLeft")) {
|
|
4053
|
+
box.marginLeft = (this.unitOf(name value))
|
|
4054
|
+
return
|
|
4055
|
+
}
|
|
4056
|
+
if ((name == "margin-right") || (name == "marginRight")) {
|
|
4057
|
+
box.marginRight = (this.unitOf(name value))
|
|
4058
|
+
return
|
|
4059
|
+
}
|
|
4060
|
+
if ((name == "margin-top") || (name == "marginTop")) {
|
|
4061
|
+
box.marginTop = (this.unitOf(name value))
|
|
4062
|
+
return
|
|
4063
|
+
}
|
|
4064
|
+
if ((name == "margin-bottom") || (name == "marginBottom")) {
|
|
4065
|
+
box.marginBottom = (this.unitOf(name value))
|
|
4066
|
+
return
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
; Padding
|
|
4070
|
+
if (name == "padding") {
|
|
4071
|
+
def ps:[EVGUnit] (EVGElement.boxSides(value false))
|
|
4072
|
+
if ((array_length ps) == 4) {
|
|
4073
|
+
box.setPaddingValues((itemAt ps 0) (itemAt ps 1) (itemAt ps 2) (itemAt ps 3))
|
|
4074
|
+
}
|
|
4075
|
+
return
|
|
4076
|
+
}
|
|
4077
|
+
if ((name == "padding-left") || (name == "paddingLeft")) {
|
|
4078
|
+
box.paddingLeft = (this.unitOf(name value))
|
|
4079
|
+
return
|
|
4080
|
+
}
|
|
4081
|
+
if ((name == "padding-right") || (name == "paddingRight")) {
|
|
4082
|
+
box.paddingRight = (this.unitOf(name value))
|
|
4083
|
+
return
|
|
4084
|
+
}
|
|
4085
|
+
if ((name == "padding-top") || (name == "paddingTop")) {
|
|
4086
|
+
box.paddingTop = (this.unitOf(name value))
|
|
4087
|
+
return
|
|
4088
|
+
}
|
|
4089
|
+
if ((name == "padding-bottom") || (name == "paddingBottom")) {
|
|
4090
|
+
box.paddingBottom = (this.unitOf(name value))
|
|
4091
|
+
return
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
; Border
|
|
4095
|
+
;
|
|
4096
|
+
; The shorthand, because it is what anyone writing CSS reaches for and
|
|
4097
|
+
; it used to be dropped in silence: no error from the stylesheet, no
|
|
4098
|
+
; border on the screen. Order-independent, like CSS — a length is the
|
|
4099
|
+
; width, a colour is the colour, and a style keyword is accepted and
|
|
4100
|
+
; ignored, since EVG strokes one way.
|
|
4101
|
+
if (name == "border") {
|
|
4102
|
+
def parts:[string] (EVGElement.splitWords(value))
|
|
4103
|
+
def i:int 0
|
|
4104
|
+
while (i < (array_length parts)) {
|
|
4105
|
+
def tok:string (itemAt parts i)
|
|
4106
|
+
if (EVGElement.isBorderStyleWord(tok)) {
|
|
4107
|
+
; `none` means no border at all, whatever else was written.
|
|
4108
|
+
if (tok == "none") {
|
|
4109
|
+
box.borderWidth = (EVGUnit.px(0.0))
|
|
4110
|
+
}
|
|
4111
|
+
} {
|
|
4112
|
+
if (EVGElement.looksLikeColor(tok)) {
|
|
4113
|
+
box.borderColor = (EVGColor.parse(tok))
|
|
4114
|
+
} {
|
|
4115
|
+
box.borderWidth = (EVGUnit.parse(tok))
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
i = (i + 1)
|
|
4119
|
+
}
|
|
4120
|
+
return
|
|
4121
|
+
}
|
|
4122
|
+
if ((name == "border-width") || (name == "borderWidth")) {
|
|
4123
|
+
box.borderWidth = (this.unitOf(name value))
|
|
4124
|
+
return
|
|
4125
|
+
}
|
|
4126
|
+
if ((name == "border-color") || (name == "borderColor")) {
|
|
4127
|
+
box.borderColor = (EVGColor.parse(value))
|
|
4128
|
+
return
|
|
4129
|
+
}
|
|
4130
|
+
; ONE TO FOUR VALUES, clockwise from the top left. The fill-in rules
|
|
4131
|
+
; are the shorthand's own and are not the ones `padding` uses: two
|
|
4132
|
+
; values are (TL+BR, TR+BL) — a DIAGONAL pair — where two paddings are
|
|
4133
|
+
; vertical and horizontal. Three are (TL, TR+BL, BR). Measured against
|
|
4134
|
+
; a browser in `oracle/radius.json` rather than recalled.
|
|
4135
|
+
if ((name == "border-radius") || (name == "borderRadius")) {
|
|
4136
|
+
def parts:[string] (EVGElement.splitWords(value))
|
|
4137
|
+
def n:int (array_length parts)
|
|
4138
|
+
if (n < 2) {
|
|
4139
|
+
box.borderRadius = (this.unitOf(name value))
|
|
4140
|
+
box.borderRadiusTL = (EVGUnit.unset())
|
|
4141
|
+
box.borderRadiusTR = (EVGUnit.unset())
|
|
4142
|
+
box.borderRadiusBR = (EVGUnit.unset())
|
|
4143
|
+
box.borderRadiusBL = (EVGUnit.unset())
|
|
4144
|
+
return
|
|
4145
|
+
}
|
|
4146
|
+
def tl:string (itemAt parts 0)
|
|
4147
|
+
def tr:string (itemAt parts 1)
|
|
4148
|
+
def br:string tl
|
|
4149
|
+
def bl:string tr
|
|
4150
|
+
if (n > 2) {
|
|
4151
|
+
br = (itemAt parts 2)
|
|
4152
|
+
}
|
|
4153
|
+
if (n > 3) {
|
|
4154
|
+
bl = (itemAt parts 3)
|
|
4155
|
+
}
|
|
4156
|
+
box.borderRadiusTL = (EVGUnit.parse(tl))
|
|
4157
|
+
box.borderRadiusTR = (EVGUnit.parse(tr))
|
|
4158
|
+
box.borderRadiusBR = (EVGUnit.parse(br))
|
|
4159
|
+
box.borderRadiusBL = (EVGUnit.parse(bl))
|
|
4160
|
+
; The uniform value stays the FIRST corner, because every backend
|
|
4161
|
+
; but the WebGL painter still reads one number and the top left is
|
|
4162
|
+
; the one the shorthand names first.
|
|
4163
|
+
box.borderRadius = (EVGUnit.parse(tl))
|
|
4164
|
+
return
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
; ---- rich EVG effect props, drawn by WasmUiRenderer from these fields.
|
|
4168
|
+
; camelCase spellings so the TSX parser (which splits '-') can carry them.
|
|
4169
|
+
; Animated glow halo strength (0..1).
|
|
4170
|
+
if (name == "glow") {
|
|
4171
|
+
def gv@(optional):double (to_double value)
|
|
4172
|
+
glowIntensity = (unwrap gv)
|
|
4173
|
+
return
|
|
4174
|
+
}
|
|
4175
|
+
; Background art clipped to the rounded box (the .as menu's tile image).
|
|
4176
|
+
if ((name == "background-image") || (name == "backgroundImage")) {
|
|
4177
|
+
bgImageSet = true
|
|
4178
|
+
bgImagePath = value
|
|
4179
|
+
return
|
|
4180
|
+
}
|
|
4181
|
+
; 2-stop linear gradient fill (RGU1 GRAD_FROM/GRAD_TO/GRAD_DIR).
|
|
4182
|
+
if ((name == "gradient-from") || (name == "gradientFrom")) {
|
|
4183
|
+
gradientFrom = (EVGColor.parse(value))
|
|
4184
|
+
gradientSet = true
|
|
4185
|
+
return
|
|
4186
|
+
}
|
|
4187
|
+
if ((name == "gradient-to") || (name == "gradientTo")) {
|
|
4188
|
+
gradientTo = (EVGColor.parse(value))
|
|
4189
|
+
gradientSet = true
|
|
4190
|
+
return
|
|
4191
|
+
}
|
|
4192
|
+
if ((name == "gradient-dir") || (name == "gradientDir")) {
|
|
4193
|
+
def dv@(optional):int (to_int value)
|
|
4194
|
+
gradientDir = (unwrap dv)
|
|
4195
|
+
return
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
; Colors
|
|
4199
|
+
if ((name == "background-color") || (name == "backgroundColor")) {
|
|
4200
|
+
backgroundColor = (EVGColor.parse(value))
|
|
4201
|
+
return
|
|
4202
|
+
}
|
|
4203
|
+
if ((name == "background-gradient") || (name == "backgroundGradient")) {
|
|
4204
|
+
backgroundGradient = value
|
|
4205
|
+
gradient = (EVGGradient.parse(value))
|
|
4206
|
+
return
|
|
4207
|
+
}
|
|
4208
|
+
; Also support "background" which can be a gradient
|
|
4209
|
+
if (name == "background") {
|
|
4210
|
+
; Check if it's a gradient
|
|
4211
|
+
if ((contains value "linear-gradient") || (contains value "radial-gradient")) {
|
|
4212
|
+
backgroundGradient = value
|
|
4213
|
+
gradient = (EVGGradient.parse(value))
|
|
4214
|
+
} {
|
|
4215
|
+
; Treat as color
|
|
4216
|
+
backgroundColor = (EVGColor.parse(value))
|
|
4217
|
+
}
|
|
4218
|
+
return
|
|
4219
|
+
}
|
|
4220
|
+
if (name == "color") {
|
|
4221
|
+
color = (EVGColor.parse(value))
|
|
4222
|
+
return
|
|
4223
|
+
}
|
|
4224
|
+
if (name == "emoji-color") {
|
|
4225
|
+
emojiColor = (EVGColor.parse(value))
|
|
4226
|
+
return
|
|
4227
|
+
}
|
|
4228
|
+
if (name == "opacity") {
|
|
4229
|
+
def val@(optional):double (to_double value)
|
|
4230
|
+
opacity = (unwrap val)
|
|
4231
|
+
return
|
|
4232
|
+
}
|
|
4233
|
+
; `backdrop-filter: blur(12px)`. Only `blur()` is understood; anything
|
|
4234
|
+
; else in the property is reported rather than half-applied, because a
|
|
4235
|
+
; `saturate()` that silently did nothing would look like a backend that
|
|
4236
|
+
; had not implemented it yet.
|
|
4237
|
+
if ((name == "backdrop-filter") || (name == "backdropFilter")) {
|
|
4238
|
+
backdropBlur = (EVGElement.blurRadiusOf(value))
|
|
4239
|
+
return
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
; Image properties
|
|
4243
|
+
if ((name == "object-fit") || (name == "objectFit")) {
|
|
4244
|
+
objectFit = value
|
|
4245
|
+
return
|
|
4246
|
+
}
|
|
4247
|
+
; The crop window into the source. A picture is not always shown
|
|
4248
|
+
; whole: Figma keeps a crop rectangle on the paint, and a backend
|
|
4249
|
+
; that drops it draws the whole frame where a detail belongs — two
|
|
4250
|
+
; boxes showing two halves of one screenshot come out as the same
|
|
4251
|
+
; picture twice.
|
|
4252
|
+
if ((name == "preserve-aspect-ratio") || (name == "preserveAspectRatio")) {
|
|
4253
|
+
preserveAspectRatio = value
|
|
4254
|
+
return
|
|
4255
|
+
}
|
|
4256
|
+
if ((name == "image-view-box") || (name == "imageViewBox")) {
|
|
4257
|
+
imageViewBox = value
|
|
4258
|
+
imageViewBoxSet = (EVGElement.parseViewBox(value this))
|
|
4259
|
+
return
|
|
4260
|
+
}
|
|
4261
|
+
if ((name == "image-offset-x") || (name == "imageOffsetX")) {
|
|
4262
|
+
imageOffsetX = (this.unitOf(name value))
|
|
4263
|
+
return
|
|
4264
|
+
}
|
|
4265
|
+
if ((name == "image-offset-y") || (name == "imageOffsetY")) {
|
|
4266
|
+
imageOffsetY = (this.unitOf(name value))
|
|
4267
|
+
return
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4270
|
+
; Layout
|
|
4271
|
+
; `direction` is two properties sharing a name; the value says which.
|
|
4272
|
+
; See the note on `textDir`.
|
|
4273
|
+
if (name == "direction") {
|
|
4274
|
+
if ((value == "rtl") || (value == "ltr")) {
|
|
4275
|
+
textDir = value
|
|
4276
|
+
return
|
|
4277
|
+
}
|
|
4278
|
+
direction = value
|
|
4279
|
+
return
|
|
4280
|
+
}
|
|
4281
|
+
if (name == "align") {
|
|
4282
|
+
align = value
|
|
4283
|
+
return
|
|
4284
|
+
}
|
|
4285
|
+
if ((name == "vertical-align") || (name == "verticalAlign")) {
|
|
4286
|
+
verticalAlign = value
|
|
4287
|
+
return
|
|
4288
|
+
}
|
|
4289
|
+
if (name == "inline") {
|
|
4290
|
+
isInline = (value == "true")
|
|
4291
|
+
return
|
|
4292
|
+
}
|
|
4293
|
+
if ((name == "line-break") || (name == "lineBreak")) {
|
|
4294
|
+
lineBreak = (value == "true")
|
|
4295
|
+
return
|
|
4296
|
+
}
|
|
4297
|
+
if ((name == "letter-spacing") || (name == "letterSpacing")) {
|
|
4298
|
+
; `normal` is the font's own, which is zero extra.
|
|
4299
|
+
if (value == "normal") {
|
|
4300
|
+
letterSpacing = 0.0
|
|
4301
|
+
return
|
|
4302
|
+
}
|
|
4303
|
+
def lsu:EVGUnit (this.unitOf(name value))
|
|
4304
|
+
if lsu.isSet {
|
|
4305
|
+
letterSpacing = lsu.pixels
|
|
4306
|
+
}
|
|
4307
|
+
return
|
|
4308
|
+
}
|
|
4309
|
+
if ((name == "paragraph-spacing") || (name == "paragraphSpacing")) {
|
|
4310
|
+
def psu:EVGUnit (this.unitOf(name value))
|
|
4311
|
+
if psu.isSet {
|
|
4312
|
+
paragraphSpacing = psu.pixels
|
|
4313
|
+
}
|
|
4314
|
+
return
|
|
4315
|
+
}
|
|
4316
|
+
if ((name == "stroke-linecap") || (name == "strokeLinecap")) {
|
|
4317
|
+
strokeLineCap = value
|
|
4318
|
+
return
|
|
4319
|
+
}
|
|
4320
|
+
if ((name == "stroke-linejoin") || (name == "strokeLinejoin")) {
|
|
4321
|
+
strokeLineJoin = value
|
|
4322
|
+
return
|
|
4323
|
+
}
|
|
4324
|
+
if (name == "overflow") {
|
|
4325
|
+
overflow = value
|
|
4326
|
+
return
|
|
4327
|
+
}
|
|
4328
|
+
; ONE OVERFLOW, BOTH AXES. This engine clips a box or it does not;
|
|
4329
|
+
; there is no per-axis clip, and the honest thing is to say so rather
|
|
4330
|
+
; than drop the declaration. `overflow-y: auto` on a menu is how the
|
|
4331
|
+
; web writes "and scroll if it is too tall", and dropping it left the
|
|
4332
|
+
; menu running off the page with no sign anything had been ignored.
|
|
4333
|
+
; A surface that clips both axes is not a problem for a submenu: a
|
|
4334
|
+
; nested surface is drawn in the top layer, outside every clip.
|
|
4335
|
+
if (((name == "overflow-y") || (name == "overflowY")) || (name == "overflow-x")) {
|
|
4336
|
+
overflow = (trim value)
|
|
4337
|
+
return
|
|
4338
|
+
}
|
|
4339
|
+
if (name == "overflowX") {
|
|
4340
|
+
overflow = (trim value)
|
|
4341
|
+
return
|
|
4342
|
+
}
|
|
4343
|
+
if (name == "cursor") {
|
|
4344
|
+
cursor = value
|
|
4345
|
+
return
|
|
4346
|
+
}
|
|
4347
|
+
if ((name == "scrollbar-width") || (name == "scrollbarWidth")) {
|
|
4348
|
+
scrollbarWidth = value
|
|
4349
|
+
return
|
|
4350
|
+
}
|
|
4351
|
+
if ((name == "scrollbar-color") || (name == "scrollbarColor")) {
|
|
4352
|
+
this.setScrollbarColor(value)
|
|
4353
|
+
return
|
|
4354
|
+
}
|
|
4355
|
+
if (name == "evg-scrollbar-label") {
|
|
4356
|
+
scrollbarLabel = value
|
|
4357
|
+
return
|
|
4358
|
+
}
|
|
4359
|
+
; The EVG effect family. Prefixed, because these are EXTENSIONS and not
|
|
4360
|
+
; CSS: a reader who meets `evg-ripple-speed` should not go looking for
|
|
4361
|
+
; it in a specification.
|
|
4362
|
+
if (name == "evg-surface-effect") {
|
|
4363
|
+
surfaceEffect = value
|
|
4364
|
+
return
|
|
4365
|
+
}
|
|
4366
|
+
if (name == "evg-effect-on") {
|
|
4367
|
+
effectTrigger = value
|
|
4368
|
+
return
|
|
4369
|
+
}
|
|
4370
|
+
; `evg-fx-<name>` — anything at all, under whatever name the plugin
|
|
4371
|
+
; that reads it chose. Matched by PREFIX and not against a list,
|
|
4372
|
+
; because the list is not in this file and never will be.
|
|
4373
|
+
if ((strlen name) > 7) {
|
|
4374
|
+
def head:string (substring name 0 7)
|
|
4375
|
+
if (head == "evg-fx-") {
|
|
4376
|
+
def key:string (substring name 7 (strlen name))
|
|
4377
|
+
this.setFx(key (EVGElement.numberOr(value 0.0)))
|
|
4378
|
+
return
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
if (name == "evg-ripple-speed") {
|
|
4382
|
+
rippleSpeed = (EVGElement.numberOr(value rippleSpeed))
|
|
4383
|
+
return
|
|
4384
|
+
}
|
|
4385
|
+
if (name == "evg-ripple-width") {
|
|
4386
|
+
rippleWidth = (EVGElement.numberOr(value rippleWidth))
|
|
4387
|
+
return
|
|
4388
|
+
}
|
|
4389
|
+
if (name == "evg-ripple-strength") {
|
|
4390
|
+
rippleStrength = (EVGElement.numberOr(value rippleStrength))
|
|
4391
|
+
return
|
|
4392
|
+
}
|
|
4393
|
+
if (name == "evg-ripple-decay") {
|
|
4394
|
+
rippleDecay = (EVGElement.numberOr(value rippleDecay))
|
|
4395
|
+
return
|
|
4396
|
+
}
|
|
4397
|
+
if (name == "evg-ripple-highlight") {
|
|
4398
|
+
rippleHighlight = (EVGElement.numberOr(value rippleHighlight))
|
|
4399
|
+
return
|
|
4400
|
+
}
|
|
4401
|
+
if (name == "evg-ripple-rings") {
|
|
4402
|
+
rippleRings = (EVGElement.numberOr(value rippleRings))
|
|
4403
|
+
return
|
|
4404
|
+
}
|
|
4405
|
+
if (name == "evg-ripple-stagger") {
|
|
4406
|
+
rippleStagger = (EVGElement.numberOr(value rippleStagger))
|
|
4407
|
+
return
|
|
4408
|
+
}
|
|
4409
|
+
if (name == "evg-ripple-ring-falloff") {
|
|
4410
|
+
rippleFalloff = (EVGElement.numberOr(value rippleFalloff))
|
|
4411
|
+
return
|
|
4412
|
+
}
|
|
4413
|
+
if (name == "evg-ripple-shine") {
|
|
4414
|
+
rippleShine = (EVGElement.numberOr(value rippleShine))
|
|
4415
|
+
return
|
|
4416
|
+
}
|
|
4417
|
+
if (name == "evg-ripple-gloss") {
|
|
4418
|
+
rippleGloss = (EVGElement.numberOr(value rippleGloss))
|
|
4419
|
+
return
|
|
4420
|
+
}
|
|
4421
|
+
if (name == "evg-ripple-bump") {
|
|
4422
|
+
rippleBump = (EVGElement.numberOr(value rippleBump))
|
|
4423
|
+
return
|
|
4424
|
+
}
|
|
4425
|
+
; Three numbers, and they do not have to be normalised — the shader
|
|
4426
|
+
; does that, because a direction somebody typed is a direction and not
|
|
4427
|
+
; a unit vector.
|
|
4428
|
+
if (name == "evg-ripple-light") {
|
|
4429
|
+
def lp:[string] (EVGElement.splitWords(value))
|
|
4430
|
+
if ((array_length lp) > 0) {
|
|
4431
|
+
rippleLightX = (EVGElement.numberOr((itemAt lp 0) rippleLightX))
|
|
4432
|
+
}
|
|
4433
|
+
if ((array_length lp) > 1) {
|
|
4434
|
+
rippleLightY = (EVGElement.numberOr((itemAt lp 1) rippleLightY))
|
|
4435
|
+
}
|
|
4436
|
+
if ((array_length lp) > 2) {
|
|
4437
|
+
rippleLightZ = (EVGElement.numberOr((itemAt lp 2) rippleLightZ))
|
|
4438
|
+
}
|
|
4439
|
+
return
|
|
4440
|
+
}
|
|
4441
|
+
; The scroll POSITION is settable, because a demo restoring where it
|
|
4442
|
+
; was is setting state and not styling. The extents are not: they are
|
|
4443
|
+
; the layout's answer, and an author writing one would only be telling
|
|
4444
|
+
; the layout what it is about to work out for itself.
|
|
4445
|
+
if ((name == "scroll-top") || (name == "scrollTop")) {
|
|
4446
|
+
def st@(optional):double (to_double value)
|
|
4447
|
+
scrollTop = (unwrap st)
|
|
4448
|
+
return
|
|
4449
|
+
}
|
|
4450
|
+
if ((name == "scroll-left") || (name == "scrollLeft")) {
|
|
4451
|
+
def sl@(optional):double (to_double value)
|
|
4452
|
+
scrollLeft = (unwrap sl)
|
|
4453
|
+
return
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
; Flexbox
|
|
4457
|
+
; `display` had no case here, so a stylesheet could set every grid
|
|
4458
|
+
; property and still lay out as a block — the container is only treated
|
|
4459
|
+
; as a grid when display == "grid".
|
|
4460
|
+
if (name == "display") {
|
|
4461
|
+
display = value
|
|
4462
|
+
return
|
|
4463
|
+
}
|
|
4464
|
+
if ((name == "flex-direction") || (name == "flexDirection")) {
|
|
4465
|
+
flexDirection = value
|
|
4466
|
+
; The reversed directions PARSE and then do nothing: the layout
|
|
4467
|
+
; branches on `== "column"` and lays every other value out as a
|
|
4468
|
+
; forward row. A value that is accepted and then ignored is the
|
|
4469
|
+
; same silence as one that is rejected, and worse, because the
|
|
4470
|
+
; property looks supported.
|
|
4471
|
+
if ((value == "row-reverse") || (value == "column-reverse")) {
|
|
4472
|
+
EVGReject.note("unsupported value" name value)
|
|
4473
|
+
}
|
|
4474
|
+
return
|
|
4475
|
+
}
|
|
4476
|
+
if ((name == "flex-wrap") || (name == "flexWrap")) {
|
|
4477
|
+
flexWrap = value
|
|
4478
|
+
return
|
|
4479
|
+
}
|
|
4480
|
+
; `flex-grow`, WHICH WAS NOT HERE. `flex-shrink`, `flex-basis` and the
|
|
4481
|
+
; `flex` shorthand all were, so a stylesheet using the shorthand worked
|
|
4482
|
+
; and one using the longhand was silently ignored — the property name
|
|
4483
|
+
; falls through to the end of this function and nothing says so.
|
|
4484
|
+
;
|
|
4485
|
+
; What that looked like: `.cx-rail { flex-grow: 1 }` in the stepper.
|
|
4486
|
+
; With the grow factor dropped the rail has no width of its own, so it
|
|
4487
|
+
; took the parent's whole 852px, each of the three did, the row
|
|
4488
|
+
; overflowed by a factor of three and the steps stacked down the page
|
|
4489
|
+
; on top of one another. Chromium gives each rail 320 — the 640 of free
|
|
4490
|
+
; space split two ways — which is what the oracle beside this now
|
|
4491
|
+
; asserts.
|
|
4492
|
+
; `position`, WHICH WAS ALSO NOT HERE. The field exists, the layout
|
|
4493
|
+
; branches on it — `if (kid.position == "absolute")` in half a dozen
|
|
4494
|
+
; places — and until now the ONLY way to set it was from Ranger code.
|
|
4495
|
+
; Every `position: absolute` written in a stylesheet was read, matched,
|
|
4496
|
+
; and dropped on the floor.
|
|
4497
|
+
;
|
|
4498
|
+
; What that looked like: `.cx-thumb { position: absolute }` on the
|
|
4499
|
+
; sliders. The thumb stayed in flow, so it took horizontal space beside
|
|
4500
|
+
; the filled range instead of riding over it, and the two overlapped by
|
|
4501
|
+
; exactly the thumb's overhang. `.fd-menu` in the filter bar is the same
|
|
4502
|
+
; fault with a louder symptom — a popup that pushes the rows below it
|
|
4503
|
+
; instead of covering them.
|
|
4504
|
+
;
|
|
4505
|
+
; `fixed` IS ITS OWN MODE NOW. It used to map to `absolute` because
|
|
4506
|
+
; nothing here modelled a viewport separate from the root; `viewportW`
|
|
4507
|
+
; and `viewportH` are inherited down the tree, so the containing block
|
|
4508
|
+
; a fixed box wants is one field away, and the one thing that made the
|
|
4509
|
+
; difference visible — a scroll — is a single translation this can be
|
|
4510
|
+
; left out of. See `EVGLayout.layoutChildren` and `applyScroll`.
|
|
4511
|
+
; `sticky` has no implementation at all and stays in flow, which is what
|
|
4512
|
+
; it does until it sticks.
|
|
4513
|
+
if (name == "position") {
|
|
4514
|
+
def pv:string (trim value)
|
|
4515
|
+
if (pv == "fixed") {
|
|
4516
|
+
position = "fixed"
|
|
4517
|
+
return
|
|
4518
|
+
}
|
|
4519
|
+
if (pv == "absolute") {
|
|
4520
|
+
position = "absolute"
|
|
4521
|
+
return
|
|
4522
|
+
}
|
|
4523
|
+
if ((pv == "static") || (pv == "relative") || (pv == "sticky")) {
|
|
4524
|
+
position = "relative"
|
|
4525
|
+
return
|
|
4526
|
+
}
|
|
4527
|
+
return
|
|
4528
|
+
}
|
|
4529
|
+
if ((name == "flex-grow") || (name == "flexGrow")) {
|
|
4530
|
+
def gv@(optional):double (to_double value)
|
|
4531
|
+
if gv {
|
|
4532
|
+
flex = (unwrap gv)
|
|
4533
|
+
}
|
|
4534
|
+
return
|
|
4535
|
+
}
|
|
4536
|
+
if ((name == "flex-shrink") || (name == "flexShrink")) {
|
|
4537
|
+
def sv@(optional):double (to_double value)
|
|
4538
|
+
if sv {
|
|
4539
|
+
flexShrink = (unwrap sv)
|
|
4540
|
+
}
|
|
4541
|
+
return
|
|
4542
|
+
}
|
|
4543
|
+
if ((name == "flex-basis") || (name == "flexBasis")) {
|
|
4544
|
+
flexBasis = (this.unitOf(name value))
|
|
4545
|
+
return
|
|
4546
|
+
}
|
|
4547
|
+
if (name == "flex") {
|
|
4548
|
+
this.setFlexShorthand(value)
|
|
4549
|
+
return
|
|
4550
|
+
}
|
|
4551
|
+
if (name == "gap") {
|
|
4552
|
+
gap = (this.unitOf(name value))
|
|
4553
|
+
return
|
|
4554
|
+
}
|
|
4555
|
+
if ((name == "row-gap") || (name == "rowGap")) {
|
|
4556
|
+
rowGap = (this.unitOf(name value))
|
|
4557
|
+
return
|
|
4558
|
+
}
|
|
4559
|
+
if ((name == "column-gap") || (name == "columnGap")) {
|
|
4560
|
+
columnGap = (this.unitOf(name value))
|
|
4561
|
+
return
|
|
4562
|
+
}
|
|
4563
|
+
if ((name == "grid-template-columns") || (name == "gridTemplateColumns")) {
|
|
4564
|
+
gridTemplateColumns = value
|
|
4565
|
+
return
|
|
4566
|
+
}
|
|
4567
|
+
if ((name == "grid-template-rows") || (name == "gridTemplateRows")) {
|
|
4568
|
+
gridTemplateRows = value
|
|
4569
|
+
return
|
|
4570
|
+
}
|
|
4571
|
+
if ((name == "grid-template-areas") || (name == "gridTemplateAreas")) {
|
|
4572
|
+
gridTemplateAreas = value
|
|
4573
|
+
return
|
|
4574
|
+
}
|
|
4575
|
+
if ((name == "grid-auto-flow") || (name == "gridAutoFlow")) {
|
|
4576
|
+
gridAutoFlow = value
|
|
4577
|
+
return
|
|
4578
|
+
}
|
|
4579
|
+
if ((name == "full-bleed") || (name == "fullBleed")) {
|
|
4580
|
+
fullBleed = ((value == "true") || (value == "1"))
|
|
4581
|
+
return
|
|
4582
|
+
}
|
|
4583
|
+
if ((name == "grid-area") || (name == "gridArea")) {
|
|
4584
|
+
gridArea = value
|
|
4585
|
+
return
|
|
4586
|
+
}
|
|
4587
|
+
if ((name == "grid-column") || (name == "gridColumn")) {
|
|
4588
|
+
gridColumn = value
|
|
4589
|
+
return
|
|
4590
|
+
}
|
|
4591
|
+
if ((name == "grid-row") || (name == "gridRow")) {
|
|
4592
|
+
gridRow = value
|
|
4593
|
+
return
|
|
4594
|
+
}
|
|
4595
|
+
if ((name == "justify-content") || (name == "justifyContent")) {
|
|
4596
|
+
justifyContent = value
|
|
4597
|
+
return
|
|
4598
|
+
}
|
|
4599
|
+
if ((name == "align-content") || (name == "alignContent")) {
|
|
4600
|
+
alignContent = value
|
|
4601
|
+
return
|
|
4602
|
+
}
|
|
4603
|
+
if ((name == "align-self") || (name == "alignSelf")) {
|
|
4604
|
+
if (value == "auto") {
|
|
4605
|
+
alignSelf = ""
|
|
4606
|
+
} {
|
|
4607
|
+
alignSelf = value
|
|
4608
|
+
}
|
|
4609
|
+
return
|
|
4610
|
+
}
|
|
4611
|
+
if ((name == "align-items") || (name == "alignItems")) {
|
|
4612
|
+
alignItems = value
|
|
4613
|
+
return
|
|
4614
|
+
}
|
|
4615
|
+
|
|
4616
|
+
; Typography
|
|
4617
|
+
if ((name == "font-size") || (name == "fontSize")) {
|
|
4618
|
+
fontSize = (this.unitOf(name value))
|
|
4619
|
+
fontSizeInherited = false
|
|
4620
|
+
return
|
|
4621
|
+
}
|
|
4622
|
+
if ((name == "font-family") || (name == "fontFamily")) {
|
|
4623
|
+
fontFamily = value
|
|
4624
|
+
return
|
|
4625
|
+
}
|
|
4626
|
+
if ((name == "font-weight") || (name == "fontWeight")) {
|
|
4627
|
+
fontWeight = value
|
|
4628
|
+
return
|
|
4629
|
+
}
|
|
4630
|
+
if ((name == "text-align") || (name == "textAlign")) {
|
|
4631
|
+
textAlign = value
|
|
4632
|
+
return
|
|
4633
|
+
}
|
|
4634
|
+
if ((name == "white-space") || (name == "whiteSpace")) {
|
|
4635
|
+
whiteSpace = (trim value)
|
|
4636
|
+
return
|
|
4637
|
+
}
|
|
4638
|
+
if ((name == "line-height") || (name == "lineHeight")) {
|
|
4639
|
+
; `normal` is the face's own line box, which is what zero means
|
|
4640
|
+
; here — see `lineHeight`.
|
|
4641
|
+
def t:string (trim value)
|
|
4642
|
+
if (t == "normal") {
|
|
4643
|
+
lineHeight = 0.0
|
|
4644
|
+
lineHeightUnit = (EVGUnit.unset())
|
|
4645
|
+
return
|
|
4646
|
+
}
|
|
4647
|
+
; A BARE NUMBER is a multiplier and a length is not. `to_double`
|
|
4648
|
+
; stops at the first character it cannot use, so "24px" would come
|
|
4649
|
+
; back 24 and be indistinguishable from "24" — which is exactly
|
|
4650
|
+
; how a 14px run got a 336-pixel line box.
|
|
4651
|
+
if (EVGUnit.isNumeric(t)) {
|
|
4652
|
+
def val@(optional):double (to_double t)
|
|
4653
|
+
if val {
|
|
4654
|
+
lineHeight = (unwrap val)
|
|
4655
|
+
lineHeightUnit = (EVGUnit.unset())
|
|
4656
|
+
}
|
|
4657
|
+
return
|
|
4658
|
+
}
|
|
4659
|
+
def u:EVGUnit (EVGUnit.parse(t))
|
|
4660
|
+
if u.isSet {
|
|
4661
|
+
lineHeightUnit = u
|
|
4662
|
+
lineHeight = 0.0
|
|
4663
|
+
}
|
|
4664
|
+
return
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
; Transform
|
|
4668
|
+
if (name == "transform") {
|
|
4669
|
+
this.applyTransform(value)
|
|
4670
|
+
return
|
|
4671
|
+
}
|
|
4672
|
+
if ((name == "transform-origin") || (name == "transformOrigin")) {
|
|
4673
|
+
this.applyTransformOrigin(value)
|
|
4674
|
+
return
|
|
4675
|
+
}
|
|
4676
|
+
if ((name == "translate-x") || (name == "translateX")) {
|
|
4677
|
+
def tvx@(optional):double (to_double value)
|
|
4678
|
+
if tvx { translateX = (unwrap tvx) }
|
|
4679
|
+
return
|
|
4680
|
+
}
|
|
4681
|
+
if ((name == "translate-y") || (name == "translateY")) {
|
|
4682
|
+
def tvy@(optional):double (to_double value)
|
|
4683
|
+
if tvy { translateY = (unwrap tvy) }
|
|
4684
|
+
return
|
|
4685
|
+
}
|
|
4686
|
+
if (name == "rotate") {
|
|
4687
|
+
def val@(optional):double (to_double value)
|
|
4688
|
+
rotate = (unwrap val)
|
|
4689
|
+
return
|
|
4690
|
+
}
|
|
4691
|
+
if (name == "scale") {
|
|
4692
|
+
def val@(optional):double (to_double value)
|
|
4693
|
+
scale = (unwrap val)
|
|
4694
|
+
return
|
|
4695
|
+
}
|
|
4696
|
+
|
|
4697
|
+
; Shadow
|
|
4698
|
+
if ((name == "shadow-radius") || (name == "shadowRadius")) {
|
|
4699
|
+
shadowRadius = (this.unitOf(name value))
|
|
4700
|
+
return
|
|
4701
|
+
}
|
|
4702
|
+
if ((name == "shadow-color") || (name == "shadowColor")) {
|
|
4703
|
+
shadowColor = (EVGColor.parse(value))
|
|
4704
|
+
return
|
|
4705
|
+
}
|
|
4706
|
+
if ((name == "shadow-offset-x") || (name == "shadowOffsetX")) {
|
|
4707
|
+
shadowOffsetX = (this.unitOf(name value))
|
|
4708
|
+
return
|
|
4709
|
+
}
|
|
4710
|
+
if ((name == "shadow-offset-y") || (name == "shadowOffsetY")) {
|
|
4711
|
+
shadowOffsetY = (this.unitOf(name value))
|
|
4712
|
+
return
|
|
4713
|
+
}
|
|
4714
|
+
|
|
4715
|
+
; Clip path (SVG path for clipping)
|
|
4716
|
+
if ((name == "clip-path") || (name == "clipPath")) {
|
|
4717
|
+
clipPath = value
|
|
4718
|
+
return
|
|
4719
|
+
}
|
|
4720
|
+
if (((name == "d") || (name == "svgPath")) || (name == "path")) {
|
|
4721
|
+
svgPath = value
|
|
4722
|
+
return
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4725
|
+
; PDF/Print settings
|
|
4726
|
+
if (name == "imageQuality") {
|
|
4727
|
+
def val@(optional):int (to_int value)
|
|
4728
|
+
if val {
|
|
4729
|
+
imageQuality = (unwrap val)
|
|
4730
|
+
}
|
|
4731
|
+
return
|
|
4732
|
+
}
|
|
4733
|
+
if (name == "maxImageSize") {
|
|
4734
|
+
def val@(optional):int (to_int value)
|
|
4735
|
+
if val {
|
|
4736
|
+
maxImageSize = (unwrap val)
|
|
4737
|
+
}
|
|
4738
|
+
return
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4741
|
+
; Anchors and connectors. `anchor-name` is CSS's; the rest belong to
|
|
4742
|
+
; the `connector` tag, which is EVG's own — CSS has no property that
|
|
4743
|
+
; draws a line between two elements.
|
|
4744
|
+
if ((name == "anchor-name") || (name == "anchorName")) {
|
|
4745
|
+
anchorName = (trim value)
|
|
4746
|
+
return
|
|
4747
|
+
}
|
|
4748
|
+
if (name == "from") {
|
|
4749
|
+
connectorFrom = (trim value)
|
|
4750
|
+
return
|
|
4751
|
+
}
|
|
4752
|
+
if (name == "to") {
|
|
4753
|
+
connectorTo = (trim value)
|
|
4754
|
+
return
|
|
4755
|
+
}
|
|
4756
|
+
if ((name == "from-side") || (name == "fromSide")) {
|
|
4757
|
+
connectorFromSide = (trim value)
|
|
4758
|
+
return
|
|
4759
|
+
}
|
|
4760
|
+
if ((name == "to-side") || (name == "toSide")) {
|
|
4761
|
+
connectorToSide = (trim value)
|
|
4762
|
+
return
|
|
4763
|
+
}
|
|
4764
|
+
if (name == "routing") {
|
|
4765
|
+
connectorRouting = (trim value)
|
|
4766
|
+
return
|
|
4767
|
+
}
|
|
4768
|
+
if ((name == "from-offset") || (name == "fromOffset")) {
|
|
4769
|
+
def fou:EVGUnit (this.unitOf(name value))
|
|
4770
|
+
if fou.isSet {
|
|
4771
|
+
connectorFromOffset = fou.pixels
|
|
4772
|
+
}
|
|
4773
|
+
return
|
|
4774
|
+
}
|
|
4775
|
+
if ((name == "to-offset") || (name == "toOffset")) {
|
|
4776
|
+
def tou:EVGUnit (this.unitOf(name value))
|
|
4777
|
+
if tou.isSet {
|
|
4778
|
+
connectorToOffset = tou.pixels
|
|
4779
|
+
}
|
|
4780
|
+
return
|
|
4781
|
+
}
|
|
4782
|
+
if ((name == "arrow-start") || (name == "arrowStart")) {
|
|
4783
|
+
arrowStart = (trim value)
|
|
4784
|
+
return
|
|
4785
|
+
}
|
|
4786
|
+
if ((name == "arrow-end") || (name == "arrowEnd")) {
|
|
4787
|
+
arrowEnd = (trim value)
|
|
4788
|
+
return
|
|
4789
|
+
}
|
|
4790
|
+
if ((name == "arrow-size") || (name == "arrowSize")) {
|
|
4791
|
+
def asu:EVGUnit (this.unitOf(name value))
|
|
4792
|
+
if asu.isSet {
|
|
4793
|
+
arrowSize = asu.pixels
|
|
4794
|
+
}
|
|
4795
|
+
return
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4798
|
+
; SVG Path attributes
|
|
4799
|
+
if ((name == "d") || (name == "svgPath")) {
|
|
4800
|
+
svgPath = value
|
|
4801
|
+
return
|
|
4802
|
+
}
|
|
4803
|
+
if ((name == "svg") || (name == "svgSource")) {
|
|
4804
|
+
svgSource = value
|
|
4805
|
+
return
|
|
4806
|
+
}
|
|
4807
|
+
if ((name == "viewBox") || (name == "view-box")) {
|
|
4808
|
+
viewBox = value
|
|
4809
|
+
return
|
|
4810
|
+
}
|
|
4811
|
+
if (name == "fill") {
|
|
4812
|
+
fillColor = (EVGColor.parse(value))
|
|
4813
|
+
return
|
|
4814
|
+
}
|
|
4815
|
+
if (name == "stroke") {
|
|
4816
|
+
strokeColor = (EVGColor.parse(value))
|
|
4817
|
+
return
|
|
4818
|
+
}
|
|
4819
|
+
if ((name == "stroke-width") || (name == "strokeWidth")) {
|
|
4820
|
+
def val@(optional):double (to_double value)
|
|
4821
|
+
if val {
|
|
4822
|
+
strokeWidth = (unwrap val)
|
|
4823
|
+
}
|
|
4824
|
+
return
|
|
4825
|
+
}
|
|
4826
|
+
if ((name == "stroke-dasharray") || (name == "strokeDasharray")) {
|
|
4827
|
+
strokeDashArray = value
|
|
4828
|
+
return
|
|
4829
|
+
}
|
|
4830
|
+
if ((name == "stroke-dashoffset") || (name == "strokeDashoffset")) {
|
|
4831
|
+
def dv@(optional):double (to_double value)
|
|
4832
|
+
if dv {
|
|
4833
|
+
strokeDashOffset = (unwrap dv)
|
|
4834
|
+
}
|
|
4835
|
+
return
|
|
4836
|
+
}
|
|
4837
|
+
if ((name == "fill-rule") || (name == "fillRule")) {
|
|
4838
|
+
if (value == "evenodd") {
|
|
4839
|
+
fillRule = "evenodd"
|
|
4840
|
+
} {
|
|
4841
|
+
fillRule = "nonzero"
|
|
4842
|
+
}
|
|
4843
|
+
return
|
|
4844
|
+
}
|
|
4845
|
+
; Nothing above claimed it. A property this engine does not implement
|
|
4846
|
+
; is the other half of the silence problem: `align-self` and
|
|
4847
|
+
; `aspect-ratio` used to fall off the end of this chain and change
|
|
4848
|
+
; nothing, with no way to tell that from a property that had been
|
|
4849
|
+
; applied and made no difference.
|
|
4850
|
+
if ((EVGElement.isHostProp(name)) == false) {
|
|
4851
|
+
EVGReject.note("unknown property" name value)
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4855
|
+
; Names that reach `setAttribute` from an authoring layer and are NOT
|
|
4856
|
+
; styling: React-shaped handlers, the tree factory's own bookkeeping, and
|
|
4857
|
+
; the `data-` namespace an application owns. Rejecting these would bury the
|
|
4858
|
+
; real complaints in noise from every button on the page.
|
|
4859
|
+
sfn isHostProp:boolean (name:string) {
|
|
4860
|
+
def n:int (strlen name)
|
|
4861
|
+
if (n == 0) {
|
|
4862
|
+
return true
|
|
4863
|
+
}
|
|
4864
|
+
if (n > 2) {
|
|
4865
|
+
def c0:int (charAt name 0)
|
|
4866
|
+
def c1:int (charAt name 1)
|
|
4867
|
+
def c2:int (charAt name 2)
|
|
4868
|
+
; onClick, onPress, onChange … — "on" then a capital, so `opacity`
|
|
4869
|
+
; and `order` are not swept up with them.
|
|
4870
|
+
if (((c0 == 111) && (c1 == 110)) && ((c2 >= 65) && (c2 <= 90))) {
|
|
4871
|
+
return true
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
if (n > 5) {
|
|
4875
|
+
if ((EVGElement.startsWithStr(name "data-")) ) {
|
|
4876
|
+
return true
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
if (name == "children") { return true }
|
|
4880
|
+
if (name == "style") { return true }
|
|
4881
|
+
if (name == "ref") { return true }
|
|
4882
|
+
if (name == "tag") { return true }
|
|
4883
|
+
if (name == "tagName") { return true }
|
|
4884
|
+
if (name == "text") { return true }
|
|
4885
|
+
if (name == "textContent") { return true }
|
|
4886
|
+
if (name == "elementType") { return true }
|
|
4887
|
+
if (name == "test-id") { return true }
|
|
4888
|
+
if (name == "testId") { return true }
|
|
4889
|
+
return false
|
|
4890
|
+
}
|
|
4891
|
+
|
|
4892
|
+
sfn startsWithStr:boolean (s:string prefix:string) {
|
|
4893
|
+
def n:int (strlen prefix)
|
|
4894
|
+
if ((strlen s) < n) {
|
|
4895
|
+
return false
|
|
4896
|
+
}
|
|
4897
|
+
def i:int 0
|
|
4898
|
+
while (i < n) {
|
|
4899
|
+
if ((charAt s i) != (charAt prefix i)) {
|
|
4900
|
+
return false
|
|
4901
|
+
}
|
|
4902
|
+
i = (i + 1)
|
|
4903
|
+
}
|
|
4904
|
+
return true
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
fn getCalculatedBounds:string () {
|
|
4908
|
+
return ("(" + (to_string calculatedX) + ", " + (to_string calculatedY) + ") " + (to_string calculatedWidth) + "x" + (to_string calculatedHeight))
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4911
|
+
fn toString:string () {
|
|
4912
|
+
return ("<" + tagName + " id=\"" + id + "\" " + (this.getCalculatedBounds()) + ">")
|
|
4913
|
+
}
|
|
4914
|
+
}
|