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,1736 @@
1
+ ; SPDX-License-Identifier: MIT
2
+
3
+ Import "SVGPathParser.rgr"
4
+ Import "VectorShapes.rgr"
5
+ Import "VectorViewBox.rgr"
6
+ Import "EVGColor.rgr"
7
+
8
+ ; ==============================================================================
9
+ ; SvgParser.rgr — an SVG document, read into the vector layer
10
+ ; ==============================================================================
11
+ ; PLAN_VECTOR_IR.md Stage 4 and Stage 5.
12
+ ;
13
+ ; The plan's central decision is that EVG does not become an SVG engine: it has
14
+ ; a renderer-neutral vector layer, and SVG is one importer into it. Stages 0-3
15
+ ; built the layer — the shared viewBox transform, the full path grammar, the
16
+ ; shared rasterizer, the basic shapes as paths. This file is the importer, and
17
+ ; it is deliberately the only place in the tree that knows any XML.
18
+ ;
19
+ ; What comes out is not a node tree. It is a flat list of `SvgVectorItem` —
20
+ ; resolved geometry plus resolved paint — because that is what every renderer
21
+ ; can already draw:
22
+ ;
23
+ ; markup ──► SvgParser ──► [SvgVectorItem] ──┬──► PDF (path operators)
24
+ ; ├──► HTML (inline <svg>)
25
+ ; └──► raster (VectorRasterizer)
26
+ ;
27
+ ; Three consequences worth stating, because they are the point rather than
28
+ ; side effects.
29
+ ;
30
+ ; **Transforms are baked.** Each item's commands are absolute, in the document's
31
+ ; own user space, with every enclosing `transform` already applied. A renderer
32
+ ; therefore needs no transform stack, no group traversal and no state machine —
33
+ ; it draws a list. Affine maps take cubics to cubics exactly, so nothing is
34
+ ; approximated by doing this.
35
+ ;
36
+ ; **Paint is resolved.** Inheritance down groups, `fill="none"`, the opacity
37
+ ; properties: all settled here, once, so the three renderers cannot disagree
38
+ ; about what colour a shape is the way they disagree about gradients today
39
+ ; (§6).
40
+ ;
41
+ ; **HTML goes through the IR too.** It would be less code to hand the original
42
+ ; markup to the browser and let it render — and it would immediately reintroduce
43
+ ; the whole class of defect this plan exists to remove, because the browser
44
+ ; would draw filters, gradients, text and clip paths that PDF and PNG cannot.
45
+ ; Out of profile has to mean out of profile everywhere, or the profile is not
46
+ ; real.
47
+ ;
48
+ ; The profile is §8: no script, no animation, no foreignObject, no filters, no
49
+ ; masks, no external CSS, no external references. Everything outside it is
50
+ ; *reported* rather than silently dropped (§7.4) — that is what makes a small
51
+ ; profile safe to ship, because "not supported" is a state you can see instead
52
+ ; of a shape that quietly went missing.
53
+ ;
54
+ ; Imported SVG is untrusted input, so the parser also refuses by construction
55
+ ; rather than by mitigation: no entity machinery exists at all (which is what
56
+ ; rules out billion-laughs and XXE), no href leaves the document, and node,
57
+ ; depth and command counts are capped and reported.
58
+ ; ==============================================================================
59
+
60
+ ; ------------------------------------------------------------------------------
61
+ ; SvgVectorItem — one drawable, ready to render.
62
+ ; ------------------------------------------------------------------------------
63
+ ; `commands` are absolute PathCommands in the document's user space. An unset
64
+ ; fill or stroke colour means that operation does not happen at all — the same
65
+ ; convention EVGElement already uses, so a renderer's existing "is there a fill?"
66
+ ; check works unchanged.
67
+ class SvgVectorItem {
68
+ def commands:[PathCommand]
69
+ def fillColor:EVGColor
70
+ def strokeColor:EVGColor
71
+ def strokeWidth:double 1.0
72
+ def fillRule:string "nonzero"
73
+ def dashArray:string ""
74
+ def dashOffset:double 0.0
75
+
76
+ Constructor () {
77
+ def c:[PathCommand]
78
+ commands = c
79
+ fillColor = (EVGColor.noColor())
80
+ strokeColor = (EVGColor.noColor())
81
+ strokeWidth = 1.0
82
+ fillRule = "nonzero"
83
+ dashArray = ""
84
+ dashOffset = 0.0
85
+ }
86
+
87
+ fn hasFill:boolean () {
88
+ return fillColor.isSet
89
+ }
90
+
91
+ fn hasStroke:boolean () {
92
+ if (strokeColor.isSet == false) {
93
+ return false
94
+ }
95
+ return (strokeWidth > 0.0)
96
+ }
97
+
98
+ ; The `d` string for this item, for targets that speak path data. Round-trips
99
+ ; through SVGPathParser, which is what vector_shapes_test already asserts of
100
+ ; VectorShapes.asPathData.
101
+ fn pathData:string () {
102
+ return (VectorShapes.asPathData(commands))
103
+ }
104
+ }
105
+
106
+ ; ------------------------------------------------------------------------------
107
+ ; SvgStyleState — what a shape inherits from the groups above it.
108
+ ; ------------------------------------------------------------------------------
109
+ ; SVG's initial values, not EVG's: fill is black, stroke is none, stroke-width
110
+ ; is 1. An imported file that sets no fill anywhere is black in a browser, so it
111
+ ; is black here.
112
+ ;
113
+ ; The three opacity properties are kept apart while parsing and multiplied into
114
+ ; the paint alpha at emit time. Group opacity is genuinely NOT the same thing —
115
+ ; the spec composites a group off-screen and then fades it as a unit, so
116
+ ; overlapping children inside a faded group show through each other under this
117
+ ; treatment and would not in a browser. Doing it properly needs an off-screen
118
+ ; buffer per group, which the raster target has no notion of and the PDF target
119
+ ; would need a transparency group for; it is recorded here rather than left to
120
+ ; be discovered, and PLAN_VECTOR_IR.md Stage 4 lists it among what did not
121
+ ; land.
122
+ class SvgStyleState {
123
+ def ctm:Matrix2D
124
+ def fill:EVGColor
125
+ def stroke:EVGColor
126
+ def strokeWidth:double 1.0
127
+ def fillRule:string "nonzero"
128
+ def dashArray:string ""
129
+ def dashOffset:double 0.0
130
+ def fillOpacity:double 1.0
131
+ def strokeOpacity:double 1.0
132
+ def groupOpacity:double 1.0
133
+
134
+ Constructor () {
135
+ ctm = (Matrix2D.identity())
136
+ fill = (EVGColor.black())
137
+ stroke = (EVGColor.noColor())
138
+ strokeWidth = 1.0
139
+ fillRule = "nonzero"
140
+ dashArray = ""
141
+ dashOffset = 0.0
142
+ fillOpacity = 1.0
143
+ strokeOpacity = 1.0
144
+ groupOpacity = 1.0
145
+ }
146
+
147
+ fn copy:SvgStyleState () {
148
+ def s (new SvgStyleState())
149
+ s.ctm = ctm
150
+ s.fill = fill
151
+ s.stroke = stroke
152
+ s.strokeWidth = strokeWidth
153
+ s.fillRule = fillRule
154
+ s.dashArray = dashArray
155
+ s.dashOffset = dashOffset
156
+ s.fillOpacity = fillOpacity
157
+ s.strokeOpacity = strokeOpacity
158
+ s.groupOpacity = groupOpacity
159
+ return s
160
+ }
161
+ }
162
+
163
+ ; ------------------------------------------------------------------------------
164
+ ; SvgDocument — the parse result.
165
+ ; ------------------------------------------------------------------------------
166
+ ; `errors` are conditions that stopped or truncated the parse. `warnings` are
167
+ ; things the document asked for that this profile does not do — the document is
168
+ ; still drawable, just not completely. Keeping the two apart matters for a
169
+ ; caller deciding whether to print or to fail.
170
+ class SvgDocument {
171
+ def items:[SvgVectorItem]
172
+ def viewBox:ViewBoxRect
173
+ ; The `width`/`height` attributes on the root, in user units. Zero when
174
+ ; absent or given in a unit this profile does not resolve (%, em, ...).
175
+ def width:double 0.0
176
+ def height:double 0.0
177
+ def warnings:[string]
178
+ def errors:[string]
179
+ def truncated:boolean false
180
+
181
+ Constructor () {
182
+ def it:[SvgVectorItem]
183
+ items = it
184
+ def w:[string]
185
+ warnings = w
186
+ def e:[string]
187
+ errors = e
188
+ viewBox = (new ViewBoxRect())
189
+ width = 0.0
190
+ height = 0.0
191
+ truncated = false
192
+ }
193
+
194
+ fn itemCount:int () {
195
+ return (array_length items)
196
+ }
197
+
198
+ fn hasErrors:boolean () {
199
+ return ((array_length errors) > 0)
200
+ }
201
+
202
+ fn hasWarnings:boolean () {
203
+ return ((array_length warnings) > 0)
204
+ }
205
+
206
+ fn commandCount:int () {
207
+ def total:int 0
208
+ def k:int 0
209
+ while (k < (array_length items)) {
210
+ def it:SvgVectorItem (itemAt items k)
211
+ total = (total + (array_length it.commands))
212
+ k = (k + 1)
213
+ }
214
+ return total
215
+ }
216
+
217
+ fn joinLines:string (lines:[string]) {
218
+ def n:int (array_length lines)
219
+ if (n == 0) {
220
+ return ""
221
+ }
222
+ def out:string (itemAt lines 0)
223
+ def k:int 1
224
+ while (k < n) {
225
+ out = (out + "; " + (itemAt lines k))
226
+ k = (k + 1)
227
+ }
228
+ return out
229
+ }
230
+
231
+ fn errorSummary:string () {
232
+ return (this.joinLines(errors))
233
+ }
234
+
235
+ fn warningSummary:string () {
236
+ return (this.joinLines(warnings))
237
+ }
238
+
239
+ ; The ink bounds of every item, control points included — the same
240
+ ; over-estimate SVGPathParser.calculateBounds makes, deliberately, so a
241
+ ; document with no viewBox is fitted the same way a `<Path>` with no viewBox
242
+ ; is.
243
+ fn bounds:PathBounds () {
244
+ def b (new PathBounds())
245
+ def minX:double 999999.0
246
+ def minY:double 999999.0
247
+ def maxX:double -999999.0
248
+ def maxY:double -999999.0
249
+ def any:boolean false
250
+
251
+ def k:int 0
252
+ while (k < (array_length items)) {
253
+ def it:SvgVectorItem (itemAt items k)
254
+ def j:int 0
255
+ while (j < (array_length it.commands)) {
256
+ def c:PathCommand (itemAt it.commands j)
257
+ if ((c.type == "Z") == false) {
258
+ any = true
259
+ if (c.x < minX) { minX = c.x }
260
+ if (c.x > maxX) { maxX = c.x }
261
+ if (c.y < minY) { minY = c.y }
262
+ if (c.y > maxY) { maxY = c.y }
263
+ }
264
+ if ((c.type == "C") || (c.type == "Q")) {
265
+ if (c.x1 < minX) { minX = c.x1 }
266
+ if (c.x1 > maxX) { maxX = c.x1 }
267
+ if (c.y1 < minY) { minY = c.y1 }
268
+ if (c.y1 > maxY) { maxY = c.y1 }
269
+ }
270
+ if (c.type == "C") {
271
+ if (c.x2 < minX) { minX = c.x2 }
272
+ if (c.x2 > maxX) { maxX = c.x2 }
273
+ if (c.y2 < minY) { minY = c.y2 }
274
+ if (c.y2 > maxY) { maxY = c.y2 }
275
+ }
276
+ j = (j + 1)
277
+ }
278
+ k = (k + 1)
279
+ }
280
+
281
+ if (any == false) {
282
+ return b
283
+ }
284
+ b.minX = minX
285
+ b.minY = minY
286
+ b.maxX = maxX
287
+ b.maxY = maxY
288
+ b.width = (maxX - minX)
289
+ b.height = (maxY - minY)
290
+ return b
291
+ }
292
+
293
+ ; Which viewBox governs this document: the declared one, else one
294
+ ; synthesised from the ink. Same rule, same fallback and same shared
295
+ ; implementation a `<Path>` goes through (VectorViewBox §2.1), so an
296
+ ; imported file and a hand-written path are placed by the same code.
297
+ fn effectiveViewBox:ViewBoxRect () {
298
+ if viewBox.isSet {
299
+ return (unwrap viewBox)
300
+ }
301
+ if ((width > 0.0) && (height > 0.0)) {
302
+ return (ViewBoxRect.create(0.0 0.0 width height))
303
+ }
304
+ def b:PathBounds (this.bounds())
305
+ return (VectorViewBox.effectiveViewBox("" b.minX b.minY b.width b.height))
306
+ }
307
+ }
308
+
309
+ ; ------------------------------------------------------------------------------
310
+ ; XML scanning
311
+ ; ------------------------------------------------------------------------------
312
+ class SvgAttr {
313
+ def name:string ""
314
+ def value:string ""
315
+ }
316
+
317
+ class SvgTag {
318
+ def name:string ""
319
+ def isEnd:boolean false
320
+ def selfClose:boolean false
321
+ def attrs:[SvgAttr]
322
+ ; False at end of input, or when scanning gave up.
323
+ def valid:boolean false
324
+
325
+ Constructor () {
326
+ def a:[SvgAttr]
327
+ attrs = a
328
+ name = ""
329
+ isEnd = false
330
+ selfClose = false
331
+ valid = false
332
+ }
333
+
334
+ fn has:boolean (n:string) {
335
+ def k:int 0
336
+ while (k < (array_length attrs)) {
337
+ def a:SvgAttr (itemAt attrs k)
338
+ if (a.name == n) {
339
+ return true
340
+ }
341
+ k = (k + 1)
342
+ }
343
+ return false
344
+ }
345
+
346
+ fn attr:string (n:string) {
347
+ def k:int 0
348
+ while (k < (array_length attrs)) {
349
+ def a:SvgAttr (itemAt attrs k)
350
+ if (a.name == n) {
351
+ return a.value
352
+ }
353
+ k = (k + 1)
354
+ }
355
+ return ""
356
+ }
357
+ }
358
+
359
+ ; ------------------------------------------------------------------------------
360
+ ; SvgParser
361
+ ; ------------------------------------------------------------------------------
362
+ class SvgParser {
363
+ def src:string ""
364
+ def pos:int 0
365
+ def len:int 0
366
+ def doc:SvgDocument
367
+
368
+ ; Caps. These exist so a hostile or merely enormous file fails with a
369
+ ; sentence rather than with an out-of-memory (§8).
370
+ def maxNodes:int 50000
371
+ def maxDepth:int 64
372
+ def maxCommands:int 500000
373
+ def maxUseDepth:int 8
374
+
375
+ ; The paint a shape gets when neither it nor anything above it names one.
376
+ ; SVG's initial value is black, and that is the default here — but the
377
+ ; hosting element can set it, which is what lets a themed icon set work:
378
+ ; the file declares geometry, the element (and therefore the stylesheet)
379
+ ; decides the colour, exactly as a hand-written `<Path>` already does. A
380
+ ; document that sets its own fills is unaffected, because its own value
381
+ ; wins. It is also the only sensible answer for `currentColor`, which is
382
+ ; otherwise a reference to a CSS property an imported file cannot see.
383
+ def initialFill:EVGColor
384
+
385
+ def nodeCount:int 0
386
+ def emittedCommands:int 0
387
+ def useDepth:int 0
388
+ def aborted:boolean false
389
+
390
+ ; id -> that element's own markup, for same-document <use>. Recorded in a
391
+ ; first pass so a <use> may appear before its target, which is legal and
392
+ ; common (Illustrator puts <defs> at the end as often as at the start).
393
+ def spans:[string:string]
394
+
395
+ ; Warnings are deduplicated by key. A file with four hundred gradient fills
396
+ ; should say "gradient paint is not supported" once; four hundred copies of
397
+ ; it is the same as saying nothing.
398
+ def warnedKeys:[string:boolean]
399
+
400
+ Constructor () {
401
+ doc = (new SvgDocument())
402
+ initialFill = (EVGColor.black())
403
+ }
404
+
405
+ fn setInitialFill:void (c:EVGColor) {
406
+ initialFill = c
407
+ }
408
+
409
+ ; --------------------------------------------------------------------------
410
+ ; Diagnostics
411
+ ; --------------------------------------------------------------------------
412
+
413
+ fn warn:void (key:string msg:string) {
414
+ def seen@(optional):boolean (get warnedKeys key)
415
+ ; Optional booleans are plain `bool` on C++; a bare `if seen` becomes
416
+ ; `seen != NULL` and warns. `!null?` emits the bool itself.
417
+ if (!null? seen) {
418
+ return
419
+ }
420
+ set warnedKeys key true
421
+ push doc.warnings msg
422
+ }
423
+
424
+ fn fail:void (msg:string) {
425
+ push doc.errors msg
426
+ doc.truncated = true
427
+ aborted = true
428
+ }
429
+
430
+ ; --------------------------------------------------------------------------
431
+ ; Entry point
432
+ ; --------------------------------------------------------------------------
433
+ fn parse:SvgDocument (source:string) {
434
+ doc = (new SvgDocument())
435
+ def emptySpans:[string:string]
436
+ spans = emptySpans
437
+ def emptyWarned:[string:boolean]
438
+ warnedKeys = emptyWarned
439
+ nodeCount = 0
440
+ emittedCommands = 0
441
+ useDepth = 0
442
+ aborted = false
443
+
444
+ src = source
445
+ len = (strlen source)
446
+ pos = 0
447
+
448
+ this.indexIds()
449
+ if aborted {
450
+ return (unwrap doc)
451
+ }
452
+
453
+ src = source
454
+ len = (strlen source)
455
+ pos = 0
456
+
457
+ def root (new SvgStyleState())
458
+ root.fill = (unwrap initialFill)
459
+ this.parseChildren(root 0 "")
460
+
461
+ return (unwrap doc)
462
+ }
463
+
464
+ ; --------------------------------------------------------------------------
465
+ ; Pass one: where every id'd element starts and ends
466
+ ; --------------------------------------------------------------------------
467
+ ; Only `id` matters here, and only so that <use href="#id"> can re-parse the
468
+ ; target's markup as a fragment. Storing the SOURCE rather than a parsed node
469
+ ; is what keeps <use> honest: the referenced element goes through exactly the
470
+ ; same code path as if it had been written inline, including its own
471
+ ; transform and its own inheritance.
472
+ fn indexIds:void () {
473
+ def openNames:[string]
474
+ def openIds:[string]
475
+ def openStarts:[int]
476
+
477
+ while (aborted == false) {
478
+ def tagStart:int (this.findTagStart())
479
+ if (tagStart < 0) {
480
+ return
481
+ }
482
+ def tag:SvgTag (this.readTag())
483
+ if (tag.valid == false) {
484
+ return
485
+ }
486
+
487
+ if tag.isEnd {
488
+ def depth:int (array_length openNames)
489
+ if (depth > 0) {
490
+ def openName:string (itemAt openNames (depth - 1))
491
+ if (openName == tag.name) {
492
+ def id:string (itemAt openIds (depth - 1))
493
+ if ((strlen id) > 0) {
494
+ def from:int (itemAt openStarts (depth - 1))
495
+ set spans id (substring src from pos)
496
+ }
497
+ removeLast openNames
498
+ removeLast openIds
499
+ removeLast openStarts
500
+ }
501
+ }
502
+ } {
503
+ def id2:string (tag.attr("id"))
504
+ if tag.selfClose {
505
+ if ((strlen id2) > 0) {
506
+ set spans id2 (substring src tagStart pos)
507
+ }
508
+ } {
509
+ push openNames tag.name
510
+ push openIds id2
511
+ push openStarts tagStart
512
+ if ((array_length openNames) > maxDepth) {
513
+ this.fail("nesting deeper than " + (to_string maxDepth) + " levels")
514
+ return
515
+ }
516
+ }
517
+ }
518
+ }
519
+ }
520
+
521
+ ; --------------------------------------------------------------------------
522
+ ; Pass two: the walk that produces items
523
+ ; --------------------------------------------------------------------------
524
+ ; `endName` is the element whose closing tag ends this level, or "" at the
525
+ ; top of a document or of a <use> fragment, where end of input ends it.
526
+ fn parseChildren:void (inherited:SvgStyleState depth:int endName:string) {
527
+ if (depth > maxDepth) {
528
+ this.fail("nesting deeper than " + (to_string maxDepth) + " levels")
529
+ return
530
+ }
531
+
532
+ while (aborted == false) {
533
+ def tagStart:int (this.findTagStart())
534
+ if (tagStart < 0) {
535
+ return
536
+ }
537
+ def tag:SvgTag (this.readTag())
538
+ if (tag.valid == false) {
539
+ return
540
+ }
541
+ if tag.isEnd {
542
+ ; The matching end tag, or a stray one. Either way this level is
543
+ ; over: XML is well-formed or it is not, and guessing which tag
544
+ ; a stray `</g>` was meant to close invents structure.
545
+ return
546
+ }
547
+
548
+ nodeCount = (nodeCount + 1)
549
+ if (nodeCount > maxNodes) {
550
+ this.fail("more than " + (to_string maxNodes) + " elements")
551
+ return
552
+ }
553
+
554
+ this.handleElement(tag inherited depth)
555
+ }
556
+ }
557
+
558
+ fn handleElement:void (tag:SvgTag inherited:SvgStyleState depth:int) {
559
+ def name:string tag.name
560
+
561
+ ; ---- out of profile, and said so ------------------------------------
562
+ if (this.isRefused(name)) {
563
+ this.refuse(name)
564
+ if (tag.selfClose == false) {
565
+ this.skipSubtree(name)
566
+ }
567
+ return
568
+ }
569
+
570
+ ; ---- carries no geometry --------------------------------------------
571
+ ; title/desc/metadata are documentation, not drawing. Skipping them is
572
+ ; not a limitation and needs no warning.
573
+ if (((name == "title") || (name == "desc")) || (name == "metadata")) {
574
+ if (tag.selfClose == false) {
575
+ this.skipSubtree(name)
576
+ }
577
+ return
578
+ }
579
+
580
+ ; ---- <defs> ----------------------------------------------------------
581
+ ; Definitions are drawn only where they are referenced. Pass one already
582
+ ; recorded their markup by id, so nothing is lost by not walking in.
583
+ if (name == "defs") {
584
+ if (tag.selfClose == false) {
585
+ this.skipSubtree(name)
586
+ }
587
+ return
588
+ }
589
+
590
+ ; ---- the root, and any nested viewport -------------------------------
591
+ if (name == "svg") {
592
+ if (depth == 0) {
593
+ this.readRootAttributes(tag)
594
+ def rootState:SvgStyleState (this.applyPresentation(inherited tag depth))
595
+ if (tag.selfClose == false) {
596
+ this.parseChildren(rootState (depth + 1) name)
597
+ }
598
+ return
599
+ }
600
+ ; A nested <svg> establishes a new viewport with its own viewBox and
601
+ ; its own clip. Drawing its children in the OUTER coordinate system
602
+ ; would put them in the wrong place at the wrong size, which is worse
603
+ ; than not drawing them, so it is refused rather than approximated.
604
+ this.warn("nested-svg" "a nested <svg> element establishes its own viewport and is not supported; its contents are not drawn")
605
+ if (tag.selfClose == false) {
606
+ this.skipSubtree(name)
607
+ }
608
+ return
609
+ }
610
+
611
+ ; ---- groups ----------------------------------------------------------
612
+ ; <a> is a group that happens to link somewhere. The link is meaningless
613
+ ; in a printed page and harmless in the others, so its children are
614
+ ; drawn and the href is ignored.
615
+ if ((name == "g") || (name == "a")) {
616
+ def groupState:SvgStyleState (this.applyPresentation(inherited tag depth))
617
+ if (tag.selfClose == false) {
618
+ this.parseChildren(groupState (depth + 1) name)
619
+ }
620
+ return
621
+ }
622
+
623
+ ; ---- <switch> --------------------------------------------------------
624
+ ; Real conditional processing needs requiredFeatures/requiredExtensions/
625
+ ; systemLanguage evaluation. This profile has no feature strings to test
626
+ ; against, so it would render every branch — which is why the branches
627
+ ; are refused instead.
628
+ if (name == "switch") {
629
+ this.warn("switch" "<switch> conditional processing is not supported; its contents are not drawn")
630
+ if (tag.selfClose == false) {
631
+ this.skipSubtree(name)
632
+ }
633
+ return
634
+ }
635
+
636
+ ; ---- <use> -----------------------------------------------------------
637
+ if (name == "use") {
638
+ this.handleUse(tag inherited depth)
639
+ if (tag.selfClose == false) {
640
+ this.skipSubtree(name)
641
+ }
642
+ return
643
+ }
644
+
645
+ ; ---- shapes ----------------------------------------------------------
646
+ if (this.isShape(name)) {
647
+ def shapeState:SvgStyleState (this.applyPresentation(inherited tag depth))
648
+ this.emitShape(tag shapeState)
649
+ ; A shape may carry <title>/<desc>/<animate> children. Nothing in
650
+ ; there is geometry.
651
+ if (tag.selfClose == false) {
652
+ this.skipSubtree(name)
653
+ }
654
+ return
655
+ }
656
+
657
+ ; ---- anything else ---------------------------------------------------
658
+ this.warn(("unknown-" + name) ("<" + name + "> is not part of the supported SVG profile and was skipped"))
659
+ if (tag.selfClose == false) {
660
+ this.skipSubtree(name)
661
+ }
662
+ }
663
+
664
+ ; Elements the profile refuses by name, each for a stated reason (§8, §6).
665
+ fn isRefused:boolean (name:string) {
666
+ if (name == "script") { return true }
667
+ if (name == "foreignObject") { return true }
668
+ if (name == "filter") { return true }
669
+ if (name == "mask") { return true }
670
+ if (name == "clipPath") { return true }
671
+ if (name == "pattern") { return true }
672
+ if (name == "marker") { return true }
673
+ if (name == "symbol") { return true }
674
+ if (name == "style") { return true }
675
+ if (name == "text") { return true }
676
+ if (name == "image") { return true }
677
+ if (name == "linearGradient") { return true }
678
+ if (name == "radialGradient") { return true }
679
+ if (name == "animate") { return true }
680
+ if (name == "animateTransform") { return true }
681
+ if (name == "animateMotion") { return true }
682
+ if (name == "set") { return true }
683
+ return false
684
+ }
685
+
686
+ fn refuse:void (name:string) {
687
+ ; <text> gets its own sentence because it is the single most likely
688
+ ; cause of a "your SVG support is broken" report: Illustrator and Figma
689
+ ; exports carry live text constantly, and a logo that quietly loses its
690
+ ; wordmark looks like a rendering bug rather than an unsupported feature
691
+ ; (§8). Converting it needs the font machinery and text layout, which is
692
+ ; a different piece of work entirely.
693
+ if (name == "text") {
694
+ this.warn("text" "<text> is not supported; convert text to outlines before export, or the wordmark will be missing")
695
+ return
696
+ }
697
+ if (name == "image") {
698
+ this.warn("image" "<image> references an external resource, which the importer does not fetch; it was skipped")
699
+ return
700
+ }
701
+ if (name == "style") {
702
+ this.warn("style" "a <style> element is not applied; CSS in the document is outside the profile, so use presentation attributes instead")
703
+ return
704
+ }
705
+ if ((name == "linearGradient") || (name == "radialGradient")) {
706
+ this.warn("gradient" "gradient paint is deferred (PLAN_VECTOR_IR.md §6): the three renderers do not agree on gradients yet, so a gradient fill is dropped rather than rendered differently in each output")
707
+ return
708
+ }
709
+ if ((name == "clipPath") || (name == "mask")) {
710
+ this.warn(("clip-" + name) ("<" + name + "> is not supported; the shapes it would have cut are drawn whole"))
711
+ return
712
+ }
713
+ this.warn(("refused-" + name) ("<" + name + "> is outside the supported SVG profile and was skipped"))
714
+ }
715
+
716
+ fn isShape:boolean (name:string) {
717
+ if (name == "path") { return true }
718
+ if (name == "rect") { return true }
719
+ if (name == "circle") { return true }
720
+ if (name == "ellipse") { return true }
721
+ if (name == "line") { return true }
722
+ if (name == "polyline") { return true }
723
+ if (name == "polygon") { return true }
724
+ return false
725
+ }
726
+
727
+ ; --------------------------------------------------------------------------
728
+ ; <use>
729
+ ; --------------------------------------------------------------------------
730
+ ; Same-document fragments only. `href` to anything else — another file, a
731
+ ; URL — is dropped, which is the profile's "no network or filesystem access
732
+ ; from inside the parser" rule enforced rather than documented (§8).
733
+ fn handleUse:void (tag:SvgTag inherited:SvgStyleState depth:int) {
734
+ def href:string (tag.attr("href"))
735
+ if ((strlen href) == 0) {
736
+ href = (tag.attr("xlink:href"))
737
+ }
738
+ if ((strlen href) == 0) {
739
+ this.warn("use-nohref" "<use> without an href draws nothing")
740
+ return
741
+ }
742
+ if ((charAt href 0) != 35) {
743
+ this.warn("use-external" "<use> may only reference a fragment in the same document; an external reference was dropped")
744
+ return
745
+ }
746
+
747
+ def id:string (substring href 1 (strlen href))
748
+ def target@(optional):string (get spans id)
749
+ def fragment:string ""
750
+ ; Prefer `!null?`: optional strings are plain std::string on C++, so a
751
+ ; bare `if target` becomes `target != NULL` and does not compile.
752
+ if (!null? target) {
753
+ fragment = (unwrap target)
754
+ } {
755
+ this.warn(("use-missing-" + id) ("<use href=\"#" + id + "\"> refers to an id that is not in this document"))
756
+ return
757
+ }
758
+
759
+ if (useDepth >= maxUseDepth) {
760
+ ; A <use> whose target contains a <use> back to it is a cycle, and
761
+ ; the spec calls it an error. Bounding the depth ends it without
762
+ ; needing to track which ids are currently open.
763
+ this.warn("use-depth" ("<use> references nested more than " + (to_string maxUseDepth) + " deep, which is either a cycle or deeper than this profile expands"))
764
+ return
765
+ }
766
+
767
+ ; x/y on a <use> is a translation of the referenced content. width and
768
+ ; height only apply when the target is a <symbol> or an <svg>, both of
769
+ ; which this profile refuses anyway.
770
+ def state:SvgStyleState (this.applyPresentation(inherited tag depth))
771
+ def ux:double (this.numAttr(tag "x" 0.0))
772
+ def uy:double (this.numAttr(tag "y" 0.0))
773
+ if (((ux == 0.0) && (uy == 0.0)) == false) {
774
+ state.ctm = (state.ctm.multiply((Matrix2D.translate(ux uy))))
775
+ }
776
+
777
+ ; The fragment is re-parsed with its own scanner position, then the
778
+ ; outer one is put back exactly where it was.
779
+ def savedSrc:string src
780
+ def savedPos:int pos
781
+ def savedLen:int len
782
+
783
+ src = fragment
784
+ len = (strlen src)
785
+ pos = 0
786
+ useDepth = (useDepth + 1)
787
+ this.parseChildren(state (depth + 1) "")
788
+ useDepth = (useDepth - 1)
789
+
790
+ src = savedSrc
791
+ pos = savedPos
792
+ len = savedLen
793
+ }
794
+
795
+ ; --------------------------------------------------------------------------
796
+ ; Presentation attributes
797
+ ; --------------------------------------------------------------------------
798
+ ; Order matters and is the CSS one: presentation attributes first, then the
799
+ ; `style` attribute, which outranks them. An exporter that writes both —
800
+ ; and they do — otherwise gets the losing value.
801
+ fn applyPresentation:SvgStyleState (inherited:SvgStyleState tag:SvgTag depth:int) {
802
+ def s:SvgStyleState (inherited.copy())
803
+
804
+ def k:int 0
805
+ while (k < (array_length tag.attrs)) {
806
+ def a:SvgAttr (itemAt tag.attrs k)
807
+ this.applyProperty(s a.name a.value)
808
+ k = (k + 1)
809
+ }
810
+
811
+ def styleAttr:string (tag.attr("style"))
812
+ if ((strlen styleAttr) > 0) {
813
+ this.applyStyleAttribute(s styleAttr)
814
+ }
815
+
816
+ ; The transform composes onto whatever the parent established, and is
817
+ ; applied after the parent's — the CTM is parent ∘ own.
818
+ def tf:string (tag.attr("transform"))
819
+ if ((strlen tf) > 0) {
820
+ def m:Matrix2D (this.parseTransform(tf))
821
+ s.ctm = (s.ctm.multiply(m))
822
+ }
823
+
824
+ return s
825
+ }
826
+
827
+ ; "fill:#f00; stroke : none" — the same property names as the attributes.
828
+ fn applyStyleAttribute:void (s:SvgStyleState style:string) {
829
+ def decls:[string] (this.splitOn(style 59))
830
+ def k:int 0
831
+ while (k < (array_length decls)) {
832
+ def decl:string (itemAt decls k)
833
+ def colon:int (indexOf decl ":")
834
+ if (colon > 0) {
835
+ def n:string (trim (substring decl 0 colon))
836
+ def v:string (trim (substring decl (colon + 1) (strlen decl)))
837
+ this.applyProperty(s n v)
838
+ }
839
+ k = (k + 1)
840
+ }
841
+ }
842
+
843
+ fn applyProperty:void (s:SvgStyleState name:string value:string) {
844
+ def v:string (trim value)
845
+
846
+ if (name == "fill") {
847
+ s.fill = (this.parsePaint(v (unwrap s.fill)))
848
+ return
849
+ }
850
+ if (name == "stroke") {
851
+ s.stroke = (this.parsePaint(v (unwrap s.stroke)))
852
+ return
853
+ }
854
+ if (name == "stroke-width") {
855
+ def w@(optional):double (str2double v)
856
+ if w {
857
+ s.strokeWidth = (unwrap w)
858
+ }
859
+ return
860
+ }
861
+ if (name == "fill-rule") {
862
+ if (v == "evenodd") {
863
+ s.fillRule = "evenodd"
864
+ }
865
+ if (v == "nonzero") {
866
+ s.fillRule = "nonzero"
867
+ }
868
+ return
869
+ }
870
+ if (name == "stroke-dasharray") {
871
+ if (v == "none") {
872
+ s.dashArray = ""
873
+ } {
874
+ s.dashArray = v
875
+ }
876
+ return
877
+ }
878
+ if (name == "stroke-dashoffset") {
879
+ def o@(optional):double (str2double v)
880
+ if o {
881
+ s.dashOffset = (unwrap o)
882
+ }
883
+ return
884
+ }
885
+ if (name == "fill-opacity") {
886
+ s.fillOpacity = (this.parseOpacity(v s.fillOpacity))
887
+ return
888
+ }
889
+ if (name == "stroke-opacity") {
890
+ s.strokeOpacity = (this.parseOpacity(v s.strokeOpacity))
891
+ return
892
+ }
893
+ if (name == "opacity") {
894
+ ; Group opacity accumulates down the tree. See the note on
895
+ ; SvgStyleState for what this does not reproduce.
896
+ def o2:double (this.parseOpacity(v 1.0))
897
+ s.groupOpacity = (s.groupOpacity * o2)
898
+ return
899
+ }
900
+
901
+ ; Accepted and ignored, once, out loud. The raster target strokes with
902
+ ; round joins and butt caps (Stage 2), so a document asking for anything
903
+ ; else gets something that is close but not what it asked for — which is
904
+ ; worth one line of output and not worth failing over.
905
+ if (((name == "stroke-linecap") || (name == "stroke-linejoin")) || (name == "stroke-miterlimit")) {
906
+ this.warn("stroke-joins" "stroke-linecap/linejoin/miterlimit are not represented; strokes are drawn with butt caps and round joins")
907
+ return
908
+ }
909
+ if (name == "class") {
910
+ this.warn("class" "class attributes have no effect because the profile applies no CSS")
911
+ return
912
+ }
913
+ }
914
+
915
+ ; A paint value. `none` means the operation does not happen; anything
916
+ ; unreadable keeps what was inherited rather than inventing a colour, so a
917
+ ; misspelling shows up as a warning and the wrong-but-visible shape rather
918
+ ; than as a hole in the drawing.
919
+ fn parsePaint:EVGColor (v:string inheritedPaint:EVGColor) {
920
+ if (v == "none") {
921
+ return (EVGColor.noColor())
922
+ }
923
+ if (v == "inherit") {
924
+ return inheritedPaint
925
+ }
926
+ if (v == "currentColor") {
927
+ ; `currentColor` names the CSS `color` property, which an imported
928
+ ; file cannot see. The element's own paint is what it is asking for.
929
+ return (unwrap initialFill)
930
+ }
931
+ if ((indexOf v "url(") == 0) {
932
+ this.warn("gradient" "gradient paint is deferred (PLAN_VECTOR_IR.md §6): the three renderers do not agree on gradients yet, so a gradient fill is dropped rather than rendered differently in each output")
933
+ return (EVGColor.noColor())
934
+ }
935
+ def c:EVGColor (EVGColor.parse(v))
936
+ if (c.isSet == false) {
937
+ this.warn(("color-" + v) ("could not read the colour \"" + v + "\"; the inherited paint was used instead"))
938
+ return inheritedPaint
939
+ }
940
+ return c
941
+ }
942
+
943
+ fn parseOpacity:double (v:string fallback:double) {
944
+ def pct:boolean false
945
+ def s:string v
946
+ if ((strlen s) > 0) {
947
+ if ((charAt s ((strlen s) - 1)) == 37) {
948
+ pct = true
949
+ s = (substring s 0 ((strlen s) - 1))
950
+ }
951
+ }
952
+ def d@(optional):double (str2double s)
953
+ def out:double fallback
954
+ if d {
955
+ out = (unwrap d)
956
+ } {
957
+ return fallback
958
+ }
959
+ if pct {
960
+ out = (out / 100.0)
961
+ }
962
+ if (out < 0.0) {
963
+ out = 0.0
964
+ }
965
+ if (out > 1.0) {
966
+ out = 1.0
967
+ }
968
+ return out
969
+ }
970
+
971
+ ; --------------------------------------------------------------------------
972
+ ; transform
973
+ ; --------------------------------------------------------------------------
974
+ ; matrix / translate / scale / rotate / skewX / skewY, composed left to
975
+ ; right, which is the order SVG applies them in.
976
+ fn parseTransform:Matrix2D (s:string) {
977
+ def m:Matrix2D (Matrix2D.identity())
978
+ def i:int 0
979
+ def n:int (strlen s)
980
+
981
+ while (i < n) {
982
+ ; function name up to '('
983
+ def open:int (this.findFrom(s i 40))
984
+ if (open < 0) {
985
+ return m
986
+ }
987
+ def fname:string (trim (substring s i open))
988
+ def close:int (this.findFrom(s (open + 1) 41))
989
+ if (close < 0) {
990
+ this.warn("transform-unclosed" ("a transform is missing its closing parenthesis: " + s))
991
+ return m
992
+ }
993
+ def args:[double] (this.parseNumberList((substring s (open + 1) close)))
994
+ def na:int (array_length args)
995
+ i = (close + 1)
996
+
997
+ ; A transform list may be separated by commas as well as spaces.
998
+ def part:Matrix2D (Matrix2D.identity())
999
+ def known:boolean true
1000
+
1001
+ if (fname == "matrix") {
1002
+ if (na == 6) {
1003
+ part = (Matrix2D.create((itemAt args 0) (itemAt args 1) (itemAt args 2) (itemAt args 3) (itemAt args 4) (itemAt args 5)))
1004
+ } {
1005
+ known = false
1006
+ }
1007
+ } {
1008
+ if (fname == "translate") {
1009
+ if (na == 1) {
1010
+ part = (Matrix2D.translate((itemAt args 0) 0.0))
1011
+ } {
1012
+ if (na == 2) {
1013
+ part = (Matrix2D.translate((itemAt args 0) (itemAt args 1)))
1014
+ } {
1015
+ known = false
1016
+ }
1017
+ }
1018
+ } {
1019
+ if (fname == "scale") {
1020
+ if (na == 1) {
1021
+ part = (Matrix2D.scale((itemAt args 0) (itemAt args 0)))
1022
+ } {
1023
+ if (na == 2) {
1024
+ part = (Matrix2D.scale((itemAt args 0) (itemAt args 1)))
1025
+ } {
1026
+ known = false
1027
+ }
1028
+ }
1029
+ } {
1030
+ if (fname == "rotate") {
1031
+ if (na == 1) {
1032
+ part = (this.rotation((itemAt args 0)))
1033
+ } {
1034
+ if (na == 3) {
1035
+ ; rotate(a cx cy) is translate(cx cy) rotate(a) translate(-cx -cy)
1036
+ def cx:double (itemAt args 1)
1037
+ def cy:double (itemAt args 2)
1038
+ def r:Matrix2D (this.rotation((itemAt args 0)))
1039
+ part = (Matrix2D.translate(cx cy))
1040
+ part = (part.multiply(r))
1041
+ part = (part.multiply((Matrix2D.translate((0.0 - cx) (0.0 - cy)))))
1042
+ } {
1043
+ known = false
1044
+ }
1045
+ }
1046
+ } {
1047
+ if (fname == "skewX") {
1048
+ if (na == 1) {
1049
+ part = (Matrix2D.create(1.0 0.0 (this.tanDeg((itemAt args 0))) 1.0 0.0 0.0))
1050
+ } {
1051
+ known = false
1052
+ }
1053
+ } {
1054
+ if (fname == "skewY") {
1055
+ if (na == 1) {
1056
+ part = (Matrix2D.create(1.0 (this.tanDeg((itemAt args 0))) 0.0 1.0 0.0 0.0))
1057
+ } {
1058
+ known = false
1059
+ }
1060
+ } {
1061
+ known = false
1062
+ }
1063
+ }
1064
+ }
1065
+ }
1066
+ }
1067
+ }
1068
+
1069
+ if (known == false) {
1070
+ this.warn(("transform-" + fname) ("the transform \"" + fname + "\" was not applied: unknown, or given the wrong number of arguments"))
1071
+ } {
1072
+ m = (m.multiply(part))
1073
+ }
1074
+ }
1075
+ return m
1076
+ }
1077
+
1078
+ fn rotation:Matrix2D (deg:double) {
1079
+ def rad:double ((deg * 3.141592653589793) / 180.0)
1080
+ def cs:double (cos rad)
1081
+ def sn:double (sin rad)
1082
+ return (Matrix2D.create(cs sn (0.0 - sn) cs 0.0 0.0))
1083
+ }
1084
+
1085
+ fn tanDeg:double (deg:double) {
1086
+ def rad:double ((deg * 3.141592653589793) / 180.0)
1087
+ return ((sin rad) / (cos rad))
1088
+ }
1089
+
1090
+ ; --------------------------------------------------------------------------
1091
+ ; Shapes
1092
+ ; --------------------------------------------------------------------------
1093
+ fn emitShape:void (tag:SvgTag s:SvgStyleState) {
1094
+ def name:string tag.name
1095
+ def cmds:[PathCommand]
1096
+
1097
+ if (name == "path") {
1098
+ def d:string (tag.attr("d"))
1099
+ if ((strlen d) == 0) {
1100
+ return
1101
+ }
1102
+ def p (new SVGPathParser())
1103
+ p.parse(d)
1104
+ ; The path grammar reports what it refused (Stage 1). Passing that
1105
+ ; through is the difference between "this icon looks wrong" and
1106
+ ; "this icon used an arc flag the parser could not read".
1107
+ if (p.hasErrors()) {
1108
+ this.warn(("pathdata-" + (p.errorSummary())) ("path data was not fully read: " + (p.errorSummary())))
1109
+ }
1110
+ cmds = (p.getCommands())
1111
+ } {
1112
+ if (name == "rect") {
1113
+ def rx:double (this.numAttr(tag "rx" -1.0))
1114
+ def ry:double (this.numAttr(tag "ry" -1.0))
1115
+ cmds = (VectorShapes.rect((this.numAttr(tag "x" 0.0)) (this.numAttr(tag "y" 0.0)) (this.numAttr(tag "width" 0.0)) (this.numAttr(tag "height" 0.0)) rx ry))
1116
+ } {
1117
+ if (name == "circle") {
1118
+ cmds = (VectorShapes.circle((this.numAttr(tag "cx" 0.0)) (this.numAttr(tag "cy" 0.0)) (this.numAttr(tag "r" 0.0))))
1119
+ } {
1120
+ if (name == "ellipse") {
1121
+ cmds = (VectorShapes.ellipse((this.numAttr(tag "cx" 0.0)) (this.numAttr(tag "cy" 0.0)) (this.numAttr(tag "rx" 0.0)) (this.numAttr(tag "ry" 0.0))))
1122
+ } {
1123
+ if (name == "line") {
1124
+ cmds = (VectorShapes.line((this.numAttr(tag "x1" 0.0)) (this.numAttr(tag "y1" 0.0)) (this.numAttr(tag "x2" 0.0)) (this.numAttr(tag "y2" 0.0))))
1125
+ } {
1126
+ if (name == "polyline") {
1127
+ cmds = (VectorShapes.polyline((this.parseNumberList((tag.attr("points"))))))
1128
+ } {
1129
+ if (name == "polygon") {
1130
+ cmds = (VectorShapes.polygon((this.parseNumberList((tag.attr("points"))))))
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ def count:int (array_length cmds)
1140
+ if (count == 0) {
1141
+ return
1142
+ }
1143
+
1144
+ emittedCommands = (emittedCommands + count)
1145
+ if (emittedCommands > maxCommands) {
1146
+ this.fail("more than " + (to_string maxCommands) + " path commands")
1147
+ return
1148
+ }
1149
+
1150
+ def item (new SvgVectorItem())
1151
+ item.commands = (this.transformCommands(cmds (unwrap s.ctm)))
1152
+ item.fillRule = s.fillRule
1153
+ item.dashArray = s.dashArray
1154
+ item.dashOffset = s.dashOffset
1155
+
1156
+ if s.fill.isSet {
1157
+ item.fillColor = (this.withAlpha((unwrap s.fill) (s.fillOpacity * s.groupOpacity)))
1158
+ }
1159
+ if s.stroke.isSet {
1160
+ item.strokeColor = (this.withAlpha((unwrap s.stroke) (s.strokeOpacity * s.groupOpacity)))
1161
+ ; A stroke is drawn in the shape's own space, so a scaled group
1162
+ ; scales the pen with it. Under a non-uniform scale there is no
1163
+ ; single width that is right — SVG strokes an outline in user space
1164
+ ; and transforms THAT — so the geometric mean is used, which is
1165
+ ; exact whenever the scale is uniform and is the usual approximation
1166
+ ; when it is not.
1167
+ item.strokeWidth = (s.strokeWidth * (this.scaleOf((unwrap s.ctm))))
1168
+ }
1169
+
1170
+ ; A shape that is neither filled nor stroked draws nothing. Keeping it
1171
+ ; would make item counts misleading for no gain.
1172
+ if ((item.hasFill() == false) && (item.hasStroke() == false)) {
1173
+ return
1174
+ }
1175
+
1176
+ push doc.items item
1177
+ }
1178
+
1179
+ fn withAlpha:EVGColor (c:EVGColor mul:double) {
1180
+ if (mul >= 1.0) {
1181
+ return c
1182
+ }
1183
+ return (EVGColor.create(c.r c.g c.b (c.a * mul)))
1184
+ }
1185
+
1186
+ ; sqrt of |det|: the factor by which the transform changes area, which is
1187
+ ; the scale a uniform transform applies and the sensible middle otherwise.
1188
+ fn scaleOf:double (m:Matrix2D) {
1189
+ def det:double ((m.a * m.d) - (m.b * m.c))
1190
+ if (det < 0.0) {
1191
+ det = (0.0 - det)
1192
+ }
1193
+ if (det == 0.0) {
1194
+ return 0.0
1195
+ }
1196
+ return (sqrt det)
1197
+ }
1198
+
1199
+ ; An affine map takes a cubic to a cubic and a quadratic to a quadratic, so
1200
+ ; transforming the control points transforms the curve exactly. This is the
1201
+ ; whole reason the IR can be flat: there is nothing a transform stack could
1202
+ ; do later that cannot be done here, once.
1203
+ fn transformCommands:[PathCommand] (cmds:[PathCommand] m:Matrix2D) {
1204
+ def out:[PathCommand]
1205
+ if (m.isIdentity()) {
1206
+ return cmds
1207
+ }
1208
+ def k:int 0
1209
+ while (k < (array_length cmds)) {
1210
+ def c:PathCommand (itemAt cmds k)
1211
+ def n (new PathCommand())
1212
+ n.type = c.type
1213
+ n.x = (m.applyX(c.x c.y))
1214
+ n.y = (m.applyY(c.x c.y))
1215
+ n.x1 = (m.applyX(c.x1 c.y1))
1216
+ n.y1 = (m.applyY(c.x1 c.y1))
1217
+ n.x2 = (m.applyX(c.x2 c.y2))
1218
+ n.y2 = (m.applyY(c.x2 c.y2))
1219
+ push out n
1220
+ k = (k + 1)
1221
+ }
1222
+ return out
1223
+ }
1224
+
1225
+ ; --------------------------------------------------------------------------
1226
+ ; Root attributes
1227
+ ; --------------------------------------------------------------------------
1228
+ fn readRootAttributes:void (tag:SvgTag) {
1229
+ def vb:string (tag.attr("viewBox"))
1230
+ if ((strlen vb) > 0) {
1231
+ def parsed:ViewBoxRect (VectorViewBox.parseViewBox(vb))
1232
+ if parsed.isSet {
1233
+ doc.viewBox = parsed
1234
+ } {
1235
+ this.warn("viewbox" ("the root viewBox \"" + vb + "\" is not four numbers with a positive width and height, and was ignored"))
1236
+ }
1237
+ }
1238
+ doc.width = (this.lengthAttr(tag "width"))
1239
+ doc.height = (this.lengthAttr(tag "height"))
1240
+
1241
+ def par:string (tag.attr("preserveAspectRatio"))
1242
+ if ((strlen par) > 0) {
1243
+ ; The element box decides how an imported document is fitted, the
1244
+ ; same way it does for a <Path>. Honouring the file's own
1245
+ ; preserveAspectRatio would mean two different fitting rules for the
1246
+ ; same element depending on where the geometry came from.
1247
+ this.warn("par" "preserveAspectRatio on the imported root is ignored; the element that hosts the drawing decides how it is fitted")
1248
+ }
1249
+ }
1250
+
1251
+ ; A root width/height in user units. `100%` is the SVG default and means
1252
+ ; "fill the viewport", which for an imported drawing is exactly what the
1253
+ ; hosting element already decides — so it, and any other unit this profile
1254
+ ; does not resolve, reads as "unspecified" rather than as a number.
1255
+ fn lengthAttr:double (tag:SvgTag name:string) {
1256
+ def raw:string (trim (tag.attr(name)))
1257
+ if ((strlen raw) == 0) {
1258
+ return 0.0
1259
+ }
1260
+ def s:string raw
1261
+ if ((indexOf s "px") > 0) {
1262
+ s = (substring s 0 (indexOf s "px"))
1263
+ }
1264
+ if ((this.isPlainNumber((trim s))) == false) {
1265
+ return 0.0
1266
+ }
1267
+ def d@(optional):double (str2double (trim s))
1268
+ def out:double 0.0
1269
+ if d {
1270
+ out = (unwrap d)
1271
+ } {
1272
+ return 0.0
1273
+ }
1274
+ if (out < 0.0) {
1275
+ return 0.0
1276
+ }
1277
+ return out
1278
+ }
1279
+
1280
+ ; str2double is happy to read "100%" as 100, which is exactly the wrong
1281
+ ; answer for a length: a percentage is relative to a viewport this profile
1282
+ ; does not resolve, and reading it as user units would size a drawing by a
1283
+ ; number that means something else. So a value is a number only if every
1284
+ ; character in it belongs to one.
1285
+ fn isPlainNumber:boolean (s:string) {
1286
+ def n:int (strlen s)
1287
+ if (n == 0) {
1288
+ return false
1289
+ }
1290
+ def k:int 0
1291
+ while (k < n) {
1292
+ def c:int (charAt s k)
1293
+ def ok:boolean false
1294
+ if ((c >= 48) && (c <= 57)) { ok = true }
1295
+ if (c == 46) { ok = true }
1296
+ if (c == 45) { ok = true }
1297
+ if (c == 43) { ok = true }
1298
+ if (c == 101) { ok = true }
1299
+ if (c == 69) { ok = true }
1300
+ if (ok == false) {
1301
+ return false
1302
+ }
1303
+ k = (k + 1)
1304
+ }
1305
+ return true
1306
+ }
1307
+
1308
+ fn numAttr:double (tag:SvgTag name:string fallback:double) {
1309
+ def raw:string (trim (tag.attr(name)))
1310
+ if ((strlen raw) == 0) {
1311
+ return fallback
1312
+ }
1313
+ def s:string raw
1314
+ if ((indexOf s "px") > 0) {
1315
+ s = (substring s 0 (indexOf s "px"))
1316
+ }
1317
+ if (this.isPlainNumber((trim s))) {
1318
+ def d@(optional):double (str2double (trim s))
1319
+ if d {
1320
+ return (unwrap d)
1321
+ }
1322
+ }
1323
+ this.warn(("num-" + name) ("the value \"" + raw + "\" on " + name + " is not a plain number, and this profile resolves no units; it was treated as unspecified"))
1324
+ return fallback
1325
+ }
1326
+
1327
+ ; --------------------------------------------------------------------------
1328
+ ; Scanning primitives
1329
+ ; --------------------------------------------------------------------------
1330
+
1331
+ ; Move `pos` to the next '<' that starts a tag, stepping over comments,
1332
+ ; CDATA, processing instructions and declarations on the way. Returns the
1333
+ ; offset of that '<', or -1 at end of input.
1334
+ fn findTagStart:int () {
1335
+ while (pos < len) {
1336
+ def c:int (charAt src pos)
1337
+ if (c != 60) {
1338
+ pos = (pos + 1)
1339
+ } {
1340
+ ; <!-- comment -->
1341
+ if (this.matchesAt((pos + 1) "!--")) {
1342
+ def end:int (this.findString((pos + 4) "-->"))
1343
+ if (end < 0) {
1344
+ pos = len
1345
+ return -1
1346
+ }
1347
+ pos = (end + 3)
1348
+ } {
1349
+ ; <![CDATA[ ... ]]>
1350
+ if (this.matchesAt((pos + 1) "![CDATA[")) {
1351
+ def cend:int (this.findString((pos + 9) "]]>"))
1352
+ if (cend < 0) {
1353
+ pos = len
1354
+ return -1
1355
+ }
1356
+ pos = (cend + 3)
1357
+ } {
1358
+ ; <?xml ... ?>
1359
+ if (this.matchesAt((pos + 1) "?")) {
1360
+ def pend:int (this.findString((pos + 2) "?>"))
1361
+ if (pend < 0) {
1362
+ pos = len
1363
+ return -1
1364
+ }
1365
+ pos = (pend + 2)
1366
+ } {
1367
+ ; <!DOCTYPE ...>
1368
+ if (this.matchesAt((pos + 1) "!")) {
1369
+ this.skipDeclaration()
1370
+ if aborted {
1371
+ return -1
1372
+ }
1373
+ } {
1374
+ return pos
1375
+ }
1376
+ }
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+ return -1
1382
+ }
1383
+
1384
+ ; A `<!...>` declaration. The internal subset — the `[ ... ]` part of a
1385
+ ; DOCTYPE — is where entity declarations live, and this parser implements no
1386
+ ; entities at all, so a document carrying one is refused rather than parsed
1387
+ ; with the entities left as literal text. That is the §8 rule "no XML entity
1388
+ ; expansion" enforced at the only place it could enter: billion-laughs and
1389
+ ; XXE are ruled out by there being nothing to expand.
1390
+ fn skipDeclaration:void () {
1391
+ def i:int (pos + 2)
1392
+ while (i < len) {
1393
+ def c:int (charAt src i)
1394
+ if (c == 91) {
1395
+ this.fail("this document has a DOCTYPE internal subset, which is where entity declarations live; the importer implements no entities and will not guess at one")
1396
+ pos = len
1397
+ return
1398
+ }
1399
+ if (c == 62) {
1400
+ pos = (i + 1)
1401
+ return
1402
+ }
1403
+ i = (i + 1)
1404
+ }
1405
+ pos = len
1406
+ }
1407
+
1408
+ ; Read one tag, assuming `pos` is on its '<'. Leaves `pos` just past '>'.
1409
+ fn readTag:SvgTag () {
1410
+ def tag (new SvgTag())
1411
+ if (pos >= len) {
1412
+ return tag
1413
+ }
1414
+ def i:int (pos + 1)
1415
+
1416
+ if (i < len) {
1417
+ if ((charAt src i) == 47) {
1418
+ tag.isEnd = true
1419
+ i = (i + 1)
1420
+ }
1421
+ }
1422
+
1423
+ def nameStart:int i
1424
+ while (i < len) {
1425
+ def c:int (charAt src i)
1426
+ if (this.isNameChar(c)) {
1427
+ i = (i + 1)
1428
+ } {
1429
+ break
1430
+ }
1431
+ }
1432
+ tag.name = (this.localName((substring src nameStart i)))
1433
+ if ((strlen tag.name) == 0) {
1434
+ ; A '<' that starts nothing. Step over it so the scan makes
1435
+ ; progress; the caller sees an invalid tag and stops this level.
1436
+ pos = (pos + 1)
1437
+ return tag
1438
+ }
1439
+
1440
+ while (i < len) {
1441
+ i = (this.skipSpaceFrom(i))
1442
+ if (i >= len) {
1443
+ break
1444
+ }
1445
+ def c2:int (charAt src i)
1446
+
1447
+ if (c2 == 62) {
1448
+ i = (i + 1)
1449
+ tag.valid = true
1450
+ pos = i
1451
+ return tag
1452
+ }
1453
+ if (c2 == 47) {
1454
+ tag.selfClose = true
1455
+ i = (i + 1)
1456
+ ; The '>' after '/'. Anything else is malformed; the tag still
1457
+ ; ends here rather than running into the document.
1458
+ if (i < len) {
1459
+ if ((charAt src i) == 62) {
1460
+ i = (i + 1)
1461
+ }
1462
+ }
1463
+ tag.valid = true
1464
+ pos = i
1465
+ return tag
1466
+ }
1467
+
1468
+ def attrStart:int i
1469
+ while (i < len) {
1470
+ def c3:int (charAt src i)
1471
+ if (this.isNameChar(c3)) {
1472
+ i = (i + 1)
1473
+ } {
1474
+ break
1475
+ }
1476
+ }
1477
+ if (i == attrStart) {
1478
+ ; Not a name character and not a tag terminator: skip it rather
1479
+ ; than spin.
1480
+ i = (i + 1)
1481
+ } {
1482
+ def attrName:string (substring src attrStart i)
1483
+ i = (this.skipSpaceFrom(i))
1484
+ def value:string ""
1485
+ if (i < len) {
1486
+ if ((charAt src i) == 61) {
1487
+ i = (i + 1)
1488
+ i = (this.skipSpaceFrom(i))
1489
+ if (i < len) {
1490
+ def q:int (charAt src i)
1491
+ if ((q == 34) || (q == 39)) {
1492
+ def vstart:int (i + 1)
1493
+ def vend:int (this.findFrom(src vstart q))
1494
+ if (vend < 0) {
1495
+ this.warn("unquoted" ("an attribute value on <" + tag.name + "> is missing its closing quote"))
1496
+ pos = len
1497
+ return tag
1498
+ }
1499
+ value = (this.decodeEntities((substring src vstart vend)))
1500
+ i = (vend + 1)
1501
+ } {
1502
+ ; XML requires quoted values. HTML tolerance
1503
+ ; here would mean guessing where the value ends.
1504
+ this.warn("unquoted" ("an attribute value on <" + tag.name + "> is not quoted; XML requires quotes, so it was skipped"))
1505
+ while (i < len) {
1506
+ def c4:int (charAt src i)
1507
+ if ((c4 == 62) || (this.isSpace(c4))) {
1508
+ break
1509
+ }
1510
+ i = (i + 1)
1511
+ }
1512
+ }
1513
+ }
1514
+ }
1515
+ }
1516
+ def a (new SvgAttr())
1517
+ a.name = (this.attrName(attrName))
1518
+ a.value = value
1519
+ push tag.attrs a
1520
+ }
1521
+ }
1522
+
1523
+ pos = len
1524
+ return tag
1525
+ }
1526
+
1527
+ ; Everything from here to the matching `</name>`, counting nested opens of
1528
+ ; the same name so that a <g> inside a skipped <g> does not end it early.
1529
+ fn skipSubtree:void (name:string) {
1530
+ def depth:int 1
1531
+ while ((depth > 0) && (aborted == false)) {
1532
+ def start:int (this.findTagStart())
1533
+ if (start < 0) {
1534
+ return
1535
+ }
1536
+ def tag:SvgTag (this.readTag())
1537
+ if (tag.valid == false) {
1538
+ return
1539
+ }
1540
+ if (tag.selfClose == false) {
1541
+ if (tag.name == name) {
1542
+ if tag.isEnd {
1543
+ depth = (depth - 1)
1544
+ } {
1545
+ depth = (depth + 1)
1546
+ }
1547
+ }
1548
+ }
1549
+ }
1550
+ }
1551
+
1552
+ ; --------------------------------------------------------------------------
1553
+ ; Small string helpers
1554
+ ; --------------------------------------------------------------------------
1555
+
1556
+ ; The five entities XML predefines, and nothing else. They cannot nest or
1557
+ ; recurse, so expanding them costs nothing in safety; every other `&name;`
1558
+ ; is left exactly as written, with one warning, because a document that
1559
+ ; needs a custom entity needs an entity machinery this parser deliberately
1560
+ ; does not have.
1561
+ fn decodeEntities:string (s:string) {
1562
+ if ((indexOf s "&") < 0) {
1563
+ return s
1564
+ }
1565
+ def out:string ""
1566
+ def i:int 0
1567
+ def n:int (strlen s)
1568
+ while (i < n) {
1569
+ def c:int (charAt s i)
1570
+ if (c != 38) {
1571
+ out = (out + (strfromcode c))
1572
+ i = (i + 1)
1573
+ } {
1574
+ def semi:int (this.findFrom(s i 59))
1575
+ def handled:boolean false
1576
+ if (semi > i) {
1577
+ def ent:string (substring s i (semi + 1))
1578
+ if (ent == "&amp;") {
1579
+ out = (out + "&")
1580
+ handled = true
1581
+ }
1582
+ if (ent == "&lt;") {
1583
+ out = (out + "<")
1584
+ handled = true
1585
+ }
1586
+ if (ent == "&gt;") {
1587
+ out = (out + ">")
1588
+ handled = true
1589
+ }
1590
+ if (ent == "&quot;") {
1591
+ out = (out + "\"")
1592
+ handled = true
1593
+ }
1594
+ if (ent == "&apos;") {
1595
+ out = (out + "'")
1596
+ handled = true
1597
+ }
1598
+ if handled {
1599
+ i = (semi + 1)
1600
+ } {
1601
+ this.warn("entity" "only the five predefined XML entities are expanded; any other reference is left as written")
1602
+ out = (out + "&")
1603
+ i = (i + 1)
1604
+ }
1605
+ } {
1606
+ out = (out + "&")
1607
+ i = (i + 1)
1608
+ }
1609
+ }
1610
+ }
1611
+ return out
1612
+ }
1613
+
1614
+ ; "svg:path" -> "path". Namespace prefixes are common in exported files and
1615
+ ; carry no meaning this profile acts on.
1616
+ fn localName:string (raw:string) {
1617
+ def colon:int (indexOf raw ":")
1618
+ if (colon < 0) {
1619
+ return raw
1620
+ }
1621
+ return (substring raw (colon + 1) (strlen raw))
1622
+ }
1623
+
1624
+ ; Attribute names keep their prefix only where the prefix is the name:
1625
+ ; `xlink:href` is how most exporters still write a reference.
1626
+ fn attrName:string (raw:string) {
1627
+ if ((indexOf raw "xlink:") == 0) {
1628
+ return raw
1629
+ }
1630
+ def colon:int (indexOf raw ":")
1631
+ if (colon < 0) {
1632
+ return raw
1633
+ }
1634
+ return (substring raw (colon + 1) (strlen raw))
1635
+ }
1636
+
1637
+ fn isSpace:boolean (c:int) {
1638
+ if (c == 32) { return true }
1639
+ if (c == 9) { return true }
1640
+ if (c == 10) { return true }
1641
+ if (c == 13) { return true }
1642
+ return false
1643
+ }
1644
+
1645
+ fn isNameChar:boolean (c:int) {
1646
+ if ((c >= 65) && (c <= 90)) { return true }
1647
+ if ((c >= 97) && (c <= 122)) { return true }
1648
+ if ((c >= 48) && (c <= 57)) { return true }
1649
+ if (c == 58) { return true }
1650
+ if (c == 45) { return true }
1651
+ if (c == 95) { return true }
1652
+ if (c == 46) { return true }
1653
+ return false
1654
+ }
1655
+
1656
+ fn skipSpaceFrom:int (from:int) {
1657
+ def i:int from
1658
+ while (i < len) {
1659
+ if (this.isSpace((charAt src i))) {
1660
+ i = (i + 1)
1661
+ } {
1662
+ break
1663
+ }
1664
+ }
1665
+ return i
1666
+ }
1667
+
1668
+ fn findFrom:int (s:string from:int ch:int) {
1669
+ def i:int from
1670
+ def n:int (strlen s)
1671
+ while (i < n) {
1672
+ if ((charAt s i) == ch) {
1673
+ return i
1674
+ }
1675
+ i = (i + 1)
1676
+ }
1677
+ return -1
1678
+ }
1679
+
1680
+ fn findString:int (from:int needle:string) {
1681
+ def nl:int (strlen needle)
1682
+ def i:int from
1683
+ while ((i + nl) <= len) {
1684
+ if (this.matchesAt(i needle)) {
1685
+ return i
1686
+ }
1687
+ i = (i + 1)
1688
+ }
1689
+ return -1
1690
+ }
1691
+
1692
+ fn matchesAt:boolean (at:int needle:string) {
1693
+ def nl:int (strlen needle)
1694
+ if ((at + nl) > len) {
1695
+ return false
1696
+ }
1697
+ return ((substring src at (at + nl)) == needle)
1698
+ }
1699
+
1700
+ fn splitOn:[string] (s:string sep:int) {
1701
+ def out:[string]
1702
+ def n:int (strlen s)
1703
+ def start:int 0
1704
+ def i:int 0
1705
+ while (i < n) {
1706
+ if ((charAt s i) == sep) {
1707
+ push out (substring s start i)
1708
+ start = (i + 1)
1709
+ }
1710
+ i = (i + 1)
1711
+ }
1712
+ push out (substring s start n)
1713
+ return out
1714
+ }
1715
+
1716
+ ; Numbers separated by whitespace and/or commas, which is how points lists
1717
+ ; and transform arguments are both written. A token that is not a number
1718
+ ; ends the list rather than being read as zero — inventing a coordinate is
1719
+ ; how malformed data turns into geometry that looks deliberate.
1720
+ fn parseNumberList:[double] (s:string) {
1721
+ def out:[double]
1722
+ def toks:[string] (VectorViewBox.splitTokens(s))
1723
+ def k:int 0
1724
+ while (k < (array_length toks)) {
1725
+ def d@(optional):double (str2double (itemAt toks k))
1726
+ if d {
1727
+ push out (unwrap d)
1728
+ } {
1729
+ this.warn(("numlist-" + (itemAt toks k)) ("\"" + (itemAt toks k) + "\" is not a number; the rest of that list was not read"))
1730
+ return out
1731
+ }
1732
+ k = (k + 1)
1733
+ }
1734
+ return out
1735
+ }
1736
+ }