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,329 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // The display list, painted with CoreGraphics.
4
+ //
5
+ // One backend for every Apple platform, on purpose. A `UIView` hands out a
6
+ // `CGContext` from `UIGraphicsGetCurrentContext()`, a SwiftUI `Canvas` hands
7
+ // one out from `GraphicsContext.withCGContext`, and a PDF or a bitmap context
8
+ // is the same object again — so an iPhone, an iPad, an Apple Watch and an
9
+ // off-screen render all go through this file rather than through four.
10
+ //
11
+ // The context is assumed FLIPPED: origin top left, y increasing downwards,
12
+ // which is what UIKit and SwiftUI both hand you and what the display list is
13
+ // already in. The one place that has to know is the text, because CoreText
14
+ // draws glyphs the other way up unless the text matrix says otherwise.
15
+ //
16
+ // Text is CoreText rather than UIKit for one reason: `CTFont` exists on every
17
+ // Apple platform including watchOS, and this file is the same file there.
18
+
19
+ import CoreGraphics
20
+ import CoreText
21
+ import Foundation
22
+
23
+ /// Faces, kept for the life of the process rather than the life of a surface.
24
+ ///
25
+ /// A surface wraps the `CGContext` the system hands to `draw(_:)`, and that is
26
+ /// a different object every frame — so a surface is made per frame, and a cache
27
+ /// owned by one is a cache thrown away sixty times a second. That is exactly
28
+ /// what the cache exists to prevent: a page draws around 190 text runs a frame
29
+ /// and asks for about six distinct faces, and `CTFontCreateUIFontForLanguage`
30
+ /// plus `CTFontCreateCopyWithSymbolicTraits` are not cheap enough to run per
31
+ /// run. Tapping a tab took most of a second on a phone with this cache being
32
+ /// discarded.
33
+ ///
34
+ /// A `CTFont` is immutable and safe to share. The lock is here because the type
35
+ /// is `Sendable` by assertion and a future host may paint off the main thread;
36
+ /// it is uncontended in the one-thread case that actually happens.
37
+ final class EvgFontCache: @unchecked Sendable {
38
+
39
+ static let shared = EvgFontCache()
40
+
41
+ private var faces: [String: CTFont] = [:]
42
+ private let lock = NSLock()
43
+
44
+ func face(size: CGFloat, family: String, bold: Bool, italic: Bool) -> CTFont {
45
+ let key = "\(family)|\(size)|\(bold)|\(italic)"
46
+ lock.lock()
47
+ if let cached = faces[key] {
48
+ lock.unlock()
49
+ return cached
50
+ }
51
+ lock.unlock()
52
+
53
+ let made = EvgFontCache.make(size: size, family: family, bold: bold, italic: italic)
54
+ lock.lock()
55
+ faces[key] = made
56
+ lock.unlock()
57
+ return made
58
+ }
59
+
60
+ private static func make(size: CGFloat, family: String, bold: Bool, italic: Bool) -> CTFont {
61
+ let lower = family.lowercased()
62
+ var base: CTFont
63
+ if lower.contains("mono") || lower.contains("courier") || lower.contains("consol") {
64
+ base = CTFontCreateWithName("Menlo" as CFString, size, nil)
65
+ } else if lower.contains("serif") && !lower.contains("sans") {
66
+ base = CTFontCreateWithName("Georgia" as CFString, size, nil)
67
+ } else {
68
+ // The platform's own sans. `gallery/ui` lays out with EVG's width
69
+ // estimate rather than a face, exactly as the browser build does,
70
+ // so bundling a TrueType file here would make the picture *less*
71
+ // like the one the demo is checked against, not more.
72
+ base = CTFontCreateUIFontForLanguage(.system, size, nil)
73
+ ?? CTFontCreateWithName("Helvetica" as CFString, size, nil)
74
+ }
75
+
76
+ var traits: CTFontSymbolicTraits = []
77
+ if bold { traits.insert(.traitBold) }
78
+ if italic { traits.insert(.traitItalic) }
79
+ if !traits.isEmpty {
80
+ if let styled = CTFontCreateCopyWithSymbolicTraits(base, size, nil, traits, traits) {
81
+ base = styled
82
+ }
83
+ }
84
+ return base
85
+ }
86
+ }
87
+
88
+ final class CoreGraphicsEvgSurface: EvgSurface {
89
+
90
+ private let ctx: CGContext
91
+
92
+ init(context: CGContext) {
93
+ self.ctx = context
94
+ }
95
+
96
+ // MARK: - state
97
+
98
+ func save() {
99
+ ctx.saveGState()
100
+ }
101
+
102
+ func restore() {
103
+ ctx.restoreGState()
104
+ }
105
+
106
+ func clipRect(x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat) {
107
+ ctx.clip(to: CGRect(x: x, y: y, width: w, height: h))
108
+ }
109
+
110
+ func rotate(degrees: CGFloat, px: CGFloat, py: CGFloat) {
111
+ ctx.translateBy(x: px, y: py)
112
+ ctx.rotate(by: degrees * .pi / 180.0)
113
+ ctx.translateBy(x: -px, y: -py)
114
+ }
115
+
116
+ // MARK: - boxes
117
+
118
+ private func boxPath(x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat, radius: CGFloat) -> CGPath {
119
+ let rect = CGRect(x: x, y: y, width: w, height: h)
120
+ if radius <= 0.0 {
121
+ return CGPath(rect: rect, transform: nil)
122
+ }
123
+ // A radius larger than half the box is a pill, and CoreGraphics draws
124
+ // something wrong rather than a pill if it is not clamped.
125
+ let r = min(radius, min(w, h) * 0.5)
126
+ return CGPath(roundedRect: rect, cornerWidth: r, cornerHeight: r, transform: nil)
127
+ }
128
+
129
+ private func applyShadow(_ shadow: EvgShadow?) {
130
+ guard let s = shadow else { return }
131
+ ctx.setShadow(offset: CGSize(width: s.dx, height: s.dy), blur: s.blur, color: s.color.cgColor)
132
+ }
133
+
134
+ func fillRect(
135
+ x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat,
136
+ radius: CGFloat,
137
+ color: EvgColor,
138
+ gradient: EvgGradient?,
139
+ shadow: EvgShadow?
140
+ ) {
141
+ if w <= 0 || h <= 0 { return }
142
+ let path = boxPath(x: x, y: y, w: w, h: h, radius: radius)
143
+
144
+ guard let grad = gradient else {
145
+ ctx.saveGState()
146
+ applyShadow(shadow)
147
+ ctx.addPath(path)
148
+ ctx.setFillColor(color.cgColor)
149
+ ctx.fillPath()
150
+ ctx.restoreGState()
151
+ return
152
+ }
153
+
154
+ // A gradient cannot be a fill colour, so the shape becomes a clip and
155
+ // the gradient is drawn through it. The shadow is the SHAPE's, not the
156
+ // gradient's, so it is painted first as a flat fill of the first stop —
157
+ // a gradient box under a shadow casts the box's silhouette.
158
+ if shadow != nil {
159
+ ctx.saveGState()
160
+ applyShadow(shadow)
161
+ ctx.addPath(path)
162
+ ctx.setFillColor(grad.color1.cgColor)
163
+ ctx.fillPath()
164
+ ctx.restoreGState()
165
+ }
166
+
167
+ ctx.saveGState()
168
+ ctx.addPath(path)
169
+ ctx.clip()
170
+ let space = CGColorSpaceCreateDeviceRGB()
171
+ let colors = [grad.color1.cgColor, grad.color2.cgColor] as CFArray
172
+ if let cg = CGGradient(colorsSpace: space, colors: colors, locations: [0.0, 1.0]) {
173
+ let start = CGPoint(x: x, y: y)
174
+ let end = grad.vertical ? CGPoint(x: x, y: y + h) : CGPoint(x: x + w, y: y)
175
+ ctx.drawLinearGradient(cg, start: start, end: end, options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
176
+ }
177
+ ctx.restoreGState()
178
+ }
179
+
180
+ func strokeRect(
181
+ x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat,
182
+ radius: CGFloat, thickness: CGFloat,
183
+ color: EvgColor
184
+ ) {
185
+ if w <= 0 || h <= 0 { return }
186
+ // EVG's border is a band INSIDE the box, and a CoreGraphics stroke
187
+ // straddles the path. Insetting by half the thickness puts the band
188
+ // where the layout said it was — which is what keeps a one-point
189
+ // hairline from spilling over the edge of a clipped scroll region.
190
+ let half = thickness * 0.5
191
+ let inset = CGRect(x: x + half, y: y + half, width: max(0, w - thickness), height: max(0, h - thickness))
192
+ let r = radius > 0 ? max(0, min(radius - half, min(inset.width, inset.height) * 0.5)) : 0
193
+ let path = r > 0
194
+ ? CGPath(roundedRect: inset, cornerWidth: r, cornerHeight: r, transform: nil)
195
+ : CGPath(rect: inset, transform: nil)
196
+ ctx.saveGState()
197
+ ctx.addPath(path)
198
+ ctx.setStrokeColor(color.cgColor)
199
+ ctx.setLineWidth(thickness)
200
+ ctx.strokePath()
201
+ ctx.restoreGState()
202
+ }
203
+
204
+ // MARK: - vector outlines
205
+
206
+ /// `pts` is flat — x, y, x, y — and `ringEnds` holds the index one past
207
+ /// each ring's last coordinate, so a shape with a hole arrives as two
208
+ /// rings and is filled as one path with two subpaths.
209
+ private func buildPath(pts: [Double], ringEnds: [Int], close: Bool) -> CGPath {
210
+ let path = CGMutablePath()
211
+ var start = 0
212
+ for rawEnd in ringEnds {
213
+ let end = min(rawEnd, pts.count)
214
+ if end - start >= 4 {
215
+ path.move(to: CGPoint(x: pts[start], y: pts[start + 1]))
216
+ var i = start + 2
217
+ while i + 1 < end {
218
+ path.addLine(to: CGPoint(x: pts[i], y: pts[i + 1]))
219
+ i += 2
220
+ }
221
+ if close { path.closeSubpath() }
222
+ }
223
+ start = max(start, end)
224
+ }
225
+ return path
226
+ }
227
+
228
+ func fillPath(
229
+ pts: [Double], ringEnds: [Int], evenOdd: Bool,
230
+ color: EvgColor,
231
+ shadow: EvgShadow?
232
+ ) {
233
+ let path = buildPath(pts: pts, ringEnds: ringEnds, close: true)
234
+ if path.isEmpty { return }
235
+ ctx.saveGState()
236
+ applyShadow(shadow)
237
+ ctx.addPath(path)
238
+ ctx.setFillColor(color.cgColor)
239
+ if evenOdd {
240
+ ctx.fillPath(using: .evenOdd)
241
+ } else {
242
+ ctx.fillPath(using: .winding)
243
+ }
244
+ ctx.restoreGState()
245
+ }
246
+
247
+ func strokePath(
248
+ pts: [Double], ringEnds: [Int],
249
+ thickness: CGFloat,
250
+ color: EvgColor
251
+ ) {
252
+ // Not closed: a stroked ring in this page is an icon outline or the
253
+ // chart's line, and closing it would draw a chord from the last point
254
+ // back to the first one.
255
+ let path = buildPath(pts: pts, ringEnds: ringEnds, close: false)
256
+ if path.isEmpty { return }
257
+ ctx.saveGState()
258
+ ctx.addPath(path)
259
+ ctx.setStrokeColor(color.cgColor)
260
+ ctx.setLineWidth(thickness)
261
+ ctx.setLineJoin(.round)
262
+ ctx.setLineCap(.round)
263
+ ctx.strokePath()
264
+ ctx.restoreGState()
265
+ }
266
+
267
+ // MARK: - images
268
+
269
+ /// This page reaches no images, and a surface that drew nothing for one
270
+ /// would be indistinguishable from a surface that drew it wrong. A visible
271
+ /// placeholder is the honest answer until an image cache is wired in.
272
+ func drawImage(
273
+ src: String,
274
+ x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat,
275
+ radius: CGFloat,
276
+ flipH: Bool, flipV: Bool
277
+ ) {
278
+ if w <= 0 || h <= 0 { return }
279
+ let path = boxPath(x: x, y: y, w: w, h: h, radius: radius)
280
+ ctx.saveGState()
281
+ ctx.addPath(path)
282
+ ctx.setFillColor(EvgColor(r: 220, g: 220, b: 226, a: 1.0).cgColor)
283
+ ctx.fillPath()
284
+ ctx.addPath(path)
285
+ ctx.setStrokeColor(EvgColor(r: 150, g: 150, b: 160, a: 1.0).cgColor)
286
+ ctx.setLineWidth(1.0)
287
+ ctx.strokePath()
288
+ ctx.move(to: CGPoint(x: x, y: y))
289
+ ctx.addLine(to: CGPoint(x: x + w, y: y + h))
290
+ ctx.strokePath()
291
+ ctx.restoreGState()
292
+ }
293
+
294
+ // MARK: - text
295
+
296
+ private func fontFor(size: CGFloat, family: String, bold: Bool, italic: Bool) -> CTFont {
297
+ EvgFontCache.shared.face(size: size, family: family, bold: bold, italic: italic)
298
+ }
299
+
300
+ func drawTextRun(
301
+ text: String,
302
+ x: CGFloat, top: CGFloat,
303
+ sizePx: CGFloat,
304
+ family: String, bold: Bool, italic: Bool,
305
+ color: EvgColor
306
+ ) {
307
+ if text.isEmpty || sizePx <= 0 { return }
308
+ let font = fontFor(size: sizePx, family: family, bold: bold, italic: italic)
309
+ // CoreText's own attribute names, spelled the long way round.
310
+ // `kCTFontAttributeName` is a `CFString`; `NSAttributedString.Key` is
311
+ // not, and UIKit's `.foregroundColor` is a DIFFERENT string that
312
+ // CoreText does not read.
313
+ let attributes: [NSAttributedString.Key: Any] = [
314
+ NSAttributedString.Key(kCTFontAttributeName as String): font,
315
+ NSAttributedString.Key(kCTForegroundColorAttributeName as String): color.cgColor,
316
+ ]
317
+ let line = CTLineCreateWithAttributedString(
318
+ NSAttributedString(string: text, attributes: attributes)
319
+ )
320
+ ctx.saveGState()
321
+ // `top` is the top of the line box; the baseline is one ascent below
322
+ // it. And the text matrix is flipped because the context is: without
323
+ // it CoreText draws every glyph upside down in a UIKit context.
324
+ ctx.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0)
325
+ ctx.textPosition = CGPoint(x: x, y: top + CTFontGetAscent(font))
326
+ CTLineDraw(line, ctx)
327
+ ctx.restoreGState()
328
+ }
329
+ }
@@ -0,0 +1,70 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // CoreText measures, EVG breaks the lines.
4
+ //
5
+ // PLAN_NATIVE_HOSTS.md S0, the Apple half. `EVGHostTextMeasurer` (Ranger,
6
+ // compiled into the app's generated Swift) takes ONE function from the
7
+ // platform — a run's width, and a face's ascent, descent and leading — and
8
+ // every layout the app builds then measures with it. Before this the layout
9
+ // measured with the advance table, a snapshot of one browser's sans, and
10
+ // `CoreGraphicsEvgSurface` drew with whatever `EvgFontCache` resolved: two
11
+ // faces, and a caret or a label's box placed from the wrong one.
12
+ //
13
+ // The face is the SAME `CTFont` the surface draws with — `EvgFontCache.shared`
14
+ // is asked with the same four arguments `drawTextRun` asks it with — so the
15
+ // width the layout measured is the width `CTLineDraw` puts on the screen.
16
+ // `CTLineGetTypographicBounds` is the advance CoreText itself lays the line
17
+ // out to, kerning and all, which is what makes this exact rather than close.
18
+ //
19
+ // Install once, before the app's `start`, on any thread: `CTFont` is
20
+ // immutable and the cache is locked, so the layout may run off the main
21
+ // actor (S1) and measure where it lays out.
22
+ //
23
+ // Nothing here is compiled by this repository's CI — Swift for Apple needs a
24
+ // Mac — which is the same standing as the rest of this directory.
25
+
26
+ import CoreGraphics
27
+ import CoreText
28
+ import Foundation
29
+
30
+ enum CoreTextMeasurer {
31
+
32
+ /// Make the measurer, attach CoreText to it and make it the default every
33
+ /// `EVGLayout` and `EVGTextEngine` in the process starts from.
34
+ ///
35
+ /// Idempotent in effect: a second call installs a second measurer, which
36
+ /// answers the same numbers from the same cache.
37
+ @discardableResult
38
+ static func install() -> EVGHostTextMeasurer {
39
+ let measurer = EVGHostTextMeasurer()
40
+ measurer.attach(f: metric, name: "coretext")
41
+ EVGDefaultMeasurer.install(m: measurer)
42
+ return measurer
43
+ }
44
+
45
+ /// The one function. `kind` 0 is the width of `text`; 1, 2 and 3 are the
46
+ /// face's ascent, descent and leading, for which `text` is empty.
47
+ static func metric(_ kind: Int, _ text: String, _ family: String,
48
+ _ size: Double, _ bold: Bool, _ italic: Bool) -> Double {
49
+ let font = EvgFontCache.shared.face(size: CGFloat(size), family: family, bold: bold, italic: italic)
50
+ switch kind {
51
+ case 0:
52
+ if text.isEmpty { return 0 }
53
+ let attributes: [NSAttributedString.Key: Any] = [
54
+ NSAttributedString.Key(kCTFontAttributeName as String): font,
55
+ ]
56
+ let line = CTLineCreateWithAttributedString(
57
+ NSAttributedString(string: text, attributes: attributes)
58
+ )
59
+ return CTLineGetTypographicBounds(line, nil, nil, nil)
60
+ case 1:
61
+ return Double(CTFontGetAscent(font))
62
+ case 2:
63
+ return Double(CTFontGetDescent(font))
64
+ default:
65
+ // The face's own line gap. `line-height: normal` is ascent +
66
+ // descent + this, which is what CoreText's own layout uses too.
67
+ return Double(CTFontGetLeading(font))
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,105 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // The engine off the main thread — Apple.
4
+ //
5
+ // PLAN_NATIVE_HOSTS.md S1. A Ranger app compiled to Swift is a value: no
6
+ // UIKit in it, no view, nothing that has to be on the main actor. What has
7
+ // to be on the main actor is the view that receives touches and the context
8
+ // that paints. So the app lives on ONE serial queue, every call the host
9
+ // makes runs there in the order the host made it, and a call that changed
10
+ // the page produces a frame — laid out and listed on the queue — that is
11
+ // handed to the main thread, which paints it and nothing else.
12
+ //
13
+ // Three verbs, and the discipline is that a host uses them and never touches
14
+ // the app directly:
15
+ //
16
+ // post { app in … } run there; return true if a frame is owed
17
+ // ask({ app in … }) { v in … } run there; answer on the main thread
18
+ // sync { app in … } run there and WAIT — for the one read UIKit
19
+ // insists on synchronously (`canBecomeFirstResponder`)
20
+ //
21
+ // Frames are coalesced: a burst of posts that each say "a frame is owed"
22
+ // produces one build after the last of them, and a build that is asked for
23
+ // while one is running produces exactly one more when it finishes — never a
24
+ // queue of stale frames the main thread has to draw through.
25
+ //
26
+ // `Frame` is whatever the host paints from: the display list alone, or the
27
+ // list with the viewport numbers the painter needs beside it, read on the
28
+ // queue so the main thread reads the app for nothing.
29
+
30
+ import Foundation
31
+
32
+ final class EvgEngineQueue<App, Frame> {
33
+
34
+ private let queue = DispatchQueue(label: "fi.ranger.evg.engine", qos: .userInteractive)
35
+ private let app: App
36
+ private let build: (App) -> Frame
37
+ private let deliver: (Frame) -> Void
38
+
39
+ // Touched on the queue only.
40
+ private var building = false
41
+ private var wanted = false
42
+
43
+ /// - Parameters:
44
+ /// - app: the Ranger app; make it BEFORE this, and never read it again
45
+ /// from the main thread
46
+ /// - build: the frame, run on the queue — `app.frame()` and whatever
47
+ /// the painter needs beside it
48
+ /// - deliver: called on the MAIN thread with each built frame; keep it
49
+ /// and `setNeedsDisplay`
50
+ init(app: App, build: @escaping (App) -> Frame, deliver: @escaping (Frame) -> Void) {
51
+ self.app = app
52
+ self.build = build
53
+ self.deliver = deliver
54
+ }
55
+
56
+ /// Run `work` on the engine queue. A `true` result asks for a frame.
57
+ func post(_ work: @escaping (App) -> Bool) {
58
+ queue.async { [self] in
59
+ if work(app) { requestFrameOnQueue() }
60
+ }
61
+ }
62
+
63
+ /// Run `work` on the engine queue and hand its answer to `then` on the
64
+ /// main thread.
65
+ func ask<T>(_ work: @escaping (App) -> T, then: @escaping (T) -> Void) {
66
+ queue.async { [self] in
67
+ let v = work(app)
68
+ DispatchQueue.main.async { then(v) }
69
+ }
70
+ }
71
+
72
+ /// Run `work` on the engine queue and wait for it. For the reads UIKit
73
+ /// asks for synchronously and nothing else: it holds the caller for as
74
+ /// long as whatever is already on the queue takes.
75
+ func sync<T>(_ work: (App) -> T) -> T {
76
+ return queue.sync { work(app) }
77
+ }
78
+
79
+ /// A frame is owed — the clock ticked, the window resized.
80
+ func invalidate() {
81
+ queue.async { [self] in requestFrameOnQueue() }
82
+ }
83
+
84
+ // On the queue.
85
+ private func requestFrameOnQueue() {
86
+ if building {
87
+ wanted = true
88
+ return
89
+ }
90
+ building = true
91
+ // Not inline: the posts already queued behind this one run first, so
92
+ // a press and the release that followed it are both in the frame.
93
+ queue.async { [self] in buildOnQueue() }
94
+ }
95
+
96
+ private func buildOnQueue() {
97
+ let f = build(app)
98
+ DispatchQueue.main.async { [self] in deliver(f) }
99
+ building = false
100
+ if wanted {
101
+ wanted = false
102
+ requestFrameOnQueue()
103
+ }
104
+ }
105
+ }