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,395 @@
1
+ ; SPDX-License-Identifier: MIT
2
+
3
+ ; ==============================================================================
4
+ ; EVGPopoverTest — surfaces: named anchors, fallbacks, fit and presentation
5
+ ; ==============================================================================
6
+ ;
7
+ ; EVG has had overlay surfaces for a while: out of flow, drawn in the top layer
8
+ ; after the whole normal tree, placed against an anchor and flipped when they
9
+ ; ran off the page. What it did not have is the four things that turn that into
10
+ ; a menu system, and this is the test for those four.
11
+ ;
12
+ ; * `position-anchor: --file` — the anchor NAMED, rather than found among the
13
+ ; surface's own siblings. A menu no longer has to be declared beside its
14
+ ; trigger, which is what a portal is for in every other toolkit.
15
+ ; * `position-try-fallbacks` — an ordered list of `position-area`s, tried
16
+ ; until one is wholly on the page, with `position-try-order: most-space`
17
+ ; to take the roomiest instead of the first.
18
+ ; * `fit-viewport` — take the room there is rather than running off the page,
19
+ ; and scroll for the rest.
20
+ ; * `presentation: sheet | fullscreen` — on a phone an anchored menu is the
21
+ ; wrong widget, not a badly placed one.
22
+ ;
23
+ ; The old behaviour is asserted by EVGOverlayTest and is deliberately not
24
+ ; repeated here; what this file checks is that the new parts do what they say
25
+ ; and that a surface that declares none of them is untouched.
26
+ ; ==============================================================================
27
+
28
+ Import "EVGElement.rgr"
29
+ Import "EVGUnit.rgr"
30
+ Import "EVGLayout.rgr"
31
+
32
+ class PopCheck {
33
+ def passed:int 0
34
+ def failed:int 0
35
+
36
+ fn ok:void (name:string cond:boolean) {
37
+ if cond {
38
+ passed = (passed + 1)
39
+ print (" PASS " + name)
40
+ } {
41
+ failed = (failed + 1)
42
+ print (" FAIL " + name)
43
+ }
44
+ }
45
+
46
+ fn eqInt:void (name:string got:int want:int) {
47
+ def good:boolean (got == want)
48
+ if (good == false) {
49
+ print (" got " + (to_string got) + " want " + (to_string want))
50
+ }
51
+ this.ok(name good)
52
+ }
53
+
54
+ fn eqStr:void (name:string got:string want:string) {
55
+ def good:boolean (got == want)
56
+ if (good == false) {
57
+ print (" got \"" + got + "\" want \"" + want + "\"")
58
+ }
59
+ this.ok(name good)
60
+ }
61
+ }
62
+
63
+ class EVGPopoverTest {
64
+
65
+ sfn box:EVGElement (id:string w:double h:double) {
66
+ def el (EVGElement.createDiv())
67
+ el.id = id
68
+ el.setAttribute("width" ((to_string (to_int w)) + "px"))
69
+ el.setAttribute("height" ((to_string (to_int h)) + "px"))
70
+ return el
71
+ }
72
+
73
+ sfn laidOut:EVGLayout (root:EVGElement w:double h:double) {
74
+ def lay (new EVGLayout())
75
+ lay.setPageSize(w h)
76
+ lay.layout(root)
77
+ return lay
78
+ }
79
+
80
+ ; A page with a bar at `barY`, a trigger in it named `--file`, and a body
81
+ ; below. The surface is added by the caller.
82
+ sfn buildPage:EVGElement (barY:double) {
83
+ def page:EVGElement (EVGPopoverTest.box("page" 400.0 800.0))
84
+ page.setAttribute("flex-direction" "column")
85
+ def spacer:EVGElement (EVGPopoverTest.box("spacer" 400.0 barY))
86
+ if (barY > 0.0) {
87
+ page.addChild(spacer)
88
+ }
89
+ def bar:EVGElement (EVGPopoverTest.box("bar" 400.0 40.0))
90
+ bar.setAttribute("flex-direction" "row")
91
+ def trigger:EVGElement (EVGPopoverTest.box("file" 80.0 40.0))
92
+ trigger.setAttribute("anchor-name" "--file")
93
+ bar.addChild(trigger)
94
+ page.addChild(bar)
95
+ ; Whatever is left under the bar. The page must not overflow: a column
96
+ ; that does shrinks its children, and then the trigger is not where the
97
+ ; test says it is.
98
+ def rest:double ((800.0 - barY) - 40.0)
99
+ if (rest > 200.0) {
100
+ rest = 200.0
101
+ }
102
+ page.addChild((EVGPopoverTest.box("body" 400.0 rest)))
103
+ return page
104
+ }
105
+
106
+ sfn menu:EVGElement (w:double h:double) {
107
+ def m (EVGElement.createDiv())
108
+ m.tagName = "popover"
109
+ m.id = "menu"
110
+ m.setAttribute("width" ((to_string (to_int w)) + "px"))
111
+ m.setAttribute("height" ((to_string (to_int h)) + "px"))
112
+ m.setAttribute("position-anchor" "--file")
113
+ return m
114
+ }
115
+
116
+ ; ---------------------------------------------------------------------
117
+
118
+ sfn testNamedAnchor:void (c:PopCheck) {
119
+ print ""
120
+ print "--- position-anchor names the box, wherever it is ---"
121
+ def page:EVGElement (EVGPopoverTest.buildPage(0.0))
122
+ def m:EVGElement (EVGPopoverTest.menu(160.0 100.0))
123
+ m.setAttribute("position-area" "bottom start")
124
+ page.addChild(m)
125
+ def lay:EVGLayout (EVGPopoverTest.laidOut(page 400.0 800.0))
126
+ ; The trigger is two levels down in another subtree; the surface is a
127
+ ; child of the page and has no anchor sibling at all.
128
+ c.eqInt("under the trigger, not under its own parent" (to_int m.calculatedY) 44)
129
+ c.eqInt("aligned to the trigger's left edge" (to_int m.calculatedX) 0)
130
+ c.eqStr("and it says where it went" m.overlayPlacedSide "bottom")
131
+ c.eqStr("…and how it was aligned" m.overlayPlacedAlign "start")
132
+ c.eqStr("…and what it is" m.overlayPlacedPresentation "anchored")
133
+ ; Out of flow: the body sits directly under the bar, and the `popover`
134
+ ; tag alone was enough to make that true — nothing said `overlay`.
135
+ def body:EVGElement (page.getChild(1))
136
+ c.eqStr("the third child is the body" body.id "body")
137
+ c.eqInt("a popover takes no space in the flow" (to_int body.calculatedY) 40)
138
+ c.ok("it is a surface" (m.isSurface()))
139
+ c.ok("…which position-anchor said on its own" m.isOverlay)
140
+ ; And the tag says it too, with no properties at all: `popover` is the
141
+ ; web's name for this and is worth having as one.
142
+ def bare (EVGElement.createDiv())
143
+ bare.tagName = "popover"
144
+ c.ok("the popover tag alone is a surface" (bare.isSurface()))
145
+ c.ok("…and it took no property to say so" (bare.isOverlay == false))
146
+ }
147
+
148
+ sfn testPositionArea:void (c:PopCheck) {
149
+ print ""
150
+ print "--- position-area, both spellings of the cross axis ---"
151
+ def page:EVGElement (EVGPopoverTest.buildPage(200.0))
152
+ def m:EVGElement (EVGPopoverTest.menu(160.0 100.0))
153
+ m.setAttribute("position-area" "bottom end")
154
+ page.addChild(m)
155
+ def lay:EVGLayout (EVGPopoverTest.laidOut(page 400.0 800.0))
156
+ ; The trigger is 80 wide at x=0, so `end` puts the surface's right edge
157
+ ; on the trigger's: 80 - 160. Clamped back onto the page at 0.
158
+ c.eqInt("end aligns the far edges" (to_int m.calculatedX) 0)
159
+ c.eqInt("below the bar at y=200" (to_int m.calculatedY) 244)
160
+
161
+ def page2:EVGElement (EVGPopoverTest.buildPage(200.0))
162
+ def m2:EVGElement (EVGPopoverTest.menu(160.0 100.0))
163
+ m2.setAttribute("position-area" "right top")
164
+ page2.addChild(m2)
165
+ def lay2:EVGLayout (EVGPopoverTest.laidOut(page2 400.0 800.0))
166
+ c.eqStr("a physical cross word is read as start" m2.overlayPlacedAlign "start")
167
+ c.eqInt("beside the trigger" (to_int m2.calculatedX) 84)
168
+ c.eqInt("…and level with its top" (to_int m2.calculatedY) 200)
169
+ }
170
+
171
+ sfn testFallbacks:void (c:PopCheck) {
172
+ print ""
173
+ print "--- position-try-fallbacks, in order, first one that fits ---"
174
+ ; The bar is at the bottom of the page: 740..780.
175
+ def page:EVGElement (EVGPopoverTest.buildPage(740.0))
176
+ def m:EVGElement (EVGPopoverTest.menu(160.0 100.0))
177
+ m.setAttribute("position-area" "bottom start")
178
+ m.setAttribute("position-try-fallbacks" "right start, top start")
179
+ page.addChild(m)
180
+ def lay:EVGLayout (EVGPopoverTest.laidOut(page 400.0 800.0))
181
+ ; bottom: 784 + 100 runs 84 past the page.
182
+ ; right: x=84 fits, but `start` puts its top at 740 and its bottom at
183
+ ; 840 — off the page by 40, so it is not a fit either.
184
+ ; top: 636..736. It fits, and it is the first that does.
185
+ c.eqStr("it landed on the first candidate that is wholly on the page" m.overlayPlacedSide "top")
186
+ c.eqInt("above the trigger, gap included" (to_int m.calculatedY) 636)
187
+
188
+ ; A fallback that is not a position-area is reported, not obeyed.
189
+ def page2:EVGElement (EVGPopoverTest.buildPage(740.0))
190
+ def m2:EVGElement (EVGPopoverTest.menu(160.0 100.0))
191
+ m2.setAttribute("position-area" "bottom start")
192
+ m2.setAttribute("position-try-fallbacks" "sideways, top start")
193
+ page2.addChild(m2)
194
+ def lay2:EVGLayout (EVGPopoverTest.laidOut(page2 400.0 800.0))
195
+ c.eqStr("a nonsense fallback is skipped" m2.overlayPlacedSide "top")
196
+ def said:boolean false
197
+ def i:int 0
198
+ while (i < (lay2.warningCount())) {
199
+ def w:string (lay2.warningAt(i))
200
+ if ((indexOf w "sideways") >= 0) {
201
+ said = true
202
+ }
203
+ i = (i + 1)
204
+ }
205
+ c.ok("…and named in the warnings" said)
206
+ }
207
+
208
+ sfn testMostSpace:void (c:PopCheck) {
209
+ print ""
210
+ print "--- position-try-order: most-space ---"
211
+ ; Trigger at 300..340: 300 above, 460 below. Both fit a 100-tall menu.
212
+ def page:EVGElement (EVGPopoverTest.buildPage(300.0))
213
+ def m:EVGElement (EVGPopoverTest.menu(160.0 100.0))
214
+ m.setAttribute("position-area" "top start")
215
+ m.setAttribute("position-try-fallbacks" "bottom start")
216
+ page.addChild(m)
217
+ def lay:EVGLayout (EVGPopoverTest.laidOut(page 400.0 800.0))
218
+ c.eqStr("in order, the declared side wins because it fits" m.overlayPlacedSide "top")
219
+
220
+ def page2:EVGElement (EVGPopoverTest.buildPage(300.0))
221
+ def m2:EVGElement (EVGPopoverTest.menu(160.0 100.0))
222
+ m2.setAttribute("position-area" "top start")
223
+ m2.setAttribute("position-try-fallbacks" "bottom start")
224
+ m2.setAttribute("position-try-order" "most-space")
225
+ page2.addChild(m2)
226
+ def lay2:EVGLayout (EVGPopoverTest.laidOut(page2 400.0 800.0))
227
+ c.eqStr("most-space takes the roomier one instead" m2.overlayPlacedSide "bottom")
228
+ c.eqInt("below the trigger" (to_int m2.calculatedY) 344)
229
+ }
230
+
231
+ sfn testFitViewport:void (c:PopCheck) {
232
+ print ""
233
+ print "--- fit-viewport: take the room there is, and scroll for the rest ---"
234
+ def page:EVGElement (EVGPopoverTest.buildPage(600.0))
235
+ def m:EVGElement (EVGPopoverTest.menu(160.0 700.0))
236
+ m.setAttribute("position-area" "bottom start")
237
+ m.setAttribute("fit-viewport" "true")
238
+ m.setAttribute("overflow" "hidden")
239
+ def r:int 0
240
+ while (r < 10) {
241
+ m.addChild((EVGPopoverTest.box(("row" + (to_string r)) 160.0 70.0)))
242
+ r = (r + 1)
243
+ }
244
+ page.addChild(m)
245
+ def lay:EVGLayout (EVGPopoverTest.laidOut(page 400.0 800.0))
246
+ ; Neither side fits 700. Above the trigger hangs off by 104 and below
247
+ ; by 544, so it opens upwards and is cut to the 596 there is.
248
+ c.eqStr("it opened on the side it loses least on" m.overlayPlacedSide "top")
249
+ c.ok("and it says it was cut" m.overlayClamped)
250
+ c.eqInt("to the room above the trigger" (to_int m.calculatedHeight) 596)
251
+ c.eqInt("which starts at the top of the page" (to_int m.calculatedY) 0)
252
+ c.eqInt("the rest is there to scroll to" (to_int m.scrollHeight) 700)
253
+ c.eqInt("…which is 104 of scrolling" (to_int (m.maxScrollTop())) 104)
254
+ }
255
+
256
+ sfn sheetMenu:EVGElement () {
257
+ def m (EVGElement.createDiv())
258
+ m.tagName = "popover"
259
+ m.id = "menu"
260
+ m.setAttribute("width" "160px")
261
+ m.setAttribute("position-anchor" "--file")
262
+ m.setAttribute("position-area" "bottom start")
263
+ m.setAttribute("flex-direction" "column")
264
+ def r:int 0
265
+ while (r < 3) {
266
+ def row:EVGElement (EVGPopoverTest.box(("row" + (to_string r)) 160.0 60.0))
267
+ row.setAttribute("width" "100%")
268
+ m.addChild(row)
269
+ r = (r + 1)
270
+ }
271
+ return m
272
+ }
273
+
274
+ sfn testSheet:void (c:PopCheck) {
275
+ print ""
276
+ print "--- presentation: a menu is the wrong widget on a phone ---"
277
+ def page:EVGElement (EVGPopoverTest.buildPage(0.0))
278
+ def m:EVGElement (EVGPopoverTest.sheetMenu())
279
+ m.setAttribute("sheet-below" "600px")
280
+ page.addChild(m)
281
+ def lay:EVGLayout (EVGPopoverTest.laidOut(page 400.0 800.0))
282
+ c.eqStr("at 400 wide it is a sheet" m.overlayPlacedPresentation "sheet")
283
+ c.eqInt("as wide as the page" (to_int m.calculatedWidth) 400)
284
+ c.eqInt("against its left edge" (to_int m.calculatedX) 0)
285
+ c.eqInt("as tall as its three rows" (to_int m.calculatedHeight) 180)
286
+ c.eqInt("along the bottom edge" (to_int m.calculatedY) 620)
287
+ ; The rows were laid out again at the sheet's width, not the menu's.
288
+ def row0:EVGElement (m.getChild(0))
289
+ c.eqInt("and the rows were laid out again at that width" (to_int row0.calculatedWidth) 400)
290
+
291
+ def wide:EVGElement (EVGPopoverTest.buildPage(0.0))
292
+ def m2:EVGElement (EVGPopoverTest.sheetMenu())
293
+ m2.setAttribute("sheet-below" "600px")
294
+ wide.addChild(m2)
295
+ def lay2:EVGLayout (EVGPopoverTest.laidOut(wide 900.0 800.0))
296
+ c.eqStr("at 900 it is an anchored menu again" m2.overlayPlacedPresentation "anchored")
297
+ c.eqInt("under the trigger" (to_int m2.calculatedY) 44)
298
+ c.eqInt("and back to its own width" (to_int m2.calculatedWidth) 160)
299
+ }
300
+
301
+ sfn testFullscreen:void (c:PopCheck) {
302
+ print ""
303
+ print "--- presentation: fullscreen ---"
304
+ def page:EVGElement (EVGPopoverTest.buildPage(0.0))
305
+ def m:EVGElement (EVGPopoverTest.sheetMenu())
306
+ m.setAttribute("presentation" "fullscreen")
307
+ page.addChild(m)
308
+ def lay:EVGLayout (EVGPopoverTest.laidOut(page 400.0 800.0))
309
+ c.eqStr("it covers the page" m.overlayPlacedPresentation "fullscreen")
310
+ c.eqInt("from the top left" (to_int m.calculatedX) 0)
311
+ c.eqInt("…" (to_int m.calculatedY) 0)
312
+ c.eqInt("as wide as the page" (to_int m.calculatedWidth) 400)
313
+ c.eqInt("and as tall" (to_int m.calculatedHeight) 800)
314
+ }
315
+
316
+ sfn testAnchorInsets:void (c:PopCheck) {
317
+ print ""
318
+ print "--- anchor() places an edge, not a side ---"
319
+ def page:EVGElement (EVGPopoverTest.buildPage(200.0))
320
+ ; A badge hanging off the trigger's top-right corner. The trigger is
321
+ ; 80x40 at (0,200), so its right edge is x=80 and its top is y=200.
322
+ def badge (EVGElement.createDiv())
323
+ badge.id = "badge"
324
+ badge.setAttribute("width" "20px")
325
+ badge.setAttribute("height" "20px")
326
+ badge.setAttribute("position-anchor" "--file")
327
+ badge.setAttribute("left" "calc(anchor(right) - 8px)")
328
+ badge.setAttribute("top" "calc(anchor(top) - 6px)")
329
+ page.addChild(badge)
330
+ def lay:EVGLayout (EVGPopoverTest.laidOut(page 400.0 800.0))
331
+ c.eqStr("placed by its edges" badge.overlayPlacedSide "anchor")
332
+ c.eqInt("left edge on the anchor's right, less the offset" (to_int badge.calculatedX) 72)
333
+ c.eqInt("top edge above the anchor's top" (to_int badge.calculatedY) 194)
334
+
335
+ ; The other two insets: `right`/`bottom` measure the box's far edges.
336
+ def page2:EVGElement (EVGPopoverTest.buildPage(200.0))
337
+ def tag2 (EVGElement.createDiv())
338
+ tag2.id = "tag"
339
+ tag2.setAttribute("width" "30px")
340
+ tag2.setAttribute("height" "10px")
341
+ tag2.setAttribute("position-anchor" "--file")
342
+ tag2.setAttribute("right" "anchor(left)")
343
+ tag2.setAttribute("bottom" "anchor(center)")
344
+ page2.addChild(tag2)
345
+ def lay2:EVGLayout (EVGPopoverTest.laidOut(page2 400.0 800.0))
346
+ c.eqInt("its right edge on the anchor's left" (to_int (tag2.calculatedX + tag2.calculatedWidth)) 0)
347
+ c.eqInt("its bottom on the anchor's middle" (to_int (tag2.calculatedY + tag2.calculatedHeight)) 220)
348
+
349
+ ; Both insets on an axis stretch the box between them.
350
+ def page3:EVGElement (EVGPopoverTest.buildPage(200.0))
351
+ def rule (EVGElement.createDiv())
352
+ rule.id = "rule"
353
+ rule.setAttribute("height" "2px")
354
+ rule.setAttribute("position-anchor" "--file")
355
+ rule.setAttribute("left" "anchor(left)")
356
+ rule.setAttribute("right" "calc(anchor(right) + 40px)")
357
+ rule.setAttribute("top" "calc(anchor(bottom) + 4px)")
358
+ page3.addChild(rule)
359
+ def lay3:EVGLayout (EVGPopoverTest.laidOut(page3 400.0 800.0))
360
+ c.eqInt("stretched from one inset to the other" (to_int rule.calculatedWidth) 120)
361
+ c.eqInt("starting at the anchor's left" (to_int rule.calculatedX) 0)
362
+ c.eqInt("just under it" (to_int rule.calculatedY) 244)
363
+
364
+ ; An inset on the wrong axis is reported, not guessed at.
365
+ def page4:EVGElement (EVGPopoverTest.buildPage(200.0))
366
+ def bad (EVGElement.createDiv())
367
+ bad.id = "bad"
368
+ bad.setAttribute("width" "10px")
369
+ bad.setAttribute("height" "10px")
370
+ bad.setAttribute("position-anchor" "--file")
371
+ bad.setAttribute("left" "anchor(bottom)")
372
+ page4.addChild(bad)
373
+ c.ok("a cross-axis edge is not taken" ((strlen bad.anchorLeftSide) == 0))
374
+ }
375
+
376
+ sfn main:void () {
377
+ def c (new PopCheck())
378
+ print "=== EVG popovers ==="
379
+ EVGPopoverTest.testNamedAnchor(c)
380
+ EVGPopoverTest.testPositionArea(c)
381
+ EVGPopoverTest.testFallbacks(c)
382
+ EVGPopoverTest.testMostSpace(c)
383
+ EVGPopoverTest.testFitViewport(c)
384
+ EVGPopoverTest.testSheet(c)
385
+ EVGPopoverTest.testFullscreen(c)
386
+ EVGPopoverTest.testAnchorInsets(c)
387
+ print ""
388
+ print ("passed=" + (to_string c.passed) + " failed=" + (to_string c.failed))
389
+ if (c.failed > 0) {
390
+ print "FAILURES"
391
+ } {
392
+ print "ALL PASS"
393
+ }
394
+ }
395
+ }
@@ -0,0 +1,226 @@
1
+ ; SPDX-License-Identifier: MIT
2
+
3
+ ; ==============================================================================
4
+ ; EVGReconcile — keyed child reconciliation, so a rebuilt tree keeps its elements
5
+ ; ==============================================================================
6
+ ; THE PROBLEM THIS EXISTS FOR, in the words the code already used.
7
+ ;
8
+ ; Every demo in `gallery/ui/demo` keeps its element tree between frames, and
9
+ ; `main.js` says why: "Rebuild only if the data changed." The comment beside it
10
+ ; is a measurement, not a worry —
11
+ ;
12
+ ; every element is new every frame, so every flight establishes at its
13
+ ; destination. Measured before it was believed: the rows making room for a
14
+ ; dragged item were at their final positions 40ms after the pointer
15
+ ; crossed, having travelled through nothing.
16
+ ;
17
+ ; So the declarative half of this gallery runs ONCE, at init, and everything
18
+ ; after that is imperative mutation of the tree it produced. `SortableDemo`'s
19
+ ; `applyShift()` is the clearest case: it re-aims an existing list at a new
20
+ ; target by hand, and it walks by INDEX — `list.children[i]` is assumed to be
21
+ ; the row for `order[i]`, which holds only because `order` does not change
22
+ ; during a drag. It is a keyed reconciler for one list, written without keys,
23
+ ; correct by luck.
24
+ ;
25
+ ; The missing piece is identity. A transition, a caret, a scroll offset and a
26
+ ; hover are all properties OF AN ELEMENT, and a tree built from scratch has
27
+ ; different elements in every position — so they have nowhere to live.
28
+ ;
29
+ ; --- WHAT THIS DOES -----------------------------------------------------------
30
+ ;
31
+ ; `reconcile(live, next)` mutates `live` until it describes what `next`
32
+ ; describes, keeping every `live` element it can:
33
+ ;
34
+ ; * children are matched within their parent by (key, tagName)
35
+ ; * a matched child KEEPS ITS ELEMENT — `adoptFrom` overwrites everything it
36
+ ; describes except `children`, `parent` and `transitions`
37
+ ; * an unmatched new child is taken from `next` as-is
38
+ ; * an unmatched old child is dropped
39
+ ; * the resulting child ORDER is `next`'s, so a reorder MOVES elements
40
+ ;
41
+ ; `next` is consumed: after this returns it must not be used again. Nothing is
42
+ ; cloned, so the two trees would share nodes.
43
+ ;
44
+ ; --- MATCHING, and the one rule that is not obvious ---------------------------
45
+ ;
46
+ ; A KEYED child matches the old child with the same key AND the same tag.
47
+ ; Same key, different tag is NOT a match: the key says "same logical child",
48
+ ; the tag says "same kind of thing", and reusing a text node's element as a
49
+ ; button's would carry a text node's leftovers into a button.
50
+ ;
51
+ ; AN UNKEYED child matches by POSITION AMONG UNKEYED SIBLINGS — not by
52
+ ; position among all siblings. Consider `[a(key=1), b, c(key=2)]` becoming
53
+ ; `[c(key=2), b, a(key=1)]`: `b` is the first unkeyed child in both, and it
54
+ ; should stay itself. Counting all siblings would match `b` against `c`,
55
+ ; fail the tag test, and throw away an element for no reason.
56
+ ;
57
+ ; A key is scoped to ITS PARENT. Two lists on one page may both key a row
58
+ ; "video"; that is the whole reason `key` is not `id`.
59
+ ;
60
+ ; --- WHAT THIS DELIBERATELY DOES NOT DO ---------------------------------------
61
+ ;
62
+ ; It does not decide WHEN to rebuild, it does not track dependencies, and it
63
+ ; does not know what a component is. It is the identity layer those need and
64
+ ; nothing above it.
65
+ ;
66
+ ; It also does not run `EVGTransition`. The order is: reconcile the structure,
67
+ ; then let the stylesheet write what it wants, then let `EVGTransition`
68
+ ; reconcile that against the flights it kept. Two passes that both decide which
69
+ ; element is "the same one" would be two opinions; this one decides, and the
70
+ ; transition pass reads the answer.
71
+ ; ==============================================================================
72
+
73
+ Import "EVGElement.rgr"
74
+
75
+ class EVGReconcileStats {
76
+ ; How many elements survived, and how many had to be made. The ratio is the
77
+ ; whole point of this class, so it is reported rather than inferred: a
78
+ ; reconciler that silently matches nothing looks exactly like one that
79
+ ; works, until something tries to animate.
80
+ def kept:int 0
81
+ def created:int 0
82
+ def dropped:int 0
83
+ def moved:int 0
84
+
85
+ Constructor () {
86
+ }
87
+ }
88
+
89
+ class EVGReconcile {
90
+
91
+ def stats:EVGReconcileStats (new EVGReconcileStats())
92
+
93
+ Constructor () {
94
+ }
95
+
96
+ ; True when these two may be the same element.
97
+ sfn matches:boolean (a:EVGElement b:EVGElement) {
98
+ if (a.tagName != b.tagName) {
99
+ return false
100
+ }
101
+ return (a.key == b.key)
102
+ }
103
+
104
+ ; Bring `live` into line with `next`, keeping `live` itself.
105
+ fn reconcile:void (live:EVGElement next:EVGElement) {
106
+ def kids:[EVGElement] (this.matchChildren(live next))
107
+ ; Matching first and adopting second is the order that reads correctly,
108
+ ; and swapping the two changes NOTHING — `adoptFrom` skips `children`,
109
+ ; so the match still sees the old list either way. Tried as a mutation
110
+ ; and it survived, which is the only reason that is stated here rather
111
+ ; than as a warning: the safety comes from `adoptFrom`'s skip list, not
112
+ ; from these two lines being in this order.
113
+ live.adoptFrom(next)
114
+ def fresh:[EVGElement]
115
+ live.children = fresh
116
+ def i:int 0
117
+ while (i < (array_length kids)) {
118
+ live.addChild((itemAt kids i))
119
+ i = (i + 1)
120
+ }
121
+ }
122
+
123
+ ; The new child list, with survivors reused in `next`'s order.
124
+ fn matchChildren:[EVGElement] (live:EVGElement next:EVGElement) {
125
+ def out:[EVGElement]
126
+ def old:[EVGElement] live.children
127
+ def n:int (array_length old)
128
+
129
+ ; Which old children have been claimed. Ranger has no set, and the
130
+ ; lists here are a screenful of rows rather than a document, so this
131
+ ; is a flag array and a linear scan.
132
+ def taken:[boolean]
133
+ def t:int 0
134
+ while (t < n) {
135
+ push taken false
136
+ t = (t + 1)
137
+ }
138
+
139
+ def m:int (array_length next.children)
140
+ def j:int 0
141
+ ; How many unkeyed children have been seen so far on each side. The
142
+ ; unkeyed match is "the k-th unkeyed here is the k-th unkeyed there".
143
+ def unkeyedSeen:int 0
144
+ while (j < m) {
145
+ def cand:EVGElement (itemAt next.children j)
146
+ def hit:int (0 - 1)
147
+ if ((strlen cand.key) > 0) {
148
+ hit = (this.findKeyed(old taken cand))
149
+ } {
150
+ hit = (this.findUnkeyed(old taken cand unkeyedSeen))
151
+ unkeyedSeen = (unkeyedSeen + 1)
152
+ }
153
+ if (hit >= 0) {
154
+ def keep:EVGElement (itemAt old hit)
155
+ set_at taken hit true
156
+ if (hit != j) {
157
+ stats.moved = (stats.moved + 1)
158
+ }
159
+ this.reconcile(keep cand)
160
+ stats.kept = (stats.kept + 1)
161
+ push out keep
162
+ } {
163
+ ; Nothing to keep. The new subtree comes across whole, and its
164
+ ; own children are already in the shape the builder made — no
165
+ ; recursion, because there is nothing on this side to match
166
+ ; them against.
167
+ stats.created = (stats.created + 1)
168
+ push out cand
169
+ }
170
+ j = (j + 1)
171
+ }
172
+
173
+ def k:int 0
174
+ while (k < n) {
175
+ if ((itemAt taken k) == false) {
176
+ stats.dropped = (stats.dropped + 1)
177
+ }
178
+ k = (k + 1)
179
+ }
180
+ return out
181
+ }
182
+
183
+ fn findKeyed:int (old:[EVGElement] taken:[boolean] cand:EVGElement) {
184
+ def i:int 0
185
+ while (i < (array_length old)) {
186
+ if ((itemAt taken i) == false) {
187
+ if (EVGReconcile.matches((itemAt old i) cand)) {
188
+ return i
189
+ }
190
+ }
191
+ i = (i + 1)
192
+ }
193
+ return (0 - 1)
194
+ }
195
+
196
+ ; The `want`-th unkeyed old child, if it is still free and the tag agrees.
197
+ ;
198
+ ; Deliberately NOT "the first free unkeyed child with a matching tag":
199
+ ; falling forward past a mismatch would pair the second unkeyed child of
200
+ ; one render with the third of another, which is how an unkeyed list ends
201
+ ; up shifting its state by one on every insert.
202
+ fn findUnkeyed:int (old:[EVGElement] taken:[boolean] cand:EVGElement want:int) {
203
+ def seen:int 0
204
+ def i:int 0
205
+ while (i < (array_length old)) {
206
+ def o:EVGElement (itemAt old i)
207
+ if ((strlen o.key) == 0) {
208
+ if (seen == want) {
209
+ if ((itemAt taken i) == false) {
210
+ if (o.tagName == cand.tagName) {
211
+ return i
212
+ }
213
+ }
214
+ return (0 - 1)
215
+ }
216
+ seen = (seen + 1)
217
+ }
218
+ i = (i + 1)
219
+ }
220
+ return (0 - 1)
221
+ }
222
+
223
+ fn resetStats:void () {
224
+ stats = (new EVGReconcileStats())
225
+ }
226
+ }