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.
Files changed (338) hide show
  1. package/CHANGELOG.md +1160 -19
  2. package/LICENSE +3 -1
  3. package/README.md +124 -29
  4. package/dist/Lang.rgr +1188 -290
  5. package/dist/api.d.ts +2343 -879
  6. package/dist/api.js +79924 -56922
  7. package/dist/lib/JSON.rgr +102 -91
  8. package/dist/lib/Shell.rgr +4 -4
  9. package/dist/lib/apple/AppleToolchain.rgr +4 -4
  10. package/dist/lib/apple/README.md +5 -4
  11. package/dist/lib/apple/apple_test.rgr +1 -1
  12. package/dist/lib/core/README.md +1 -1
  13. package/dist/lib/evg/EVG.rgr +12 -0
  14. package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
  15. package/dist/lib/evg/EVGA11yTree.rgr +894 -0
  16. package/dist/lib/evg/EVGBox.rgr +267 -0
  17. package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
  18. package/dist/lib/evg/EVGCodepoint.rgr +316 -0
  19. package/dist/lib/evg/EVGColor.rgr +700 -0
  20. package/dist/lib/evg/EVGCommands.rgr +177 -0
  21. package/dist/lib/evg/EVGComponent.rgr +331 -0
  22. package/dist/lib/evg/EVGComponentTest.rgr +387 -0
  23. package/dist/lib/evg/EVGConnector.rgr +541 -0
  24. package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
  25. package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
  26. package/dist/lib/evg/EVGEasing.rgr +370 -0
  27. package/dist/lib/evg/EVGEffectTest.rgr +253 -0
  28. package/dist/lib/evg/EVGElement.rgr +4914 -0
  29. package/dist/lib/evg/EVGFixedTest.rgr +289 -0
  30. package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
  31. package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
  32. package/dist/lib/evg/EVGFling.rgr +244 -0
  33. package/dist/lib/evg/EVGFocus.rgr +400 -0
  34. package/dist/lib/evg/EVGFocusTest.rgr +399 -0
  35. package/dist/lib/evg/EVGGradient.rgr +322 -0
  36. package/dist/lib/evg/EVGGrapheme.rgr +190 -0
  37. package/dist/lib/evg/EVGGrid.rgr +975 -0
  38. package/dist/lib/evg/EVGHitTest.rgr +208 -0
  39. package/dist/lib/evg/EVGHoles.rgr +294 -0
  40. package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
  41. package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
  42. package/dist/lib/evg/EVGHostTree.rgr +834 -0
  43. package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
  44. package/dist/lib/evg/EVGImageDecode.rgr +117 -0
  45. package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
  46. package/dist/lib/evg/EVGInspect.rgr +883 -0
  47. package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
  48. package/dist/lib/evg/EVGJsonTest.rgr +535 -0
  49. package/dist/lib/evg/EVGLayout.rgr +4220 -0
  50. package/dist/lib/evg/EVGMeasure.rgr +1267 -0
  51. package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
  52. package/dist/lib/evg/EVGPatch.rgr +1721 -0
  53. package/dist/lib/evg/EVGPatchTest.rgr +692 -0
  54. package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
  55. package/dist/lib/evg/EVGReconcile.rgr +226 -0
  56. package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
  57. package/dist/lib/evg/EVGReject.rgr +126 -0
  58. package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
  59. package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
  60. package/dist/lib/evg/EVGRuler.rgr +232 -0
  61. package/dist/lib/evg/EVGRulerTest.rgr +172 -0
  62. package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
  63. package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
  64. package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
  65. package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
  66. package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
  67. package/dist/lib/evg/EVGText.rgr +105 -0
  68. package/dist/lib/evg/EVGTextEngine.rgr +536 -0
  69. package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
  70. package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
  71. package/dist/lib/evg/EVGToolbar.rgr +1369 -0
  72. package/dist/lib/evg/EVGTransition.rgr +728 -0
  73. package/dist/lib/evg/EVGTreeJson.rgr +721 -0
  74. package/dist/lib/evg/EVGUnit.rgr +554 -0
  75. package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
  76. package/dist/lib/evg/EvgApp.rgr +189 -0
  77. package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
  78. package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
  79. package/dist/lib/evg/EvgHost.rgr +451 -0
  80. package/dist/lib/evg/EvgTest.rgr +91 -0
  81. package/dist/lib/evg/EvgTraceColor.rgr +236 -0
  82. package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
  83. package/dist/lib/evg/EvgTraceFit.rgr +767 -0
  84. package/dist/lib/evg/EvgTracePath.rgr +435 -0
  85. package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
  86. package/dist/lib/evg/EvgViewport.rgr +313 -0
  87. package/dist/lib/evg/FxDemoDoc.rgr +185 -0
  88. package/dist/lib/evg/HOSTS.md +227 -0
  89. package/dist/lib/evg/ISSUES.md +835 -0
  90. package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
  91. package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
  92. package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
  93. package/dist/lib/evg/PLAN_EVG.md +518 -0
  94. package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
  95. package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
  96. package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
  97. package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
  98. package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
  99. package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
  100. package/dist/lib/evg/PathBuilder.rgr +333 -0
  101. package/dist/lib/evg/README.md +1236 -0
  102. package/dist/lib/evg/SPEC.md +1252 -0
  103. package/dist/lib/evg/SVGPathParser.rgr +1199 -0
  104. package/dist/lib/evg/SvgParser.rgr +1736 -0
  105. package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
  106. package/dist/lib/evg/VectorShapes.rgr +331 -0
  107. package/dist/lib/evg/VectorStroke.rgr +107 -0
  108. package/dist/lib/evg/VectorViewBox.rgr +380 -0
  109. package/dist/lib/evg/agent/README.md +290 -0
  110. package/dist/lib/evg/agent/evg_agent.rgr +899 -0
  111. package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
  112. package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
  113. package/dist/lib/evg/agent/fixtures/connector.css +63 -0
  114. package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
  115. package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
  116. package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
  117. package/dist/lib/evg/agent/fixtures/popover.css +99 -0
  118. package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
  119. package/dist/lib/evg/agent/roundtrip.sh +74 -0
  120. package/dist/lib/evg/agent/smoke.sh +230 -0
  121. package/dist/lib/evg/android/README.md +97 -0
  122. package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
  123. package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
  124. package/dist/lib/evg/android/androidstubs/App.kt +30 -0
  125. package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
  126. package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
  127. package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
  128. package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
  129. package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
  130. package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
  131. package/dist/lib/evg/android/androidstubs/View.kt +113 -0
  132. package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
  133. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
  134. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
  135. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
  136. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
  137. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
  138. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
  139. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
  140. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
  141. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
  142. package/dist/lib/evg/apple/README.md +128 -0
  143. package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
  144. package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
  145. package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
  146. package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
  147. package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
  148. package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
  149. package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
  150. package/dist/lib/evg/bench/README.md +64 -0
  151. package/dist/lib/evg/bench/layout-bench.mjs +316 -0
  152. package/dist/lib/evg/bench/layout-cases.mjs +510 -0
  153. package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
  154. package/dist/lib/evg/bin/.gitignore +17 -0
  155. package/dist/lib/evg/evg_test.rgr +313 -0
  156. package/dist/lib/evg/gl/README.md +198 -0
  157. package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
  158. package/dist/lib/evg/gl/blur-check.mjs +399 -0
  159. package/dist/lib/evg/gl/boxmodel.json +1 -0
  160. package/dist/lib/evg/gl/demo.html +46 -0
  161. package/dist/lib/evg/gl/effect-presets.css +285 -0
  162. package/dist/lib/evg/gl/effect-presets.js +63 -0
  163. package/dist/lib/evg/gl/effect-shots.mjs +135 -0
  164. package/dist/lib/evg/gl/evg-a11y.js +566 -0
  165. package/dist/lib/evg/gl/evg-binary.js +160 -0
  166. package/dist/lib/evg/gl/evg-engine.js +296 -0
  167. package/dist/lib/evg/gl/evg-fx.js +237 -0
  168. package/dist/lib/evg/gl/evg-gestures.js +209 -0
  169. package/dist/lib/evg/gl/evg-list.js +167 -0
  170. package/dist/lib/evg/gl/evg-measure.js +186 -0
  171. package/dist/lib/evg/gl/evg-textinput.js +303 -0
  172. package/dist/lib/evg/gl/evg-view.js +144 -0
  173. package/dist/lib/evg/gl/evg-webgl.js +3942 -0
  174. package/dist/lib/evg/gl/fx-check.mjs +998 -0
  175. package/dist/lib/evg/gl/fx-demo.html +166 -0
  176. package/dist/lib/evg/gl/fx-demo.js +2 -0
  177. package/dist/lib/evg/gl/fx-serve.mjs +47 -0
  178. package/dist/lib/evg/gl/gestures-check.mjs +189 -0
  179. package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
  180. package/dist/lib/evg/gl/measure-check.mjs +135 -0
  181. package/dist/lib/evg/gl/rotation-check.mjs +207 -0
  182. package/dist/lib/evg/gl/shift-check.mjs +113 -0
  183. package/dist/lib/evg/gl/stroke-check.mjs +168 -0
  184. package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
  185. package/dist/lib/evg/gl/view-check.mjs +343 -0
  186. package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
  187. package/dist/lib/evg/html/evg-dom.js +318 -0
  188. package/dist/lib/evg/html/evg-html.js +600 -0
  189. package/dist/lib/evg/inspect/README.md +415 -0
  190. package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
  191. package/dist/lib/evg/inspect/evg-inspect.js +947 -0
  192. package/dist/lib/evg/inspect/shots/css.png +0 -0
  193. package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
  194. package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
  195. package/dist/lib/evg/inspect/shots/state.png +0 -0
  196. package/dist/lib/evg/inspect/shots.mjs +226 -0
  197. package/dist/lib/evg/oracle/css-blur.json +576 -0
  198. package/dist/lib/evg/oracle/css-box.json +157 -0
  199. package/dist/lib/evg/oracle/css-timing.json +709 -0
  200. package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
  201. package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
  202. package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
  203. package/dist/lib/evg/original/EVGColor.clj +310 -0
  204. package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
  205. package/dist/lib/evg/original/SVGPath.rgr +627 -0
  206. package/dist/lib/evg/original/Vec2.crgr +103 -0
  207. package/dist/lib/evg/ranger.json +9 -0
  208. package/dist/lib/evg/showcase/README.md +246 -0
  209. package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
  210. package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
  211. package/dist/lib/evg/showcase/build.mjs +651 -0
  212. package/dist/lib/evg/showcase/pages/album.tsx +30 -0
  213. package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
  214. package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
  215. package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
  216. package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
  217. package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
  218. package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
  219. package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
  220. package/dist/lib/evg/showcase/pages/more.tsx +332 -0
  221. package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
  222. package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
  223. package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
  224. package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
  225. package/dist/lib/evg/showcase/pages/units.tsx +35 -0
  226. package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
  227. package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
  228. package/dist/lib/evg/showcase/pages/views.tsx +223 -0
  229. package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
  230. package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
  231. package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
  232. package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
  233. package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
  234. package/dist/lib/evg/showcase/themes/more-default.css +128 -0
  235. package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
  236. package/dist/lib/evg/showcase/themes/showcase.css +508 -0
  237. package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
  238. package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
  239. package/dist/lib/evg/showcase/themes/views-default.css +15 -0
  240. package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
  241. package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
  242. package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
  243. package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
  244. package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
  245. package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
  246. package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
  247. package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
  248. package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
  249. package/dist/lib/evg/web/responsive/README.md +97 -0
  250. package/dist/lib/evg/web/responsive/build.mjs +90 -0
  251. package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
  252. package/dist/lib/evg/web/responsive/index.html +177 -0
  253. package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
  254. package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
  255. package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
  256. package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
  257. package/dist/lib/evg/web/tools/minify.mjs +60 -0
  258. package/dist/lib/evg/web/tracer/build.mjs +87 -0
  259. package/dist/lib/evg/web/tracer/index.html +2805 -0
  260. package/dist/lib/evg/web/tracer/sample.png +0 -0
  261. package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
  262. package/dist/lib/evgr/Cargo.lock +21 -0
  263. package/dist/lib/evgr/Cargo.toml +19 -0
  264. package/dist/lib/evgr/README.md +140 -0
  265. package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
  266. package/dist/lib/evgr/bench/compare.mjs +252 -0
  267. package/dist/lib/evgr/bench/speed.mjs +277 -0
  268. package/dist/lib/evgr/bin/.gitignore +3 -0
  269. package/dist/lib/evgr/src/bin/bench.rs +106 -0
  270. package/dist/lib/evgr/src/bin/smoke.rs +13 -0
  271. package/dist/lib/evgr/src/grid.rs +730 -0
  272. package/dist/lib/evgr/src/lib.rs +1003 -0
  273. package/dist/lib/evgr/src/style.rs +468 -0
  274. package/dist/lib/evgr/src/text.rs +83 -0
  275. package/dist/lib/image/BitReader.rgr +171 -0
  276. package/dist/lib/image/Buffer.rgr +173 -0
  277. package/dist/lib/image/DCT.rgr +283 -0
  278. package/dist/lib/image/Deflate.rgr +339 -0
  279. package/dist/lib/image/HuffmanDecoder.rgr +181 -0
  280. package/dist/lib/image/ImageBuffer.rgr +706 -0
  281. package/dist/lib/image/JPEGDecoder.rgr +808 -0
  282. package/dist/lib/image/PNGDecoder.rgr +544 -0
  283. package/dist/lib/image/PNGEncoder.rgr +388 -0
  284. package/dist/lib/image/PPMImage.rgr +224 -0
  285. package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
  286. package/dist/lib/image/README.md +33 -0
  287. package/dist/lib/image/RasterBuffer.rgr +294 -0
  288. package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
  289. package/dist/lib/image/WebPDecoder.rgr +433 -0
  290. package/dist/lib/image/WebPLossless.rgr +1035 -0
  291. package/dist/lib/image/WebPLossy.rgr +1929 -0
  292. package/dist/lib/image/ranger.json +9 -0
  293. package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
  294. package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
  295. package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
  296. package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
  297. package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
  298. package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
  299. package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
  300. package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
  301. package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
  302. package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
  303. package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
  304. package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
  305. package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
  306. package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
  307. package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
  308. package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
  309. package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
  310. package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
  311. package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
  312. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
  313. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
  314. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
  315. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
  316. package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
  317. package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
  318. package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
  319. package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
  320. package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
  321. package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
  322. package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
  323. package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
  324. package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
  325. package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
  326. package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
  327. package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
  328. package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
  329. package/dist/lib/image/tests/run_webp_tests.sh +39 -0
  330. package/dist/lib/rust/RsJson.rgr +468 -0
  331. package/dist/lib/rust/RsPrelude.rgr +1663 -0
  332. package/dist/lib/shell_test.rgr +7 -7
  333. package/dist/lib/stdlib.rgr +21 -5
  334. package/dist/lib/zip/ranger.json +6 -0
  335. package/dist/rgrc.js +87318 -67610
  336. package/package.json +1010 -1040
  337. package/dist/README.md +0 -117
  338. 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
+ }