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,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build the live responsive-layout page.
4
+ *
5
+ * node lib/evg/web/responsive/build.mjs
6
+ * node lib/evg/web/responsive/build.mjs --out path/to/evg/responsive
7
+ *
8
+ * Compiles `EvgResponsiveDemo.rgr` — which is EVGElement, EVGStyleSheet,
9
+ * EVGLayout, EVGTextEngine and EVGDisplayList, the whole engine — to a browser
10
+ * IIFE, and copies the page and the SVG painter beside it. Nothing about the
11
+ * layout is precomputed here: the compiled engine runs in the browser, at the
12
+ * width the window happens to be.
13
+ *
14
+ * The showcase build calls the same steps for /evg/responsive/ on Pages.
15
+ */
16
+ import { execFileSync } from "node:child_process";
17
+ import fs from "node:fs";
18
+ import path from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
22
+ const ROOT = path.resolve(HERE, "../../../..");
23
+ const argv = process.argv.slice(2);
24
+ const outFlag = argv.indexOf("--out");
25
+ const OUT = outFlag >= 0 ? path.resolve(argv[outFlag + 1]) : path.join(HERE, "dist");
26
+ const STAGE = path.join(HERE, ".stage");
27
+
28
+ fs.mkdirSync(STAGE, { recursive: true });
29
+ fs.mkdirSync(OUT, { recursive: true });
30
+
31
+ const env = { ...process.env, RANGER_LIB: "./compiler/Lang.rgr:./lib/stdops.rgr" };
32
+
33
+ const log = execFileSync(
34
+ process.execPath,
35
+ [
36
+ "dist/rgrc.js",
37
+ "-es6",
38
+ "lib/evg/web/responsive/EvgResponsiveDemo.rgr",
39
+ `-d=${path.relative(ROOT, STAGE)}`,
40
+ "-o=evg_responsive_demo.js",
41
+ "-nodecli",
42
+ ],
43
+ { cwd: ROOT, env, encoding: "utf8" }
44
+ );
45
+
46
+ if (log.includes("Compilation FAILED")) {
47
+ process.stderr.write(log + "\n");
48
+ process.exit(1);
49
+ }
50
+
51
+ const rawPath = path.join(STAGE, "evg_responsive_demo.js");
52
+ if (!fs.existsSync(rawPath)) {
53
+ process.stderr.write("compiler wrote no " + rawPath + "\n");
54
+ process.exit(1);
55
+ }
56
+
57
+ const bundle = fs.readFileSync(rawPath, "utf8").replace(/^#![^\n]*\n/, "");
58
+
59
+ // Load it here, with `require` hidden, exactly as the browser will: the
60
+ // generated file asks for Node's modules when it can see them, and a bundle
61
+ // that only fails once it is in a browser fails somewhere nobody is watching.
62
+ {
63
+ const previous = globalThis.require;
64
+ globalThis.require = undefined;
65
+ const found = (0, eval)(bundle + "; typeof EvgResponsiveDemo + '|' + typeof EVGLayout + '|' + typeof EVGHostTextMeasurer");
66
+ globalThis.require = previous;
67
+ if (found !== "function|function|function") {
68
+ throw new Error("evg_responsive_demo.js missing browser exports (got " + found + ")");
69
+ }
70
+ }
71
+
72
+ const scoped =
73
+ "// GENERATED from lib/evg/web/responsive/EvgResponsiveDemo.rgr — do not edit.\n" +
74
+ "(function () {\n" +
75
+ bundle +
76
+ "\n;globalThis.EvgResponsiveDemo = EvgResponsiveDemo;" +
77
+ // What the browser's text measurer needs off the module (evg-measure.js).
78
+ "\n;globalThis.EvgResponsiveModule = { EVGHostTextMeasurer: EVGHostTextMeasurer, EVGDefaultMeasurer: EVGDefaultMeasurer };" +
79
+ "\n})();\n";
80
+
81
+ fs.writeFileSync(path.join(OUT, "evg_responsive_demo.js"), scoped);
82
+ fs.copyFileSync(path.join(HERE, "index.html"), path.join(OUT, "index.html"));
83
+ // The painter, copied rather than imported across directories, so the built
84
+ // page is a directory of four files that can be served from anywhere.
85
+ fs.copyFileSync(path.join(ROOT, "lib/evg/html/evg-html.js"), path.join(OUT, "evg-html.js"));
86
+ fs.copyFileSync(path.join(ROOT, "lib/evg/gl/evg-measure.js"), path.join(OUT, "evg-measure.js"));
87
+ fs.copyFileSync(path.join(ROOT, "lib/evg/html/evg-dom.js"), path.join(OUT, "evg-dom.js"));
88
+
89
+ fs.rmSync(STAGE, { recursive: true, force: true });
90
+ process.stdout.write("Wrote responsive page to " + path.relative(ROOT, OUT) + "\n");
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MIT
3
+ //
4
+ // The retained DOM painter, in a browser: are the nodes where the engine
5
+ // said, and do they survive a frame?
6
+ //
7
+ // npm run evg:dom:check (after: npm run evg:responsive:web)
8
+ //
9
+ // `evg-dom.js` keeps one DOM node per element and patches it from the host
10
+ // tree's ops. Two things have to be true of it, and neither is visible in a
11
+ // screenshot:
12
+ //
13
+ // * every node's rectangle in the page — where the browser put it, through
14
+ // parent-relative offsets and a scroll layer's transform — is the page
15
+ // rectangle EVG gave the element. The same claim the SVG painter is
16
+ // held to, made of DOM placement instead of `<rect>` attributes;
17
+ // * a resize UPDATES the nodes rather than remaking them: the cards
18
+ // rearrange from four across to two, and the DOM nodes are the same
19
+ // objects, born in the first build.
20
+ //
21
+ // The smoke test's questions about the breakpoints are asked again here on
22
+ // the DOM, so the two painters are held to the same layout.
23
+
24
+ import fs from "node:fs";
25
+ import path from "node:path";
26
+ import http from "node:http";
27
+ import { fileURLToPath } from "node:url";
28
+ import { createRequire } from "node:module";
29
+
30
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
31
+ const ROOT = path.resolve(HERE, "../../../..");
32
+ const DIST = path.resolve(process.argv[2] || path.join(HERE, "dist"));
33
+
34
+ function loadPlaywright() {
35
+ for (const anchor of [path.join(ROOT, "package.json"), import.meta.url]) {
36
+ for (const name of ["playwright", "playwright-core"]) {
37
+ try { return createRequire(anchor)(name); } catch { /* next */ }
38
+ }
39
+ }
40
+ return null;
41
+ }
42
+ function findChrome() {
43
+ if (process.env.RANGER_CHROMIUM) return process.env.RANGER_CHROMIUM;
44
+ // The browsers Playwright installed, whichever build they are — the
45
+ // package's own resolution wants the exact build it was released with.
46
+ const base = process.env.PLAYWRIGHT_BROWSERS_PATH || "/opt/pw-browsers";
47
+ if (fs.existsSync(base)) {
48
+ for (const entry of fs.readdirSync(base)) {
49
+ if (!entry.startsWith("chromium-")) continue;
50
+ const exe = path.join(base, entry, "chrome-linux", "chrome");
51
+ if (fs.existsSync(exe)) return exe;
52
+ }
53
+ }
54
+ for (const c of [process.env.CHROME_PATH, "/usr/bin/chromium", "/usr/bin/google-chrome"].filter(Boolean)) {
55
+ if (fs.existsSync(c)) return c;
56
+ }
57
+ return null;
58
+ }
59
+
60
+ const pw = loadPlaywright();
61
+ if (!pw) {
62
+ console.log("Playwright is not available — the DOM painter was not checked.");
63
+ process.exit(0);
64
+ }
65
+ for (const f of ["index.html", "evg_responsive_demo.js", "evg-dom.js"]) {
66
+ if (!fs.existsSync(path.join(DIST, f))) {
67
+ console.error(`no ${f} in ${path.relative(ROOT, DIST)} — run: npm run evg:responsive:web`);
68
+ process.exit(1);
69
+ }
70
+ }
71
+
72
+ const TYPES = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css" };
73
+ const server = http.createServer((req, res) => {
74
+ const rel = decodeURIComponent(new URL(req.url, "http://x").pathname).replace(/^\/+/, "") || "index.html";
75
+ const file = path.join(DIST, rel);
76
+ if (!file.startsWith(DIST) || !fs.existsSync(file)) { res.writeHead(404).end("not found"); return; }
77
+ res.writeHead(200, { "content-type": TYPES[path.extname(file)] || "application/octet-stream" });
78
+ fs.createReadStream(file).pipe(res);
79
+ });
80
+ await new Promise((r) => server.listen(0, r));
81
+ const port = server.address().port;
82
+
83
+ const exe = findChrome();
84
+ const browser = await pw.chromium.launch(exe ? { executablePath: exe } : {});
85
+ const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
86
+ const errors = [];
87
+ page.on("pageerror", (e) => errors.push(String(e)));
88
+
89
+ let passed = 0, failed = 0;
90
+ const check = (what, cond, detail = "") => {
91
+ if (cond) passed += 1; else failed += 1;
92
+ console.log(` ${cond ? "PASS" : "FAIL"} ${what}${cond || !detail ? "" : " (" + detail + ")"}`);
93
+ };
94
+
95
+ await page.goto(`http://127.0.0.1:${port}/index.html?painter=dom`, { waitUntil: "networkidle" });
96
+ await page.waitForFunction(() => window.__evgResponsive && window.__evgResponsive.painter === "dom", null, { timeout: 20000 });
97
+
98
+ // What the DOM did with the ops: every node's rectangle, as the browser
99
+ // placed it, against the page rectangle the engine gave the element.
100
+ const placement = () => page.evaluate(() => {
101
+ const stage = document.getElementById("stage").getBoundingClientRect();
102
+ const out = [];
103
+ for (const n of window.__evgDom.nodes()) {
104
+ const r = n.el.getBoundingClientRect();
105
+ out.push({
106
+ path: n.path, born: n.born,
107
+ dx: Math.abs(r.left - stage.left - n.px), dy: Math.abs(r.top - stage.top - n.py),
108
+ dw: Math.abs(r.width - n.w), dh: Math.abs(r.height - n.h),
109
+ lines: n.el.querySelectorAll(":scope > .evg-line").length,
110
+ pre: [...n.el.querySelectorAll(":scope > .evg-line")].every((l) => getComputedStyle(l).whiteSpace === "pre"),
111
+ });
112
+ }
113
+ return out;
114
+ });
115
+ const state = () => page.evaluate(() => window.__evgResponsive);
116
+
117
+ console.log("dom painter check");
118
+ let s = await state();
119
+ check("the page painted through the DOM painter", s.painter === "dom" && s.host && s.host.nodes > 0, JSON.stringify(s.host));
120
+ check("the stylesheet parsed clean", s.cssErrors === 0, `cssErrors=${s.cssErrors}`);
121
+ check("the first build created every node", s.host.created === s.host.count && s.host.updated === 0, JSON.stringify(s.host));
122
+
123
+ let nodes = await placement();
124
+ const worst = (list) => list.reduce((m, n) => Math.max(m, n.dx, n.dy, n.dw, n.dh), 0);
125
+ check(`every node is where the engine put it (${nodes.length} nodes)`, worst(nodes) < 0.51, `worst drift ${worst(nodes).toFixed(2)}px`);
126
+ check("text is lines, and the browser breaks none of them", nodes.some((n) => n.lines > 0) && nodes.every((n) => n.pre), "");
127
+ check("a paragraph wrapped into more than one line", nodes.some((n) => n.lines > 1), "");
128
+
129
+ // Cards, counted from the DOM: the same question the smoke test asks of
130
+ // the SVG, asked of nodes. A card is a node the tree classed `card`; the
131
+ // count in the first row is the column layout.
132
+ const columns = async () => page.evaluate(() => {
133
+ const ys = new Map();
134
+ for (const n of window.__evgDom.nodes()) {
135
+ if (!n.el.classList.contains("card")) continue;
136
+ const y = Math.round(n.py);
137
+ ys.set(y, (ys.get(y) || 0) + 1);
138
+ }
139
+ return [...ys.values()];
140
+ });
141
+ let cols = await columns();
142
+ check("1400px: cards across", cols.length > 0 && cols[0] === 4, JSON.stringify(cols));
143
+
144
+ // Narrow the window: the layout changes, the nodes do not.
145
+ await page.setViewportSize({ width: 760, height: 900 });
146
+ await page.waitForFunction((want) => window.__evgResponsive && Math.abs(window.__evgResponsive.width - want) < 30, 760, { timeout: 10000 });
147
+ await page.waitForTimeout(100);
148
+ s = await state();
149
+ check("760px: the resize was updates, not creates", s.host.created === 0 && s.host.removed === 0 && s.host.updated > 0, JSON.stringify(s.host));
150
+ nodes = await placement();
151
+ check("760px: every node is still where the engine put it", worst(nodes) < 0.51, `worst drift ${worst(nodes).toFixed(2)}px`);
152
+ check("760px: the nodes are the ones born in the first build", nodes.every((n) => n.born === 1), `${nodes.filter((n) => n.born !== 1).length} reborn`);
153
+ cols = await columns();
154
+ check("760px: two cards across", cols.length > 0 && cols[0] === 2, JSON.stringify(cols));
155
+
156
+ await page.setViewportSize({ width: 1400, height: 900 });
157
+ await page.waitForFunction((want) => window.__evgResponsive && Math.abs(window.__evgResponsive.width - want) < 30, 1400, { timeout: 10000 });
158
+ await page.waitForTimeout(100);
159
+ cols = await columns();
160
+ check("back at 1400px: four across again", cols.length > 0 && cols[0] === 4, JSON.stringify(cols));
161
+ nodes = await placement();
162
+ check("and still the same nodes", nodes.every((n) => n.born === 1), "");
163
+ check("no page errors", errors.length === 0, errors.join("; "));
164
+
165
+ await browser.close();
166
+ server.close();
167
+ console.log(`\npassed = ${passed} failed = ${failed}`);
168
+ if (failed > 0) { console.log("FAILURES"); process.exit(1); }
169
+ console.log("ALL PASS");
@@ -0,0 +1,177 @@
1
+ <!doctype html>
2
+ <html lang="fi">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>EVG — responsive layout</title>
7
+ <style>
8
+ /* This stylesheet styles the CHROME — the strip of controls at the bottom —
9
+ and nothing else. The page above it has no CSS in this file at all: it is
10
+ an <svg> full of absolute coordinates that EVG computed, and the browser
11
+ is not laying any of it out. */
12
+ * { box-sizing: border-box }
13
+ html, body { margin: 0; padding: 0; background: #f6f7fb }
14
+ body { font: 13px/1.4 ui-sans-serif, system-ui, sans-serif; color: #10162b }
15
+
16
+ /* The stage is the full width of the document, which is the whole point:
17
+ whatever this element is, EVG is told that number and lays the page out
18
+ to it. `clientWidth` excludes the scrollbar, so the width EVG is given is
19
+ the width it actually has. */
20
+ #stage { width: 100%; display: block }
21
+ #stage svg { display: block }
22
+
23
+ #hud {
24
+ position: fixed; left: 50%; bottom: 14px; transform: translateX(-50%);
25
+ display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
26
+ padding: 8px 10px; border-radius: 999px;
27
+ background: rgba(16, 22, 43, .92); color: #dbe4f7;
28
+ box-shadow: 0 6px 24px rgba(16, 22, 43, .28);
29
+ font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
30
+ }
31
+ #hud button {
32
+ font: inherit; color: #dbe4f7; cursor: pointer;
33
+ background: #26314c; border: 1px solid #3a4767;
34
+ border-radius: 999px; padding: 4px 10px;
35
+ }
36
+ #hud button:hover { border-color: #7cc4ff }
37
+ #hud button[aria-pressed="true"] { background: #7cc4ff; color: #06122e; border-color: #7cc4ff }
38
+ #hud .num { color: #7cc4ff }
39
+ #hud .sep { opacity: .35 }
40
+ #err { padding: 20px; color: #a1002a; font: 13px ui-monospace, monospace; white-space: pre-wrap }
41
+ </style>
42
+ </head>
43
+ <body>
44
+
45
+ <div id="stage"></div>
46
+ <p id="err"></p>
47
+
48
+ <div id="hud">
49
+ <span>leveys <b class="num" id="hudW">–</b> px</span>
50
+ <span class="sep">·</span>
51
+ <span><b class="num" id="hudCmds">–</b> komentoa</span>
52
+ <span class="sep">·</span>
53
+ <span><b class="num" id="hudMs">–</b> ms</span>
54
+ <span class="sep">|</span>
55
+ <button id="btnFull" aria-pressed="true">koko ikkuna</button>
56
+ <button data-w="1280">1280</button>
57
+ <button data-w="900">900</button>
58
+ <button data-w="720">720</button>
59
+ <button data-w="420">420</button>
60
+ <span class="sep">|</span>
61
+ <button id="btnCoarse" aria-pressed="false">pointer: coarse</button>
62
+ <span class="sep">|</span>
63
+ <button id="btnSvg" data-painter="svg" title="evg-html.js: the display list as <svg>, rebuilt every frame">svg</button>
64
+ <button id="btnDom" data-painter="dom" title="evg-dom.js: a DOM node per element, kept and patched from the host tree">dom</button>
65
+ </div>
66
+
67
+ <script src="./evg_responsive_demo.js"></script>
68
+ <script type="module">
69
+ import { renderDisplayList } from "./evg-html.js";
70
+ import { createDomPainter } from "./evg-dom.js";
71
+ import { installCanvasMeasurer } from "./evg-measure.js";
72
+
73
+ // The browser measures the text the page is laid out with — the same face
74
+ // `evg-html.js` sets on its <text> nodes — rather than the advance table.
75
+ const fontMeasure = installCanvasMeasurer(globalThis.EvgResponsiveModule);
76
+ window.__fontMeasure = fontMeasure;
77
+
78
+ const stage = document.getElementById("stage");
79
+ const errBox = document.getElementById("err");
80
+
81
+ const demo = new EvgResponsiveDemo();
82
+
83
+ // `?painter=dom` keeps a DOM node per element and patches it from the host
84
+ // tree's ops (lib/evg/html/evg-dom.js); the default paints the display
85
+ // list as <svg> and rebuilds it every frame.
86
+ const painterMode = new URLSearchParams(location.search).get("painter") || "svg";
87
+ const domPainter = painterMode === "dom" ? createDomPainter(stage) : null;
88
+ window.__evgDom = domPainter;
89
+ for (const b of document.querySelectorAll("#hud button[data-painter]")) {
90
+ b.setAttribute("aria-pressed", String(b.dataset.painter === painterMode));
91
+ b.addEventListener("click", (e) => {
92
+ const u = new URL(location.href);
93
+ u.searchParams.set("painter", e.currentTarget.dataset.painter);
94
+ location.href = u.toString();
95
+ });
96
+ }
97
+
98
+ /** null = follow the window; a number = pretend the window is that wide. */
99
+ let forcedWidth = null;
100
+ let coarse = false;
101
+ let lastKey = "";
102
+
103
+ function paint(force) {
104
+ // The width EVG is handed. When a preset is active the stage is narrowed to
105
+ // it and centred, so the page is laid out to a width it can see — nothing
106
+ // here scales or transforms the result, because a scaled screenshot is
107
+ // exactly what this demo is not.
108
+ const w = Math.max(240, Math.round(forcedWidth !== null ? forcedWidth : stage.clientWidth));
109
+ const h = Math.max(240, window.innerHeight);
110
+ const key = `${w}x${h}:${coarse}`;
111
+ if (!force && key === lastKey) return;
112
+ lastKey = key;
113
+
114
+ demo.setViewport(w, h, coarse);
115
+ const t0 = performance.now();
116
+ let doc, stats = null, commands = 0;
117
+ if (domPainter) {
118
+ doc = JSON.parse(demo.renderHost());
119
+ stats = domPainter.apply(doc);
120
+ commands = doc.host.count;
121
+ } else {
122
+ doc = JSON.parse(demo.render());
123
+ renderDisplayList(stage, doc);
124
+ commands = doc.list.cmds.length;
125
+ }
126
+ const ms = performance.now() - t0;
127
+
128
+ document.getElementById("hudW").textContent = String(w);
129
+ document.getElementById("hudCmds").textContent = String(commands);
130
+ document.getElementById("hudMs").textContent = ms.toFixed(1);
131
+
132
+ // What a browser check needs, and what is worth seeing in a console: the
133
+ // numbers the layout produced, not the picture it produced them for.
134
+ window.__evgResponsive = {
135
+ width: w, height: doc.height, coarse, painter: painterMode,
136
+ commands, cssErrors: doc.cssErrors, ms, host: stats,
137
+ };
138
+ }
139
+
140
+ function setForced(w, button) {
141
+ forcedWidth = w;
142
+ for (const b of document.querySelectorAll("#hud button[data-w], #btnFull")) {
143
+ b.setAttribute("aria-pressed", String(b === button));
144
+ }
145
+ stage.style.maxWidth = w === null ? "" : w + "px";
146
+ stage.style.margin = w === null ? "" : "0 auto";
147
+ paint(true);
148
+ }
149
+
150
+ document.getElementById("btnFull").addEventListener("click", (e) => setForced(null, e.currentTarget));
151
+ for (const b of document.querySelectorAll("#hud button[data-w]")) {
152
+ b.addEventListener("click", (e) => setForced(Number(e.currentTarget.dataset.w), e.currentTarget));
153
+ }
154
+ document.getElementById("btnCoarse").addEventListener("click", (e) => {
155
+ coarse = !coarse;
156
+ e.currentTarget.setAttribute("aria-pressed", String(coarse));
157
+ paint(true);
158
+ });
159
+
160
+ // The resize path. A ResizeObserver on the stage rather than a window listener
161
+ // because the two differ exactly where it matters: the page can grow tall
162
+ // enough for a scrollbar, which takes ~15px off the width, and only the
163
+ // element knows that happened. The observer fires again, the page is laid out
164
+ // narrower, and it settles — `lastKey` is what stops it looping when nothing
165
+ // actually moved.
166
+ new ResizeObserver(() => paint(false)).observe(stage);
167
+ window.addEventListener("resize", () => paint(false));
168
+
169
+ try {
170
+ paint(true);
171
+ } catch (e) {
172
+ errBox.textContent = String((e && e.stack) || e);
173
+ window.__evgResponsive = { error: String(e) };
174
+ }
175
+ </script>
176
+ </body>
177
+ </html>
@@ -0,0 +1,221 @@
1
+ /**
2
+ * responsive/smoke.mjs — does the page relayout in a real browser?
3
+ *
4
+ * npm run evg:responsive:web && npm run evg:responsive:web:smoke
5
+ *
6
+ * `EvgResponsiveCheck` already proves the layout in Node. What only a browser
7
+ * can answer is the part in between: that the compiled engine loads, that a
8
+ * window resize actually re-runs it, and that what came out is an <svg> with
9
+ * the boxes in it — not a blank stage and a caught exception.
10
+ *
11
+ * The column count is read off the PAINTED rectangles, at their fill colour,
12
+ * so the assertion is about the picture and not about the display list that
13
+ * produced it.
14
+ */
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import http from "node:http";
18
+ import { fileURLToPath } from "node:url";
19
+ import { createRequire } from "node:module";
20
+
21
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
22
+ const ROOT = path.resolve(HERE, "../../../..");
23
+ const DIST = path.resolve(process.argv[2] || path.join(HERE, "dist"));
24
+
25
+ function loadPlaywright() {
26
+ const anchors = [
27
+ "/opt/node22/lib/node_modules/x",
28
+ path.join(ROOT, "package.json"),
29
+ path.join(process.cwd(), "package.json"),
30
+ import.meta.url,
31
+ ];
32
+ for (const anchor of anchors) {
33
+ for (const name of ["playwright", "playwright-core"]) {
34
+ try {
35
+ return createRequire(anchor)(name);
36
+ } catch { /* next */ }
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+
42
+ function findChrome() {
43
+ const fixed = [
44
+ process.env.CHROME_PATH,
45
+ "/opt/pw-browsers/chromium/chrome-linux/chrome",
46
+ "/usr/bin/chromium",
47
+ "/usr/bin/google-chrome",
48
+ "/usr/local/bin/google-chrome",
49
+ ].filter(Boolean);
50
+ for (const c of fixed) if (fs.existsSync(c)) return c;
51
+ return null;
52
+ }
53
+
54
+ const pw = loadPlaywright();
55
+ if (!pw) {
56
+ console.log("Playwright is not available — the responsive page was not checked.");
57
+ process.exit(0);
58
+ }
59
+ for (const f of ["index.html", "evg_responsive_demo.js", "evg-html.js"]) {
60
+ if (!fs.existsSync(path.join(DIST, f))) {
61
+ console.error(`no ${f} in ${path.relative(ROOT, DIST)} — run: npm run evg:responsive:web`);
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ const TYPES = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css" };
67
+ const server = http.createServer((req, res) => {
68
+ const rel = decodeURIComponent(new URL(req.url, "http://x").pathname).replace(/^\/+/, "") || "index.html";
69
+ const file = path.join(DIST, rel);
70
+ if (!file.startsWith(DIST) || !fs.existsSync(file)) {
71
+ res.writeHead(404).end("not found");
72
+ return;
73
+ }
74
+ res.writeHead(200, { "content-type": TYPES[path.extname(file)] || "application/octet-stream" });
75
+ res.end(fs.readFileSync(file));
76
+ });
77
+ await new Promise((r) => server.listen(0, "127.0.0.1", r));
78
+ const base = `http://127.0.0.1:${server.address().port}/`;
79
+
80
+ const failures = [];
81
+ const check = (name, cond, detail) => {
82
+ if (cond) console.log(" PASS " + name);
83
+ else {
84
+ console.log(" FAIL " + name + (detail ? " — " + detail : ""));
85
+ failures.push(name);
86
+ }
87
+ };
88
+
89
+ const launchOpts = {};
90
+ const chrome = findChrome();
91
+ if (chrome) launchOpts.executablePath = chrome;
92
+
93
+ const browser = await pw.chromium.launch(launchOpts);
94
+ const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
95
+ const pageErrors = [];
96
+ page.on("pageerror", (e) => pageErrors.push(String(e)));
97
+ page.on("console", (m) => { if (m.type() === "error") pageErrors.push(m.text()); });
98
+
99
+ await page.goto(base, { waitUntil: "load" });
100
+ await page.waitForFunction(() => window.__evgResponsive, null, { timeout: 20000 });
101
+
102
+ /** The cards are the only white boxes with a 12px radius; count the ones that
103
+ * share the topmost row. Same definition the Node check uses, read out of the
104
+ * DOM the painter actually built. */
105
+ const cardColumns = () => page.evaluate(() => {
106
+ const rects = [...document.querySelectorAll("#stage svg rect")]
107
+ .filter((r) => (r.getAttribute("fill") || "").replace(/\s+/g, "") === "rgb(255,255,255)"
108
+ && Math.abs(Number(r.getAttribute("rx") || 0) - 12) < 0.5)
109
+ .map((r) => ({ x: +r.getAttribute("x"), y: +r.getAttribute("y"), w: +r.getAttribute("width") }));
110
+ // The sidebar is a white 12px box too, and at wide widths it is the leftmost
111
+ // one on its own row. Cards are the boxes whose width repeats.
112
+ const byWidth = new Map();
113
+ for (const r of rects) {
114
+ const k = Math.round(r.w);
115
+ byWidth.set(k, (byWidth.get(k) || []).concat([r]));
116
+ }
117
+ let best = [];
118
+ for (const group of byWidth.values()) if (group.length > best.length) best = group;
119
+ const top = Math.min(...best.map((r) => r.y));
120
+ return best.filter((r) => Math.abs(r.y - top) < 1).length;
121
+ });
122
+
123
+ const state = () => page.evaluate(() => window.__evgResponsive);
124
+
125
+ async function atWidth(w) {
126
+ await page.setViewportSize({ width: w, height: 900 });
127
+ await page.waitForFunction(
128
+ (want) => window.__evgResponsive && Math.abs(window.__evgResponsive.width - want) < 30,
129
+ w,
130
+ { timeout: 20000 }
131
+ );
132
+ return state();
133
+ }
134
+
135
+ console.log("responsive page smoke");
136
+
137
+ let s = await state();
138
+ check("the engine ran in the browser", s && !s.error, s && s.error);
139
+ check("the stylesheet parsed clean", s.cssErrors === 0, `cssErrors=${s.cssErrors}`);
140
+ check("the page was painted", (await page.locator("#stage svg rect").count()) > 20);
141
+ check("EVG was given the document width", Math.abs(s.width - 1400) < 30, `width=${s.width}`);
142
+ check("1400px: four cards across", (await cardColumns()) === 4);
143
+
144
+ s = await atWidth(1000);
145
+ check("1000px: three cards across", (await cardColumns()) === 3, `width=${s.width}`);
146
+
147
+ s = await atWidth(760);
148
+ check("760px: two cards across", (await cardColumns()) === 2, `width=${s.width}`);
149
+
150
+ s = await atWidth(420);
151
+ check("420px: one card per row", (await cardColumns()) === 1, `width=${s.width}`);
152
+ check("420px: the page grew taller than the window", s.height > 900, `height=${s.height}`);
153
+
154
+ // The flicker. `.side` and `.main` are a flex row and `.main` is `flex: 1`, so
155
+ // its width is computed out of the line it is then measured against — and the
156
+ // sum coming back one ulp over made the row wrap, at 127 of the 680 integer
157
+ // widths between 821 and 1500, scattered. On a dragged window edge that is a
158
+ // page that jumps rather than a layout that responds (ISSUES #8).
159
+ //
160
+ // Read off the painted boxes, and by VERTICAL OVERLAP rather than by a shared
161
+ // top edge: `.main` opens with a heading, so the first card starts lower than
162
+ // the sidebar even when the two are side by side. Overlapping means one row;
163
+ // a card that starts below the sidebar's bottom edge means the row wrapped.
164
+ const sidebarBesideContent = () => page.evaluate(() => {
165
+ const boxes = [...document.querySelectorAll("#stage svg rect")]
166
+ .filter((r) => (r.getAttribute("fill") || "").replace(/\s+/g, "") === "rgb(255,255,255)"
167
+ && Math.abs(Number(r.getAttribute("rx") || 0) - 12) < 0.5)
168
+ .map((r) => ({ x: +r.getAttribute("x"), y: +r.getAttribute("y"),
169
+ w: +r.getAttribute("width"), h: +r.getAttribute("height") }));
170
+ if (boxes.length < 2) return false;
171
+ // The sidebar is the leftmost card-shaped box; the deck is everything that
172
+ // starts to the right of it.
173
+ const side = boxes.reduce((a, b) => (b.x < a.x ? b : a));
174
+ const deck = boxes.filter((b) => b.x > side.x + side.w - 1);
175
+ if (!deck.length) return false;
176
+ const top = Math.min(...deck.map((b) => b.y));
177
+ return top < side.y + side.h - 1;
178
+ });
179
+
180
+ // A sweep rather than a list of widths: the scrollbar takes an unknown few
181
+ // pixels off, so naming the widths that used to fail would be naming the wrong
182
+ // ones. Every step in this range is above the 820px breakpoint even after the
183
+ // scrollbar, so the sidebar must be beside the content at all of them.
184
+ {
185
+ let jumped = 0;
186
+ let firstBad = 0;
187
+ for (let w = 870; w <= 1110; w += 6) {
188
+ const st = await atWidth(w);
189
+ if (!(await sidebarBesideContent())) {
190
+ jumped += 1;
191
+ if (!firstBad) firstBad = st.width;
192
+ }
193
+ }
194
+ check("the sidebar stays beside the content at every width from 870 to 1110",
195
+ jumped === 0, `${jumped} widths wrapped, first at ${firstBad}`);
196
+ }
197
+
198
+ // Back up again: a layout that only narrows is half a layout.
199
+ s = await atWidth(1400);
200
+ check("back at 1400px: four cards across again", (await cardColumns()) === 4);
201
+
202
+ // The SVG is sized to the layout, which is what makes the document scroll
203
+ // rather than the picture shrink.
204
+ const svgBox = await page.evaluate(() => {
205
+ const svg = document.querySelector("#stage svg");
206
+ return { w: +svg.getAttribute("width"), h: +svg.getAttribute("height") };
207
+ });
208
+ check("the surface is the size EVG laid out", Math.abs(svgBox.w - s.width) < 1 && Math.abs(svgBox.h - s.height) < 1,
209
+ `svg=${svgBox.w}x${svgBox.h} state=${s.width}x${s.height}`);
210
+
211
+ check("no page errors", pageErrors.length === 0, pageErrors.slice(0, 3).join(" | "));
212
+
213
+ await browser.close();
214
+ server.close();
215
+
216
+ console.log("");
217
+ if (failures.length) {
218
+ console.log(`FAILED: ${failures.length} of the checks — ${failures.join(", ")}`);
219
+ process.exit(1);
220
+ }
221
+ console.log("OK");