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,529 @@
1
+ /**
2
+ * The browser, asked what `backdrop-filter: blur()` actually does to pixels.
3
+ *
4
+ * node lib/evg/oracle/css_blur_oracle.mjs
5
+ *
6
+ * Writes `css-blur.json` beside this file. Nothing in EVG reads it at run
7
+ * time; the WebGL painter is written against these numbers and
8
+ * `EVGBlurTest.rgr` checks the parts that can be checked without a GPU.
9
+ *
10
+ * WHY MEASURE AT ALL. Everyone "knows" CSS blur is a Gaussian, and the filter
11
+ * spec even says `blur(v)` is a Gaussian with standard deviation `v`. Both
12
+ * halves of that are traps:
13
+ *
14
+ * 1. A Gaussian with sigma = v is not what browsers compute. The spec allows
15
+ * the classic THREE-BOX approximation from SVG, and that is what every
16
+ * engine ships — so the profile is very slightly different from a true
17
+ * Gaussian, and the box widths follow a formula worth copying exactly
18
+ * rather than reinventing.
19
+ * 2. `backdrop-filter` is not `filter`. It samples what is BEHIND the
20
+ * element, and the interesting question is what happens at the element's
21
+ * own edge: a naive implementation blurs only the pixels inside the box,
22
+ * so the edge fades toward whatever is outside the sample — usually
23
+ * transparent — and the result has a dark or pale rim a real one does
24
+ * not. Question 3 below is that rim, measured.
25
+ *
26
+ * Five questions:
27
+ * 1. The PROFILE. A hard black/white edge behind a blurred pane: what does
28
+ * the ramp across it look like, sample by sample? That is the kernel,
29
+ * read off the screen.
30
+ * 2. The RADIUS. Does the ramp scale linearly with the radius?
31
+ * 3. The EDGE. A uniform colour behind the pane: is the pane's own border
32
+ * the same colour all the way to its edge, or does it fall off?
33
+ * 4. COMPOSITING. A translucent background-color on the blurring element —
34
+ * is it painted over the blurred backdrop, and with what?
35
+ * 5. ROUNDING. Does the blur respect the element's border-radius?
36
+ */
37
+
38
+ import fs from "node:fs";
39
+ import path from "node:path";
40
+ import { fileURLToPath, pathToFileURL } from "node:url";
41
+ import { requireDom, findChromium, assertDomInstalled } from "../../../gallery/ui/conformance/dom-adapter.mjs";
42
+
43
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
44
+
45
+ // A page is one 400x200 backdrop with a 200x120 pane centred over it. Every
46
+ // case changes only the backdrop's paint and the pane's filter, so a
47
+ // difference in the pixels is a difference in the thing being asked about.
48
+ const page = (backdrop, paneStyle) => `<!doctype html><meta charset="utf-8">
49
+ <style>
50
+ html,body{margin:0;padding:0;background:#fff}
51
+ #stage{position:relative;width:400px;height:200px;overflow:hidden}
52
+ #back{position:absolute;inset:0;${backdrop}}
53
+ #pane{position:absolute;left:100px;top:40px;width:200px;height:120px;${paneStyle}}
54
+ </style>
55
+ <div id="stage"><div id="back"></div><div id="pane"></div></div>`;
56
+
57
+ assertDomInstalled();
58
+ const { chromium } = requireDom("playwright-core");
59
+ const browser = await chromium.launch({ executablePath: findChromium() });
60
+ const tab = await browser.newPage({ viewport: { width: 420, height: 220 } });
61
+
62
+ const file = path.join(HERE, ".blur-probe.html");
63
+
64
+ /** One case: render it, screenshot the stage, hand back a pixel reader. */
65
+ async function shoot(backdrop, paneStyle) {
66
+ fs.writeFileSync(file, page(backdrop, paneStyle));
67
+ await tab.goto(pathToFileURL(file).href);
68
+ await tab.evaluate(() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r))));
69
+ const buf = await tab.locator("#stage").screenshot();
70
+ // Decode the PNG with the page itself — no image library, and the browser
71
+ // is right there.
72
+ return tab.evaluate(async (b64) => {
73
+ const img = new Image();
74
+ img.src = "data:image/png;base64," + b64;
75
+ await img.decode();
76
+ const c = document.createElement("canvas");
77
+ c.width = img.width; c.height = img.height;
78
+ const g = c.getContext("2d", { willReadFrequently: true });
79
+ g.drawImage(img, 0, 0);
80
+ const d = g.getImageData(0, 0, c.width, c.height).data;
81
+ const at = (x, y) => {
82
+ const i = (y * c.width + x) * 4;
83
+ return [d[i], d[i + 1], d[i + 2], d[i + 3]];
84
+ };
85
+ return { w: c.width, h: c.height, px: [...Array(c.height)].map((_, y) =>
86
+ [...Array(c.width)].map((_, x) => at(x, y))) };
87
+ }, buf.toString("base64"));
88
+ }
89
+
90
+ const lum = (p) => Math.round(0.2126 * p[0] + 0.7152 * p[1] + 0.0722 * p[2]);
91
+ const out = {};
92
+
93
+ // --- 1 & 2. the profile across a hard edge, at three radii ------------------
94
+ // The backdrop is black on the left of x=200 and white on the right. Reading
95
+ // row 100 across that boundary gives the kernel's step response, which is its
96
+ // integral — and the derivative of that is the kernel itself.
97
+ const EDGE = "background:linear-gradient(to right,#000 0,#000 50%,#fff 50%,#fff 100%)";
98
+ out.stepResponse = {};
99
+ for (const r of [4, 8, 16]) {
100
+ const s = await shoot(EDGE, `backdrop-filter:blur(${r}px)`);
101
+ const row = s.px[100];
102
+ // 60 samples either side of the boundary, as luminance 0..255.
103
+ const from = 200 - 40, to = 200 + 40;
104
+ out.stepResponse["blur(" + r + "px)"] = {
105
+ xFrom: from - 200, xTo: to - 200,
106
+ luma: row.slice(from, to).map(lum),
107
+ };
108
+ }
109
+ out.$profileComment =
110
+ "Row 100 across a black/white boundary at x=200, as luminance. This is the " +
111
+ "kernel's step response; the difference between neighbouring samples is the " +
112
+ "kernel. Compare a true Gaussian against the three-box approximation here.";
113
+
114
+ // --- 3. the element's own edge over a UNIFORM backdrop -----------------------
115
+ // Mid grey everywhere behind. If the implementation samples only what is
116
+ // inside the pane, the pane's rim fades toward transparent and this row is not
117
+ // flat. A real one is flat: the backdrop is sampled past the edge and then
118
+ // clipped.
119
+ {
120
+ const s = await shoot("background:#808080", "backdrop-filter:blur(12px)");
121
+ const row = s.px[100];
122
+ out.uniformBackdrop = {
123
+ // Just inside the pane's left edge, which is at x=100.
124
+ insideLeft: [0, 1, 2, 3, 4, 6, 8, 12, 20].map((d) => ({ dx: d, luma: lum(row[100 + d]) })),
125
+ centre: lum(row[200]),
126
+ $comment:
127
+ "A flat row means the backdrop is sampled BEYOND the element and then " +
128
+ "clipped to it. A ramp would mean the sample was clamped to the box, " +
129
+ "which is the bug this case exists to catch.",
130
+ };
131
+ }
132
+
133
+ // --- 4. a translucent background over the blurred backdrop -------------------
134
+ {
135
+ const s = await shoot("background:#000", "backdrop-filter:blur(12px);background:rgba(255,255,255,0.6)");
136
+ out.overColour = {
137
+ paneCentre: s.px[100][200],
138
+ $comment:
139
+ "Black backdrop, blurred, under a 60% white fill. The blur of a uniform " +
140
+ "black is black, so this is the fill composited over black: 255*0.6 = 153.",
141
+ };
142
+ }
143
+
144
+ // --- 4b. a feature that straddles the pane's own edge -------------------------
145
+ // Cases 1-3 all have the same colour on both sides of the pane's border, which
146
+ // makes them blind to two mistakes that look identical in them: a blur that
147
+ // samples only inside the box, and one that ignores the corner radius. Both
148
+ // were written, both passed, and both had to be caught by a case where what is
149
+ // OUTSIDE the pane differs from what is just inside it.
150
+ //
151
+ // White page, a black stripe starting exactly at the pane's left edge. Now:
152
+ // - just inside that edge, a correct blur is lightened by the white it can
153
+ // see OUTSIDE the pane; one that cannot look past the box stays black;
154
+ // - just outside the rounded corner, the backdrop is untouched black, while
155
+ // a blur drawn square would have lightened it there too.
156
+ {
157
+ const s = await shoot(
158
+ "background:#fff",
159
+ "backdrop-filter:blur(12px);border-radius:40px");
160
+ // The stripe is drawn as a second layer behind the pane, so a separate page.
161
+ const withStripe = `<!doctype html><meta charset="utf-8">
162
+ <style>
163
+ html,body{margin:0;padding:0;background:#fff}
164
+ #stage{position:relative;width:400px;height:200px;overflow:hidden}
165
+ #back{position:absolute;inset:0;background:#fff}
166
+ #stripe{position:absolute;left:99px;top:0;width:101px;height:200px;background:#000}
167
+ #pane{position:absolute;left:100px;top:40px;width:200px;height:120px;
168
+ backdrop-filter:blur(12px);border-radius:40px}
169
+ </style>
170
+ <div id="stage"><div id="back"></div><div id="stripe"></div><div id="pane"></div></div>`;
171
+ fs.writeFileSync(file, withStripe);
172
+ await tab.goto(pathToFileURL(file).href);
173
+ await tab.evaluate(() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r))));
174
+ const buf = await tab.locator("#stage").screenshot();
175
+ const shot = await tab.evaluate(async (b64) => {
176
+ const img = new Image();
177
+ img.src = "data:image/png;base64," + b64;
178
+ await img.decode();
179
+ const c = document.createElement("canvas");
180
+ c.width = img.width; c.height = img.height;
181
+ const g = c.getContext("2d", { willReadFrequently: true });
182
+ g.drawImage(img, 0, 0);
183
+ const d = g.getImageData(0, 0, c.width, c.height).data;
184
+ const at = (x, y) => { const i = (y * c.width + x) * 4; return [d[i], d[i+1], d[i+2], d[i+3]]; };
185
+ return { insideEdge: [at(102,100), at(105,100), at(110,100), at(120,100)],
186
+ outsideCorner: at(103, 43), deepInside: at(150, 100) };
187
+ }, buf.toString("base64"));
188
+ out.acrossTheEdge = {
189
+ ...shot,
190
+ $comment:
191
+ "White page, black stripe from x=99 to x=200 — one pixel LEFT of the " +
192
+ "pane's own left edge at x=100, so that the stripe's border is outside " +
193
+ "the region a correct blur samples. A painter that antialiases its " +
194
+ "rectangles (EVG does; a browser's div does not) otherwise leaves a soft " +
195
+ "seam exactly on the pane's border, and that seam blurs into the first " +
196
+ "few pixels and muddies the very comparison this case exists for. " +
197
+ "`insideEdge` is x = 102, 105, 110, 120 at mid height: a blur that can " +
198
+ "see past the pane's border is lightened there by the white outside it, " +
199
+ "and one that cannot stays black. `outsideCorner` is (103,43), which is " +
200
+ "outside the 40px corner: the backdrop there is UNTOUCHED, so a blur " +
201
+ "drawn as a square rather than a rounded box lightens a pixel that " +
202
+ "should have stayed black.",
203
+ };
204
+ }
205
+
206
+ // --- 4c. a corner with something to blur behind it ---------------------------
207
+ // Case 5 below proves the FILL is clipped to the radius. It cannot prove the
208
+ // BLUR is, because its backdrop is uniform black: blurred black is black, so a
209
+ // blur drawn as a square rather than a rounded box looks identical.
210
+ //
211
+ // This one puts a hard horizontal band across the top of the pane. Outside the
212
+ // rounded corner the backdrop is the raw band — black — while a square blur
213
+ // would have mixed it with the white below and left grey there instead.
214
+ {
215
+ const banded = `<!doctype html><meta charset="utf-8">
216
+ <style>
217
+ html,body{margin:0;padding:0;background:#fff}
218
+ #stage{position:relative;width:400px;height:200px;overflow:hidden}
219
+ #back{position:absolute;inset:0;background:#fff}
220
+ #band{position:absolute;left:0;top:39px;width:400px;height:31px;background:#000}
221
+ #pane{position:absolute;left:100px;top:40px;width:200px;height:120px;
222
+ backdrop-filter:blur(12px);border-radius:40px}
223
+ </style>
224
+ <div id="stage"><div id="back"></div><div id="band"></div><div id="pane"></div></div>`;
225
+ fs.writeFileSync(file, banded);
226
+ await tab.goto(pathToFileURL(file).href);
227
+ await tab.evaluate(() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r))));
228
+ const buf = await tab.locator("#stage").screenshot();
229
+ const shot = await tab.evaluate(async (b64) => {
230
+ const img = new Image();
231
+ img.src = "data:image/png;base64," + b64;
232
+ await img.decode();
233
+ const c = document.createElement("canvas");
234
+ c.width = img.width; c.height = img.height;
235
+ const g = c.getContext("2d", { willReadFrequently: true });
236
+ g.drawImage(img, 0, 0);
237
+ const d = g.getImageData(0, 0, c.width, c.height).data;
238
+ const at = (x, y) => { const i = (y * c.width + x) * 4; return [d[i], d[i+1], d[i+2], d[i+3]]; };
239
+ return { outsideCorner: at(103, 43), insideBand: at(200, 50), belowBand: at(200, 90) };
240
+ }, buf.toString("base64"));
241
+ out.bandedCorner = {
242
+ ...shot,
243
+ $comment:
244
+ "A black band across the top of a rounded pane over white, starting one " +
245
+ "pixel ABOVE the pane so its own edge is outside what the blur samples " +
246
+ "— the same precaution `acrossTheEdge` explains. " +
247
+ "`outsideCorner` (103,43) lies outside the 40px radius, so the backdrop " +
248
+ "there is the RAW band and stays black; a blur drawn square would have " +
249
+ "mixed in the white below and left grey. `insideBand` and `belowBand` " +
250
+ "are the softened band itself, well inside the shape.",
251
+ };
252
+ }
253
+
254
+ // --- 4d. a corner with a VERTICAL feature through it --------------------------
255
+ // 4c's band is horizontal, so near the top-left corner everything is black and
256
+ // blurred-black is black: a blur drawn square instead of rounded looks exactly
257
+ // the same. This turns the feature ninety degrees. A narrow black column down
258
+ // the pane's left side means that just outside the corner the raw backdrop is
259
+ // black while the blurred one has pulled in the white to its right — so the
260
+ // two answers are 0 and something near 100, and a square clip cannot hide.
261
+ {
262
+ const striped = `<!doctype html><meta charset="utf-8">
263
+ <style>
264
+ html,body{margin:0;padding:0;background:#fff}
265
+ #stage{position:relative;width:400px;height:200px;overflow:hidden}
266
+ #back{position:absolute;inset:0;background:#fff}
267
+ #col{position:absolute;left:95px;top:0;width:14px;height:200px;background:#000}
268
+ #pane{position:absolute;left:100px;top:40px;width:200px;height:120px;
269
+ backdrop-filter:blur(12px);border-radius:40px}
270
+ </style>
271
+ <div id="stage"><div id="back"></div><div id="col"></div><div id="pane"></div></div>`;
272
+ fs.writeFileSync(file, striped);
273
+ await tab.goto(pathToFileURL(file).href);
274
+ await tab.evaluate(() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r))));
275
+ const buf = await tab.locator("#stage").screenshot();
276
+ const shot = await tab.evaluate(async (b64) => {
277
+ const img = new Image();
278
+ img.src = "data:image/png;base64," + b64;
279
+ await img.decode();
280
+ const c = document.createElement("canvas");
281
+ c.width = img.width; c.height = img.height;
282
+ const g = c.getContext("2d", { willReadFrequently: true });
283
+ g.drawImage(img, 0, 0);
284
+ const d = g.getImageData(0, 0, c.width, c.height).data;
285
+ const at = (x, y) => { const i = (y * c.width + x) * 4; return [d[i], d[i+1], d[i+2], d[i+3]]; };
286
+ return { outsideCorner: at(104, 43), wellInside: at(104, 100), rightOfIt: at(140, 100) };
287
+ }, buf.toString("base64"));
288
+ out.verticalCorner = {
289
+ ...shot,
290
+ $comment:
291
+ "A black column covering x = 95..109, so that inside the pane it is " +
292
+ "100..109 and its own edge is five pixels clear of the pane's border. " +
293
+ "That clearance matters more here than anywhere else: the border pixel " +
294
+ "is what every clamped sample reads, so a painter that softens its " +
295
+ "rectangles by a third of a pixel has that third replicated across half " +
296
+ "the kernel. Narrow on purpose, too: a wide column stays dark when blurred and the " +
297
+ "two answers end up 0 and 20, which is inside the noise a GPU comparison " +
298
+ "has to allow. A narrow one blurs almost away, and the gap becomes the " +
299
+ "width of the scale. " +
300
+ "of the pane. `outsideCorner` (104,43) is outside the 40px radius: raw " +
301
+ "black. `wellInside` (104,100) is the same x at mid height, inside the " +
302
+ "shape: the blurred column, much lighter. The gap between those two " +
303
+ "numbers is the whole test — a blur drawn square gives the second answer " +
304
+ "in both places.",
305
+ };
306
+ }
307
+
308
+ // --- 4e. a narrow feature well clear of every border --------------------------
309
+ // The step response in case 1 is a single edge at the pane's centre. This is a
310
+ // narrow COLUMN — two edges close together, which is the harder thing for a
311
+ // kernel to get right — and it is placed 50px in from the nearest border so
312
+ // that nothing about the element's edge is involved. That separation is the
313
+ // point: it is the profile against which the kernel itself can be judged.
314
+ {
315
+ const inner = `<!doctype html><meta charset="utf-8">
316
+ <style>
317
+ html,body{margin:0;padding:0;background:#fff}
318
+ #stage{position:relative;width:400px;height:200px;overflow:hidden}
319
+ #back{position:absolute;inset:0;background:#fff}
320
+ #col{position:absolute;left:150px;top:0;width:14px;height:200px;background:#000}
321
+ #pane{position:absolute;left:100px;top:40px;width:200px;height:120px;
322
+ backdrop-filter:blur(12px)}
323
+ </style>
324
+ <div id="stage"><div id="back"></div><div id="col"></div><div id="pane"></div></div>`;
325
+ fs.writeFileSync(file, inner);
326
+ await tab.goto(pathToFileURL(file).href);
327
+ await tab.evaluate(() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r))));
328
+ const buf = await tab.locator("#stage").screenshot();
329
+ const shot = await tab.evaluate(async (b64) => {
330
+ const img = new Image();
331
+ img.src = "data:image/png;base64," + b64;
332
+ await img.decode();
333
+ const c = document.createElement("canvas");
334
+ c.width = img.width; c.height = img.height;
335
+ const g = c.getContext("2d", { willReadFrequently: true });
336
+ g.drawImage(img, 0, 0);
337
+ const d = g.getImageData(0, 0, c.width, c.height).data;
338
+ const xs = [];
339
+ for (let x = 130; x <= 190; x += 2) xs.push(x);
340
+ return { xs, luma: xs.map((x) => {
341
+ const i = (100 * c.width + x) * 4;
342
+ return Math.round(0.2126 * d[i] + 0.7152 * d[i+1] + 0.0722 * d[i+2]);
343
+ }) };
344
+ }, buf.toString("base64"));
345
+ out.innerColumn = {
346
+ ...shot,
347
+ $comment:
348
+ "A 14px black column at x=150, fifty pixels clear of the pane's nearest " +
349
+ "border, sampled across at mid height. Two edges close together is the " +
350
+ "case a kernel is easiest to get wrong on, and there is no border " +
351
+ "behaviour mixed into it.",
352
+ };
353
+ }
354
+
355
+ // --- 4f. and what happens WITHIN a kernel's reach of the border ---------------
356
+ // Recorded because it is a real difference, and not matched.
357
+ //
358
+ // Move that same column to straddle the pane's left border and the browser
359
+ // stops producing a blur profile at all. Across x = 100..108 it is a straight
360
+ // line of about 8 levels a pixel; at x = 109, the column's own right edge, it
361
+ // JUMPS 63 levels; and from 110 it is another straight line of about 2.5. A
362
+ // blurred step is an S-curve. Two straight segments with a discontinuity
363
+ // between them is not a Gaussian, a three-box approximation, or any kernel —
364
+ // it is what a compositor's downsampled edge handling looks like.
365
+ //
366
+ // So EVG clamps at the element's border, which is the principled reading of
367
+ // the same rule, and matches the browser everywhere the border is more than a
368
+ // kernel's reach away — within 3 levels, see `innerColumn`. Inside that band
369
+ // the two differ by up to about 45, and the difference is Chrome's artefact
370
+ // rather than a behaviour worth copying. Written down here so the next person
371
+ // to compare screenshots finds the answer instead of the puzzle.
372
+ {
373
+ const straddling = `<!doctype html><meta charset="utf-8">
374
+ <style>
375
+ html,body{margin:0;padding:0;background:#fff}
376
+ #stage{position:relative;width:400px;height:200px;overflow:hidden}
377
+ #back{position:absolute;inset:0;background:#fff}
378
+ #col{position:absolute;left:95px;top:0;width:14px;height:200px;background:#000}
379
+ #pane{position:absolute;left:100px;top:40px;width:200px;height:120px;
380
+ backdrop-filter:blur(12px)}
381
+ </style>
382
+ <div id="stage"><div id="back"></div><div id="col"></div><div id="pane"></div></div>`;
383
+ fs.writeFileSync(file, straddling);
384
+ await tab.goto(pathToFileURL(file).href);
385
+ await tab.evaluate(() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r))));
386
+ const buf = await tab.locator("#stage").screenshot();
387
+ const shot = await tab.evaluate(async (b64) => {
388
+ const img = new Image();
389
+ img.src = "data:image/png;base64," + b64;
390
+ await img.decode();
391
+ const c = document.createElement("canvas");
392
+ c.width = img.width; c.height = img.height;
393
+ const g = c.getContext("2d", { willReadFrequently: true });
394
+ g.drawImage(img, 0, 0);
395
+ const d = g.getImageData(0, 0, c.width, c.height).data;
396
+ const xs = [];
397
+ for (let x = 98; x <= 130; x++) xs.push(x);
398
+ return { xs, luma: xs.map((x) => {
399
+ const i = (100 * c.width + x) * 4;
400
+ return Math.round(0.2126 * d[i] + 0.7152 * d[i+1] + 0.0722 * d[i+2]);
401
+ }) };
402
+ }, buf.toString("base64"));
403
+ out.borderBand = {
404
+ ...shot,
405
+ matched: false,
406
+ $comment:
407
+ "NOT matched, on purpose. Two straight segments with a 63-level jump at " +
408
+ "x=109 — Chrome's downsampled edge handling, not a kernel. EVG clamps at " +
409
+ "the border instead and agrees with the browser everywhere further than " +
410
+ "a kernel's reach from it. Kept so the difference is a decision on " +
411
+ "record rather than a surprise.",
412
+ };
413
+ }
414
+
415
+ // --- 5. rounded corners ------------------------------------------------------
416
+ {
417
+ const s = await shoot("background:#000", "backdrop-filter:blur(12px);background:rgba(255,255,255,0.85);border-radius:40px");
418
+ // The pane's top-left corner is at (100,40) with a 40px radius, so (104,44)
419
+ // is outside the rounded shape and (140,80) is well inside it.
420
+ out.rounded = {
421
+ outsideTheCorner: s.px[44][104],
422
+ insideTheCorner: s.px[80][140],
423
+ $comment:
424
+ "Outside the rounded corner the backdrop shows through untouched " +
425
+ "(black); inside, the pane's fill is over it. The blur is clipped to " +
426
+ "the BORDER BOX INCLUDING its radius, not to the rectangle.",
427
+ };
428
+ }
429
+
430
+ await browser.close();
431
+ fs.rmSync(file, { force: true });
432
+
433
+ // --- which model is it, then? -----------------------------------------------
434
+ // The step responses above are enough to decide between the two candidates, so
435
+ // the oracle decides rather than leaving it to whoever reads the numbers. Both
436
+ // are fitted here and the residuals are recorded: a claim about the kernel
437
+ // that comes with its own error bar is one an implementation can be held to.
438
+ function stepOfThreeBoxes(sigma, n) {
439
+ // The SVG filter spec's approximation, which the CSS filter spec inherits:
440
+ // three box passes of width d = floor(sigma * 3 * sqrt(2*pi) / 4 + 0.5).
441
+ // An odd d centres; an even d is offset by half a pixel on two of the three
442
+ // passes and widened by one on the third, which is what makes the result
443
+ // symmetric.
444
+ let d = Math.floor((sigma * 3 * Math.sqrt(2 * Math.PI)) / 4 + 0.5);
445
+ if (d < 1) d = 1;
446
+ const step = new Array(2 * n).fill(0).map((_, i) => (i < n ? 0 : 1));
447
+ const pass = (sig, w, off) => sig.map((_, i) => {
448
+ const half = w >> 1;
449
+ let acc = 0;
450
+ for (let k = i - half + off; k < i - half + off + w; k++) {
451
+ acc += sig[Math.min(Math.max(k, 0), sig.length - 1)];
452
+ }
453
+ return acc / w;
454
+ });
455
+ return d % 2 === 1
456
+ ? pass(pass(pass(step, d, 0), d, 0), d, 0)
457
+ : pass(pass(pass(step, d, 0), d, 1), d + 1, 0);
458
+ }
459
+ const erf = (x) => {
460
+ // Abramowitz & Stegun 7.1.26, plenty for four significant figures.
461
+ const s = x < 0 ? -1 : 1; x = Math.abs(x);
462
+ const t = 1 / (1 + 0.3275911 * x);
463
+ const y = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t
464
+ - 0.284496736) * t + 0.254829592) * t * Math.exp(-x * x);
465
+ return s * y;
466
+ };
467
+ const phi = (z) => 0.5 * (1 + erf(z / Math.SQRT2));
468
+
469
+ out.kernel = {};
470
+ for (const [label, v] of Object.entries(out.stepResponse)) {
471
+ const r = parseInt(label.match(/\d+/)[0], 10);
472
+ const N = 300;
473
+ const models = {
474
+ "gaussian sigma=r": (x) => 255 * phi(x / r),
475
+ "gaussian sigma=r/2": (x) => 255 * phi(x / (r / 2)),
476
+ "three-box sigma=r": ((a) => (x) => 255 * a[N + x])(stepOfThreeBoxes(r, N)),
477
+ "three-box sigma=r/2": ((a) => (x) => 255 * a[N + x])(stepOfThreeBoxes(r / 2, N)),
478
+ };
479
+ const fits = {};
480
+ for (const [name, f] of Object.entries(models)) {
481
+ let worst = 0;
482
+ for (let i = 0; i < v.luma.length; i++) {
483
+ const x = v.xFrom + i;
484
+ worst = Math.max(worst, Math.abs(v.luma[i] - f(x)));
485
+ }
486
+ fits[name] = Math.round(worst * 10) / 10;
487
+ }
488
+ out.kernel[label] = fits;
489
+ }
490
+ out.kernel.$comment =
491
+ "Worst absolute error in luminance (0..255) between the measured step " +
492
+ "response and each candidate. `three-box sigma=r` wins at every radius by " +
493
+ "a wide margin — so `blur(r)` is the SVG three-box approximation with " +
494
+ "sigma = r, NOT a true Gaussian and NOT sigma = r/2. The common belief is " +
495
+ "half right: the sigma is the radius, and the shape is not a Gaussian.";
496
+
497
+
498
+ const outFile = path.join(HERE, "css-blur.json");
499
+ fs.writeFileSync(outFile, JSON.stringify(out, null, 2) + "\n");
500
+ console.log("wrote " + path.relative(process.cwd(), outFile));
501
+ for (const [k, v] of Object.entries(out.stepResponse)) {
502
+ const l = v.luma;
503
+ console.log(` ${k.padEnd(14)} edge ramp ${l[35]} ${l[38]} ${l[40]} ${l[42]} ${l[45]}`);
504
+ }
505
+ console.log(" uniform backdrop, inside the left edge:",
506
+ out.uniformBackdrop.insideLeft.map((s) => s.luma).join(" "));
507
+ console.log(" centre:", out.uniformBackdrop.centre);
508
+ console.log(" 60% white over blurred black:", JSON.stringify(out.overColour.paneCentre));
509
+ console.log(" outside a 40px corner:", JSON.stringify(out.rounded.outsideTheCorner),
510
+ " inside:", JSON.stringify(out.rounded.insideTheCorner));
511
+ console.log(" across the pane's own edge:",
512
+ out.acrossTheEdge.insideEdge.map((p) => lum(p)).join(" "),
513
+ " outside the corner:", lum(out.acrossTheEdge.outsideCorner),
514
+ " deep inside:", lum(out.acrossTheEdge.deepInside));
515
+ console.log(" banded corner: outside=", lum(out.bandedCorner.outsideCorner),
516
+ " in the band=", lum(out.bandedCorner.insideBand),
517
+ " below it=", lum(out.bandedCorner.belowBand));
518
+ console.log(" vertical corner: outside=", lum(out.verticalCorner.outsideCorner),
519
+ " same x inside=", lum(out.verticalCorner.wellInside),
520
+ " right of it=", lum(out.verticalCorner.rightOfIt));
521
+ console.log(" inner column:", out.innerColumn.luma.slice(0, 12).join(" "), "...");
522
+ console.log(" border band (NOT matched):", out.borderBand.luma.slice(0, 14).join(" "), "...");
523
+ console.log(" worst luminance error per candidate kernel:");
524
+ for (const [label, fits] of Object.entries(out.kernel)) {
525
+ if (label.startsWith("$")) continue;
526
+ const best = Object.entries(fits).sort((a, b) => a[1] - b[1])[0][0];
527
+ console.log(" " + label.padEnd(14) +
528
+ Object.entries(fits).map(([n, e]) => `${n}=${e}`).join(" ") + " -> " + best);
529
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The browser, asked what CSS actually does with a box shorthand.
3
+ *
4
+ * node lib/evg/oracle/css_box_oracle.mjs
5
+ *
6
+ * Writes `css-box.json` beside this file. Nothing in EVG reads it at run time;
7
+ * `EVGBoxShorthandTest.rgr` is checked against it by hand, the same way the
8
+ * timing capture works.
9
+ *
10
+ * WHY THIS EXISTS. `padding: 0 16px` on the table demo's rows put nothing at
11
+ * all on the sides. EVG parsed the whole declaration as ONE unit — the parser
12
+ * read the leading `0` and applied it to four sides — so every two-, three-
13
+ * and four-value box shorthand in the gallery was silently wrong, and had been
14
+ * since the sheets were written. Nobody noticed because a padding that is too
15
+ * small looks like a layout someone chose.
16
+ *
17
+ * So the questions are the ones the shorthand answers, and a few the value
18
+ * grammar answers underneath it:
19
+ *
20
+ * 1. How do one, two, three and four values map onto the four sides?
21
+ * 2. Is a bare `0` legal without a unit, and is `0px` the same thing?
22
+ * 3. Does a negative value survive on `margin` (yes) and on `padding` (no —
23
+ * the whole declaration is dropped, not clamped)?
24
+ * 4. What happens to a shorthand with a junk component, or five values?
25
+ * 5. Do percentages resolve against the containing block's WIDTH on every
26
+ * side, top and bottom included?
27
+ */
28
+
29
+ import fs from "node:fs";
30
+ import path from "node:path";
31
+ import { fileURLToPath, pathToFileURL } from "node:url";
32
+ import { requireDom, findChromium, assertDomInstalled } from "../../../gallery/ui/conformance/dom-adapter.mjs";
33
+
34
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
35
+
36
+ // Each case is a declaration put on a 400x200 box inside a 400x200 parent, so
37
+ // a percentage has something definite to resolve against.
38
+ const CASES = [
39
+ { prop: "padding", value: "10px" },
40
+ { prop: "padding", value: "0 16px" },
41
+ { prop: "padding", value: "1px 2px 3px" },
42
+ { prop: "padding", value: "1px 2px 3px 4px" },
43
+ { prop: "padding", value: "0" },
44
+ { prop: "padding", value: "2px 10px" },
45
+ { prop: "padding", value: " 4px 8px " },
46
+ { prop: "padding", value: "10%" },
47
+ { prop: "padding", value: "-4px" },
48
+ { prop: "padding", value: "1px 2px 3px 4px 5px" },
49
+ { prop: "padding", value: "1px nonsense" },
50
+ { prop: "padding", value: "1em 2em" },
51
+ { prop: "margin", value: "10px" },
52
+ { prop: "margin", value: "0 auto" },
53
+ { prop: "margin", value: "-4px 8px" },
54
+ { prop: "margin", value: "1px 2px 3px 4px" },
55
+ { prop: "margin", value: "5%" },
56
+ ];
57
+
58
+ assertDomInstalled();
59
+ const { chromium } = requireDom("playwright-core");
60
+
61
+ const html =
62
+ `<!doctype html><meta charset="utf-8">` +
63
+ `<style>html,body{margin:0;padding:0;font-size:16px}` +
64
+ `#parent{width:400px;height:200px}#box{width:400px;height:200px}</style>` +
65
+ `<div id="parent"><div id="box"></div></div>`;
66
+ const pageFile = path.join(HERE, ".box-probe.html");
67
+ fs.writeFileSync(pageFile, html);
68
+
69
+ const browser = await chromium.launch({ executablePath: findChromium() });
70
+ const page = await browser.newPage();
71
+ await page.goto(pathToFileURL(pageFile).href);
72
+
73
+ const results = [];
74
+ for (const c of CASES) {
75
+ const r = await page.evaluate(({ prop, value }) => {
76
+ const box = document.getElementById("box");
77
+ // Clear first, so a rejected declaration reads as the initial value rather
78
+ // than as whatever the previous case left behind. That is the whole point
79
+ // of case 3: a dropped declaration and a zero one look identical unless
80
+ // you start from a known state.
81
+ box.style.cssText = "width:400px;height:200px";
82
+ box.style.setProperty(prop, value);
83
+ // What the style object kept is the parse result; what the computed style
84
+ // says is the used value. Both matter — the first says whether the
85
+ // declaration survived at all.
86
+ const kept = box.style.getPropertyValue(prop);
87
+ const cs = getComputedStyle(box);
88
+ const side = (s) => cs.getPropertyValue(prop + "-" + s);
89
+ return {
90
+ kept,
91
+ top: side("top"), right: side("right"), bottom: side("bottom"), left: side("left"),
92
+ };
93
+ }, c);
94
+ results.push({ ...c, ...r });
95
+ }
96
+ await browser.close();
97
+ fs.rmSync(pageFile, { force: true });
98
+
99
+ const file = path.join(HERE, "css-box.json");
100
+ fs.writeFileSync(file, JSON.stringify({ cases: results }, null, 2) + "\n");
101
+ console.log("wrote " + path.relative(process.cwd(), file));
102
+ for (const r of results) {
103
+ console.log(
104
+ ` ${r.prop}: ${JSON.stringify(r.value).padEnd(26)} -> ` +
105
+ `${r.top} ${r.right} ${r.bottom} ${r.left}` + (r.kept === "" ? " (DROPPED)" : ""));
106
+ }