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,1721 @@
1
+ ; SPDX-License-Identifier: MIT
2
+
3
+ ; ==============================================================================
4
+ ; EVGPatch — the only way an agent is allowed to change a tree
5
+ ; ==============================================================================
6
+ ;
7
+ ; An LLM that edits a document needs three things this engine already has:
8
+ ; a stable address for a node, a change that is validated before it lands, and
9
+ ; a way to put it back. This is the second and third of those.
10
+ ;
11
+ ; The rule the whole file exists to enforce: THE MODEL NEVER WRITES CODE INTO
12
+ ; THE DOCUMENT. It writes ops — `set-prop`, `set-text`, `insert`, `remove`,
13
+ ; `move` — each addressed by an `EVGInspect` path, each validated against the
14
+ ; engine's real property set, and each carrying its own inverse. The worst a
15
+ ; bad generation can do is be rejected.
16
+ ;
17
+ ; --- VALIDATION IS NOT A LIST HERE --------------------------------------------
18
+ ;
19
+ ; There is no whitelist of property names in this file, and that is deliberate:
20
+ ; a second list would drift from the first. `EVGElement.setAttribute` already
21
+ ; ends with `EVGReject.note("unknown property" …)` for anything it does not
22
+ ; implement, so the engine itself is the validator. A `set-prop` clears nothing
23
+ ; and counts nothing global: it reads `EVGReject.noteTotal()` before and after,
24
+ ; and a note that appeared during the write is the rejection.
25
+ ;
26
+ ; What this file DOES enumerate is the readable set — `readProp` — because an
27
+ ; op has to be invertible before it is allowed to apply. That is the stronger
28
+ ; rule and it is the one worth stating: **an op that cannot be undone is not
29
+ ; applied**. So the patchable set is exactly the set this file can read back,
30
+ ; and a property outside it is rejected with that as the reason rather than
31
+ ; applied and lost.
32
+ ;
33
+ ; --- ALL OR NOTHING -----------------------------------------------------------
34
+ ;
35
+ ; A rejected op rolls the whole patch back. "3 of 5 applied" hands the caller a
36
+ ; document in a state nobody designed and a diff nobody asked for; the agent
37
+ ; then has to work out which 3. Failing the batch keeps the document exactly as
38
+ ; it was, and the reason says which op and why.
39
+ ;
40
+ ; --- ADDRESSES ARE REBUILT, NOT CACHED ----------------------------------------
41
+ ;
42
+ ; `insert`, `remove` and `move` change what every later path means, so the
43
+ ; `EVGInspect` index is rebuilt before each op rather than once per patch. That
44
+ ; is O(n) per op and it is the right trade at this size: the alternative is a
45
+ ; path-rewriting scheme that is wrong in exactly the cases — an unkeyed sibling
46
+ ; shifting under an insert — that the inspector's own doc comment warns about.
47
+ ;
48
+ ; --- PARENTS ------------------------------------------------------------------
49
+ ;
50
+ ; `EVGElement.addChild` deliberately keeps no `parent` back-reference (it broke
51
+ ; the C++ backend — see its comment). So nothing here reads `el.parent`. The
52
+ ; parent of a node is its path with the last segment removed, which is what
53
+ ; `EVGInspect` already tracks, and structural ops go through the parent element
54
+ ; found that way.
55
+ ; ==============================================================================
56
+
57
+ Import "EVGElement.rgr"
58
+ Import "EVGInspect.rgr"
59
+ Import "EVGReject.rgr"
60
+ Import "EVGUnit.rgr"
61
+ Import "EVGColor.rgr"
62
+ Import "EVGDisplayList.rgr"
63
+
64
+ ; One edit. Built through the static helpers below rather than by hand, so the
65
+ ; fields that do not apply to a given op stay at their defaults.
66
+ class EVGPatchOp {
67
+ def op:string ""
68
+ def at:string ""
69
+ def prop:string ""
70
+ def value:string ""
71
+ def toPath:string ""
72
+ def index:int 0
73
+ def tag:string ""
74
+ ; `insert` may carry a whole SUBTREE rather than one empty tag. A real
75
+ ; control is a tree — a switch is a track with a thumb in it — and an
76
+ ; agent that can only insert one empty node at a time has to build one
77
+ ; out of a dozen ops, which is how a screen ends up with a drawing of a
78
+ ; switch instead of a switch. Reading it is the caller's: `evg_agent`
79
+ ; parses `"node": {…}` with the same reader that reads a document.
80
+ def node@(optional):EVGElement
81
+ ; `restore` only: which detached subtree in the result's stash to put back.
82
+ def stash:int 0
83
+
84
+ Constructor () {}
85
+
86
+ sfn setProp:EVGPatchOp (at:string prop:string value:string) {
87
+ def o (new EVGPatchOp())
88
+ o.op = "set-prop"
89
+ o.at = at
90
+ o.prop = prop
91
+ o.value = value
92
+ return o
93
+ }
94
+
95
+ ; A node's `id` is not a property — nothing paints it, and the patchable
96
+ ; set is about what the engine can read back. It is the node's NAME: the
97
+ ; hit test answers with it, and an app turns a press on it into an event.
98
+ ; Without an op for it an agent can only name a node by rewriting the file
99
+ ; by hand, which is how a screen ends up with buttons nothing can press.
100
+ sfn setId:EVGPatchOp (at:string value:string) {
101
+ def o (new EVGPatchOp())
102
+ o.op = "set-id"
103
+ o.at = at
104
+ o.value = value
105
+ return o
106
+ }
107
+
108
+ ; The document's stylesheet, whole. It is not a property of a node — it
109
+ ; belongs to the document — so it has an op of its own rather than a place
110
+ ; in the patchable set. `at` is ignored; there is one sheet.
111
+ sfn setCss:EVGPatchOp (value:string) {
112
+ def o (new EVGPatchOp())
113
+ o.op = "set-css"
114
+ o.at = "0"
115
+ o.value = value
116
+ return o
117
+ }
118
+
119
+ sfn setText:EVGPatchOp (at:string value:string) {
120
+ def o (new EVGPatchOp())
121
+ o.op = "set-text"
122
+ o.at = at
123
+ o.value = value
124
+ return o
125
+ }
126
+
127
+ sfn insertNode:EVGPatchOp (at:string index:int tag:string) {
128
+ def o (new EVGPatchOp())
129
+ o.op = "insert"
130
+ o.at = at
131
+ o.index = index
132
+ o.tag = tag
133
+ return o
134
+ }
135
+
136
+ ; A subtree, put in whole. Its inverse is the same `remove` an empty
137
+ ; insert gets, so undo needs to know nothing about this.
138
+ sfn insertTree:EVGPatchOp (at:string index:int kid:EVGElement) {
139
+ def o (new EVGPatchOp())
140
+ o.op = "insert"
141
+ o.at = at
142
+ o.index = index
143
+ o.tag = kid.tagName
144
+ o.node = kid
145
+ return o
146
+ }
147
+
148
+ sfn removeNode:EVGPatchOp (at:string) {
149
+ def o (new EVGPatchOp())
150
+ o.op = "remove"
151
+ o.at = at
152
+ return o
153
+ }
154
+
155
+ sfn move:EVGPatchOp (at:string toPath:string index:int) {
156
+ def o (new EVGPatchOp())
157
+ o.op = "move"
158
+ o.at = at
159
+ o.toPath = toPath
160
+ o.index = index
161
+ return o
162
+ }
163
+
164
+ sfn restore:EVGPatchOp (at:string index:int stash:int) {
165
+ def o (new EVGPatchOp())
166
+ o.op = "restore"
167
+ o.at = at
168
+ o.index = index
169
+ o.stash = stash
170
+ return o
171
+ }
172
+
173
+ fn describe:string () {
174
+ def s:string op
175
+ s = ((s + " ") + at)
176
+ if ((strlen prop) > 0) {
177
+ s = ((s + " ") + prop)
178
+ }
179
+ if ((strlen value) > 0) {
180
+ s = ((s + "=") + value)
181
+ }
182
+ return s
183
+ }
184
+ }
185
+
186
+ ; What a patch did, and everything needed to undo it.
187
+ ;
188
+ ; `inverse` is in APPLY order, so undoing means walking it backwards. That is
189
+ ; stated here because the alternative — storing it pre-reversed — reads fine
190
+ ; until a caller appends two patches' inverses together and the seam is wrong.
191
+ class EVGPatchResult {
192
+ def ok:boolean true
193
+ def applied:int 0
194
+ def inverse:[EVGPatchOp]
195
+ def rejected:[string]
196
+ ; Subtrees taken out by `remove`, kept alive so `restore` can put them back.
197
+ def stash:[EVGElement]
198
+
199
+ Constructor () {
200
+ def a:[EVGPatchOp]
201
+ inverse = a
202
+ def b:[string]
203
+ rejected = b
204
+ def c:[EVGElement]
205
+ stash = c
206
+ }
207
+
208
+ fn reason:string () {
209
+ if ((array_length rejected) == 0) {
210
+ return ""
211
+ }
212
+ return (itemAt rejected 0)
213
+ }
214
+ }
215
+
216
+ class EVGPatch {
217
+ def insp:EVGInspect
218
+ def res:EVGPatchResult
219
+ ; Off while rolling back: an undo must not record an undo of itself.
220
+ def recording:boolean true
221
+
222
+ Constructor () {
223
+ insp = (new EVGInspect())
224
+ res = (new EVGPatchResult())
225
+ }
226
+
227
+ ; --- the entry point ------------------------------------------------------
228
+
229
+ fn apply:EVGPatchResult (root:EVGElement ops:[EVGPatchOp]) {
230
+ def out (new EVGPatchResult())
231
+ res = out
232
+ recording = true
233
+ def i:int 0
234
+ while (i < (array_length ops)) {
235
+ def op:EVGPatchOp (itemAt ops i)
236
+ def why:string (this.applyOne(root op))
237
+ if ((strlen why) > 0) {
238
+ def msg:string ((((("op " + (to_string i)) + " (") + (op.describe())) + "): ") + why)
239
+ push out.rejected msg
240
+ this.rollback(root)
241
+ out.ok = false
242
+ out.applied = 0
243
+ return out
244
+ }
245
+ out.applied = (out.applied + 1)
246
+ i = (i + 1)
247
+ }
248
+ return out
249
+ } doc {
250
+ description "Applies ops to a tree, all or nothing, recording an inverse for each."
251
+ param root "The tree to edit."
252
+ param ops "The edits, applied in order."
253
+ returns "What was applied, why it was not, and how to undo it."
254
+ }
255
+
256
+ ; Walk the recorded inverse backwards. Used on rejection, and callable by a
257
+ ; host that wants an undo button.
258
+ fn rollback:void (root:EVGElement) {
259
+ recording = false
260
+ def i:int ((array_length res.inverse) - 1)
261
+ while (i >= 0) {
262
+ def op:EVGPatchOp (itemAt res.inverse i)
263
+ def ignored:string (this.applyOne(root op))
264
+ i = (i - 1)
265
+ }
266
+ recording = true
267
+ } doc {
268
+ description "Undoes an applied patch, newest edit first."
269
+ param root "The tree the patch was applied to."
270
+ }
271
+
272
+ ; --- one op ---------------------------------------------------------------
273
+ ;
274
+ ; Returns "" when it applied, and the reason when it did not. Nothing here
275
+ ; throws: a rejection is data the caller shows the model, not an exception.
276
+
277
+ fn applyOne:string (root:EVGElement op:EVGPatchOp) {
278
+ insp.build(root)
279
+ if (op.op == "set-prop") {
280
+ return (this.doSetProp(op))
281
+ }
282
+ if (op.op == "set-text") {
283
+ return (this.doSetText(op))
284
+ }
285
+ if (op.op == "set-id") {
286
+ return (this.doSetId(op))
287
+ }
288
+ if (op.op == "set-css") {
289
+ return (this.doSetCss(root op))
290
+ }
291
+ if (op.op == "insert") {
292
+ return (this.doInsert(op))
293
+ }
294
+ if (op.op == "remove") {
295
+ return (this.doRemove(op))
296
+ }
297
+ if (op.op == "move") {
298
+ return (this.doMove(op))
299
+ }
300
+ if (op.op == "restore") {
301
+ return (this.doRestore(op))
302
+ }
303
+ return ("unknown op \"" + (op.op + "\""))
304
+ }
305
+
306
+ fn record:void (op:EVGPatchOp) {
307
+ if recording {
308
+ push res.inverse op
309
+ }
310
+ }
311
+
312
+ ; --- set-prop -------------------------------------------------------------
313
+
314
+ fn doSetProp:string (op:EVGPatchOp) {
315
+ def hit@(optional):EVGElement (insp.elementAt(op.at))
316
+ if (null? hit) {
317
+ return (this.noSuchNode(op.at))
318
+ }
319
+ def el:EVGElement (unwrap hit)
320
+ def name:string (EVGElement.toKebab(op.prop))
321
+ if ((EVGPatch.readable(name)) == false) {
322
+ return ((("property \"" + name) + "\" is not patchable — nothing here can read it back, so the edit could not be undone"))
323
+ }
324
+ def before:string (EVGPatch.readProp(el name))
325
+ def wasInline:boolean (el.hasInline(name))
326
+ def notesBefore:int (EVGReject.noteTotal())
327
+ if (EVGReject.isAbsent(op.value)) {
328
+ def cleared:boolean (EVGPatch.clearProp(el name))
329
+ if (cleared == false) {
330
+ return ((("property \"" + name) + "\" cannot be cleared"))
331
+ }
332
+ el.unmarkInline(name)
333
+ } {
334
+ el.setAttribute(name op.value)
335
+ el.markInline(name)
336
+ }
337
+ def notesAfter:int (EVGReject.noteTotal())
338
+ if (notesAfter > notesBefore) {
339
+ def last:int ((EVGReject.noteCount()) - 1)
340
+ def why:string "rejected by the engine"
341
+ if (last >= 0) {
342
+ why = (EVGReject.noteAt(last))
343
+ }
344
+ ; Put it back exactly as it was before complaining.
345
+ def undo:boolean (EVGPatch.clearProp(el name))
346
+ if ((strlen before) > 0) {
347
+ el.setAttribute(name before)
348
+ }
349
+ if (wasInline == false) {
350
+ el.unmarkInline(name)
351
+ }
352
+ return why
353
+ }
354
+ def inv:EVGPatchOp (EVGPatchOp.setProp(op.at name before))
355
+ this.record(inv)
356
+ return ""
357
+ }
358
+
359
+ ; --- set-css --------------------------------------------------------------
360
+
361
+ fn doSetCss:string (root:EVGElement op:EVGPatchOp) {
362
+ def want:string op.value
363
+ if (EVGReject.isAbsent(want)) {
364
+ want = ""
365
+ }
366
+ def before:string root.documentCss
367
+ root.documentCss = want
368
+ def inv:EVGPatchOp (EVGPatchOp.setCss(before))
369
+ this.record(inv)
370
+ return ""
371
+ }
372
+
373
+ ; --- set-id ---------------------------------------------------------------
374
+
375
+ fn doSetId:string (op:EVGPatchOp) {
376
+ def hit@(optional):EVGElement (insp.elementAt(op.at))
377
+ if (null? hit) {
378
+ return (this.noSuchNode(op.at))
379
+ }
380
+ def el:EVGElement (unwrap hit)
381
+ def want:string op.value
382
+ if (EVGReject.isAbsent(want)) {
383
+ want = ""
384
+ }
385
+ ; Two nodes with one id is a screen where the same press means two
386
+ ; things, and the hit test can only answer with one of them. Say which
387
+ ; node already has it rather than let the second one look live.
388
+ if ((strlen want) > 0) {
389
+ def other:string (this.pathOfId(want op.at))
390
+ if ((strlen other) > 0) {
391
+ return ((("id \"" + want) + ("\" is already on " + (other + " — an id names one node, and a press can only reach one of them"))))
392
+ }
393
+ }
394
+ def before:string el.id
395
+ el.id = want
396
+ def inv:EVGPatchOp (EVGPatchOp.setId(op.at before))
397
+ this.record(inv)
398
+ return ""
399
+ }
400
+
401
+ ; The path of the node carrying `id`, ignoring `skip` (the node being named,
402
+ ; which may already carry it). "" when nothing else has it.
403
+ fn pathOfId:string (id:string skip:string) {
404
+ def n:int (insp.count())
405
+ def i:int 0
406
+ while (i < n) {
407
+ def p:string (insp.pathAt(i))
408
+ if (p != skip) {
409
+ def hit@(optional):EVGElement (insp.elementAt(p))
410
+ if (null? hit) {
411
+ } {
412
+ if ((unwrap hit).id == id) {
413
+ return p
414
+ }
415
+ }
416
+ }
417
+ i = (i + 1)
418
+ }
419
+ return ""
420
+ }
421
+
422
+ ; --- set-text -------------------------------------------------------------
423
+
424
+ fn doSetText:string (op:EVGPatchOp) {
425
+ def hit@(optional):EVGElement (insp.elementAt(op.at))
426
+ if (null? hit) {
427
+ return (this.noSuchNode(op.at))
428
+ }
429
+ def el:EVGElement (unwrap hit)
430
+ if ((array_length el.children) > 0) {
431
+ return "set-text on a node with children — remove them first, or write to the child that holds the text"
432
+ }
433
+ def before:string el.textContent
434
+ el.textContent = op.value
435
+ def inv:EVGPatchOp (EVGPatchOp.setText(op.at before))
436
+ this.record(inv)
437
+ return ""
438
+ }
439
+
440
+ ; --- insert ---------------------------------------------------------------
441
+
442
+ fn doInsert:string (op:EVGPatchOp) {
443
+ def hit@(optional):EVGElement (insp.elementAt(op.at))
444
+ if (null? hit) {
445
+ return (this.noSuchNode(op.at))
446
+ }
447
+ def parent:EVGElement (unwrap hit)
448
+ def made@(optional):EVGElement (EVGPatch.create(op.tag))
449
+ if (op.node) {
450
+ made = op.node
451
+ }
452
+ if (null? made) {
453
+ return ((("no element type for tag \"" + op.tag) + "\" — div, span, img or path"))
454
+ }
455
+ def kid:EVGElement (unwrap made)
456
+ def at:int (EVGPatch.clampIndex(op.index (array_length parent.children)))
457
+ EVGPatch.insertAt(parent kid at)
458
+ def inv:EVGPatchOp (EVGPatchOp.removeNode((EVGPatch.childPath(op.at parent at))))
459
+ this.record(inv)
460
+ return ""
461
+ }
462
+
463
+ ; --- remove ---------------------------------------------------------------
464
+
465
+ fn doRemove:string (op:EVGPatchOp) {
466
+ if (op.at == "0") {
467
+ return "cannot remove the root"
468
+ }
469
+ def hit@(optional):EVGElement (insp.elementAt(op.at))
470
+ if (null? hit) {
471
+ return (this.noSuchNode(op.at))
472
+ }
473
+ def el:EVGElement (unwrap hit)
474
+ def parentPath:string (insp.parentOf(op.at))
475
+ def ph@(optional):EVGElement (insp.elementAt(parentPath))
476
+ if (null? ph) {
477
+ return (this.noSuchNode(parentPath))
478
+ }
479
+ def parent:EVGElement (unwrap ph)
480
+ def at:int (EVGPatch.indexOfChild(parent el))
481
+ if (at < 0) {
482
+ return "the node is not among its parent's children — the index is stale"
483
+ }
484
+ EVGPatch.removeAt(parent at)
485
+ push res.stash el
486
+ def slot:int ((array_length res.stash) - 1)
487
+ def inv:EVGPatchOp (EVGPatchOp.restore(parentPath at slot))
488
+ this.record(inv)
489
+ return ""
490
+ }
491
+
492
+ ; `restore` is the inverse of `remove` and exists only for that. It puts a
493
+ ; stashed subtree back where it was, which is why it names a slot and not a
494
+ ; tag: the node is the same object, with its children and state intact.
495
+ fn doRestore:string (op:EVGPatchOp) {
496
+ def hit@(optional):EVGElement (insp.elementAt(op.at))
497
+ if (null? hit) {
498
+ return (this.noSuchNode(op.at))
499
+ }
500
+ def parent:EVGElement (unwrap hit)
501
+ if (op.stash >= (array_length res.stash)) {
502
+ return "nothing stashed at that slot"
503
+ }
504
+ def el:EVGElement (itemAt res.stash op.stash)
505
+ def at:int (EVGPatch.clampIndex(op.index (array_length parent.children)))
506
+ EVGPatch.insertAt(parent el at)
507
+ def inv:EVGPatchOp (EVGPatchOp.removeNode((EVGPatch.childPath(op.at parent at))))
508
+ this.record(inv)
509
+ return ""
510
+ }
511
+
512
+ ; --- move -----------------------------------------------------------------
513
+
514
+ fn doMove:string (op:EVGPatchOp) {
515
+ if (op.at == "0") {
516
+ return "cannot move the root"
517
+ }
518
+ def hit@(optional):EVGElement (insp.elementAt(op.at))
519
+ if (null? hit) {
520
+ return (this.noSuchNode(op.at))
521
+ }
522
+ def el:EVGElement (unwrap hit)
523
+ def destHit@(optional):EVGElement (insp.elementAt(op.toPath))
524
+ if (null? destHit) {
525
+ return (this.noSuchNode(op.toPath))
526
+ }
527
+ def dest:EVGElement (unwrap destHit)
528
+ if (EVGPatch.subtreeHas(el dest)) {
529
+ return "cannot move a node into itself or its own subtree"
530
+ }
531
+ def fromPath:string (insp.parentOf(op.at))
532
+ def fh@(optional):EVGElement (insp.elementAt(fromPath))
533
+ if (null? fh) {
534
+ return (this.noSuchNode(fromPath))
535
+ }
536
+ def from:EVGElement (unwrap fh)
537
+ def at:int (EVGPatch.indexOfChild(from el))
538
+ if (at < 0) {
539
+ return "the node is not among its parent's children — the index is stale"
540
+ }
541
+ EVGPatch.removeAt(from at)
542
+ def landing:int (EVGPatch.clampIndex(op.index (array_length dest.children)))
543
+ EVGPatch.insertAt(dest el landing)
544
+ ; The inverse names the node by where it now IS, and puts it back by
545
+ ; the parent path and index it came from.
546
+ def nowPath:string (EVGPatch.childPath(op.toPath dest landing))
547
+ def inv:EVGPatchOp (EVGPatchOp.move(nowPath fromPath at))
548
+ this.record(inv)
549
+ return ""
550
+ }
551
+
552
+ fn noSuchNode:string (path:string) {
553
+ def n:int (insp.count())
554
+ return ((((("no node at \"" + path) + "\" (") + (to_string n)) + " nodes in this tree)"))
555
+ }
556
+
557
+ ; --- children, as arrays --------------------------------------------------
558
+ ;
559
+ ; There is no array insert in the standard ops, so both of these rebuild the
560
+ ; list. At the size of a child list that is cheaper than being clever.
561
+
562
+ sfn insertAt:void (parent:EVGElement kid:EVGElement at:int) {
563
+ def rebuilt:[EVGElement]
564
+ def i:int 0
565
+ def n:int (array_length parent.children)
566
+ while (i < n) {
567
+ if (i == at) {
568
+ push rebuilt kid
569
+ }
570
+ push rebuilt (itemAt parent.children i)
571
+ i = (i + 1)
572
+ }
573
+ if (at >= n) {
574
+ push rebuilt kid
575
+ }
576
+ parent.children = rebuilt
577
+ }
578
+
579
+ sfn removeAt:void (parent:EVGElement at:int) {
580
+ def rebuilt:[EVGElement]
581
+ def i:int 0
582
+ def n:int (array_length parent.children)
583
+ while (i < n) {
584
+ if (i != at) {
585
+ push rebuilt (itemAt parent.children i)
586
+ }
587
+ i = (i + 1)
588
+ }
589
+ parent.children = rebuilt
590
+ }
591
+
592
+ ; Identity is `inspectSlot`, which `EVGInspect.build` has just stamped on
593
+ ; every node in this tree — the same handle the display list uses to say
594
+ ; which element emitted a command. A node that carries no slot was never in
595
+ ; the index, and answering for it would be a guess.
596
+ sfn indexOfChild:int (parent:EVGElement kid:EVGElement) {
597
+ if (kid.inspectSlot < 0) {
598
+ return (0 - 1)
599
+ }
600
+ def i:int 0
601
+ while (i < (array_length parent.children)) {
602
+ def c:EVGElement (itemAt parent.children i)
603
+ if (c.inspectSlot == kid.inspectSlot) {
604
+ return i
605
+ }
606
+ i = (i + 1)
607
+ }
608
+ return (0 - 1)
609
+ }
610
+
611
+ sfn subtreeHas:boolean (el:EVGElement maybe:EVGElement) {
612
+ if (el.inspectSlot == maybe.inspectSlot) {
613
+
614
+ return true
615
+ }
616
+ def i:int 0
617
+ while (i < (array_length el.children)) {
618
+ def c:EVGElement (itemAt el.children i)
619
+ if (EVGPatch.subtreeHas(c maybe)) {
620
+
621
+ return true
622
+ }
623
+ i = (i + 1)
624
+ }
625
+ return false
626
+ }
627
+
628
+ sfn clampIndex:int (want:int n:int) {
629
+ if (want < 0) {
630
+ return 0
631
+ }
632
+ if (want > n) {
633
+ return n
634
+ }
635
+ return want
636
+ }
637
+
638
+ ; The path a child at `at` has under `parentPath`, by the same rule
639
+ ; `EVGInspect.visit` uses: the key when there is one, the index otherwise.
640
+ sfn childPath:string (parentPath:string parent:EVGElement at:int) {
641
+ def seg:string (to_string at)
642
+ if (at < (array_length parent.children)) {
643
+ def kid:EVGElement (itemAt parent.children at)
644
+ if ((strlen kid.key) > 0) {
645
+ seg = ("k:" + kid.key)
646
+ }
647
+ }
648
+ return ((parentPath + "/") + seg)
649
+ }
650
+
651
+
652
+ ; --- the patchable set ----------------------------------------------------
653
+ ;
654
+ ; `kindOf` is the ONE list. `readable` is defined from it, and `readProp`
655
+ ; and `clearProp` answer by name for the names it admits — so a name this
656
+ ; admits and they do not is a bug, and `EVGPatchTest` walks the list on a
657
+ ; fresh element to catch exactly that.
658
+ ;
659
+ ; Membership is narrower than what `setAttribute` accepts, and the missing
660
+ ; ones are missing for a reason: a property this file cannot read back
661
+ ; cannot be inverted, and an op that cannot be inverted does not apply.
662
+
663
+ sfn KIND_NONE:int () { return 0 }
664
+ sfn KIND_STRING:int () { return 1 }
665
+ sfn KIND_UNIT:int () { return 2 }
666
+ sfn KIND_COLOR:int () { return 3 }
667
+ sfn KIND_NUMBER:int () { return 4 }
668
+ ; There is no separate "box" kind. A box side is a length like any other —
669
+ ; the difference is only WHICH field to read, and reading the wrong one is
670
+ ; the mistake this note exists to prevent: `EVGBox` carries the authored
671
+ ; `paddingLeft:EVGUnit` AND the `paddingLeftPx` that layout resolves from
672
+ ; it. `EVGInspect` prints the px, correctly, because an inspector reports
673
+ ; what layout did. A patch must read the unit, because an inverse has to
674
+ ; restore what the author wrote and the px is 0 until the first pass.
675
+
676
+ ; `class` is what an author writes and `class-name` is what `setAttribute`
677
+ ; answers to, so the alias is resolved once, here, rather than at each use.
678
+ ; `evg-fx-<something>`. A family rather than a list, because the names
679
+ ; belong to whichever plugin reads them and this file will never know
680
+ ; them all.
681
+ sfn isFx:boolean (name:string) {
682
+ if ((strlen name) <= 7) {
683
+ return false
684
+ }
685
+ return ((substring name 0 7) == "evg-fx-")
686
+ }
687
+
688
+ ; The `evg-fx-*` properties THIS element carries, full names. `patchableNames`
689
+ ; cannot list them — they are per element, not per format — so anything that
690
+ ; walks the static list and wants the whole truth walks this too.
691
+ sfn fxNamesOf:[string] (el:EVGElement) {
692
+ def out:[string]
693
+ def i:int 0
694
+ while (i < (array_length el.fxNames)) {
695
+ push out ("evg-fx-" + (itemAt el.fxNames i))
696
+ i = (i + 1)
697
+ }
698
+ return out
699
+ }
700
+
701
+ sfn canonical:string (name:string) {
702
+ def n:string (EVGElement.toKebab(name))
703
+ if (n == "class") {
704
+ return "class-name"
705
+ }
706
+ return n
707
+ }
708
+
709
+ sfn kindOf:int (rawName:string) {
710
+ def name:string (EVGPatch.canonical(rawName))
711
+ if (((name == "display") || (name == "position")) || (name == "flex-direction")) { return 1 }
712
+ if (((name == "justify-content") || (name == "align-items")) || (name == "align-content")) { return 1 }
713
+ if (((name == "flex-wrap") || (name == "grid-area")) || (name == "grid-template-columns")) { return 1 }
714
+ ; The rest of the grid vocabulary. It was incomplete, and the hole was
715
+ ; invisible in a property list and obvious in a picture: the showcase's
716
+ ; card deck places every card with `grid-row: 1 / 3` against the deck's
717
+ ; `subgrid` rows, so a conversion that dropped `grid-row` laid the
718
+ ; captions on top of the paragraph below them.
719
+ if (((name == "grid-row") || (name == "grid-column")) || (name == "grid-template-areas")) { return 1 }
720
+ if (name == "grid-auto-flow") { return 1 }
721
+ if (((name == "grid-template-rows") || (name == "font-family")) || (name == "font-weight")) { return 1 }
722
+ if (((name == "text-align") || (name == "overflow")) || (name == "cursor")) { return 1 }
723
+ if (((name == "class-name") || (name == "theme")) || (name == "src")) { return 1 }
724
+ if (((name == "object-fit") || (name == "transform")) || (name == "transition")) { return 1 }
725
+ ; A turn is two properties, not one. `transform` says by how much;
726
+ ; `transform-origin` says about WHAT, and its default is the box's own
727
+ ; centre — which is the wrong pivot for anything anchored at a point.
728
+ ; A chart's turned axis label reads as centred on its tick until this
729
+ ; is carried, and then slides half its own length along the axis.
730
+ ; `white-space` belongs with them for a related reason: a label placed
731
+ ; in a box measured by an ESTIMATE wraps the moment the estimate is a
732
+ ; pixel short, and "markdown" comes back as "markdo".
733
+ if ((name == "transform-origin") || (name == "white-space")) { return 1 }
734
+ if (name == "background-gradient") { return 1 }
735
+ ; The vector vocabulary. Left out of the first draft, and running the
736
+ ; converter over the showcase said what that cost: every chart, every
737
+ ; diagram and every piece of imported SVG lost its geometry — 72 paths
738
+ ; on one page alone — and came out as correctly-sized empty boxes.
739
+ ; A format for editing documents that cannot hold a chart is not one.
740
+ if (((name == "d") || (name == "view-box")) || (name == "fill-rule")) { return 1 }
741
+ if (((name == "stroke-linecap") || (name == "stroke-linejoin")) || (name == "stroke-dasharray")) { return 1 }
742
+ if ((name == "alt") || (name == "href")) { return 1 }
743
+ ; Anchors and connectors. The geometry ones are names and keywords;
744
+ ; the three lengths are plain numbers, the way `stroke-width` is.
745
+ ; Surfaces: the overlay vocabulary and CSS Anchor Positioning's.
746
+ ; Without these the editable format could not express a menu at all —
747
+ ; a document could hold the rows and not the fact that they are a
748
+ ; popover.
749
+ if (((name == "overlay") || (name == "overlay-anchor-role")) || (name == "overlay-side")) { return 1 }
750
+ if (((name == "overlay-align") || (name == "position-anchor")) || (name == "position-area")) { return 1 }
751
+ if (((name == "position-try-fallbacks") || (name == "position-try-order")) || (name == "presentation")) { return 1 }
752
+ if (name == "fit-viewport") { return 1 }
753
+ if ((name == "overlay-gap") || (name == "sheet-below")) { return 4 }
754
+ if (((name == "anchor-name") || (name == "from")) || (name == "to")) { return 1 }
755
+ if (((name == "from-side") || (name == "to-side")) || (name == "routing")) { return 1 }
756
+ if ((name == "arrow-start") || (name == "arrow-end")) { return 1 }
757
+ if (((name == "arrow-size") || (name == "from-offset")) || (name == "to-offset")) { return 4 }
758
+ ; An imported SVG document is its TEXT, held on the element and parsed
759
+ ; by whoever draws it. Leaving it out cost the showcase's `svg` page
760
+ ; every graphic on it while both audits reported the conversion clean —
761
+ ; the boxes were right, because the boxes are sized by CSS, and only
762
+ ; the painting was empty. It is carried at full length: a document that
763
+ ; renders correctly is worth more than a short file.
764
+ if (name == "svg") { return 1 }
765
+ ; The two-stop gradient. Found by converting a Figma page that had one:
766
+ ; the scene carried it, `SceneToEVG` emitted `gradient-from` and
767
+ ; `gradient-to`, and the editable format — which is this set — dropped
768
+ ; both, so a gradient fill arrived in the document as nothing at all.
769
+ if (name == "gradient-dir") { return 4 }
770
+ ; Surface effects. The engine reads none of this: `starfield` is a
771
+ ; name, `evg-fx-density` is a number under a name, and the plugin that
772
+ ; draws it is the first thing that attaches meaning to either. Left
773
+ ; out of this set they could not be patched and could not be written
774
+ ; down — so a document could hold a page with a starfield behind it
775
+ ; and lose the starfield the next time anything rewrote the file.
776
+ if ((name == "evg-surface-effect") || (name == "evg-effect-on")) { return 1 }
777
+ if (EVGPatch.isFx(name)) { return 4 }
778
+
779
+ if (((name == "width") || (name == "height")) || (name == "min-width")) { return 2 }
780
+ if (((name == "min-height") || (name == "max-width")) || (name == "max-height")) { return 2 }
781
+ if (((name == "left") || (name == "top")) || (name == "right")) { return 2 }
782
+ if (((name == "bottom") || (name == "gap")) || (name == "font-size")) { return 2 }
783
+ if (name == "flex-basis") { return 2 }
784
+
785
+ if (((name == "color") || (name == "background-color")) || (name == "border-color")) { return 3 }
786
+ if ((name == "fill") || (name == "stroke")) { return 3 }
787
+ if ((name == "gradient-from") || (name == "gradient-to")) { return 3 }
788
+ ; `emoji-color` is a TINT, and that is the whole reason it exists: the
789
+ ; face is monochrome outlines with no colour of its own, so something
790
+ ; has to say what to fill them with, and unset it inherits the text
791
+ ; colour. The showcase's emoji page tints three rows through it
792
+ ; (`.tintRose`, `.tintBlue`, `.tintGreen`); dropping the property did
793
+ ; not lose colours the glyphs had, it lost the tint and let all three
794
+ ; fall back to the text colour. Every audit was silent, because the
795
+ ; draw commands matched in count and kind and differed only in fill.
796
+ if (name == "emoji-color") { return 3 }
797
+
798
+ if (((name == "opacity") || (name == "line-height")) || (name == "flex-grow")) { return 4 }
799
+ if (((name == "flex-shrink") || (name == "rotate")) || (name == "scale")) { return 4 }
800
+ if ((name == "stroke-width") || (name == "stroke-dashoffset")) { return 4 }
801
+
802
+ if (((name == "margin-top") || (name == "margin-right")) || (name == "margin-bottom")) { return 2 }
803
+ if (((name == "margin-left") || (name == "padding-top")) || (name == "padding-right")) { return 2 }
804
+ if (((name == "padding-bottom") || (name == "padding-left")) || (name == "border-width")) { return 2 }
805
+ if (name == "border-radius") { return 2 }
806
+
807
+ return 0
808
+ }
809
+
810
+ ; The same set `kindOf` admits, in a form something can iterate: the
811
+ ; serializer walks it to decide what a node has to say, and the test walks
812
+ ; it to prove `readProp` and `clearProp` answer for all of it. Kept here
813
+ ; rather than in either caller so there is still one list.
814
+ sfn patchableNames:[string] () {
815
+ def a:[string]
816
+ push a "display"
817
+ push a "position"
818
+ push a "flex-direction"
819
+ push a "justify-content"
820
+ push a "align-items"
821
+ push a "align-content"
822
+ push a "flex-wrap"
823
+ push a "grid-area"
824
+ push a "grid-row"
825
+ push a "grid-column"
826
+ push a "grid-template-areas"
827
+ push a "grid-auto-flow"
828
+ push a "grid-template-columns"
829
+ push a "grid-template-rows"
830
+ push a "font-family"
831
+ push a "font-weight"
832
+ push a "text-align"
833
+ push a "overflow"
834
+ push a "cursor"
835
+ push a "class-name"
836
+ push a "theme"
837
+ push a "src"
838
+ push a "object-fit"
839
+ push a "transform"
840
+ push a "transform-origin"
841
+ push a "white-space"
842
+ push a "transition"
843
+ push a "background-gradient"
844
+ push a "svg"
845
+ push a "d"
846
+ push a "view-box"
847
+ push a "fill-rule"
848
+ push a "stroke-linecap"
849
+ push a "stroke-linejoin"
850
+ push a "stroke-dasharray"
851
+ push a "overlay"
852
+ push a "overlay-anchor-role"
853
+ push a "overlay-side"
854
+ push a "overlay-align"
855
+ push a "overlay-gap"
856
+ push a "position-anchor"
857
+ push a "position-try-fallbacks"
858
+ push a "position-try-order"
859
+ push a "presentation"
860
+ push a "sheet-below"
861
+ push a "fit-viewport"
862
+ push a "anchor-name"
863
+ push a "from"
864
+ push a "to"
865
+ push a "from-side"
866
+ push a "to-side"
867
+ push a "routing"
868
+ push a "arrow-start"
869
+ push a "arrow-end"
870
+ push a "arrow-size"
871
+ push a "from-offset"
872
+ push a "to-offset"
873
+ push a "alt"
874
+ push a "href"
875
+ push a "fill"
876
+ push a "stroke"
877
+ push a "emoji-color"
878
+ push a "gradient-from"
879
+ push a "gradient-to"
880
+ push a "gradient-dir"
881
+ push a "stroke-width"
882
+ push a "stroke-dashoffset"
883
+ push a "width"
884
+ push a "height"
885
+ push a "min-width"
886
+ push a "min-height"
887
+ push a "max-width"
888
+ push a "max-height"
889
+ push a "left"
890
+ push a "top"
891
+ push a "right"
892
+ push a "bottom"
893
+ push a "gap"
894
+ push a "font-size"
895
+ push a "flex-basis"
896
+ push a "color"
897
+ push a "background-color"
898
+ push a "border-color"
899
+ push a "evg-surface-effect"
900
+ push a "evg-effect-on"
901
+ push a "opacity"
902
+ push a "line-height"
903
+ push a "flex-grow"
904
+ push a "flex-shrink"
905
+ push a "rotate"
906
+ push a "scale"
907
+ push a "margin-top"
908
+ push a "margin-right"
909
+ push a "margin-bottom"
910
+ push a "margin-left"
911
+ push a "padding-top"
912
+ push a "padding-right"
913
+ push a "padding-bottom"
914
+ push a "padding-left"
915
+ push a "border-width"
916
+ push a "border-radius"
917
+ return a
918
+ }
919
+
920
+ ; An inset written as an anchor reference, in the form `setAttribute`
921
+ ; takes back: `anchor(right)`, or `calc(anchor(right) + 8px)`.
922
+ sfn anchorInsetText:string (side:string offset:double) {
923
+ if ((strlen side) == 0) {
924
+ return ""
925
+ }
926
+ def inner:string (("anchor(" + side) + ")")
927
+ if (offset == 0.0) {
928
+ return inner
929
+ }
930
+ def sign:string " + "
931
+ def mag:double offset
932
+ if (offset < 0.0) {
933
+ sign = " - "
934
+ mag = (0.0 - offset)
935
+ }
936
+ def out:string (("calc(" + inner) + sign)
937
+ out = ((out + (EVGDisplayList.num(mag))) + "px)")
938
+ return out
939
+ }
940
+
941
+ sfn readable:boolean (name:string) {
942
+ return ((EVGPatch.kindOf(name)) != 0)
943
+ } doc {
944
+ description "Whether a property can be patched — which is to say, read back and undone."
945
+ param name "A property name, kebab or camel."
946
+ returns "True when an op naming it will be considered."
947
+ }
948
+
949
+ ; What the element says about this property NOW, in the form `setAttribute`
950
+ ; would take back. "" means it was never set, which `clearProp` restores.
951
+ sfn readProp:string (el:EVGElement name:string) {
952
+ def n:string (EVGPatch.canonical(name))
953
+ def kind:int (EVGPatch.kindOf(n))
954
+ if (kind == 0) {
955
+ return ""
956
+ }
957
+ def bx:EVGBox el.box
958
+
959
+ if (n == "display") { return el.display }
960
+ if (n == "position") { return el.position }
961
+ if (n == "flex-direction") { return el.flexDirection }
962
+ if (n == "justify-content") { return el.justifyContent }
963
+ if (n == "align-items") { return el.alignItems }
964
+ if (n == "align-content") { return el.alignContent }
965
+ if (n == "flex-wrap") { return el.flexWrap }
966
+ if (n == "grid-area") { return el.gridArea }
967
+ if (n == "grid-row") { return el.gridRow }
968
+ if (n == "grid-column") { return el.gridColumn }
969
+ if (n == "grid-template-areas") { return el.gridTemplateAreas }
970
+ if (n == "grid-auto-flow") { return el.gridAutoFlow }
971
+ if (n == "grid-template-columns") { return el.gridTemplateColumns }
972
+ if (n == "grid-template-rows") { return el.gridTemplateRows }
973
+ if (n == "font-family") { return el.fontFamily }
974
+ if (n == "font-weight") { return el.fontWeight }
975
+ if (n == "text-align") { return el.textAlign }
976
+ if (n == "overflow") { return el.overflow }
977
+ if (n == "cursor") { return el.cursor }
978
+ if (n == "class-name") { return el.className }
979
+ if (n == "theme") { return el.theme }
980
+ if (n == "src") { return el.src }
981
+ if (n == "object-fit") { return el.objectFit }
982
+ if (n == "transform") { return el.transformSpec }
983
+ if (n == "transform-origin") { return el.transformOriginSpec }
984
+ if (n == "white-space") { return el.whiteSpace }
985
+ if (n == "transition") { return el.transitionSpec }
986
+ if (n == "background-gradient") { return el.backgroundGradient }
987
+ if (n == "svg") { return el.svgSource }
988
+ ; A CONNECTOR'S `d` AND `view-box` ARE THE LAYOUT'S, not the
989
+ ; document's. Writing them back would freeze the geometry the tag
990
+ ; exists to keep fluid — the file would carry the arrow measured at
991
+ ; one width, which is the hand-placed arrow all over again.
992
+ if (n == "d") {
993
+ if (el.tagName == "connector") {
994
+ return ""
995
+ }
996
+ return el.svgPath
997
+ }
998
+ if (n == "view-box") {
999
+ if (el.tagName == "connector") {
1000
+ return ""
1001
+ }
1002
+ return el.viewBox
1003
+ }
1004
+ if (n == "overlay") {
1005
+ if el.isOverlay {
1006
+ return "true"
1007
+ }
1008
+ return ""
1009
+ }
1010
+ if (n == "overlay-anchor-role") {
1011
+ if el.isOverlayAnchor {
1012
+ return "true"
1013
+ }
1014
+ return ""
1015
+ }
1016
+ if (n == "fit-viewport") {
1017
+ if el.fitViewport {
1018
+ return "true"
1019
+ }
1020
+ return ""
1021
+ }
1022
+ if (n == "overlay-side") { return el.overlaySide }
1023
+ if (n == "overlay-align") { return el.overlayAlign }
1024
+ if (n == "overlay-gap") { return (EVGDisplayList.num(el.overlayGap)) }
1025
+ if (n == "position-anchor") { return el.positionAnchor }
1026
+ if (n == "position-try-fallbacks") { return el.positionTryFallbacks }
1027
+ if (n == "position-try-order") { return el.positionTryOrder }
1028
+ if (n == "presentation") { return el.presentation }
1029
+ if (n == "sheet-below") { return (EVGDisplayList.num(el.sheetBelow)) }
1030
+ if (n == "anchor-name") { return el.anchorName }
1031
+ if (n == "from") { return el.connectorFrom }
1032
+ if (n == "to") { return el.connectorTo }
1033
+ if (n == "from-side") { return el.connectorFromSide }
1034
+ if (n == "to-side") { return el.connectorToSide }
1035
+ if (n == "routing") { return el.connectorRouting }
1036
+ if (n == "arrow-start") { return el.arrowStart }
1037
+ if (n == "arrow-end") { return el.arrowEnd }
1038
+ if (n == "arrow-size") { return (EVGDisplayList.num(el.arrowSize)) }
1039
+ if (n == "from-offset") { return (EVGDisplayList.num(el.connectorFromOffset)) }
1040
+ if (n == "to-offset") { return (EVGDisplayList.num(el.connectorToOffset)) }
1041
+ if (n == "fill-rule") { return el.fillRule }
1042
+ if (n == "stroke-linecap") { return el.strokeLineCap }
1043
+ if (n == "stroke-linejoin") { return el.strokeLineJoin }
1044
+ if (n == "stroke-dasharray") { return el.strokeDashArray }
1045
+ if (n == "alt") { return el.alt }
1046
+ if (n == "href") { return el.href }
1047
+
1048
+ if (n == "width") { return (EVGPatch.unitText(el.width)) }
1049
+ if (n == "height") { return (EVGPatch.unitText(el.height)) }
1050
+ if (n == "min-width") { return (EVGPatch.unitText(el.minWidth)) }
1051
+ if (n == "min-height") { return (EVGPatch.unitText(el.minHeight)) }
1052
+ if (n == "max-width") { return (EVGPatch.unitText(el.maxWidth)) }
1053
+ if (n == "max-height") { return (EVGPatch.unitText(el.maxHeight)) }
1054
+ ; AN INSET MAY BE AN `anchor()` RATHER THAN A LENGTH, and then the
1055
+ ; length is empty — which would have written the document back with
1056
+ ; the badge unpositioned and nothing to say it had been dropped.
1057
+ if (n == "left") {
1058
+ def la:string (EVGPatch.anchorInsetText(el.anchorLeftSide el.anchorLeftOffset))
1059
+ if ((strlen la) > 0) {
1060
+ return la
1061
+ }
1062
+ return (EVGPatch.unitText(el.left))
1063
+ }
1064
+ if (n == "top") {
1065
+ def ta:string (EVGPatch.anchorInsetText(el.anchorTopSide el.anchorTopOffset))
1066
+ if ((strlen ta) > 0) {
1067
+ return ta
1068
+ }
1069
+ return (EVGPatch.unitText(el.top))
1070
+ }
1071
+ if (n == "right") {
1072
+ def ra:string (EVGPatch.anchorInsetText(el.anchorRightSide el.anchorRightOffset))
1073
+ if ((strlen ra) > 0) {
1074
+ return ra
1075
+ }
1076
+ return (EVGPatch.unitText(el.right))
1077
+ }
1078
+ if (n == "bottom") {
1079
+ def ba:string (EVGPatch.anchorInsetText(el.anchorBottomSide el.anchorBottomOffset))
1080
+ if ((strlen ba) > 0) {
1081
+ return ba
1082
+ }
1083
+ return (EVGPatch.unitText(el.bottom))
1084
+ }
1085
+ if (n == "gap") { return (EVGPatch.unitText(el.gap)) }
1086
+ if (n == "font-size") { return (EVGPatch.unitText(el.fontSize)) }
1087
+ if (n == "flex-basis") { return (EVGPatch.unitText(el.flexBasis)) }
1088
+
1089
+ if (n == "color") { return (EVGPatch.colorText(el.color)) }
1090
+ if (n == "background-color") { return (EVGPatch.colorText(el.backgroundColor)) }
1091
+ if (n == "border-color") { return (EVGPatch.colorText(bx.borderColor)) }
1092
+ if (n == "fill") { return (EVGPatch.colorText(el.fillColor)) }
1093
+ if (n == "stroke") { return (EVGPatch.colorText(el.strokeColor)) }
1094
+ if (n == "emoji-color") { return (EVGPatch.colorText(el.emojiColor)) }
1095
+ if (n == "gradient-from") { return (EVGPatch.gradientText(el.gradientFrom el.gradientSet)) }
1096
+ if (n == "gradient-to") { return (EVGPatch.gradientText(el.gradientTo el.gradientSet)) }
1097
+
1098
+ if (n == "evg-surface-effect") { return el.surfaceEffect }
1099
+ if (n == "evg-effect-on") { return el.effectTrigger }
1100
+ if (EVGPatch.isFx(n)) {
1101
+ def key:string (substring n 7 (strlen n))
1102
+ def i:int 0
1103
+ while (i < (array_length el.fxNames)) {
1104
+ if ((itemAt el.fxNames i) == key) {
1105
+ return (EVGDisplayList.num((itemAt el.fxValues i)))
1106
+ }
1107
+ i = (i + 1)
1108
+ }
1109
+ return ""
1110
+ }
1111
+ if (n == "opacity") { return (EVGDisplayList.num(el.opacity)) }
1112
+ if (n == "line-height") { return (EVGDisplayList.num(el.lineHeight)) }
1113
+ if (n == "flex-grow") { return (EVGDisplayList.num(el.flex)) }
1114
+ if (n == "flex-shrink") { return (EVGDisplayList.num(el.flexShrink)) }
1115
+ if (n == "rotate") { return (EVGDisplayList.num(el.rotate)) }
1116
+ if (n == "scale") { return (EVGDisplayList.num(el.scale)) }
1117
+ if (n == "gradient-dir") { return (EVGDisplayList.num((to_double el.gradientDir))) }
1118
+ if (n == "stroke-width") { return (EVGDisplayList.num(el.strokeWidth)) }
1119
+ if (n == "stroke-dashoffset") { return (EVGDisplayList.num(el.strokeDashOffset)) }
1120
+
1121
+ if (n == "margin-top") { return (EVGPatch.unitText(bx.marginTop)) }
1122
+ if (n == "margin-right") { return (EVGPatch.unitText(bx.marginRight)) }
1123
+ if (n == "margin-bottom") { return (EVGPatch.unitText(bx.marginBottom)) }
1124
+ if (n == "margin-left") { return (EVGPatch.unitText(bx.marginLeft)) }
1125
+ if (n == "padding-top") { return (EVGPatch.unitText(bx.paddingTop)) }
1126
+ if (n == "padding-right") { return (EVGPatch.unitText(bx.paddingRight)) }
1127
+ if (n == "padding-bottom") { return (EVGPatch.unitText(bx.paddingBottom)) }
1128
+ if (n == "padding-left") { return (EVGPatch.unitText(bx.paddingLeft)) }
1129
+ if (n == "border-width") { return (EVGPatch.unitText(bx.borderWidth)) }
1130
+ if (n == "border-radius") { return (EVGPatch.unitText(bx.borderRadius)) }
1131
+
1132
+ return ""
1133
+ } doc {
1134
+ description "Reads a property back in the form setAttribute would accept."
1135
+ param el "The element to read."
1136
+ param name "A patchable property name."
1137
+ returns "The current value, or \"\" when the property was never set."
1138
+ }
1139
+
1140
+ ; Put a property back to the value a fresh element has. This is what an
1141
+ ; inverse op does when the property had never been set — writing "" through
1142
+ ; `setAttribute` would be a parse failure, not an erasure.
1143
+ sfn clearProp:boolean (el:EVGElement name:string) {
1144
+ def n:string (EVGPatch.canonical(name))
1145
+ def kind:int (EVGPatch.kindOf(n))
1146
+ if (kind == 0) {
1147
+ return false
1148
+ }
1149
+ def bx:EVGBox el.box
1150
+
1151
+ if (n == "display") {
1152
+ el.display = "block"
1153
+ return true
1154
+ }
1155
+ if (n == "position") {
1156
+ el.position = "relative"
1157
+ return true
1158
+ }
1159
+ if (n == "flex-direction") {
1160
+ el.flexDirection = "column"
1161
+ return true
1162
+ }
1163
+ if (n == "justify-content") {
1164
+ el.justifyContent = "flex-start"
1165
+ return true
1166
+ }
1167
+ if (n == "align-items") {
1168
+ el.alignItems = "flex-start"
1169
+ return true
1170
+ }
1171
+ if (n == "align-content") {
1172
+ el.alignContent = "flex-start"
1173
+ return true
1174
+ }
1175
+ if (n == "flex-wrap") {
1176
+ el.flexWrap = "wrap"
1177
+ return true
1178
+ }
1179
+ if (n == "grid-area") {
1180
+ el.gridArea = ""
1181
+ return true
1182
+ }
1183
+ if (n == "grid-row") {
1184
+ el.gridRow = ""
1185
+ return true
1186
+ }
1187
+ if (n == "grid-column") {
1188
+ el.gridColumn = ""
1189
+ return true
1190
+ }
1191
+ if (n == "grid-template-areas") {
1192
+ el.gridTemplateAreas = ""
1193
+ return true
1194
+ }
1195
+ if (n == "grid-auto-flow") {
1196
+ el.gridAutoFlow = "row"
1197
+ return true
1198
+ }
1199
+ if (n == "grid-template-columns") {
1200
+ el.gridTemplateColumns = ""
1201
+ return true
1202
+ }
1203
+ if (n == "grid-template-rows") {
1204
+ el.gridTemplateRows = ""
1205
+ return true
1206
+ }
1207
+ if (n == "font-family") {
1208
+ el.fontFamily = "Noto Sans"
1209
+ return true
1210
+ }
1211
+ if (n == "font-weight") {
1212
+ el.fontWeight = "normal"
1213
+ return true
1214
+ }
1215
+ if (n == "text-align") {
1216
+ el.textAlign = "left"
1217
+ return true
1218
+ }
1219
+ if (n == "overflow") {
1220
+ el.overflow = "visible"
1221
+ return true
1222
+ }
1223
+ if (n == "cursor") {
1224
+ el.cursor = ""
1225
+ return true
1226
+ }
1227
+ if (n == "class-name") {
1228
+ el.className = ""
1229
+ return true
1230
+ }
1231
+ if (n == "theme") {
1232
+ el.theme = ""
1233
+ return true
1234
+ }
1235
+ if (n == "src") {
1236
+ el.src = ""
1237
+ return true
1238
+ }
1239
+ if (n == "object-fit") {
1240
+ el.objectFit = "cover"
1241
+ return true
1242
+ }
1243
+ if (n == "transform") {
1244
+ el.transformSpec = ""
1245
+ return true
1246
+ }
1247
+ if (n == "transform-origin") {
1248
+ el.transformOriginSpec = ""
1249
+ el.transformOriginX = (EVGUnit.unset())
1250
+ el.transformOriginY = (EVGUnit.unset())
1251
+ return true
1252
+ }
1253
+ if (n == "white-space") {
1254
+ el.whiteSpace = "normal"
1255
+ return true
1256
+ }
1257
+ if (n == "transition") {
1258
+ el.transitionSpec = ""
1259
+ return true
1260
+ }
1261
+ if (n == "background-gradient") {
1262
+ el.backgroundGradient = ""
1263
+ return true
1264
+ }
1265
+ if (n == "svg") {
1266
+ el.svgSource = ""
1267
+ return true
1268
+ }
1269
+ if (n == "d") {
1270
+ el.svgPath = ""
1271
+ return true
1272
+ }
1273
+ if (n == "view-box") {
1274
+ el.viewBox = ""
1275
+ return true
1276
+ }
1277
+ if (n == "fill-rule") {
1278
+ el.fillRule = "nonzero"
1279
+ return true
1280
+ }
1281
+ if (n == "stroke-linecap") {
1282
+ el.strokeLineCap = "butt"
1283
+ return true
1284
+ }
1285
+ if (n == "stroke-linejoin") {
1286
+ el.strokeLineJoin = "miter"
1287
+ return true
1288
+ }
1289
+ if (n == "stroke-dasharray") {
1290
+ el.strokeDashArray = ""
1291
+ return true
1292
+ }
1293
+ if (n == "overlay") {
1294
+ el.isOverlay = false
1295
+ return true
1296
+ }
1297
+ if (n == "overlay-anchor-role") {
1298
+ el.isOverlayAnchor = false
1299
+ return true
1300
+ }
1301
+ if (n == "overlay-side") {
1302
+ el.overlaySide = "bottom"
1303
+ return true
1304
+ }
1305
+ if (n == "overlay-align") {
1306
+ el.overlayAlign = "start"
1307
+ return true
1308
+ }
1309
+ if (n == "overlay-gap") {
1310
+ el.overlayGap = 4.0
1311
+ return true
1312
+ }
1313
+ if (n == "position-anchor") {
1314
+ el.positionAnchor = ""
1315
+ return true
1316
+ }
1317
+ if (n == "position-try-fallbacks") {
1318
+ el.positionTryFallbacks = ""
1319
+ return true
1320
+ }
1321
+ if (n == "position-try-order") {
1322
+ el.positionTryOrder = ""
1323
+ return true
1324
+ }
1325
+ if (n == "presentation") {
1326
+ el.presentation = ""
1327
+ return true
1328
+ }
1329
+ if (n == "sheet-below") {
1330
+ el.sheetBelow = 0.0
1331
+ return true
1332
+ }
1333
+ if (n == "fit-viewport") {
1334
+ el.fitViewport = false
1335
+ return true
1336
+ }
1337
+ if (n == "anchor-name") {
1338
+ el.anchorName = ""
1339
+ return true
1340
+ }
1341
+ if (n == "from") {
1342
+ el.connectorFrom = ""
1343
+ return true
1344
+ }
1345
+ if (n == "to") {
1346
+ el.connectorTo = ""
1347
+ return true
1348
+ }
1349
+ if (n == "from-side") {
1350
+ el.connectorFromSide = "auto"
1351
+ return true
1352
+ }
1353
+ if (n == "to-side") {
1354
+ el.connectorToSide = "auto"
1355
+ return true
1356
+ }
1357
+ if (n == "routing") {
1358
+ el.connectorRouting = "straight"
1359
+ return true
1360
+ }
1361
+ if (n == "arrow-start") {
1362
+ el.arrowStart = "none"
1363
+ return true
1364
+ }
1365
+ if (n == "arrow-end") {
1366
+ el.arrowEnd = "none"
1367
+ return true
1368
+ }
1369
+ if (n == "arrow-size") {
1370
+ el.arrowSize = 10.0
1371
+ return true
1372
+ }
1373
+ if (n == "from-offset") {
1374
+ el.connectorFromOffset = 0.0
1375
+ return true
1376
+ }
1377
+ if (n == "to-offset") {
1378
+ el.connectorToOffset = 0.0
1379
+ return true
1380
+ }
1381
+ if (n == "alt") {
1382
+ el.alt = ""
1383
+ return true
1384
+ }
1385
+ if (n == "href") {
1386
+ el.href = ""
1387
+ return true
1388
+ }
1389
+
1390
+ def u:EVGUnit (EVGUnit.unset())
1391
+ if (n == "width") {
1392
+ el.width = u
1393
+ return true
1394
+ }
1395
+ if (n == "height") {
1396
+ el.height = u
1397
+ return true
1398
+ }
1399
+ if (n == "min-width") {
1400
+ el.minWidth = u
1401
+ return true
1402
+ }
1403
+ if (n == "min-height") {
1404
+ el.minHeight = u
1405
+ return true
1406
+ }
1407
+ if (n == "max-width") {
1408
+ el.maxWidth = u
1409
+ return true
1410
+ }
1411
+ if (n == "max-height") {
1412
+ el.maxHeight = u
1413
+ return true
1414
+ }
1415
+ if (n == "left") {
1416
+ el.left = u
1417
+ el.anchorLeftSide = ""
1418
+ el.anchorLeftOffset = 0.0
1419
+ return true
1420
+ }
1421
+ if (n == "top") {
1422
+ el.top = u
1423
+ el.anchorTopSide = ""
1424
+ el.anchorTopOffset = 0.0
1425
+ return true
1426
+ }
1427
+ if (n == "right") {
1428
+ el.right = u
1429
+ el.anchorRightSide = ""
1430
+ el.anchorRightOffset = 0.0
1431
+ return true
1432
+ }
1433
+ if (n == "bottom") {
1434
+ el.bottom = u
1435
+ el.anchorBottomSide = ""
1436
+ el.anchorBottomOffset = 0.0
1437
+ return true
1438
+ }
1439
+ if (n == "gap") {
1440
+ el.gap = u
1441
+ return true
1442
+ }
1443
+ if (n == "font-size") {
1444
+ el.fontSize = u
1445
+ return true
1446
+ }
1447
+ if (n == "flex-basis") {
1448
+ el.flexBasis = u
1449
+ return true
1450
+ }
1451
+
1452
+ ; `color` is not like the other two. A fresh element's background and
1453
+ ; border are UNSET — nothing is painted until something asks — but its
1454
+ ; text colour is a real black, because text has to be drawn in
1455
+ ; something. Clearing it to `noColor` made a reverted element read ""
1456
+ ; where a fresh one reads rgb(0,0,0), which is the shape of bug this
1457
+ ; file's last test exists to catch.
1458
+ def c:EVGColor (EVGColor.noColor())
1459
+ if (n == "color") {
1460
+ el.color = (new EVGColor())
1461
+ return true
1462
+ }
1463
+ if (n == "background-color") {
1464
+ el.backgroundColor = c
1465
+ return true
1466
+ }
1467
+ if (n == "border-color") {
1468
+ bx.borderColor = c
1469
+ return true
1470
+ }
1471
+ if (n == "fill") {
1472
+ el.fillColor = c
1473
+ return true
1474
+ }
1475
+ if (n == "stroke") {
1476
+ el.strokeColor = c
1477
+ return true
1478
+ }
1479
+ if (n == "emoji-color") {
1480
+ el.emojiColor = c
1481
+ return true
1482
+ }
1483
+ ; Clearing either end clears the gradient: half a gradient is not a
1484
+ ; state the renderer has, and leaving `gradientSet` on with one end
1485
+ ; unset paints from black.
1486
+ if (n == "gradient-from") {
1487
+ el.gradientFrom = c
1488
+ el.gradientSet = false
1489
+ return true
1490
+ }
1491
+ if (n == "gradient-to") {
1492
+ el.gradientTo = c
1493
+ el.gradientSet = false
1494
+ return true
1495
+ }
1496
+
1497
+ if (n == "evg-surface-effect") {
1498
+ el.surfaceEffect = ""
1499
+ return true
1500
+ }
1501
+ if (n == "evg-effect-on") {
1502
+ el.effectTrigger = ""
1503
+ return true
1504
+ }
1505
+ ; Clearing an fx parameter takes it OUT rather than zeroing it: zero is
1506
+ ; a value a plugin reads, and `readProp` answering "0" for a property
1507
+ ; that was never set would make every undo write one.
1508
+ if (EVGPatch.isFx(n)) {
1509
+ def key:string (substring n 7 (strlen n))
1510
+ def names:[string]
1511
+ def values:[double]
1512
+ def i:int 0
1513
+ while (i < (array_length el.fxNames)) {
1514
+ if ((itemAt el.fxNames i) == key) {
1515
+ } {
1516
+ push names (itemAt el.fxNames i)
1517
+ push values (itemAt el.fxValues i)
1518
+ }
1519
+ i = (i + 1)
1520
+ }
1521
+ el.fxNames = names
1522
+ el.fxValues = values
1523
+ return true
1524
+ }
1525
+ if (n == "opacity") {
1526
+ el.opacity = 1.0
1527
+ return true
1528
+ }
1529
+ if (n == "line-height") {
1530
+ el.lineHeight = 0.0
1531
+ return true
1532
+ }
1533
+ if (n == "flex-grow") {
1534
+ el.flex = 0.0
1535
+ return true
1536
+ }
1537
+ if (n == "flex-shrink") {
1538
+ el.flexShrink = 1.0
1539
+ return true
1540
+ }
1541
+ if (n == "rotate") {
1542
+ el.rotate = 0.0
1543
+ return true
1544
+ }
1545
+ if (n == "scale") {
1546
+ el.scale = 1.0
1547
+ return true
1548
+ }
1549
+ if (n == "stroke-width") {
1550
+ el.strokeWidth = 0.0
1551
+ return true
1552
+ }
1553
+ if (n == "stroke-dashoffset") {
1554
+ el.strokeDashOffset = 0.0
1555
+ return true
1556
+ }
1557
+ if (n == "gradient-dir") {
1558
+ el.gradientDir = 0
1559
+ return true
1560
+ }
1561
+
1562
+ if (n == "margin-top") {
1563
+ bx.marginTop = u
1564
+ return true
1565
+ }
1566
+ if (n == "margin-right") {
1567
+ bx.marginRight = u
1568
+ return true
1569
+ }
1570
+ if (n == "margin-bottom") {
1571
+ bx.marginBottom = u
1572
+ return true
1573
+ }
1574
+ if (n == "margin-left") {
1575
+ bx.marginLeft = u
1576
+ return true
1577
+ }
1578
+ if (n == "padding-top") {
1579
+ bx.paddingTop = u
1580
+ return true
1581
+ }
1582
+ if (n == "padding-right") {
1583
+ bx.paddingRight = u
1584
+ return true
1585
+ }
1586
+ if (n == "padding-bottom") {
1587
+ bx.paddingBottom = u
1588
+ return true
1589
+ }
1590
+ if (n == "padding-left") {
1591
+ bx.paddingLeft = u
1592
+ return true
1593
+ }
1594
+ if (n == "border-width") {
1595
+ bx.borderWidth = u
1596
+ return true
1597
+ }
1598
+ if (n == "border-radius") {
1599
+ bx.borderRadius = u
1600
+ return true
1601
+ }
1602
+
1603
+ return false
1604
+ }
1605
+
1606
+ ; The unit fields are optional on the element, so "never said anything
1607
+ ; about width" is a real answer here and not a defensive check — the same
1608
+ ; reading `EVGInspect.unitProp` takes.
1609
+ sfn unitText:string (u@(optional):EVGUnit) {
1610
+ if (null? u) {
1611
+ return ""
1612
+ }
1613
+ def uu:EVGUnit (unwrap u)
1614
+ if (uu.isSet == false) {
1615
+ return ""
1616
+ }
1617
+ return (uu.toString())
1618
+ }
1619
+
1620
+ ; A gradient end reads as nothing until the gradient is on. Without the
1621
+ ; guard a fresh element reports two black ends it does not have, and every
1622
+ ; document would carry them.
1623
+ sfn gradientText:string (c@(optional):EVGColor on:boolean) {
1624
+ if (on == false) {
1625
+ return ""
1626
+ }
1627
+ return (EVGPatch.colorText(c))
1628
+ }
1629
+
1630
+ sfn colorText:string (c@(optional):EVGColor) {
1631
+ if (null? c) {
1632
+ return ""
1633
+ }
1634
+ def cc:EVGColor (unwrap c)
1635
+ if (cc.isSet == false) {
1636
+ return ""
1637
+ }
1638
+ return (cc.toCSSString())
1639
+ }
1640
+
1641
+
1642
+ ; An element for a tag name — and the tag name is KEPT, not normalised.
1643
+ ;
1644
+ ; The engine has two tag vocabularies and this has to know both. The
1645
+ ; factories above spell things the HTML way (`span`, `img`), while
1646
+ ; `JSXToEVG.mapTagName` emits the engine's own (`text`, `image`, `page`,
1647
+ ; `layer`). A document written by one and read by the other has to come
1648
+ ; back as what it was, so what the tag decides here is the ELEMENT TYPE —
1649
+ ; container, text, image or path, which is what layout and every renderer
1650
+ ; actually switch on — and the name itself is preserved.
1651
+ ;
1652
+ ; A tag in neither vocabulary is refused rather than defaulted to a
1653
+ ; container. An `insert` op naming a tag nobody has heard of is a typo, and
1654
+ ; a document carrying one would otherwise read back as a silently different
1655
+ ; element. `evg_json_tool` reports it as a loss for that reason.
1656
+ sfn create@(optional):EVGElement (tag:string) {
1657
+ def family:int (EVGPatch.familyOf(tag))
1658
+ if (family < 0) {
1659
+ def none@(optional):EVGElement
1660
+ return none
1661
+ }
1662
+ def el:EVGElement (EVGElement.createDiv())
1663
+ if (family == 1) {
1664
+ el = (EVGElement.createSpan())
1665
+ }
1666
+ if (family == 2) {
1667
+ el = (EVGElement.createImg())
1668
+ }
1669
+ if (family == 3) {
1670
+ el = (EVGElement.createPath())
1671
+ }
1672
+ el.tagName = tag
1673
+ def out@(optional):EVGElement el
1674
+ return out
1675
+ }
1676
+
1677
+ ; 0 container, 1 text, 2 image, 3 path — `EVGElement.elementType` — or -1
1678
+ ; for a tag neither vocabulary claims.
1679
+ sfn familyOf:int (tag:string) {
1680
+ if (((tag == "span") || (tag == "text")) || (tag == "Label")) {
1681
+ return 1
1682
+ }
1683
+ if (((tag == "img") || (tag == "image")) || (tag == "Image")) {
1684
+ return 2
1685
+ }
1686
+ if (((tag == "path") || (tag == "Path")) || (tag == "svg")) {
1687
+ return 3
1688
+ }
1689
+ ; A connector IS a path — one whose `d` the layout writes rather than
1690
+ ; the author. Everything downstream of here treats it as one.
1691
+ if (tag == "connector") {
1692
+ return 3
1693
+ }
1694
+ if (tag == "Svg") {
1695
+ return 3
1696
+ }
1697
+ if (((tag == "div") || (tag == "View")) || (tag == "box")) {
1698
+ return 0
1699
+ }
1700
+ if (((tag == "row") || (tag == "column")) || (tag == "page")) {
1701
+ return 0
1702
+ }
1703
+ if (((tag == "Page") || (tag == "print")) || (tag == "Print")) {
1704
+ return 0
1705
+ }
1706
+ if (((tag == "section") || (tag == "Section")) || (tag == "layer")) {
1707
+ return 0
1708
+ }
1709
+ ; A popover is a container that happens to be a surface — see
1710
+ ; `EVGElement.isSurface`. The tag is the web's name for it and saves
1711
+ ; writing `overlay: true`.
1712
+ if ((tag == "popover") || (tag == "Popover")) {
1713
+ return 0
1714
+ }
1715
+ if (((tag == "Layer") || (tag == "divider")) || (tag == "spacer")) {
1716
+ return 0
1717
+ }
1718
+ return (0 - 1)
1719
+ }
1720
+
1721
+ }