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,198 @@
1
+ # EVG on the GPU
2
+
3
+ Running the CSS palette — flex, grid, the stylesheet layer, real TTF metrics
4
+ and kerning — on a GPU, without a browser doing the layout.
5
+
6
+ ```
7
+ npm run evg:displaylist -- page.tsx out.json -css sheet.css -theme editorial
8
+ ```
9
+
10
+ ## The seam
11
+
12
+ `EVGDisplayList` walks a laid-out tree once and emits flat draw commands:
13
+ filled rect, border, image quad, text run, push/pop clip. Absolute pixels,
14
+ colours resolved to 0–255 + alpha, no tree and no units left.
15
+
16
+ ```
17
+ JSX + CSS ──► EVG layout ──► display list ──┬─► WebGL 2 (evg-webgl.js)
18
+ ├─► SVG / DOM (../html/evg-html.js)
19
+ ├─► SDL2 + GL (C++ target)
20
+ ├─► PDF, PNG, HTML (existing)
21
+ └─► framebuffer (v2 game UI)
22
+ ```
23
+
24
+ The SVG backend is the second consumer of this seam and the evidence that the
25
+ seam is one: it is 500 lines, it shares no code with this file, and the two are
26
+ differenced pixel for pixel over the same frames — 0.022% on a sheet built to
27
+ exercise every command kind, 0.000% on every slide of the .pptx deck. See
28
+ [`gallery/pptx/web/html/`](../../../gallery/pptx/web/html/README.md), which is the slide
29
+ editor with this painter swapped out for that one and nothing else changed.
30
+
31
+ Everything above the seam is the code the PDF is made with. Everything below
32
+ knows only about quads, glyph runs and scissor rectangles.
33
+
34
+ This matters for more than tidiness: five painters already walk the tree
35
+ themselves, and each decides again what a box means. That is how border-radius
36
+ came to work in PDF and silently not in PNG — one painter read
37
+ `box.borderRadius`, another a stale `el.borderRadius` that nothing wrote. A
38
+ GPU backend should not become a sixth copy of that walk.
39
+
40
+ ## WebGL 2 — working
41
+
42
+ ```
43
+ python3 -m http.server 8000 --directory <repo root>
44
+ # open /lib/evg/gl/demo.html?list=boxmodel.json
45
+ ```
46
+
47
+ One quad per command, drawn instanced. Rounded corners and borders come from a
48
+ signed distance function in the fragment shader, so a rounded rect costs the
49
+ same two triangles as a square one and needs no tessellation:
50
+
51
+ ```glsl
52
+ sdRoundedBox(p, b, r) = length(max(|p| - b + r, 0)) + min(max(qx, qy), 0) - r
53
+ ```
54
+
55
+ Antialiasing is one `fwidth()` smoothstep. It is **clamped** — on a very large
56
+ quad the derivative jumps across the triangle strip's diagonal and drew a
57
+ visible hairline across the page.
58
+
59
+ Text: the list carries the run, the face and the size, and the position is
60
+ EVG's — measured from the same TTF the PDF is set in, kerning included. Only
61
+ the glyph *images* come from the platform. Here a 2D canvas rasterizes each run
62
+ into an atlas, one slot per run rather than per glyph, which keeps the run
63
+ intact and therefore keeps EVG's kerning exactly.
64
+
65
+ ## Images
66
+
67
+ `object-fit: cover` is the only fit the raster and PDF targets implement, and
68
+ it is done here the same way they do it — by cropping, not by squashing. The
69
+ crop is a UV rectangle computed from the source and box aspect ratios, so the
70
+ GPU samples only the covered region and the quad stays two triangles. A radius
71
+ on the element clips the photo through the same distance field the rectangles
72
+ use.
73
+
74
+ A photo needs its own texture bound, which a single instanced draw cannot do,
75
+ so the list is split into **runs**: consecutive quads that share the atlas are
76
+ one draw, and an image breaks the run and is drawn on its own. Runs read the
77
+ same instance buffers offset to their first instance, because WebGL 2 has no
78
+ base-instance parameter. That keeps paint order exactly as the list has it —
79
+ `boxmodel` is one run, `album` is six.
80
+
81
+ The published gallery copies the referenced photos next to the viewer and
82
+ rewrites the list to point at the copies. Without that the `src` was the path
83
+ the author wrote — relative to the page source — which resolves in the
84
+ repository and nowhere else, so the deployed viewer fetched four 404s and drew
85
+ a page with holes in it. The PDF and PNG targets never hit this: they read the
86
+ file at render time and embed the pixels.
87
+
88
+ Verified against `showcase/pages/boxmodel.tsx`: 20 commands, 15 rects with
89
+ their radii, one border, 4 text runs, rendering to the same geometry as the
90
+ PDF — and against `album`, whose four photos now draw.
91
+
92
+ Not done yet: a real clip stack. A single scissor rect is straightforward;
93
+ nested clips need a stack. It is marked in the source.
94
+
95
+ ## SDL2 + OpenGL — the portable half is ready
96
+
97
+ The display list module compiles to **C++, Rust and Go** as well as ES6, which
98
+ is the whole reason this shape was chosen — the same list can drive WebGL in a
99
+ browser and SDL2 on a Raspberry Pi or a Mac.
100
+
101
+ What is already in the repo:
102
+
103
+ - `EVGDisplayList` compiles cleanly to the C++ target (checked).
104
+ - `ranger_games/sprite_char_sdl.rgr` is an existing Ranger → C++ → SDL2
105
+ front-end with a real window and gamepad, so the platform pattern is
106
+ established rather than hypothetical.
107
+ - `imaging/raster/RasterText` already rasterizes glyph outlines — including
108
+ composite glyphs, so `ä`/`ö` work — which is the atlas source on a platform
109
+ with no canvas.
110
+
111
+ What is missing is the platform shim: create an SDL2 window with a GL context,
112
+ upload the same vertex data, and use the same shader source. The shaders are
113
+ plain GLSL ES 3.00 and need no change for desktop GL 3.3 beyond the `#version`
114
+ line.
115
+
116
+ The honest gap: none of that is compiled or run here, because this container
117
+ has neither SDL2 nor a GPU. Treat the C++ compile as evidence that the
118
+ portable half holds, not that the app runs.
119
+
120
+ ## The engine in a Worker
121
+
122
+ Everything above the seam is pure Ranger, so it can run in a Worker; only the
123
+ painter needs the page. [`evg-engine.js`](evg-engine.js) is the harness —
124
+ `serveEngine` in the worker, `connectEngine` on the page — and what crosses
125
+ back per frame is one of three replies: a new build as `toBinary()`'s typed
126
+ arrays, transferred, and read by [`evg-binary.js`](evg-binary.js) into the
127
+ same commands `evg-list.js` reads off the object; a kept list that only moved,
128
+ as the layers' shifts; or nothing. Input crosses the other way as the calls
129
+ the host already made, batched into the frame request, and the hit test runs
130
+ where the tree is. `gallery/realtrainer/web/main-worker.js` is a host written
131
+ that way; `?engine=worker` on the RealTrainer page runs it, and `rt:frame`
132
+ drives both hosts through one check. `npm run evg:binary:check` holds the two
133
+ readers against each other on real frames. PLAN_NATIVE_HOSTS.md S1.
134
+
135
+ ## Moving the view — [`evg-gestures.js`](evg-gestures.js)
136
+
137
+ > The gestures move a view; the *view* itself is still baked into the
138
+ > coordinates of every command, so a canvas rebuilds its whole list for every
139
+ > frame of a pan. It does not have to:
140
+ > [`../PLAN_VIEW_TRANSFORM.md`](../PLAN_VIEW_TRANSFORM.md) designs the camera
141
+ > as a uniform beside `uShift`, measured on the canvases in this repository.
142
+
143
+
144
+ Every page that draws a scene bigger than its canvas needs the same four
145
+ gestures, and every standalone had written its own: drag to pan, wheel to
146
+ zoom, a press that does not travel is a click — and, missing from all of
147
+ them, two fingers to pinch.
148
+
149
+ ```js
150
+ attachViewGestures(canvas, {
151
+ view: () => ({ x, y, sc }), // the pan and scale in force
152
+ setView: (x, y, sc) => { … }, // schedule this one — it does not paint
153
+ onTap: (clientX, clientY) => { … },
154
+ });
155
+ ```
156
+
157
+ It never touches the scene: it reads the view the host keeps and hands back
158
+ another, so the host goes on deciding what a view is and when to paint one.
159
+
160
+ The anchor is what makes a zoom feel like a zoom — the point under the
161
+ cursor, or under the midpoint of two fingers, stays where it is. Keeping the
162
+ scene point `(screen - pan) / scale` where it was gives
163
+ `pan' = screen - (screen - pan) * (scale' / scale)`, which is also what moves
164
+ the picture when the fingers travel: a pinch pans and zooms in one expression
165
+ rather than two that fight.
166
+
167
+ A trackpad pinch is not a touch — browsers report it as a wheel with `ctrl`
168
+ held, a fraction of a notch at a time, so it gets its own rate or it crawls
169
+ where a wheel flies; Safari's own `gesture*` events are read too. A finger
170
+ lifted out of a pinch leaves the other one panning rather than stopping the
171
+ picture until both are lifted. `npm run evg:gestures:check` drives all of it
172
+ against a canvas that is not one — 19 checks, including the pinch no headless
173
+ driver will send.
174
+
175
+ ## Nobody can read it — so there is a second list
176
+
177
+ A GPU frame is invisible to a screen reader: NVDA asks the platform for a tree
178
+ of roles and names, and this file produces quads. So the app emits a second list
179
+ beside the display list — `EVGA11yTree`, meaning where this one carries geometry
180
+ — and `evg-a11y.js` mirrors it as real DOM over the canvas, positioned at the
181
+ rectangles that were painted.
182
+
183
+ ```
184
+ GridApp ─┬─ sceneJson() ─► evg-webgl.js ─► pixels
185
+ └─ a11yJson() ─► evg-a11y.js ─► DOM ─► VoiceOver / NVDA / Orca
186
+ ```
187
+
188
+ The canvas becomes `aria-hidden`. The sheet is a `role="grid"` that claims all
189
+ ten thousand rows and emits the forty on screen; the caret cell is the one tab
190
+ stop in the application; a dialog is modal and hides the sheet behind it. The
191
+ mirror never tells the app where focus is — the app tells the mirror, or the two
192
+ chase each other.
193
+
194
+ Checked in a real browser by `npm run datagrid:web:test`, which asks the DOM
195
+ what a reader would be handed. Turn it off with `?a11y=0`.
196
+
197
+ Design, state and how to try it with VoiceOver:
198
+ [../PLAN_ACCESSIBILITY.md](../PLAN_ACCESSIBILITY.md).
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MIT
3
+ //
4
+ // The accessibility mirror may not paint.
5
+ //
6
+ // node lib/evg/gl/a11y-paint-check.mjs
7
+ //
8
+ // The mirror is real DOM over the canvas — that is the whole point of it, and
9
+ // it is why a reader can find a button EVG drew. The canvas underneath has
10
+ // already drawn every one of those nodes, so a mirror node that puts one
11
+ // pixel on the screen is a SECOND COPY OF THE PAGE. And because the mirror is
12
+ // rebuilt on its own schedule rather than once a frame, that copy is at an
13
+ // older scroll offset: two pages superimposed, sliding over each other.
14
+ //
15
+ // `color: transparent` reads as enough and is not. The mirror makes NATIVE
16
+ // controls on purpose — a real <button> and a real <input>, because that is
17
+ // what readers understand and what the text bridge edits in — and a native
18
+ // control on iOS paints through `-webkit-text-fill-color` and
19
+ // `-webkit-appearance`, neither of which `color` reaches.
20
+ //
21
+ // So this is a list, and the list is the point: every way a user agent can
22
+ // decide to draw something has to be turned off by name. It runs without a
23
+ // DOM — `styleBase` only ever writes to `el.style`, so a plain object is a
24
+ // good enough element for the question being asked.
25
+
26
+ import { styleBase } from "./evg-a11y.js";
27
+
28
+ const MUST_BE = {
29
+ background: "transparent",
30
+ color: "transparent",
31
+ webkitTextFillColor: "transparent",
32
+ webkitAppearance: "none",
33
+ appearance: "none",
34
+ caretColor: "transparent",
35
+ textShadow: "none",
36
+ boxShadow: "none",
37
+ border: "0",
38
+ outline: "none",
39
+ overflow: "hidden",
40
+ pointerEvents: "none",
41
+ userSelect: "none",
42
+ // NOT A PAINTING RULE, and here anyway because it lives in the same style
43
+ // block and is the same kind of invisible-until-a-phone mistake: iOS Safari
44
+ // zooms the whole page in when it focuses a text field computing to under
45
+ // 16px, and the mirror's `<input>` is what a tap on a drawn field focuses.
46
+ // It was 10px, which is small enough to trigger it and invisible enough
47
+ // that nothing on a desktop ever said so.
48
+ fontSize: "16px",
49
+ };
50
+
51
+ const el = { style: {} };
52
+ styleBase(el);
53
+
54
+ let failed = 0;
55
+ for (const [prop, want] of Object.entries(MUST_BE)) {
56
+ const got = el.style[prop];
57
+ if (got === want) console.log(" PASS " + prop + ": " + want);
58
+ else {
59
+ failed += 1;
60
+ console.log(" FAIL " + prop + " is " + JSON.stringify(got) + ", want " + JSON.stringify(want));
61
+ }
62
+ }
63
+
64
+ console.log("");
65
+ if (failed > 0) {
66
+ console.log(` ${failed} way(s) the mirror could still paint, or zoom a phone`);
67
+ process.exit(1);
68
+ }
69
+ console.log(` ${Object.keys(MUST_BE).length} ways it cannot paint, or make a phone zoom`);
70
+ // The marker `scripts/run-gallery-editor-tests.sh` greps for. The compiler
71
+ // prints `[FAIL]` and still exits 0, so that runner refuses to take a zero
72
+ // exit as a pass — a suite has to SAY it passed.
73
+ console.log("ALL PASS");
@@ -0,0 +1,399 @@
1
+ /**
2
+ * The painter's backdrop blur, checked in a real GPU context against the
3
+ * browser's own numbers.
4
+ *
5
+ * node lib/evg/gl/blur-check.mjs [--write-png out.png]
6
+ *
7
+ * `oracle/css_blur_oracle.mjs` measured what Chrome does with
8
+ * `backdrop-filter: blur()`. This draws the same picture through the WebGL
9
+ * painter and reads the same pixels back, so the two can be compared where it
10
+ * counts — in the framebuffer, not in a comment.
11
+ *
12
+ * Three claims, and each one fails differently:
13
+ *
14
+ * THE PROFILE. A hard black/white edge behind a blurred pane. The ramp
15
+ * across it is the kernel's step response, and it is compared sample by
16
+ * sample against what the browser produced. A true Gaussian instead of the
17
+ * three-box approximation is out by up to 14 luminance levels here — small
18
+ * enough to look fine and large enough to be the wrong filter.
19
+ *
20
+ * THE EDGE. A flat grey behind the pane must come out flat: same value at
21
+ * the pane's border as at its centre. An implementation that blurs only the
22
+ * pixels inside the box gets a pale rim, because its samples fall off the
23
+ * edge of what it copied. This is the case that catches the mistake nobody
24
+ * notices until they put a scrim over a photograph.
25
+ *
26
+ * THE CLIP. Outside a rounded corner, the backdrop is untouched.
27
+ *
28
+ * Exit code 0 when every probe is within tolerance.
29
+ */
30
+
31
+ import fs from "node:fs";
32
+ import path from "node:path";
33
+ import { fileURLToPath, pathToFileURL } from "node:url";
34
+ import { requireDom, findChromium, assertDomInstalled } from "../../../gallery/ui/conformance/dom-adapter.mjs";
35
+
36
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
37
+ const ORACLE = JSON.parse(fs.readFileSync(path.join(HERE, "..", "oracle", "css-blur.json"), "utf8"));
38
+
39
+ const W = 400;
40
+ const H = 200;
41
+ // The same geometry the oracle used: a 400x200 backdrop, a 200x120 pane at
42
+ // (100,40). Same numbers on both sides or the comparison is not one.
43
+ const PANE = { x: 100, y: 40, w: 200, h: 120 };
44
+
45
+ /** The three scenes, matching the oracle's cases. */
46
+ const SCENES = {
47
+ // Black left of x=200, white right of it, under a pane that blurs and
48
+ // paints nothing of its own.
49
+ edge: (blur) => ({
50
+ width: W, height: H,
51
+ cmds: [
52
+ { k: 0, x: 0, y: 0, w: 200, h: H, c: [0, 0, 0, 1] },
53
+ { k: 0, x: 200, y: 0, w: 200, h: H, c: [255, 255, 255, 1] },
54
+ { k: 0, ...PANE, c: [0, 0, 0, 0], bb: blur },
55
+ ],
56
+ }),
57
+ // Flat mid grey everywhere behind.
58
+ uniform: (blur) => ({
59
+ width: W, height: H,
60
+ cmds: [
61
+ { k: 0, x: 0, y: 0, w: W, h: H, c: [128, 128, 128, 1] },
62
+ { k: 0, ...PANE, c: [0, 0, 0, 0], bb: blur },
63
+ ],
64
+ }),
65
+ // White page, a black stripe starting EXACTLY at the pane's left edge. This
66
+ // is the case that decides whether the backdrop is the element's own region
67
+ // or the page around it, and cases that do not straddle the border cannot
68
+ // tell — which is how this implementation shipped the wrong rule for an
69
+ // afternoon and passed every check it had.
70
+ acrossTheEdge: (blur) => ({
71
+ width: W, height: H,
72
+ cmds: [
73
+ { k: 0, x: 0, y: 0, w: W, h: H, c: [255, 255, 255, 1] },
74
+ // From x=99, one pixel left of the pane, so this rectangle's own
75
+ // antialiased edge falls OUTSIDE the region the blur samples. EVG
76
+ // softens a rectangle's border by about a third of a pixel and a
77
+ // browser's div does not; with the two edges on top of each other that
78
+ // seam blurs inward and reads as a bleed this is meant to be measuring.
79
+ { k: 0, x: 99, y: 0, w: 101, h: H, c: [0, 0, 0, 1] },
80
+ { k: 0, ...PANE, r: 40, c: [0, 0, 0, 0], bb: blur },
81
+ ],
82
+ }),
83
+ // A hard band across the top of a rounded pane, over white. This is the only
84
+ // scene where the blur ITSELF can be caught being drawn square: everywhere
85
+ // else the backdrop near the corner is uniform, and blurred uniform is
86
+ // uniform, so a square clip and a rounded one are the same picture.
87
+ banded: (blur) => ({
88
+ width: W, height: H,
89
+ cmds: [
90
+ { k: 0, x: 0, y: 0, w: W, h: H, c: [255, 255, 255, 1] },
91
+ { k: 0, x: 0, y: 39, w: W, h: 31, c: [0, 0, 0, 1] },
92
+ { k: 0, ...PANE, r: 40, c: [0, 0, 0, 0], bb: blur },
93
+ ],
94
+ }),
95
+ // A narrow black column down the pane's left side. The banded scene cannot
96
+ // see a blur drawn square, because near the top-left corner everything in it
97
+ // is black either way; turning the feature ninety degrees means the raw
98
+ // backdrop outside the corner is black while the blurred one is nearly
99
+ // white, and the two cannot be confused.
100
+ verticalColumn: (blur) => ({
101
+ width: W, height: H,
102
+ cmds: [
103
+ { k: 0, x: 0, y: 0, w: W, h: H, c: [255, 255, 255, 1] },
104
+ { k: 0, x: 95, y: 0, w: 14, h: H, c: [0, 0, 0, 1] },
105
+ { k: 0, ...PANE, r: 40, c: [0, 0, 0, 0], bb: blur },
106
+ ],
107
+ }),
108
+ // The same column, moved fifty pixels clear of the nearest border. Two edges
109
+ // close together is the case a kernel is easiest to get wrong on, and with
110
+ // no border within reach it is the kernel alone being compared.
111
+ innerColumn: (blur) => ({
112
+ width: W, height: H,
113
+ cmds: [
114
+ { k: 0, x: 0, y: 0, w: W, h: H, c: [255, 255, 255, 1] },
115
+ { k: 0, x: 150, y: 0, w: 14, h: H, c: [0, 0, 0, 1] },
116
+ { k: 0, ...PANE, c: [0, 0, 0, 0], bb: blur },
117
+ ],
118
+ }),
119
+ // The same small pane, alone and after a bigger one — the pair for case 6.
120
+ //
121
+ // The painter keeps its blur targets between draws and grows them without
122
+ // shrinking, so a second, smaller pane reads out of a texture larger than
123
+ // its own region, with whatever the first pane left in the rest of it right
124
+ // there to be sampled. Every other scene here renders one pane into a fresh
125
+ // context and cannot see that.
126
+ //
127
+ // The two panes must NOT overlap. The first version put a big pane over the
128
+ // same band and compared the result with the small pane alone, and the
129
+ // difference it found — 12 against 189 — was not a bug at all: the big pane
130
+ // had already softened that backdrop, so the small one was blurring an
131
+ // already-blurred band. Correct behaviour, wrong reference.
132
+ smallAlone: (blur) => ({
133
+ width: W, height: H,
134
+ cmds: [
135
+ { k: 0, x: 0, y: 0, w: W, h: H, c: [255, 255, 255, 1] },
136
+ { k: 0, x: 0, y: 39, w: W, h: 31, c: [0, 0, 0, 1] },
137
+ { k: 0, x: 200, y: 30, w: 150, h: 120, c: [0, 0, 0, 0], bb: blur },
138
+ ],
139
+ }),
140
+ smallAfterBig: (blur) => ({
141
+ width: W, height: H,
142
+ cmds: [
143
+ { k: 0, x: 0, y: 0, w: W, h: H, c: [255, 255, 255, 1] },
144
+ { k: 0, x: 0, y: 39, w: W, h: 31, c: [0, 0, 0, 1] },
145
+ // Its own dark patch, well clear of the small pane, so anything left
146
+ // behind in the reused texture is distinctive rather than the same white
147
+ // the small pane would see anyway.
148
+ { k: 0, x: 10, y: 100, w: 170, h: 80, c: [20, 20, 20, 1] },
149
+ // 180 wide by 90 tall: wider than the small pane and shorter, so the
150
+ // shared texture ends up bigger than the small pane's region on one axis
151
+ // and the leftovers are inside it.
152
+ { k: 0, x: 5, y: 95, w: 180, h: 90, c: [0, 0, 0, 0], bb: blur },
153
+ { k: 0, x: 200, y: 30, w: 150, h: 120, c: [0, 0, 0, 0], bb: blur },
154
+ ],
155
+ }),
156
+ // A rounded pane with a translucent white fill over black.
157
+ rounded: (blur) => ({
158
+ width: W, height: H,
159
+ cmds: [
160
+ { k: 0, x: 0, y: 0, w: W, h: H, c: [0, 0, 0, 1] },
161
+ { k: 0, ...PANE, r: 40, c: [255, 255, 255, 0.85], bb: blur },
162
+ ],
163
+ }),
164
+ };
165
+
166
+ const painter = fs.readFileSync(path.join(HERE, "evg-webgl.js"), "utf8").replace(/^export /gm, "");
167
+
168
+ const pageFor = (scene) => `<!doctype html><meta charset="utf-8"><body style="margin:0;background:#fff">
169
+ <canvas id="c" width="${W}" height="${H}" style="width:${W}px;height:${H}px"></canvas>
170
+ <script>
171
+ ${painter}
172
+ (async () => {
173
+ try {
174
+ const c = document.getElementById("c");
175
+ const gl = c.getContext("webgl2", { antialias: true, preserveDrawingBuffer: true });
176
+ const doc = ${JSON.stringify(scene)};
177
+ window.__stats = renderDisplayList(gl, { list: { cmds: doc.cmds }, width: doc.width, height: doc.height }, { dpr: 1 });
178
+ } catch (e) { window.__ERR__ = String(e && e.stack || e); }
179
+ window.__DONE__ = true;
180
+ })();
181
+ </script></body>`;
182
+
183
+ assertDomInstalled();
184
+ const { chromium } = requireDom("playwright-core");
185
+ // SwiftShader, for the same reason the rotation check uses it: a check that
186
+ // only runs on a machine with a GPU is not a check.
187
+ const browser = await chromium.launch({
188
+ executablePath: findChromium(),
189
+ args: ["--use-gl=swiftshader", "--enable-unsafe-swiftshader"],
190
+ });
191
+ const page = await browser.newPage({ viewport: { width: W + 40, height: H + 40 } });
192
+ const pageErrors = [];
193
+ page.on("pageerror", (e) => pageErrors.push(String(e.message)));
194
+
195
+ const tmp = path.join(HERE, ".blur-check.html");
196
+
197
+ /** Render one scene and hand back a row of RGBA readings. */
198
+ async function rowOf(scene, y, xs) {
199
+ fs.writeFileSync(tmp, pageFor(scene));
200
+ await page.goto(pathToFileURL(tmp).href);
201
+ await page.waitForFunction("window.__DONE__ === true", { timeout: 30000 });
202
+ const err = await page.evaluate(() => window.__ERR__ || null);
203
+ if (err) throw new Error("painter threw: " + err);
204
+ const write = process.argv.indexOf("--write-png");
205
+ if (write > -1 && process.argv[write + 1]) {
206
+ await page.locator("#c").screenshot({ path: process.argv[write + 1] });
207
+ }
208
+ return page.evaluate(([yy, xxs, hh]) => {
209
+ const gl = document.getElementById("c").getContext("webgl2");
210
+ const px = new Uint8Array(4);
211
+ return xxs.map((x) => {
212
+ // GL reads from the bottom; the display list counts from the top.
213
+ gl.readPixels(x, hh - 1 - yy, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, px);
214
+ return [px[0], px[1], px[2], px[3]];
215
+ });
216
+ }, [y, xs, H]);
217
+ }
218
+
219
+ const lum = (p) => Math.round(0.2126 * p[0] + 0.7152 * p[1] + 0.0722 * p[2]);
220
+ let bad = 0, checked = 0;
221
+ const say = (ok, name, detail) => {
222
+ checked++;
223
+ if (ok) { console.log(" PASS " + name); return; }
224
+ bad++;
225
+ console.log(" FAIL " + name + "\n " + detail);
226
+ };
227
+
228
+ console.log("=== the painter's backdrop blur, against the browser's own numbers ===");
229
+
230
+ // --- 1. the profile, at each radius the oracle measured ---------------------
231
+ // Tolerance of 6 luminance levels out of 255. Wide enough for SwiftShader's
232
+ // rounding and a slightly different sample grid; far tighter than the 14 that
233
+ // separates the three-box kernel from a true Gaussian, which is the mistake
234
+ // this is here to catch.
235
+ const TOL = 6;
236
+ console.log("");
237
+ console.log("-- the ramp across a hard edge");
238
+ for (const [label, v] of Object.entries(ORACLE.stepResponse)) {
239
+ const r = parseInt(label.match(/\d+/)[0], 10);
240
+ // Sample inside the pane only: the pane spans x 100..300 and the edge is at
241
+ // x=200, so +-40 is comfortably inside it.
242
+ const xs = [];
243
+ for (let x = v.xFrom; x < v.xTo; x++) xs.push(200 + x);
244
+ const row = await rowOf(SCENES.edge(r), 100, xs);
245
+ let worst = 0, worstAt = 0;
246
+ for (let i = 0; i < xs.length; i++) {
247
+ const d = Math.abs(lum(row[i]) - v.luma[i]);
248
+ if (d > worst) { worst = d; worstAt = v.xFrom + i; }
249
+ }
250
+ say(worst <= TOL, `${label}: the ramp matches the browser's (worst ${worst})`,
251
+ `worst error ${worst} luminance levels at x=${worstAt}, tolerance ${TOL}`);
252
+ }
253
+
254
+ // --- 2. the element's own edge over a flat backdrop --------------------------
255
+ console.log("");
256
+ console.log("-- a flat backdrop stays flat to the pane's edge");
257
+ {
258
+ const want = ORACLE.uniformBackdrop.centre;
259
+ const xs = ORACLE.uniformBackdrop.insideLeft.map((s) => 100 + s.dx);
260
+ const row = await rowOf(SCENES.uniform(12), 100, xs);
261
+ const got = row.map(lum);
262
+ const worst = Math.max(...got.map((g) => Math.abs(g - want)));
263
+ say(worst <= TOL, `no rim at the edge (worst ${worst})`,
264
+ `wanted ${want} everywhere, got ${got.join(" ")} — a ramp here means the ` +
265
+ `blur sampled only inside the box`);
266
+ }
267
+
268
+ // --- 3. the rounded corner ---------------------------------------------------
269
+ console.log("");
270
+ console.log("-- the blur is clipped to the rounded box");
271
+ {
272
+ const row = await rowOf(SCENES.rounded(12), 44, [104, 140]);
273
+ const outside = lum(row[0]);
274
+ const inside = lum(row[1]);
275
+ const wantOutside = lum(ORACLE.rounded.outsideTheCorner);
276
+ say(Math.abs(outside - wantOutside) <= TOL,
277
+ `outside the corner the backdrop is untouched (${outside})`,
278
+ `wanted ${wantOutside}, got ${outside}`);
279
+ const rowIn = await rowOf(SCENES.rounded(12), 80, [140]);
280
+ const wantInside = lum(ORACLE.rounded.insideTheCorner);
281
+ say(Math.abs(lum(rowIn[0]) - wantInside) <= TOL,
282
+ `and inside it the fill is over the blur (${lum(rowIn[0])})`,
283
+ `wanted ${wantInside}, got ${lum(rowIn[0])}`);
284
+ }
285
+
286
+ // --- 4. the case the others are blind to -------------------------------------
287
+ console.log("");
288
+ console.log("-- a feature that straddles the pane's own border");
289
+ {
290
+ const want = ORACLE.acrossTheEdge;
291
+ const xs = [102, 105, 110, 120];
292
+ const row = await rowOf(SCENES.acrossTheEdge(12), 100, xs);
293
+ const got = row.map(lum);
294
+ const wantL = want.insideEdge.map(lum);
295
+ const worst = Math.max(...got.map((g, i) => Math.abs(g - wantL[i])));
296
+ say(worst <= TOL,
297
+ `the white outside the pane does not bleed in (${got.join(" ")})`,
298
+ `wanted ${wantL.join(" ")}, got ${got.join(" ")} — anything much above ` +
299
+ `zero here means the copied region reached past the element, and the page ` +
300
+ `behind it is showing through its own border`);
301
+
302
+ // Outside the rounded corner the backdrop is untouched black. A blur drawn
303
+ // as a square rather than a rounded box lightens this pixel, because it
304
+ // carries the white from x < 100 across the corner.
305
+ const corner = await rowOf(SCENES.acrossTheEdge(12), 43, [103]);
306
+ const wantCorner = lum(want.outsideCorner);
307
+ say(Math.abs(lum(corner[0]) - wantCorner) <= TOL,
308
+ `and the corner outside the radius is untouched (${lum(corner[0])})`,
309
+ `wanted ${wantCorner}, got ${lum(corner[0])} — a square clip shows here`);
310
+ }
311
+
312
+ // --- 5. the blur itself, clipped to the radius -------------------------------
313
+ console.log("");
314
+ console.log("-- the BLUR, not just the fill, respects the radius");
315
+ {
316
+ const want = ORACLE.bandedCorner;
317
+ const row = await rowOf(SCENES.banded(12), 43, [103]);
318
+ const got = lum(row[0]);
319
+ say(Math.abs(got - lum(want.outsideCorner)) <= TOL,
320
+ `outside the corner the band is still hard (${got})`,
321
+ `wanted ${lum(want.outsideCorner)}, got ${got} — grey here means the ` +
322
+ `blurred backdrop was drawn as a rectangle and spilled past the radius`);
323
+ const inBand = await rowOf(SCENES.banded(12), 50, [200]);
324
+ say(Math.abs(lum(inBand[0]) - lum(want.insideBand)) <= TOL,
325
+ `and inside the shape the band is softened (${lum(inBand[0])})`,
326
+ `wanted ${lum(want.insideBand)}, got ${lum(inBand[0])}`);
327
+ }
328
+
329
+ // --- 5b. and the radius, where the two answers are far apart -----------------
330
+ console.log("");
331
+ console.log("-- outside the corner, the raw backdrop and the blurred one differ");
332
+ {
333
+ const want = ORACLE.verticalCorner;
334
+ const outside = lum((await rowOf(SCENES.verticalColumn(12), 43, [104]))[0]);
335
+ const inside = lum((await rowOf(SCENES.verticalColumn(12), 100, [104]))[0]);
336
+ say(Math.abs(outside - lum(want.outsideCorner)) <= TOL,
337
+ `outside the radius the column is still hard (${outside})`,
338
+ `wanted ${lum(want.outsideCorner)}, got ${outside} — the blurred value at ` +
339
+ `this x is ${lum(want.wellInside)}, so anything near that means the blur ` +
340
+ `was drawn as a rectangle`);
341
+ // Deliberately NOT compared with the browser: this x is four pixels from the
342
+ // pane's border, and inside a kernel's reach of it Chrome stops producing a
343
+ // blur profile at all — see `borderBand` in the oracle. What is checked here
344
+ // is only that the blur HAPPENED, which is what makes the probe above mean
345
+ // something: raw black outside the corner, softened inside it.
346
+ say(inside > 40,
347
+ `and at the same x inside the shape it is softened (${inside})`,
348
+ `expected the column to have blurred away from black here, got ${inside}`);
349
+ }
350
+
351
+ // --- 5c. the kernel again, on a narrow feature clear of every border ---------
352
+ // The step response is one edge. This is two, close together, which is where a
353
+ // kernel that is nearly right stops being right — and it is 50px from the
354
+ // nearest border, so nothing about the element's edge is mixed in.
355
+ console.log("");
356
+ console.log("-- a narrow column, well clear of the borders");
357
+ {
358
+ const want = ORACLE.innerColumn;
359
+ const row = (await rowOf(SCENES.innerColumn(12), 100, want.xs)).map(lum);
360
+ let worst = 0, at = 0;
361
+ for (let i = 0; i < row.length; i++) {
362
+ const d = Math.abs(row[i] - want.luma[i]);
363
+ if (d > worst) { worst = d; at = want.xs[i]; }
364
+ }
365
+ say(worst <= TOL, `the profile matches the browser's (worst ${worst})`,
366
+ `worst ${worst} levels at x=${at}\n browser ${want.luma.join(" ")}` +
367
+ `\n painter ${row.join(" ")}`);
368
+ }
369
+
370
+ // --- 6. a second pane must not read the first one's leftovers ----------------
371
+ // Not against the browser: against the SAME pane drawn alone. The claim is
372
+ // that a pane looks the same whether or not a bigger one was blurred before it
373
+ // in the frame, and the reference for that is the pane on its own.
374
+ console.log("");
375
+ console.log("-- a pane looks the same after a bigger one has been blurred");
376
+ {
377
+ const xs = [205, 240, 275, 310, 345];
378
+ const alone = (await rowOf(SCENES.smallAlone(12), 55, xs)).map(lum);
379
+ const after = (await rowOf(SCENES.smallAfterBig(12), 55, xs)).map(lum);
380
+ const worst = Math.max(...alone.map((a, i) => Math.abs(a - after[i])));
381
+ say(worst <= TOL,
382
+ `the second pane is unchanged by the first (worst ${worst})`,
383
+ `alone ${alone.join(" ")} vs after ${after.join(" ")} — a difference here ` +
384
+ `means the blur passes sampled outside their own region and found what the ` +
385
+ `bigger pane left in the reused texture`);
386
+ }
387
+
388
+ await browser.close();
389
+ fs.rmSync(tmp, { force: true });
390
+ if (pageErrors.length) {
391
+ console.log("");
392
+ console.log("page errors:");
393
+ for (const e of pageErrors) console.log(" " + e);
394
+ bad += pageErrors.length;
395
+ }
396
+ console.log("");
397
+ console.log(`passed=${checked - bad} failed=${bad}`);
398
+ if (bad > 0) { console.log("FAILURES"); process.exit(1); }
399
+ console.log("ALL PASS");
@@ -0,0 +1 @@
1
+ {"width":595.00,"height":842.00,"list":{"cmds":[{"k":0,"x":0.00,"y":0.00,"w":595.00,"h":842.00,"c":[251,247,240,1.00]},{"k":3,"x":44.00,"y":44.00,"w":258.74,"h":40.80,"c":[59,47,42,1.00],"text":"Laatikkomalli","font":"Cinzel","size":34.00},{"k":0,"x":44.00,"y":98.80,"w":88.00,"h":3.00,"c":[176,141,87,1.00]},{"k":3,"x":44.00,"y":127.80,"w":76.17,"h":15.60,"c":[138,115,96,1.00],"text":"Täyte ja reunus","font":"Josefin Sans","size":13.00},{"k":0,"x":44.00,"y":151.40,"w":159.67,"h":90.00,"r":6.00,"c":[232,220,200,1.00]},{"k":0,"x":58.00,"y":165.40,"w":131.67,"h":22.00,"r":3.00,"c":[176,141,87,1.00]},{"k":0,"x":217.67,"y":151.40,"w":159.67,"h":90.00,"r":6.00,"c":[232,220,200,1.00]},{"k":0,"x":225.67,"y":155.40,"w":131.67,"h":22.00,"r":3.00,"c":[176,141,87,1.00]},{"k":0,"x":391.33,"y":151.40,"w":159.67,"h":90.00,"r":6.00,"c":[232,220,200,1.00]},{"k":0,"x":404.33,"y":164.40,"w":133.67,"h":22.00,"r":3.00,"c":[176,141,87,1.00]},{"k":3,"x":44.00,"y":267.40,"w":95.23,"h":15.60,"c":[138,115,96,1.00],"text":"Sisäkkäiset laatikot","font":"Josefin Sans","size":13.00},{"k":0,"x":44.00,"y":299.00,"w":507.00,"h":120.00,"r":6.00,"c":[239,230,214,1.00]},{"k":0,"x":56.00,"y":319.00,"w":483.00,"h":88.00,"r":6.00,"c":[229,216,194,1.00]},{"k":0,"x":68.00,"y":339.00,"w":459.00,"h":56.00,"r":6.00,"c":[216,199,171,1.00]},{"k":0,"x":80.00,"y":359.00,"w":435.00,"h":24.00,"r":6.00,"c":[176,141,87,1.00]},{"k":3,"x":44.00,"y":445.00,"w":103.09,"h":15.60,"c":[138,115,96,1.00],"text":"Marginaalit erikseen","font":"Josefin Sans","size":13.00},{"k":0,"x":44.00,"y":468.60,"w":507.00,"h":90.00,"r":6.00,"c":[232,220,200,1.00]},{"k":0,"x":58.00,"y":482.60,"w":479.00,"h":21.00,"r":3.00,"c":[176,141,87,1.00]},{"k":0,"x":82.00,"y":523.60,"w":383.20,"h":21.00,"r":3.00,"c":[176,141,87,1.00]}]}}