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,115 @@
1
+ // SPDX-License-Identifier: MIT
2
+ package fi.ranger.evg
3
+
4
+ import java.util.concurrent.Executors
5
+ import java.util.concurrent.atomic.AtomicBoolean
6
+
7
+ /**
8
+ * The engine off the UI thread — Android, and the JVM.
9
+ *
10
+ * PLAN_NATIVE_HOSTS.md S1. A Ranger app compiled to Kotlin has no `android.*`
11
+ * in it, so nothing about it has to run on the UI thread; what has to is the
12
+ * `View` that receives touches and the `Canvas` that paints. So the app lives
13
+ * on ONE thread of its own, every call the host makes runs there in the order
14
+ * the host made it, and a call that changed the page produces a frame — laid
15
+ * out and listed on that thread — that is handed to the UI thread, which
16
+ * paints it and nothing else.
17
+ *
18
+ * Three verbs, and the discipline is that a host uses them and never touches
19
+ * the app directly:
20
+ *
21
+ * post { app -> … } run there; return true if a frame is owed
22
+ * ask({ app -> … }) { v -> … } run there; answer on the UI thread
23
+ * sync { app -> … } run there and WAIT — for the one read the
24
+ * platform insists on synchronously
25
+ * (`onCheckIsTextEditor`)
26
+ *
27
+ * Frames are coalesced: a burst of posts that each say "a frame is owed"
28
+ * produces one build after the last of them, and a build asked for while one
29
+ * is running produces exactly one more when it finishes — never a queue of
30
+ * stale frames the UI thread has to draw through.
31
+ *
32
+ * This file is in the platform-free source set on purpose: `onMain` is how
33
+ * the host reaches its UI thread (`view::post` on Android, `SwingUtilities`
34
+ * or a plain call on a JVM check), so the same class serves both.
35
+ *
36
+ * @param app the Ranger app; make it BEFORE this, and never read it again
37
+ * from the UI thread
38
+ * @param build the frame, run on the engine thread — `app.frame()` and
39
+ * whatever the painter needs beside it
40
+ * @param onMain run a block on the UI thread
41
+ * @param deliver called on the UI thread with each built frame; keep it and
42
+ * `invalidate()`
43
+ */
44
+ class EvgEngineThread<App, Frame>(
45
+ private val app: App,
46
+ private val build: (App) -> Frame,
47
+ private val onMain: (Runnable) -> Unit,
48
+ private val deliver: (Frame) -> Unit,
49
+ ) {
50
+ private val exec = Executors.newSingleThreadExecutor { r ->
51
+ Thread(r, "evg-engine").apply { isDaemon = true }
52
+ }
53
+
54
+ // Touched on the engine thread only.
55
+ private var building = false
56
+ private var wanted = false
57
+ private val closed = AtomicBoolean(false)
58
+
59
+ /** Run `work` on the engine thread. A `true` result asks for a frame. */
60
+ fun post(work: (App) -> Boolean) {
61
+ if (closed.get()) return
62
+ exec.execute {
63
+ if (work(app)) requestFrameOnThread()
64
+ }
65
+ }
66
+
67
+ /** Run `work` on the engine thread and hand its answer to `then` on the UI thread. */
68
+ fun <T> ask(work: (App) -> T, then: (T) -> Unit) {
69
+ if (closed.get()) return
70
+ exec.execute {
71
+ val v = work(app)
72
+ onMain(Runnable { then(v) })
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Run `work` on the engine thread and wait for it. For the reads the
78
+ * platform asks for synchronously and nothing else: it holds the caller
79
+ * for as long as whatever is already queued takes.
80
+ */
81
+ fun <T> sync(work: (App) -> T): T = exec.submit<T> { work(app) }.get()
82
+
83
+ /** A frame is owed — the clock ticked, the view resized. */
84
+ fun invalidate() {
85
+ if (closed.get()) return
86
+ exec.execute { requestFrameOnThread() }
87
+ }
88
+
89
+ fun close() {
90
+ closed.set(true)
91
+ exec.shutdown()
92
+ }
93
+
94
+ // On the engine thread.
95
+ private fun requestFrameOnThread() {
96
+ if (building) {
97
+ wanted = true
98
+ return
99
+ }
100
+ building = true
101
+ // Not inline: the posts already queued behind this one run first, so
102
+ // a press and the release that followed it are both in the frame.
103
+ exec.execute { buildOnThread() }
104
+ }
105
+
106
+ private fun buildOnThread() {
107
+ val f = build(app)
108
+ onMain(Runnable { deliver(f) })
109
+ building = false
110
+ if (wanted) {
111
+ wanted = false
112
+ requestFrameOnThread()
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,231 @@
1
+ package fi.ranger.evg
2
+
3
+ import fi.ranger.rgr.EVGDisplayList
4
+ import fi.ranger.rgr.EVGDrawCmd
5
+
6
+ /**
7
+ * The walk: an `EVGDisplayList` in, calls on an [EvgSurface] out.
8
+ *
9
+ * The display list's own comment says a sixth backend should not be a sixth
10
+ * copy of the traversal, and this is that rule applied one level down: Android
11
+ * and AWT differ in how they fill a rounded box, not in what a command *means*,
12
+ * so what a command means is decided exactly once, here.
13
+ *
14
+ * Two things this painter honours that the existing backends do not:
15
+ *
16
+ * * **Gradients and shadows.** `EVGDisplayList.toJson` carries neither, so the
17
+ * WebGL host cannot draw them and only the CPU rasteriser does. Reading the
18
+ * objects directly — which is what a Kotlin host can do and a browser cannot
19
+ * — brings both back.
20
+ * * **Clipping and multi-ring paths.** The SoftCanvas painter ignores
21
+ * `PUSH_CLIP` / `POP_CLIP` and flattens every path to one polygon, so a
22
+ * ring-with-a-hole comes out solid. `save`/`clipRect`/`restore` and a real
23
+ * even-odd fill are one line each on both surfaces here.
24
+ */
25
+ object EvgPainter {
26
+
27
+ private const val RECT = 0
28
+ private const val BORDER = 1
29
+ private const val IMAGE = 2
30
+ private const val TEXT = 3
31
+ private const val PUSH_CLIP = 4
32
+ private const val POP_CLIP = 5
33
+ private const val PATH = 6
34
+ private const val STROKE = 7
35
+
36
+ /** Anything below this is invisible; drawing it is work for nothing. */
37
+ private const val MIN_ALPHA = 0.001
38
+
39
+ /**
40
+ * Paint every command in order. Returns how many were actually drawn, which
41
+ * is what a host puts in a debug overlay — a frame that draws 0 of 300
42
+ * commands is a bug you want to see rather than a black screen.
43
+ */
44
+ fun paint(list: EVGDisplayList, surface: EvgSurface): Int {
45
+ var drawn = 0
46
+ // A list that pushes more clips than it pops must not leave the surface
47
+ // with a clip nobody asked for, and one that pops more than it pushes
48
+ // must not restore past the state the host handed us. The depth counter
49
+ // is the whole of both guarantees.
50
+ var clipDepth = 0
51
+ val n = list.count()
52
+ var i = 0
53
+ while (i < n) {
54
+ val c = list.at(i)
55
+ i++
56
+ when (c.kind) {
57
+ PUSH_CLIP -> {
58
+ surface.save()
59
+ surface.clipRect(c.x.toFloat(), c.y.toFloat(), c.w.toFloat(), c.h.toFloat())
60
+ clipDepth++
61
+ continue
62
+ }
63
+ POP_CLIP -> {
64
+ if (clipDepth > 0) {
65
+ surface.restore()
66
+ clipDepth--
67
+ }
68
+ continue
69
+ }
70
+ }
71
+
72
+ // A rotation turns the command about the point the display list
73
+ // names, and about the command's own box centre only when it names
74
+ // none. `EVGDisplayList` resolves `transform-origin` into
75
+ // rotOriginX/Y and sets hasRotOrigin, exactly because the centre is
76
+ // "right for a lone rotated label and wrong for everything else: a
77
+ // box, its text and its children have to turn about ONE point or
78
+ // they come apart".
79
+ //
80
+ // Ignoring it is what made RealTrainer's loading spinner a single
81
+ // small bar. Its twelve blades are one 8x26 box each, fanned into a
82
+ // ring by `transform: rotate(Ndeg)` about `transform-origin: 4px
83
+ // 54px` -- a point 41px BELOW each blade's own centre. Turned about
84
+ // their centres they all spin in place and land on top of each
85
+ // other. Wrapping it in save/restore keeps the clip stack above
86
+ // untouched.
87
+ val turned = c.rotate > 0.001 || c.rotate < -0.001
88
+ if (turned) {
89
+ surface.save()
90
+ surface.rotate(
91
+ c.rotate.toFloat(),
92
+ (if (c.hasRotOrigin) c.rotOriginX else c.x + c.w * 0.5).toFloat(),
93
+ (if (c.hasRotOrigin) c.rotOriginY else c.y + c.h * 0.5).toFloat(),
94
+ )
95
+ }
96
+
97
+ if (drawOne(c, surface)) drawn++
98
+
99
+ if (turned) surface.restore()
100
+ }
101
+ while (clipDepth > 0) {
102
+ surface.restore()
103
+ clipDepth--
104
+ }
105
+ return drawn
106
+ }
107
+
108
+ private fun drawOne(c: EVGDrawCmd, surface: EvgSurface): Boolean {
109
+ when (c.kind) {
110
+ RECT -> {
111
+ val shadow = shadowOf(c)
112
+ val gradient = gradientOf(c)
113
+ if (c.a < MIN_ALPHA && gradient == null && shadow == null) return false
114
+ surface.fillRect(
115
+ c.x.toFloat(), c.y.toFloat(), c.w.toFloat(), c.h.toFloat(),
116
+ c.radius.toFloat(),
117
+ colorOf(c),
118
+ gradient,
119
+ shadow,
120
+ )
121
+ return true
122
+ }
123
+ BORDER -> {
124
+ if (c.a < MIN_ALPHA) return false
125
+ // A border with no thickness is still a border: the display
126
+ // list leaves the default at 0 for a hairline, and a stroke of
127
+ // width 0 is invisible on both surfaces.
128
+ val t = if (c.thickness > 0.0) c.thickness.toFloat() else 1f
129
+ surface.strokeRect(
130
+ c.x.toFloat(), c.y.toFloat(), c.w.toFloat(), c.h.toFloat(),
131
+ c.radius.toFloat(), t,
132
+ colorOf(c),
133
+ )
134
+ return true
135
+ }
136
+ IMAGE -> {
137
+ if (c.src.isEmpty()) return false
138
+ surface.drawImage(
139
+ c.src,
140
+ c.x.toFloat(), c.y.toFloat(), c.w.toFloat(), c.h.toFloat(),
141
+ c.radius.toFloat(),
142
+ c.flipH, c.flipV,
143
+ )
144
+ return true
145
+ }
146
+ TEXT -> {
147
+ if (c.text.isEmpty()) return false
148
+ if (c.a < MIN_ALPHA) return false
149
+ surface.drawTextRun(
150
+ c.text,
151
+ c.x.toFloat(), c.y.toFloat(),
152
+ c.fontSize.toFloat(),
153
+ c.fontFamily,
154
+ isBold(c.fontWeight),
155
+ // `addText` has nowhere else to record a slanted face, so
156
+ // it writes "italic" into textAlign and `toJson` reads it
157
+ // back out the same way. Not lovely, but it is the format.
158
+ c.textAlign == "italic",
159
+ colorOf(c),
160
+ )
161
+ return true
162
+ }
163
+ PATH -> {
164
+ if (c.a < MIN_ALPHA) return false
165
+ if (c.pts.size < 6) return false
166
+ surface.fillPath(c.pts, ringsOf(c), c.evenOdd, colorOf(c), shadowOf(c))
167
+ return true
168
+ }
169
+ STROKE -> {
170
+ if (c.a < MIN_ALPHA) return false
171
+ if (c.pts.size < 4) return false
172
+ val t = if (c.thickness > 0.0) c.thickness.toFloat() else 1f
173
+ surface.strokePath(c.pts, ringsOf(c), t, colorOf(c))
174
+ return true
175
+ }
176
+ }
177
+ return false
178
+ }
179
+
180
+ /**
181
+ * A path emitted by the SVG walk carries its ring boundaries; one emitted
182
+ * by `addPolygon` / `addPolyline` may not. A single implicit ring covering
183
+ * every point is the right reading of an empty `ringEnds` — it is what the
184
+ * polygon filler assumes.
185
+ */
186
+ private fun ringsOf(c: EVGDrawCmd): List<Int> =
187
+ if (c.ringEnds.isEmpty()) listOf(c.pts.size) else c.ringEnds
188
+
189
+ private fun gradientOf(c: EVGDrawCmd): EvgGradient? {
190
+ if (!c.hasGrad) return null
191
+ return EvgGradient(
192
+ color1 = argb(c.a, c.r, c.g, c.b),
193
+ color2 = argb(c.a2, c.r2, c.g2, c.b2),
194
+ // dir 0 = vertical (top to bottom), 1 = horizontal (left to right)
195
+ vertical = c.gradDir != 1,
196
+ )
197
+ }
198
+
199
+ private fun shadowOf(c: EVGDrawCmd): EvgShadow? {
200
+ if (!c.hasShadow) return null
201
+ if (c.shadowA < MIN_ALPHA) return null
202
+ return EvgShadow(
203
+ dx = c.shadowX.toFloat(),
204
+ dy = c.shadowY.toFloat(),
205
+ blur = c.shadowBlur.toFloat(),
206
+ color = argb(c.shadowA, c.shadowR, c.shadowG, c.shadowB),
207
+ )
208
+ }
209
+
210
+ private fun colorOf(c: EVGDrawCmd): Int = argb(c.a, c.r, c.g, c.b)
211
+
212
+ /** 0-255 channels and a 0-1 alpha, the way every command carries them. */
213
+ fun argb(alpha: Double, r: Int, g: Int, b: Int): Int {
214
+ val a = clamp255((alpha * 255.0 + 0.5).toInt())
215
+ return (a shl 24) or (clamp255(r) shl 16) or (clamp255(g) shl 8) or clamp255(b)
216
+ }
217
+
218
+ private fun clamp255(v: Int): Int = if (v < 0) 0 else if (v > 255) 255 else v
219
+
220
+ /**
221
+ * The display list writes the word, not the number — `addText` only ever
222
+ * says "bold" — but a stylesheet path could put a CSS weight here, so a
223
+ * number is read as one.
224
+ */
225
+ private fun isBold(weight: String): Boolean {
226
+ if (weight.isEmpty()) return false
227
+ if (weight == "bold" || weight == "bolder") return true
228
+ val n = weight.toIntOrNull() ?: return false
229
+ return n >= 600
230
+ }
231
+ }
@@ -0,0 +1,117 @@
1
+ package fi.ranger.evg
2
+
3
+ /**
4
+ * Everything a backend has to be able to draw, and nothing else.
5
+ *
6
+ * `EVGDisplayList` is eight command kinds of absolute pixels and resolved
7
+ * colours — no tree, no units, no fonts to resolve. That is small enough that
8
+ * a backend is a page of delegation rather than a renderer, which is the point:
9
+ * `AndroidEvgSurface` paints with `android.graphics.Canvas`, `AwtEvgSurface`
10
+ * paints the same list with `java.awt.Graphics2D`, and the walk that decides
11
+ * what each command *means* ([EvgPainter]) is written once and shared.
12
+ *
13
+ * That second implementation is not decoration. An Android painter can only be
14
+ * checked on a device or an emulator; the same painter driven through AWT
15
+ * renders a deck to a PNG on any JVM, which is what makes the port testable in
16
+ * CI at all.
17
+ *
18
+ * Geometry is in the app's own pixels. A host that wants device pixels scales
19
+ * its canvas and leaves these numbers alone — text then rasterises at the
20
+ * panel's real resolution instead of being blown up from a small bitmap.
21
+ */
22
+ interface EvgSurface {
23
+ /** Push the clip/transform stack. Paired with [restore]. */
24
+ fun save()
25
+
26
+ /** Pop back to the state the matching [save] recorded. */
27
+ fun restore()
28
+
29
+ /** Intersect the clip with this rectangle. Only ever inside a [save]. */
30
+ fun clipRect(x: Float, y: Float, w: Float, h: Float)
31
+
32
+ /** Turn the coordinate system `degrees` about (`px`, `py`). */
33
+ fun rotate(degrees: Float, px: Float, py: Float)
34
+
35
+ /**
36
+ * A filled box, possibly rounded, possibly a two-stop gradient, possibly
37
+ * under a drop shadow. The shadow is painted first and is the shape's own
38
+ * silhouette — a rounded box casts a rounded shadow.
39
+ */
40
+ fun fillRect(
41
+ x: Float, y: Float, w: Float, h: Float,
42
+ radius: Float,
43
+ color: Int,
44
+ gradient: EvgGradient?,
45
+ shadow: EvgShadow?,
46
+ )
47
+
48
+ /** A stroked outline, drawn as a band of `thickness` INSIDE the box. */
49
+ fun strokeRect(
50
+ x: Float, y: Float, w: Float, h: Float,
51
+ radius: Float, thickness: Float,
52
+ color: Int,
53
+ )
54
+
55
+ /**
56
+ * A filled vector outline. The rings are already flattened and already in
57
+ * page coordinates; `ringEnds` gives the index in `pts` one past each
58
+ * ring's last coordinate, so a shape with a hole arrives as two rings.
59
+ */
60
+ fun fillPath(
61
+ pts: List<Double>, ringEnds: List<Int>, evenOdd: Boolean,
62
+ color: Int,
63
+ shadow: EvgShadow?,
64
+ )
65
+
66
+ /** The same rings, stroked rather than filled. */
67
+ fun strokePath(
68
+ pts: List<Double>, ringEnds: List<Int>,
69
+ thickness: Float,
70
+ color: Int,
71
+ )
72
+
73
+ /**
74
+ * A picture, by the package part name the deck gave it. A surface that has
75
+ * no bytes for `src` should draw the placeholder rather than nothing — a
76
+ * missing texture and a blank slide look identical otherwise.
77
+ */
78
+ fun drawImage(
79
+ src: String,
80
+ x: Float, y: Float, w: Float, h: Float,
81
+ radius: Float,
82
+ flipH: Boolean, flipV: Boolean,
83
+ )
84
+
85
+ /**
86
+ * One run, on one line, in one face.
87
+ *
88
+ * `top` is the top of the LINE BOX, not the baseline: EVG measured the run
89
+ * with the same TrueType face the surface is about to draw it in, and the
90
+ * baseline sits one face-ascent below. Placing the ink at `top` instead
91
+ * lifts every run by the empty space above its capitals — a couple of
92
+ * pixels on a caption, most of a line on a heading. The surface owns that
93
+ * conversion because only it knows what its own font metrics say.
94
+ */
95
+ fun drawTextRun(
96
+ text: String,
97
+ x: Float, top: Float,
98
+ sizePx: Float,
99
+ family: String, bold: Boolean, italic: Boolean,
100
+ color: Int,
101
+ )
102
+ }
103
+
104
+ /** A two-stop linear gradient. `vertical` is EVG's `gradDir == 0`. */
105
+ data class EvgGradient(
106
+ val color1: Int,
107
+ val color2: Int,
108
+ val vertical: Boolean,
109
+ )
110
+
111
+ /** An outer drop shadow: offset, blur radius, colour. */
112
+ data class EvgShadow(
113
+ val dx: Float,
114
+ val dy: Float,
115
+ val blur: Float,
116
+ val color: Int,
117
+ )
@@ -0,0 +1,94 @@
1
+ package fi.ranger.evg
2
+
3
+
4
+ /**
5
+ * A surface that counts what it was asked to draw, and then draws it.
6
+ *
7
+ * "Every slide produced some ink" is a weak thing to check: a deck of nothing
8
+ * but grey boxes passes it, and so does a painter that has quietly stopped
9
+ * dispatching text. What a coverage run wants to know is which *kinds* of
10
+ * command reached a surface at all — whether the corpus exercises paths and
11
+ * clips and gradients and mirrored pictures, and whether the walk delivers
12
+ * them — so this wraps the real surface and keeps a tally on the way through.
13
+ */
14
+ class RecordingSurface(private val inner: EvgSurface) : EvgSurface {
15
+
16
+ val calls = HashMap<String, Int>()
17
+
18
+ private fun bump(what: String) {
19
+ calls[what] = (calls[what] ?: 0) + 1
20
+ }
21
+
22
+ fun count(what: String): Int = calls[what] ?: 0
23
+
24
+ override fun save() {
25
+ bump("save"); inner.save()
26
+ }
27
+
28
+ override fun restore() {
29
+ bump("restore"); inner.restore()
30
+ }
31
+
32
+ override fun clipRect(x: Float, y: Float, w: Float, h: Float) {
33
+ bump("clipRect"); inner.clipRect(x, y, w, h)
34
+ }
35
+
36
+ override fun rotate(degrees: Float, px: Float, py: Float) {
37
+ bump("rotate"); inner.rotate(degrees, px, py)
38
+ }
39
+
40
+ override fun fillRect(
41
+ x: Float, y: Float, w: Float, h: Float,
42
+ radius: Float, color: Int, gradient: EvgGradient?, shadow: EvgShadow?,
43
+ ) {
44
+ bump("fillRect")
45
+ if (radius > 0.5f) bump("roundedRect")
46
+ if (gradient != null) bump("gradient")
47
+ if (shadow != null) bump("shadow")
48
+ inner.fillRect(x, y, w, h, radius, color, gradient, shadow)
49
+ }
50
+
51
+ override fun strokeRect(
52
+ x: Float, y: Float, w: Float, h: Float,
53
+ radius: Float, thickness: Float, color: Int,
54
+ ) {
55
+ bump("strokeRect"); inner.strokeRect(x, y, w, h, radius, thickness, color)
56
+ }
57
+
58
+ override fun fillPath(
59
+ pts: List<Double>, ringEnds: List<Int>, evenOdd: Boolean,
60
+ color: Int, shadow: EvgShadow?,
61
+ ) {
62
+ bump("fillPath")
63
+ if (ringEnds.size > 1) bump("multiRingPath")
64
+ if (evenOdd) bump("evenOddPath")
65
+ if (shadow != null) bump("shadow")
66
+ inner.fillPath(pts, ringEnds, evenOdd, color, shadow)
67
+ }
68
+
69
+ override fun strokePath(pts: List<Double>, ringEnds: List<Int>, thickness: Float, color: Int) {
70
+ bump("strokePath"); inner.strokePath(pts, ringEnds, thickness, color)
71
+ }
72
+
73
+ override fun drawImage(
74
+ src: String, x: Float, y: Float, w: Float, h: Float,
75
+ radius: Float, flipH: Boolean, flipV: Boolean,
76
+ ) {
77
+ bump("drawImage")
78
+ if (flipH || flipV) bump("mirroredImage")
79
+ inner.drawImage(src, x, y, w, h, radius, flipH, flipV)
80
+ }
81
+
82
+ override fun drawTextRun(
83
+ text: String, x: Float, top: Float, sizePx: Float,
84
+ family: String, bold: Boolean, italic: Boolean, color: Int,
85
+ ) {
86
+ bump("drawTextRun")
87
+ if (bold) bump("boldText")
88
+ if (italic) bump("italicText")
89
+ // A run outside the Latin block is what tells you the TrueType reader
90
+ // and the face pool are doing something rather than getting lucky.
91
+ if (text.any { it.code > 127 }) bump("nonAsciiText")
92
+ inner.drawTextRun(text, x, top, sizePx, family, bold, italic, color)
93
+ }
94
+ }
@@ -0,0 +1,128 @@
1
+ # `lib/evg/apple` — the EVG display list, painted with CoreGraphics
2
+
3
+ **License: AGPL-3.0-or-later** (Gallery).
4
+
5
+ The Apple sibling of [`lib/evg/android`](../android/README.md), and
6
+ deliberately the same shape: an `EvgSurface` protocol of eight drawing calls,
7
+ one `EvgPainter` that decides what each display-list command *means*, and a
8
+ backend per drawing API.
9
+
10
+ There is only one backend here, and that is the point. A `UIView` hands out a
11
+ `CGContext` from `UIGraphicsGetCurrentContext()`; a SwiftUI `Canvas` hands one
12
+ out from `GraphicsContext.withCGContext`; a bitmap or a PDF context is the same
13
+ object again. So an iPhone, an iPad, an Apple Watch and an off-screen render all
14
+ paint through `CoreGraphicsEvgSurface`, not through four surfaces that have to
15
+ be kept in step.
16
+
17
+ ```
18
+ EVGDisplayList (Ranger, compiled to Swift)
19
+ │
20
+ EvgPainter.paint(list, into:) what a command MEANS — written once
21
+ │
22
+ EvgSurface eight calls, and nothing else
23
+ │
24
+ ├── CoreGraphicsEvgSurface CGContext ← UIKit, SwiftUI, watchOS
25
+ └── RecordingSurface counts what was dispatched
26
+ ```
27
+
28
+ | File | Lines | What it is |
29
+ | --- | --- | --- |
30
+ | `Sources/EvgSurface.swift` | 163 | The protocol, and the colour/gradient/shadow types it takes |
31
+ | `Sources/EvgPainter.swift` | 213 | The walk: a display list in, surface calls out |
32
+ | `Sources/CoreGraphicsEvgSurface.swift` | 291 | Boxes, borders, paths, images and CoreText, on a `CGContext` |
33
+ | `Sources/RecordingSurface.swift` | 110 | Draws nothing, counts everything |
34
+ | `Sources/CoreTextMeasurer.swift` | 70 | CoreText measures the text the layout is made with — the same `CTFont` the surface draws with |
35
+ | `Sources/EvgEngineQueue.swift` | 110 | The engine on its own serial queue: `post`, `ask`, `sync`, and frames coalesced and delivered to the main thread |
36
+
37
+ ## What the painter is careful about
38
+
39
+ `EvgPainter` is a transliteration of the Kotlin one rather than a second reading
40
+ of the format. Where the two disagree, one of them is wrong. Four things it
41
+ carries that a JSON-fed backend cannot:
42
+
43
+ * **Gradients and shadows.** `EVGDisplayList.toJson` carries neither, so a
44
+ browser host cannot draw them. Reading the objects directly brings both back.
45
+ * **Clipping.** `save`/`clipRect`/`restore`, with a depth counter: a list that
46
+ pushes more clips than it pops must not leave the surface clipped for the rest
47
+ of the frame, and one that pops more than it pushes must not restore past the
48
+ state the host handed over. On CoreGraphics the first of those is a page with
49
+ most of it missing.
50
+ * **Multi-ring paths.** `ringEnds` gives the index one past each ring's last
51
+ coordinate, so a shape with a hole is one path with two subpaths and a real
52
+ even-odd fill.
53
+ * **Rotation about a command's own box centre**, wrapped in save/restore so the
54
+ clip stack above is untouched.
55
+
56
+ ## Three things CoreGraphics needs that Skia did not
57
+
58
+ **Text is upside down unless you say otherwise.** The context is flipped —
59
+ origin top left, y down, which is what UIKit and SwiftUI both hand you and what
60
+ the display list is already in — and CoreText draws glyphs the other way up.
61
+ `ctx.textMatrix = CGAffineTransform(scaleX: 1, y: -1)` is the fix, and it is one
62
+ line in exactly one place.
63
+
64
+ **`top` is not the baseline.** EVG measured the run and the baseline sits one
65
+ face-ascent below the top of the line box. `CTFontGetAscent` is where that comes
66
+ from, and the surface owns the conversion because only it knows what its own
67
+ metrics say.
68
+
69
+ **A border is a band INSIDE the box.** A CoreGraphics stroke straddles its path,
70
+ so `strokeRect` insets by half the thickness and shrinks the corner radius to
71
+ match. Without it a one-point hairline spills over the edge of a clipped scroll
72
+ region.
73
+
74
+ ## Fonts
75
+
76
+ `CTFont`, not `UIFont`: CoreText exists on every Apple platform including
77
+ watchOS, so this is the same file there. The family name from the display list
78
+ picks between a monospace face, a serif and the platform's own sans; bold and
79
+ italic go through `CTFontCreateCopyWithSymbolicTraits`.
80
+
81
+ No font files are bundled, and none are needed for the layout to be honest:
82
+ `CoreTextMeasurer.install()` — one stored property in each host, declared
83
+ above the app so it runs first — hands EVG's `EVGHostTextMeasurer` a function
84
+ that measures a run with the SAME `CTFont` `EvgFontCache` gives the surface,
85
+ `CTLineGetTypographicBounds` and all. So the page is laid out with the face it
86
+ is drawn with, rather than with the table it used to guess from. The pptx port
87
+ still bundles the deck's own faces, for the opposite reason: a deck is measured
88
+ against the faces it was authored in.
89
+
90
+ ## What is checked, and where
91
+
92
+ Nothing in this directory is compiled by this repository's CI, because Swift for
93
+ Apple platforms needs a Mac. What IS checked, on any machine, is everything the
94
+ painter is handed:
95
+ [`gallery/ui/ios/ranger/check_ios.rgr`](../../../gallery/ui/ios/ranger/check_ios.rgr)
96
+ asserts that the page reaches text, filled boxes, borders, rounded corners,
97
+ clipping, a filled path and stroked paths — and that every clip pushed is
98
+ popped, which is the invariant this painter exists to keep.
99
+
100
+ `RecordingSurface` is the Mac-side half of that: wrap the real surface in one,
101
+ paint a frame, and the tally says what the **painter** dispatched rather than
102
+ what the page produced.
103
+
104
+ ## Scroll layers, and what this painter does not do yet
105
+
106
+ A clip that can scroll arrives with `layer` set on its PUSH_CLIP — see
107
+ `EVGDisplayList.refreshLayers`. An app that keeps its list across a scroll
108
+ hands the painter the SAME list, with the commands inside that clip already
109
+ moved to where the scroll put them, so this painter draws the right frame
110
+ without knowing a layer from any other clip. What the host saves is the
111
+ layout and the list build; what it still pays is rasterising every command
112
+ in the layer, every frame.
113
+
114
+ The next step, when a long page on a phone asks for it, is the one the
115
+ browser painter took: keep what was made from the layer. Here that is a
116
+ `CGLayer` (or a bitmap) the size of the layer's range, painted once per
117
+ `buildSeq`, and blitted per frame at the layer's clip with
118
+ `layerShiftX/Y` subtracted from the shift it was painted at — a scroll
119
+ frame is then one `draw(layer, at:)` under the clip and the commands
120
+ outside the layer. The list's `layerFirst`/`layerLast` give the command
121
+ range; nothing else in the list has to change.
122
+
123
+ ## Related
124
+
125
+ * [`lib/evg/android`](../android/README.md) — the same interface over
126
+ `android.graphics.Canvas` and `java.awt.Graphics2D`
127
+ * [`gallery/ui/ios`](../../../gallery/ui/ios/README.md) — the dashboard demo through this
128
+ painter, on iPhone, iPad and Apple Watch