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,894 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
; ==============================================================================
|
|
4
|
+
; EVGA11yTree.rgr — the second list: what the frame MEANS.
|
|
5
|
+
; ==============================================================================
|
|
6
|
+
;
|
|
7
|
+
; EVGDisplayList carries geometry — absolute pixels, resolved colours, no tree.
|
|
8
|
+
; That is exactly what a GPU wants and exactly what a screen reader cannot use.
|
|
9
|
+
; NVDA, VoiceOver, JAWS and Orca do not look at pixels: they ask the platform
|
|
10
|
+
; for a TREE of nodes with a role, a name, a value, a state and a rectangle,
|
|
11
|
+
; and they walk it with the arrow keys, drive it with Tab, mirror it onto a
|
|
12
|
+
; braille display and speak what changed.
|
|
13
|
+
;
|
|
14
|
+
; So a program drawn with EVG on WebGL or SDL2+OpenGL is, to an assistive
|
|
15
|
+
; technology, one empty graphic. The fix is not a smarter renderer and it is
|
|
16
|
+
; certainly not guessing meaning back out of the display list — that is OCR
|
|
17
|
+
; against your own program, and the list is lossy on purpose. The fix is to
|
|
18
|
+
; emit a SECOND list from the SAME walk:
|
|
19
|
+
;
|
|
20
|
+
; +--> EVGDisplayList (geometry) --> WebGL / SDL2 / PDF
|
|
21
|
+
; app state --walk---|
|
|
22
|
+
; +--> EVGA11yTree (meaning) --> DOM mirror / UIA /
|
|
23
|
+
; AT-SPI / NSAccessibility
|
|
24
|
+
;
|
|
25
|
+
; One walk, because two walks drift: five painters each walking the laid-out
|
|
26
|
+
; tree is how border-radius came to work in PDF and silently not in PNG. The
|
|
27
|
+
; same bug in an accessibility tree would be invisible to everyone who can see
|
|
28
|
+
; the screen, and only a blind user would ever hit it.
|
|
29
|
+
;
|
|
30
|
+
; This module is PURE — no device resources, no host calls, no file system — so
|
|
31
|
+
; it compiles to ES6, C++, Rust and Go like the display list does. That is the
|
|
32
|
+
; point: the accessibility model is written once and every host consumes it,
|
|
33
|
+
; instead of being reimplemented per platform the way it normally is.
|
|
34
|
+
;
|
|
35
|
+
; The field set is deliberately the intersection of ARIA, UI Automation,
|
|
36
|
+
; AT-SPI2, NSAccessibility and AccessKit. Everything here exists in all five,
|
|
37
|
+
; which is what keeps the browser mirror and a future native adapter reading
|
|
38
|
+
; the same tree.
|
|
39
|
+
;
|
|
40
|
+
; Bounds are ints. Every EVG surface that has semantics — EVGControl, the grid
|
|
41
|
+
; geometry, the toolbar — already works in whole pixels, and an assistive
|
|
42
|
+
; technology uses a rectangle to route the mouse, place a magnifier and answer
|
|
43
|
+
; a touch, none of which care about a quarter of a pixel.
|
|
44
|
+
; ==============================================================================
|
|
45
|
+
|
|
46
|
+
; ------------------------------------------------------------------------------
|
|
47
|
+
; Roles. Ints so a backend can switch cheaply — the same reason the draw
|
|
48
|
+
; commands are ints — with the ARIA spelling kept here so every host agrees on
|
|
49
|
+
; the vocabulary instead of each inventing its own mapping.
|
|
50
|
+
; ------------------------------------------------------------------------------
|
|
51
|
+
class EVGA11yRole {
|
|
52
|
+
; Decoration. Never surfaced: a node with this role and everything under it
|
|
53
|
+
; is skipped, which is how a reader is kept from announcing 300 rectangles.
|
|
54
|
+
sfn none:int () { return 0 }
|
|
55
|
+
sfn group:int () { return 1 }
|
|
56
|
+
sfn text:int () { return 2 }
|
|
57
|
+
sfn heading:int () { return 3 }
|
|
58
|
+
sfn button:int () { return 4 }
|
|
59
|
+
sfn checkbox:int () { return 5 }
|
|
60
|
+
sfn radio:int () { return 6 }
|
|
61
|
+
sfn textField:int () { return 7 }
|
|
62
|
+
sfn image:int () { return 8 }
|
|
63
|
+
sfn link:int () { return 9 }
|
|
64
|
+
sfn grid:int () { return 10 }
|
|
65
|
+
sfn row:int () { return 11 }
|
|
66
|
+
sfn columnHeader:int () { return 12 }
|
|
67
|
+
sfn rowHeader:int () { return 13 }
|
|
68
|
+
sfn cell:int () { return 14 }
|
|
69
|
+
sfn tabList:int () { return 15 }
|
|
70
|
+
sfn tab:int () { return 16 }
|
|
71
|
+
sfn toolbar:int () { return 17 }
|
|
72
|
+
sfn menuItem:int () { return 18 }
|
|
73
|
+
sfn dialog:int () { return 19 }
|
|
74
|
+
sfn status:int () { return 20 }
|
|
75
|
+
sfn separator:int () { return 21 }
|
|
76
|
+
sfn list:int () { return 22 }
|
|
77
|
+
sfn listItem:int () { return 23 }
|
|
78
|
+
; Added for the gallery/ui parity work: roles Radix exposes that this
|
|
79
|
+
; catalogue had no word for. Appended, so every existing number stands.
|
|
80
|
+
sfn switchControl:int () { return 24 }
|
|
81
|
+
sfn radioGroup:int () { return 25 }
|
|
82
|
+
sfn tabPanel:int () { return 26 }
|
|
83
|
+
sfn region:int () { return 27 }
|
|
84
|
+
sfn progressBar:int () { return 28 }
|
|
85
|
+
; The overlay family: a menu and its surface, a tooltip, the dialog
|
|
86
|
+
; variant a reader must interrupt for, and a value a user drags.
|
|
87
|
+
sfn menu:int () { return 29 }
|
|
88
|
+
sfn tooltip:int () { return 30 }
|
|
89
|
+
sfn alertDialog:int () { return 31 }
|
|
90
|
+
sfn slider:int () { return 32 }
|
|
91
|
+
; A menu bar and the two menu rows that carry a state. ARIA spells these as
|
|
92
|
+
; separate roles rather than a menuitem with aria-checked, and a reader
|
|
93
|
+
; announces them differently, so they are separate here too.
|
|
94
|
+
sfn menuBar:int () { return 33 }
|
|
95
|
+
sfn menuItemCheckbox:int () { return 34 }
|
|
96
|
+
sfn menuItemRadio:int () { return 35 }
|
|
97
|
+
; A select: a button that opens a list of options, and the list itself.
|
|
98
|
+
; `combobox` even when nothing can be typed — it is what the reference
|
|
99
|
+
; reports, and what every platform maps a picker onto.
|
|
100
|
+
sfn comboBox:int () { return 36 }
|
|
101
|
+
sfn listBox:int () { return 37 }
|
|
102
|
+
sfn option:int () { return 38 }
|
|
103
|
+
; `table` and not `grid`. They are different promises: a grid is a widget
|
|
104
|
+
; you arrow around cell by cell, a table is tabular DATA with ordinary
|
|
105
|
+
; controls inside it. A sortable, selectable table of the ReUI kind is the
|
|
106
|
+
; second — you tab to the sort button and the checkbox, you do not steer a
|
|
107
|
+
; cell cursor — and a native `<table>`, which is what the reference
|
|
108
|
+
; renders, reports exactly this.
|
|
109
|
+
sfn table:int () { return 39 }
|
|
110
|
+
; `cell` and `gridcell` are different roles, not two spellings. A cell
|
|
111
|
+
; belongs to a table, a gridcell to a grid — and a reader announces the
|
|
112
|
+
; second as something you can steer around with the arrow keys. `cell()`
|
|
113
|
+
; keeps its old meaning so the datagrid is untouched.
|
|
114
|
+
sfn tableCell:int () { return 40 }
|
|
115
|
+
|
|
116
|
+
; A tree, and its rows. Nesting is carried by `aria-level` and not by a
|
|
117
|
+
; `group` per folder — which is what headless-tree does, and therefore what
|
|
118
|
+
; ReUI's tree does: `getItems()` hands back the visible rows already
|
|
119
|
+
; flattened, all siblings, each saying how deep it is.
|
|
120
|
+
sfn tree:int () { return 41 }
|
|
121
|
+
sfn treeItem:int () { return 42 }
|
|
122
|
+
|
|
123
|
+
; A landmark. `navigation` is the one a breadcrumb needs, and it needs a
|
|
124
|
+
; NAME with it: a page has several navigations and an unnamed one leaves a
|
|
125
|
+
; reader to guess which of them it has landed in.
|
|
126
|
+
sfn navigation:int () { return 43 }
|
|
127
|
+
|
|
128
|
+
; A form is a landmark ONLY when it is named, which is the HTML-ARIA
|
|
129
|
+
; mapping and not a simplification: an unnamed <form> is not a landmark,
|
|
130
|
+
; because a reader offered "form" with nothing to distinguish it from the
|
|
131
|
+
; other three on the page has been given a list and no way to choose from
|
|
132
|
+
; it. The demo names its own; a controller that does not should not use
|
|
133
|
+
; this role.
|
|
134
|
+
sfn form:int () { return 44 }
|
|
135
|
+
|
|
136
|
+
; The page's own landmarks: the header strip and the content. A reader
|
|
137
|
+
; jumps between them, and a screen with a bottom bar and a top bar has to
|
|
138
|
+
; say which of its three regions is which.
|
|
139
|
+
sfn banner:int () { return 47 }
|
|
140
|
+
sfn mainContent:int () { return 48 }
|
|
141
|
+
; A paragraph of running text. Rendered markdown is paragraphs, and a
|
|
142
|
+
; reader steps through them; without the role the text was dropped from
|
|
143
|
+
; the tree with everything under it.
|
|
144
|
+
sfn paragraph:int () { return 49 }
|
|
145
|
+
; A number field that steps: the value moves with the arrow keys and is
|
|
146
|
+
; published as valuenow/min/max, the way a slider's is.
|
|
147
|
+
sfn spinButton:int () { return 50 }
|
|
148
|
+
|
|
149
|
+
; `rowgroup` — a thead or a tbody. Missing until a table was built out of
|
|
150
|
+
; roles rather than out of `<table>` tags, and an unrecognised role drops
|
|
151
|
+
; the element AND EVERYTHING UNDER IT, so the whole body of the dashboard's
|
|
152
|
+
; table was absent from the tree while the picture looked perfect. The
|
|
153
|
+
; lint is the only reason that was noticed.
|
|
154
|
+
sfn rowGroup:int () { return 45 }
|
|
155
|
+
|
|
156
|
+
; A LOG: a live region whose new content is appended, and which a reader
|
|
157
|
+
; announces as it arrives. `status` is the other polite one and they are
|
|
158
|
+
; not interchangeable — a status is a single changing value, a log is a
|
|
159
|
+
; growing list, and a transcript is the second.
|
|
160
|
+
;
|
|
161
|
+
; Added because a tree literal saying role="log" produced no error and no
|
|
162
|
+
; node: `EVGA11yFromTree` drops a role it does not know AND everything
|
|
163
|
+
; under it, so a whole conversation went missing from the accessible tree
|
|
164
|
+
; while the picture looked perfect. The lint said so, which is the only
|
|
165
|
+
; reason it was noticed.
|
|
166
|
+
sfn log:int () { return 46 }
|
|
167
|
+
|
|
168
|
+
; The ARIA role string. A browser host writes this straight into role="…";
|
|
169
|
+
; a native adapter maps it to the platform's own constant.
|
|
170
|
+
sfn ariaName:string (role:int) {
|
|
171
|
+
if (role == 43) { return "navigation" }
|
|
172
|
+
if (role == 44) { return "form" }
|
|
173
|
+
if (role == 45) { return "rowgroup" }
|
|
174
|
+
if (role == 46) { return "log" }
|
|
175
|
+
if (role == 47) { return "banner" }
|
|
176
|
+
if (role == 48) { return "main" }
|
|
177
|
+
if (role == 49) { return "paragraph" }
|
|
178
|
+
if (role == 50) { return "spinbutton" }
|
|
179
|
+
if (role == 41) { return "tree" }
|
|
180
|
+
if (role == 42) { return "treeitem" }
|
|
181
|
+
if (role == 1) { return "group" }
|
|
182
|
+
if (role == 2) { return "text" }
|
|
183
|
+
if (role == 3) { return "heading" }
|
|
184
|
+
if (role == 4) { return "button" }
|
|
185
|
+
if (role == 5) { return "checkbox" }
|
|
186
|
+
if (role == 6) { return "radio" }
|
|
187
|
+
if (role == 7) { return "textbox" }
|
|
188
|
+
if (role == 8) { return "img" }
|
|
189
|
+
if (role == 9) { return "link" }
|
|
190
|
+
if (role == 39) { return "table" }
|
|
191
|
+
if (role == 40) { return "cell" }
|
|
192
|
+
if (role == 10) { return "grid" }
|
|
193
|
+
if (role == 11) { return "row" }
|
|
194
|
+
if (role == 12) { return "columnheader" }
|
|
195
|
+
if (role == 13) { return "rowheader" }
|
|
196
|
+
if (role == 14) { return "gridcell" }
|
|
197
|
+
if (role == 15) { return "tablist" }
|
|
198
|
+
if (role == 16) { return "tab" }
|
|
199
|
+
if (role == 17) { return "toolbar" }
|
|
200
|
+
if (role == 18) { return "menuitem" }
|
|
201
|
+
if (role == 19) { return "dialog" }
|
|
202
|
+
if (role == 20) { return "status" }
|
|
203
|
+
if (role == 21) { return "separator" }
|
|
204
|
+
if (role == 22) { return "list" }
|
|
205
|
+
if (role == 23) { return "listitem" }
|
|
206
|
+
if (role == 24) { return "switch" }
|
|
207
|
+
if (role == 25) { return "radiogroup" }
|
|
208
|
+
if (role == 26) { return "tabpanel" }
|
|
209
|
+
if (role == 27) { return "region" }
|
|
210
|
+
if (role == 28) { return "progressbar" }
|
|
211
|
+
if (role == 29) { return "menu" }
|
|
212
|
+
if (role == 30) { return "tooltip" }
|
|
213
|
+
if (role == 31) { return "alertdialog" }
|
|
214
|
+
if (role == 32) { return "slider" }
|
|
215
|
+
if (role == 33) { return "menubar" }
|
|
216
|
+
if (role == 34) { return "menuitemcheckbox" }
|
|
217
|
+
if (role == 35) { return "menuitemradio" }
|
|
218
|
+
if (role == 36) { return "combobox" }
|
|
219
|
+
if (role == 37) { return "listbox" }
|
|
220
|
+
if (role == 38) { return "option" }
|
|
221
|
+
return "none"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
; Tri-states. The default is "not applicable" rather than "false", because
|
|
226
|
+
; `aria-checked="false"` on something that is not checkable makes a reader
|
|
227
|
+
; announce a checkbox that does not exist.
|
|
228
|
+
class EVGA11yTri {
|
|
229
|
+
sfn notApplicable:int () { return 0 }
|
|
230
|
+
sfn no:int () { return 1 }
|
|
231
|
+
sfn yes:int () { return 2 }
|
|
232
|
+
sfn mixed:int () { return 3 }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
class EVGA11yLive {
|
|
236
|
+
sfn off:int () { return 0 }
|
|
237
|
+
sfn polite:int () { return 1 }
|
|
238
|
+
sfn assertive:int () { return 2 }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
class EVGA11yNode {
|
|
242
|
+
; STABLE across frames, and derived from IDENTITY rather than paint order:
|
|
243
|
+
; "win:7/ctrl:3", "sheet:Q3/cell:B7", "el:#total". An id that changes every
|
|
244
|
+
; frame makes the reader lose its cursor, resets focus and flickers the
|
|
245
|
+
; braille display — and everything still looks correct on screen, which is
|
|
246
|
+
; why this is the most common way a first implementation fails.
|
|
247
|
+
def id:string ""
|
|
248
|
+
def parentId:string ""
|
|
249
|
+
def role:int 0
|
|
250
|
+
; What gets spoken. A focusable node without one is a lint failure.
|
|
251
|
+
def name:string ""
|
|
252
|
+
; Spoken after a pause, or on request: the formula behind a cell, the note
|
|
253
|
+
; attached to it, the shortcut on a button.
|
|
254
|
+
def description:string ""
|
|
255
|
+
; The id of the node whose text IS the description, when the sentence is
|
|
256
|
+
; on screen as its own node (a form field's error message). Serialised as
|
|
257
|
+
; `describedby`; the DOM mirror turns it into `aria-describedby`.
|
|
258
|
+
def describedBy:string ""
|
|
259
|
+
; `aria-controls` / `aria-labelledby`: ids of other nodes. A tab controls
|
|
260
|
+
; its panel and the panel is labelled by its tab.
|
|
261
|
+
def controls:string ""
|
|
262
|
+
def labelledBy:string ""
|
|
263
|
+
; What a reader says INSTEAD of the role: "sortable" on a draggable row.
|
|
264
|
+
; Every platform has it — aria-roledescription, UIA LocalizedControlType,
|
|
265
|
+
; AT-SPI's localized role name — because some controls are a known role
|
|
266
|
+
; doing an unfamiliar job, and the role name alone then says nothing useful.
|
|
267
|
+
def roleDescription:string ""
|
|
268
|
+
; `aria-haspopup`. What a reader is told about a trigger that opens
|
|
269
|
+
; something — a select's closed box is a word in a rectangle without it.
|
|
270
|
+
def hasPopup:string ""
|
|
271
|
+
; A field's text, a cell's value — separate from the name, which is the
|
|
272
|
+
; label that does not change when the content does.
|
|
273
|
+
def value:string ""
|
|
274
|
+
|
|
275
|
+
def x:int 0
|
|
276
|
+
def y:int 0
|
|
277
|
+
def w:int 0
|
|
278
|
+
def h:int 0
|
|
279
|
+
|
|
280
|
+
def focusable:boolean false
|
|
281
|
+
def focused:boolean false
|
|
282
|
+
def disabled:boolean false
|
|
283
|
+
def selected:boolean false
|
|
284
|
+
; `aria-required`, `aria-invalid` and `aria-readonly`, as the DOM spells
|
|
285
|
+
; them: "true", "false", or "" for a node that says nothing.
|
|
286
|
+
;
|
|
287
|
+
; STRINGS, and `readOnly` used to be a boolean here. That was the weaker
|
|
288
|
+
; spelling and it could not express the distinction the `input_required`
|
|
289
|
+
; spec exists to record: absent and "false" are different claims. The
|
|
290
|
+
; controller path has always used three states; this one had one and a
|
|
291
|
+
; half, and no `required` or `invalid` at all — so a demo-built form drew
|
|
292
|
+
; a red ring and an error sentence for a person and told a reader nothing.
|
|
293
|
+
def required:string ""
|
|
294
|
+
def invalid:string ""
|
|
295
|
+
def readOnly:string ""
|
|
296
|
+
; A dialog that owns the screen while it is up. Every platform has this —
|
|
297
|
+
; aria-modal, UIA IsModal, AT-SPI MODAL, AccessKit Modal — and a host uses
|
|
298
|
+
; it to hide everything the user cannot currently reach, which is the
|
|
299
|
+
; difference between "a dialog is open" and "there are two applications".
|
|
300
|
+
def modal:boolean false
|
|
301
|
+
def checked:int 0
|
|
302
|
+
; `aria-pressed`, tri-state like `checked`. A toggle button that is ON —
|
|
303
|
+
; a carried sortable row, a show/hide switch beside a password — and NOT
|
|
304
|
+
; the same claim as `checked`: a pressed button is a button held down, a
|
|
305
|
+
; checked one is a box with a tick in it, and a reader says different
|
|
306
|
+
; words for each.
|
|
307
|
+
def pressed:int 0
|
|
308
|
+
def expanded:int 0
|
|
309
|
+
; `aria-sort` on a column header: 0 none, 1 ascending, 2 descending.
|
|
310
|
+
;
|
|
311
|
+
; It needs its own field for the same reason `valuenow` did. Sorting a
|
|
312
|
+
; table changes which rows are where and nothing else about any node — the
|
|
313
|
+
; header keeps its name, its role and its position — so without this the
|
|
314
|
+
; one thing the control does is the one thing the trace cannot see.
|
|
315
|
+
def sorted:int 0
|
|
316
|
+
; `aria-orientation`, verbatim: "horizontal", "vertical", or "" for none.
|
|
317
|
+
; A string rather than a code because there are exactly two values and a
|
|
318
|
+
; reader says them; the tri-state codes exist where a THIRD state (mixed,
|
|
319
|
+
; not-applicable) has to be told apart from false.
|
|
320
|
+
def orientation:string ""
|
|
321
|
+
; `aria-current`, verbatim: "page" on a breadcrumb's last crumb.
|
|
322
|
+
def current:string ""
|
|
323
|
+
|
|
324
|
+
; A number the platform announces: where a slider sits, how far a progress
|
|
325
|
+
; bar has got. `hasValueNow` is not decoration — a progress bar with a range
|
|
326
|
+
; and NO position is how every platform spells "busy, length unknown", and
|
|
327
|
+
; a reader that is handed 0 instead says the work has not started.
|
|
328
|
+
def hasValueNow:boolean false
|
|
329
|
+
def valueNow:int 0
|
|
330
|
+
def hasValueRange:boolean false
|
|
331
|
+
def valueMin:int 0
|
|
332
|
+
def valueMax:int 0
|
|
333
|
+
|
|
334
|
+
; Position in a set the tree does not fully contain — the whole point of
|
|
335
|
+
; virtualization. A 10,000 row sheet emits the ~40 rows on screen; "row
|
|
336
|
+
; 4,120 of 10,000" comes from these and nothing else. Lying here makes the
|
|
337
|
+
; reader announce positions that do not exist.
|
|
338
|
+
def rowIndex:int 0
|
|
339
|
+
def colIndex:int 0
|
|
340
|
+
def rowCount:int 0
|
|
341
|
+
def colCount:int 0
|
|
342
|
+
def posInSet:int 0
|
|
343
|
+
def setSize:int 0
|
|
344
|
+
; How deep a `treeitem` sits, counting from 1. A flat list of rows whose
|
|
345
|
+
; nesting lives only in this number is what headless-tree publishes and
|
|
346
|
+
; what a reader announces as "level 3" — WITHOUT it a tree is announced as
|
|
347
|
+
; a flat list of siblings, which is a different structure entirely and not
|
|
348
|
+
; the one on screen. 0 means the node is not in a levelled structure.
|
|
349
|
+
def level:int 0
|
|
350
|
+
|
|
351
|
+
; Text fields: caret and selection as offsets into `value`.
|
|
352
|
+
def caret:int 0
|
|
353
|
+
def selStart:int 0
|
|
354
|
+
def selEnd:int 0
|
|
355
|
+
|
|
356
|
+
; What the node can be asked to DO. A platform adapter reports these as
|
|
357
|
+
; available actions and routes them back into the app; the browser mirror
|
|
358
|
+
; uses them to decide what kind of element to render. Booleans rather than a
|
|
359
|
+
; bitmask because Ranger has no bitwise operators, and because the JSON that
|
|
360
|
+
; crosses the seam is then readable by a person debugging it.
|
|
361
|
+
;
|
|
362
|
+
; `focusable` is the focus action; these are the rest.
|
|
363
|
+
def actActivate:boolean false
|
|
364
|
+
def actSetValue:boolean false
|
|
365
|
+
def actExpand:boolean false
|
|
366
|
+
def actScrollTo:boolean false
|
|
367
|
+
def live:int 0
|
|
368
|
+
|
|
369
|
+
Constructor () {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
class EVGA11yTree {
|
|
374
|
+
def nodes:[EVGA11yNode]
|
|
375
|
+
def rootId:string ""
|
|
376
|
+
; The app's focus, never the host's. One owner, or the two chase each other
|
|
377
|
+
; forever: the mirror focuses a node, the browser reports focusin, the app
|
|
378
|
+
; moves its focus, the mirror focuses a node…
|
|
379
|
+
def focusId:string ""
|
|
380
|
+
; The same counter the scene carries, so a host can skip a rebuild it has
|
|
381
|
+
; already done — a platform accessibility API wants events, not a fresh
|
|
382
|
+
; tree sixty times a second.
|
|
383
|
+
def generation:int 0
|
|
384
|
+
; What the BUILDER could not represent, in its own words. A builder walks
|
|
385
|
+
; a source it does not fully control — an element tree, a row list — and
|
|
386
|
+
; anything it cannot map lands here rather than being dropped in silence.
|
|
387
|
+
; `lint()` reports them first.
|
|
388
|
+
def notes:[string]
|
|
389
|
+
|
|
390
|
+
Constructor () {
|
|
391
|
+
def n:[string]
|
|
392
|
+
notes = n
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fn count:int () {
|
|
396
|
+
return (array_length nodes)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
fn at:EVGA11yNode (i:int) {
|
|
400
|
+
if (i < 0) {
|
|
401
|
+
return (new EVGA11yNode)
|
|
402
|
+
}
|
|
403
|
+
if (i >= (array_length nodes)) {
|
|
404
|
+
return (new EVGA11yNode)
|
|
405
|
+
}
|
|
406
|
+
return (itemAt nodes i)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
; Create a node and hang it under `parentId`. The first node added with an
|
|
410
|
+
; empty parent becomes the root.
|
|
411
|
+
fn node:EVGA11yNode (id:string parentId:string role:int) {
|
|
412
|
+
def n:EVGA11yNode (new EVGA11yNode)
|
|
413
|
+
n.id = id
|
|
414
|
+
n.parentId = parentId
|
|
415
|
+
n.role = role
|
|
416
|
+
push nodes n
|
|
417
|
+
if ((strlen rootId) == 0) {
|
|
418
|
+
if ((strlen parentId) == 0) {
|
|
419
|
+
rootId = id
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return n
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
; --- the shapes that come up over and over --------------------------------
|
|
426
|
+
|
|
427
|
+
fn group:EVGA11yNode (id:string parentId:string name:string) {
|
|
428
|
+
def n:EVGA11yNode (this.node(id parentId (EVGA11yRole.group())))
|
|
429
|
+
n.name = name
|
|
430
|
+
return n
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
fn label:EVGA11yNode (id:string parentId:string text:string) {
|
|
434
|
+
def n:EVGA11yNode (this.node(id parentId (EVGA11yRole.text())))
|
|
435
|
+
n.name = text
|
|
436
|
+
return n
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
fn button:EVGA11yNode (id:string parentId:string name:string) {
|
|
440
|
+
def n:EVGA11yNode (this.node(id parentId (EVGA11yRole.button())))
|
|
441
|
+
n.name = name
|
|
442
|
+
n.focusable = true
|
|
443
|
+
n.actActivate = true
|
|
444
|
+
return n
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
fn field:EVGA11yNode (id:string parentId:string name:string value:string) {
|
|
448
|
+
def n:EVGA11yNode (this.node(id parentId (EVGA11yRole.textField())))
|
|
449
|
+
n.name = name
|
|
450
|
+
n.value = value
|
|
451
|
+
n.focusable = true
|
|
452
|
+
n.actSetValue = true
|
|
453
|
+
return n
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
; A region whose changes are announced without the user going to look —
|
|
457
|
+
; "Recalculated 1,200 cells", "Sorted by Region". Polite by default:
|
|
458
|
+
; assertive interrupts whatever is being read, which is for errors only.
|
|
459
|
+
fn statusRegion:EVGA11yNode (id:string parentId:string text:string) {
|
|
460
|
+
def n:EVGA11yNode (this.node(id parentId (EVGA11yRole.status())))
|
|
461
|
+
n.value = text
|
|
462
|
+
n.live = (EVGA11yLive.polite())
|
|
463
|
+
return n
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
fn place:void (n:EVGA11yNode nx:int ny:int nw:int nh:int) {
|
|
467
|
+
n.x = nx
|
|
468
|
+
n.y = ny
|
|
469
|
+
n.w = nw
|
|
470
|
+
n.h = nh
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
; --- lookup ---------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
fn find:EVGA11yNode (id:string) {
|
|
476
|
+
def i:int 0
|
|
477
|
+
while (i < (array_length nodes)) {
|
|
478
|
+
def n:EVGA11yNode (itemAt nodes i)
|
|
479
|
+
if (n.id == id) {
|
|
480
|
+
return n
|
|
481
|
+
}
|
|
482
|
+
i = (i + 1)
|
|
483
|
+
}
|
|
484
|
+
return (new EVGA11yNode)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
fn has:boolean (id:string) {
|
|
488
|
+
def n:EVGA11yNode (this.find(id))
|
|
489
|
+
return ((strlen n.id) > 0)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
; Mark one node focused and clear the rest. Focus is a property of the
|
|
493
|
+
; tree, not of a node, and letting a caller set the flag by hand is how a
|
|
494
|
+
; tree ends up with two focused nodes and a reader with none.
|
|
495
|
+
fn setFocus:void (id:string) {
|
|
496
|
+
focusId = id
|
|
497
|
+
def i:int 0
|
|
498
|
+
while (i < (array_length nodes)) {
|
|
499
|
+
def n:EVGA11yNode (itemAt nodes i)
|
|
500
|
+
n.focused = (n.id == id)
|
|
501
|
+
i = (i + 1)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
; --- serialization --------------------------------------------------------
|
|
506
|
+
; Short keys and omitted defaults, because this crosses the same seam the
|
|
507
|
+
; display list does and a grid emits a few hundred nodes per frame.
|
|
508
|
+
|
|
509
|
+
fn toJson:string () {
|
|
510
|
+
def out:string "{\"root\":"
|
|
511
|
+
out = (out + (EVGA11yTree.jsonString(rootId)))
|
|
512
|
+
out = (out + ",\"focus\":" + (EVGA11yTree.jsonString(focusId)))
|
|
513
|
+
out = (out + ",\"gen\":" + (to_string generation))
|
|
514
|
+
out = (out + ",\"nodes\":[")
|
|
515
|
+
def i:int 0
|
|
516
|
+
while (i < (array_length nodes)) {
|
|
517
|
+
def n:EVGA11yNode (itemAt nodes i)
|
|
518
|
+
if (i > 0) {
|
|
519
|
+
out = (out + ",")
|
|
520
|
+
}
|
|
521
|
+
out = (out + (this.nodeJson(n)))
|
|
522
|
+
i = (i + 1)
|
|
523
|
+
}
|
|
524
|
+
return (out + "]}")
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
fn nodeJson:string (n:EVGA11yNode) {
|
|
528
|
+
def out:string "{\"id\":"
|
|
529
|
+
out = (out + (EVGA11yTree.jsonString(n.id)))
|
|
530
|
+
if ((strlen n.parentId) > 0) {
|
|
531
|
+
out = (out + ",\"p\":" + (EVGA11yTree.jsonString(n.parentId)))
|
|
532
|
+
}
|
|
533
|
+
out = (out + ",\"role\":" + (EVGA11yTree.jsonString((EVGA11yRole.ariaName(n.role)))))
|
|
534
|
+
if ((strlen n.name) > 0) {
|
|
535
|
+
out = (out + ",\"name\":" + (EVGA11yTree.jsonString(n.name)))
|
|
536
|
+
}
|
|
537
|
+
if ((strlen n.value) > 0) {
|
|
538
|
+
out = (out + ",\"value\":" + (EVGA11yTree.jsonString(n.value)))
|
|
539
|
+
}
|
|
540
|
+
if ((strlen n.hasPopup) > 0) {
|
|
541
|
+
out = (out + ",\"haspopup\":" + (EVGA11yTree.jsonString(n.hasPopup)))
|
|
542
|
+
}
|
|
543
|
+
if ((strlen n.description) > 0) {
|
|
544
|
+
out = (out + ",\"desc\":" + (EVGA11yTree.jsonString(n.description)))
|
|
545
|
+
}
|
|
546
|
+
if ((strlen n.describedBy) > 0) {
|
|
547
|
+
out = (out + ",\"describedby\":" + (EVGA11yTree.jsonString(n.describedBy)))
|
|
548
|
+
}
|
|
549
|
+
if ((strlen n.controls) > 0) {
|
|
550
|
+
out = (out + ",\"controls\":" + (EVGA11yTree.jsonString(n.controls)))
|
|
551
|
+
}
|
|
552
|
+
if ((strlen n.labelledBy) > 0) {
|
|
553
|
+
out = (out + ",\"labelledby\":" + (EVGA11yTree.jsonString(n.labelledBy)))
|
|
554
|
+
}
|
|
555
|
+
if n.hasValueNow {
|
|
556
|
+
out = (out + ",\"now\":" + (to_string n.valueNow))
|
|
557
|
+
}
|
|
558
|
+
if n.hasValueRange {
|
|
559
|
+
out = (out + ",\"min\":" + (to_string n.valueMin))
|
|
560
|
+
out = (out + ",\"max\":" + (to_string n.valueMax))
|
|
561
|
+
}
|
|
562
|
+
out = (out + ",\"b\":[" + (to_string n.x) + "," + (to_string n.y))
|
|
563
|
+
out = (out + "," + (to_string n.w) + "," + (to_string n.h) + "]")
|
|
564
|
+
if n.focusable {
|
|
565
|
+
out = (out + ",\"focusable\":true")
|
|
566
|
+
}
|
|
567
|
+
if n.focused {
|
|
568
|
+
out = (out + ",\"focused\":true")
|
|
569
|
+
}
|
|
570
|
+
if n.disabled {
|
|
571
|
+
out = (out + ",\"disabled\":true")
|
|
572
|
+
}
|
|
573
|
+
if n.selected {
|
|
574
|
+
out = (out + ",\"selected\":true")
|
|
575
|
+
}
|
|
576
|
+
if ((strlen n.required) > 0) {
|
|
577
|
+
out = (out + ",\"required\":" + (EVGA11yTree.jsonString(n.required)))
|
|
578
|
+
}
|
|
579
|
+
if ((strlen n.invalid) > 0) {
|
|
580
|
+
out = (out + ",\"invalid\":" + (EVGA11yTree.jsonString(n.invalid)))
|
|
581
|
+
}
|
|
582
|
+
if ((strlen n.readOnly) > 0) {
|
|
583
|
+
out = (out + ",\"readonly\":" + (EVGA11yTree.jsonString(n.readOnly)))
|
|
584
|
+
}
|
|
585
|
+
if n.modal {
|
|
586
|
+
out = (out + ",\"modal\":true")
|
|
587
|
+
}
|
|
588
|
+
if (n.checked != 0) {
|
|
589
|
+
out = (out + ",\"checked\":" + (to_string n.checked))
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (n.pressed != 0) {
|
|
593
|
+
out = (out + ",\"pressed\":" + (to_string n.pressed))
|
|
594
|
+
}
|
|
595
|
+
if ((strlen n.orientation) > 0) {
|
|
596
|
+
out = (out + ",\"orientation\":\"" + n.orientation + "\"")
|
|
597
|
+
}
|
|
598
|
+
if ((strlen n.current) > 0) {
|
|
599
|
+
out = (out + ",\"current\":\"" + n.current + "\"")
|
|
600
|
+
}
|
|
601
|
+
if (n.sorted != 0) {
|
|
602
|
+
out = (out + ",\"sorted\":" + (to_string n.sorted))
|
|
603
|
+
}
|
|
604
|
+
if (n.expanded != 0) {
|
|
605
|
+
out = (out + ",\"expanded\":" + (to_string n.expanded))
|
|
606
|
+
}
|
|
607
|
+
if (n.rowIndex != 0) {
|
|
608
|
+
out = (out + ",\"row\":" + (to_string n.rowIndex))
|
|
609
|
+
}
|
|
610
|
+
if (n.colIndex != 0) {
|
|
611
|
+
out = (out + ",\"col\":" + (to_string n.colIndex))
|
|
612
|
+
}
|
|
613
|
+
if (n.rowCount != 0) {
|
|
614
|
+
out = (out + ",\"rows\":" + (to_string n.rowCount))
|
|
615
|
+
}
|
|
616
|
+
if (n.colCount != 0) {
|
|
617
|
+
out = (out + ",\"cols\":" + (to_string n.colCount))
|
|
618
|
+
}
|
|
619
|
+
if ((strlen n.roleDescription) > 0) {
|
|
620
|
+
out = (out + ",\"roledesc\":" + (EVGA11yTree.jsonString(n.roleDescription)))
|
|
621
|
+
}
|
|
622
|
+
if (n.posInSet != 0) {
|
|
623
|
+
out = (out + ",\"pos\":" + (to_string n.posInSet))
|
|
624
|
+
}
|
|
625
|
+
if (n.level != 0) {
|
|
626
|
+
out = (out + ",\"level\":" + (to_string n.level))
|
|
627
|
+
}
|
|
628
|
+
if (n.setSize != 0) {
|
|
629
|
+
out = (out + ",\"size\":" + (to_string n.setSize))
|
|
630
|
+
}
|
|
631
|
+
if (n.caret != 0) {
|
|
632
|
+
out = (out + ",\"caret\":" + (to_string n.caret))
|
|
633
|
+
}
|
|
634
|
+
if (n.selStart != n.selEnd) {
|
|
635
|
+
out = (out + ",\"sel\":[" + (to_string n.selStart) + "," + (to_string n.selEnd) + "]")
|
|
636
|
+
}
|
|
637
|
+
if n.actActivate {
|
|
638
|
+
out = (out + ",\"activate\":true")
|
|
639
|
+
}
|
|
640
|
+
if n.actSetValue {
|
|
641
|
+
out = (out + ",\"setValue\":true")
|
|
642
|
+
}
|
|
643
|
+
if n.actExpand {
|
|
644
|
+
out = (out + ",\"expandable\":true")
|
|
645
|
+
}
|
|
646
|
+
if n.actScrollTo {
|
|
647
|
+
out = (out + ",\"scrollTo\":true")
|
|
648
|
+
}
|
|
649
|
+
if (n.live != 0) {
|
|
650
|
+
out = (out + ",\"live\":" + (to_string n.live))
|
|
651
|
+
}
|
|
652
|
+
return (out + "}")
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
; --- the golden dump ------------------------------------------------------
|
|
656
|
+
; The tree is text, which makes accessibility unusually easy to regression
|
|
657
|
+
; test: check this in beside the display-list dumps and a refactor that
|
|
658
|
+
; silently drops a name shows up as a diff. No host and no screen reader
|
|
659
|
+
; are involved.
|
|
660
|
+
|
|
661
|
+
fn dump:string () {
|
|
662
|
+
return (this.dumpFrom(rootId 0))
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
fn dumpFrom:string (id:string depth:int) {
|
|
666
|
+
def n:EVGA11yNode (this.find(id))
|
|
667
|
+
if ((strlen n.id) == 0) {
|
|
668
|
+
return ""
|
|
669
|
+
}
|
|
670
|
+
def out:string (EVGA11yTree.indent(depth))
|
|
671
|
+
out = (out + (EVGA11yRole.ariaName(n.role)))
|
|
672
|
+
out = (out + " #" + n.id)
|
|
673
|
+
if ((strlen n.name) > 0) {
|
|
674
|
+
out = (out + " \"" + n.name + "\"")
|
|
675
|
+
}
|
|
676
|
+
if ((strlen n.value) > 0) {
|
|
677
|
+
out = (out + " =" + n.value)
|
|
678
|
+
}
|
|
679
|
+
def st:string (this.stateText(n))
|
|
680
|
+
if ((strlen st) > 0) {
|
|
681
|
+
out = (out + " [" + st + "]")
|
|
682
|
+
}
|
|
683
|
+
out = (out + " (" + (to_string n.x) + "," + (to_string n.y))
|
|
684
|
+
out = (out + " " + (to_string n.w) + "x" + (to_string n.h) + ")\n")
|
|
685
|
+
def i:int 0
|
|
686
|
+
while (i < (array_length nodes)) {
|
|
687
|
+
def c:EVGA11yNode (itemAt nodes i)
|
|
688
|
+
if (c.parentId == id) {
|
|
689
|
+
def nd:int (depth + 1)
|
|
690
|
+
out = (out + (this.dumpFrom(c.id nd)))
|
|
691
|
+
}
|
|
692
|
+
i = (i + 1)
|
|
693
|
+
}
|
|
694
|
+
return out
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
fn stateText:string (n:EVGA11yNode) {
|
|
698
|
+
def out:string ""
|
|
699
|
+
if n.focused {
|
|
700
|
+
out = (EVGA11yTree.addWord(out "focused"))
|
|
701
|
+
}
|
|
702
|
+
if n.focusable {
|
|
703
|
+
if (n.focused == false) {
|
|
704
|
+
out = (EVGA11yTree.addWord(out "focusable"))
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if n.disabled {
|
|
708
|
+
out = (EVGA11yTree.addWord(out "disabled"))
|
|
709
|
+
}
|
|
710
|
+
if n.selected {
|
|
711
|
+
out = (EVGA11yTree.addWord(out "selected"))
|
|
712
|
+
}
|
|
713
|
+
; The words a reader hears. Only a "true" is spoken: "not required" is
|
|
714
|
+
; not something anybody says out loud, and `aria-required="false"` is
|
|
715
|
+
; still a claim worth carrying in the tree even though it is silent.
|
|
716
|
+
if (n.required == "true") {
|
|
717
|
+
out = (EVGA11yTree.addWord(out "required"))
|
|
718
|
+
}
|
|
719
|
+
if (n.invalid == "true") {
|
|
720
|
+
out = (EVGA11yTree.addWord(out "invalid"))
|
|
721
|
+
}
|
|
722
|
+
if (n.readOnly == "true") {
|
|
723
|
+
out = (EVGA11yTree.addWord(out "readonly"))
|
|
724
|
+
}
|
|
725
|
+
if n.modal {
|
|
726
|
+
out = (EVGA11yTree.addWord(out "modal"))
|
|
727
|
+
}
|
|
728
|
+
if (n.checked == 2) {
|
|
729
|
+
out = (EVGA11yTree.addWord(out "checked"))
|
|
730
|
+
}
|
|
731
|
+
if (n.checked == 3) {
|
|
732
|
+
out = (EVGA11yTree.addWord(out "mixed"))
|
|
733
|
+
}
|
|
734
|
+
if (n.live == 1) {
|
|
735
|
+
out = (EVGA11yTree.addWord(out "live"))
|
|
736
|
+
}
|
|
737
|
+
if (n.live == 2) {
|
|
738
|
+
out = (EVGA11yTree.addWord(out "live!"))
|
|
739
|
+
}
|
|
740
|
+
if (n.rowIndex > 0) {
|
|
741
|
+
out = (EVGA11yTree.addWord(out ("r" + (to_string n.rowIndex))))
|
|
742
|
+
}
|
|
743
|
+
if (n.colIndex > 0) {
|
|
744
|
+
out = (EVGA11yTree.addWord(out ("c" + (to_string n.colIndex))))
|
|
745
|
+
}
|
|
746
|
+
if (n.rowCount > 0) {
|
|
747
|
+
out = (EVGA11yTree.addWord(out ("of " + (to_string n.rowCount) + " rows")))
|
|
748
|
+
}
|
|
749
|
+
return out
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
; --- the lints ------------------------------------------------------------
|
|
753
|
+
; Failures a sighted developer cannot see. These belong in the test suite,
|
|
754
|
+
; because "the button has no name" is invisible on screen and total for
|
|
755
|
+
; someone using a reader.
|
|
756
|
+
|
|
757
|
+
fn lint:[string] () {
|
|
758
|
+
def problems:[string]
|
|
759
|
+
; Whatever the BUILDER could not represent, reported before anything
|
|
760
|
+
; this tree can see for itself. A node that was never made breaks no
|
|
761
|
+
; rule about the nodes that were, so without this the failure mode of
|
|
762
|
+
; an unknown role is a clean audit over an empty tree — which is what
|
|
763
|
+
; the tree demo got: eighteen rows on screen, one node, PASS.
|
|
764
|
+
def i0:int 0
|
|
765
|
+
while (i0 < (array_length notes)) {
|
|
766
|
+
push problems (itemAt notes i0)
|
|
767
|
+
i0 = (i0 + 1)
|
|
768
|
+
}
|
|
769
|
+
if ((strlen rootId) == 0) {
|
|
770
|
+
push problems "tree has no root"
|
|
771
|
+
}
|
|
772
|
+
def i:int 0
|
|
773
|
+
while (i < (array_length nodes)) {
|
|
774
|
+
def n:EVGA11yNode (itemAt nodes i)
|
|
775
|
+
def dup:int 0
|
|
776
|
+
def j:int 0
|
|
777
|
+
while (j < (array_length nodes)) {
|
|
778
|
+
def m:EVGA11yNode (itemAt nodes j)
|
|
779
|
+
if (m.id == n.id) {
|
|
780
|
+
dup = (dup + 1)
|
|
781
|
+
}
|
|
782
|
+
j = (j + 1)
|
|
783
|
+
}
|
|
784
|
+
if (dup > 1) {
|
|
785
|
+
if (i == 0) {
|
|
786
|
+
push problems ("duplicate id: " + n.id)
|
|
787
|
+
} {
|
|
788
|
+
def prevSeen:boolean false
|
|
789
|
+
def k:int 0
|
|
790
|
+
while (k < i) {
|
|
791
|
+
def p:EVGA11yNode (itemAt nodes k)
|
|
792
|
+
if (p.id == n.id) {
|
|
793
|
+
prevSeen = true
|
|
794
|
+
}
|
|
795
|
+
k = (k + 1)
|
|
796
|
+
}
|
|
797
|
+
if (prevSeen == false) {
|
|
798
|
+
push problems ("duplicate id: " + n.id)
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if ((strlen n.id) == 0) {
|
|
803
|
+
push problems "node with an empty id"
|
|
804
|
+
}
|
|
805
|
+
if ((strlen n.parentId) > 0) {
|
|
806
|
+
if ((this.has(n.parentId)) == false) {
|
|
807
|
+
push problems (n.id + ": parent " + n.parentId + " is not in the tree")
|
|
808
|
+
}
|
|
809
|
+
} {
|
|
810
|
+
if (n.id != rootId) {
|
|
811
|
+
push problems (n.id + ": no parent, and it is not the root")
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if n.focusable {
|
|
815
|
+
def named:boolean ((strlen n.name) > 0)
|
|
816
|
+
if (named == false) {
|
|
817
|
+
push problems (n.id + ": focusable with no accessible name")
|
|
818
|
+
}
|
|
819
|
+
if (n.w <= 0) {
|
|
820
|
+
push problems (n.id + ": focusable with no width")
|
|
821
|
+
}
|
|
822
|
+
if (n.h <= 0) {
|
|
823
|
+
push problems (n.id + ": focusable with no height")
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (n.role == 0) {
|
|
827
|
+
if ((strlen n.name) > 0) {
|
|
828
|
+
push problems (n.id + ": named, but the role is decoration")
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
i = (i + 1)
|
|
832
|
+
}
|
|
833
|
+
if ((strlen focusId) > 0) {
|
|
834
|
+
if ((this.has(focusId)) == false) {
|
|
835
|
+
push problems ("focus points at " + focusId + ", which is not in the tree")
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return problems
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
; --- helpers --------------------------------------------------------------
|
|
842
|
+
|
|
843
|
+
sfn addWord:string (acc:string word:string) {
|
|
844
|
+
if ((strlen acc) == 0) {
|
|
845
|
+
return word
|
|
846
|
+
}
|
|
847
|
+
return (acc + " " + word)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
sfn indent:string (depth:int) {
|
|
851
|
+
def out:string ""
|
|
852
|
+
def i:int 0
|
|
853
|
+
while (i < depth) {
|
|
854
|
+
out = (out + " ")
|
|
855
|
+
i = (i + 1)
|
|
856
|
+
}
|
|
857
|
+
return out
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
sfn jsonString:string (v:string) {
|
|
861
|
+
def out:string "\""
|
|
862
|
+
def i:int 0
|
|
863
|
+
while (i < (strlen v)) {
|
|
864
|
+
def ch:int (charAt v i)
|
|
865
|
+
if (ch == 34) {
|
|
866
|
+
out = (out + "\\\"")
|
|
867
|
+
} {
|
|
868
|
+
if (ch == 92) {
|
|
869
|
+
out = (out + "\\\\")
|
|
870
|
+
} {
|
|
871
|
+
if (ch == 10) {
|
|
872
|
+
out = (out + "\\n")
|
|
873
|
+
} {
|
|
874
|
+
if (ch == 13) {
|
|
875
|
+
out = (out + "\\r")
|
|
876
|
+
} {
|
|
877
|
+
if (ch == 9) {
|
|
878
|
+
out = (out + "\\t")
|
|
879
|
+
} {
|
|
880
|
+
if (ch < 32) {
|
|
881
|
+
out = (out + " ")
|
|
882
|
+
} {
|
|
883
|
+
out = (out + (strfromcode ch))
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
i = (i + 1)
|
|
891
|
+
}
|
|
892
|
+
return (out + "\"")
|
|
893
|
+
}
|
|
894
|
+
}
|