ranger-compiler 3.5.1 → 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 +1064 -20
  2. package/LICENSE +3 -1
  3. package/README.md +123 -28
  4. package/dist/Lang.rgr +1188 -290
  5. package/dist/api.d.ts +2343 -879
  6. package/dist/api.js +79923 -56921
  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,160 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // The display list as it crosses a thread: `EVGSceneBinary` in, the
4
+ // painter's commands out.
5
+ //
6
+ // `evg-list.js` reads the list off the Ranger OBJECT, which is what a host
7
+ // that shares a heap with the engine does. A host whose engine runs in a
8
+ // Worker (`evg-engine.js`, PLAN_NATIVE_HOSTS.md S1) cannot: what crosses is
9
+ // `EVGDisplayList.toBinary()` — three `Int32Array`s and a string pool,
10
+ // transferred rather than copied — and this is the reader. It produces the
11
+ // SAME command objects `cmdsOf` produces, key for key, so the painter does
12
+ // not know which way the list arrived. `list-binary-check.mjs` holds the two
13
+ // against each other on real frames.
14
+ //
15
+ // The record width is DERIVED from the buffer, never assumed: `cmds` is
16
+ // allocated as exactly `count * stride`, so dividing recovers the writer's
17
+ // number (ISSUES #4). This reader wants 36 fields; a wider record is fine and
18
+ // a narrower one is refused with both numbers in the message. Values are
19
+ // hundredths — `EVGDisplayList.fixed` rounds to what `toJson` writes — so the
20
+ // division below gives back exactly the JSON's numbers.
21
+ //
22
+ // Slots 36 to 40 are `letter-spacing` and a stroke's cap, join and dash, and
23
+ // they are read only when the buffer is wide
24
+ // enough to have it. Raising FIELDS_READ instead would have refused every
25
+ // bundle built before it existed — a hard failure for a field whose absence
26
+ // means "the font's own spacing", which is what those bundles meant. A slot
27
+ // that is optional in the data is optional in the reader.
28
+
29
+ export const FIELDS_READ = 36;
30
+
31
+ /** How many ints per command this buffer carries. */
32
+ export function binaryStride(bin) {
33
+ const n = bin.count | 0;
34
+ const len = bin.cmds.length;
35
+ if (n === 0) return FIELDS_READ;
36
+ const stride = (len / n) | 0;
37
+ if (stride * n !== len) {
38
+ throw new Error(`scene binary: ${len} ints do not divide into ${n} commands`);
39
+ }
40
+ if (stride < FIELDS_READ) {
41
+ throw new Error(`scene binary: ${stride} fields per command, but this reader wants ${FIELDS_READ}`);
42
+ }
43
+ return stride;
44
+ }
45
+
46
+ const rgb = (packed) => [(packed >> 16) & 255, (packed >> 8) & 255, packed & 255];
47
+
48
+ /**
49
+ * The camera on the envelope, or null when the list carried none.
50
+ *
51
+ * It is three numbers beside the page size rather than a field per command,
52
+ * which is why the record stride above is untouched by it: a reader that
53
+ * knows nothing about views reads this buffer exactly as it always did, and
54
+ * draws the scene unpanned — which is the right answer for a list whose host
55
+ * never asked for a camera.
56
+ */
57
+ export function viewOfBinary(bin) {
58
+ if (!bin || !bin.hasView) return null;
59
+ return { x: bin.viewX || 0, y: bin.viewY || 0, scale: bin.viewScale === undefined ? 1 : bin.viewScale };
60
+ }
61
+
62
+ /** The commands, in the shape `evg-list.js`'s `cmdsOf` gives them. */
63
+ export function cmdsOfBinary(bin) {
64
+ const n = bin.count | 0;
65
+ const stride = binaryStride(bin);
66
+ const r = bin.cmds, p = bin.pts, e = bin.ends, pool = bin.strings;
67
+ const out = new Array(n);
68
+ for (let i = 0; i < n; i += 1) {
69
+ const b = i * stride;
70
+ const o = { k: r[b] };
71
+ const layer = r[b + 30];
72
+ if (layer > 0) o.layer = layer;
73
+ o.x = r[b + 1] / 100;
74
+ o.y = r[b + 2] / 100;
75
+ o.w = r[b + 3] / 100;
76
+ o.h = r[b + 4] / 100;
77
+ const flags = r[b + 9];
78
+ const radius = r[b + 5] / 100;
79
+ if (flags & 64) o.rc = [radius, r[b + 27] / 100, r[b + 28] / 100, r[b + 29] / 100];
80
+ if (radius > 0) o.r = radius;
81
+ const t = r[b + 6] / 100;
82
+ if (t > 0) {
83
+ o.t = t;
84
+ if (stride > 38) {
85
+ if (r[b + 37] !== 0) o.cap = r[b + 37];
86
+ if (r[b + 38] !== 0) o.join = r[b + 38];
87
+ }
88
+ if (stride > 40) {
89
+ const d = r[b + 39];
90
+ if (d >= 0) {
91
+ o.dash = pool[d];
92
+ const off = r[b + 40] / 100;
93
+ if (off !== 0) o.dashoff = off;
94
+ }
95
+ }
96
+ }
97
+ o.c = [...rgb(r[b + 7]), r[b + 8] / 100];
98
+ if (flags & 1) {
99
+ o.gd = r[b + 10];
100
+ o.c2 = [...rgb(r[b + 11]), r[b + 12] / 100];
101
+ }
102
+ const textIdx = r[b + 15];
103
+ if (textIdx >= 0) {
104
+ o.text = pool[textIdx];
105
+ o.font = pool[r[b + 16]];
106
+ o.size = r[b + 13] / 100;
107
+ const w = r[b + 17];
108
+ if (w >= 0) o.weight = pool[w];
109
+ if (stride > 36) {
110
+ const ls = r[b + 36];
111
+ if (ls !== 0) o.ls = ls / 100;
112
+ }
113
+ if (flags & 2) o.italic = true;
114
+ }
115
+ const src = r[b + 18];
116
+ if (src >= 0) o.src = pool[src];
117
+ if (flags & 4) o.fx = true;
118
+ if (flags & 8) o.fy = true;
119
+ // The crop window into the source, when the picture is not shown whole.
120
+ // Ten-thousandths, not hundredths: a hundredth of a fraction is a whole
121
+ // percent of the bitmap, sixteen pixels on a wide screenshot.
122
+ if (stride > 44 && flags & 256) {
123
+ o.cu = [r[b + 41] / 10000, r[b + 42] / 10000, r[b + 43] / 10000, r[b + 44] / 10000];
124
+ }
125
+ const rot = r[b + 14] / 100;
126
+ if (rot !== 0) {
127
+ o.rot = rot;
128
+ if (flags & 32) {
129
+ o.rox = r[b + 24] / 100;
130
+ o.roy = r[b + 25] / 100;
131
+ }
132
+ }
133
+ const bb = r[b + 26] / 100;
134
+ if (bb > 0) o.bb = bb;
135
+ if (flags & 128) {
136
+ o.sh = {
137
+ x: r[b + 31] / 100, y: r[b + 32] / 100, blur: r[b + 33] / 100,
138
+ c: [...rgb(r[b + 34]), r[b + 35] / 100],
139
+ };
140
+ }
141
+ const pStart = r[b + 19], pCount = r[b + 20];
142
+ if (pCount > 0) {
143
+ const pts = new Array(pCount);
144
+ for (let j = 0; j < pCount; j += 1) pts[j] = p[pStart + j] / 100;
145
+ o.pts = pts;
146
+ const eStart = r[b + 21], eCount = r[b + 22];
147
+ const ends = new Array(eCount);
148
+ for (let j = 0; j < eCount; j += 1) ends[j] = e[eStart + j];
149
+ o.ends = ends;
150
+ if (flags & 16) o.eo = 1;
151
+ }
152
+ out[i] = o;
153
+ }
154
+ return out;
155
+ }
156
+
157
+ /** The typed arrays a `postMessage` may TRANSFER rather than copy. */
158
+ export function transferablesOf(bin) {
159
+ return [bin.cmds.buffer, bin.pts.buffer, bin.ends.buffer];
160
+ }
@@ -0,0 +1,296 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // The engine off the main thread.
4
+ //
5
+ // PLAN_NATIVE_HOSTS.md S1, the web half. An EVG app — the tree, the cascade,
6
+ // the layout, the display list, the hit test — is pure Ranger and needs no
7
+ // DOM, so it can run in a Worker, and then a layout that takes twelve
8
+ // milliseconds on a chart-heavy page blocks neither the pointer nor the
9
+ // compositor. What crosses back is the display list as `toBinary()` gives
10
+ // it: three `Int32Array`s and a string pool, TRANSFERRED rather than copied,
11
+ // read on the main thread by `evg-binary.js` into the same commands the
12
+ // painter has always been fed. Input crosses the other way, as the calls the
13
+ // host already made — `press`, `scrollDrag`, `setHover` — and the hit test
14
+ // happens where the tree is.
15
+ //
16
+ // One file, two halves:
17
+ //
18
+ // serveEngine(opts) the worker's side — owns the app and answers
19
+ // connectEngine(worker) the page's side — a proxy the host talks to
20
+ //
21
+ // THE PROTOCOL, and why it is shaped like this.
22
+ //
23
+ // post(name, ...args) a call with no answer, QUEUED — a pointer move, a
24
+ // key, a press. Nothing is sent yet.
25
+ // call(name, ...args) a call with an answer, as a promise: `hitId` for
26
+ // the cursor, `fieldStateJson` for the text bridge.
27
+ // Sent at once, with everything queued before it,
28
+ // so order is what the host wrote.
29
+ // frame(dt) the frame: every queued post, then `tick(dt)`,
30
+ // then the display list — ONE message each way per
31
+ // frame, however many events the browser delivered.
32
+ //
33
+ // The reply to `frame` is one of three things, and the host's painter
34
+ // already knows what to do with each — it is exactly the distinction
35
+ // `gallery/realtrainer/web/main.js` draws with `buildSeq` and `frameSeq`:
36
+ //
37
+ // { t: "frame", doc, shifts } a new build: buffers to upload
38
+ // { t: "shift", shifts } the kept list moved — a scroll — draw the
39
+ // frame on the card with new offsets
40
+ // { t: "idle" } nothing moved; do not draw
41
+ //
42
+ // Every reply carries `state`: a small object the app-side bootstrap
43
+ // composes from what the host reads on every frame — the scene's name, the
44
+ // focused field, the scroll velocity — so the host is not making a round
45
+ // trip for a string. What a host cannot do is READ synchronously; that is
46
+ // the whole change, and `main-worker.js` beside the RealTrainer page shows
47
+ // what a host looks like written that way.
48
+
49
+ import { cmdsOfBinary, transferablesOf } from "./evg-binary.js";
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // The worker's side
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Serve an app from inside a Worker.
57
+ *
58
+ * @param {object} opts
59
+ * @param {(init: any) => object} opts.make build the app from the `init`
60
+ * message's payload — install the measurer FIRST (`evg-measure.js`
61
+ * works here through `OffscreenCanvas`), because the app keeps a
62
+ * layout from the moment it exists
63
+ * @param {(app: object) => object} opts.display the display list for the
64
+ * frame: `app.display()` for a host that keeps its list
65
+ * @param {(app: object, dt: number) => boolean} [opts.tick] the clock;
66
+ * default `app.tick(dt)`
67
+ * @param {(app: object) => object} [opts.state] what every reply carries
68
+ * @param {(app: object) => object|undefined} [opts.effect] the surface
69
+ * effect, in the JSON's shape — `effectOf` from evg-list.js
70
+ * @param {(app: object) => number[][]} opts.shifts the layers' shifts —
71
+ * `shiftsOf` from evg-list.js
72
+ * @param {(app: object, dl: object) => object} [opts.onBuild] what a NEW
73
+ * BUILD carries beside the picture — the accessibility tree, say,
74
+ * which changes when the tree changes and at no other time — merged
75
+ * into the "frame" reply as `built`
76
+ * @param {object} [opts.hooks] functions the host may post or call by
77
+ * `@name` — host logic that belongs beside the tree, such as "hit
78
+ * test this point and hover what is there", written once here rather
79
+ * than as three round trips
80
+ * @param {object} [opts.scope] `self` by default
81
+ */
82
+ export function serveEngine(opts) {
83
+ const scope = opts.scope || self;
84
+ let app = null;
85
+ let lastList = null;
86
+ let lastSeq = -1;
87
+ let lastShifts = "";
88
+ const tick = opts.tick || ((a, dt) => a.tick(dt));
89
+ const state = opts.state || (() => ({}));
90
+
91
+ const reply = (msg, transfer) => scope.postMessage(msg, transfer || []);
92
+
93
+ const run = (name, args) => {
94
+ if (name.charCodeAt(0) === 64) {
95
+ const h = opts.hooks && opts.hooks[name.slice(1)];
96
+ if (typeof h !== "function") throw new Error(`engine: no hook ${name}`);
97
+ return h(app, ...args);
98
+ }
99
+ const f = app[name];
100
+ if (typeof f !== "function") throw new Error(`engine: the app has no method ${name}`);
101
+ return f.apply(app, args);
102
+ };
103
+
104
+ const frameReply = (dt, dirty) => {
105
+ let ticked = false;
106
+ if (dt !== undefined && dt !== null) ticked = !!tick(app, dt);
107
+ const dl = opts.display(app);
108
+ const seq = dl.buildSeq * 100000 + dl.frameSeq;
109
+ const shifts = opts.shifts(app, dl);
110
+ const st = state(app, dl);
111
+ if (dl !== lastList || seq !== lastSeq) {
112
+ lastList = dl;
113
+ lastSeq = seq;
114
+ lastShifts = JSON.stringify(shifts);
115
+ const bin = dl.toBinary();
116
+ const doc = {
117
+ width: bin.width, height: bin.height, seq,
118
+ bin: { count: bin.count, cmds: bin.cmds, pts: bin.pts, ends: bin.ends, strings: bin.strings },
119
+ };
120
+ const effect = opts.effect ? opts.effect(app, dl) : undefined;
121
+ if (effect) doc.effect = effect;
122
+ const built = opts.onBuild ? opts.onBuild(app, dl) : undefined;
123
+ reply({ t: "frame", doc, shifts, state: st, ticked, built }, transferablesOf(bin));
124
+ return;
125
+ }
126
+ const sh = JSON.stringify(shifts);
127
+ if (sh !== lastShifts || ticked || dirty) {
128
+ lastShifts = sh;
129
+ reply({ t: "shift", shifts, state: st, ticked });
130
+ return;
131
+ }
132
+ reply({ t: "idle", state: st, ticked });
133
+ };
134
+
135
+ scope.onmessage = (ev) => {
136
+ const m = ev.data;
137
+ try {
138
+ switch (m.t) {
139
+ case "init":
140
+ app = opts.make(m.init);
141
+ reply({ t: "ready", state: state(app, null) });
142
+ break;
143
+ case "post":
144
+ run(m.name, m.args);
145
+ break;
146
+ case "call": {
147
+ const value = run(m.name, m.args);
148
+ reply({ t: "ret", id: m.id, value: value === undefined ? null : value });
149
+ break;
150
+ }
151
+ case "batch": {
152
+ let dirty = false;
153
+ for (const [name, args] of m.posts) {
154
+ const r = run(name, args);
155
+ if (r === true) dirty = true;
156
+ }
157
+ for (const c of m.calls) {
158
+ const value = run(c.name, c.args);
159
+ reply({ t: "ret", id: c.id, value: value === undefined ? null : value });
160
+ }
161
+ if (m.frame) frameReply(m.dt, dirty || m.dirty);
162
+ break;
163
+ }
164
+ case "close":
165
+ scope.close();
166
+ break;
167
+ default:
168
+ throw new Error(`engine: unknown message ${m.t}`);
169
+ }
170
+ } catch (e) {
171
+ reply({ t: "error", id: m.id, message: String((e && e.stack) || e), during: m.t + ":" + (m.name || "") });
172
+ }
173
+ };
174
+ }
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // The page's side
178
+ // ---------------------------------------------------------------------------
179
+
180
+ /**
181
+ * Talk to an app served by `serveEngine`.
182
+ *
183
+ * @param {Worker} worker
184
+ * @param {any} init what the worker's `make` is handed
185
+ * @returns {{
186
+ * ready: Promise<object>,
187
+ * post: (name: string, ...args: any[]) => void,
188
+ * call: (name: string, ...args: any[]) => Promise<any>,
189
+ * frame: (dt?: number, dirty?: boolean) => Promise<object>,
190
+ * flush: () => void,
191
+ * state: () => object,
192
+ * onError: (fn: (e: {message: string, during: string}) => void) => void,
193
+ * close: () => void,
194
+ * }}
195
+ */
196
+ export function connectEngine(worker, init) {
197
+ let nextId = 1;
198
+ const waiting = new Map();
199
+ let posts = [];
200
+ let last = {};
201
+ let errorFn = (e) => console.error("engine:", e.during, e.message);
202
+ let readyResolve;
203
+ const ready = new Promise((r) => { readyResolve = r; });
204
+ // At most one frame in flight: a host that asks for the next frame before
205
+ // the last one came back is asking the worker to fall behind, and a
206
+ // second request is answered by the reply already coming.
207
+ let frameWaiting = null;
208
+
209
+ worker.onmessage = (ev) => {
210
+ const m = ev.data;
211
+ switch (m.t) {
212
+ case "ready":
213
+ last = m.state || {};
214
+ readyResolve(last);
215
+ break;
216
+ case "ret": {
217
+ const w = waiting.get(m.id);
218
+ if (w) { waiting.delete(m.id); w.resolve(m.value); }
219
+ break;
220
+ }
221
+ case "frame": {
222
+ // The buffers arrive owned by this thread; the commands are made once.
223
+ m.doc.list = { seq: m.doc.seq, shifts: m.shifts, cmds: cmdsOfBinary(m.doc.bin) };
224
+ if (m.doc.effect) m.doc.list.effect = m.doc.effect;
225
+ delete m.doc.bin;
226
+ // falls through
227
+ }
228
+ case "shift":
229
+ case "idle": {
230
+ last = m.state || last;
231
+ const w = frameWaiting;
232
+ frameWaiting = null;
233
+ if (w) w.resolve(m);
234
+ break;
235
+ }
236
+ case "error": {
237
+ if (m.id !== undefined && waiting.has(m.id)) {
238
+ const w = waiting.get(m.id);
239
+ waiting.delete(m.id);
240
+ w.reject(new Error(m.message));
241
+ } else if (frameWaiting && m.during.startsWith("batch")) {
242
+ const w = frameWaiting;
243
+ frameWaiting = null;
244
+ w.reject(new Error(m.message));
245
+ }
246
+ errorFn(m);
247
+ break;
248
+ }
249
+ default:
250
+ break;
251
+ }
252
+ };
253
+
254
+ worker.postMessage({ t: "init", init });
255
+
256
+ const send = (calls, frame, dt, dirty) => {
257
+ const batch = { t: "batch", posts, calls, frame, dt, dirty };
258
+ posts = [];
259
+ worker.postMessage(batch);
260
+ };
261
+
262
+ return {
263
+ ready,
264
+ post(name, ...args) {
265
+ posts.push([name, args]);
266
+ },
267
+ call(name, ...args) {
268
+ const id = nextId++;
269
+ return new Promise((resolve, reject) => {
270
+ waiting.set(id, { resolve, reject });
271
+ send([{ id, name, args }], false);
272
+ });
273
+ },
274
+ frame(dt, dirty) {
275
+ if (frameWaiting) return frameWaiting.promise;
276
+ let resolve, reject;
277
+ const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
278
+ frameWaiting = { promise, resolve, reject };
279
+ send([], true, dt, !!dirty);
280
+ return promise;
281
+ },
282
+ /** Send what is queued without asking for a frame. */
283
+ flush() {
284
+ if (posts.length) send([], false);
285
+ },
286
+ state() {
287
+ return last;
288
+ },
289
+ onError(fn) {
290
+ errorFn = fn;
291
+ },
292
+ close() {
293
+ worker.postMessage({ t: "close" });
294
+ },
295
+ };
296
+ }
@@ -0,0 +1,237 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // The effect driver: what turns a pointer into the events a surface effect
4
+ // reads, from what the DOCUMENT said rather than from what the page's code
5
+ // remembered to do.
6
+ //
7
+ // BEFORE THIS, the ripple was driven like this, in the demo's own main.js:
8
+ //
9
+ // canvas.addEventListener("pointerdown", (ev) => {
10
+ // app.ripple(ev.offsetX, ev.offsetY); // every press, anywhere
11
+ // });
12
+ //
13
+ // and the application kept the drops, aged them on every tick, retired them,
14
+ // and stamped them onto the root at paint time. Three arrays of state and four
15
+ // methods on a dashboard, for an effect the dashboard has no opinion about.
16
+ // Nothing in that chain could answer "which element ripples" — the answer was
17
+ // "the page", because the effect was the page's.
18
+ //
19
+ // Now the document answers it:
20
+ //
21
+ // .card { evg-surface-effect: ripple; evg-effect-on: press drag }
22
+ // .hero-sky { evg-surface-effect: starfield; evg-effect-on: always }
23
+ //
24
+ // and this file is the whole of the wiring:
25
+ //
26
+ // const fx = createEffectDriver();
27
+ // attachEffectPointer(canvas, fx, () => schedule()); // optional
28
+ // // per frame:
29
+ // fx.tick(dtMs, list); // ages the events, stamps the clock on
30
+ // renderDisplayList(gl, { width, height, list }, { dpr });
31
+ //
32
+ // WHAT IT OWNS is the events: where a press landed, in PAGE pixels, and how
33
+ // long ago. What it does not own is anything about how they look — that is the
34
+ // plugin's, in `evg-webgl.js` — or which element they belong to, which is the
35
+ // display list's, because that came from the stylesheet.
36
+ //
37
+ // WHY IT IS NOT IN THE ENGINE. The engine has no pointer. A press is a browser
38
+ // event on the web, a MotionEvent on Android and a UITouch on iOS, and the one
39
+ // thing all three have in common is that the box they landed in is already in
40
+ // the display list. So the driver is per host and small, and the part that is
41
+ // the same everywhere — which box, what trigger, how the event ages — is here
42
+ // rather than written again in each one.
43
+
44
+ /** How far a drag travels before it drops another event, in page pixels.
45
+ * Closer and the rings pile into one bright blob; further and a wake reads
46
+ * as a row of separate taps. */
47
+ const DRAG_STEP = 26;
48
+
49
+ /** The most events one instance carries. Eight is what a dragged finger fills
50
+ * in a third of a second, and it is what the shaders' `MAX_EVENTS` is. */
51
+ const MAX_EVENTS = 8;
52
+
53
+ /** How long an event lives, in seconds. The ripple's own decay has taken it
54
+ * under a 250th by three seconds, so this is the renderer's number and not a
55
+ * guess. */
56
+ const LIFE = 3;
57
+
58
+ /** Does this instance's trigger list mention `what`? A trigger is a list of
59
+ * words — `press drag` — because a card can want both and CSS has no better
60
+ * shape for "one or more of these".
61
+ *
62
+ * An instance the host has switched off (`inst.off`) answers no to everything,
63
+ * so a press on it lands on whatever is under it rather than being swallowed
64
+ * by an effect that is not running. The painter skips the same flag, and
65
+ * between them "off" means off rather than "invisible but still listening". */
66
+ export function triggerHas(inst, what) {
67
+ if (!inst || inst.off) return false;
68
+ const on = inst.on || "";
69
+ return on.length > 0 && on.split(/[\s,]+/).indexOf(what) >= 0;
70
+ }
71
+
72
+ /** Is the point inside the instance's box? Corners are not tested: an effect
73
+ * is triggered by a press on the element, and the element's rounded corner is
74
+ * a question about painting rather than about who was pressed. */
75
+ function inBox(inst, x, y) {
76
+ const b = inst.box;
77
+ return !!b && x >= b[0] && y >= b[1] && x <= b[0] + b[2] && y <= b[1] + b[3];
78
+ }
79
+
80
+ export function createEffectDriver(opts = {}) {
81
+ // Per instance id: the events in flight and where the last drag event was
82
+ // dropped. Keyed by ID and not by object, because the display list is
83
+ // rebuilt — a reflow makes new instance objects for the same elements, and
84
+ // a ripple must not die because the window was resized.
85
+ const state = new Map();
86
+ // Seconds since the driver was made. What `always` effects run on, and the
87
+ // reason a starfield needs no events at all.
88
+ let clock = 0;
89
+ const life = opts.life === undefined ? LIFE : opts.life;
90
+ const step = opts.dragStep === undefined ? DRAG_STEP : opts.dragStep;
91
+
92
+ const stateFor = (id) => {
93
+ let st = state.get(id);
94
+ if (!st) { st = { events: [], lastX: -1, lastY: -1 }; state.set(id, st); }
95
+ return st;
96
+ };
97
+
98
+ // The instances of the list last synced, newest first — the reverse of paint
99
+ // order, so the TOPMOST element that wants the event gets it. Two effects
100
+ // that overlap are a stack, and a press belongs to the one on top.
101
+ let instances = [];
102
+
103
+ const fire = (x, y, what) => {
104
+ let hit = null;
105
+ for (let i = instances.length - 1; i >= 0; i -= 1) {
106
+ const inst = instances[i];
107
+ if (!triggerHas(inst, what)) continue;
108
+ if (!inBox(inst, x, y)) continue;
109
+ hit = inst;
110
+ break;
111
+ }
112
+ if (!hit) return null;
113
+ const st = stateFor(hit.id);
114
+ st.events.push([x, y, 0]);
115
+ if (st.events.length > MAX_EVENTS) st.events.shift();
116
+ st.lastX = x;
117
+ st.lastY = y;
118
+ return hit;
119
+ };
120
+
121
+ return {
122
+ /** The instances this driver is watching, for a host that wants to look. */
123
+ instances: () => instances,
124
+
125
+ /** Seconds since the driver was made. */
126
+ time: () => clock,
127
+
128
+ /** Take the list of the frame about to be drawn, advance every event by
129
+ * `dtMs`, and stamp what the painter reads onto each instance.
130
+ *
131
+ * IT MUTATES THE LIST, and that is deliberate: the instances are the
132
+ * painter's own objects, so a host that keeps a built frame across many
133
+ * draws sees the events move without rebuilding anything. Nothing in the
134
+ * tree changes — the clock is not in the document, which is what keeps a
135
+ * running effect off the layout's critical path entirely. */
136
+ tick(dtMs, list) {
137
+ const dt = (dtMs || 0) / 1000;
138
+ clock += dt;
139
+ for (const st of state.values()) {
140
+ if (!st.events.length) continue;
141
+ const kept = [];
142
+ for (const e of st.events) {
143
+ e[2] += dt;
144
+ if (e[2] < life) kept.push(e);
145
+ }
146
+ st.events = kept;
147
+ }
148
+ instances = (list && list.effects) || [];
149
+ for (const inst of instances) {
150
+ const st = state.get(inst.id);
151
+ inst.events = st ? st.events : [];
152
+ inst.time = clock;
153
+ }
154
+ return this.busy();
155
+ },
156
+
157
+ /** Is anything still moving? A page whose effects are all idle can stop
158
+ * asking for frames, which is the difference between an effect and a
159
+ * background process. */
160
+ busy() {
161
+ for (const inst of instances) {
162
+ if (inst.off) continue;
163
+ if (triggerHas(inst, "always")) return true;
164
+ const st = state.get(inst.id);
165
+ if (st && st.events.length) return true;
166
+ }
167
+ return false;
168
+ },
169
+
170
+ /** A press at a point in PAGE pixels. Returns the instance it went to, or
171
+ * null when it landed on nothing that wanted it — which is the ordinary
172
+ * case, and the whole difference from a page that ripples everywhere. */
173
+ press(x, y) { return fire(x, y, "press"); },
174
+
175
+ /** The pointer moving with the button down. One event per step rather than
176
+ * one that follows the pointer: a wake is a row of sources, and a single
177
+ * source that moves has no history. */
178
+ drag(x, y) {
179
+ for (let i = instances.length - 1; i >= 0; i -= 1) {
180
+ const inst = instances[i];
181
+ if (!triggerHas(inst, "drag")) continue;
182
+ if (!inBox(inst, x, y)) continue;
183
+ const st = stateFor(inst.id);
184
+ if (st.lastX < 0) { st.lastX = x; st.lastY = y; return null; }
185
+ const dx = x - st.lastX, dy = y - st.lastY;
186
+ if (dx * dx + dy * dy < step * step) return null;
187
+ return fire(x, y, "drag");
188
+ }
189
+ return null;
190
+ },
191
+
192
+ /** The pointer moving with no button down. */
193
+ hover(x, y) { return fire(x, y, "hover"); },
194
+
195
+ /** The button came up: the next drag starts a fresh trail rather than
196
+ * joining the last one. */
197
+ release() {
198
+ for (const st of state.values()) { st.lastX = -1; st.lastY = -1; }
199
+ },
200
+
201
+ /** Everything in flight, gone. For a host that navigated away from the
202
+ * page the events belonged to. */
203
+ clear() { state.clear(); },
204
+ };
205
+ }
206
+
207
+ /**
208
+ * The web host's three lines of it. `onChange` is called whenever something
209
+ * happened that the page has to draw — a host that is already drawing every
210
+ * frame can leave it out.
211
+ *
212
+ * The coordinates are `offsetX`/`offsetY`, which are CSS pixels relative to the
213
+ * canvas, which is the page space the display list is in. A canvas with a
214
+ * camera on it has to unproject them first, and then this is the wrong helper —
215
+ * pass the driver the page point yourself.
216
+ */
217
+ export function attachEffectPointer(canvas, driver, onChange) {
218
+ const poke = () => { if (onChange) onChange(); };
219
+ const down = (ev) => { if (driver.press(ev.offsetX, ev.offsetY)) poke(); };
220
+ const move = (ev) => {
221
+ const hit = ev.buttons ? driver.drag(ev.offsetX, ev.offsetY) : driver.hover(ev.offsetX, ev.offsetY);
222
+ if (hit) poke();
223
+ };
224
+ const up = () => driver.release();
225
+ canvas.addEventListener("pointerdown", down);
226
+ canvas.addEventListener("pointermove", move);
227
+ canvas.addEventListener("pointerup", up);
228
+ canvas.addEventListener("pointercancel", up);
229
+ canvas.addEventListener("pointerleave", up);
230
+ return () => {
231
+ canvas.removeEventListener("pointerdown", down);
232
+ canvas.removeEventListener("pointermove", move);
233
+ canvas.removeEventListener("pointerup", up);
234
+ canvas.removeEventListener("pointercancel", up);
235
+ canvas.removeEventListener("pointerleave", up);
236
+ };
237
+ }