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,135 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MIT
3
+ //
4
+ // Is the browser the thing measuring the page?
5
+ //
6
+ // npm run evg:measure:web
7
+ //
8
+ // `evg-measure.js` hands `EVGHostTextMeasurer` one function and every layout
9
+ // in the page is meant to measure with it. A page that silently fell back to
10
+ // the table would look the same to every other check — the picture is a
11
+ // picture either way — so this asks the pages themselves, in Chromium:
12
+ //
13
+ // * is the measurer installed and attached, and did the layout CALL it;
14
+ // * does a run measured through EVG come out at the width canvas
15
+ // `measureText` gives the same font shorthand, which is the width the
16
+ // painter draws it at — the claim the file exists to make good on;
17
+ // * are the face metrics the canvas's `fontBoundingBox*`, and is the line
18
+ // gap non-negative and taken from a real `line-height: normal` box.
19
+ //
20
+ // Two pages, because they take the measurer two ways: the RealTrainer page
21
+ // through an ES module bundle, the responsive page through a global.
22
+
23
+ import fs from "node:fs";
24
+ import path from "node:path";
25
+ import { fileURLToPath } from "node:url";
26
+ import { createServer } from "node:http";
27
+ import { requireDom, findChromium } from "../../../gallery/ui/conformance/dom-adapter.mjs";
28
+
29
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
30
+ const ROOT = path.resolve(HERE, "..", "..", "..");
31
+
32
+ const PAGES = [
33
+ {
34
+ name: "realtrainer (bundle)",
35
+ built: "gallery/realtrainer/web/bundle.js",
36
+ build: "npm run rt:page",
37
+ url: "/gallery/realtrainer/web/index.html?page=980x760",
38
+ ready: () => window.__app && window.__fontMeasure,
39
+ },
40
+ {
41
+ name: "responsive (global)",
42
+ built: "lib/evg/web/responsive/dist/index.html",
43
+ build: "npm run evg:responsive:web",
44
+ url: "/lib/evg/web/responsive/dist/index.html",
45
+ ready: () => window.__evgResponsive && window.__fontMeasure,
46
+ },
47
+ ];
48
+
49
+ for (const p of PAGES) {
50
+ if (!fs.existsSync(path.join(ROOT, p.built))) {
51
+ console.error(`${p.built} missing — run \`${p.build}\` first`);
52
+ process.exit(3);
53
+ }
54
+ }
55
+
56
+ const TYPES = {
57
+ ".html": "text/html", ".js": "text/javascript", ".mjs": "text/javascript",
58
+ ".cjs": "text/javascript", ".css": "text/css", ".json": "application/json",
59
+ ".png": "image/png", ".svg": "image/svg+xml",
60
+ };
61
+ const server = createServer((req, res) => {
62
+ const rel = decodeURIComponent(new URL(req.url, "http://x").pathname);
63
+ const file = path.join(ROOT, rel.slice(1));
64
+ if (!file.startsWith(ROOT) || !fs.existsSync(file) || fs.statSync(file).isDirectory()) {
65
+ res.writeHead(404).end("not found");
66
+ return;
67
+ }
68
+ res.writeHead(200, { "content-type": TYPES[path.extname(file)] || "application/octet-stream" })
69
+ .end(fs.readFileSync(file));
70
+ });
71
+ await new Promise((r) => server.listen(0, r));
72
+ const port = server.address().port;
73
+
74
+ const { chromium } = requireDom("playwright-core");
75
+ const browser = await chromium.launch({ executablePath: findChromium(), args: ["--use-gl=angle", "--use-angle=swiftshader"] });
76
+
77
+ let passed = 0, failed = 0;
78
+ const ok = (what, cond, detail = "") => {
79
+ if (cond) passed += 1; else failed += 1;
80
+ console.log(` ${cond ? "PASS" : "FAIL"} ${what}${cond || !detail ? "" : " (" + detail + ")"}`);
81
+ };
82
+
83
+ // What the page says about its measurer. Runs in the browser.
84
+ const probe = () => {
85
+ const h = window.__fontMeasure;
86
+ const m = h.measurers[0];
87
+ const c = document.createElement("canvas").getContext("2d");
88
+ const family = "sans-serif", size = 14, text = "Päiväkirja — 3x5@90kg";
89
+ c.font = `${size}px ${family}`;
90
+ const canvasW = c.measureText("‭" + text + "‬").width;
91
+ const fm = c.measureText("Hg");
92
+ const run = m.measureText(text, family, size);
93
+ c.font = `bold ${size}px ${family}`;
94
+ const canvasBoldW = c.measureText("‭" + text + "‬").width;
95
+ const bold = m.measureText(text, family + "-Bold", size);
96
+ return {
97
+ attached: m.isAttached(), host: m.hostName, key: m.measureKey(),
98
+ widthCalls: m.widthCalls, faceCalls: m.faceCalls,
99
+ evgW: run.width, canvasW, evgBoldW: bold.width, canvasBoldW,
100
+ asc: run.ascent, desc: run.descent, canvasAsc: fm.fontBoundingBoxAscent, canvasDesc: fm.fontBoundingBoxDescent,
101
+ lineH: m.getLineHeight(family, size),
102
+ // What the table would have said, for the record.
103
+ tableW: (typeof m.fallback === "object" && m.fallback) ? m.fallback.measureText(text, family, size).width : -1,
104
+ };
105
+ };
106
+
107
+ for (const p of PAGES) {
108
+ console.log(`--- ${p.name}`);
109
+ const page = await browser.newPage({ viewport: { width: 1200, height: 800 } });
110
+ const errors = [];
111
+ page.on("pageerror", (e) => errors.push(String(e)));
112
+ await page.goto(`http://127.0.0.1:${port}${p.url}`, { waitUntil: "networkidle" });
113
+ await page.waitForFunction(p.ready, null, { timeout: 20000 });
114
+ const r = await page.evaluate(probe);
115
+ ok("the measurer is installed and attached", r.attached && r.host === "canvas", JSON.stringify({ attached: r.attached, host: r.host }));
116
+ ok("the layout called the browser for widths", r.widthCalls > 0, `widthCalls=${r.widthCalls}`);
117
+ ok("and for faces, once per face", r.faceCalls > 0 && r.faceCalls % 3 === 0 && r.faceCalls <= r.widthCalls * 3, `faceCalls=${r.faceCalls}`);
118
+ ok("a run through EVG is the width the painter's canvas gives it", Math.abs(r.evgW - r.canvasW) < 0.001, `evg=${r.evgW} canvas=${r.canvasW}`);
119
+ ok("and a bold run, through the -Bold convention", Math.abs(r.evgBoldW - r.canvasBoldW) < 0.001 && r.evgBoldW !== r.evgW, `evg=${r.evgBoldW} canvas=${r.canvasBoldW}`);
120
+ ok("the ascent and descent are the face's", Math.abs(r.asc - r.canvasAsc) < 0.001 && Math.abs(r.desc - r.canvasDesc) < 0.001, `evg=${r.asc}/${r.desc} canvas=${r.canvasAsc}/${r.canvasDesc}`);
121
+ ok("line-height: normal is at least the face's box", r.lineH >= r.asc + r.desc - 0.001 && r.lineH < (r.asc + r.desc) * 1.3, `lineH=${r.lineH} asc+desc=${r.asc + r.desc}`);
122
+ ok("the key names the host", /^host:canvas:/.test(r.key), r.key);
123
+ ok("no page errors", errors.length === 0, errors.join("; "));
124
+ console.log(` (table would have said ${r.tableW.toFixed(2)}, the browser says ${r.canvasW.toFixed(2)})`);
125
+ await page.close();
126
+ }
127
+
128
+ await browser.close();
129
+ server.close();
130
+ console.log(`\npassed = ${passed} failed = ${failed}`);
131
+ if (failed > 0) {
132
+ console.log("FAILURES");
133
+ process.exit(1);
134
+ }
135
+ console.log("ALL PASS");
@@ -0,0 +1,207 @@
1
+ /**
2
+ * The painter's rotation pivot, checked in a real GPU context.
3
+ *
4
+ * node lib/evg/gl/rotation-check.mjs [--write-png out.png]
5
+ *
6
+ * `EVGTimingTest` proves the DISPLAY LIST carries the right origin. It cannot
7
+ * prove the shader turns anything about it — that lives in GLSL, and the only
8
+ * honest way to check GLSL is to run it and look at the pixels.
9
+ *
10
+ * The case that matters is a box AND its text. A text command's quad is sized
11
+ * to the ink, not to the line box it was laid out in, so its own centre is
12
+ * nowhere near the element's. Turn each command about its own centre and the
13
+ * words slide out of the box; turn both about the shared origin and they stay
14
+ * put. Every probe below is a point that is inside the box under one of those
15
+ * rules and outside it under the other.
16
+ *
17
+ * Exit code 0 when every probe reads the colour it should.
18
+ */
19
+
20
+ import fs from "node:fs";
21
+ import path from "node:path";
22
+ import { fileURLToPath, pathToFileURL } from "node:url";
23
+ import { requireDom, findChromium, assertDomInstalled } from "../../../gallery/ui/conformance/dom-adapter.mjs";
24
+
25
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
26
+ const BLUE = [37, 99, 235];
27
+ const W = 620;
28
+ const H = 260;
29
+
30
+ /**
31
+ * One 160x60 box with a label in it, turned about `origin` — given as a
32
+ * fraction of the box on each axis, so [0.5, 0.5] is the centre and [0, 0] is
33
+ * the top-left corner. What `transform-origin` resolves to, in other words;
34
+ * this file checks the painter, and the resolution itself is checked in
35
+ * `EVGTimingTest` against the browser's own numbers.
36
+ */
37
+ function panel(x, y, deg, origin = [0.5, 0.5]) {
38
+ const rot = deg ? { rot: deg, rox: x + 160 * origin[0], roy: y + 60 * origin[1] } : {};
39
+ return [
40
+ { k: 0, x, y, w: 160, h: 60, r: 8, c: [...BLUE, 1], ...rot },
41
+ { k: 3, x: x + 14, y: y + 18, w: 132, h: 24, text: "Rotated", size: 22,
42
+ font: "system-ui", c: [255, 255, 255, 1], ...rot },
43
+ ];
44
+ }
45
+
46
+ const LIST = {
47
+ width: W,
48
+ height: H,
49
+ cmds: [
50
+ ...panel(20, 30, 0),
51
+ ...panel(230, 30, 20),
52
+ ...panel(430, 30, 45),
53
+ // The same 45 degrees about the TOP-LEFT corner rather than the centre.
54
+ // Without a working origin this is indistinguishable from the panel above
55
+ // it, so it is what proves the field is read at all rather than being
56
+ // carried around and ignored.
57
+ ...panel(120, 150, 45, [0, 0]),
58
+ ],
59
+ };
60
+
61
+ // Each probe names a point and what must be there. The centre of a turned box
62
+ // is still its centre — that is the whole claim — and the corner of the page
63
+ // must stay empty, which is what fails when a rotation is applied about the
64
+ // page origin instead.
65
+ const PROBES = [
66
+ { at: [100, 60], want: "blue", why: "the unturned box is where it was put" },
67
+ { at: [310, 60], want: "blue", why: "a 20 degree turn keeps its own centre" },
68
+ { at: [510, 60], want: "blue", why: "and so does a 45 degree turn" },
69
+ // "Blank" is ALPHA zero, not white: the canvas clears to transparent and the
70
+ // page's white shows through it. Reading the colour channels here would
71
+ // compare against a background this canvas never painted.
72
+ { at: [5, 250], want: "blank", why: "nothing was flung to the page corner" },
73
+ { at: [610, 250], want: "blank", why: "nor to the other one" },
74
+ // Turned about its top-left corner, the second panel's own corner stays put
75
+ // while the rest of it swings down and right. Its centre therefore is NOT
76
+ // where a centre-pivoted turn would leave it.
77
+ // Just inside the pivoted corner, not on it: the box has an 8px radius and
78
+ // an antialiased edge, so the corner pixel itself is legitimately empty.
79
+ { at: [120, 167], want: "blue", why: "a corner pivot leaves the corner where it was" },
80
+ { at: [120, 210], want: "blue", why: "and the box now hangs below that corner" },
81
+ { at: [190, 180], want: "blank", why: "instead of sitting where a centre pivot would put it" },
82
+ ];
83
+
84
+ const painter = fs.readFileSync(path.join(HERE, "evg-webgl.js"), "utf8").replace(/^export /gm, "");
85
+
86
+ const PAGE = `<!doctype html><meta charset="utf-8"><body style="margin:0;background:#fff">
87
+ <canvas id="c" width="${W}" height="${H}" style="width:${W}px;height:${H}px"></canvas>
88
+ <script>
89
+ ${painter}
90
+ (async () => {
91
+ try {
92
+ const c = document.getElementById("c");
93
+ const gl = c.getContext("webgl2", { antialias: true, preserveDrawingBuffer: true });
94
+ await document.fonts.ready;
95
+ window.__stats = renderDisplayList(gl, { list: ${JSON.stringify(LIST)}, width: ${W}, height: ${H} }, { dpr: 1 });
96
+ } catch (e) { window.__ERR__ = String(e && e.stack || e); }
97
+ window.__DONE__ = true;
98
+ })();
99
+ </script></body>`;
100
+
101
+ assertDomInstalled();
102
+ const { chromium } = requireDom("playwright-core");
103
+ // SwiftShader: this has to run on a machine with no GPU, and a check that only
104
+ // runs on someone's laptop is not a check.
105
+ const browser = await chromium.launch({
106
+ executablePath: findChromium(),
107
+ args: ["--use-gl=swiftshader", "--enable-unsafe-swiftshader"],
108
+ });
109
+ const page = await browser.newPage({ viewport: { width: W + 40, height: H + 40 }, deviceScaleFactor: 2 });
110
+ const pageErrors = [];
111
+ page.on("pageerror", (e) => pageErrors.push(String(e.message)));
112
+
113
+ const tmp = path.join(HERE, ".rotation-check.html");
114
+ fs.writeFileSync(tmp, PAGE);
115
+ try {
116
+ await page.goto(pathToFileURL(tmp).href);
117
+ await page.waitForFunction("window.__DONE__ === true", { timeout: 30000 });
118
+ const err = await page.evaluate(() => window.__ERR__ || null);
119
+ if (err) throw new Error("painter threw: " + err);
120
+
121
+ const write = process.argv.indexOf("--write-png");
122
+ if (write > -1 && process.argv[write + 1]) {
123
+ await page.locator("#c").screenshot({ path: process.argv[write + 1] });
124
+ }
125
+
126
+ const read = await page.evaluate((probes) => {
127
+ const c = document.getElementById("c");
128
+ const gl = c.getContext("webgl2");
129
+ const px = new Uint8Array(4);
130
+ return probes.map(([x, y]) => {
131
+ // WebGL reads from the bottom left; the display list is y-down.
132
+ gl.readPixels(x, c.height - y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, px);
133
+ return [px[0], px[1], px[2], px[3]];
134
+ });
135
+ }, PROBES.map((p) => p.at));
136
+
137
+ // Where the WORDS ended up in the 45-degree panel, as the centroid of the
138
+ // white ink. This is the probe that actually separates the two pivots.
139
+ //
140
+ // The painter sizes a text quad to the INK, not to the line box the command
141
+ // carries, so "Rotated" sits left of centre in a 160-wide panel and its own
142
+ // centre is about 20px from the element's. Turned about the shared origin
143
+ // that offset swings up and to the left; turned about itself the ink stays
144
+ // put while the box moves out from under it. Twenty pixels of disagreement,
145
+ // in a place a box-centre probe cannot see.
146
+ const ink = await page.evaluate((box) => {
147
+ const c = document.getElementById("c");
148
+ const gl = c.getContext("webgl2");
149
+ const buf = new Uint8Array(c.width * c.height * 4);
150
+ gl.readPixels(0, 0, c.width, c.height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
151
+ let sx = 0, sy = 0, n = 0;
152
+ for (let y = 0; y < c.height; y++) {
153
+ for (let x = box[0]; x < box[1]; x++) {
154
+ const i = (((c.height - 1 - y) * c.width) + x) * 4;
155
+ // White and opaque: the glyphs. The panel behind them is strong blue,
156
+ // so the two do not blur into each other at any antialiased edge.
157
+ if (buf[i + 3] > 200 && buf[i] > 200 && buf[i + 1] > 200 && buf[i + 2] > 200) {
158
+ sx += x; sy += y; n += 1;
159
+ }
160
+ }
161
+ }
162
+ return n ? { x: sx / n, y: sy / n, n } : null;
163
+ }, [380, 620]);
164
+
165
+ let failed = 0;
166
+ console.log("EVG WebGL painter — rotation about a named origin\n");
167
+ for (let i = 0; i < PROBES.length; i++) {
168
+ const p = PROBES[i];
169
+ const got = read[i];
170
+ // Antialiasing makes an exact match the wrong test; "which of the two
171
+ // colours is this nearer" is the question actually being asked.
172
+ const near = (a, b) => Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]) < 60;
173
+ const ok = p.want === "blue" ? (got[3] > 200 && near(got, BLUE)) : got[3] < 20;
174
+ if (!ok) failed += 1;
175
+ console.log(` ${ok ? "PASS" : "FAIL"} ${p.why} — (${p.at}) is rgba(${got})`);
176
+ }
177
+ if (!ink) {
178
+ failed += 1;
179
+ console.log(" FAIL no white ink found in the turned panel at all");
180
+ } else {
181
+ // Expected under the shared pivot. Derived from the geometry, not from a
182
+ // previous run: the ink's own centre offset from the element centre,
183
+ // swung 45 degrees about that centre.
184
+ const okX = Math.abs(ink.x - 495) < 6;
185
+ const okY = Math.abs(ink.y - 45) < 6;
186
+ if (!okX || !okY) failed += 1;
187
+ console.log(
188
+ ` ${okX && okY ? "PASS" : "FAIL"} the words were carried round the ELEMENT centre, ` +
189
+ `not spun about their own — ink at (${ink.x.toFixed(1)}, ${ink.y.toFixed(1)}), ` +
190
+ `expected near (495, 45); spun in place would be near (489, 60)`,
191
+ );
192
+ }
193
+ if (pageErrors.length) {
194
+ failed += 1;
195
+ for (const e of pageErrors) console.log(" FAIL page error: " + e);
196
+ }
197
+ console.log("");
198
+ if (failed) {
199
+ console.log(`RESULT FAIL — ${failed}`);
200
+ process.exitCode = 1;
201
+ } else {
202
+ console.log("RESULT OK");
203
+ }
204
+ } finally {
205
+ await browser.close();
206
+ fs.rmSync(tmp, { force: true });
207
+ }
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MIT
3
+ //
4
+ // Every draw in the WebGL painter has to honour the layer shift.
5
+ //
6
+ // node lib/evg/gl/shift-check.mjs
7
+ //
8
+ // A SCROLL DOES NOT REBUILD THE FRAME. The painter keeps what it uploaded and
9
+ // moves it with a uniform — `uShift`, one per shader, set from `curShift`
10
+ // before each run. That is the whole reason a scroll frame costs a fraction of
11
+ // a millisecond, and it has one failure mode: a draw that forgets the uniform
12
+ // goes on painting where the frame was BUILT while everything around it moves.
13
+ //
14
+ // It is invisible until you scroll, it needs a GPU to see, and there is no
15
+ // headless oracle for it. It happened: `drawFill` never set the path shader's
16
+ // shift, so on the diary's charts the area under the curve stayed behind while
17
+ // the curve, the card and the page moved — for a whole scroll, until something
18
+ // forced the list to be built again.
19
+ //
20
+ // So the rule is checked as text, which needs no GPU: every `drawX` in the
21
+ // paint loop, and the batch draw itself, must mention `curShift`. That is
22
+ // coarse on purpose — it cannot tell a correct shift from a wrong one — but
23
+ // the mistake it catches is the one that actually happens: forgetting it
24
+ // entirely in a function written beside three that do it.
25
+ //
26
+ // Exit code 0 when every draw accounts for the shift.
27
+
28
+ import fs from "node:fs";
29
+ import path from "node:path";
30
+ import { fileURLToPath } from "node:url";
31
+
32
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
33
+ const FILE = path.join(HERE, "evg-webgl.js");
34
+ const src = fs.readFileSync(FILE, "utf8");
35
+ const lines = src.split("\n");
36
+
37
+ let failed = 0;
38
+ const ok = (name, cond, detail) => {
39
+ if (cond) console.log(" PASS " + name);
40
+ else {
41
+ failed += 1;
42
+ console.log(" FAIL " + name + (detail === undefined ? "" : " — " + detail));
43
+ }
44
+ };
45
+
46
+ // The painter's own draw helpers: `const drawSomething = (…) => {`.
47
+ const draws = [];
48
+ for (let i = 0; i < lines.length; i += 1) {
49
+ const m = /^\s*const (draw[A-Za-z0-9_]*)\s*=\s*\(/.exec(lines[i]);
50
+ if (!m) continue;
51
+ // The body, by brace depth from this line.
52
+ let depth = 0;
53
+ let started = false;
54
+ const body = [];
55
+ for (let j = i; j < lines.length; j += 1) {
56
+ for (const ch of lines[j]) {
57
+ if (ch === "{") { depth += 1; started = true; }
58
+ else if (ch === "}") depth -= 1;
59
+ }
60
+ body.push(lines[j]);
61
+ if (started && depth <= 0) break;
62
+ }
63
+ draws.push({ name: m[1], line: i + 1, body: body.join("\n") });
64
+ }
65
+
66
+ // The draws that are NOT in a layer, each with the reason. A post-pass runs
67
+ // over the finished surface: the layer's shift is in those pixels already, and
68
+ // a pass that applied it again would move the effect twice — once with the
69
+ // page and once more on top of it. Each one is named here so that leaving a
70
+ // draw out of the rule stays a decision rather than an omission.
71
+ const EXPECTED_SHIFTLESS = {
72
+ drawLegacyRipple: "a post-pass over the finished surface — the shift is in the texture",
73
+ };
74
+
75
+ console.log("--- the painter's draws ---");
76
+ ok("there are draws to check", draws.length >= 3, draws.length + " found");
77
+ for (const d of draws) {
78
+ if (d.name in EXPECTED_SHIFTLESS) {
79
+ ok(`${d.name} is deliberately outside a layer`,
80
+ !d.body.includes("curShift"),
81
+ `evg-webgl.js:${d.line} mentions curShift, but it is listed as ` +
82
+ `shiftless: ${EXPECTED_SHIFTLESS[d.name]}`);
83
+ continue;
84
+ }
85
+ ok(`${d.name} accounts for the layer shift`,
86
+ d.body.includes("curShift"),
87
+ `evg-webgl.js:${d.line} draws without mentioning curShift`);
88
+ }
89
+
90
+ console.log("");
91
+ console.log("--- and the shaders that carry it ---");
92
+ // Both programs declare the uniform; both must have a location read for it,
93
+ // or a draw that "sets the shift" would be writing to nothing.
94
+ const shifts = (src.match(/uniform vec2 uShift;/g) || []).length;
95
+ ok("both shaders declare uShift", shifts === 2, shifts + " declarations");
96
+ ok("the batch shader's location is read", /uShift:\s*gl\.getUniformLocation/.test(src));
97
+ ok("the path shader's location is read", /pathShiftLoc:\s*gl\.getUniformLocation/.test(src));
98
+ // …and every location that is read is written somewhere, or it is decoration.
99
+ for (const loc of ["built.uShift", "built.pathShiftLoc"]) {
100
+ const uses = src.split(`gl.uniform2f(${loc},`).length - 1;
101
+ ok(`${loc} is set by a draw`, uses >= 1, "never passed to uniform2f");
102
+ }
103
+
104
+ console.log("");
105
+ if (failed > 0) {
106
+ console.log(` ${failed} check(s) failed`);
107
+ process.exit(1);
108
+ }
109
+ console.log(" every draw in the painter moves with its layer");
110
+ // The marker `scripts/run-gallery-editor-tests.sh` greps for. The compiler
111
+ // prints `[FAIL]` and still exits 0, so that runner refuses to take a zero
112
+ // exit as a pass — a suite has to SAY it passed.
113
+ console.log("ALL PASS");
@@ -0,0 +1,168 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // A stroke's corners, ends and dashes, measured rather than looked at.
4
+ //
5
+ // node lib/evg/gl/stroke-check.mjs
6
+ //
7
+ // The painter turns a polyline into triangles, and until the joins existed
8
+ // it turned it into a chain of SEPARATE BARS: two rectangles meeting at an
9
+ // angle cover the inside of the turn twice and the outside not at all, so a
10
+ // thick line came apart at every corner. That is invisible in a unit test
11
+ // that counts commands and obvious on a page, which is the combination that
12
+ // keeps a bug.
13
+ //
14
+ // So this measures area. An L of two 100-long segments at width 20 is two
15
+ // 2,000 quads and a corner; what goes in the corner is exactly what the
16
+ // join is, and each of them has a number that can be worked out on paper:
17
+ //
18
+ // miter, 90° the 10 x 10 square the quads left empty 100
19
+ // bevel the triangle across it 50
20
+ // round a disc of radius 10 at each of the three vertices
21
+ // square cap half a width added to each open end 2 x 200
22
+ //
23
+ // Areas are summed with the shoelace formula over every triangle, so an
24
+ // overlap counts twice — which is why the round numbers below are ranges
25
+ // and the flat ones are exact.
26
+
27
+ import { strokeTriangles, dashRings, parseDash } from "./evg-webgl.js";
28
+
29
+ let passed = 0;
30
+ let failed = 0;
31
+ const ok = (name, cond, detail) => {
32
+ if (cond) {
33
+ passed += 1;
34
+ console.log(" PASS " + name + (detail ? " (" + detail + ")" : ""));
35
+ } else {
36
+ failed += 1;
37
+ console.log(" FAIL " + name + (detail ? " (" + detail + ")" : ""));
38
+ }
39
+ };
40
+ const near = (a, b, eps) => Math.abs(a - b) <= eps;
41
+
42
+ /** The area of a triangle soup, overlaps counted twice. */
43
+ function area(tris) {
44
+ let a = 0;
45
+ for (let i = 0; i + 5 < tris.length; i += 6) {
46
+ a += Math.abs(
47
+ (tris[i + 2] - tris[i]) * (tris[i + 5] - tris[i + 1]) -
48
+ (tris[i + 4] - tris[i]) * (tris[i + 3] - tris[i + 1]),
49
+ ) / 2;
50
+ }
51
+ return a;
52
+ }
53
+
54
+ /** Is any triangle covering this point? */
55
+ function covers(tris, px, py) {
56
+ const side = (ax, ay, bx, by) => (bx - ax) * (py - ay) - (by - ay) * (px - ax);
57
+ for (let i = 0; i + 5 < tris.length; i += 6) {
58
+ const d1 = side(tris[i], tris[i + 1], tris[i + 2], tris[i + 3]);
59
+ const d2 = side(tris[i + 2], tris[i + 3], tris[i + 4], tris[i + 5]);
60
+ const d3 = side(tris[i + 4], tris[i + 5], tris[i], tris[i + 1]);
61
+ const neg = d1 < 0 || d2 < 0 || d3 < 0;
62
+ const pos = d1 > 0 || d2 > 0 || d3 > 0;
63
+ if (!(neg && pos)) return true;
64
+ }
65
+ return false;
66
+ }
67
+
68
+ const L = [0, 0, 100, 0, 100, 100];
69
+ const LINE = [0, 0, 100, 0];
70
+ const BOX = [0, 0, 100, 0, 100, 100, 0, 100, 0, 0];
71
+ const W = 20;
72
+
73
+ console.log("=== stroke caps, joins and dashes ===");
74
+
75
+ // --- the corner is filled, and by the right amount ------------------------
76
+ {
77
+ const miter = strokeTriangles([L], W, 0, 0);
78
+ ok("a miter corner is the square the quads left empty", near(area(miter), 4100, 0.01), area(miter).toFixed(1));
79
+ const bevel = strokeTriangles([L], W, 0, 2);
80
+ ok("a bevel is the triangle across it", near(area(bevel), 4050, 0.01), area(bevel).toFixed(1));
81
+ const round = strokeTriangles([L], W, 0, 1);
82
+ ok("a round join is a disc at the vertex", area(round) > 4200 && area(round) < 5000, area(round).toFixed(1));
83
+ }
84
+
85
+ // --- the outside of the turn is actually covered --------------------------
86
+ {
87
+ const butt = strokeTriangles([L], W, 0, 99); // an unknown join: bevel only
88
+ // The far corner of the turn, a whisker inside the outer edge.
89
+ ok("the outer corner is covered", covers(strokeTriangles([L], W, 0, 0), 109, -9));
90
+ ok("and was not before a join existed", !covers(
91
+ // What the painter drew before: the two quads alone.
92
+ strokeTriangles([[0, 0, 100, 0]], W, 0, 0).concat(strokeTriangles([[100, 0, 100, 100]], W, 0, 0)),
93
+ 109, -9,
94
+ ));
95
+ ok("an unknown join still bevels rather than gapping", covers(butt, 105, -5));
96
+ }
97
+
98
+ // --- caps ------------------------------------------------------------------
99
+ {
100
+ const butt = strokeTriangles([LINE], W, 0, 0);
101
+ ok("a butt cap stops where the line stops", near(area(butt), 2000, 0.01), area(butt).toFixed(1));
102
+ ok("and draws nothing past it", !covers(butt, 105, 0));
103
+ const square = strokeTriangles([LINE], W, 2, 0);
104
+ ok("a square cap runs on by half a width", near(area(square), 2400, 0.01), area(square).toFixed(1));
105
+ ok("which covers the point past the end", covers(square, 105, 0));
106
+ ok("but not two widths past it", !covers(square, 115, 0));
107
+ const round = strokeTriangles([LINE], W, 1, 0);
108
+ ok("a round cap is a disc at each end", area(round) > 2400 && area(round) < 2700, area(round).toFixed(1));
109
+ ok("round covers just past the end", covers(round, 105, 0));
110
+ ok("and not the corner a square cap would fill", !covers(round, 109, 9));
111
+ }
112
+
113
+ // --- a closed ring has joins, not caps ------------------------------------
114
+ {
115
+ const closed = strokeTriangles([BOX], W, 1, 0);
116
+ ok("a closed ring gets no cap blob on its seam", near(area(closed), 4 * 2000 + 4 * 100, 0.01), area(closed).toFixed(1));
117
+ ok("and its seam corner is filled", covers(closed, -9, -9));
118
+ }
119
+
120
+ // --- a near-reversal does not grow a spike --------------------------------
121
+ {
122
+ const hairpin = [0, 0, 100, 0, 0, 1];
123
+ const t = strokeTriangles([hairpin], W, 0, 0);
124
+ let far = 0;
125
+ for (let i = 0; i < t.length; i += 2) far = Math.max(far, t[i]);
126
+ ok("the miter is cut back at the limit", far < 100 + 4 * (W / 2) + 1, "furthest x " + far.toFixed(1));
127
+ }
128
+
129
+ // --- a degenerate ring is not a crash -------------------------------------
130
+ {
131
+ ok("a ring with one point draws nothing", strokeTriangles([[5, 5]], W, 1, 1).length === 0);
132
+ ok("a zero-length segment draws nothing", strokeTriangles([[5, 5, 5, 5]], W, 1, 1).length === 0);
133
+ ok("and no rings at all is no triangles", strokeTriangles([], W, 1, 1).length === 0);
134
+ }
135
+
136
+ // --- dashes ---------------------------------------------------------------
137
+ {
138
+ const line = [[0, 0, 100, 0]];
139
+ ok("a pattern of two cuts the line", dashRings(line, [4, 4], 0).length === 13);
140
+ // An ODD list runs twice: "10" is 10 on and 10 off, not 10 on and nothing
141
+ // off — which would draw a solid line and read as a missing feature.
142
+ const single = dashRings(line, [10], 0);
143
+ ok("an odd pattern runs twice", single.length === 5, JSON.stringify(single[0]) + " …");
144
+ ok("and its first dash is the pattern long", single[0][2] === 10);
145
+ ok("with a gap of the same", single[1][0] === 20);
146
+ const off = dashRings(line, [10], 5);
147
+ ok("an offset starts part-way into the pattern", off[0][2] === 5, JSON.stringify(off[0]));
148
+ ok("a pattern of nothing is the line as it was", dashRings(line, [], 0) === line);
149
+ ok("and so is a pattern of zeroes", dashRings(line, [0, 0], 0) === line);
150
+
151
+ // A dash keeps its place in the pattern around a corner rather than
152
+ // restarting at the bend, which is what a browser does and what makes a
153
+ // dashed box look regular.
154
+ const corner = dashRings([[0, 0, 10, 0, 10, 10]], [6, 4], 0);
155
+ ok("a dash carries on round a corner", corner.length === 2, JSON.stringify(corner));
156
+
157
+ ok("the text is read as numbers", JSON.stringify(parseDash("6 4")) === "[6,4]");
158
+ ok("commas too", JSON.stringify(parseDash("2,3")) === "[2,3]");
159
+ ok("and nonsense is dropped", JSON.stringify(parseDash("none")) === "[]");
160
+ }
161
+
162
+ console.log("");
163
+ console.log(`passed=${passed} failed=${failed}`);
164
+ if (failed > 0) {
165
+ console.log("SOME FAILED");
166
+ process.exit(1);
167
+ }
168
+ console.log("ALL PASS");