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,600 @@
1
+ /**
2
+ * evg-html.js — the same display list, painted as DOM instead of pixels.
3
+ *
4
+ * `evg-webgl.js` is the reference for this file. Both are handed the SAME
5
+ * `{width, height, list:{cmds}}` that `EVGDisplayList` emits, and both are
6
+ * expected to put the same picture on the screen; only the mechanism differs.
7
+ * Nothing above the seam knows which one is running, which is the whole reason
8
+ * the seam is there — see lib/evg/gl/README.md.
9
+ *
10
+ * EVGDisplayList ──┬─► evg-webgl.js ─► quads, glyph atlas, GLSL
11
+ * └─► THIS FILE ─► <svg>: rect, path, text, image
12
+ *
13
+ * WHY SVG AND NOT DIVS. The list has eight command kinds and SVG has a native
14
+ * answer for seven of them, including the two a stack of absolutely positioned
15
+ * `<div>`s cannot express at all:
16
+ *
17
+ * * PATH / STROKE are flattened rings in page coordinates — `<path d=…>` and
18
+ * `fill-rule="evenodd"` take them verbatim, with no tessellation and no
19
+ * stencil buffer. The GL backend needs both.
20
+ * * PUSH_CLIP / POP_CLIP is a STACK, and a stack of nested `<g clip-path>`
21
+ * intersects by construction. The GL backend has one scissor rectangle and
22
+ * has to intersect the stack by hand; `EVGListToElements`, which converts a
23
+ * list into an element tree for the PDF writer, gives up on clipping
24
+ * entirely and says so in its header.
25
+ *
26
+ * So this backend is not a degraded one. It is missing the ripple post-pass,
27
+ * which is a GPU effect with no DOM equivalent, and it gains selectable text,
28
+ * resolution independence, and a picture that survives being printed.
29
+ *
30
+ * (That sibling now exists: `evg-dom.js` reads `EVGHostTree`, the list that
31
+ * carries identity, and keeps its nodes. This file stays what it is.)
32
+ *
33
+ * WHAT IS DELIBERATELY NOT HERE. Element identity. A draw command carries no
34
+ * id — `EVGDrawCmd` has geometry and paint and nothing else — so every frame
35
+ * rebuilds every node, and a CSS transition, a focus ring or a native `<input>`
36
+ * has nothing stable to attach to. That is a property of the SEAM, not of this
37
+ * file: getting it would mean an id on the command or a second walk over the
38
+ * element tree. A painter is what this is.
39
+ */
40
+
41
+ const KIND = {
42
+ RECT: 0, BORDER: 1, IMAGE: 2, TEXT: 3, PUSH_CLIP: 4, POP_CLIP: 5,
43
+ PATH: 6, STROKE: 7,
44
+ };
45
+
46
+ const SVG_NS = "http://www.w3.org/2000/svg";
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Text, and the one number that decides where every run sits
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /**
53
+ * Bidi, forced off — the same rule `evg-webgl.js` applies, for the same reason.
54
+ *
55
+ * EVG has already decided the order of a line: a line that changes colour
56
+ * partway along arrives as several TEXT commands at computed x positions. A
57
+ * renderer that reorders each of them in isolation reverses Arabic inside its
58
+ * own run and then places the runs left to right anyway. U+202D forces every
59
+ * character to level 0 and U+202C pops it.
60
+ *
61
+ * It matters MORE here than it does on the GL side. There the string is
62
+ * rasterised into an atlas and the result is a picture; here it is a live
63
+ * `<text>` node that the browser is still free to shape and reorder at paint
64
+ * time.
65
+ */
66
+ const LRO = "\u202D", PDF = "\u202C";
67
+ export const verbatim = (t) => LRO + t + PDF;
68
+
69
+ let fallbackStack = "sans-serif";
70
+
71
+ /** The families the browser may fall back to, after the one a command names.
72
+ *
73
+ * Same contract as the GL backend: our FontManager falls back per CODEPOINT
74
+ * across every loaded face and every width in the layout was measured through
75
+ * that walk, so a stack that ends at `sans-serif` answers those codepoints
76
+ * from whatever the system has — right-looking glyphs at widths nobody
77
+ * measured. A page sets this to its own pool, in load order.
78
+ */
79
+ export function setFontFallback(families) {
80
+ const list = (families || []).filter((f) => f && f.length).map((f) => `"${f}"`);
81
+ list.push("sans-serif");
82
+ fallbackStack = list.join(", ");
83
+ }
84
+
85
+ /** One place that turns a TEXT command into a CSS font shorthand. */
86
+ // The family a command names, less the `-Bold` suffix `effectiveFontFamily`
87
+ // writes for a bold element: a convention the TTF measurers read as "the
88
+ // bold face", and a browser reads as a family nobody has. The weight arrives
89
+ // on its own and is what the browser is given. `gl/evg-measure.js` strips
90
+ // the same suffix, so the layout and this painter ask for one face.
91
+ const familyOf = (c) => (c.font && c.font.endsWith("-Bold") ? c.font.slice(0, -5) : c.font || "");
92
+
93
+ function fontSpec(c) {
94
+ return `${c.italic ? "italic " : ""}${c.weight ? c.weight + " " : ""}${c.size}px "${familyOf(c)}", ${fallbackStack}`;
95
+ }
96
+
97
+ // Face metrics per font spec. `fontBoundingBox*` is a property of the FACE at
98
+ // a size, not of the string, so one measurement answers every run that shares
99
+ // a spec — which on a slide is nearly all of them.
100
+ const FACE_METRICS = new Map();
101
+ let metricCtx = null;
102
+
103
+ function faceMetrics(c) {
104
+ const spec = fontSpec(c);
105
+ const hit = FACE_METRICS.get(spec);
106
+ if (hit) return hit;
107
+ if (!metricCtx) {
108
+ const canvas = document.createElement("canvas");
109
+ canvas.width = 8;
110
+ canvas.height = 8;
111
+ metricCtx = canvas.getContext("2d");
112
+ }
113
+ let asc = c.size * 1.05, desc = c.size * 0.212;
114
+ if (metricCtx) {
115
+ metricCtx.font = spec;
116
+ // "Hg" rather than the run: the ink of a particular string is irrelevant
117
+ // here and measuring per run would defeat the cache.
118
+ const m = metricCtx.measureText("Hg");
119
+ if (m.fontBoundingBoxAscent) asc = m.fontBoundingBoxAscent;
120
+ if (m.fontBoundingBoxDescent) desc = m.fontBoundingBoxDescent;
121
+ }
122
+ const out = { asc, desc };
123
+ FACE_METRICS.set(spec, out);
124
+ return out;
125
+ }
126
+
127
+ /** Drop the cache when the page has registered new faces.
128
+ *
129
+ * A metric measured before `document.fonts.add()` resolves is the FALLBACK
130
+ * face's, and it is then cached forever — every run in the deck placed by the
131
+ * system sans's ascent while being drawn in Open Sans. A page that loads fonts
132
+ * after this module calls this.
133
+ */
134
+ export function clearFontMetrics() {
135
+ FACE_METRICS.clear();
136
+ }
137
+
138
+ /**
139
+ * Where the baseline of a TEXT command goes.
140
+ *
141
+ * Transcribed from `evg-webgl.js`, which is the definition:
142
+ *
143
+ * baseline = y + (lineBox - (faceAsc + faceDesc)) / 2 + faceAsc
144
+ *
145
+ * A TEXT command's `y` is the top of the LINE BOX and `h` is its height, so
146
+ * the baseline is one half-leading plus one face ascent below it. The
147
+ * half-leading term is not decoration — a line box is normally taller than the
148
+ * face it holds, so leaving it out draws every run high, in one direction
149
+ * only.
150
+ *
151
+ * SVG's default `dominant-baseline` is alphabetic, so a `<text y=…>` placed
152
+ * here needs no further adjustment: the two conventions already agree.
153
+ */
154
+ function baselineOf(c) {
155
+ const { asc, desc } = faceMetrics(c);
156
+ const halfLeading = c.h ? (c.h - (asc + desc)) / 2 : 0;
157
+ return c.y + halfLeading + asc;
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // Markup
162
+ // ---------------------------------------------------------------------------
163
+
164
+ const XML_ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&apos;" };
165
+ const esc = (s) => String(s).replace(/[&<>"']/g, (ch) => XML_ESCAPES[ch]);
166
+
167
+ /** Two decimals, and no exponent.
168
+ *
169
+ * The display list is already written to hundredths — `EVGDisplayList.num`
170
+ * rounds there — so this loses nothing. What it prevents is `toString`
171
+ * reaching for scientific notation on a very small number: `1e-7` is not a
172
+ * valid SVG length, and one of them in a path's `d` invalidates the whole
173
+ * attribute rather than the one coordinate.
174
+ */
175
+ function n(v) {
176
+ if (!Number.isFinite(v)) return "0";
177
+ const r = Math.round(v * 100) / 100;
178
+ return Object.is(r, -0) ? "0" : String(r);
179
+ }
180
+
181
+ const rgba = (c) => {
182
+ const col = c || [0, 0, 0, 1];
183
+ const a = col[3] === undefined ? 1 : col[3];
184
+ return a >= 1 ? `rgb(${col[0] | 0},${col[1] | 0},${col[2] | 0})`
185
+ : `rgba(${col[0] | 0},${col[1] | 0},${col[2] | 0},${n(a)})`;
186
+ };
187
+
188
+ /** The four corner radii of a command, clamped the way CSS clamps them.
189
+ *
190
+ * `rc` is present only when the four differ; a box with one radius carries a
191
+ * single `r`, which is the shape every backend but the GL one reads.
192
+ */
193
+ function radiiOf(c) {
194
+ const r = c.rc ? c.rc.slice(0, 4) : [c.r || 0, c.r || 0, c.r || 0, c.r || 0];
195
+ const lim = Math.min(c.w, c.h) / 2;
196
+ return r.map((v) => Math.max(0, Math.min(v || 0, lim)));
197
+ }
198
+
199
+ const uniform = (r) => r[0] === r[1] && r[1] === r[2] && r[2] === r[3];
200
+
201
+ /**
202
+ * A rounded rectangle as a path, for the case `rx` cannot express.
203
+ *
204
+ * Corners are TL, TR, BR, BL — the order `border-radius` writes them in, which
205
+ * is the order the display list carries and the order the GL shader reads.
206
+ */
207
+ function roundedPath(x, y, w, h, r) {
208
+ const [tl, tr, br, bl] = r;
209
+ return [
210
+ `M${n(x + tl)},${n(y)}`,
211
+ `H${n(x + w - tr)}`, tr ? `A${n(tr)},${n(tr)} 0 0 1 ${n(x + w)},${n(y + tr)}` : "",
212
+ `V${n(y + h - br)}`, br ? `A${n(br)},${n(br)} 0 0 1 ${n(x + w - br)},${n(y + h)}` : "",
213
+ `H${n(x + bl)}`, bl ? `A${n(bl)},${n(bl)} 0 0 1 ${n(x)},${n(y + h - bl)}` : "",
214
+ `V${n(y + tl)}`, tl ? `A${n(tl)},${n(tl)} 0 0 1 ${n(x + tl)},${n(y)}` : "",
215
+ "Z",
216
+ ].filter(Boolean).join("");
217
+ }
218
+
219
+ /** The rings of a PATH / STROKE command as one `d`.
220
+ *
221
+ * `ends` gives the index one past each ring's last COORDINATE, so a shape with
222
+ * a hole survives as two subpaths rather than one confused outline. A command
223
+ * that set `pts` and forgot `ends` is treated as one ring covering everything,
224
+ * which is what the JSON writer does too.
225
+ */
226
+ function ringsPath(c, close) {
227
+ const pts = c.pts;
228
+ if (!pts || pts.length < 4) return "";
229
+ const ends = c.ends && c.ends.length ? c.ends : [pts.length];
230
+ let d = "", at = 0;
231
+ for (const end of ends) {
232
+ if (end - at < 4) { at = end; continue; }
233
+ d += `M${n(pts[at])},${n(pts[at + 1])}`;
234
+ for (let i = at + 2; i + 1 < end; i += 2) d += `L${n(pts[i])},${n(pts[i + 1])}`;
235
+ if (close) d += "Z";
236
+ at = end;
237
+ }
238
+ return d;
239
+ }
240
+
241
+ /**
242
+ * What a command turns about.
243
+ *
244
+ * An explicit origin is the easy half. Without one the rule is "the command's
245
+ * own centre", and for TEXT that is not the centre of the line box: the GL
246
+ * backend draws a run in a quad sized to its INK, so the pivot it falls back to
247
+ * is the ink's centre. The two are a few pixels apart on a 22px label — enough
248
+ * that the parity check sees it, which is how this was found.
249
+ *
250
+ * `EVGDisplayList` says the same thing from the other side: `rotOriginX/Y` were
251
+ * added precisely because turning a box and its own words about their separate
252
+ * centres pulls them apart, and anything that cares emits an origin. This is
253
+ * the case that does not — a lone turned label — and there the ink centre is
254
+ * both what the GPU does and what looks right.
255
+ *
256
+ * Three values are needed to say "unset" rather than two, because (0, 0) is a
257
+ * legal pivot and cannot be told apart from "none" by its coordinates.
258
+ */
259
+ function rotAttr(c) {
260
+ if (!c.rot) return "";
261
+ let cx, cy;
262
+ if (c.rox !== undefined) {
263
+ cx = c.rox;
264
+ cy = c.roy === undefined ? c.y + c.h / 2 : c.roy;
265
+ } else if (c.k === KIND.TEXT) {
266
+ const ink = inkBox(c);
267
+ cx = ink.cx;
268
+ cy = ink.cy;
269
+ } else {
270
+ cx = c.x + c.w / 2;
271
+ cy = c.y + c.h / 2;
272
+ }
273
+ return ` transform="rotate(${n(c.rot)} ${n(cx)} ${n(cy)})"`;
274
+ }
275
+
276
+ /**
277
+ * The centre of the quad the GL backend would have drawn this run in.
278
+ *
279
+ * Transcribed from `buildTextAtlas`: a slot is the run's measured width and its
280
+ * INK height — `actualBoundingBox*`, the ascent and descent of these particular
281
+ * letters — with two pixels of padding, placed so the baseline lines up. The
282
+ * padding cancels out of the centre, so what is left is the ink box.
283
+ *
284
+ * Measured per run rather than per face, which the cached `faceMetrics` cannot
285
+ * be: the ink of "moon" and of "Ág" are different boxes in the same font. It is
286
+ * only reached for a turned run with no origin, which is rare enough that the
287
+ * measurement does not need a cache of its own.
288
+ */
289
+ function inkBox(c) {
290
+ const base = baselineOf(c);
291
+ if (!metricCtx) faceMetrics(c);
292
+ if (!metricCtx) return { cx: c.x + c.w / 2, cy: c.y + c.h / 2 };
293
+ metricCtx.font = fontSpec(c);
294
+ const m = metricCtx.measureText(verbatim(c.text));
295
+ const asc = m.actualBoundingBoxAscent || c.size * 0.8;
296
+ const desc = m.actualBoundingBoxDescent || c.size * 0.25;
297
+ return {
298
+ cx: c.x + Math.ceil(m.width) / 2,
299
+ cy: base - asc + Math.ceil(asc + desc) / 2,
300
+ };
301
+ }
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Images
305
+ // ---------------------------------------------------------------------------
306
+
307
+ /**
308
+ * Load every distinct image the list names. Same signature as the GL backend's,
309
+ * so a host that already has one needs no second code path.
310
+ */
311
+ export async function loadImages(doc, opts = {}) {
312
+ const base = opts.base || "";
313
+ const srcs = [...new Set(doc.list.cmds.filter((c) => c.k === KIND.IMAGE && c.src).map((c) => c.src))];
314
+ const out = new Map();
315
+ await Promise.all(srcs.map((src) => new Promise((resolve) => {
316
+ const img = new Image();
317
+ img.crossOrigin = "anonymous";
318
+ img.onload = () => { out.set(src, img); resolve(); };
319
+ img.onerror = () => { out.set(src, null); resolve(); };
320
+ img.src = base + src;
321
+ })));
322
+ return out;
323
+ }
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // The paint
327
+ // ---------------------------------------------------------------------------
328
+
329
+ /**
330
+ * Paint a display list into an `<svg>`.
331
+ *
332
+ * `target` is the element to paint into: an `<svg>` is used directly, anything
333
+ * else gets one created inside it and reused across frames.
334
+ *
335
+ * The markup is assembled as a STRING and handed over in one `innerHTML`. The
336
+ * obvious alternative — `createElementNS` per command, appended as it goes —
337
+ * is between two and four times slower on a slide-sized list, because each
338
+ * append is a live mutation of a rendered tree; a string is parsed once with
339
+ * nothing on screen depending on it.
340
+ *
341
+ * Returns the same shape of stats the GL backend returns, so a page can report
342
+ * what it drew without knowing which backend drew it.
343
+ */
344
+ export function renderDisplayList(target, doc, opts = {}) {
345
+ const images = opts.images || new Map();
346
+ const cmds = doc.list.cmds;
347
+ const svg = svgFor(target);
348
+
349
+ const defs = [];
350
+ const body = [];
351
+ let openClips = 0;
352
+ let drawn = 0, textRuns = 0, paths = 0, drawnImages = 0, missingImages = 0, clips = 0;
353
+ let uid = 0;
354
+
355
+ for (const c of cmds) {
356
+ switch (c.k) {
357
+ case KIND.PUSH_CLIP: {
358
+ // A nested <g clip-path> intersects with everything above it, which is
359
+ // exactly what the stack means — and is the part the GL backend has to
360
+ // compute by hand because a scissor is not a stack.
361
+ const id = `evgclip${uid++}`;
362
+ defs.push(`<clipPath id="${id}"><rect x="${n(c.x)}" y="${n(c.y)}" width="${n(Math.max(0, c.w))}" height="${n(Math.max(0, c.h))}"/></clipPath>`);
363
+ body.push(`<g clip-path="url(#${id})">`);
364
+ openClips += 1;
365
+ clips += 1;
366
+ break;
367
+ }
368
+ case KIND.POP_CLIP:
369
+ // A pop with nothing pushed is a producer's bug, not this file's: close
370
+ // nothing rather than emitting a stray `</g>` that would swallow the
371
+ // rest of the frame into whatever encloses it.
372
+ if (openClips > 0) { body.push("</g>"); openClips -= 1; }
373
+ break;
374
+
375
+ case KIND.RECT: {
376
+ if (c.w <= 0 || c.h <= 0) break;
377
+ let fill = rgba(c.c);
378
+ if (c.gd !== undefined && c.c2) {
379
+ // `gd` is 1 for across the box and anything else for down it — the
380
+ // two directions the display list carries.
381
+ const id = `evggrad${uid++}`;
382
+ const across = c.gd === 1;
383
+ defs.push(
384
+ `<linearGradient id="${id}" x1="0" y1="0" x2="${across ? 1 : 0}" y2="${across ? 0 : 1}">` +
385
+ `<stop offset="0" stop-color="${rgba(c.c)}"/><stop offset="1" stop-color="${rgba(c.c2)}"/>` +
386
+ `</linearGradient>`);
387
+ fill = `url(#${id})`;
388
+ }
389
+ if (c.bb > 0) body.push(backdrop(c));
390
+ const r = radiiOf(c);
391
+ // The drop shadow, under the box: the same rounded rectangle, moved
392
+ // and softened. A Gaussian of sigma = blur/2 is what CSS means by a
393
+ // blur radius, and the GL shader's falloff spans the same width.
394
+ if (c.sh) {
395
+ const b = Math.max(c.sh.blur || 0, 0);
396
+ const sx = c.x + (c.sh.x || 0), sy = c.y + (c.sh.y || 0);
397
+ let attrs = "";
398
+ if (b > 0) {
399
+ const id = `evgshadow${uid++}`;
400
+ // The filter region has to be wide enough for the kernel or the
401
+ // blur is clipped square at the edge of the shape's own box.
402
+ defs.push(`<filter id="${id}" x="-50%" y="-50%" width="200%" height="200%">` +
403
+ `<feGaussianBlur stdDeviation="${n(b / 2)}"/></filter>`);
404
+ attrs = ` filter="url(#${id})"`;
405
+ }
406
+ body.push(uniform(r)
407
+ ? `<rect x="${n(sx)}" y="${n(sy)}" width="${n(c.w)}" height="${n(c.h)}"${r[0] ? ` rx="${n(r[0])}"` : ""} fill="${rgba(c.sh.c)}"${attrs}${rotAttr(c)}/>`
408
+ : `<path d="${roundedPath(sx, sy, c.w, c.h, r)}" fill="${rgba(c.sh.c)}"${attrs}${rotAttr(c)}/>`);
409
+ }
410
+ body.push(uniform(r)
411
+ ? `<rect x="${n(c.x)}" y="${n(c.y)}" width="${n(c.w)}" height="${n(c.h)}"${r[0] ? ` rx="${n(r[0])}"` : ""} fill="${fill}"${rotAttr(c)}/>`
412
+ : `<path d="${roundedPath(c.x, c.y, c.w, c.h, r)}" fill="${fill}"${rotAttr(c)}/>`);
413
+ drawn += 1;
414
+ break;
415
+ }
416
+
417
+ case KIND.BORDER: {
418
+ // The GL shader keeps a band from d = -thickness to d = 0: the border
419
+ // sits INSIDE the box, not centred on its edge. An SVG stroke is
420
+ // centred, so the rectangle is inset by half the thickness and the
421
+ // radius comes down by the same amount — otherwise a 2px border on a
422
+ // 100px box covers 101px of it and the corner is a hair too round.
423
+ const t = c.t || 1;
424
+ if (c.w <= t || c.h <= t) break;
425
+ const r = radiiOf(c).map((v) => Math.max(0, v - t / 2));
426
+ const x = c.x + t / 2, y = c.y + t / 2, w = c.w - t, h = c.h - t;
427
+ body.push(uniform(r)
428
+ ? `<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}"${r[0] ? ` rx="${n(r[0])}"` : ""} fill="none" stroke="${rgba(c.c)}" stroke-width="${n(t)}"${rotAttr(c)}/>`
429
+ : `<path d="${roundedPath(x, y, w, h, r)}" fill="none" stroke="${rgba(c.c)}" stroke-width="${n(t)}"${rotAttr(c)}/>`);
430
+ drawn += 1;
431
+ break;
432
+ }
433
+
434
+ case KIND.IMAGE: {
435
+ const img = c.src ? images.get(c.src) : null;
436
+ if (!img) { missingImages += 1; break; }
437
+ // `preserveAspectRatio="…slice"` IS object-fit: cover — fill the box and
438
+ // crop the overflow, centred, rather than distort the picture. The GL
439
+ // backend computes the same crop as a UV rectangle because a quad has
440
+ // nowhere else to put it.
441
+ // A crop window (`cu`, in the bitmap's own 0..1 space) is drawn by
442
+ // placing the WHOLE picture on the larger rectangle that puts that
443
+ // window over the box, and clipping to the box. `none` because the
444
+ // window is stretched to fill the box exactly — Figma's crop tool
445
+ // already keeps it the shape of the frame, so nothing is distorted.
446
+ const cu = c.cu;
447
+ const cw = cu ? cu[2] - cu[0] : 1, chh = cu ? cu[3] - cu[1] : 1;
448
+ const dw = cu ? c.w / cw : c.w, dh = cu ? c.h / chh : c.h;
449
+ const dx = cu ? c.x - cu[0] * dw : c.x, dy = cu ? c.y - cu[1] * dh : c.y;
450
+ let attrs = `x="${n(dx)}" y="${n(dy)}" width="${n(dw)}" height="${n(dh)}"` +
451
+ ` preserveAspectRatio="${cu ? "none" : "xMidYMid slice"}" href="${esc(img.src)}"`;
452
+ const transforms = [];
453
+ if (c.rot) {
454
+ const cx = c.rox === undefined ? c.x + c.w / 2 : c.rox;
455
+ const cy = c.roy === undefined ? c.y + c.h / 2 : c.roy;
456
+ transforms.push(`rotate(${n(c.rot)} ${n(cx)} ${n(cy)})`);
457
+ }
458
+ if (c.fx || c.fy) {
459
+ // Mirroring is a scale about the box's own centre, which is what
460
+ // reading the texture backwards amounts to on the GL side.
461
+ const cx = c.x + c.w / 2, cy = c.y + c.h / 2;
462
+ transforms.push(`translate(${n(cx)} ${n(cy)}) scale(${c.fx ? -1 : 1} ${c.fy ? -1 : 1}) translate(${n(-cx)} ${n(-cy)})`);
463
+ }
464
+ if (transforms.length) attrs += ` transform="${transforms.join(" ")}"`;
465
+ const r = radiiOf(c);
466
+ if (r.some((v) => v > 0) || cu) {
467
+ // A photo in a rounded box is clipped by the same shape the box is
468
+ // drawn with — the GL backend runs it through the same distance
469
+ // field for the same reason. A cropped one is clipped to the box
470
+ // whatever its corners: the picture is drawn larger than the box
471
+ // on purpose and the rest of it must not show.
472
+ const id = `evgimgclip${uid++}`;
473
+ defs.push(`<clipPath id="${id}"><path d="${roundedPath(c.x, c.y, c.w, c.h, r)}"/></clipPath>`);
474
+ body.push(`<g clip-path="url(#${id})"><image ${attrs}/></g>`);
475
+ } else {
476
+ body.push(`<image ${attrs}/>`);
477
+ }
478
+ drawnImages += 1;
479
+ drawn += 1;
480
+ break;
481
+ }
482
+
483
+ case KIND.TEXT: {
484
+ if (!c.text) break;
485
+ // `xml:space="preserve"` is not optional: SVG collapses runs of spaces
486
+ // by default, and EVG emits a run's leading and trailing space as part
487
+ // of the run it measured. Without it an indented line starts flush.
488
+ let a = `x="${n(c.x)}" y="${n(baselineOf(c))}" fill="${rgba(c.c)}"` +
489
+ ` font-family="&quot;${esc(familyOf(c))}&quot;, ${esc(fallbackStack)}"` +
490
+ ` font-size="${n(c.size)}"`;
491
+ if (c.weight) a += ` font-weight="${esc(c.weight)}"`;
492
+ if (c.italic) a += ` font-style="italic"`;
493
+ body.push(`<text ${a} xml:space="preserve"${rotAttr(c)}>${esc(verbatim(c.text))}</text>`);
494
+ textRuns += 1;
495
+ drawn += 1;
496
+ break;
497
+ }
498
+
499
+ case KIND.PATH: {
500
+ const d = ringsPath(c, true);
501
+ if (!d) break;
502
+ // A gradient on a shape that is not its box. The stops run across the
503
+ // command's own rectangle, which is what the GL side hands its path
504
+ // shader as a uniform; `userSpaceOnUse` is how SVG says the same.
505
+ let pfill = rgba(c.c);
506
+ if (c.c2) {
507
+ const id = `evgpgrad${uid++}`;
508
+ const across = c.gd === 1;
509
+ const x0 = c.x, y0 = c.y, x1 = c.x + (across ? c.w : 0), y1 = c.y + (across ? 0 : c.h);
510
+ defs.push(
511
+ `<linearGradient id="${id}" gradientUnits="userSpaceOnUse"` +
512
+ ` x1="${n(x0)}" y1="${n(y0)}" x2="${n(x1)}" y2="${n(y1)}">` +
513
+ `<stop offset="0" stop-color="${rgba(c.c)}"/><stop offset="1" stop-color="${rgba(c.c2)}"/>` +
514
+ `</linearGradient>`);
515
+ pfill = `url(#${id})`;
516
+ }
517
+ body.push(`<path d="${d}" fill="${pfill}"${c.eo ? ` fill-rule="evenodd"` : ""}${rotAttr(c)}/>`);
518
+ paths += 1;
519
+ drawn += 1;
520
+ break;
521
+ }
522
+
523
+ case KIND.STROKE: {
524
+ const d = ringsPath(c, false);
525
+ if (!d) break;
526
+ body.push(`<path d="${d}" fill="none" stroke="${rgba(c.c)}" stroke-width="${n(c.t || 1)}"` +
527
+ ` stroke-linejoin="round" stroke-linecap="round"${rotAttr(c)}/>`);
528
+ paths += 1;
529
+ drawn += 1;
530
+ break;
531
+ }
532
+
533
+ default:
534
+ break;
535
+ }
536
+ }
537
+
538
+ // A producer that pushed more clips than it popped would otherwise leave the
539
+ // markup unbalanced, and an unbalanced tree is repaired by the HTML parser in
540
+ // whatever way it likes.
541
+ while (openClips > 0) { body.push("</g>"); openClips -= 1; }
542
+
543
+ svg.setAttribute("viewBox", `0 0 ${n(doc.width)} ${n(doc.height)}`);
544
+ svg.setAttribute("width", n(doc.width));
545
+ svg.setAttribute("height", n(doc.height));
546
+ svg.innerHTML = (defs.length ? `<defs>${defs.join("")}</defs>` : "") + body.join("");
547
+
548
+ return {
549
+ drawn, textRuns, paths, images: drawnImages, missingImages, clips,
550
+ // Every element, not `childElementCount`: a clipped run lives inside a
551
+ // <g>, so the top-level count drops the whole of a scrolling panel and
552
+ // reports a frame as nearly empty when it is nothing of the kind.
553
+ nodes: svg.getElementsByTagName("*").length,
554
+ // The ripple is a GPU post-pass over the finished frame. There is no DOM
555
+ // equivalent, and saying so is better than silently dropping it.
556
+ // `doc.list.effect`, not `doc.effect`: the effect rides the LIST, the way
557
+ // the commands do, and read off the envelope this was null on every page
558
+ // that has ever had one — including the one it was written for. Nothing
559
+ // read it, which is why it stayed wrong.
560
+ unsupportedEffect: (doc.list && doc.list.effect) ? doc.list.effect.kind : null,
561
+ // And the same for the element-scoped ones: a starfield is a shader, and a
562
+ // backend with no render target draws the page without it. Named rather
563
+ // than counted, because the useful question here is WHICH effect a page
564
+ // loses when it is exported as SVG.
565
+ unsupportedEffects: (doc.list && doc.list.effects ? doc.list.effects : []).map((e) => e.kind),
566
+ };
567
+ }
568
+
569
+ function svgFor(target) {
570
+ if (target && target.namespaceURI === SVG_NS && target.tagName.toLowerCase() === "svg") return target;
571
+ let svg = target.querySelector(":scope > svg");
572
+ if (!svg) {
573
+ svg = document.createElementNS(SVG_NS, "svg");
574
+ svg.setAttribute("xmlns", SVG_NS);
575
+ target.appendChild(svg);
576
+ }
577
+ return svg;
578
+ }
579
+
580
+ /**
581
+ * `backdrop-filter: blur(Npx)` — softening what is already painted behind a
582
+ * rectangle before drawing it.
583
+ *
584
+ * The GL backend needed a render target, a separable blur and a second shader
585
+ * for this. Here it is the CSS property it was named after, reached through a
586
+ * `<foreignObject>`, because SVG filters do not see through to what is under
587
+ * the element.
588
+ *
589
+ * A browser that does not support `backdrop-filter` draws the rectangle and
590
+ * loses only the softening, which is the right failure — the same one the
591
+ * display list's own header asks a backend to have.
592
+ */
593
+ function backdrop(c) {
594
+ const r = radiiOf(c);
595
+ const radius = uniform(r) ? `${n(r[0])}px` : r.map((v) => `${n(v)}px`).join(" ");
596
+ return `<foreignObject x="${n(c.x)}" y="${n(c.y)}" width="${n(c.w)}" height="${n(c.h)}">` +
597
+ `<div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;` +
598
+ `border-radius:${radius};backdrop-filter:blur(${n(c.bb)}px);-webkit-backdrop-filter:blur(${n(c.bb)}px)"></div>` +
599
+ `</foreignObject>`;
600
+ }