ranger-compiler 3.5.0 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1160 -19
- package/LICENSE +3 -1
- package/README.md +124 -29
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79924 -56922
- package/dist/lib/JSON.rgr +102 -91
- package/dist/lib/Shell.rgr +4 -4
- package/dist/lib/apple/AppleToolchain.rgr +4 -4
- package/dist/lib/apple/README.md +5 -4
- package/dist/lib/apple/apple_test.rgr +1 -1
- package/dist/lib/core/README.md +1 -1
- package/dist/lib/evg/EVG.rgr +12 -0
- package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
- package/dist/lib/evg/EVGA11yTree.rgr +894 -0
- package/dist/lib/evg/EVGBox.rgr +267 -0
- package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
- package/dist/lib/evg/EVGCodepoint.rgr +316 -0
- package/dist/lib/evg/EVGColor.rgr +700 -0
- package/dist/lib/evg/EVGCommands.rgr +177 -0
- package/dist/lib/evg/EVGComponent.rgr +331 -0
- package/dist/lib/evg/EVGComponentTest.rgr +387 -0
- package/dist/lib/evg/EVGConnector.rgr +541 -0
- package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
- package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
- package/dist/lib/evg/EVGEasing.rgr +370 -0
- package/dist/lib/evg/EVGEffectTest.rgr +253 -0
- package/dist/lib/evg/EVGElement.rgr +4914 -0
- package/dist/lib/evg/EVGFixedTest.rgr +289 -0
- package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
- package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
- package/dist/lib/evg/EVGFling.rgr +244 -0
- package/dist/lib/evg/EVGFocus.rgr +400 -0
- package/dist/lib/evg/EVGFocusTest.rgr +399 -0
- package/dist/lib/evg/EVGGradient.rgr +322 -0
- package/dist/lib/evg/EVGGrapheme.rgr +190 -0
- package/dist/lib/evg/EVGGrid.rgr +975 -0
- package/dist/lib/evg/EVGHitTest.rgr +208 -0
- package/dist/lib/evg/EVGHoles.rgr +294 -0
- package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
- package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
- package/dist/lib/evg/EVGHostTree.rgr +834 -0
- package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
- package/dist/lib/evg/EVGImageDecode.rgr +117 -0
- package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
- package/dist/lib/evg/EVGInspect.rgr +883 -0
- package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
- package/dist/lib/evg/EVGJsonTest.rgr +535 -0
- package/dist/lib/evg/EVGLayout.rgr +4220 -0
- package/dist/lib/evg/EVGMeasure.rgr +1267 -0
- package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
- package/dist/lib/evg/EVGPatch.rgr +1721 -0
- package/dist/lib/evg/EVGPatchTest.rgr +692 -0
- package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
- package/dist/lib/evg/EVGReconcile.rgr +226 -0
- package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
- package/dist/lib/evg/EVGReject.rgr +126 -0
- package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
- package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
- package/dist/lib/evg/EVGRuler.rgr +232 -0
- package/dist/lib/evg/EVGRulerTest.rgr +172 -0
- package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
- package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
- package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
- package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
- package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
- package/dist/lib/evg/EVGText.rgr +105 -0
- package/dist/lib/evg/EVGTextEngine.rgr +536 -0
- package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
- package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
- package/dist/lib/evg/EVGToolbar.rgr +1369 -0
- package/dist/lib/evg/EVGTransition.rgr +728 -0
- package/dist/lib/evg/EVGTreeJson.rgr +721 -0
- package/dist/lib/evg/EVGUnit.rgr +554 -0
- package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
- package/dist/lib/evg/EvgApp.rgr +189 -0
- package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
- package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
- package/dist/lib/evg/EvgHost.rgr +451 -0
- package/dist/lib/evg/EvgTest.rgr +91 -0
- package/dist/lib/evg/EvgTraceColor.rgr +236 -0
- package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
- package/dist/lib/evg/EvgTraceFit.rgr +767 -0
- package/dist/lib/evg/EvgTracePath.rgr +435 -0
- package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
- package/dist/lib/evg/EvgViewport.rgr +313 -0
- package/dist/lib/evg/FxDemoDoc.rgr +185 -0
- package/dist/lib/evg/HOSTS.md +227 -0
- package/dist/lib/evg/ISSUES.md +835 -0
- package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
- package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
- package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
- package/dist/lib/evg/PLAN_EVG.md +518 -0
- package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
- package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
- package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
- package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
- package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
- package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
- package/dist/lib/evg/PathBuilder.rgr +333 -0
- package/dist/lib/evg/README.md +1236 -0
- package/dist/lib/evg/SPEC.md +1252 -0
- package/dist/lib/evg/SVGPathParser.rgr +1199 -0
- package/dist/lib/evg/SvgParser.rgr +1736 -0
- package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
- package/dist/lib/evg/VectorShapes.rgr +331 -0
- package/dist/lib/evg/VectorStroke.rgr +107 -0
- package/dist/lib/evg/VectorViewBox.rgr +380 -0
- package/dist/lib/evg/agent/README.md +290 -0
- package/dist/lib/evg/agent/evg_agent.rgr +899 -0
- package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
- package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
- package/dist/lib/evg/agent/fixtures/connector.css +63 -0
- package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
- package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
- package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
- package/dist/lib/evg/agent/fixtures/popover.css +99 -0
- package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
- package/dist/lib/evg/agent/roundtrip.sh +74 -0
- package/dist/lib/evg/agent/smoke.sh +230 -0
- package/dist/lib/evg/android/README.md +97 -0
- package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
- package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
- package/dist/lib/evg/android/androidstubs/App.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
- package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
- package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
- package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
- package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
- package/dist/lib/evg/android/androidstubs/View.kt +113 -0
- package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
- package/dist/lib/evg/apple/README.md +128 -0
- package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
- package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
- package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
- package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
- package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
- package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
- package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
- package/dist/lib/evg/bench/README.md +64 -0
- package/dist/lib/evg/bench/layout-bench.mjs +316 -0
- package/dist/lib/evg/bench/layout-cases.mjs +510 -0
- package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
- package/dist/lib/evg/bin/.gitignore +17 -0
- package/dist/lib/evg/evg_test.rgr +313 -0
- package/dist/lib/evg/gl/README.md +198 -0
- package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
- package/dist/lib/evg/gl/blur-check.mjs +399 -0
- package/dist/lib/evg/gl/boxmodel.json +1 -0
- package/dist/lib/evg/gl/demo.html +46 -0
- package/dist/lib/evg/gl/effect-presets.css +285 -0
- package/dist/lib/evg/gl/effect-presets.js +63 -0
- package/dist/lib/evg/gl/effect-shots.mjs +135 -0
- package/dist/lib/evg/gl/evg-a11y.js +566 -0
- package/dist/lib/evg/gl/evg-binary.js +160 -0
- package/dist/lib/evg/gl/evg-engine.js +296 -0
- package/dist/lib/evg/gl/evg-fx.js +237 -0
- package/dist/lib/evg/gl/evg-gestures.js +209 -0
- package/dist/lib/evg/gl/evg-list.js +167 -0
- package/dist/lib/evg/gl/evg-measure.js +186 -0
- package/dist/lib/evg/gl/evg-textinput.js +303 -0
- package/dist/lib/evg/gl/evg-view.js +144 -0
- package/dist/lib/evg/gl/evg-webgl.js +3942 -0
- package/dist/lib/evg/gl/fx-check.mjs +998 -0
- package/dist/lib/evg/gl/fx-demo.html +166 -0
- package/dist/lib/evg/gl/fx-demo.js +2 -0
- package/dist/lib/evg/gl/fx-serve.mjs +47 -0
- package/dist/lib/evg/gl/gestures-check.mjs +189 -0
- package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
- package/dist/lib/evg/gl/measure-check.mjs +135 -0
- package/dist/lib/evg/gl/rotation-check.mjs +207 -0
- package/dist/lib/evg/gl/shift-check.mjs +113 -0
- package/dist/lib/evg/gl/stroke-check.mjs +168 -0
- package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
- package/dist/lib/evg/gl/view-check.mjs +343 -0
- package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
- package/dist/lib/evg/html/evg-dom.js +318 -0
- package/dist/lib/evg/html/evg-html.js +600 -0
- package/dist/lib/evg/inspect/README.md +415 -0
- package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
- package/dist/lib/evg/inspect/evg-inspect.js +947 -0
- package/dist/lib/evg/inspect/shots/css.png +0 -0
- package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
- package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
- package/dist/lib/evg/inspect/shots/state.png +0 -0
- package/dist/lib/evg/inspect/shots.mjs +226 -0
- package/dist/lib/evg/oracle/css-blur.json +576 -0
- package/dist/lib/evg/oracle/css-box.json +157 -0
- package/dist/lib/evg/oracle/css-timing.json +709 -0
- package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
- package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
- package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
- package/dist/lib/evg/original/EVGColor.clj +310 -0
- package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
- package/dist/lib/evg/original/SVGPath.rgr +627 -0
- package/dist/lib/evg/original/Vec2.crgr +103 -0
- package/dist/lib/evg/ranger.json +9 -0
- package/dist/lib/evg/showcase/README.md +246 -0
- package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
- package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
- package/dist/lib/evg/showcase/build.mjs +651 -0
- package/dist/lib/evg/showcase/pages/album.tsx +30 -0
- package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
- package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
- package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
- package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
- package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
- package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
- package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
- package/dist/lib/evg/showcase/pages/more.tsx +332 -0
- package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
- package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
- package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
- package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
- package/dist/lib/evg/showcase/pages/units.tsx +35 -0
- package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
- package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
- package/dist/lib/evg/showcase/pages/views.tsx +223 -0
- package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
- package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
- package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
- package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
- package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
- package/dist/lib/evg/showcase/themes/more-default.css +128 -0
- package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
- package/dist/lib/evg/showcase/themes/showcase.css +508 -0
- package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
- package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
- package/dist/lib/evg/showcase/themes/views-default.css +15 -0
- package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
- package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
- package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
- package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
- package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
- package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
- package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
- package/dist/lib/evg/web/responsive/README.md +97 -0
- package/dist/lib/evg/web/responsive/build.mjs +90 -0
- package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
- package/dist/lib/evg/web/responsive/index.html +177 -0
- package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
- package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
- package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
- package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
- package/dist/lib/evg/web/tools/minify.mjs +60 -0
- package/dist/lib/evg/web/tracer/build.mjs +87 -0
- package/dist/lib/evg/web/tracer/index.html +2805 -0
- package/dist/lib/evg/web/tracer/sample.png +0 -0
- package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
- package/dist/lib/evgr/Cargo.lock +21 -0
- package/dist/lib/evgr/Cargo.toml +19 -0
- package/dist/lib/evgr/README.md +140 -0
- package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
- package/dist/lib/evgr/bench/compare.mjs +252 -0
- package/dist/lib/evgr/bench/speed.mjs +277 -0
- package/dist/lib/evgr/bin/.gitignore +3 -0
- package/dist/lib/evgr/src/bin/bench.rs +106 -0
- package/dist/lib/evgr/src/bin/smoke.rs +13 -0
- package/dist/lib/evgr/src/grid.rs +730 -0
- package/dist/lib/evgr/src/lib.rs +1003 -0
- package/dist/lib/evgr/src/style.rs +468 -0
- package/dist/lib/evgr/src/text.rs +83 -0
- package/dist/lib/image/BitReader.rgr +171 -0
- package/dist/lib/image/Buffer.rgr +173 -0
- package/dist/lib/image/DCT.rgr +283 -0
- package/dist/lib/image/Deflate.rgr +339 -0
- package/dist/lib/image/HuffmanDecoder.rgr +181 -0
- package/dist/lib/image/ImageBuffer.rgr +706 -0
- package/dist/lib/image/JPEGDecoder.rgr +808 -0
- package/dist/lib/image/PNGDecoder.rgr +544 -0
- package/dist/lib/image/PNGEncoder.rgr +388 -0
- package/dist/lib/image/PPMImage.rgr +224 -0
- package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
- package/dist/lib/image/README.md +33 -0
- package/dist/lib/image/RasterBuffer.rgr +294 -0
- package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
- package/dist/lib/image/WebPDecoder.rgr +433 -0
- package/dist/lib/image/WebPLossless.rgr +1035 -0
- package/dist/lib/image/WebPLossy.rgr +1929 -0
- package/dist/lib/image/ranger.json +9 -0
- package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
- package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
- package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
- package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
- package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
- package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
- package/dist/lib/image/tests/run_webp_tests.sh +39 -0
- package/dist/lib/rust/RsJson.rgr +468 -0
- package/dist/lib/rust/RsPrelude.rgr +1663 -0
- package/dist/lib/shell_test.rgr +7 -7
- package/dist/lib/stdlib.rgr +21 -5
- package/dist/lib/zip/ranger.json +6 -0
- package/dist/rgrc.js +87318 -67610
- package/package.json +1010 -1040
- package/dist/README.md +0 -117
- package/dist/package.json +0 -47
|
@@ -0,0 +1,1267 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
; ==============================================================================
|
|
4
|
+
; EVGMeasure — lay a document out and answer, in numbers, what is wrong with it
|
|
5
|
+
; ==============================================================================
|
|
6
|
+
;
|
|
7
|
+
; A model cannot see a screen. Asked whether a layout is right, it renders a
|
|
8
|
+
; picture and describes what it expected to be there. This lays the document
|
|
9
|
+
; out with the real engine and reports the geometry that decides the answer:
|
|
10
|
+
; what overlaps and by how much, what left its parent or the page, how far each
|
|
11
|
+
; element is from the next, and how much room is left at the bottom.
|
|
12
|
+
;
|
|
13
|
+
; --- WHY THIS IS NOT INSIDE THE CLI -------------------------------------------
|
|
14
|
+
;
|
|
15
|
+
; It was, and a second, weaker copy grew in the live-build server: off-page
|
|
16
|
+
; only, no overlap, no distances. Two implementations of "is this layout
|
|
17
|
+
; wrong?" answer differently, and the one the agent reads was the one that
|
|
18
|
+
; knew less. The checks live here so both doors ask the same question.
|
|
19
|
+
;
|
|
20
|
+
; --- WHAT COUNTS AS A FINDING -------------------------------------------------
|
|
21
|
+
;
|
|
22
|
+
; A finding is a fact with a number in it, never an opinion about taste. Off
|
|
23
|
+
; the page, out of a clipping parent, and two in-flow siblings on top of each
|
|
24
|
+
; other are defects at any size. Crowding is reported separately as `tight`,
|
|
25
|
+
; because 2px between two cards is a judgement call the caller makes; the
|
|
26
|
+
; measurement is not.
|
|
27
|
+
;
|
|
28
|
+
; Absolutely positioned nodes are excluded from overlap and from the free-space
|
|
29
|
+
; number: lying on top of the flow is what they are for, and a bottom bar would
|
|
30
|
+
; otherwise report the page as full.
|
|
31
|
+
; ==============================================================================
|
|
32
|
+
|
|
33
|
+
Import "EVGElement.rgr"
|
|
34
|
+
Import "EVGInspect.rgr"
|
|
35
|
+
Import "EVGLayout.rgr"
|
|
36
|
+
Import "EVGTreeJson.rgr"
|
|
37
|
+
Import "EVGUnit.rgr"
|
|
38
|
+
|
|
39
|
+
class EVGMeasure {
|
|
40
|
+
def findings:[string]
|
|
41
|
+
def tight:[string]
|
|
42
|
+
def align:[string]
|
|
43
|
+
; Controls somebody DREW: a pill with a circle in it is a switch, a small
|
|
44
|
+
; square with a tick in it is a checkbox. They are not defects — a picture
|
|
45
|
+
; may want one — so they are not in `findings` and do not move `count`.
|
|
46
|
+
; They are the one thing about a screen that a layout check can see and a
|
|
47
|
+
; person cannot: it looks right, and nothing about it works.
|
|
48
|
+
def drawn:[string]
|
|
49
|
+
; Controls that ARE the real thing and still cannot move: a switch whose
|
|
50
|
+
; state is a fixed class word, or whose colours are written on the node.
|
|
51
|
+
; The opposite mistake to `drawn` and a harder one to see, because the
|
|
52
|
+
; picture is right — it is the only one of the two that survives "use the
|
|
53
|
+
; host's control" and still does nothing when pressed.
|
|
54
|
+
def frozen:[string]
|
|
55
|
+
; Set by a caller that is measuring a page whose holes a machine has
|
|
56
|
+
; ALREADY filled — an app's page at check time. There a fixed state word
|
|
57
|
+
; is the correct result of binding, not a control nailed shut, so that
|
|
58
|
+
; half of the `frozen` check is off. Inline paint still counts: it
|
|
59
|
+
; outranks the sheet whoever wrote it.
|
|
60
|
+
def boundView:boolean false
|
|
61
|
+
def boxes:[string]
|
|
62
|
+
def width:int 0
|
|
63
|
+
def height:int 0
|
|
64
|
+
def nodes:int 0
|
|
65
|
+
def bottomFree:double 0.0
|
|
66
|
+
def hasFlow:boolean false
|
|
67
|
+
def tightMore:int 0
|
|
68
|
+
def insp:EVGInspect
|
|
69
|
+
|
|
70
|
+
Constructor () {
|
|
71
|
+
insp = (new EVGInspect())
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
; Lay out and collect. `at` narrows the box listing to one subtree; "" asks
|
|
75
|
+
; for none, because the listing is the only part of this that grows with
|
|
76
|
+
; the document and most callers want the findings alone.
|
|
77
|
+
fn look:void (root:EVGElement w:int h:int at:string) {
|
|
78
|
+
def fresh:[string]
|
|
79
|
+
findings = fresh
|
|
80
|
+
def fresh2:[string]
|
|
81
|
+
tight = fresh2
|
|
82
|
+
def fresh2b:[string]
|
|
83
|
+
align = fresh2b
|
|
84
|
+
def fresh2c:[string]
|
|
85
|
+
drawn = fresh2c
|
|
86
|
+
def fresh2d:[string]
|
|
87
|
+
frozen = fresh2d
|
|
88
|
+
def fresh3:[string]
|
|
89
|
+
boxes = fresh3
|
|
90
|
+
width = w
|
|
91
|
+
height = h
|
|
92
|
+
bottomFree = 0.0
|
|
93
|
+
hasFlow = false
|
|
94
|
+
tightMore = 0
|
|
95
|
+
|
|
96
|
+
def l (new EVGLayout())
|
|
97
|
+
l.setPageSize((to_double w) (to_double h))
|
|
98
|
+
l.layout(root)
|
|
99
|
+
insp.build(root)
|
|
100
|
+
nodes = (insp.count())
|
|
101
|
+
|
|
102
|
+
def pw:double (to_double w)
|
|
103
|
+
def ph:double (to_double h)
|
|
104
|
+
def lowest:double 0.0
|
|
105
|
+
def i:int 0
|
|
106
|
+
while (i < nodes) {
|
|
107
|
+
def path:string (insp.pathAt(i))
|
|
108
|
+
def hit@(optional):EVGElement (insp.elementAt(path))
|
|
109
|
+
if ((null? hit) == false) {
|
|
110
|
+
def el:EVGElement (unwrap hit)
|
|
111
|
+
this.checkOffPage(path el pw ph)
|
|
112
|
+
this.checkOverflow(path el)
|
|
113
|
+
this.checkSiblings(path el)
|
|
114
|
+
this.checkAlignment(path el)
|
|
115
|
+
this.checkAbsolutes(path el)
|
|
116
|
+
this.checkDrawnControl(path el)
|
|
117
|
+
this.checkDrawnSlider(path el)
|
|
118
|
+
this.checkFrozenControl(path el)
|
|
119
|
+
; The free space is the room left under the CONTENT, so a top
|
|
120
|
+
; level node is not counted: a root that is the page itself
|
|
121
|
+
; would answer 0 for every document and mean nothing by it.
|
|
122
|
+
if ((EVGMeasure.lastSlash(path)) > 0) {
|
|
123
|
+
if ((el.hasAbsolutePosition()) == false) {
|
|
124
|
+
def bottom:double (el.calculatedY + el.calculatedHeight)
|
|
125
|
+
if (bottom > lowest) {
|
|
126
|
+
lowest = bottom
|
|
127
|
+
hasFlow = true
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if ((strlen at) > 0) {
|
|
132
|
+
if (EVGMeasure.under(path at)) {
|
|
133
|
+
this.addBox(path el)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
i = (i + 1)
|
|
138
|
+
}
|
|
139
|
+
bottomFree = (ph - lowest)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
; The whole answer. `pretty` is not decoration: the CLI prints this for a
|
|
143
|
+
; model to read and puts one finding per line, while the live-build wire is
|
|
144
|
+
; NDJSON and a newline inside an event ends the event. One writer, so the
|
|
145
|
+
; two can never drift apart in content.
|
|
146
|
+
fn json:string (pretty:boolean) {
|
|
147
|
+
def out:string "{\"width\":"
|
|
148
|
+
out = (out + (to_string width))
|
|
149
|
+
out = ((out + ",\"height\":") + (to_string height))
|
|
150
|
+
out = ((out + ",\"nodes\":") + (to_string nodes))
|
|
151
|
+
out = ((out + ",\"findings\":") + (EVGMeasure.strArray(findings pretty)))
|
|
152
|
+
out = ((out + ",\"count\":") + (to_string (array_length findings)))
|
|
153
|
+
if hasFlow {
|
|
154
|
+
out = ((out + ",\"bottomFree\":") + (EVGMeasure.num(bottomFree)))
|
|
155
|
+
}
|
|
156
|
+
if ((array_length tight) > 0) {
|
|
157
|
+
out = ((out + ",\"tight\":") + (EVGMeasure.strArray(tight pretty)))
|
|
158
|
+
if (tightMore > 0) {
|
|
159
|
+
out = ((out + ",\"tightMore\":") + (to_string tightMore))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if ((array_length align) > 0) {
|
|
163
|
+
out = ((out + ",\"align\":") + (EVGMeasure.strArray(align pretty)))
|
|
164
|
+
}
|
|
165
|
+
if ((array_length drawn) > 0) {
|
|
166
|
+
out = ((out + ",\"drawn\":") + (EVGMeasure.strArray(drawn pretty)))
|
|
167
|
+
}
|
|
168
|
+
if ((array_length frozen) > 0) {
|
|
169
|
+
out = ((out + ",\"frozen\":") + (EVGMeasure.strArray(frozen pretty)))
|
|
170
|
+
}
|
|
171
|
+
if ((array_length boxes) > 0) {
|
|
172
|
+
out = (out + ",\"boxes\":[")
|
|
173
|
+
def i:int 0
|
|
174
|
+
while (i < (array_length boxes)) {
|
|
175
|
+
if (i > 0) {
|
|
176
|
+
out = (out + ",")
|
|
177
|
+
}
|
|
178
|
+
if pretty {
|
|
179
|
+
out = (out + "\n ")
|
|
180
|
+
}
|
|
181
|
+
out = (out + (itemAt boxes i))
|
|
182
|
+
i = (i + 1)
|
|
183
|
+
}
|
|
184
|
+
if pretty {
|
|
185
|
+
out = (out + "\n")
|
|
186
|
+
}
|
|
187
|
+
out = (out + "]")
|
|
188
|
+
}
|
|
189
|
+
return (out + "}")
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
; The same numbers small enough to ride along with something else — what
|
|
193
|
+
; `patch` says about the document it just wrote. `limit` caps the findings
|
|
194
|
+
; quoted; the count is always the real one.
|
|
195
|
+
fn summary:string (limit:int) {
|
|
196
|
+
def out:string "{\"nodes\":"
|
|
197
|
+
out = (out + (to_string nodes))
|
|
198
|
+
out = ((out + ",\"count\":") + (to_string (array_length findings)))
|
|
199
|
+
if hasFlow {
|
|
200
|
+
out = ((out + ",\"bottomFree\":") + (EVGMeasure.num(bottomFree)))
|
|
201
|
+
}
|
|
202
|
+
if ((array_length drawn) > 0) {
|
|
203
|
+
out = ((out + ",\"drawn\":") + (to_string (array_length drawn)))
|
|
204
|
+
}
|
|
205
|
+
; A count would say "something is wrong" and nothing else, and the
|
|
206
|
+
; whole value of this check is the sentence. One line costs little and
|
|
207
|
+
; is the one an agent can act on.
|
|
208
|
+
if ((array_length frozen) > 0) {
|
|
209
|
+
out = ((out + ",\"frozen\":") + (to_string (array_length frozen)))
|
|
210
|
+
out = ((out + ",\"frozenFirst\":") + (EVGTreeJson.str((itemAt frozen 0))))
|
|
211
|
+
}
|
|
212
|
+
def n:int (array_length findings)
|
|
213
|
+
if (n > 0) {
|
|
214
|
+
def shown:int n
|
|
215
|
+
if (shown > limit) {
|
|
216
|
+
shown = limit
|
|
217
|
+
}
|
|
218
|
+
out = (out + ",\"findings\":[")
|
|
219
|
+
def i:int 0
|
|
220
|
+
while (i < shown) {
|
|
221
|
+
if (i > 0) {
|
|
222
|
+
out = (out + ",")
|
|
223
|
+
}
|
|
224
|
+
out = (out + (EVGTreeJson.str((itemAt findings i))))
|
|
225
|
+
i = (i + 1)
|
|
226
|
+
}
|
|
227
|
+
out = (out + "]")
|
|
228
|
+
if (n > shown) {
|
|
229
|
+
out = ((out + ",\"more\":") + (to_string (n - shown)))
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return (out + "}")
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
; --- the checks -----------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
; --- a control somebody drew ----------------------------------------------
|
|
238
|
+
; The one defect a layout check can see and a person cannot. A switch made
|
|
239
|
+
; of a 48×28 pill with a 22px circle in it looks right in the picture and
|
|
240
|
+
; is not a control: nothing presses it, it has no state to report, and a
|
|
241
|
+
; reader is told about a box. Naming it is the whole job here — what to
|
|
242
|
+
; use instead is the host's business, because this engine does not know
|
|
243
|
+
; which kit is on the other side of it.
|
|
244
|
+
|
|
245
|
+
sfn radiusPx:double (el:EVGElement) {
|
|
246
|
+
def r:EVGUnit el.box.borderRadius
|
|
247
|
+
if r {
|
|
248
|
+
if r.isSet {
|
|
249
|
+
return r.pixels
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return 0.0
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
; Round enough that somebody meant a circle or a pill.
|
|
256
|
+
sfn isRound:boolean (el:EVGElement) {
|
|
257
|
+
def h:double el.calculatedHeight
|
|
258
|
+
if (h < 6.0) {
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
return ((EVGMeasure.radiusPx(el)) >= ((h * 0.5) - 1.5))
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
sfn hasWords:boolean (el:EVGElement) {
|
|
265
|
+
if ((strlen (trim el.textContent)) > 0) {
|
|
266
|
+
return true
|
|
267
|
+
}
|
|
268
|
+
def i:int 0
|
|
269
|
+
while (i < (array_length el.children)) {
|
|
270
|
+
def c:EVGElement (itemAt el.children i)
|
|
271
|
+
if (EVGMeasure.hasWords(c)) {
|
|
272
|
+
return true
|
|
273
|
+
}
|
|
274
|
+
i = (i + 1)
|
|
275
|
+
}
|
|
276
|
+
return false
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
; Whether something in this node's ancestry already SAYS it is a control.
|
|
280
|
+
; A real one declares a role and hides its parts from the reader, and its
|
|
281
|
+
; track is a pill with a knob in it like any drawn switch — the difference
|
|
282
|
+
; is exactly that it said so.
|
|
283
|
+
fn claimedByControl:boolean (path:string el:EVGElement) {
|
|
284
|
+
if (el.a11yHidden) {
|
|
285
|
+
return true
|
|
286
|
+
}
|
|
287
|
+
def at:string path
|
|
288
|
+
def guard:int 0
|
|
289
|
+
while ((guard < 32) && ((strlen at) > 0)) {
|
|
290
|
+
def hit@(optional):EVGElement (insp.elementAt(at))
|
|
291
|
+
if (null? hit) {
|
|
292
|
+
return false
|
|
293
|
+
}
|
|
294
|
+
def node:EVGElement (unwrap hit)
|
|
295
|
+
if ((strlen node.role) > 0) {
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
if (node.a11yFocusable) {
|
|
299
|
+
return true
|
|
300
|
+
}
|
|
301
|
+
at = (insp.parentOf(at))
|
|
302
|
+
guard = (guard + 1)
|
|
303
|
+
}
|
|
304
|
+
return false
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
fn checkDrawnControl:void (path:string el:EVGElement) {
|
|
308
|
+
if ((array_length el.children) != 1) {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
if (this.claimedByControl(path el)) {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
def kid:EVGElement (itemAt el.children 0)
|
|
315
|
+
def w:double el.calculatedWidth
|
|
316
|
+
def h:double el.calculatedHeight
|
|
317
|
+
def kw:double kid.calculatedWidth
|
|
318
|
+
def kh:double kid.calculatedHeight
|
|
319
|
+
if ((h < 12.0) || (h > 44.0)) {
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
if ((kh < 6.0) || (kw < 6.0)) {
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
; A SWITCH: a pill about twice as wide as it is tall, with one round
|
|
326
|
+
; knob inside it that nearly fills its height and says nothing.
|
|
327
|
+
if (EVGMeasure.isRound(el)) {
|
|
328
|
+
if (EVGMeasure.isRound(kid)) {
|
|
329
|
+
def ratio:double (w / h)
|
|
330
|
+
def fill:double (kh / h)
|
|
331
|
+
if ((ratio > 1.35) && (ratio < 2.6) && (fill > 0.55) && (fill < 0.95)) {
|
|
332
|
+
if ((EVGMeasure.hasWords(kid)) == false) {
|
|
333
|
+
push drawn (path + ": a pill with a knob in it is a drawn switch — use the host's switch control, not boxes")
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
; A CHECKBOX: a small square-ish box with a tick drawn or typed in it.
|
|
340
|
+
def square:double (w / h)
|
|
341
|
+
if ((square > 0.75) && (square < 1.35) && (h >= 14.0) && (h <= 34.0)) {
|
|
342
|
+
def mark:boolean false
|
|
343
|
+
if ((strlen kid.svgPath) > 0) {
|
|
344
|
+
mark = true
|
|
345
|
+
}
|
|
346
|
+
def t:string (trim kid.textContent)
|
|
347
|
+
if ((t == "✓") || (t == "✔") || (t == "×") || (t == "x") || (t == "-") || (t == "–")) {
|
|
348
|
+
mark = true
|
|
349
|
+
}
|
|
350
|
+
if mark {
|
|
351
|
+
push drawn (path + ": a box with a tick in it is a drawn checkbox — use the host's checkbox control, not boxes")
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
; --- a control that cannot move --------------------------------------------
|
|
357
|
+
; `drawn` catches the screen that never asked for a control. This catches
|
|
358
|
+
; the one that did and got a photograph of it.
|
|
359
|
+
;
|
|
360
|
+
; A control works in an app when two things are true of the document:
|
|
361
|
+
;
|
|
362
|
+
; the state is a HOLE class-name="ui-switch ui-switch-state-{wifi}"
|
|
363
|
+
; the paint is in the SHEET .ui-switch-track-state-checked { … }
|
|
364
|
+
;
|
|
365
|
+
; The hole is what `evg_app init` reads to give the control a context key
|
|
366
|
+
; and an event — with a fixed word there instead, the machine is written
|
|
367
|
+
; without it and a press on it is not an event of any state. The sheet is
|
|
368
|
+
; what the hole then switches; a colour written on the node is inline and
|
|
369
|
+
; outranks every rule, so even a bound control repainted that way never
|
|
370
|
+
; changes. Either one alone is enough to freeze it, and both together is
|
|
371
|
+
; what an agent that wrote the document in one pass usually produces.
|
|
372
|
+
;
|
|
373
|
+
; Like `drawn` these are not defects — a picture of a settings screen is
|
|
374
|
+
; allowed to be a picture — so they stay out of `findings` and out of
|
|
375
|
+
; `count`, and say what to do instead.
|
|
376
|
+
fn checkFrozenControl:void (path:string el:EVGElement) {
|
|
377
|
+
def role:string el.role
|
|
378
|
+
def isCtl:boolean false
|
|
379
|
+
if (role == "switch") {
|
|
380
|
+
isCtl = true
|
|
381
|
+
}
|
|
382
|
+
if (role == "checkbox") {
|
|
383
|
+
isCtl = true
|
|
384
|
+
}
|
|
385
|
+
if (role == "radio") {
|
|
386
|
+
isCtl = true
|
|
387
|
+
}
|
|
388
|
+
if (isCtl == false) {
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
def cn:string el.className
|
|
392
|
+
def stateClass:string (EVGMeasure.stateClassIn(cn))
|
|
393
|
+
def bound:boolean ((indexOf cn "{") >= 0)
|
|
394
|
+
if boundView {
|
|
395
|
+
bound = true
|
|
396
|
+
}
|
|
397
|
+
def paint:string (EVGMeasure.inlinePaintIn(el 0))
|
|
398
|
+
def named:boolean ((strlen el.id) > 0)
|
|
399
|
+
; A control with no state class at all is not claiming to have two
|
|
400
|
+
; looks, so there is nothing here to call frozen.
|
|
401
|
+
if ((strlen stateClass) == 0) {
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
if (bound && ((strlen paint) == 0)) {
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
def why:string ""
|
|
408
|
+
if (bound == false) {
|
|
409
|
+
why = ((("its state is the fixed class " + stateClass) + " rather than ") + (EVGMeasure.holeFor(stateClass)))
|
|
410
|
+
}
|
|
411
|
+
if ((strlen paint) > 0) {
|
|
412
|
+
if ((strlen why) > 0) {
|
|
413
|
+
why = (why + ", and ")
|
|
414
|
+
}
|
|
415
|
+
why = (((why + "its ") + paint) + " is written on the node, which outranks every rule")
|
|
416
|
+
}
|
|
417
|
+
def who:string ((role + " \"") + el.id)
|
|
418
|
+
if (named == false) {
|
|
419
|
+
who = (role + " \"(no id)")
|
|
420
|
+
}
|
|
421
|
+
def fix:string " — put the state in a {key} and the colours in the sheet"
|
|
422
|
+
if (named == false) {
|
|
423
|
+
fix = " — and it has no id, so a press cannot reach it either"
|
|
424
|
+
}
|
|
425
|
+
push frozen ((((path + ": ") + who) + "\" cannot change: ") + (why + fix))
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
; `ui-switch-state-checked` said the way a bound one would be.
|
|
429
|
+
sfn holeFor:string (stateClass:string) {
|
|
430
|
+
def cut:int (indexOf stateClass "-state-")
|
|
431
|
+
if (cut < 0) {
|
|
432
|
+
return stateClass
|
|
433
|
+
}
|
|
434
|
+
return ((substring stateClass 0 (cut + 7)) + "{key}")
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
; The first class token carrying a state word, quoted back to whoever
|
|
438
|
+
; wrote it. "" when there is none.
|
|
439
|
+
sfn stateClassIn:string (className:string) {
|
|
440
|
+
def n:int (strlen className)
|
|
441
|
+
def at:int 0
|
|
442
|
+
while (at < n) {
|
|
443
|
+
def rest:string (substring className at n)
|
|
444
|
+
def sp:int (indexOf rest " ")
|
|
445
|
+
def end:int n
|
|
446
|
+
if (sp >= 0) {
|
|
447
|
+
end = (at + sp)
|
|
448
|
+
}
|
|
449
|
+
def tok:string (substring className at end)
|
|
450
|
+
if ((indexOf tok "-state-") > 0) {
|
|
451
|
+
return tok
|
|
452
|
+
}
|
|
453
|
+
at = (end + 1)
|
|
454
|
+
}
|
|
455
|
+
return ""
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
; The first paint property written on this element or on a part of it.
|
|
459
|
+
; Only the ones a state switches are asked for: a width on a track is a
|
|
460
|
+
; size the sheet is welcome to keep, a colour on it is the state.
|
|
461
|
+
sfn inlinePaintIn:string (el:EVGElement depth:int) {
|
|
462
|
+
if (el.hasInline("background-color")) {
|
|
463
|
+
return "background-color"
|
|
464
|
+
}
|
|
465
|
+
if (el.hasInline("stroke")) {
|
|
466
|
+
return "stroke"
|
|
467
|
+
}
|
|
468
|
+
if (el.hasInline("fill")) {
|
|
469
|
+
return "fill"
|
|
470
|
+
}
|
|
471
|
+
if (el.hasInline("transform")) {
|
|
472
|
+
return "transform"
|
|
473
|
+
}
|
|
474
|
+
if (el.hasInline("border-color")) {
|
|
475
|
+
return "border-color"
|
|
476
|
+
}
|
|
477
|
+
if (depth > 3) {
|
|
478
|
+
return ""
|
|
479
|
+
}
|
|
480
|
+
def i:int 0
|
|
481
|
+
while (i < (array_length el.children)) {
|
|
482
|
+
def sub:string (EVGMeasure.inlinePaintIn((itemAt el.children i) (depth + 1)))
|
|
483
|
+
if ((strlen sub) > 0) {
|
|
484
|
+
return sub
|
|
485
|
+
}
|
|
486
|
+
i = (i + 1)
|
|
487
|
+
}
|
|
488
|
+
return ""
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
; A SLIDER: a long thin groove with one round knob standing proud of it.
|
|
492
|
+
;
|
|
493
|
+
; This is a separate check rather than a third case inside
|
|
494
|
+
; `checkDrawnControl` because that one's shape guard — a box 12 to 44
|
|
495
|
+
; pixels tall — is what a switch and a checkbox have and a slider does
|
|
496
|
+
; not. A volume groove is four to eight pixels tall with a knob three
|
|
497
|
+
; times that sitting on it, so every drawn slider ever made fell out of
|
|
498
|
+
; the guard on the first line and nothing was ever said about it.
|
|
499
|
+
;
|
|
500
|
+
; What separates a knob from a progress bar's fill is that a knob is
|
|
501
|
+
; ROUND, about as wide as it is tall, and TALLER than the groove. A fill
|
|
502
|
+
; is as tall as the groove and as wide as the value, so it fails all
|
|
503
|
+
; three, and a progress bar drawn by hand is not reported here.
|
|
504
|
+
fn checkDrawnSlider:void (path:string el:EVGElement) {
|
|
505
|
+
if ((array_length el.children) != 1) {
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
if (this.claimedByControl(path el)) {
|
|
509
|
+
return
|
|
510
|
+
}
|
|
511
|
+
def w:double el.calculatedWidth
|
|
512
|
+
def h:double el.calculatedHeight
|
|
513
|
+
if ((h < 2.0) || (h > 16.0)) {
|
|
514
|
+
return
|
|
515
|
+
}
|
|
516
|
+
if (w < 60.0) {
|
|
517
|
+
return
|
|
518
|
+
}
|
|
519
|
+
if ((w / h) < 4.0) {
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
def kid:EVGElement (itemAt el.children 0)
|
|
523
|
+
def kw:double kid.calculatedWidth
|
|
524
|
+
def kh:double kid.calculatedHeight
|
|
525
|
+
if ((kw < 10.0) || (kh < 10.0)) {
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
if (kh <= h) {
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
def square:double (kw / kh)
|
|
532
|
+
if ((square < 0.75) || (square > 1.35)) {
|
|
533
|
+
return
|
|
534
|
+
}
|
|
535
|
+
if ((EVGMeasure.isRound(kid)) == false) {
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
if (EVGMeasure.hasWords(kid)) {
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
push drawn (path + ": a groove with a knob standing proud of it is a drawn slider — use the host's slider control, not boxes")
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
fn checkOffPage:void (path:string el:EVGElement w:double h:double) {
|
|
545
|
+
; A `path` carries its geometry in `d` and its box is 0x0, so every
|
|
546
|
+
; check below would pass on a shape that is nowhere near the page. A
|
|
547
|
+
; diagram exported from RangerFlow is ALL paths, which made this
|
|
548
|
+
; silent on exactly the documents it was most wanted for.
|
|
549
|
+
if ((strlen el.svgPath) > 0) {
|
|
550
|
+
this.checkPathOffPage(path el w h)
|
|
551
|
+
return
|
|
552
|
+
}
|
|
553
|
+
def right:double (el.calculatedX + el.calculatedWidth)
|
|
554
|
+
def bottom:double (el.calculatedY + el.calculatedHeight)
|
|
555
|
+
if (right > (w + 0.5)) {
|
|
556
|
+
push findings ((((path + ": right edge ") + (EVGMeasure.num(right))) + " is past the page width ") + (EVGMeasure.num(w)))
|
|
557
|
+
}
|
|
558
|
+
if (bottom > (h + 0.5)) {
|
|
559
|
+
push findings ((((path + ": bottom edge ") + (EVGMeasure.num(bottom))) + " is past the page height ") + (EVGMeasure.num(h)))
|
|
560
|
+
}
|
|
561
|
+
if (el.calculatedX < (0.0 - 0.5)) {
|
|
562
|
+
push findings ((path + ": left edge ") + (EVGMeasure.num(el.calculatedX)))
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
; The outer bound of a path, from the numbers in its `d`.
|
|
567
|
+
;
|
|
568
|
+
; Every coordinate a curve names — its control points included — lies
|
|
569
|
+
; outside or on the curve, so the extremes of the whole list bound the
|
|
570
|
+
; drawn shape. That is looser than the true bounds and it is the right kind
|
|
571
|
+
; of loose here: a shape reported as on the page is definitely on it.
|
|
572
|
+
;
|
|
573
|
+
; Only M, L, C, Q and Z are read. `A` mixes radii and flags in among the
|
|
574
|
+
; coordinates, and treating those as points would invent a bound rather
|
|
575
|
+
; than widen one, so a path using anything else is left unchecked and said
|
|
576
|
+
; to be.
|
|
577
|
+
fn checkPathOffPage:void (path:string el:EVGElement w:double h:double) {
|
|
578
|
+
if (EVGMeasure.hasUnreadCommand(el.svgPath)) {
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
def xs:[double]
|
|
582
|
+
def ys:[double]
|
|
583
|
+
EVGMeasure.coordsOf(el.svgPath xs ys)
|
|
584
|
+
if ((array_length xs) == 0) {
|
|
585
|
+
return
|
|
586
|
+
}
|
|
587
|
+
def minX:double (itemAt xs 0)
|
|
588
|
+
def maxX:double (itemAt xs 0)
|
|
589
|
+
def i:int 1
|
|
590
|
+
while (i < (array_length xs)) {
|
|
591
|
+
def v:double (itemAt xs i)
|
|
592
|
+
if (v < minX) {
|
|
593
|
+
minX = v
|
|
594
|
+
}
|
|
595
|
+
if (v > maxX) {
|
|
596
|
+
maxX = v
|
|
597
|
+
}
|
|
598
|
+
i = (i + 1)
|
|
599
|
+
}
|
|
600
|
+
def minY:double (itemAt ys 0)
|
|
601
|
+
def maxY:double (itemAt ys 0)
|
|
602
|
+
def k:int 1
|
|
603
|
+
while (k < (array_length ys)) {
|
|
604
|
+
def v:double (itemAt ys k)
|
|
605
|
+
if (v < minY) {
|
|
606
|
+
minY = v
|
|
607
|
+
}
|
|
608
|
+
if (v > maxY) {
|
|
609
|
+
maxY = v
|
|
610
|
+
}
|
|
611
|
+
k = (k + 1)
|
|
612
|
+
}
|
|
613
|
+
if (maxX > (w + 0.5)) {
|
|
614
|
+
push findings ((((path + ": the shape reaches ") + (EVGMeasure.num(maxX))) + ", past the page width ") + (EVGMeasure.num(w)))
|
|
615
|
+
}
|
|
616
|
+
if (maxY > (h + 0.5)) {
|
|
617
|
+
push findings ((((path + ": the shape reaches ") + (EVGMeasure.num(maxY))) + ", past the page height ") + (EVGMeasure.num(h)))
|
|
618
|
+
}
|
|
619
|
+
if (minX < (0.0 - 0.5)) {
|
|
620
|
+
push findings ((path + ": the shape starts at ") + (EVGMeasure.num(minX)))
|
|
621
|
+
}
|
|
622
|
+
if (minY < (0.0 - 0.5)) {
|
|
623
|
+
push findings ((path + ": the shape starts above the page, at ") + (EVGMeasure.num(minY)))
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
; A child wider or taller than the box it is in. `overflow: visible` says
|
|
628
|
+
; the author MEANT it to spill, so only a clipping parent is a finding —
|
|
629
|
+
; otherwise every deliberate overhang is reported as a defect.
|
|
630
|
+
; A child's address, the way `EVGInspect` writes it — the key when it has
|
|
631
|
+
; one, the index otherwise. A finding that names "0/0/1" for a node whose
|
|
632
|
+
; real address is "0/0/k:title" sends the reader to patch the wrong thing.
|
|
633
|
+
sfn segOf:string (kid:EVGElement i:int) {
|
|
634
|
+
if ((strlen kid.key) > 0) {
|
|
635
|
+
return ("k:" + kid.key)
|
|
636
|
+
}
|
|
637
|
+
return (to_string i)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
fn checkOverflow:void (path:string el:EVGElement) {
|
|
641
|
+
if (el.overflow == "visible") {
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
def i:int 0
|
|
645
|
+
while (i < (array_length el.children)) {
|
|
646
|
+
def kid:EVGElement (itemAt el.children i)
|
|
647
|
+
def kidRight:double (kid.calculatedX + kid.calculatedWidth)
|
|
648
|
+
def myRight:double (el.calculatedX + el.calculatedWidth)
|
|
649
|
+
def kidBottom:double (kid.calculatedY + kid.calculatedHeight)
|
|
650
|
+
def myBottom:double (el.calculatedY + el.calculatedHeight)
|
|
651
|
+
def who:string ((path + "/") + (EVGMeasure.segOf(kid i)))
|
|
652
|
+
if (kidRight > (myRight + 0.5)) {
|
|
653
|
+
push findings ((who + " overflows its parent to the right by ") + (EVGMeasure.num((kidRight - myRight))))
|
|
654
|
+
}
|
|
655
|
+
if (kidBottom > (myBottom + 0.5)) {
|
|
656
|
+
push findings ((who + " overflows its parent below by ") + (EVGMeasure.num((kidBottom - myBottom))))
|
|
657
|
+
}
|
|
658
|
+
i = (i + 1)
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
; --- WHY ALIGNMENT IS MEASURED AND NOT LEFT TO TASTE ----------------------
|
|
663
|
+
;
|
|
664
|
+
; A screen where nothing lines up passes every check above: no overlap, no
|
|
665
|
+
; overflow, everything on the page, and it still looks like it fell down
|
|
666
|
+
; the stairs. It is also the defect an agent is least able to catch, since
|
|
667
|
+
; the markup it wrote says `padding: 16px` in six places and the picture
|
|
668
|
+
; does not, and nothing in the document names the edge the eye is looking
|
|
669
|
+
; for.
|
|
670
|
+
;
|
|
671
|
+
; So this asks the one question that has an answer: do these children share
|
|
672
|
+
; an edge? A stack whose lefts agree is left-aligned, one whose centres
|
|
673
|
+
; agree is centred, one whose rights agree is right-aligned — all three are
|
|
674
|
+
; deliberate. A stack that agrees on NONE of them was not aligned to
|
|
675
|
+
; anything, and that is what gets reported, with the spread in pixels.
|
|
676
|
+
;
|
|
677
|
+
; Two children a few pixels apart get their own line: 3px is never a design
|
|
678
|
+
; decision, it is an edge somebody meant to share.
|
|
679
|
+
fn checkAlignment:void (path:string el:EVGElement) {
|
|
680
|
+
def kids:[EVGElement]
|
|
681
|
+
def names:[string]
|
|
682
|
+
def n:int (array_length el.children)
|
|
683
|
+
def i:int 0
|
|
684
|
+
while (i < n) {
|
|
685
|
+
def kid:EVGElement (itemAt el.children i)
|
|
686
|
+
if ((kid.hasAbsolutePosition()) == false) {
|
|
687
|
+
if ((kid.calculatedWidth > 0.0) && (kid.calculatedHeight > 0.0)) {
|
|
688
|
+
push kids kid
|
|
689
|
+
push names ((path + "/") + (EVGMeasure.segOf(kid i)))
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
i = (i + 1)
|
|
693
|
+
}
|
|
694
|
+
def k:int (array_length kids)
|
|
695
|
+
if (k < 3) {
|
|
696
|
+
return
|
|
697
|
+
}
|
|
698
|
+
; A row is measured against its top edges, a column against its lefts.
|
|
699
|
+
; Anything that is neither — a wrapped run, a grid — has no single edge
|
|
700
|
+
; to be off, so it is left alone rather than guessed at.
|
|
701
|
+
def dir:string (EVGMeasure.stackOf(kids))
|
|
702
|
+
if (dir == "none") {
|
|
703
|
+
return
|
|
704
|
+
}
|
|
705
|
+
def starts:[double]
|
|
706
|
+
def middles:[double]
|
|
707
|
+
def ends:[double]
|
|
708
|
+
def j:int 0
|
|
709
|
+
while (j < k) {
|
|
710
|
+
def kid:EVGElement (itemAt kids j)
|
|
711
|
+
def a:double kid.calculatedX
|
|
712
|
+
def len:double kid.calculatedWidth
|
|
713
|
+
if (dir == "row") {
|
|
714
|
+
a = kid.calculatedY
|
|
715
|
+
len = kid.calculatedHeight
|
|
716
|
+
}
|
|
717
|
+
push starts a
|
|
718
|
+
push middles (a + (len / 2.0))
|
|
719
|
+
push ends (a + len)
|
|
720
|
+
j = (j + 1)
|
|
721
|
+
}
|
|
722
|
+
def edge:string "left"
|
|
723
|
+
if (dir == "row") {
|
|
724
|
+
edge = "top"
|
|
725
|
+
}
|
|
726
|
+
def sSpread:double (EVGMeasure.spreadOf(starts))
|
|
727
|
+
def mSpread:double (EVGMeasure.spreadOf(middles))
|
|
728
|
+
def eSpread:double (EVGMeasure.spreadOf(ends))
|
|
729
|
+
def best:double sSpread
|
|
730
|
+
if (mSpread < best) {
|
|
731
|
+
best = mSpread
|
|
732
|
+
}
|
|
733
|
+
if (eSpread < best) {
|
|
734
|
+
best = eSpread
|
|
735
|
+
}
|
|
736
|
+
if (best <= 0.5) {
|
|
737
|
+
; Aligned to something on purpose. A near miss inside an otherwise
|
|
738
|
+
; tidy stack is still worth a line.
|
|
739
|
+
this.nearMiss(names starts edge)
|
|
740
|
+
return
|
|
741
|
+
}
|
|
742
|
+
if (best <= 4.0) {
|
|
743
|
+
push align ((((((EVGMeasure.pathHead(names)) + ": ") + (to_string k)) + " children are ") + (EVGMeasure.num(best))) + "px off a shared edge — nothing lines up exactly")
|
|
744
|
+
this.nearMiss(names starts edge)
|
|
745
|
+
return
|
|
746
|
+
}
|
|
747
|
+
push align ((((((((EVGMeasure.pathHead(names)) + ": ") + (to_string k)) + " children share no edge — ") + edge) + " spread ") + (EVGMeasure.num(sSpread))) + "px")
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
; --- WHY AN ABSOLUTE CHILD IS CHECKED SEPARATELY --------------------------
|
|
751
|
+
;
|
|
752
|
+
; An overlay is exempt from overlap and from the free-space number, and it
|
|
753
|
+
; still has to line up with the column it floats over — a bottom bar 12px
|
|
754
|
+
; to the right of every card under it is wrong, and no check above sees it,
|
|
755
|
+
; because the bar is off the flow and lands on the page either way.
|
|
756
|
+
;
|
|
757
|
+
; The cause is nearly always the same, and it is worth naming rather than
|
|
758
|
+
; only measuring: `left` on an absolute child is resolved from inside the
|
|
759
|
+
; parent's padding, so a 12px padding plus `left: 12px` puts the child at
|
|
760
|
+
; 24 while the flow starts at 12. The padding is already there; asking for
|
|
761
|
+
; it again moves the child by exactly one padding.
|
|
762
|
+
fn checkAbsolutes:void (path:string el:EVGElement) {
|
|
763
|
+
def lefts:[double]
|
|
764
|
+
def rights:[double]
|
|
765
|
+
def n:int (array_length el.children)
|
|
766
|
+
def i:int 0
|
|
767
|
+
while (i < n) {
|
|
768
|
+
def kid:EVGElement (itemAt el.children i)
|
|
769
|
+
if ((kid.hasAbsolutePosition()) == false) {
|
|
770
|
+
if ((kid.calculatedWidth > 0.0) && (kid.calculatedHeight > 0.0)) {
|
|
771
|
+
push lefts kid.calculatedX
|
|
772
|
+
push rights (kid.calculatedX + kid.calculatedWidth)
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
i = (i + 1)
|
|
776
|
+
}
|
|
777
|
+
if ((array_length lefts) < 2) {
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
def leftShared:boolean ((EVGMeasure.spreadOf(lefts)) <= 0.5)
|
|
781
|
+
def rightShared:boolean ((EVGMeasure.spreadOf(rights)) <= 0.5)
|
|
782
|
+
if ((leftShared == false) && (rightShared == false)) {
|
|
783
|
+
return
|
|
784
|
+
}
|
|
785
|
+
def flowLeft:double (itemAt lefts 0)
|
|
786
|
+
def flowRight:double (itemAt rights 0)
|
|
787
|
+
def padLeft:double (flowLeft - el.calculatedX)
|
|
788
|
+
def k:int 0
|
|
789
|
+
while (k < n) {
|
|
790
|
+
def kid:EVGElement (itemAt el.children k)
|
|
791
|
+
if (kid.hasAbsolutePosition()) {
|
|
792
|
+
if ((kid.calculatedWidth > 0.0) && (kid.calculatedHeight > 0.0)) {
|
|
793
|
+
def who:string ((path + "/") + (EVGMeasure.segOf(kid k)))
|
|
794
|
+
if (leftShared) {
|
|
795
|
+
this.offEdge(who "starts" kid.calculatedX flowLeft padLeft)
|
|
796
|
+
}
|
|
797
|
+
if (rightShared) {
|
|
798
|
+
def mine:double (kid.calculatedX + kid.calculatedWidth)
|
|
799
|
+
this.offEdge(who "ends" mine flowRight (0.0 - padLeft))
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
k = (k + 1)
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
; One edge of one overlay against the column it floats over.
|
|
808
|
+
fn offEdge:void (who:string verb:string mine:double theirs:double pad:double) {
|
|
809
|
+
def d:double (mine - theirs)
|
|
810
|
+
def size:double d
|
|
811
|
+
if (size < 0.0) {
|
|
812
|
+
size = (0.0 - size)
|
|
813
|
+
}
|
|
814
|
+
if (size <= 0.5) {
|
|
815
|
+
return
|
|
816
|
+
}
|
|
817
|
+
; Past a screen's worth it is a different design, not a missed edge.
|
|
818
|
+
if (size > 24.0) {
|
|
819
|
+
return
|
|
820
|
+
}
|
|
821
|
+
if ((array_length align) >= 12) {
|
|
822
|
+
return
|
|
823
|
+
}
|
|
824
|
+
def line:string (((((((who + " ") + verb) + " at ") + (EVGMeasure.num(mine))) + ", the flow at ") + (EVGMeasure.num(theirs))) + " — ")
|
|
825
|
+
def padSize:double pad
|
|
826
|
+
if (padSize < 0.0) {
|
|
827
|
+
padSize = (0.0 - padSize)
|
|
828
|
+
}
|
|
829
|
+
def near:double (d - pad)
|
|
830
|
+
if (near < 0.0) {
|
|
831
|
+
near = (0.0 - near)
|
|
832
|
+
}
|
|
833
|
+
if ((padSize > 0.5) && (near <= 0.5)) {
|
|
834
|
+
push align (line + ("that is one padding: an absolute left/right is measured from inside the parent's padding, so asking for it again adds it twice"))
|
|
835
|
+
return
|
|
836
|
+
}
|
|
837
|
+
push align (line + ((EVGMeasure.num(size)) + "px off the shared edge"))
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
; Two children that nearly agree. Reported by address, because "somewhere
|
|
841
|
+
; in this stack" is not something anybody can act on.
|
|
842
|
+
fn nearMiss:void (names:[string] starts:[double] edge:string) {
|
|
843
|
+
def n:int (array_length starts)
|
|
844
|
+
def i:int 0
|
|
845
|
+
while (i < n) {
|
|
846
|
+
def j:int (i + 1)
|
|
847
|
+
while (j < n) {
|
|
848
|
+
def d:double ((itemAt starts i) - (itemAt starts j))
|
|
849
|
+
if (d < 0.0) {
|
|
850
|
+
d = (0.0 - d)
|
|
851
|
+
}
|
|
852
|
+
if (d > 0.5) {
|
|
853
|
+
if (d <= 4.0) {
|
|
854
|
+
if ((array_length align) < 12) {
|
|
855
|
+
push align ((((((itemAt names i) + " and ") + (itemAt names j)) + ": ") + edge) + ((" edges " + (EVGMeasure.num(d))) + "px apart — align them or mean it"))
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
j = (j + 1)
|
|
860
|
+
}
|
|
861
|
+
i = (i + 1)
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
; Are these children a column, a row, or neither? Each one has to start
|
|
866
|
+
; after the one before it on the same axis; a single exception makes it
|
|
867
|
+
; neither, which is the honest answer for a wrap or a grid.
|
|
868
|
+
sfn stackOf:string (kids:[EVGElement]) {
|
|
869
|
+
def n:int (array_length kids)
|
|
870
|
+
def column:boolean true
|
|
871
|
+
def row:boolean true
|
|
872
|
+
def i:int 1
|
|
873
|
+
while (i < n) {
|
|
874
|
+
def prev:EVGElement (itemAt kids (i - 1))
|
|
875
|
+
def cur:EVGElement (itemAt kids i)
|
|
876
|
+
if (cur.calculatedY < ((prev.calculatedY + prev.calculatedHeight) - 0.5)) {
|
|
877
|
+
column = false
|
|
878
|
+
}
|
|
879
|
+
if (cur.calculatedX < ((prev.calculatedX + prev.calculatedWidth) - 0.5)) {
|
|
880
|
+
row = false
|
|
881
|
+
}
|
|
882
|
+
i = (i + 1)
|
|
883
|
+
}
|
|
884
|
+
if (column) {
|
|
885
|
+
return "column"
|
|
886
|
+
}
|
|
887
|
+
if (row) {
|
|
888
|
+
return "row"
|
|
889
|
+
}
|
|
890
|
+
return "none"
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
sfn spreadOf:double (vals:[double]) {
|
|
894
|
+
def n:int (array_length vals)
|
|
895
|
+
if (n == 0) {
|
|
896
|
+
return 0.0
|
|
897
|
+
}
|
|
898
|
+
def lo:double (itemAt vals 0)
|
|
899
|
+
def hi:double (itemAt vals 0)
|
|
900
|
+
def i:int 1
|
|
901
|
+
while (i < n) {
|
|
902
|
+
def v:double (itemAt vals i)
|
|
903
|
+
if (v < lo) {
|
|
904
|
+
lo = v
|
|
905
|
+
}
|
|
906
|
+
if (v > hi) {
|
|
907
|
+
hi = v
|
|
908
|
+
}
|
|
909
|
+
i = (i + 1)
|
|
910
|
+
}
|
|
911
|
+
return (hi - lo)
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
; The parent's address, read off the first child's.
|
|
915
|
+
sfn pathHead:string (names:[string]) {
|
|
916
|
+
if ((array_length names) == 0) {
|
|
917
|
+
return "0"
|
|
918
|
+
}
|
|
919
|
+
def first:string (itemAt names 0)
|
|
920
|
+
def cut:int (EVGMeasure.lastSlash(first))
|
|
921
|
+
if (cut < 0) {
|
|
922
|
+
return first
|
|
923
|
+
}
|
|
924
|
+
return (substring first 0 cut)
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
; Neighbours: what lies on top of what, and how far apart the rest are.
|
|
928
|
+
;
|
|
929
|
+
; The distance is the interesting half. "They do not overlap" is what a
|
|
930
|
+
; renderer already guarantees; "these two cards are 2px apart and those two
|
|
931
|
+
; are 24" is the thing a model cannot get from the document it wrote,
|
|
932
|
+
; because the gap is the layout's answer, not the markup's.
|
|
933
|
+
fn checkSiblings:void (path:string el:EVGElement) {
|
|
934
|
+
def n:int (array_length el.children)
|
|
935
|
+
def i:int 0
|
|
936
|
+
while (i < n) {
|
|
937
|
+
def a:EVGElement (itemAt el.children i)
|
|
938
|
+
def j:int (i + 1)
|
|
939
|
+
while (j < n) {
|
|
940
|
+
def b:EVGElement (itemAt el.children j)
|
|
941
|
+
if (((a.hasAbsolutePosition()) == false) && ((b.hasAbsolutePosition()) == false)) {
|
|
942
|
+
this.pair(path i a j b)
|
|
943
|
+
}
|
|
944
|
+
j = (j + 1)
|
|
945
|
+
}
|
|
946
|
+
i = (i + 1)
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
; One pair of in-flow siblings, once their boxes are known.
|
|
951
|
+
fn pair:void (path:string i:int a:EVGElement j:int b:EVGElement) {
|
|
952
|
+
def nameA:string ((path + "/") + (EVGMeasure.segOf(a i)))
|
|
953
|
+
def nameB:string ((path + "/") + (EVGMeasure.segOf(b j)))
|
|
954
|
+
if (EVGMeasure.overlaps(a b)) {
|
|
955
|
+
def ax2:double (a.calculatedX + a.calculatedWidth)
|
|
956
|
+
def bx2:double (b.calculatedX + b.calculatedWidth)
|
|
957
|
+
def ay2:double (a.calculatedY + a.calculatedHeight)
|
|
958
|
+
def by2:double (b.calculatedY + b.calculatedHeight)
|
|
959
|
+
def overX:double (EVGMeasure.lower(ax2 bx2) - EVGMeasure.higher(a.calculatedX b.calculatedX))
|
|
960
|
+
def overY:double (EVGMeasure.lower(ay2 by2) - EVGMeasure.higher(a.calculatedY b.calculatedY))
|
|
961
|
+
push findings ((((((nameA + " and ") + nameB) + " overlap by ") + (EVGMeasure.num(overX))) + "×") + (EVGMeasure.num(overY)))
|
|
962
|
+
return
|
|
963
|
+
}
|
|
964
|
+
; Only neighbours in the same run are worth a distance: the gap between
|
|
965
|
+
; the first and the last child of a column is arithmetic, not news.
|
|
966
|
+
if (j != (i + 1)) {
|
|
967
|
+
return
|
|
968
|
+
}
|
|
969
|
+
def d:double (EVGMeasure.gapBetween(a b))
|
|
970
|
+
if (d < 0.0) {
|
|
971
|
+
return
|
|
972
|
+
}
|
|
973
|
+
if (d < 4.0) {
|
|
974
|
+
; A document where everything touches would send its whole tree
|
|
975
|
+
; down the wire one pair at a time. The count of what is not shown
|
|
976
|
+
; is printed, because a list that stops without saying so reads as
|
|
977
|
+
; a document with nothing else wrong.
|
|
978
|
+
if ((array_length tight) < 12) {
|
|
979
|
+
push tight ((((nameA + " → ") + nameB) + ": ") + ((EVGMeasure.num(d)) + " apart"))
|
|
980
|
+
} {
|
|
981
|
+
tightMore = (tightMore + 1)
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
; The space between two boxes along the axis that separates them. Boxes
|
|
987
|
+
; side by side answer with the horizontal gap, stacked ones the vertical;
|
|
988
|
+
; two that share neither axis have no single distance and answer -1.
|
|
989
|
+
sfn gapBetween:double (a:EVGElement b:EVGElement) {
|
|
990
|
+
def ax2:double (a.calculatedX + a.calculatedWidth)
|
|
991
|
+
def ay2:double (a.calculatedY + a.calculatedHeight)
|
|
992
|
+
def bx2:double (b.calculatedX + b.calculatedWidth)
|
|
993
|
+
def by2:double (b.calculatedY + b.calculatedHeight)
|
|
994
|
+
def sharesRow:boolean ((ay2 > (b.calculatedY + 0.5)) && (by2 > (a.calculatedY + 0.5)))
|
|
995
|
+
def sharesCol:boolean ((ax2 > (b.calculatedX + 0.5)) && (bx2 > (a.calculatedX + 0.5)))
|
|
996
|
+
if sharesCol {
|
|
997
|
+
if (b.calculatedY >= ay2) {
|
|
998
|
+
return (b.calculatedY - ay2)
|
|
999
|
+
}
|
|
1000
|
+
return (a.calculatedY - by2)
|
|
1001
|
+
}
|
|
1002
|
+
if sharesRow {
|
|
1003
|
+
if (b.calculatedX >= ax2) {
|
|
1004
|
+
return (b.calculatedX - ax2)
|
|
1005
|
+
}
|
|
1006
|
+
return (a.calculatedX - bx2)
|
|
1007
|
+
}
|
|
1008
|
+
return (0.0 - 1.0)
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
; One node's box, and how far the next sibling starts from it.
|
|
1012
|
+
fn addBox:void (path:string el:EVGElement) {
|
|
1013
|
+
def out:string "{\"at\":"
|
|
1014
|
+
out = (out + (EVGTreeJson.str(path)))
|
|
1015
|
+
out = ((out + ",\"tag\":") + (EVGTreeJson.str(el.tagName)))
|
|
1016
|
+
out = ((out + ",\"x\":") + (EVGMeasure.num(el.calculatedX)))
|
|
1017
|
+
out = ((out + ",\"y\":") + (EVGMeasure.num(el.calculatedY)))
|
|
1018
|
+
out = ((out + ",\"w\":") + (EVGMeasure.num(el.calculatedWidth)))
|
|
1019
|
+
out = ((out + ",\"h\":") + (EVGMeasure.num(el.calculatedHeight)))
|
|
1020
|
+
def next@(optional):EVGElement (this.nextSibling(path))
|
|
1021
|
+
if ((null? next) == false) {
|
|
1022
|
+
def sib:EVGElement (unwrap next)
|
|
1023
|
+
def d:double (EVGMeasure.gapBetween(el sib))
|
|
1024
|
+
if (d >= 0.0) {
|
|
1025
|
+
out = ((out + ",\"gapNext\":") + (EVGMeasure.num(d)))
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
if ((strlen el.textContent) > 0) {
|
|
1029
|
+
out = ((out + ",\"text\":") + (EVGTreeJson.str(EVGMeasure.clip(el.textContent 32))))
|
|
1030
|
+
}
|
|
1031
|
+
push boxes (out + "}")
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
; The node after this one under the same parent. Walking the parent's
|
|
1035
|
+
; children rather than adding one to the address is what makes this work on
|
|
1036
|
+
; a keyed document, where the next node after "k:title" is not "k:title+1".
|
|
1037
|
+
fn nextSibling@(optional):EVGElement (path:string) {
|
|
1038
|
+
def none@(optional):EVGElement
|
|
1039
|
+
def cut:int (EVGMeasure.lastSlash(path))
|
|
1040
|
+
if (cut < 0) {
|
|
1041
|
+
return none
|
|
1042
|
+
}
|
|
1043
|
+
def parentPath:string (substring path 0 cut)
|
|
1044
|
+
def par@(optional):EVGElement (insp.elementAt(parentPath))
|
|
1045
|
+
if (null? par) {
|
|
1046
|
+
return none
|
|
1047
|
+
}
|
|
1048
|
+
def p:EVGElement (unwrap par)
|
|
1049
|
+
def seg:string (substring path (cut + 1) (strlen path))
|
|
1050
|
+
def n:int (array_length p.children)
|
|
1051
|
+
def i:int 0
|
|
1052
|
+
while (i < n) {
|
|
1053
|
+
def kid:EVGElement (itemAt p.children i)
|
|
1054
|
+
if ((EVGMeasure.segOf(kid i)) == seg) {
|
|
1055
|
+
if ((i + 1) < n) {
|
|
1056
|
+
def nxt@(optional):EVGElement (itemAt p.children (i + 1))
|
|
1057
|
+
return nxt
|
|
1058
|
+
}
|
|
1059
|
+
return none
|
|
1060
|
+
}
|
|
1061
|
+
i = (i + 1)
|
|
1062
|
+
}
|
|
1063
|
+
return none
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
; --- small things ---------------------------------------------------------
|
|
1067
|
+
|
|
1068
|
+
sfn overlaps:boolean (a:EVGElement b:EVGElement) {
|
|
1069
|
+
def ax2:double (a.calculatedX + a.calculatedWidth)
|
|
1070
|
+
def ay2:double (a.calculatedY + a.calculatedHeight)
|
|
1071
|
+
def bx2:double (b.calculatedX + b.calculatedWidth)
|
|
1072
|
+
def by2:double (b.calculatedY + b.calculatedHeight)
|
|
1073
|
+
if ((a.calculatedWidth <= 0.0) || (b.calculatedWidth <= 0.0)) {
|
|
1074
|
+
return false
|
|
1075
|
+
}
|
|
1076
|
+
if ((a.calculatedHeight <= 0.0) || (b.calculatedHeight <= 0.0)) {
|
|
1077
|
+
return false
|
|
1078
|
+
}
|
|
1079
|
+
if (ax2 <= (b.calculatedX + 0.5)) {
|
|
1080
|
+
return false
|
|
1081
|
+
}
|
|
1082
|
+
if (bx2 <= (a.calculatedX + 0.5)) {
|
|
1083
|
+
return false
|
|
1084
|
+
}
|
|
1085
|
+
if (ay2 <= (b.calculatedY + 0.5)) {
|
|
1086
|
+
return false
|
|
1087
|
+
}
|
|
1088
|
+
if (by2 <= (a.calculatedY + 0.5)) {
|
|
1089
|
+
return false
|
|
1090
|
+
}
|
|
1091
|
+
return true
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
sfn hasUnreadCommand:boolean (d:string) {
|
|
1095
|
+
def i:int 0
|
|
1096
|
+
while (i < (strlen d)) {
|
|
1097
|
+
def c:int (charAt d i)
|
|
1098
|
+
def isLetter:boolean (((c >= 65) && (c <= 90)) || ((c >= 97) && (c <= 122)))
|
|
1099
|
+
if isLetter {
|
|
1100
|
+
if ((EVGMeasure.isReadCommand(c)) == false) {
|
|
1101
|
+
return true
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
i = (i + 1)
|
|
1105
|
+
}
|
|
1106
|
+
return false
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
sfn isReadCommand:boolean (c:int) {
|
|
1110
|
+
if ((c == 77) || (c == 109)) {
|
|
1111
|
+
return true
|
|
1112
|
+
}
|
|
1113
|
+
if ((c == 76) || (c == 108)) {
|
|
1114
|
+
return true
|
|
1115
|
+
}
|
|
1116
|
+
if ((c == 67) || (c == 99)) {
|
|
1117
|
+
return true
|
|
1118
|
+
}
|
|
1119
|
+
if ((c == 81) || (c == 113)) {
|
|
1120
|
+
return true
|
|
1121
|
+
}
|
|
1122
|
+
if ((c == 90) || (c == 122)) {
|
|
1123
|
+
return true
|
|
1124
|
+
}
|
|
1125
|
+
return false
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
; The numbers in a path, taken in pairs. A trailing odd number would mean a
|
|
1129
|
+
; malformed path, and it is dropped rather than paired with nothing.
|
|
1130
|
+
sfn coordsOf:void (d:string xs:[double] ys:[double]) {
|
|
1131
|
+
def nums:[double]
|
|
1132
|
+
def cur:string ""
|
|
1133
|
+
def i:int 0
|
|
1134
|
+
while (i < (strlen d)) {
|
|
1135
|
+
def c:int (charAt d i)
|
|
1136
|
+
def isNum:boolean (((c >= 48) && (c <= 57)) || (((c == 46) || (c == 45)) || (c == 43)))
|
|
1137
|
+
if isNum {
|
|
1138
|
+
; A minus starts a new number unless it opens this one.
|
|
1139
|
+
if ((c == 45) && ((strlen cur) > 0)) {
|
|
1140
|
+
def done@(optional):double (to_double cur)
|
|
1141
|
+
if done {
|
|
1142
|
+
push nums (unwrap done)
|
|
1143
|
+
}
|
|
1144
|
+
cur = ""
|
|
1145
|
+
}
|
|
1146
|
+
cur = (cur + (substring d i (i + 1)))
|
|
1147
|
+
} {
|
|
1148
|
+
if ((strlen cur) > 0) {
|
|
1149
|
+
def v@(optional):double (to_double cur)
|
|
1150
|
+
if v {
|
|
1151
|
+
push nums (unwrap v)
|
|
1152
|
+
}
|
|
1153
|
+
cur = ""
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
i = (i + 1)
|
|
1157
|
+
}
|
|
1158
|
+
if ((strlen cur) > 0) {
|
|
1159
|
+
def last@(optional):double (to_double cur)
|
|
1160
|
+
if last {
|
|
1161
|
+
push nums (unwrap last)
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
def k:int 0
|
|
1165
|
+
while ((k + 1) < (array_length nums)) {
|
|
1166
|
+
push xs (itemAt nums k)
|
|
1167
|
+
push ys (itemAt nums (k + 1))
|
|
1168
|
+
k = (k + 2)
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
; The document's own size in pixels, or the fallback when it does not say.
|
|
1173
|
+
; Measuring a document against a guessed page reports the guess: every
|
|
1174
|
+
; export came back "past the page width" for no reason but the default.
|
|
1175
|
+
sfn sizeOf:int (root:EVGElement wide:boolean fallback:int) {
|
|
1176
|
+
def u@(optional):EVGUnit root.height
|
|
1177
|
+
if wide {
|
|
1178
|
+
u = root.width
|
|
1179
|
+
}
|
|
1180
|
+
if (null? u) {
|
|
1181
|
+
return fallback
|
|
1182
|
+
}
|
|
1183
|
+
def uu:EVGUnit (unwrap u)
|
|
1184
|
+
if (uu.isSet == false) {
|
|
1185
|
+
return fallback
|
|
1186
|
+
}
|
|
1187
|
+
if (uu.unitType != 0) {
|
|
1188
|
+
return fallback
|
|
1189
|
+
}
|
|
1190
|
+
def px:int (to_int uu.value)
|
|
1191
|
+
if (px <= 0) {
|
|
1192
|
+
return fallback
|
|
1193
|
+
}
|
|
1194
|
+
return px
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
sfn under:boolean (path:string from:string) {
|
|
1198
|
+
if (path == from) {
|
|
1199
|
+
return true
|
|
1200
|
+
}
|
|
1201
|
+
def head:string (from + "/")
|
|
1202
|
+
def n:int (strlen head)
|
|
1203
|
+
if ((strlen path) <= n) {
|
|
1204
|
+
return false
|
|
1205
|
+
}
|
|
1206
|
+
return ((substring path 0 n) == head)
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
sfn lastSlash:int (path:string) {
|
|
1210
|
+
def at:int (0 - 1)
|
|
1211
|
+
def i:int 0
|
|
1212
|
+
while (i < (strlen path)) {
|
|
1213
|
+
if ((charAt path i) == 47) {
|
|
1214
|
+
at = i
|
|
1215
|
+
}
|
|
1216
|
+
i = (i + 1)
|
|
1217
|
+
}
|
|
1218
|
+
return at
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
sfn lower:double (a:double b:double) {
|
|
1222
|
+
if (a < b) {
|
|
1223
|
+
return a
|
|
1224
|
+
}
|
|
1225
|
+
return b
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
sfn higher:double (a:double b:double) {
|
|
1229
|
+
if (a > b) {
|
|
1230
|
+
return a
|
|
1231
|
+
}
|
|
1232
|
+
return b
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
sfn clip:string (s:string width:int) {
|
|
1236
|
+
if ((strlen s) <= width) {
|
|
1237
|
+
return s
|
|
1238
|
+
}
|
|
1239
|
+
return ((substring s 0 width) + "…")
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
sfn strArray:string (items:[string] pretty:boolean) {
|
|
1243
|
+
def out:string "["
|
|
1244
|
+
def i:int 0
|
|
1245
|
+
while (i < (array_length items)) {
|
|
1246
|
+
if (i > 0) {
|
|
1247
|
+
out = (out + ",")
|
|
1248
|
+
}
|
|
1249
|
+
if pretty {
|
|
1250
|
+
out = (out + "\n ")
|
|
1251
|
+
}
|
|
1252
|
+
out = (out + (EVGTreeJson.str((itemAt items i))))
|
|
1253
|
+
i = (i + 1)
|
|
1254
|
+
}
|
|
1255
|
+
if pretty {
|
|
1256
|
+
if ((array_length items) > 0) {
|
|
1257
|
+
out = (out + "\n")
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return (out + "]")
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
sfn num:string (v:double) {
|
|
1264
|
+
def whole:int (to_int v)
|
|
1265
|
+
return (to_string whole)
|
|
1266
|
+
}
|
|
1267
|
+
}
|