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,664 @@
1
+ # EVG Vector IR & SVG Import Plan
2
+
3
+ **Status:** Draft for discussion
4
+ **Scope:** `lib/evg/` (renderer-neutral) + the three renderers in
5
+ `gallery/pdf_writer/src/` and the WASM UI in `gallery/game_engine/v2/ui/`
6
+
7
+ ## 1. Premise
8
+
9
+ The proposal is: *do not make EVG an SVG engine — give EVG a renderer-neutral
10
+ vector layer and make SVG one importer into it.* That direction is right, and
11
+ this document adapts it to what is already in the tree.
12
+
13
+ The important correction to the original sketch: **this is not a greenfield
14
+ build.** `lib/evg/SVGPathParser.rgr` already exists (568 lines) and already
15
+ contains `PathCommand`, an SVG path-data parser, bounds calculation, scaling and
16
+ a curve `flatten()`. It already has three independent consumers:
17
+
18
+ | Consumer | File | Uses |
19
+ | --- | --- | --- |
20
+ | PDF renderer | `pdf_writer/src/core/EVGPDFRenderer.rgr:1762` | `getScaledCommands()` → PDF `m`/`l`/`c`/`h` |
21
+ | WASM UI | `game_engine/v2/ui/WasmUiSelect.rgr:538` | `flatten(12)` → `UIContext.fillPolygon` |
22
+ | EVG tests | `game_engine/v2/evg/evg_test.rgr:2210` | parse + bounds |
23
+
24
+ The HTML renderer is a fourth consumer in spirit: it does not parse at all, it
25
+ forwards `el.svgPath` and `el.viewBox` straight into an inline `<svg>` and lets
26
+ the browser do the work (`EVGHTMLRenderer.rgr:1505`).
27
+
28
+ So the work is **not** "add a vector layer". It is "the vector layer exists,
29
+ is under-specified, and every consumer interprets it differently". That is a
30
+ stronger argument for the plan than the original sketch made, because the
31
+ divergence is already producing wrong output today.
32
+
33
+ ## 2. What is actually broken today
34
+
35
+ These are the concrete defects that a shared Vector IR is meant to eliminate.
36
+ Each one is a real behavioural difference, not a hypothetical.
37
+
38
+ ### 2.1 PDF and HTML disagree about `viewBox`
39
+
40
+ For the same `<Path>` element:
41
+
42
+ * **PDF** calls `getScaledCommands(w, h)`, which maps the path's *bounding box*
43
+ onto the element box with **independent X and Y scale factors**, and ignores
44
+ `viewBox` completely.
45
+ * **HTML** emits `<svg viewBox="…" width="w" height="h"><path d="…">` and the
46
+ browser applies real SVG semantics: viewBox → viewport with
47
+ `preserveAspectRatio="xMidYMid meet"` (uniform scale, centred, letterboxed).
48
+
49
+ Consequences: a non-square icon is stretched in PDF and letterboxed in HTML;
50
+ intentional padding inside the viewBox (very common in 24×24 icon sets) is
51
+ silently cropped away in PDF because bbox-fitting removes it; and a path that
52
+ does not start at the viewBox origin lands in a different place in each output.
53
+
54
+ This one issue justifies the whole plan on its own: the same document does not
55
+ render the same way, and no amount of per-renderer patching fixes it because
56
+ there is no shared definition of what the coordinates *mean*.
57
+
58
+ ### 2.2 The path parser silently drops what it does not understand
59
+
60
+ `SVGPathParser.parseCommand` handles `M m L l H h V v C c Q q Z z`. It does
61
+ **not** handle:
62
+
63
+ * `A`/`a` — elliptical arc. `PathCommand` has `rx`, `ry`, `rotation`,
64
+ `largeArc`, `sweep` fields and `flatten()` has an `"A"` branch, but the parser
65
+ never emits one, so those are dead code.
66
+ * `S`/`s`, `T`/`t` — smooth cubic/quadratic. Common in optimizer output (SVGO)
67
+ and in hand-written paths.
68
+ * **Implicit command repetition** — `M 10 10 20 20 30 30`, `C … … …` with
69
+ repeated coordinate sets. The main loop reads one letter, `parseCommand`
70
+ consumes exactly one coordinate set and returns.
71
+
72
+ The failure mode is the problem: unrecognised letters and leftover numbers are
73
+ skipped one character at a time by the `i = i + 1` branch in `parse()`. Nothing
74
+ raises, nothing warns. The path just quietly loses segments and the shape closes
75
+ across the gap. For hand-authored icon paths this has been survivable; for
76
+ imported `.svg` files it is not.
77
+
78
+ ### 2.3 `Q` → cubic conversion in the PDF renderer is mathematically wrong
79
+
80
+ `EVGPDFRenderer.rgr:1804` emits, for a quadratic segment with control `C`:
81
+
82
+ ```
83
+ x1 y1 x1 y1 x y c
84
+ ```
85
+
86
+ i.e. it uses the quadratic control point as *both* cubic control points. The
87
+ correct degree elevation is:
88
+
89
+ ```
90
+ C1 = P0 + 2/3 (C - P0)
91
+ C2 = P3 + 2/3 (C - P3)
92
+ ```
93
+
94
+ The current form pulls the curve toward the control point far too weakly, so
95
+ quadratic curves render visibly flatter in PDF than in HTML (where the browser
96
+ does it correctly). The comment in the source (`; For simplicity, approximate
97
+ with cubic`) shows this was known to be an approximation; it is a
98
+ one-line-per-coordinate exact fix, and in a shared IR it is fixed once.
99
+
100
+ ### 2.4 `flatten()` loses subpath structure, so holes are impossible
101
+
102
+ `flatten(steps)` returns a flat `[x0,y0,x1,y1,…]` list with no ring boundaries —
103
+ an `M` in the middle of the path just pushes another point. `UIContext.fillPolygon`
104
+ then treats the whole list as one closed polygon (it wraps last → first) and
105
+ fills by counting scanline crossings.
106
+
107
+ So any path with more than one subpath renders wrong: a donut, the letter "O",
108
+ a check mark inside a ring, any icon with a counter. Every real icon set hits
109
+ this.
110
+
111
+ ### 2.5 Raster has no path support at all
112
+
113
+ `RasterPrimitives` has line/rect/rounded-rect/circle/ellipse. There is no path
114
+ entry point, which is exactly what `TODO_EVG.md` tracks as **PNG 0.9 SVG path
115
+ rasterization — TODO**. A `<Path>` in a TSX document renders in PDF and HTML and
116
+ is simply absent from PNG output.
117
+
118
+ ## 3. The one big asset the sketch under-valued
119
+
120
+ The original sketch treats the raster side as "the actual work" and proposes a
121
+ new `VectorRasterizer`. In fact **a general anti-aliased path rasterizer already
122
+ exists in the tree** — it is just hard-wired to font glyphs. In
123
+ `pdf_writer/src/raster/RasterText.rgr`:
124
+
125
+ * `GlyphContour` — a closed contour of points (`RasterText.rgr:31`)
126
+ * `GlyphEdge` — an edge carrying a winding direction (`:71`)
127
+ * `flattenContour` — quadratic curve subdivision into edges (`:570`)
128
+ * `scanlineFillAA` — scanline fill, **non-zero winding rule**, 4×4 subpixel
129
+ supersampling (`:908`)
130
+
131
+ That is a complete polygon rasterizer. Nothing about the fill stage is
132
+ font-specific; only the *input* is (TrueType contours, quadratic-only, on/off
133
+ curve points).
134
+
135
+ This changes the cost estimate substantially. The raster milestone is mostly an
136
+ **extraction**, not a new algorithm:
137
+
138
+ ```
139
+ RasterText VectorRasterizer RasterText
140
+ GlyphContour ──► Contour ◄── (feeds glyph contours)
141
+ GlyphEdge ──► Edge ◄── SVGPathParser (flattened)
142
+ scanlineFillAA ──► fillAA(rule) ◄── UIContext.fillPolygon
143
+ ```
144
+
145
+ Three wins from one refactor: PNG 0.9 gets done, the WASM UI's `fillPolygon`
146
+ gains anti-aliasing and hole support for free, and glyph rendering and path
147
+ rendering can no longer drift apart. The only genuinely new capability needed is
148
+ an **even-odd** fill rule alongside the existing non-zero.
149
+
150
+ ## 4. Recommended architecture
151
+
152
+ Agreed with the sketch on the central decision:
153
+
154
+ ```
155
+ SVG file ──► SvgParser ──┐
156
+ ├──► Vector IR ──┬──► PDF (native path operators)
157
+ TSX <Path>/<Vector> ─────┤ ├──► HTML (inline <svg>)
158
+ │ ├──► Raster(VectorRasterizer)
159
+ programmatic (charts) ───┘ └──► WASM UI (UIContext)
160
+ ```
161
+
162
+ Two refinements.
163
+
164
+ ### 4.1 Do not introduce a second node tree — until imports need one
165
+
166
+ The sketch proposes `VectorGroup` / `VectorPath` / `VectorRect` / `VectorEllipse`
167
+ as a parallel node tree. EVG already has an element tree (`EVGElement`, with
168
+ children, `transform`, `viewBox`, `fillColor`, `strokeColor`, `strokeWidth`) plus
169
+ `EVGDisplayList` as a flattened draw-command stream. A third tree means a third
170
+ traversal, a third transform resolution and a third paint resolution.
171
+
172
+ Recommended split, decided by **where the nodes come from**:
173
+
174
+ * **Authored vector** (`<Path>`, `<Vector>` in TSX): stays `EVGElement`. There
175
+ are few nodes, they benefit from layout, props and data binding, and this is
176
+ already how `<Path>` works.
177
+ * **Imported SVG** (`<Svg src="logo.svg">`): collapses into **one** `EVGElement`
178
+ carrying a lightweight, layout-free vector display list. An Illustrator export
179
+ can be thousands of nodes; giving each one a full `EVGElement` layout box would
180
+ be very expensive for something that never participates in layout.
181
+
182
+ This is consistent with the sketch's own observation that EVG should see an
183
+ imported SVG as a single layout element. It also keeps the symmetry the sketch
184
+ wants — `SVG → EVG` next to `TSX → EVG` — with `EVGElement` as the shared target.
185
+
186
+ The value types the IR does need are small and uncontroversial: `PathCommand`
187
+ (exists), `Matrix2D`, `Paint`, `FillRule`.
188
+
189
+ ### 4.2 Put it in `lib/evg/`, not a new tree
190
+
191
+ `lib/evg/` is already the renderer-neutral module (`EVGElement`, `EVGLayout`,
192
+ `EVGColor`, `EVGGradient`, `EVGDisplayList`, `SVGPathParser`). The renderers live
193
+ in `gallery/pdf_writer/src/{core,raster}` and `game_engine/v2/ui`. The vector
194
+ files belong next to `SVGPathParser.rgr`:
195
+
196
+ ```
197
+ lib/evg/
198
+ SVGPathParser.rgr (exists — extend, keep the name: 3 importers + tests)
199
+ VectorPath.rgr (contours, fill rule, paint, bounds)
200
+ Matrix2D.rgr
201
+ VectorViewBox.rgr (viewBox + preserveAspectRatio → element-box transform)
202
+ SvgParser.rgr (XML subset → EVGElement / vector display list)
203
+ EvgBitmapTracer.rgr (bitmap → path; Potrace-style params for benchmarking)
204
+ EvgTraceFit.rgr (optimal polygon: calcLon / bestPolygon / adjustVertices)
205
+ EvgTraceCurve.rgr (alphamax smooth + optiCurve)
206
+ EvgTraceTypes.rgr (shared bitmap/options/point types)
207
+
208
+ gallery/pdf_writer/src/raster/
209
+ VectorRasterizer.rgr (extracted from RasterText)
210
+ ```
211
+
212
+ Keeping the `SVGPathParser.rgr` filename matters: three files import it by
213
+ relative path and one is outside `pdf_writer`. Renaming it is a separate,
214
+ mechanical commit if wanted at all.
215
+
216
+ ## 5. Staging
217
+
218
+ Ordered so that each stage lands something visible and nothing depends on a
219
+ stage that has not shipped.
220
+
221
+ ### Stage 0 — Make the renderers agree — **DONE**
222
+
223
+ The highest value per line of code in the whole plan.
224
+
225
+ 1. ✅ `Q` → cubic in `EVGPDFRenderer` now uses exact degree elevation (§2.3).
226
+ 2. ✅ `lib/evg/VectorViewBox.rgr` — `Matrix2D` plus one implementation of
227
+ the SVG viewBox / `preserveAspectRatio` rule.
228
+ 3. ✅ PDF resolves through it and emits the result as a `cm` matrix, with path
229
+ commands left in user units; `getScaledCommands(w, h)` is no longer on the
230
+ render path.
231
+ 4. ✅ HTML asks the same class which viewBox applies and emits it, so the
232
+ browser applies the identical rule.
233
+ 5. ✅ `bash gallery/pdf_writer/test/run_vector.sh` (`npm run test:evg:vector`).
234
+
235
+ **One behaviour change worth knowing about.** No document in the repo sets
236
+ `viewBox` on a `<Path>` — every one relies on the art being fitted to
237
+ `width`/`height`. Dropping that would have broken all of them, so the fallback
238
+ is now: *explicit viewBox wins; otherwise synthesise one from the path bounds*,
239
+ and both renderers use the same fallback. Two consequences:
240
+
241
+ * PDF no longer stretches non-square art — the fit is uniform (`meet`), so a
242
+ path drawn into a box of a different aspect ratio is letterboxed rather than
243
+ distorted.
244
+ * HTML now emits the synthesised viewBox where it previously emitted none, so
245
+ the browser fits the art instead of drawing it at 1:1 in the corner.
246
+
247
+ Both moved toward each other, and toward what the documents were clearly asking
248
+ for. Verified end to end: on the same fixture, PDF emits `2 0 0 2 -20 -20 cm`
249
+ where HTML emits `viewBox="10 10 30 30"` — the same transform expressed twice.
250
+
251
+ ### Stage 1 — Path completeness — **DONE**
252
+
253
+ 1. `S/s`, `T/t` (smooth cubic/quad, reflected control point).
254
+ 2. Implicit command repetition in the parse loop.
255
+ 3. **Fail loudly**: unknown command letters and trailing garbage must be reported
256
+ (a warning list on the parser), never silently skipped. Today's behaviour is
257
+ the worst kind — wrong output, no signal.
258
+ 4. `A/a` arcs: parse, then normalise endpoint → centre parameterisation and emit
259
+ cubics **in the IR**, so PDF, raster, HTML and WASM all get arcs from one
260
+ implementation. (The PDF renderer currently has no `"A"` branch at all, so an
261
+ arc would vanish silently.)
262
+
263
+ ### Stage 2 — Contours and the shared rasterizer — **DONE**
264
+
265
+ 1. ✅ `flattenRings` returns **rings** and applies the transform while
266
+ flattening, so curves are subdivided at the size they are drawn.
267
+ `PathRing` is the unit; `flatten()` is kept for its existing callers and
268
+ documented as unable to express more than one contour.
269
+ 2. ✅ `VectorRasterizer` extracted from `RasterText`: `VectorEdge`, `addRing`,
270
+ `fillAA(rule)`, with even-odd alongside non-zero.
271
+ 3. ✅ `RasterText` feeds glyph contours into it. Verified by rendering a text
272
+ page before and after the refactor: **byte-identical PNG**. The duplicate
273
+ non-AA fill path in `RasterText` (`renderGlyphFast`, `scanlineFill` and
274
+ their helpers, all unreachable) went with it — leaving it would have left
275
+ two fills to drift apart, which is what the stage exists to prevent.
276
+ 4. ✅ Raster `<Path>` support, fill and stroke → closes **PNG 0.9**.
277
+ 5. ❌ `UIContext.fillPolygon` — see the note below.
278
+
279
+ Note on scope: **fill was nearly free, stroke was not.** As predicted, fill came
280
+ out of the extraction almost unchanged, while stroking needed its own answer.
281
+ Shipped as the approximation described above: one quad per flattened segment
282
+ plus a disc at each joint, merged by the non-zero rule, giving round joins and
283
+ butt caps. It is indistinguishable from a real stroke at icon weights and
284
+ visibly an approximation for very thick strokes with sharp corners, where a
285
+ mitre would run to a point. PDF and HTML stroke natively, so only raster carries
286
+ it. Real offset curves remain the eventual answer.
287
+
288
+ **What did not land: the WASM UI still has its own fill.** `UIContext` paints
289
+ into `SoftCanvas` (`game_engine/v2/framebuffer.rgr`) while the rasterizer works
290
+ on `RasterBuffer` (`pdf_writer/src/raster`). Sharing the code needs either a
291
+ buffer abstraction or a cross-gallery dependency, and that is a design decision
292
+ worth making deliberately rather than as a side effect of this stage. The
293
+ shapes `WasmUiSelect` draws today are single-ring chevrons and check marks, so
294
+ it has no live defect from the missing hole support — but it does still lack
295
+ anti-aliasing, and `flatten()` is still what it calls.
296
+
297
+ ### Stage 3 — Shape normalisation — **DONE**
298
+
299
+ `lib/evg/VectorShapes.rgr`: `rect` (with SVG's rx/ry rules), `circle`,
300
+ `ellipse`, `line`, `polyline`, `polygon` → `PathCommand[]`, plus `asPathData`
301
+ to go back to a `d` string. Pure functions, no renderer changes, no state.
302
+
303
+ Curves come out as cubics using the exact kappa rather than arcs, so consumers
304
+ need no arc conversion. Checked geometrically — sampled points on a circle must
305
+ lie on that circle, which is what would catch a wrong kappa where endpoint
306
+ comparisons never would — and round-tripped: emit a shape as path data, parse
307
+ it back with `SVGPathParser`, confirm the geometry survived. That last one
308
+ exercises the writer and the reader against each other and is what makes the
309
+ emitted `d` safe to hand to a browser.
310
+
311
+ **What this makes redundant, as a follow-up.** Three hand-rolled versions of
312
+ the same geometry now exist alongside the derived one:
313
+
314
+ | Duplicate | Where |
315
+ | --- | --- |
316
+ | `drawRoundedRectPath` | `EVGPDFRenderer.rgr:1966`, 7 call sites, with kappa truncated to `0.5523` |
317
+ | `fillRoundedRect` / `drawRoundedRect` | `RasterPrimitives.rgr:93`, `:129` |
318
+ | `fillCircle` / `fillEllipse` | `RasterPrimitives.rgr:170`, `:326` |
319
+
320
+ Routing them through `VectorShapes` is the obvious cleanup, and it is
321
+ deliberately NOT part of this stage: `drawRoundedRectPath` works in PDF's
322
+ y-up space and is used for clipping as well as filling, so the winding
323
+ direction of the replacement matters, and no golden test currently covers
324
+ border-radius output. Worth doing with a rendering comparison in hand rather
325
+ than on the way past.
326
+
327
+ A language note found here, recorded because it will bite again: **`to_int`
328
+ floors rather than truncating toward zero.** Splitting a negative number into
329
+ whole and fractional parts directly gives the wrong pair — -2.5 comes apart as
330
+ whole -3 and fraction 0.4999.
331
+
332
+ ### Stage 3.5 — Chart primitives — **DONE**
333
+
334
+ Not in the original plan. These came out of asking what a chart engine would
335
+ need on top of the vector layer, and each turned out to be a gap the layer had
336
+ rather than something a chart library could paper over.
337
+
338
+ 1. ✅ `PathBuilder` — the counterpart to the parser. A chart's geometry comes
339
+ from data, and without this the only way to draw it is to concatenate `d` by
340
+ hand and hand the result straight back to the parser. The basic shapes are
341
+ included, so a whole scene accumulates into one path with one fill.
342
+ 2. ✅ `stroke-dasharray` / `stroke-dashoffset` in all three targets, parsed
343
+ once in `VectorStroke`. Gridlines.
344
+ 3. ✅ `rotate` rendered. It had been parsed into `EVGElement` all along with no
345
+ renderer reading it, so a rotated axis label was silently upright. The
346
+ rasterizer takes a transform, which is what makes it work for glyphs and
347
+ paths through one implementation.
348
+ 4. ✅ Rectangular clipping in the raster target, so a series stops at the plot
349
+ area. PDF and HTML already clipped; only raster leaked.
350
+ 5. ✅ `opacity` in the raster target, per element rather than per group.
351
+
352
+ **Found on the way, and worth knowing.** `to_string` reaches for exponential
353
+ notation on very small magnitudes — `cos 90°` is `6.123233995736766e-17` — and
354
+ **PDF real numbers have no exponential form**. That was going into content
355
+ streams, where a viewer is within its rights to reject the whole thing. Now
356
+ clamped, with the gate asserting no exponent ever reaches the stream.
357
+
358
+ **What is left of this group.** Opacity in the PDF target needs an ExtGState
359
+ resource threaded into the page dictionary in two code paths. It is the most
360
+ delicate part of the PDF writer and nothing golden-tests its structure, so it
361
+ was left rather than done in a hurry.
362
+
363
+ Also still per-target rather than shared: rotation applies in raster to what
364
+ goes through the rasterizer — text and paths. Rectangular backgrounds come from
365
+ `RasterPrimitives` and stay upright. Routing those through `VectorShapes` is the
366
+ same follow-up Stage 3 already records.
367
+
368
+ ### Stage 4 — `SvgParser` and the `<Svg>` element — **DONE**
369
+
370
+ `lib/evg/SvgParser.rgr` reads an SVG document into the vector layer, and
371
+ `<Svg src="logo.svg">` draws one in a TSX document. It is the only file in the
372
+ tree that knows any XML.
373
+
374
+ **What comes out is a flat list, not a node tree.** `SvgVectorItem` is resolved
375
+ geometry plus resolved paint, and the whole document is a `[SvgVectorItem]`.
376
+ Two decisions make that possible and both are worth stating, because they are
377
+ the reason the renderers needed almost no new code:
378
+
379
+ * **Transforms are baked.** Every enclosing `transform` is applied to the
380
+ commands as they are produced, so an item's geometry is absolute in the
381
+ document's own user space. An affine map takes a cubic to a cubic, so this
382
+ approximates nothing — and it means a renderer needs no transform stack, no
383
+ group traversal and no state machine. It draws a list.
384
+ * **Paint is resolved.** Inheritance down groups, `fill="none"`, the three
385
+ opacity properties, `style` outranking presentation attributes: all settled
386
+ once, in the importer, so the three renderers cannot come to different
387
+ conclusions about what colour a shape is.
388
+
389
+ **The element reuses the path type rather than adding one.** An imported
390
+ drawing is laid out, sized and positioned exactly like a `<Path>`, so a new
391
+ `elementType` would have meant a second copy of that in the layout engine and in
392
+ all three renderers with no behavioural difference. `EVGElement.svgSource`
393
+ carries the markup; `<Svg src>` is resolved relative to the document that names
394
+ it, in `JSXToEVG.parseFile`, which is the one place that touches the filesystem
395
+ — `SvgParser` itself never opens anything, which is what keeps §8's "no network
396
+ or filesystem access from inside the parser" true by construction rather than by
397
+ review.
398
+
399
+ **HTML goes through the IR too, and this is the load-bearing decision.** It
400
+ would have been *less* code to hand the original markup to the browser inside an
401
+ inline `<svg>` — and it would have reintroduced exactly the class of defect this
402
+ plan exists to remove, because the browser would happily draw the filters,
403
+ gradients, clip paths and live text that PDF and PNG cannot. The HTML target
404
+ therefore emits one `<path>` per resolved item, and a construct outside the
405
+ profile is missing from the preview in the same way it is missing from the
406
+ print. Out of profile has to mean out of profile everywhere, or the profile is
407
+ not real.
408
+
409
+ **In the profile:** `svg`, `g`, `a`, `defs`, `use` (same-document fragments,
410
+ including forward references and `xlink:href`), `path`, `rect`, `circle`,
411
+ `ellipse`, `line`, `polyline`, `polygon`; `transform` with `matrix`,
412
+ `translate`, `scale`, `rotate` (both forms), `skewX`, `skewY`; `fill`, `stroke`,
413
+ `stroke-width`, `fill-rule`, `stroke-dasharray`, `stroke-dashoffset`,
414
+ `opacity`, `fill-opacity`, `stroke-opacity`, and the `style` attribute carrying
415
+ any of them; the root's `viewBox` and `width`/`height`; comments, CDATA,
416
+ processing instructions and namespace prefixes.
417
+
418
+ **Refused, and reported (§7.4):** `text` — with its own sentence, because a logo
419
+ that quietly loses its wordmark is the most likely "SVG support is broken"
420
+ report — plus `script`, `style`, `image`, `filter`, `mask`, `clipPath`,
421
+ `pattern`, `marker`, `symbol`, the animation elements, gradient elements (§6),
422
+ `switch`, nested `<svg>` viewports, external `href`, unknown elements, unknown
423
+ transform functions, unreadable colours and unreadable lengths. Warnings are
424
+ deduplicated by kind, so a file with four hundred gradient fills says it once.
425
+
426
+ **Refused by construction, not by mitigation:** there is no entity machinery at
427
+ all, and a DOCTYPE carrying an internal subset — the only place an entity
428
+ declaration can appear — is a parse error rather than something parsed with the
429
+ entities left as text. That removes billion-laughs and XXE by there being
430
+ nothing to expand. Node count, nesting depth, path-command count and `<use>`
431
+ expansion depth are all capped and reported as errors rather than as an
432
+ out-of-memory.
433
+
434
+ **Found on the way, and worth knowing.** `str2double` reads `"100%"` as `100`.
435
+ A root `width="100%"` is the SVG default and means "fill the viewport" — which
436
+ for an imported drawing is a decision the hosting element makes — so reading it
437
+ as user units would have sized every default-width document by a number that
438
+ means something else entirely. Lengths are now checked to be numbers before they
439
+ are read as numbers.
440
+
441
+ **Fixed on the way past.** Two divergences the imported case walked straight
442
+ into:
443
+
444
+ * The PDF renderer had no even-odd fill: it always emitted `f`/`B`, never
445
+ `f*`/`B*`, so `fill-rule="evenodd"` — which HTML hands to the browser and the
446
+ raster target has honoured since Stage 2 — filled the middle of a ring in PDF
447
+ and left it hollow in the other two.
448
+ * The HTML renderer did not rotate a `<Path>`. PDF emitted a rotation matrix and
449
+ the raster target turned the shape on the shared rasterizer, but this renderer
450
+ builds the inline `<svg>`'s style itself rather than going through the builder
451
+ that carries `transform`, so `rotate` never reached it and the shape stayed
452
+ upright.
453
+
454
+ **The element's fill is the document's initial paint.** SVG's initial `fill` is
455
+ black, and that is still the default — but when the hosting element has a fill,
456
+ that becomes the value any shape inherits when neither it nor anything above it
457
+ names one. A file that sets its own colours is untouched, because its own value
458
+ wins. This is what lets an icon set follow a theme without editing the files,
459
+ and it is the only sensible answer for `currentColor`, which otherwise refers to
460
+ a CSS property an imported document cannot see. It is also what keeps the
461
+ showcase's rule — geometry in the tree, appearance in the stylesheet — true for
462
+ files the gallery does not own.
463
+
464
+ **Four consumers, not three.** `EVGDisplayList` carries imported documents too,
465
+ so the GL/WebGL backend gets them. Leaving it out would have made a `<Svg>`
466
+ present in three targets and absent from the fourth, which is the exact failure
467
+ that file was written to prevent.
468
+
469
+ **Not in this stage.** Stroke joins and caps are not represented (the raster
470
+ stroke is butt caps and round joins, Stage 2), `preserveAspectRatio` inside an
471
+ imported document is ignored because the hosting element decides the fit, and
472
+ group `opacity` is multiplied into each child's alpha rather than composited as
473
+ a unit — overlapping children inside a faded group show through each other here
474
+ and would not in a browser. Each is reported when a document uses it.
475
+
476
+ **Showcase.** A `svg` page joins the gallery with two files that make the
477
+ distinction visible: `rosette.svg` names no colours and is painted by the theme
478
+ (and is one petal referenced eight times, so `<use>` and the baked rotations are
479
+ what is on the page), while `emblem.svg` brings its own palette, its own group
480
+ transform and an even-odd counter, and keeps all three.
481
+
482
+ ### Stage 5 — Paint (flat colour only) — **DONE**
483
+
484
+ Landed with Stage 4, because paint resolution is not separable from the walk
485
+ that produces the items: `fill`, `stroke`, `stroke-width`, `fill-rule`, the dash
486
+ properties and the three opacity properties, inherited down groups and resolved
487
+ into each item. Gradient paint remains deliberately **out of scope for this
488
+ plan** — see §6 — and a `url(#…)` reference is dropped with a diagnostic rather
489
+ than rendered differently in each target.
490
+
491
+ ## 6. Gradients are deferred, on purpose
492
+
493
+ `linearGradient` / `radialGradient` are dropped from the first vector profile.
494
+ Everything else in this plan comes first. The reasoning is worth recording,
495
+ because it is not just a scheduling call.
496
+
497
+ **Gradients are already the worst parity offender in the codebase.** Today the
498
+ same `background-gradient` produces three different results:
499
+
500
+ | Target | Behaviour |
501
+ | --- | --- |
502
+ | HTML | `EVGHTMLRenderer.rgr:827` — the raw CSS string is passed through verbatim; the browser interpolates, with all stops |
503
+ | Raster | `evg_png_tool.rgr:490` — only `getStartColor()` and `getEndColor()` are read; **every intermediate stop is discarded**, and `RasterGradient` does its own interpolation |
504
+ | PDF | Not implemented at all (`TODO_EVG.md` still lists Type 2 / Type 3 shading as upcoming) |
505
+
506
+ So a three-stop gradient is correct in HTML, silently reduced to two stops in
507
+ PNG, and absent from PDF. Extending that into the vector layer would multiply an
508
+ existing inconsistency across every path, and the whole point of §2 is to *remove*
509
+ this class of divergence.
510
+
511
+ **Print makes the guarantee unkeepable anyway.** Even with PDF shading
512
+ implemented, gradients are the one construct where identical output cannot be
513
+ promised: many RIPs and printer drivers rasterize smooth shading rather than
514
+ executing it, at a resolution and dither of their own choosing, and banding
515
+ behaviour then depends on the device. A flat fill survives that pipeline
516
+ predictably; a gradient does not.
517
+
518
+ **Colour management is a separate, larger axis — and it affects flat colour
519
+ too.** Worth stating explicitly so it is not mistaken for a gradient problem:
520
+
521
+ * `EVGColor` is RGBA doubles with no colour-space tag (`EVGColor.rgr:5`).
522
+ * The PDF renderer writes `rg` / `RG` (DeviceRGB) everywhere, and images as
523
+ `/DeviceRGB`. There is no ICC profile, no `/OutputIntent`, no CMYK path.
524
+
525
+ For screen output this is fine. For print, DeviceRGB means the conversion to the
526
+ press's CMYK happens somewhere downstream, outside EVG's control — brand colours
527
+ are the usual casualty. That is a genuine gap, but it is a **document-level
528
+ colour pipeline** question (output intent, ICC, spot colours), not something to
529
+ solve as a side effect of adding gradients. Deferring gradients keeps the two
530
+ questions from getting entangled.
531
+
532
+ **When to revisit.** Gradients are visually valuable, particularly for
533
+ application UI where the raster and HTML targets dominate and print parity is
534
+ not a constraint. The natural time to come back is after Stage 2, once
535
+ `VectorRasterizer` exists: gradient paint then becomes "which colour does this
536
+ covered pixel get", a property of the shared rasterizer rather than a
537
+ per-renderer special case. Prerequisites when that happens: N-stop support in
538
+ `RasterGradient` (removing the two-stop truncation), and PDF Type 2/3 shading,
539
+ so all three targets start from the same model.
540
+
541
+ ## 7. Conformance: lean on the standard, and on the browser
542
+
543
+ The profile is deliberately small, but **everything inside it should follow
544
+ SVG 1.1 semantics exactly**. Adopt the spec's *semantics*, not its *scope*.
545
+ This matters more than it sounds: it is what turns "the renderers disagree"
546
+ from a design debate into a defect with a right answer. In §2.1, PDF is not an
547
+ alternative convention — it is wrong, and the spec says so.
548
+
549
+ Three levels of conformance backing, in order of value per effort.
550
+
551
+ ### 7.1 Spec-derived unit assertions (do this from Stage 0)
552
+
553
+ The parts of SVG 1.1 that are literally formulas or grammar need no images, no
554
+ browser and no rendering — they are pure functions with published expected
555
+ values:
556
+
557
+ | What | Spec source | Test shape |
558
+ | --- | --- | --- |
559
+ | Path data grammar | §8.3 BNF | path string → `PathCommand[]`, incl. implicit repeats and `S`/`T` reflection |
560
+ | Path error handling | §8.3 | malformed `d` → commands up to the error, plus a diagnostic |
561
+ | Elliptical arc | Appendix F.6.5 (endpoint → centre) and F.6.6 (out-of-range radii correction) | arc params → centre/angles → cubics, numeric comparison |
562
+ | `preserveAspectRatio` | viewBox/viewport section | (viewBox, viewport, align, meet\|slice) → `Matrix2D`, all nine align values |
563
+ | Fill rules | `fill-rule` property | self-intersecting star → nonzero vs even-odd coverage |
564
+ | Shape → path | shape sections (incl. the arc-magic-number for `rect` corners) | `rect`/`circle`/`ellipse`/`polygon` → `PathCommand[]` |
565
+
566
+ One refinement this forces on Stage 1: the spec **defines** path-data error
567
+ handling — render up to, but not including, the command containing the first
568
+ error. That is better than either today's silent skip or a hard failure. Do
569
+ both halves: truncate per spec *and* surface a diagnostic, and assert both.
570
+
571
+ ### 7.2 The browser as oracle — the harness already exists
572
+
573
+ `test/browser_parity_snapshot.js` + `browser_parity_test.rgr` already implement
574
+ exactly the right pattern for this, for font metrics: Chromium establishes the
575
+ numbers **once** into a committed snapshot, and the offline gate then checks EVG
576
+ against that snapshot with no browser and no network — because, as that file
577
+ puts it, a correctness gate that can be skipped stops being one.
578
+
579
+ Extend the same mechanism to vector geometry. The browser is already in the loop
580
+ as a conformance-grade SVG implementation (the HTML renderer delegates to it),
581
+ and the useful outputs are *numbers*, not images:
582
+
583
+ * `getScreenCTM()` on a `<svg>` with a given `viewBox`/size → the exact
584
+ viewBox transform `VectorViewBox` must reproduce.
585
+ * `getBBox()` on a `<path>` → geometry after arc/smooth-curve resolution.
586
+ * `getPointAtLength()` sampled along `getTotalLength()` → a curve fingerprint
587
+ that catches a wrong `Q`→cubic conversion (§2.3) immediately.
588
+
589
+ This fits the existing snapshot design directly: same recorder shape, same
590
+ committed-fixture gate, no image diffing, no fuzzy comparison. `playwright-core`
591
+ is already a root dependency and Chromium is already pinned by path in the
592
+ recorder.
593
+
594
+ ### 7.3 The W3C SVG 1.1 test suite — later, and curated
595
+
596
+ Tempting, but not the right backbone right now:
597
+
598
+ * It is **reference-PNG based**, so it needs a working rasterizer plus fuzzy
599
+ image comparison — i.e. it cannot help until after Stage 2, and it brings a
600
+ whole image-diff tolerance problem with it.
601
+ * Most of it exercises features deliberately outside the profile (text, fonts,
602
+ filters, masks, animation). Those tests would fail for reasons that are not
603
+ defects, so raw pass rate is meaningless.
604
+ * It carries its own licensing/vendoring question for the repo.
605
+
606
+ If it is adopted later, adopt it as a **curated subset with an explicit
607
+ out-of-profile expected-fail list**, so the number the suite reports means
608
+ something.
609
+
610
+ ### 7.4 Conformance also defines what "unsupported" must look like
611
+
612
+ A restricted profile is only coherent if *not implemented* is a defined,
613
+ detectable state rather than silently wrong output. So the profile in §8 should
614
+ be enforced with the same rigour as the features: for anything outside it, the
615
+ assertion is that a **diagnostic is produced**, not that something renders. That
616
+ is what makes it safe to ship a small profile and grow it.
617
+
618
+ ## 8. Profile for the first SVG version
619
+
620
+ Agreed with the sketch's subset. Explicitly out, and enforced by the parser
621
+ rather than by convention:
622
+
623
+ ```
624
+ script animation foreignObject
625
+ external CSS filters masks
626
+ external refs <use> href to external documents
627
+ ```
628
+
629
+ Additions worth making explicit, because imported SVG is untrusted input:
630
+
631
+ * **No XML entity expansion.** Simply do not implement entities — that removes
632
+ billion-laughs and XXE by construction rather than by mitigation.
633
+ * **No network or filesystem access from inside the parser.** `href` /
634
+ `xlink:href` to anything other than a same-document fragment is dropped.
635
+ * **Hard caps** on node count, nesting depth and path-command count, reported as
636
+ a parse error rather than an OOM.
637
+ * **`<text>` must warn, not silently drop.** Illustrator and Figma exports very
638
+ often contain `<text>`, and a logo that quietly loses its wordmark is the most
639
+ likely "SVG support is broken" report. Detect it and say so.
640
+
641
+ ## 9. Summary
642
+
643
+ The plan's core decision — *general Vector IR, SVG as an importer* — is correct
644
+ and is the right next big EVG feature. Three adjustments to the sketch:
645
+
646
+ 1. **It is half-built already.** `SVGPathParser` + `PathCommand` exist with three
647
+ consumers. The job is consolidation and correctness, not new construction.
648
+ 2. **The immediate win is agreement, not capability.** PDF and HTML currently
649
+ disagree about `viewBox`, and the quadratic conversion in PDF is wrong. Stage 0
650
+ is small and fixes visible defects.
651
+ 3. **The raster rasterizer already exists inside `RasterText`.** Extracting it is
652
+ cheaper than writing one and pays out in three places. The genuinely new work
653
+ on the raster side is stroking, not filling.
654
+
655
+ Conformance backing (§7): follow SVG 1.1 semantics exactly inside the profile,
656
+ starting with spec-derived unit assertions on the pure functions, and extend the
657
+ existing browser-parity snapshot harness to vector geometry. The W3C test suite
658
+ is a later, curated addition — not the backbone.
659
+
660
+ Gradients are explicitly deferred (§6): they are the one construct where
661
+ cross-target parity cannot be promised — print pipelines rasterize them at their
662
+ own discretion — and the existing implementations already disagree with each
663
+ other. Flat colour first; revisit once `VectorRasterizer` makes gradient paint a
664
+ property of one shared fill stage rather than three separate ones.