ranger-compiler 3.5.0 → 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 +1160 -19
  2. package/LICENSE +3 -1
  3. package/README.md +124 -29
  4. package/dist/Lang.rgr +1188 -290
  5. package/dist/api.d.ts +2343 -879
  6. package/dist/api.js +79924 -56922
  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,1199 @@
1
+ ; SPDX-License-Identifier: MIT
2
+
3
+ ; SVGPathParser.rgr - Parse SVG path data and calculate bounding boxes
4
+ ;
5
+ ; This module parses SVG path commands (M, L, C, Q, A, Z) and provides:
6
+ ; 1. Bounding box calculation for scaling
7
+ ; 2. PDF path command generation
8
+
9
+ ; Path command types
10
+ class PathCommand {
11
+ def type:string "" ; "M", "L", "C", "Q", "A", "Z"
12
+ def x:double 0.0
13
+ def y:double 0.0
14
+ def x1:double 0.0
15
+ def y1:double 0.0
16
+ def x2:double 0.0
17
+ def y2:double 0.0
18
+ def rx:double 0.0
19
+ def ry:double 0.0
20
+ def rotation:double 0.0
21
+ def largeArc:boolean false
22
+ def sweep:boolean false
23
+ }
24
+
25
+ ; One closed contour of a flattened path, as a flat [x0 y0 x1 y1 ...] list.
26
+ ;
27
+ ; Rings are the unit a fill is defined over, which is why flattening has to
28
+ ; preserve them: `flatten` returns every point of every subpath in one list with
29
+ ; no boundary between them, so a consumer can only treat the result as a single
30
+ ; polygon. That is why a donut, the letter "O", or any icon with a counter came
31
+ ; out solid — the hole and the outline were welded into one ring.
32
+ class PathRing {
33
+ def pts:[double]
34
+ ; Z ended this ring. A fill closes every ring by itself; a stroke draws
35
+ ; the closing edge only when told the ring has one.
36
+ def closed:boolean false
37
+
38
+ Constructor () {
39
+ def p:[double]
40
+ pts = p
41
+ }
42
+
43
+ fn pointCount:int () {
44
+ return (idiv (array_length pts) 2)
45
+ }
46
+ }
47
+
48
+ class PathBounds {
49
+ def minX:double 0.0
50
+ def minY:double 0.0
51
+ def maxX:double 0.0
52
+ def maxY:double 0.0
53
+ def width:double 0.0
54
+ def height:double 0.0
55
+ }
56
+
57
+ class SVGPathParser {
58
+ def pathData:string ""
59
+ def i:int 0
60
+ def len:int 0
61
+ def currentX:double 0.0
62
+ def currentY:double 0.0
63
+ def startX:double 0.0
64
+ def startY:double 0.0
65
+ def commands:[PathCommand]
66
+ def bounds:PathBounds
67
+
68
+ ; The previous segment's second control point, reflected by S and T.
69
+ ; `lastCtrlKind` is "C" after a cubic, "Q" after a quadratic and "" after
70
+ ; anything else — S following a non-cubic (and T following a non-quadratic)
71
+ ; must use the current point instead of a stale reflection.
72
+ def lastCtrlX:double 0.0
73
+ def lastCtrlY:double 0.0
74
+ def lastCtrlKind:string ""
75
+
76
+ ; Diagnostics. SVG 1.1 §8.3 says a path with a syntax error renders up to,
77
+ ; but not including, the command containing the error — so bad data is
78
+ ; partially drawn rather than dropped or fatal. That is implemented, but on
79
+ ; its own it is indistinguishable from art that just ends there, which is
80
+ ; how unsupported commands went unnoticed for so long. So the parser also
81
+ ; records WHAT it refused and where, and callers can surface it.
82
+ def errors:[string]
83
+ def truncated:boolean false
84
+
85
+ ; Set when a number was expected and not found. Commands check it after
86
+ ; reading their arguments and decline to emit a partial segment.
87
+ def numFail:boolean false
88
+
89
+ Constructor () {
90
+ def emptyCommands:[PathCommand]
91
+ commands = emptyCommands
92
+ def emptyErrors:[string]
93
+ errors = emptyErrors
94
+ bounds = (new PathBounds())
95
+ }
96
+
97
+ fn getErrors:[string] () {
98
+ return errors
99
+ }
100
+
101
+ fn hasErrors:boolean () {
102
+ return ((array_length errors) > 0)
103
+ }
104
+
105
+ ; One line naming every problem, for a renderer or CLI to print. Empty when
106
+ ; the path parsed cleanly.
107
+ fn errorSummary:string () {
108
+ def n:int (array_length errors)
109
+ if (n == 0) {
110
+ return ""
111
+ }
112
+ def out:string (itemAt errors 0)
113
+ def k:int 1
114
+ while (k < n) {
115
+ out = (out + "; " + (itemAt errors k))
116
+ k = (k + 1)
117
+ }
118
+ return out
119
+ }
120
+
121
+ fn addError:void (msg:string) {
122
+ push errors (msg + " at offset " + (to_string i))
123
+ }
124
+
125
+ ; Parse SVG path data string
126
+ fn parse:void (data:string) {
127
+ pathData = data
128
+ i = 0
129
+ len = (strlen data)
130
+ currentX = 0.0
131
+ currentY = 0.0
132
+ startX = 0.0
133
+ startY = 0.0
134
+
135
+ ; Reset commands array
136
+ def emptyCommands:[PathCommand]
137
+ commands = emptyCommands
138
+ def emptyErrors:[string]
139
+ errors = emptyErrors
140
+ truncated = false
141
+ lastCtrlKind = ""
142
+
143
+ ; The command letter currently in force. SVG lets a command's argument
144
+ ; set repeat without repeating the letter — "M 10 10 20 20 30 30" is a
145
+ ; moveto followed by two linetos — and real files lean on it heavily,
146
+ ; both hand-written and from optimizers. The old loop read one letter,
147
+ ; consumed exactly one argument set, and then skipped the leftover
148
+ ; numbers one character at a time, silently losing every repeat.
149
+ def pending:int 0
150
+
151
+ while (i < len) {
152
+ this.skipWhitespace()
153
+ if (i >= len) {
154
+ break
155
+ }
156
+
157
+ def ch:char (charAt pathData i)
158
+ def chInt:int ch
159
+
160
+ def isLetter:boolean false
161
+ if ((chInt >= 65) && (chInt <= 90)) { isLetter = true }
162
+ if ((chInt >= 97) && (chInt <= 122)) { isLetter = true }
163
+
164
+ if isLetter {
165
+ pending = chInt
166
+ i = (i + 1)
167
+ } {
168
+ if (pending == 0) {
169
+ this.addError("path data must begin with a command letter")
170
+ truncated = true
171
+ break
172
+ }
173
+ ; A repeated moveto is a lineto — the pen only moves once.
174
+ if (pending == 77) { pending = 76 }
175
+ if (pending == 109) { pending = 108 }
176
+ ; closepath takes no arguments, so a number here cannot be a
177
+ ; repeat of anything. Without this the loop would spin forever
178
+ ; on a command that consumes nothing.
179
+ if (pending == 90) {
180
+ this.addError("unexpected number after closepath")
181
+ truncated = true
182
+ break
183
+ }
184
+ if (pending == 122) {
185
+ this.addError("unexpected number after closepath")
186
+ truncated = true
187
+ break
188
+ }
189
+ }
190
+
191
+ def ok:boolean (this.parseCommand(pending))
192
+ if (ok == false) {
193
+ truncated = true
194
+ break
195
+ }
196
+ }
197
+
198
+ ; Calculate bounding box
199
+ this.calculateBounds()
200
+ }
201
+
202
+ ; True when the next non-separator character could start a number, which is
203
+ ; how an implicit repeat is told apart from the end of the path.
204
+ fn hasNumberAhead:boolean () {
205
+ this.skipWhitespace()
206
+ if (i >= len) {
207
+ return false
208
+ }
209
+ def ch:char (charAt pathData i)
210
+ def chInt:int ch
211
+ if ((chInt >= 48) && (chInt <= 57)) { return true }
212
+ if (chInt == 46) { return true }
213
+ if (chInt == 45) { return true }
214
+ if (chInt == 43) { return true }
215
+ return false
216
+ }
217
+
218
+ ; An arc flag is a single character, 0 or 1, and needs no separator after
219
+ ; it: "a1 1 0 011 1" is legal and optimizers emit exactly that. Reading a
220
+ ; flag as a general number would swallow the digits that follow it and throw
221
+ ; the rest of the arc out of alignment.
222
+ fn parseFlag:boolean () {
223
+ this.skipWhitespace()
224
+ if (i >= len) {
225
+ numFail = true
226
+ this.addError("arc flag expected")
227
+ return false
228
+ }
229
+ def ch:char (charAt pathData i)
230
+ def chInt:int ch
231
+ if (chInt == 48) {
232
+ i = (i + 1)
233
+ return false
234
+ }
235
+ if (chInt == 49) {
236
+ i = (i + 1)
237
+ return true
238
+ }
239
+ numFail = true
240
+ this.addError("arc flag must be 0 or 1")
241
+ return false
242
+ }
243
+
244
+ fn skipWhitespace:void () {
245
+ while (i < len) {
246
+ def ch:char (charAt pathData i)
247
+ def chInt:int ch
248
+ ; Space, tab, newline, carriage return, comma
249
+ if ((chInt == 32) || (chInt == 9) || (chInt == 10) || (chInt == 13) || (chInt == 44)) {
250
+ i = i + 1
251
+ } {
252
+ break
253
+ }
254
+ }
255
+ }
256
+
257
+ fn parseNumber:double () {
258
+ this.skipWhitespace()
259
+ def start:int i
260
+ def ch:char (charAt pathData i)
261
+ def chInt:int ch
262
+
263
+ ; Handle optional sign
264
+ if ((chInt == 45) || (chInt == 43)) {
265
+ i = i + 1
266
+ }
267
+
268
+ ; Parse integer part
269
+ while (i < len) {
270
+ def ch2:char (charAt pathData i)
271
+ def chInt2:int ch2
272
+ if ((chInt2 >= 48) && (chInt2 <= 57)) {
273
+ i = i + 1
274
+ } {
275
+ break
276
+ }
277
+ }
278
+
279
+ ; Parse decimal part
280
+ if (i < len) {
281
+ def ch3:char (charAt pathData i)
282
+ def chInt3:int ch3
283
+ if (chInt3 == 46) {
284
+ i = i + 1
285
+ while (i < len) {
286
+ def ch4:char (charAt pathData i)
287
+ def chInt4:int ch4
288
+ if ((chInt4 >= 48) && (chInt4 <= 57)) {
289
+ i = i + 1
290
+ } {
291
+ break
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ ; Parse exponent
298
+ if (i < len) {
299
+ def ch5:char (charAt pathData i)
300
+ def chInt5:int ch5
301
+ if ((chInt5 == 101) || (chInt5 == 69)) {
302
+ i = i + 1
303
+ if (i < len) {
304
+ def ch6:char (charAt pathData i)
305
+ def chInt6:int ch6
306
+ if ((chInt6 == 45) || (chInt6 == 43)) {
307
+ i = i + 1
308
+ }
309
+ }
310
+ while (i < len) {
311
+ def ch7:char (charAt pathData i)
312
+ def chInt7:int ch7
313
+ if ((chInt7 >= 48) && (chInt7 <= 57)) {
314
+ i = i + 1
315
+ } {
316
+ break
317
+ }
318
+ }
319
+ }
320
+ }
321
+
322
+ def numStr:string (substring pathData start i)
323
+ ; A sign with no digits after it, or nothing at all, is a syntax error
324
+ ; rather than a zero. Reading it as zero is how a malformed path used to
325
+ ; turn into geometry that looked deliberate.
326
+ def parsed@(optional):double (str2double numStr)
327
+ if parsed {
328
+ return (unwrap parsed)
329
+ }
330
+ numFail = true
331
+ this.addError("expected a number")
332
+ return 0.0
333
+ }
334
+
335
+ ; Parse ONE argument set for `cmdInt`. Returns false when the arguments
336
+ ; were malformed or the command is not one this profile implements, and the
337
+ ; caller then stops — matching SVG 1.1 §8.3, which renders a path up to but
338
+ ; not including the command containing the first error.
339
+ ;
340
+ ; The command letter has already been consumed by the caller, because an
341
+ ; implicit repeat has no letter to consume.
342
+ fn parseCommand:boolean (cmdInt:int) {
343
+ numFail = false
344
+
345
+ ; ----------------------------------------------------------------- M m
346
+ if ((cmdInt == 77) || (cmdInt == 109)) {
347
+ def x:double (this.parseNumber())
348
+ def y:double (this.parseNumber())
349
+ if numFail {
350
+ return false
351
+ }
352
+ if (cmdInt == 109) {
353
+ x = currentX + x
354
+ y = currentY + y
355
+ }
356
+ def pathCmd (new PathCommand())
357
+ pathCmd.type = "M"
358
+ pathCmd.x = x
359
+ pathCmd.y = y
360
+ push commands pathCmd
361
+ currentX = x
362
+ currentY = y
363
+ startX = x
364
+ startY = y
365
+ lastCtrlKind = ""
366
+ return true
367
+ }
368
+
369
+ ; ----------------------------------------------------------------- L l
370
+ if ((cmdInt == 76) || (cmdInt == 108)) {
371
+ def x:double (this.parseNumber())
372
+ def y:double (this.parseNumber())
373
+ if numFail {
374
+ return false
375
+ }
376
+ if (cmdInt == 108) {
377
+ x = currentX + x
378
+ y = currentY + y
379
+ }
380
+ this.emitLine(x y)
381
+ return true
382
+ }
383
+
384
+ ; ----------------------------------------------------------------- H h
385
+ if ((cmdInt == 72) || (cmdInt == 104)) {
386
+ def x:double (this.parseNumber())
387
+ if numFail {
388
+ return false
389
+ }
390
+ if (cmdInt == 104) {
391
+ x = currentX + x
392
+ }
393
+ this.emitLine(x currentY)
394
+ return true
395
+ }
396
+
397
+ ; ----------------------------------------------------------------- V v
398
+ if ((cmdInt == 86) || (cmdInt == 118)) {
399
+ def y:double (this.parseNumber())
400
+ if numFail {
401
+ return false
402
+ }
403
+ if (cmdInt == 118) {
404
+ y = currentY + y
405
+ }
406
+ this.emitLine(currentX y)
407
+ return true
408
+ }
409
+
410
+ ; ----------------------------------------------------------------- C c
411
+ if ((cmdInt == 67) || (cmdInt == 99)) {
412
+ def x1:double (this.parseNumber())
413
+ def y1:double (this.parseNumber())
414
+ def x2:double (this.parseNumber())
415
+ def y2:double (this.parseNumber())
416
+ def x:double (this.parseNumber())
417
+ def y:double (this.parseNumber())
418
+ if numFail {
419
+ return false
420
+ }
421
+ if (cmdInt == 99) {
422
+ x1 = currentX + x1
423
+ y1 = currentY + y1
424
+ x2 = currentX + x2
425
+ y2 = currentY + y2
426
+ x = currentX + x
427
+ y = currentY + y
428
+ }
429
+ this.emitCubic(x1 y1 x2 y2 x y)
430
+ return true
431
+ }
432
+
433
+ ; ----------------------------------------------------------------- S s
434
+ ; Smooth cubic: the first control point is the reflection of the
435
+ ; previous cubic's second control point about the current point. After
436
+ ; anything that was not a cubic, the spec says to use the current point
437
+ ; itself, which is what makes a lone S behave like a plain C.
438
+ if ((cmdInt == 83) || (cmdInt == 115)) {
439
+ def x2:double (this.parseNumber())
440
+ def y2:double (this.parseNumber())
441
+ def x:double (this.parseNumber())
442
+ def y:double (this.parseNumber())
443
+ if numFail {
444
+ return false
445
+ }
446
+ if (cmdInt == 115) {
447
+ x2 = currentX + x2
448
+ y2 = currentY + y2
449
+ x = currentX + x
450
+ y = currentY + y
451
+ }
452
+ def x1:double currentX
453
+ def y1:double currentY
454
+ if (lastCtrlKind == "C") {
455
+ x1 = ((2.0 * currentX) - lastCtrlX)
456
+ y1 = ((2.0 * currentY) - lastCtrlY)
457
+ }
458
+ this.emitCubic(x1 y1 x2 y2 x y)
459
+ return true
460
+ }
461
+
462
+ ; ----------------------------------------------------------------- Q q
463
+ if ((cmdInt == 81) || (cmdInt == 113)) {
464
+ def x1:double (this.parseNumber())
465
+ def y1:double (this.parseNumber())
466
+ def x:double (this.parseNumber())
467
+ def y:double (this.parseNumber())
468
+ if numFail {
469
+ return false
470
+ }
471
+ if (cmdInt == 113) {
472
+ x1 = currentX + x1
473
+ y1 = currentY + y1
474
+ x = currentX + x
475
+ y = currentY + y
476
+ }
477
+ this.emitQuad(x1 y1 x y)
478
+ return true
479
+ }
480
+
481
+ ; ----------------------------------------------------------------- T t
482
+ ; Smooth quadratic: same reflection rule, against the previous
483
+ ; quadratic's single control point.
484
+ if ((cmdInt == 84) || (cmdInt == 116)) {
485
+ def x:double (this.parseNumber())
486
+ def y:double (this.parseNumber())
487
+ if numFail {
488
+ return false
489
+ }
490
+ if (cmdInt == 116) {
491
+ x = currentX + x
492
+ y = currentY + y
493
+ }
494
+ def x1:double currentX
495
+ def y1:double currentY
496
+ if (lastCtrlKind == "Q") {
497
+ x1 = ((2.0 * currentX) - lastCtrlX)
498
+ y1 = ((2.0 * currentY) - lastCtrlY)
499
+ }
500
+ this.emitQuad(x1 y1 x y)
501
+ return true
502
+ }
503
+
504
+ ; ----------------------------------------------------------------- A a
505
+ if ((cmdInt == 65) || (cmdInt == 97)) {
506
+ def rx:double (this.parseNumber())
507
+ def ry:double (this.parseNumber())
508
+ def rot:double (this.parseNumber())
509
+ def largeArc:boolean (this.parseFlag())
510
+ def sweep:boolean (this.parseFlag())
511
+ def x:double (this.parseNumber())
512
+ def y:double (this.parseNumber())
513
+ if numFail {
514
+ return false
515
+ }
516
+ if (cmdInt == 97) {
517
+ x = currentX + x
518
+ y = currentY + y
519
+ }
520
+ this.emitArc(rx ry rot largeArc sweep x y)
521
+ return true
522
+ }
523
+
524
+ ; ----------------------------------------------------------------- Z z
525
+ if ((cmdInt == 90) || (cmdInt == 122)) {
526
+ def pathCmd (new PathCommand())
527
+ pathCmd.type = "Z"
528
+ push commands pathCmd
529
+ currentX = startX
530
+ currentY = startY
531
+ lastCtrlKind = ""
532
+ return true
533
+ }
534
+
535
+ ; Anything else is outside this profile. Saying so is the point: an
536
+ ; unknown letter used to be skipped one character at a time, taking its
537
+ ; arguments with it, so the path silently closed across the gap and
538
+ ; looked like art that had always been that shape.
539
+ this.addError("unsupported path command '" + (strfromcode cmdInt) + "'")
540
+ return false
541
+ }
542
+
543
+ ; --------------------------------------------------------------------------
544
+ ; Segment emitters — one place each, so the pen and the reflection state
545
+ ; cannot get out of step with what was pushed.
546
+ ; --------------------------------------------------------------------------
547
+
548
+ fn emitLine:void (x:double y:double) {
549
+ def pathCmd (new PathCommand())
550
+ pathCmd.type = "L"
551
+ pathCmd.x = x
552
+ pathCmd.y = y
553
+ push commands pathCmd
554
+ currentX = x
555
+ currentY = y
556
+ lastCtrlKind = ""
557
+ }
558
+
559
+ fn emitCubic:void (x1:double y1:double x2:double y2:double x:double y:double) {
560
+ def pathCmd (new PathCommand())
561
+ pathCmd.type = "C"
562
+ pathCmd.x1 = x1
563
+ pathCmd.y1 = y1
564
+ pathCmd.x2 = x2
565
+ pathCmd.y2 = y2
566
+ pathCmd.x = x
567
+ pathCmd.y = y
568
+ push commands pathCmd
569
+ currentX = x
570
+ currentY = y
571
+ lastCtrlX = x2
572
+ lastCtrlY = y2
573
+ lastCtrlKind = "C"
574
+ }
575
+
576
+ fn emitQuad:void (x1:double y1:double x:double y:double) {
577
+ def pathCmd (new PathCommand())
578
+ pathCmd.type = "Q"
579
+ pathCmd.x1 = x1
580
+ pathCmd.y1 = y1
581
+ pathCmd.x = x
582
+ pathCmd.y = y
583
+ push commands pathCmd
584
+ currentX = x
585
+ currentY = y
586
+ lastCtrlX = x1
587
+ lastCtrlY = y1
588
+ lastCtrlKind = "Q"
589
+ }
590
+
591
+ ; --------------------------------------------------------------------------
592
+ ; Elliptical arc -> cubics
593
+ ; --------------------------------------------------------------------------
594
+ ; SVG 1.1 Appendix F.6.5 (endpoint -> centre parameterisation) and F.6.6
595
+ ; (correction of out-of-range radii). The arc is converted here, once, so
596
+ ; every consumer gets arcs for free: PDF has no arc operator, the raster
597
+ ; side flattens curves anyway, and the previous state of affairs was that
598
+ ; PathCommand carried rx/ry/rotation/largeArc/sweep fields that nothing ever
599
+ ; produced.
600
+ ;
601
+ ; The conversion follows the same construction as the older SVGPath.rgr in
602
+ ; evg/original/: split the sweep into pieces of at most 90 degrees and give
603
+ ; each piece a cubic whose control points sit along the arc's tangents.
604
+ fn emitArc:void (rxIn:double ryIn:double rotDeg:double largeArc:boolean sweep:boolean x:double y:double) {
605
+ def x1:double currentX
606
+ def y1:double currentY
607
+ def x2:double x
608
+ def y2:double y
609
+
610
+ def rx:double (fabs rxIn)
611
+ def ry:double (fabs ryIn)
612
+
613
+ def dx:double (x1 - x2)
614
+ def dy:double (y1 - y2)
615
+ def d:double (sqrt ((dx * dx) + (dy * dy)))
616
+
617
+ ; Degenerate: coincident endpoints, or a radius of zero. The spec says
618
+ ; to draw a straight line — not to drop the segment.
619
+ if (d < 0.00001) {
620
+ this.emitLine(x2 y2)
621
+ return
622
+ }
623
+ if (rx < 0.00001) {
624
+ this.emitLine(x2 y2)
625
+ return
626
+ }
627
+ if (ry < 0.00001) {
628
+ this.emitLine(x2 y2)
629
+ return
630
+ }
631
+
632
+ def PI:double (M_PI)
633
+ def rot:double ((rotDeg / 180.0) * PI)
634
+ def sinrot:double (sin rot)
635
+ def cosrot:double (cos rot)
636
+
637
+ ; Endpoint -> centre, step 1: the endpoint midpoint in the ellipse's
638
+ ; own rotated frame.
639
+ def x1p:double (((cosrot * dx) / 2.0) + ((sinrot * dy) / 2.0))
640
+ def y1p:double ((((0.0 - sinrot) * dx) / 2.0) + ((cosrot * dy) / 2.0))
641
+
642
+ ; F.6.6: radii too small to span the endpoints are scaled up until they
643
+ ; exactly reach, rather than the arc being abandoned.
644
+ def lambda:double (((x1p * x1p) / (rx * rx)) + ((y1p * y1p) / (ry * ry)))
645
+ if (lambda > 1.0) {
646
+ def k:double (sqrt lambda)
647
+ rx = (rx * k)
648
+ ry = (ry * k)
649
+ }
650
+
651
+ def sa:double ((((rx * rx) * (ry * ry)) - ((rx * rx) * (y1p * y1p))) - ((ry * ry) * (x1p * x1p)))
652
+ def sb:double (((rx * rx) * (y1p * y1p)) + ((ry * ry) * (x1p * x1p)))
653
+ if (sa < 0.0) {
654
+ sa = 0.0
655
+ }
656
+ def s:double 0.0
657
+ if (sb > 0.0) {
658
+ s = (sqrt (sa / sb))
659
+ }
660
+ ; The two flags pick which of the two possible ellipses is drawn.
661
+ if (largeArc == sweep) {
662
+ s = (0.0 - s)
663
+ }
664
+
665
+ def cxp:double (((s * rx) * y1p) / ry)
666
+ def cyp:double ((((0.0 - s) * ry) * x1p) / rx)
667
+ def cx:double (((x1 + x2) / 2.0) + ((cosrot * cxp) - (sinrot * cyp)))
668
+ def cy:double (((y1 + y2) / 2.0) + ((sinrot * cxp) + (cosrot * cyp)))
669
+
670
+ ; Start angle and swept angle, on the unit circle the ellipse maps from.
671
+ def ux:double ((x1p - cxp) / rx)
672
+ def uy:double ((y1p - cyp) / ry)
673
+ def vx:double (((0.0 - x1p) - cxp) / rx)
674
+ def vy:double (((0.0 - y1p) - cyp) / ry)
675
+
676
+ def a1:double (this.vecAngle(1.0 0.0 ux uy))
677
+ def da:double (this.vecAngle(ux uy vx vy))
678
+
679
+ if (sweep == false) {
680
+ if (da > 0.0) {
681
+ da = (da - (2.0 * PI))
682
+ }
683
+ } {
684
+ if (da < 0.0) {
685
+ da = ((2.0 * PI) + da)
686
+ }
687
+ }
688
+
689
+ ; One cubic per <=90 degrees keeps the approximation error far below
690
+ ; anything a renderer can show.
691
+ def ndivs:int (to_int (((fabs da) / (PI * 0.5)) + 1.0))
692
+ def hda:double ((da / (to_double ndivs)) / 2.0)
693
+ def kappa:double (fabs (((4.0 / 3.0) * (1.0 - (cos hda))) / (sin hda)))
694
+ if (da < 0.0) {
695
+ kappa = (0.0 - kappa)
696
+ }
697
+
698
+ def px:double 0.0
699
+ def py:double 0.0
700
+ def ptanx:double 0.0
701
+ def ptany:double 0.0
702
+
703
+ def k:int 0
704
+ while (k <= ndivs) {
705
+ def a:double (a1 + ((da * (to_double k)) / (to_double ndivs)))
706
+ def cosa:double (cos a)
707
+ def sina:double (sin a)
708
+
709
+ ; Point on the ellipse, and the tangent scaled by kappa — both
710
+ ; carried through the same rotation and translation.
711
+ def ex:double (((cosrot * (cosa * rx)) - (sinrot * (sina * ry))) + cx)
712
+ def ey:double (((sinrot * (cosa * rx)) + (cosrot * (sina * ry))) + cy)
713
+ def tvx:double ((0.0 - sina) * (rx * kappa))
714
+ def tvy:double (cosa * (ry * kappa))
715
+ def tanx:double ((cosrot * tvx) - (sinrot * tvy))
716
+ def tany:double ((sinrot * tvx) + (cosrot * tvy))
717
+
718
+ if (k > 0) {
719
+ this.emitCubic((px + ptanx) (py + ptany) (ex - tanx) (ey - tany) ex ey)
720
+ }
721
+ px = ex
722
+ py = ey
723
+ ptanx = tanx
724
+ ptany = tany
725
+ k = (k + 1)
726
+ }
727
+
728
+ ; An arc leaves no control point for S/T to reflect.
729
+ currentX = x2
730
+ currentY = y2
731
+ lastCtrlKind = ""
732
+ }
733
+
734
+ ; Signed angle from (ux, uy) to (vx, vy), in radians.
735
+ fn vecAngle:double (ux:double uy:double vx:double vy:double) {
736
+ def magU:double (sqrt ((ux * ux) + (uy * uy)))
737
+ def magV:double (sqrt ((vx * vx) + (vy * vy)))
738
+ def denom:double (magU * magV)
739
+ if (denom < 0.0000000001) {
740
+ return 0.0
741
+ }
742
+ def r:double (((ux * vx) + (uy * vy)) / denom)
743
+ ; Rounding can push the cosine a hair outside [-1, 1], where acos is
744
+ ; undefined and would poison every later coordinate.
745
+ if (r < (0.0 - 1.0)) {
746
+ r = (0.0 - 1.0)
747
+ }
748
+ if (r > 1.0) {
749
+ r = 1.0
750
+ }
751
+ def sign:double 1.0
752
+ if ((ux * vy) < (uy * vx)) {
753
+ sign = (0.0 - 1.0)
754
+ }
755
+ return (sign * (acos r))
756
+ }
757
+
758
+
759
+ fn calculateBounds:void () {
760
+ if ((array_length commands) == 0) {
761
+ return
762
+ }
763
+
764
+ def minX:double 999999.0
765
+ def minY:double 999999.0
766
+ def maxX:double -999999.0
767
+ def maxY:double -999999.0
768
+
769
+ def i:int 0
770
+ while (i < (array_length commands)) {
771
+ def cmd:PathCommand (itemAt commands i)
772
+
773
+ if ((cmd.type == "M") || (cmd.type == "L")) {
774
+ if (cmd.x < minX) {
775
+ minX = cmd.x
776
+ }
777
+ if (cmd.x > maxX) {
778
+ maxX = cmd.x
779
+ }
780
+ if (cmd.y < minY) {
781
+ minY = cmd.y
782
+ }
783
+ if (cmd.y > maxY) {
784
+ maxY = cmd.y
785
+ }
786
+ }
787
+
788
+ if (cmd.type == "C") {
789
+ ; Include control points for better bounds
790
+ if (cmd.x1 < minX) {
791
+ minX = cmd.x1
792
+ }
793
+ if (cmd.x1 > maxX) {
794
+ maxX = cmd.x1
795
+ }
796
+ if (cmd.y1 < minY) {
797
+ minY = cmd.y1
798
+ }
799
+ if (cmd.y1 > maxY) {
800
+ maxY = cmd.y1
801
+ }
802
+ if (cmd.x2 < minX) {
803
+ minX = cmd.x2
804
+ }
805
+ if (cmd.x2 > maxX) {
806
+ maxX = cmd.x2
807
+ }
808
+ if (cmd.y2 < minY) {
809
+ minY = cmd.y2
810
+ }
811
+ if (cmd.y2 > maxY) {
812
+ maxY = cmd.y2
813
+ }
814
+ if (cmd.x < minX) {
815
+ minX = cmd.x
816
+ }
817
+ if (cmd.x > maxX) {
818
+ maxX = cmd.x
819
+ }
820
+ if (cmd.y < minY) {
821
+ minY = cmd.y
822
+ }
823
+ if (cmd.y > maxY) {
824
+ maxY = cmd.y
825
+ }
826
+ }
827
+
828
+ if (cmd.type == "Q") {
829
+ ; Include control point
830
+ if (cmd.x1 < minX) {
831
+ minX = cmd.x1
832
+ }
833
+ if (cmd.x1 > maxX) {
834
+ maxX = cmd.x1
835
+ }
836
+ if (cmd.y1 < minY) {
837
+ minY = cmd.y1
838
+ }
839
+ if (cmd.y1 > maxY) {
840
+ maxY = cmd.y1
841
+ }
842
+ if (cmd.x < minX) {
843
+ minX = cmd.x
844
+ }
845
+ if (cmd.x > maxX) {
846
+ maxX = cmd.x
847
+ }
848
+ if (cmd.y < minY) {
849
+ minY = cmd.y
850
+ }
851
+ if (cmd.y > maxY) {
852
+ maxY = cmd.y
853
+ }
854
+ }
855
+
856
+ i = i + 1
857
+ }
858
+
859
+ bounds.minX = minX
860
+ bounds.minY = minY
861
+ bounds.maxX = maxX
862
+ bounds.maxY = maxY
863
+ bounds.width = maxX - minX
864
+ bounds.height = maxY - minY
865
+ }
866
+
867
+ fn getBounds:PathBounds () {
868
+ def result:PathBounds (unwrap bounds)
869
+ return result
870
+ }
871
+
872
+ fn getCommands:[PathCommand] () {
873
+ return commands
874
+ }
875
+
876
+ ; Scale path commands to fit target dimensions
877
+ fn getScaledCommands:[PathCommand] (targetWidth:double targetHeight:double) {
878
+ def scaleX:double 1.0
879
+ def scaleY:double 1.0
880
+
881
+ if (bounds.width > 0.0) {
882
+ scaleX = targetWidth / bounds.width
883
+ }
884
+ if (bounds.height > 0.0) {
885
+ scaleY = targetHeight / bounds.height
886
+ }
887
+
888
+ def scaled:[PathCommand]
889
+ def i:int 0
890
+ while (i < (array_length commands)) {
891
+ def cmd:PathCommand (itemAt commands i)
892
+ def newCmd (new PathCommand())
893
+ newCmd.type = cmd.type
894
+
895
+ if ((cmd.type == "M") || (cmd.type == "L")) {
896
+ newCmd.x = (cmd.x - bounds.minX) * scaleX
897
+ newCmd.y = (cmd.y - bounds.minY) * scaleY
898
+ }
899
+
900
+ if (cmd.type == "C") {
901
+ newCmd.x1 = (cmd.x1 - bounds.minX) * scaleX
902
+ newCmd.y1 = (cmd.y1 - bounds.minY) * scaleY
903
+ newCmd.x2 = (cmd.x2 - bounds.minX) * scaleX
904
+ newCmd.y2 = (cmd.y2 - bounds.minY) * scaleY
905
+ newCmd.x = (cmd.x - bounds.minX) * scaleX
906
+ newCmd.y = (cmd.y - bounds.minY) * scaleY
907
+ }
908
+
909
+ if (cmd.type == "Q") {
910
+ newCmd.x1 = (cmd.x1 - bounds.minX) * scaleX
911
+ newCmd.y1 = (cmd.y1 - bounds.minY) * scaleY
912
+ newCmd.x = (cmd.x - bounds.minX) * scaleX
913
+ newCmd.y = (cmd.y - bounds.minY) * scaleY
914
+ }
915
+
916
+ push scaled newCmd
917
+ i = i + 1
918
+ }
919
+
920
+ return scaled
921
+ }
922
+
923
+ ; Flatten the path to a polygon outline: a flat [x0,y0,x1,y1,...] list of
924
+ ; points, sampling C/Q curves into `steps` segments each and approximating A
925
+ ; arcs as a straight line to the endpoint. M starts a new point, Z closes the
926
+ ; ring (the fill closes implicitly, so Z adds no point). Used by a filled-path
927
+ ; renderer (UIContext.fillPolygon) to rasterize <Path> elements.
928
+ ; Flatten to closed contours, transformed by an affine matrix on the way.
929
+ ;
930
+ ; The six matrix arguments are passed as plain numbers rather than a
931
+ ; Matrix2D so that this file stays free of dependencies — it is imported by
932
+ ; the PDF renderer, the WASM UI and the game-engine tests alike. They are in
933
+ ; SVG matrix(a b c d e f) order:
934
+ ;
935
+ ; x' = a*x + c*y + e
936
+ ; y' = b*x + d*y + f
937
+ ;
938
+ ; `steps` is the number of line segments each curve is split into. Doing the
939
+ ; transform HERE rather than afterwards matters: the subdivision then
940
+ ; happens at the size the shape is actually drawn, so a path scaled up does
941
+ ; not show the facets of a curve flattened at its original scale.
942
+ ; A parser wrapped around commands that were produced somewhere else — by
943
+ ; VectorShapes, by PathBuilder, or by the SVG importer, which resolves a
944
+ ; whole document into per-shape command lists. Everything below the parse
945
+ ; step (bounds, flattening, ring extraction) then works on them unchanged,
946
+ ; so a renderer has one way to turn commands into geometry rather than one
947
+ ; per producer.
948
+ sfn fromCommands:SVGPathParser (cmds:[PathCommand]) {
949
+ def p (new SVGPathParser())
950
+ p.commands = cmds
951
+ p.calculateBounds()
952
+ return p
953
+ }
954
+
955
+ ; --- how finely to cut ONE curve ------------------------------------------
956
+ ;
957
+ ; `steps` is the caller's ceiling, chosen from the size of the thing being
958
+ ; drawn. Inside a path that size says nothing about the curves: a heading
959
+ ; 2,855 pixels wide cut every curve of every glyph in it 47 ways in order
960
+ ; to draw those glyphs two pixels tall, and one frame of a Figma board
961
+ ; carried 2.5 million points because of it.
962
+ ;
963
+ ; So a curve is cut by ITS OWN length — the control polygon, measured
964
+ ; through the transform, at about a point every two device pixels. Never
965
+ ; finer than the caller asked for, so nothing gets slower or heavier than
966
+ ; it was; never fewer than two, so a curve is never a straight line.
967
+ ; ...and when there is NO transform, there is no device length to cut by.
968
+ ; `flattenRingsPlain` is how a shape catalogue flattens: the path is
969
+ ; authored in a unit box, 0 to 1, and scaled to the slide afterwards. A
970
+ ; curve of it is half a unit long, so the rule above cuts it in two and
971
+ ; a smiley face's head came back as an octagon — nine preset shapes, all
972
+ ; of which still returned points, so nothing counted them as broken.
973
+ ; There the caller's number is the count it has always been.
974
+ def plain:boolean false
975
+
976
+ fn stepsFor:int (cap:int len:double) {
977
+ if plain {
978
+ return cap
979
+ }
980
+ return (SVGPathParser.curveSteps(cap len))
981
+ }
982
+
983
+ sfn curveSteps:int (cap:int len:double) {
984
+ def want:int (to_int ((len / 2.0) + 0.999))
985
+ if (want < 2) {
986
+ want = 2
987
+ }
988
+ if (want > cap) {
989
+ want = cap
990
+ }
991
+ return want
992
+ }
993
+
994
+ ; The distance between two USER-space points once the transform has put
995
+ ; them on the device.
996
+ sfn devLen:double (x0:double y0:double x1:double y1:double ma:double mb:double mc:double md:double) {
997
+ def dx:double (x1 - x0)
998
+ def dy:double (y1 - y0)
999
+ def ax:double ((ma * dx) + (mc * dy))
1000
+ def ay:double ((mb * dx) + (md * dy))
1001
+ return (sqrt ((ax * ax) + (ay * ay)))
1002
+ }
1003
+
1004
+ fn flattenRings:[PathRing] (steps:int ma:double mb:double mc:double md:double me:double mf:double) {
1005
+ def rings:[PathRing]
1006
+ def current (new PathRing())
1007
+ def started:boolean false
1008
+
1009
+ ; Pen position in USER units — the curve maths runs before the
1010
+ ; transform, so control points and the pen have to agree.
1011
+ def cx:double 0.0
1012
+ def cy:double 0.0
1013
+ def sx:double 0.0
1014
+ def sy:double 0.0
1015
+
1016
+ def n:int (array_length commands)
1017
+ def k:int 0
1018
+ while (k < n) {
1019
+ def cmd:PathCommand (itemAt commands k)
1020
+
1021
+ if (cmd.type == "M") {
1022
+ ; A new subpath closes whatever was open.
1023
+ if started {
1024
+ if ((current.pointCount()) >= 2) {
1025
+ push rings current
1026
+ }
1027
+ }
1028
+ current = (new PathRing())
1029
+ started = true
1030
+ cx = cmd.x
1031
+ cy = cmd.y
1032
+ sx = cmd.x
1033
+ sy = cmd.y
1034
+ push current.pts ((ma * cx) + ((mc * cy) + me))
1035
+ push current.pts ((mb * cx) + ((md * cy) + mf))
1036
+ }
1037
+
1038
+ if (cmd.type == "L") {
1039
+ cx = cmd.x
1040
+ cy = cmd.y
1041
+ push current.pts ((ma * cx) + ((mc * cy) + me))
1042
+ push current.pts ((mb * cx) + ((md * cy) + mf))
1043
+ }
1044
+
1045
+ if (cmd.type == "C") {
1046
+ def d1:double (SVGPathParser.devLen(cx cy cmd.x1 cmd.y1 ma mb mc md))
1047
+ def d2:double (SVGPathParser.devLen(cmd.x1 cmd.y1 cmd.x2 cmd.y2 ma mb mc md))
1048
+ def d3:double (SVGPathParser.devLen(cmd.x2 cmd.y2 cmd.x cmd.y ma mb mc md))
1049
+ def cs:int (this.stepsFor(steps ((d1 + d2) + d3)))
1050
+ def s:int 1
1051
+ while (s <= cs) {
1052
+ def tt:double ((to_double s) / (to_double cs))
1053
+ def u:double (1.0 - tt)
1054
+ def b0:double ((u * u) * u)
1055
+ def b1:double (((3.0 * u) * u) * tt)
1056
+ def b2:double (((3.0 * u) * tt) * tt)
1057
+ def b3:double ((tt * tt) * tt)
1058
+ def px:double ((((b0 * cx) + (b1 * cmd.x1)) + (b2 * cmd.x2)) + (b3 * cmd.x))
1059
+ def py:double ((((b0 * cy) + (b1 * cmd.y1)) + (b2 * cmd.y2)) + (b3 * cmd.y))
1060
+ push current.pts ((ma * px) + ((mc * py) + me))
1061
+ push current.pts ((mb * px) + ((md * py) + mf))
1062
+ s = (s + 1)
1063
+ }
1064
+ cx = cmd.x
1065
+ cy = cmd.y
1066
+ }
1067
+
1068
+ if (cmd.type == "Q") {
1069
+ def q1:double (SVGPathParser.devLen(cx cy cmd.x1 cmd.y1 ma mb mc md))
1070
+ def q2:double (SVGPathParser.devLen(cmd.x1 cmd.y1 cmd.x cmd.y ma mb mc md))
1071
+ def qs:int (this.stepsFor(steps (q1 + q2)))
1072
+ def s:int 1
1073
+ while (s <= qs) {
1074
+ def tt:double ((to_double s) / (to_double qs))
1075
+ def u:double (1.0 - tt)
1076
+ def b0:double (u * u)
1077
+ def b1:double ((2.0 * u) * tt)
1078
+ def b2:double (tt * tt)
1079
+ def px:double (((b0 * cx) + (b1 * cmd.x1)) + (b2 * cmd.x))
1080
+ def py:double (((b0 * cy) + (b1 * cmd.y1)) + (b2 * cmd.y))
1081
+ push current.pts ((ma * px) + ((mc * py) + me))
1082
+ push current.pts ((mb * px) + ((md * py) + mf))
1083
+ s = (s + 1)
1084
+ }
1085
+ cx = cmd.x
1086
+ cy = cmd.y
1087
+ }
1088
+
1089
+ if (cmd.type == "Z") {
1090
+ ; The fill closes the ring itself, so Z adds no point — it ends
1091
+ ; the ring and returns the pen to the subpath start. The ring
1092
+ ; remembers it was closed, for the stroke.
1093
+ if started {
1094
+ current.closed = true
1095
+ if ((current.pointCount()) >= 2) {
1096
+ push rings current
1097
+ }
1098
+ }
1099
+ current = (new PathRing())
1100
+ started = false
1101
+ cx = sx
1102
+ cy = sy
1103
+ }
1104
+
1105
+ k = (k + 1)
1106
+ }
1107
+
1108
+ ; An unclosed final subpath still fills — SVG closes it implicitly for
1109
+ ; the purposes of filling.
1110
+ if started {
1111
+ if ((current.pointCount()) >= 2) {
1112
+ push rings current
1113
+ }
1114
+ }
1115
+
1116
+ return rings
1117
+ }
1118
+
1119
+ ; Flatten with no transform.
1120
+ fn flattenRingsPlain:[PathRing] (steps:int) {
1121
+ plain = true
1122
+ def rings:[PathRing] (this.flattenRings(steps 1.0 0.0 0.0 1.0 0.0 0.0))
1123
+ plain = false
1124
+ return rings
1125
+ }
1126
+
1127
+ ; Older flattening that returns every point of every subpath in ONE list.
1128
+ ; Kept because existing callers depend on the signature, but it cannot
1129
+ ; express a shape with more than one contour: use flattenRings for anything
1130
+ ; that will be filled.
1131
+ fn flatten:[double] (steps:int) {
1132
+ def pts:[double]
1133
+ def cx:double 0.0
1134
+ def cy:double 0.0
1135
+ def n:int (array_length commands)
1136
+ def i:int 0
1137
+ while (i < n) {
1138
+ def cmd:PathCommand (itemAt commands i)
1139
+ if (cmd.type == "M") {
1140
+ cx = cmd.x
1141
+ cy = cmd.y
1142
+ push pts cx
1143
+ push pts cy
1144
+ }
1145
+ if (cmd.type == "L") {
1146
+ cx = cmd.x
1147
+ cy = cmd.y
1148
+ push pts cx
1149
+ push pts cy
1150
+ }
1151
+ if (cmd.type == "C") {
1152
+ def s:int 1
1153
+ while (s <= steps) {
1154
+ def tt:double ((to_double s) / (to_double steps))
1155
+ def u:double (1.0 - tt)
1156
+ ; cubic Bezier: u^3 P0 + 3u^2 t C1 + 3u t^2 C2 + t^3 P1
1157
+ def b0:double (((u * u) * u))
1158
+ def b1:double ((((3.0 * u) * u) * tt))
1159
+ def b2:double ((((3.0 * u) * tt) * tt))
1160
+ def b3:double (((tt * tt) * tt))
1161
+ def px:double ((((b0 * cx) + (b1 * cmd.x1)) + (b2 * cmd.x2)) + (b3 * cmd.x))
1162
+ def py:double ((((b0 * cy) + (b1 * cmd.y1)) + (b2 * cmd.y2)) + (b3 * cmd.y))
1163
+ push pts px
1164
+ push pts py
1165
+ s = (s + 1)
1166
+ }
1167
+ cx = cmd.x
1168
+ cy = cmd.y
1169
+ }
1170
+ if (cmd.type == "Q") {
1171
+ def s:int 1
1172
+ while (s <= steps) {
1173
+ def tt:double ((to_double s) / (to_double steps))
1174
+ def u:double (1.0 - tt)
1175
+ ; quadratic Bezier: u^2 P0 + 2u t C + t^2 P1
1176
+ def b0:double ((u * u))
1177
+ def b1:double (((2.0 * u) * tt))
1178
+ def b2:double ((tt * tt))
1179
+ def px:double (((b0 * cx) + (b1 * cmd.x1)) + (b2 * cmd.x))
1180
+ def py:double (((b0 * cy) + (b1 * cmd.y1)) + (b2 * cmd.y))
1181
+ push pts px
1182
+ push pts py
1183
+ s = (s + 1)
1184
+ }
1185
+ cx = cmd.x
1186
+ cy = cmd.y
1187
+ }
1188
+ if (cmd.type == "A") {
1189
+ ; arc approximated as a straight segment to the endpoint.
1190
+ cx = cmd.x
1191
+ cy = cmd.y
1192
+ push pts cx
1193
+ push pts cy
1194
+ }
1195
+ i = (i + 1)
1196
+ }
1197
+ return pts
1198
+ }
1199
+ }