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,835 @@
1
+ # EVG Layout Engine Known Issues
2
+
3
+ ## Issue #1: Text/Label Elements Don't Auto-Size Width Based on Content
4
+
5
+ **Status:** Resolved
6
+ **Severity:** Medium
7
+ **Found:** December 19, 2025
8
+ **Resolved:** August 14, 2026
9
+ **Component:** EVGLayout.rgr
10
+
11
+ ### Resolution
12
+
13
+ Text leaf nodes shrink-wrap to their measured content in `EVGLayout.layoutElement`
14
+ and `estimateChildWidth`, so a `<Label>` no longer claims the full parent width
15
+ in a `flexDirection="row"`.
16
+
17
+ Two follow-ups were needed before the fix could be trusted, both landed with the
18
+ font-correctness work:
19
+
20
+ - The measurement passed a hardcoded `"Helvetica"` for every string, so the
21
+ shrink-wrapped width was right in shape but wrong in size for any other face.
22
+ It now measures with the element's own `fontFamily`.
23
+ - The measurement itself was a `fontSize * 0.55` guess. Layout now measures with
24
+ the same TTF the output embeds, checked against a browser in
25
+ `gallery/pdf_writer/test/font_parity.js`.
26
+
27
+ Covered by `evg_test`: "text label shrink-wraps (not full width)",
28
+ "sibling stays on the same row", "sibling sits right after the label", and
29
+ "layout measured with the element's family". The original report below is kept
30
+ for history and no longer describes current behavior.
31
+
32
+ ### Description
33
+
34
+ Text and Label elements in the EVG layout engine do not calculate their width based on text content. Instead, they default to taking the full parent width, which causes layout problems when using `flexDirection="row"`.
35
+
36
+ ### Problem
37
+
38
+ When laying out elements horizontally with `flexDirection="row"`, text elements without an explicit width will:
39
+
40
+ 1. Take the full available parent width
41
+ 2. Push subsequent elements to the next row
42
+ 3. Ignore their actual text content width
43
+
44
+ This makes it impossible to have inline layouts like:
45
+
46
+ ```tsx
47
+ <View flexDirection="row">
48
+ <Label>Some text</Label> {/* Takes full width */}
49
+ <Image src="icon.jpg" /> {/* Wraps to next line */}
50
+ </View>
51
+ ```
52
+
53
+ ### Root Cause
54
+
55
+ In `EVGLayout.rgr`, the `layoutElement` function (lines 150-172) only calculates **height** for text elements based on text wrapping and line count. It does not calculate **width** based on text content.
56
+
57
+ Additionally, in `layoutChildren` (lines 240-245), when an element has:
58
+
59
+ - No explicit width (`width.isSet == false`)
60
+ - No flex value (`flex == 0`)
61
+
62
+ The layout engine treats it as "taking full width":
63
+
64
+ ```ranger
65
+ ; No width and no flex - treat as taking full width (will wrap)
66
+ fixedWidth = fixedWidth + innerWidth + c.box.marginLeftPx + c.box.marginRightPx
67
+ ```
68
+
69
+ This assumption works for block-level elements but fails for inline text.
70
+
71
+ ### Current Workaround
72
+
73
+ ## Issue #2: JSX Tokenizer Creates Separate Tokens for Words
74
+
75
+ **Status:** Resolved (December 19, 2025)
76
+ **Severity:** High
77
+ **Component:** ts_parser_simple.rgr, ComponentEngine.rgr
78
+
79
+ ### Description
80
+
81
+ The JSX text tokenizer was splitting multi-word text content into separate tokens (one per word), losing whitespace between words. This caused text like "Showcasing custom fonts" to render as "Showcasingcustomfonts".
82
+
83
+ ### Root Cause
84
+
85
+ The TypeScript parser's lexer tokenizes based on whitespace, creating separate tokens for each word. When the JSX parser processes text content, each word becomes a separate `JSXText` node without the original spacing.
86
+
87
+ The `ComponentEngine.evaluateTextContent` function was normalizing and trimming each individual token before concatenating, which removed the information about word boundaries.
88
+
89
+ ### Solution
90
+
91
+ Modified `ComponentEngine.evaluateTextContent` to:
92
+
93
+ 1. Accumulate all JSXText tokens with spaces between them (since they were originally separated by whitespace)
94
+ 2. Concatenate raw token values first: `result = result + " " + rawText`
95
+ 3. Apply normalization and trimming to the **complete** accumulated text
96
+ 4. This preserves word boundaries while still handling newlines and extra whitespace correctly
97
+
98
+ **Fixed in:** ComponentEngine.rgr, `evaluateTextContent` function (lines 738-780)
99
+
100
+ ---
101
+
102
+ ## Issue #3: SVG Path Elements Not Implemented
103
+
104
+ **Status:** Open
105
+ **Severity:** Medium
106
+ **Found:** December 19, 2025 (via test_features.tsx)
107
+ **Component:** ComponentEngine.rgr, EVGElement.rgr, EVGPDFRenderer.rgr
108
+
109
+ ### Description
110
+
111
+ SVG `<Path>` elements are not implemented in the EVG component system. When used in TSX files, they are treated as unknown components and rendered as empty `<div>` elements.
112
+
113
+ ### Problem
114
+
115
+ When attempting to use SVG paths for icons or vector graphics:
116
+
117
+ ```tsx
118
+ <Path
119
+ d="M10,6.5c-2.2,0-4,1.8-4,4s1.8,4,4,4s4-1.8,4-4S12.2,6.5,10,6.5"
120
+ width="80"
121
+ height="80"
122
+ viewBox="0 0 20 20"
123
+ backgroundColor="#27ae60"
124
+ />
125
+ ```
126
+
127
+ The system outputs:
128
+
129
+ - **Warning:** "Unknown component: Path"
130
+ - Renders as: `<div>` with no visual output
131
+
132
+ ### Impact
133
+
134
+ - Cannot render vector icons or SVG graphics
135
+ - Must use raster images instead (PNG/JPG)
136
+ - Limits design flexibility for scalable icons and shapes
137
+
138
+ ### Required Implementation
139
+
140
+ 1. **Add Path to evg_types.tsx:**
141
+
142
+ ```tsx
143
+ export function Path(props: PathProps): JSX.Element;
144
+
145
+ interface PathProps extends EVGStyle {
146
+ d: string; // SVG path data
147
+ svgPath?: string; // Alias for d
148
+ viewBox?: string; // ViewBox for scaling
149
+ fill?: Color; // Fill color
150
+ stroke?: Color; // Stroke color
151
+ strokeWidth?: number; // Stroke width
152
+ }
153
+ ```
154
+
155
+ 2. **Update ComponentEngine.rgr:** Add "path" to known element types (currently recognizes: View, Label, Image, Section, Page, Print)
156
+
157
+ 3. **Implement path rendering in EVGPDFRenderer.rgr:** Parse SVG path commands (M, L, C, Z, etc.) and render using PDF drawing primitives
158
+
159
+ 4. **Add path rendering to EVGElement.rgr:** Store path data (d attribute, viewBox) as element properties
160
+
161
+ ### Workaround
162
+
163
+ Use raster image formats (PNG, JPG) for icons and graphics instead of vector SVG paths.
164
+
165
+ ---
166
+
167
+ Explicitly set a width percentage or fixed width on text elements in row layouts:
168
+
169
+ ```tsx
170
+ <View flexDirection="row">
171
+ <Label width="80%">Some text</Label>
172
+ <Image src="icon.jpg" width={20} height={20} />
173
+ </View>
174
+ ```
175
+
176
+ Or use flex values:
177
+
178
+ ```tsx
179
+ <View flexDirection="row">
180
+ <Label flex={1}>Some text</Label>
181
+ <Image src="icon.jpg" width={20} height={20} />
182
+ </View>
183
+ ```
184
+
185
+ ### Proper Solution
186
+
187
+ Text/Label elements should calculate their intrinsic width based on:
188
+
189
+ 1. Text content length
190
+ 2. Font size and family
191
+ 3. Font metrics from the text measurer
192
+
193
+ The layout algorithm should:
194
+
195
+ 1. Check if element is a text/label type
196
+ 2. If no explicit width is set, measure the text content
197
+ 3. Use the measured width instead of defaulting to parent width
198
+ 4. Still respect `maxWidth` constraints for wrapping
199
+
200
+ ### Suggested Code Changes
201
+
202
+ In `EVGLayout.rgr`, around line 290-300, add text width measurement:
203
+
204
+ ```ranger
205
+ ; Calculate child dimensions
206
+ def childWidth:double innerWidth
207
+ if child.width.isSet {
208
+ childWidth = child.width.pixels
209
+ } {
210
+ ; NEW: For text elements, measure content width
211
+ if ((child.tagName == "text") || (child.tagName == "span")) {
212
+ def fontSize:double child.inheritedFontSize
213
+ if child.fontSize.isSet {
214
+ fontSize = child.fontSize.pixels
215
+ }
216
+ if (fontSize <= 0.0) {
217
+ fontSize = 14.0
218
+ }
219
+ def fontFamily:string child.inheritedFontFamily
220
+ childWidth = (measurer.measureTextWidth(child.textContent fontFamily fontSize))
221
+ ; Add some padding for safety
222
+ childWidth = childWidth + 4.0
223
+ } {
224
+ ; Check if this child has a calculated flex width
225
+ if (child.calculatedFlexWidth > 0.0) {
226
+ childWidth = child.calculatedFlexWidth
227
+ }
228
+ }
229
+ }
230
+ ```
231
+
232
+ ### Impact
233
+
234
+ - **High**: Affects all horizontal layouts with text
235
+ - **Workaround Available**: Yes (explicit width or flex)
236
+ - **Breaking Change**: Potentially, as existing layouts may rely on current behavior
237
+
238
+ ### Related Code
239
+
240
+ - `lib/evg/EVGLayout.rgr` - Lines 200-400 (layoutChildren function)
241
+ - `lib/evg/EVGTextMeasurer.rgr` - Text measurement utilities
242
+ - `gallery/pdf_writer/FontManager.rgr` - Font metrics for accurate measurement
243
+
244
+ ### Test Case
245
+
246
+ See `gallery/pdf_writer/components/ListItem.tsx` for a component that demonstrates this issue.
247
+
248
+ ### Notes
249
+
250
+ - The current behavior may be intentional for some use cases (e.g., full-width text blocks)
251
+ - A proper fix should distinguish between "inline" and "block" text elements
252
+ - Consider adding a `display: inline` or similar property to control this behavior
253
+ - Text measurement requires access to font metrics (FontManager in PDF writer context)
254
+
255
+ ---
256
+
257
+ ## Issue #4: The scene binary's record width was agreed in advance, not published
258
+
259
+ **Status:** Resolved
260
+ **Severity:** High — every field after the first command read from the wrong offset
261
+ **Found:** August 30, 2026 (in CI, on master)
262
+ **Resolved:** August 30, 2026
263
+ **Component:** `EVGDisplayList.rgr`, `gallery/pptx/web/host/pptx-host.mjs`
264
+
265
+ ### What happened
266
+
267
+ `EVGDisplayList` publishes a frame as three `Int32Array`s: one fixed-size record
268
+ per draw command, then the ring coordinates and the strings those records index
269
+ into. It is the fast path — the JSON one costs 1.5 MB of text a frame — and it
270
+ is POSITIONAL. Nothing in it says how wide a record is.
271
+
272
+ The record grew from 24 ints to 26 when `transform: rotate()` needed an origin
273
+ to turn about. `EVGDisplayList.stride()` was updated, and its comment even says
274
+ "read it through this function and never inline the number". The decoder on the
275
+ other side of the bridge had inlined it:
276
+
277
+ ```js
278
+ export const SCENE_STRIDE = 24; // pptx-host.mjs
279
+ const b = i * SCENE_STRIDE;
280
+ ```
281
+
282
+ So from the second command on, every field was read two ints early. Colours
283
+ became coordinates, coordinates became flags, and the frame was nonsense that
284
+ still *looked* like numbers. Nothing threw until a ring count read out of
285
+ somebody's colour reached `new Array(eCount)`:
286
+
287
+ ```
288
+ RangeError: Invalid array length
289
+ at decodeScene (pptx-host.mjs:104)
290
+ ```
291
+
292
+ — in the WebAssembly parity job, which needs an Emscripten toolchain, runs late
293
+ in the deploy workflow, and points a hundred fields downstream of the mistake.
294
+
295
+ ### Resolution
296
+
297
+ **The shape is derived from the bytes, not agreed in advance.** `cmds` is
298
+ allocated as exactly `count * stride`, so `sceneStride(bin)` recovers the number
299
+ the writer used by dividing. That answer cannot drift, and it needs no new
300
+ export — which matters, because three producers publish this frame by three
301
+ different routes (the Ranger engine, the Emscripten build and the Rust one) and
302
+ only one of them is in a position to export a constant.
303
+
304
+ What the decoder now names is the FLOOR: `SCENE_FIELDS_READ = 23`, the number
305
+ of fields it actually reads. A record wider than that is fine — the extra
306
+ fields are not its business — and one narrower throws with both numbers in the
307
+ message.
308
+
309
+ ### Why it was not caught sooner
310
+
311
+ The claim that the two paths agree was written in a comment and checked
312
+ nowhere. `gallery/pptx/web/host/scene-binary-check.mjs` now asks the engine for
313
+ both the JSON and the binary, for every slide of every fixture, and compares
314
+ them field by field: 37 decks, 45 slides, 8,658 commands, no browser, no
315
+ toolchain, one second. It is in `scripts/run-gallery-editor-tests.sh` and runs
316
+ in CI *before* the WebAssembly build rather than after it.
317
+
318
+ Two other checks would also have caught this and neither was wired up: the
319
+ standalone smoke test (`npm run pptx:web:test`) fails outright, and the frame
320
+ is visibly empty in the playground. Both were reachable the whole time.
321
+
322
+ ### The general lesson
323
+
324
+ A positional binary format needs its shape carried with it or derivable from
325
+ it. "Both sides know the layout" is not a property anything enforces, and the
326
+ failure mode is not a crash at the boundary — it is plausible-looking garbage
327
+ that surfaces somewhere unrecognisable.
328
+
329
+
330
+ ---
331
+
332
+ ## Issue #5: `backdrop-filter` sampled the page, not the element
333
+
334
+ **Status:** Resolved
335
+ **Severity:** Medium — a scrim showed the page bleeding through its own border
336
+ **Found:** August 30, 2026, while implementing it
337
+ **Resolved:** the same day
338
+ **Component:** `lib/evg/gl/evg-webgl.js`
339
+
340
+ ### What happened
341
+
342
+ The first implementation grew the copied region by the kernel's reach and
343
+ blurred that, on the reading that `backdrop-filter` samples past the element's
344
+ edge. The evidence for it was a flat grey behind a pane coming out flat to the
345
+ border, with no rim.
346
+
347
+ That is not evidence. **A uniform field is uniform under either rule** — edge
348
+ clamping and sampling-past give the same answer when there is nothing outside
349
+ worth sampling. The observation ruled out only a third possibility, that edge
350
+ samples read transparent and the border fades.
351
+
352
+ The case that decides is a feature that straddles the border. White page, black
353
+ stripe starting exactly at the pane's left edge:
354
+
355
+ ```
356
+ x: 95 96 97 98 99 | 100 101 102 ...
357
+ browser 255 255 255 255 255| 0 0 0
358
+ ```
359
+
360
+ No ramp at the border at all — while the same black/white boundary a hundred
361
+ pixels further in, inside the pane, gets the full smooth curve. Outside content
362
+ does not enter.
363
+
364
+ ### Resolution
365
+
366
+ Copy exactly the element's box and let `CLAMP_TO_EDGE` do the rest, which is
367
+ the measured rule expressed in one place.
368
+
369
+ ### The general lesson
370
+
371
+ Three checks passed against the wrong implementation, and all three were scenes
372
+ where the backdrop did not change across the element's border. A test that
373
+ cannot distinguish the two candidate rules is not weak evidence for one of
374
+ them; it is no evidence at all. Before trusting a case, ask what the WRONG
375
+ implementation would print — and if the answer is "the same thing", the case is
376
+ not about the question.
377
+
378
+ ---
379
+
380
+ ## Issue #6: a text run's reported width changes between the first frame and the rest
381
+
382
+ **Status:** open, pre-existing, cosmetic in the display list only.
383
+
384
+ Found while proving that moving `TreeDemo`'s rows onto component instances
385
+ changed nothing observable. It did not — but the display list on frame 7 is not
386
+ the display list on frame 1, and that is true with or without the change.
387
+
388
+ Two text commands in the tree demo differ between the first paint and any later
389
+ one:
390
+
391
+ ```
392
+ frame 1 {"k":3,"x":105.14,"y":261,"w":58.1, …,"text":"Jane Doe"}
393
+ frame 7 {"k":3,"x":105.57,"y":261,"w":340, …,"text":"Jane Doe"}
394
+ ```
395
+
396
+ `w` goes from the MEASURED run width (58.1) to the label's declared box width
397
+ (340), and `x` shifts by 0.43px. Only the deepest rows are affected — the ones
398
+ whose label sits after an indent spacer whose width is set on the element
399
+ rather than by the sheet.
400
+
401
+ Nothing on screen moves: `k:3` is a text draw and the painter positions the run
402
+ from `x` and the glyphs, not from `w`. So this is a display-list fidelity bug
403
+ rather than a rendering one, and it was invisible until something compared two
404
+ frames of the same state.
405
+
406
+ ### Why it is recorded rather than fixed here
407
+
408
+ It is not the component work's to fix, and it was verified as pre-existing by
409
+ running the identical probe on both sides of that change — same two commands,
410
+ same numbers. Fixing it means understanding why a second layout pass over the
411
+ same tree resolves the label's width differently, which is `EVGLayout`'s
412
+ business and wants its own measurement.
413
+
414
+ ### The general lesson
415
+
416
+ "Nothing changed" is only checkable if the thing you compare is stable to begin
417
+ with. A baseline captured on frame 1 and a candidate settled over six frames
418
+ are not the same experiment, and the first version of that comparison reported a
419
+ difference my change had not caused. Compare like with like, and when a
420
+ comparison fails, check the baseline before the change.
421
+
422
+ ## Issue #7: shrink-to-fit text can wrap inside the box its own width produced
423
+
424
+ **Status:** Resolved (September 5, 2026)
425
+ **Severity:** Low (cosmetic; a label that fits is drawn on two lines)
426
+ **Found:** September 2, 2026
427
+ **Component:** `EVGLayout.rgr` / `EVGTextEngine.rgr`
428
+
429
+ ### Resolution
430
+
431
+ `EVGTextEngine.breakLines` allows a line `EVGTextEngine.fitEpsilon()` — a
432
+ millionth of a pixel — over the width it is breaking into. The error a
433
+ shrink-wrap round trip introduces is about seven parts in a quadrillion, so
434
+ the tolerance is a hundred million times it and a millionth of the smallest
435
+ thing anyone can see: it cannot change a wrap a person asked for, and it can
436
+ only undo a round trip that should have been the identity.
437
+
438
+ Covered by `evg:textbox:check` — "the round trip a shrink-wrapped box makes",
439
+ which sweeps twenty-one strings across eight sizes, eight paddings and four
440
+ border widths, asserts that some of those round trips really do come back
441
+ short (or the check would be proving nothing), and that not one of the 5632
442
+ labels breaks inside its own box. Before the fix, 310 of them did.
443
+
444
+ The report below is kept for history.
445
+
446
+ ### What happens
447
+
448
+ A text element with no stated width shrink-wraps: layout measures the run, adds
449
+ the padding and border, and that sum is the box. The line breaker then works
450
+ with the content width, which is that sum *minus* the same padding — and for
451
+ some strings the round trip does not land back on the number it started from,
452
+ so the breaker sees a width a fraction narrower than the text and breaks it.
453
+
454
+ Reproduced in `lib/evg/web/responsive`, whose sidebar is six labels in a
455
+ column. Five sit on one line and `Display list` does not:
456
+
457
+ ```
458
+ measure 'Display list' = 62.12453 'Text engine' = 67.93241
459
+ item 'Display list' w=86.12453 h=47.9 <- 24px padding, two lines
460
+ item 'Text engine' w=91.93241 h=32.95 <- 24px padding, one line
461
+ ```
462
+
463
+ Both boxes are exactly `measured + 24`. Both are therefore exactly on the
464
+ threshold. One wraps and the other does not, which is what says this is a
465
+ floating-point artifact of `(w + pad) - pad` and not a measurement disagreement:
466
+ the run measurement and the sum of the per-word measurements agree to the last
467
+ digit for both strings.
468
+
469
+ ### Why it is recorded rather than fixed here
470
+
471
+ The fix belongs in the line breaker — a shrink-wrapped box should not be able to
472
+ break its own content, so the comparison there wants an epsilon (or the content
473
+ width wants to be carried alongside the box width rather than recomputed from
474
+ it). Either is a change to how every EVG document breaks lines, and it needs
475
+ its own measurement against the conformance oracles before it lands.
476
+
477
+ ### Workaround
478
+
479
+ State a `min-width`, which takes the box off the threshold. The responsive
480
+ demo's `.nav-item` does exactly that, and says so.
481
+
482
+ ## Issue #8: a flex row wrapped because of its own arithmetic
483
+
484
+ **Status:** Resolved
485
+ **Severity:** High (visible flicker while a window is resized)
486
+ **Found:** September 2, 2026
487
+ **Resolved:** September 2, 2026
488
+ **Component:** `EVGLayout.rgr`
489
+
490
+ ### Resolution
491
+
492
+ The row-wrap test in `EVGLayout.layoutFlexChildren` compares the child's total
493
+ width against the space left on the line. It now allows a hundredth of a pixel
494
+ of overflow before it wraps. `lib/evg/EVGFlexWrapTest.rgr`
495
+ (`npm run evg:flexwrap:test`) sweeps a 240px sidebar beside a `flex: 1` panel
496
+ across every width from 400 to 1800 in quarter-pixel steps and asserts the two
497
+ stay on one line — and, in the same file, that a row which genuinely does not
498
+ fit still wraps and that `flex-wrap: nowrap` still means nowrap. Without the
499
+ tolerance the first two of those five checks fail.
500
+
501
+ `lib/evg/web/responsive` carries the same sweep for the page the defect was
502
+ found on.
503
+
504
+ ### What happened
505
+
506
+ The commonest two-column layout there is:
507
+
508
+ ```css
509
+ .body { display: flex; gap: 24px } /* wrap allowed — EVG's default */
510
+ .side { width: 240px }
511
+ .main { flex: 1 }
512
+ ```
513
+
514
+ `.main`'s width is computed **by the layout** out of the same line it is then
515
+ measured against: the flex pass takes the inner width, subtracts the sidebar and
516
+ the gap, and hands back the remainder. Adding those three back up is not
517
+ guaranteed to return the number they were subtracted from — in binary floating
518
+ point `(a - b - c) + b + c` can land one ulp above `a` — and a bare `>` then
519
+ reads the row as too wide for itself.
520
+
521
+ At a window width of 823px: `4vw` padding either side leaves an inner width of
522
+ 757.16, the sidebar is 240, the gap 24, and `.main` came out 493.15999999999997.
523
+ The three sum to 757.1600000000001.
524
+
525
+ ### Why it mattered more than one pixel
526
+
527
+ The failing widths are **scattered**, not a band. Of the 680 integer widths
528
+ between 821 and 1500, 127 wrapped — 823, 826, 836, 842, 845, 848, 851, 861 …
529
+ So dragging a window edge did not cross a threshold once; it dropped the content
530
+ below the sidebar and pulled it back up again every few pixels, which reads as a
531
+ flickering page rather than as a layout decision.
532
+
533
+ ### The general lesson
534
+
535
+ Same root as issue #7, one stage further up: a number this engine **derived from
536
+ a bound** must not then be tested against that bound with an exact comparison.
537
+ Subtraction and addition do not cancel in floating point, so any "does what I
538
+ just computed still fit in what I computed it from" test needs a tolerance —
539
+ sized below what a painter can draw and above the error being cancelled.
540
+
541
+ ## Issue #9: `line-height` written as a length was read as a multiplier
542
+
543
+ **Status:** Resolved (September 5, 2026)
544
+ **Severity:** High (text drawn far outside its own box)
545
+ **Found:** September 5, 2026 — reported as "vertical align sometimes goes to
546
+ the bottom", from a phone screenshot of a duration pill
547
+ **Component:** `EVGElement.rgr` / `EVGLayout.rgr` / `EVGDisplayList.rgr`
548
+
549
+ ### What happened
550
+
551
+ CSS gives `line-height` three ways to be written and keeps them apart:
552
+ `normal` is the face's own line box, a NUMBER is that many times the font
553
+ size, and a LENGTH is itself. `EVGElement` held one double and put every
554
+ value through `to_double`, which stops at the first character it cannot use —
555
+ so `24px` came back `24` and was used as a multiplier.
556
+
557
+ Measured, on a 12px pill with `height: 20px` and `line-height: 16px`:
558
+
559
+ ```
560
+ before line box 192px baseline 102.2 — the text drawn 82px BELOW the pill
561
+ after line box 16px baseline 14.2 — inside it
562
+ ```
563
+
564
+ A line box twelve times too tall does not look twelve times too tall, because
565
+ the painter puts the baseline a half-leading below its top: it looks like a
566
+ line that has fallen to the bottom of everything near it, and only in the
567
+ places whose stylesheet writes a length. Which is what "sometimes" meant.
568
+
569
+ ### The fix
570
+
571
+ `EVGElement.lineHeightUnit` holds a stated LENGTH as an `EVGUnit`, beside the
572
+ number; `EVGElement.lineBoxFor` is the single place that tells the three
573
+ apart, and both the layout (which reserves the height) and the display list
574
+ (which steps the lines) call it rather than each computing their own. A
575
+ percentage resolves against the element's own font size — CSS 2.1 §10.8.1,
576
+ the one percentage in the box model that does not look at the containing
577
+ block — `em` against its own size and `rem` against the root's.
578
+
579
+ Covered by `evg:textbox:check`, "line-height as a number, a length and a
580
+ percentage": eight cases across `normal`, unset, `1.5`, `150%`, `24px`,
581
+ `2em`, `1.5rem` and `0.8`.
582
+
583
+ ## Issue #10: a text element drew its text on top of its own border
584
+
585
+ **Status:** Resolved (September 5, 2026)
586
+ **Severity:** Medium
587
+ **Found:** September 5, 2026, while measuring the above
588
+ **Component:** `EVGDisplayList.rgr` / `EVGLayout.rgr`
589
+
590
+ ### What happened
591
+
592
+ `EVGDisplayList` started a text run at `x + paddingLeft`, `y + paddingTop` —
593
+ the border was missing from both. But the width it broke the run into is
594
+ `EVGBox.getInnerWidth`, which takes the border off, and the layout's own
595
+ `calculatedBaseline` is measured from the border edge and adds border AND
596
+ padding. So a text element with a border drew its text one border-width high
597
+ and one left, over its own top border, and measured against a width that
598
+ assumed it started inside it.
599
+
600
+ EVG disagreeing with EVG, in three places that each looked right alone. The
601
+ layout's wrap width had the mirror-image of the same slip: it subtracted the
602
+ paddings and not the border, so a bordered element counted its lines at one
603
+ width and was broken at another — the height reserved for a wrap the layout
604
+ imagined, and the painter drawing a different one.
605
+
606
+ Covered by `evg:textbox:check`, "a border on a text element", which asserts
607
+ the run starts inside both and — the one that matters — that the layout's
608
+ baseline and the painter's are the same point.
609
+
610
+ ## Issue #11: a flex container's own text ignored `align-items`
611
+
612
+ **Status:** Resolved (September 5, 2026)
613
+ **Severity:** Medium (a pill's label sits against its top edge)
614
+ **Found:** September 5, 2026 — reported as "the minute still shows wrong",
615
+ from a phone screenshot of a `10min` pill
616
+ **Component:** `EVGLayout.rgr` / `EVGElement.rgr` / `EVGDisplayList.rgr`
617
+
618
+ ### What happened
619
+
620
+ An element that is a flex container and carries text of its OWN does not lay
621
+ that text out as a block. CSS wraps it in an ANONYMOUS FLEX ITEM, and from
622
+ then on `align-items` and `justify-content` place it like any other item.
623
+
624
+ That is the pill idiom, and it is written that way everywhere:
625
+
626
+ ```css
627
+ .pill { display: flex; align-items: center; height: 34px; padding: 0 12px }
628
+ ```
629
+
630
+ with the label as the element's own text. EVG placed the line box at the top
631
+ of the content box and never consulted `align-items`. Measured on that pill,
632
+ the space above and below the digits:
633
+
634
+ ```
635
+ before above 3.0 below 20.2 the label against the top edge
636
+ after above 11.4 below 11.8 where the same text in a child sits
637
+ ```
638
+
639
+ `gallery/realtrainer`'s stylesheet has a hundred and nine rules with
640
+ `align-items: center`, so the shape is not rare.
641
+
642
+ ### The fix
643
+
644
+ `EVGLayout` writes `EVGElement.textShiftY` once the box's height is final —
645
+ the offset is a share of the slack and there is no slack until then — and the
646
+ display list adds it to every line. `calculatedBaseline` includes it too, or
647
+ an `align-items: baseline` row around the pill would line the pill up by a
648
+ baseline the painter does not draw at.
649
+
650
+ `align-items` across a row, `justify-content` along a column, and a BLOCK is
651
+ untouched: its line boxes still start at the top of its content box whatever
652
+ `align-items` says.
653
+
654
+ ### The check
655
+
656
+ `evg:textbox:check`, "a flex container's own text", asserts the EQUIVALENCE
657
+ rather than any number: the element's own text has to land exactly where the
658
+ same text in a child of the same container lands. A browser cannot tell those
659
+ two apart and neither may EVG. Six alignments, plus that the slack really is
660
+ there to be shared, that the layout's baseline is still the painter's, and
661
+ that a block ignores `align-items`.
662
+
663
+ ## Issue #12: the accessibility mirror does not hide the page behind a dialog
664
+
665
+ Open, and NOT fixed here. The drawn half is fixed — see `EVGFocus`, the focus
666
+ trap — and this is the other half, recorded so it is not rediscovered.
667
+
668
+ ### What happens
669
+
670
+ `evg-a11y.js` already knows how: `applyModal` looks for a node in the
671
+ accessibility tree that carries `modal` (`aria-modal`), walks up to the
672
+ top-level region holding it, and puts `aria-hidden="true"` on every other
673
+ top-level region. That is the right mechanism and it is written.
674
+
675
+ It never fires in realtrainer, because there is no such node. The element that
676
+ declares `a11yModal` is the overlay — the scrim, which is what covers the page
677
+ — and an element with no role and no name is not published to the tree at all.
678
+ So `tree.nodes.find(n => n.modal)` finds nothing, nothing is hidden, and a
679
+ screen reader walks straight out of an open dialog into the page behind it,
680
+ even though a sighted keyboard user can no longer do that.
681
+
682
+ ### Why it is not a one-line fix
683
+
684
+ The node has to exist, which means the overlay or the sheet has to carry a
685
+ role — `dialog` is the right one — and that adds a node to the accessibility
686
+ tree of every screen that has a sheet on it. Those trees are recorded:
687
+ `gallery/realtrainer/traces/*.json` is the Ranger side's own transcript, and
688
+ `traces/reference/*.json` is the app being ported. Adding the role is very
689
+ likely a step TOWARDS the reference — a real dialog there is a
690
+ `<div role="dialog" aria-modal="true">` — but "very likely" is not "checked",
691
+ and the check needs the private frontend the reference recorder runs against.
692
+
693
+ So the shape of the fix is known and the cost is a re-record plus a look at
694
+ the reference diff, which is a machine this repository's CI does not have.
695
+
696
+ ### What holds in the meantime
697
+
698
+ The keyboard is trapped (`EVGFocus`, `rt:keys`), Escape closes the topmost
699
+ dialog, and the mirror still reports the dialog's own controls correctly — it
700
+ just also reports the page behind them.
701
+
702
+ ---
703
+
704
+ ## Issue #13: a kept frame moved the page and left the focus ring behind
705
+
706
+ Fixed. Recorded because the first half of the fix was not enough and the
707
+ second half was in a place nobody would look.
708
+
709
+ ### What happened
710
+
711
+ The ring is drawn LAST and outside every clip, so that a button inside a panel
712
+ is not half-ringed by the panel's edge. Outside every clip is also outside
713
+ every scroll LAYER — and a host does not re-read the display list for a frame
714
+ that only scrolled. It moves the kept frame by a per-layer offset: `uShift` in
715
+ the WebGL painter, the same arithmetic on the worker page. The ring belonged to
716
+ no layer, so it got no offset: the page moved and the ring stayed.
717
+
718
+ ### Why the first fix did not show it
719
+
720
+ `EVGDisplayList.refreshRing` (Issue #12's neighbour, landed earlier) puts the
721
+ ring back against its own box whenever the kept LIST is refreshed, and every
722
+ check that reads `displayListJson()` therefore passes — the list was right all
723
+ along. What was wrong was the FRAME, which is built from the list once and
724
+ then moved by uniforms. A check that reads the list cannot see it; a
725
+ screenshot can.
726
+
727
+ ### The fix
728
+
729
+ `ringAround` marks the ring command with the layer its element scrolls in
730
+ (`layerAround`, the innermost layer container that holds it, 1-based as the
731
+ painter numbers them), and the painter reads `layer` off a command that
732
+ carries one instead of taking it from the clip nesting alone. A control that
733
+ scrolls with nothing — a header button — is in layer 0 and stays put, which is
734
+ also checked.
735
+
736
+ ### What holds it
737
+
738
+ `rt:keys` — "the ring is round its element, whatever the page is doing": the
739
+ invariant across wheels, a throw moved by the clock, a rebuild under the ring
740
+ and a page turn, plus the two assertions on the layer the ring declares. The
741
+ invariant is what was missing: a delta test ("it moved by 160") passes for a
742
+ ring that lags a frame and for one that leads one.
743
+
744
+ ## Issue #11: a gradient drew on the GPU and nowhere else
745
+
746
+ **Status:** Resolved (September 13, 2026)
747
+ **Component:** `evg_png_tool`, `EVGPDFRenderer`, `EVGDisplayList`
748
+
749
+ EVG has two ways to say "gradient" and the painters did not agree on which one
750
+ they read:
751
+
752
+ | | `gradient-from` / `-to` / `-dir` | `background-gradient` (the CSS string) |
753
+ | --- | --- | --- |
754
+ | display list → WebGL, SVG, GL | **yes** | no |
755
+ | raster (PNG) | no | **yes** |
756
+ | PDF | no | **yes** |
757
+
758
+ So a gradient written in the pair — which is the display list's own vocabulary,
759
+ and what `SceneToEVG` emits for a Figma fill — drew on the GPU and was silently
760
+ absent from the image and the print. That is the opposite of the claim this
761
+ engine makes about its targets, and nothing reported it: the element laid out,
762
+ measured and composited exactly as it should, and the fill was simply not
763
+ painted.
764
+
765
+ Found by converting a Figma page with a gradient and looking at the PNG.
766
+
767
+ ### The fix, and the trap inside it
768
+
769
+ Both painters now take the pair. The trap is that they do not share an angle
770
+ convention: the rasteriser builds a direction vector from `(cos, sin)` with 0 to
771
+ the right, while the PDF renderer parses CSS where 0 is to the top. One `dir`
772
+ code, two correct angles, and using either one in the other place turns every
773
+ gradient a quarter turn in one target only.
774
+
775
+ Both inverses therefore live in `EVGDisplayList`, beside the `applyGradient`
776
+ that reduces an angle to the code — `rasterAngleOfDir` and `cssAngleOfDir`,
777
+ named so the difference is visible rather than discovered. `EVGJsonTest` checks
778
+ them against that reduction on the same axes rather than trusting two constants
779
+ to stay in step.
780
+
781
+ Cost: one boolean test per element. The GPU path is untouched, since it already
782
+ read the right fields.
783
+
784
+ ## Issue #12: the rasteriser knew one image format out of three
785
+
786
+ **Status:** Resolved (September 13, 2026)
787
+ **Component:** `evg_png_tool`, `EVGImageDecode`, `ProgressiveJPEGDecoder`
788
+
789
+ `evg_png_tool` chose between the baseline and progressive JPEG decoders itself
790
+ and had never heard of PNG, so a document with one rendered
791
+ `Not a JPEG file (missing SOI)` and stopped. A Figma archive is full of PNGs.
792
+
793
+ `EVGImageDecode` already sniffed all three formats — the tool was simply not
794
+ asking it — but it had the mirror gap: no progressive branch, because
795
+ `ProgressiveJPEGDecoder` had only `decode(dir, name)` and the sniffing decoder
796
+ has bytes. That one now has `decodeBytes`, and a `quiet` flag for the same
797
+ reason `JPEGDecoder` has one: twenty-four lines of narration per photograph.
798
+
799
+ ### The smaller defect underneath
800
+
801
+ `PNGDecoder.decodeBytes` returns a 1x1 image when it gives up, and
802
+ `EVGImageDecode` judged success by the returned buffer. A genuine 1x1 PNG — a
803
+ spacer, a placeholder, the thumbnail inside a `.fig` — was therefore
804
+ indistinguishable from a failure and was rejected. It now asks the decoder what
805
+ its header said: those dimensions stay 0 when nothing parsed, and say 1x1 when
806
+ the file really is that.
807
+
808
+
809
+ ## #13 A negative resolved size drew mirrored rather than empty — FIXED
810
+
811
+ `width: -40%` is not a width: CSS throws the declaration away. EVG kept the
812
+ number, and the raster painter drew the box mirrored through its own left edge
813
+ — so RealTrainer's progress bar, whose app-side arithmetic produced "week -41
814
+ of 104", came out as a FULL blue bar where an empty one was correct. The same
815
+ document exported to Figma showed nothing there, which is how the disagreement
816
+ was found: two renderers of the same box, one drawing a bar and one drawing
817
+ nothing.
818
+
819
+ `EVGLayout` clamps a calculated width or height to zero now. The clamp is on
820
+ the SIZE and not on `EVGUnit.resolve`, because a negative offset is perfectly
821
+ ordinary — `left: -10px` moves a box left — and clamping there would break it.
822
+
823
+ ## #14 `to_double` disagrees with itself across targets — OPEN
824
+
825
+ `(to_double "10 ")` is 10 in JavaScript and Python and NOTHING in Go, whose
826
+ parse is strict about surrounding whitespace. A number scanned up to the next
827
+ operator carries a trailing space more often than not, so a recursive-descent
828
+ parser written the obvious way returns the right answer on two targets and zero
829
+ on the third — from one source, which is the one thing the compiler promises
830
+ not to do.
831
+
832
+ The portable form is `(to_double (trim text))`, and the plugin's `calc` example
833
+ says so in a comment because it was found by running the example on all three.
834
+ Whether the fix belongs in the Go writer (trim before parsing) or in the
835
+ documented contract (callers trim) is open; the disagreement is not.