ranger-compiler 3.5.1 → 3.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/CHANGELOG.md +1064 -20
  2. package/LICENSE +3 -1
  3. package/README.md +123 -28
  4. package/dist/Lang.rgr +1188 -290
  5. package/dist/api.d.ts +2343 -879
  6. package/dist/api.js +79923 -56921
  7. package/dist/lib/JSON.rgr +102 -91
  8. package/dist/lib/Shell.rgr +4 -4
  9. package/dist/lib/apple/AppleToolchain.rgr +4 -4
  10. package/dist/lib/apple/README.md +5 -4
  11. package/dist/lib/apple/apple_test.rgr +1 -1
  12. package/dist/lib/core/README.md +1 -1
  13. package/dist/lib/evg/EVG.rgr +12 -0
  14. package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
  15. package/dist/lib/evg/EVGA11yTree.rgr +894 -0
  16. package/dist/lib/evg/EVGBox.rgr +267 -0
  17. package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
  18. package/dist/lib/evg/EVGCodepoint.rgr +316 -0
  19. package/dist/lib/evg/EVGColor.rgr +700 -0
  20. package/dist/lib/evg/EVGCommands.rgr +177 -0
  21. package/dist/lib/evg/EVGComponent.rgr +331 -0
  22. package/dist/lib/evg/EVGComponentTest.rgr +387 -0
  23. package/dist/lib/evg/EVGConnector.rgr +541 -0
  24. package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
  25. package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
  26. package/dist/lib/evg/EVGEasing.rgr +370 -0
  27. package/dist/lib/evg/EVGEffectTest.rgr +253 -0
  28. package/dist/lib/evg/EVGElement.rgr +4914 -0
  29. package/dist/lib/evg/EVGFixedTest.rgr +289 -0
  30. package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
  31. package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
  32. package/dist/lib/evg/EVGFling.rgr +244 -0
  33. package/dist/lib/evg/EVGFocus.rgr +400 -0
  34. package/dist/lib/evg/EVGFocusTest.rgr +399 -0
  35. package/dist/lib/evg/EVGGradient.rgr +322 -0
  36. package/dist/lib/evg/EVGGrapheme.rgr +190 -0
  37. package/dist/lib/evg/EVGGrid.rgr +975 -0
  38. package/dist/lib/evg/EVGHitTest.rgr +208 -0
  39. package/dist/lib/evg/EVGHoles.rgr +294 -0
  40. package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
  41. package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
  42. package/dist/lib/evg/EVGHostTree.rgr +834 -0
  43. package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
  44. package/dist/lib/evg/EVGImageDecode.rgr +117 -0
  45. package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
  46. package/dist/lib/evg/EVGInspect.rgr +883 -0
  47. package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
  48. package/dist/lib/evg/EVGJsonTest.rgr +535 -0
  49. package/dist/lib/evg/EVGLayout.rgr +4220 -0
  50. package/dist/lib/evg/EVGMeasure.rgr +1267 -0
  51. package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
  52. package/dist/lib/evg/EVGPatch.rgr +1721 -0
  53. package/dist/lib/evg/EVGPatchTest.rgr +692 -0
  54. package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
  55. package/dist/lib/evg/EVGReconcile.rgr +226 -0
  56. package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
  57. package/dist/lib/evg/EVGReject.rgr +126 -0
  58. package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
  59. package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
  60. package/dist/lib/evg/EVGRuler.rgr +232 -0
  61. package/dist/lib/evg/EVGRulerTest.rgr +172 -0
  62. package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
  63. package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
  64. package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
  65. package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
  66. package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
  67. package/dist/lib/evg/EVGText.rgr +105 -0
  68. package/dist/lib/evg/EVGTextEngine.rgr +536 -0
  69. package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
  70. package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
  71. package/dist/lib/evg/EVGToolbar.rgr +1369 -0
  72. package/dist/lib/evg/EVGTransition.rgr +728 -0
  73. package/dist/lib/evg/EVGTreeJson.rgr +721 -0
  74. package/dist/lib/evg/EVGUnit.rgr +554 -0
  75. package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
  76. package/dist/lib/evg/EvgApp.rgr +189 -0
  77. package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
  78. package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
  79. package/dist/lib/evg/EvgHost.rgr +451 -0
  80. package/dist/lib/evg/EvgTest.rgr +91 -0
  81. package/dist/lib/evg/EvgTraceColor.rgr +236 -0
  82. package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
  83. package/dist/lib/evg/EvgTraceFit.rgr +767 -0
  84. package/dist/lib/evg/EvgTracePath.rgr +435 -0
  85. package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
  86. package/dist/lib/evg/EvgViewport.rgr +313 -0
  87. package/dist/lib/evg/FxDemoDoc.rgr +185 -0
  88. package/dist/lib/evg/HOSTS.md +227 -0
  89. package/dist/lib/evg/ISSUES.md +835 -0
  90. package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
  91. package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
  92. package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
  93. package/dist/lib/evg/PLAN_EVG.md +518 -0
  94. package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
  95. package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
  96. package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
  97. package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
  98. package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
  99. package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
  100. package/dist/lib/evg/PathBuilder.rgr +333 -0
  101. package/dist/lib/evg/README.md +1236 -0
  102. package/dist/lib/evg/SPEC.md +1252 -0
  103. package/dist/lib/evg/SVGPathParser.rgr +1199 -0
  104. package/dist/lib/evg/SvgParser.rgr +1736 -0
  105. package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
  106. package/dist/lib/evg/VectorShapes.rgr +331 -0
  107. package/dist/lib/evg/VectorStroke.rgr +107 -0
  108. package/dist/lib/evg/VectorViewBox.rgr +380 -0
  109. package/dist/lib/evg/agent/README.md +290 -0
  110. package/dist/lib/evg/agent/evg_agent.rgr +899 -0
  111. package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
  112. package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
  113. package/dist/lib/evg/agent/fixtures/connector.css +63 -0
  114. package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
  115. package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
  116. package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
  117. package/dist/lib/evg/agent/fixtures/popover.css +99 -0
  118. package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
  119. package/dist/lib/evg/agent/roundtrip.sh +74 -0
  120. package/dist/lib/evg/agent/smoke.sh +230 -0
  121. package/dist/lib/evg/android/README.md +97 -0
  122. package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
  123. package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
  124. package/dist/lib/evg/android/androidstubs/App.kt +30 -0
  125. package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
  126. package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
  127. package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
  128. package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
  129. package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
  130. package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
  131. package/dist/lib/evg/android/androidstubs/View.kt +113 -0
  132. package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
  133. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
  134. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
  135. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
  136. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
  137. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
  138. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
  139. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
  140. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
  141. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
  142. package/dist/lib/evg/apple/README.md +128 -0
  143. package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
  144. package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
  145. package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
  146. package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
  147. package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
  148. package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
  149. package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
  150. package/dist/lib/evg/bench/README.md +64 -0
  151. package/dist/lib/evg/bench/layout-bench.mjs +316 -0
  152. package/dist/lib/evg/bench/layout-cases.mjs +510 -0
  153. package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
  154. package/dist/lib/evg/bin/.gitignore +17 -0
  155. package/dist/lib/evg/evg_test.rgr +313 -0
  156. package/dist/lib/evg/gl/README.md +198 -0
  157. package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
  158. package/dist/lib/evg/gl/blur-check.mjs +399 -0
  159. package/dist/lib/evg/gl/boxmodel.json +1 -0
  160. package/dist/lib/evg/gl/demo.html +46 -0
  161. package/dist/lib/evg/gl/effect-presets.css +285 -0
  162. package/dist/lib/evg/gl/effect-presets.js +63 -0
  163. package/dist/lib/evg/gl/effect-shots.mjs +135 -0
  164. package/dist/lib/evg/gl/evg-a11y.js +566 -0
  165. package/dist/lib/evg/gl/evg-binary.js +160 -0
  166. package/dist/lib/evg/gl/evg-engine.js +296 -0
  167. package/dist/lib/evg/gl/evg-fx.js +237 -0
  168. package/dist/lib/evg/gl/evg-gestures.js +209 -0
  169. package/dist/lib/evg/gl/evg-list.js +167 -0
  170. package/dist/lib/evg/gl/evg-measure.js +186 -0
  171. package/dist/lib/evg/gl/evg-textinput.js +303 -0
  172. package/dist/lib/evg/gl/evg-view.js +144 -0
  173. package/dist/lib/evg/gl/evg-webgl.js +3942 -0
  174. package/dist/lib/evg/gl/fx-check.mjs +998 -0
  175. package/dist/lib/evg/gl/fx-demo.html +166 -0
  176. package/dist/lib/evg/gl/fx-demo.js +2 -0
  177. package/dist/lib/evg/gl/fx-serve.mjs +47 -0
  178. package/dist/lib/evg/gl/gestures-check.mjs +189 -0
  179. package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
  180. package/dist/lib/evg/gl/measure-check.mjs +135 -0
  181. package/dist/lib/evg/gl/rotation-check.mjs +207 -0
  182. package/dist/lib/evg/gl/shift-check.mjs +113 -0
  183. package/dist/lib/evg/gl/stroke-check.mjs +168 -0
  184. package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
  185. package/dist/lib/evg/gl/view-check.mjs +343 -0
  186. package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
  187. package/dist/lib/evg/html/evg-dom.js +318 -0
  188. package/dist/lib/evg/html/evg-html.js +600 -0
  189. package/dist/lib/evg/inspect/README.md +415 -0
  190. package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
  191. package/dist/lib/evg/inspect/evg-inspect.js +947 -0
  192. package/dist/lib/evg/inspect/shots/css.png +0 -0
  193. package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
  194. package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
  195. package/dist/lib/evg/inspect/shots/state.png +0 -0
  196. package/dist/lib/evg/inspect/shots.mjs +226 -0
  197. package/dist/lib/evg/oracle/css-blur.json +576 -0
  198. package/dist/lib/evg/oracle/css-box.json +157 -0
  199. package/dist/lib/evg/oracle/css-timing.json +709 -0
  200. package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
  201. package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
  202. package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
  203. package/dist/lib/evg/original/EVGColor.clj +310 -0
  204. package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
  205. package/dist/lib/evg/original/SVGPath.rgr +627 -0
  206. package/dist/lib/evg/original/Vec2.crgr +103 -0
  207. package/dist/lib/evg/ranger.json +9 -0
  208. package/dist/lib/evg/showcase/README.md +246 -0
  209. package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
  210. package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
  211. package/dist/lib/evg/showcase/build.mjs +651 -0
  212. package/dist/lib/evg/showcase/pages/album.tsx +30 -0
  213. package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
  214. package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
  215. package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
  216. package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
  217. package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
  218. package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
  219. package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
  220. package/dist/lib/evg/showcase/pages/more.tsx +332 -0
  221. package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
  222. package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
  223. package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
  224. package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
  225. package/dist/lib/evg/showcase/pages/units.tsx +35 -0
  226. package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
  227. package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
  228. package/dist/lib/evg/showcase/pages/views.tsx +223 -0
  229. package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
  230. package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
  231. package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
  232. package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
  233. package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
  234. package/dist/lib/evg/showcase/themes/more-default.css +128 -0
  235. package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
  236. package/dist/lib/evg/showcase/themes/showcase.css +508 -0
  237. package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
  238. package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
  239. package/dist/lib/evg/showcase/themes/views-default.css +15 -0
  240. package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
  241. package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
  242. package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
  243. package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
  244. package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
  245. package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
  246. package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
  247. package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
  248. package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
  249. package/dist/lib/evg/web/responsive/README.md +97 -0
  250. package/dist/lib/evg/web/responsive/build.mjs +90 -0
  251. package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
  252. package/dist/lib/evg/web/responsive/index.html +177 -0
  253. package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
  254. package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
  255. package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
  256. package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
  257. package/dist/lib/evg/web/tools/minify.mjs +60 -0
  258. package/dist/lib/evg/web/tracer/build.mjs +87 -0
  259. package/dist/lib/evg/web/tracer/index.html +2805 -0
  260. package/dist/lib/evg/web/tracer/sample.png +0 -0
  261. package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
  262. package/dist/lib/evgr/Cargo.lock +21 -0
  263. package/dist/lib/evgr/Cargo.toml +19 -0
  264. package/dist/lib/evgr/README.md +140 -0
  265. package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
  266. package/dist/lib/evgr/bench/compare.mjs +252 -0
  267. package/dist/lib/evgr/bench/speed.mjs +277 -0
  268. package/dist/lib/evgr/bin/.gitignore +3 -0
  269. package/dist/lib/evgr/src/bin/bench.rs +106 -0
  270. package/dist/lib/evgr/src/bin/smoke.rs +13 -0
  271. package/dist/lib/evgr/src/grid.rs +730 -0
  272. package/dist/lib/evgr/src/lib.rs +1003 -0
  273. package/dist/lib/evgr/src/style.rs +468 -0
  274. package/dist/lib/evgr/src/text.rs +83 -0
  275. package/dist/lib/image/BitReader.rgr +171 -0
  276. package/dist/lib/image/Buffer.rgr +173 -0
  277. package/dist/lib/image/DCT.rgr +283 -0
  278. package/dist/lib/image/Deflate.rgr +339 -0
  279. package/dist/lib/image/HuffmanDecoder.rgr +181 -0
  280. package/dist/lib/image/ImageBuffer.rgr +706 -0
  281. package/dist/lib/image/JPEGDecoder.rgr +808 -0
  282. package/dist/lib/image/PNGDecoder.rgr +544 -0
  283. package/dist/lib/image/PNGEncoder.rgr +388 -0
  284. package/dist/lib/image/PPMImage.rgr +224 -0
  285. package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
  286. package/dist/lib/image/README.md +33 -0
  287. package/dist/lib/image/RasterBuffer.rgr +294 -0
  288. package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
  289. package/dist/lib/image/WebPDecoder.rgr +433 -0
  290. package/dist/lib/image/WebPLossless.rgr +1035 -0
  291. package/dist/lib/image/WebPLossy.rgr +1929 -0
  292. package/dist/lib/image/ranger.json +9 -0
  293. package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
  294. package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
  295. package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
  296. package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
  297. package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
  298. package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
  299. package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
  300. package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
  301. package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
  302. package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
  303. package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
  304. package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
  305. package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
  306. package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
  307. package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
  308. package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
  309. package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
  310. package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
  311. package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
  312. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
  313. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
  314. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
  315. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
  316. package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
  317. package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
  318. package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
  319. package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
  320. package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
  321. package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
  322. package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
  323. package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
  324. package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
  325. package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
  326. package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
  327. package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
  328. package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
  329. package/dist/lib/image/tests/run_webp_tests.sh +39 -0
  330. package/dist/lib/rust/RsJson.rgr +468 -0
  331. package/dist/lib/rust/RsPrelude.rgr +1663 -0
  332. package/dist/lib/shell_test.rgr +7 -7
  333. package/dist/lib/stdlib.rgr +21 -5
  334. package/dist/lib/zip/ranger.json +6 -0
  335. package/dist/rgrc.js +87318 -67610
  336. package/package.json +1010 -1040
  337. package/dist/README.md +0 -117
  338. package/dist/package.json +0 -47
@@ -0,0 +1,262 @@
1
+ package fi.ranger.evg
2
+
3
+ import android.graphics.RenderEffect
4
+ import android.graphics.RuntimeShader
5
+ import android.os.Build
6
+ import android.util.Log
7
+ import android.view.View
8
+ import fi.ranger.rgr.EVGDisplayList
9
+
10
+ /**
11
+ * `evg-surface-effect: ripple`, on Android, as an AGSL post-process.
12
+ *
13
+ * The effect is not a command in the display list and cannot be: it is a pass
14
+ * over the FINISHED pixels, which is why the WebGL host renders the page to a
15
+ * texture and then draws that texture through a fragment shader. An
16
+ * `android.graphics.Canvas` has no such stage — which is why this port shipped
17
+ * without the ripple and said so — but a `RuntimeShader` does. At API 33 the
18
+ * platform hands Skia's own shading language to an app, and
19
+ * `RenderEffect.createRuntimeShaderEffect` wires it into exactly that place:
20
+ * the view is rendered into a texture, the texture is bound to a named
21
+ * `uniform shader`, and the program below decides every pixel on screen.
22
+ *
23
+ * So the shader is a translation of `lib/evg/gl/evg-webgl.js`'s
24
+ * `RIPPLE_FRAG` and not a second effect that has to be kept looking like the
25
+ * first one. Two things are genuinely different, and both are simplifications:
26
+ *
27
+ * * **No flips.** A GL texture's origin is the bottom left and the page's is
28
+ * the top left, which cost the WebGL host two sign errors worth the comments
29
+ * it now carries. A view's coordinate space runs y-down like the page, so
30
+ * nothing is negated here.
31
+ * * **No `dFdx`.** AGSL has no screen-space derivatives, so the height field's
32
+ * gradient — which is where the glint comes from — is taken by evaluating
33
+ * the wave one page pixel away and subtracting, rather than by asking the
34
+ * hardware. Two extra evaluations of a loop that runs a handful of times.
35
+ *
36
+ * Below API 33 this is a no-op and the page draws exactly as it did.
37
+ */
38
+ class RippleEffect(private val view: View) {
39
+
40
+ private var shader: RuntimeShader? = null
41
+ private var applied = false
42
+ /** A program that will not compile is reported once, not every frame. */
43
+ private var broken = false
44
+
45
+ /** True when the platform can run this at all. */
46
+ val available: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
47
+
48
+ /**
49
+ * Put the current touches on screen, or take the effect off when there are
50
+ * none left.
51
+ *
52
+ * `list` is only read for the effect's PARAMETERS — they come off the
53
+ * stylesheet and are stamped onto every display list — while the drops are
54
+ * passed separately, because a ripple frame must not cost a rebuilt page:
55
+ * ageing a touch changes nothing about what was drawn.
56
+ *
57
+ * `drops` is x, y, age per touch, in page pixels and seconds, oldest first.
58
+ * `scale` is device pixels per page pixel and `panX` is how far the page is
59
+ * scrolled sideways — together they are how a page coordinate becomes a
60
+ * coordinate this shader is asked about.
61
+ */
62
+ fun update(list: EVGDisplayList, drops: FloatArray, count: Int, scale: Float, panX: Float) {
63
+ if (!available || broken) return
64
+ if (list.effectKind != "ripple" || count <= 0) {
65
+ clear()
66
+ return
67
+ }
68
+ val s = shaderOrNull() ?: return
69
+ val n = if (count > MAX_DROPS) MAX_DROPS else count
70
+ // The array is always the full eight: a uniform array is sized by its
71
+ // declaration, and handing it a short one is an argument error rather
72
+ // than a partial update.
73
+ val packed = FloatArray(MAX_DROPS * 3)
74
+ for (i in 0 until n) {
75
+ packed[i * 3] = drops[i * 3]
76
+ packed[i * 3 + 1] = drops[i * 3 + 1]
77
+ packed[i * 3 + 2] = drops[i * 3 + 2]
78
+ }
79
+ try {
80
+ s.setFloatUniform("uDrops", packed)
81
+ s.setIntUniform("uCount", n)
82
+ s.setFloatUniform("uScale", if (scale > 0f) scale else 1f)
83
+ s.setFloatUniform("uPanX", panX)
84
+ s.setFloatUniform("uSpeed", list.effectSpeed.toFloat())
85
+ s.setFloatUniform("uWidth", maxOf(list.effectWidth.toFloat(), 0.001f))
86
+ s.setFloatUniform("uStrength", list.effectStrength.toFloat())
87
+ s.setFloatUniform("uDecay", list.effectDecay.toFloat())
88
+ s.setFloatUniform("uHi", list.effectHighlight.toFloat())
89
+ s.setIntUniform("uRings", list.effectRings.toInt().coerceIn(1, MAX_RINGS))
90
+ s.setFloatUniform("uStagger", list.effectStagger.toFloat())
91
+ s.setFloatUniform("uFalloff", list.effectFalloff.toFloat())
92
+ s.setFloatUniform("uShine", list.effectShine.toFloat())
93
+ s.setFloatUniform("uGloss", maxOf(list.effectGloss.toFloat(), 1f))
94
+ s.setFloatUniform("uBump", list.effectBump.toFloat())
95
+ s.setFloatUniform(
96
+ "uLight",
97
+ list.effectLightX.toFloat(),
98
+ list.effectLightY.toFloat(),
99
+ list.effectLightZ.toFloat(),
100
+ )
101
+ view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(s, "uContent"))
102
+ applied = true
103
+ } catch (e: Throwable) {
104
+ // A uniform this platform will not take is worth exactly one line
105
+ // in the log and then silence: the page is still drawn, it just is
106
+ // not rippling.
107
+ broken = true
108
+ clear()
109
+ Log.w(TAG, "ripple uniforms rejected, effect off: " + e)
110
+ }
111
+ }
112
+
113
+ /** Off, and cheaply: a view with a render effect is composited off-screen. */
114
+ fun clear() {
115
+ if (!applied) return
116
+ applied = false
117
+ view.setRenderEffect(null)
118
+ }
119
+
120
+ private fun shaderOrNull(): RuntimeShader? {
121
+ shader?.let { return it }
122
+ return try {
123
+ val s = RuntimeShader(AGSL)
124
+ shader = s
125
+ s
126
+ } catch (e: Throwable) {
127
+ broken = true
128
+ Log.w(TAG, "AGSL would not compile, ripple off: " + e)
129
+ null
130
+ }
131
+ }
132
+
133
+ private companion object {
134
+ const val TAG = "EvgRipple"
135
+
136
+ /**
137
+ * Eight touches is not a number anybody reaches by tapping; it is what
138
+ * a finger dragged across the surface fills in a third of a second, and
139
+ * the oldest is retired to make room. Five rings is one touch's train.
140
+ */
141
+ const val MAX_DROPS = 8
142
+ const val MAX_RINGS = 5
143
+
144
+ val AGSL = """
145
+ uniform shader uContent;
146
+ uniform float3 uDrops[8]; // x, y in PAGE pixels; z is seconds since the touch
147
+ uniform int uCount;
148
+ uniform float uScale; // device pixels per page pixel
149
+ uniform float uPanX; // page pixels scrolled sideways
150
+ uniform float uSpeed; // page px per second the ring travels
151
+ uniform float uWidth; // the envelope's sigma, page px
152
+ uniform float uStrength; // displacement at the crest, page px
153
+ uniform float uDecay; // per second
154
+ uniform float uHi; // how much the crest lightens
155
+ uniform int uRings;
156
+ uniform float uStagger; // seconds between one wavefront and the next
157
+ uniform float uFalloff; // what each ring behind the front is worth
158
+ uniform float uShine; // how strong the glint is
159
+ uniform float uGloss; // Blinn-Phong exponent: how tight it is
160
+ uniform float uBump; // what turns the height field into a slope
161
+ uniform float3 uLight; // where the light is, not necessarily normalised
162
+
163
+ // The wave sum at one page point: the displacement as a VECTOR, the crest, and
164
+ // the envelope. Summing the displacement rather than an amplitude is the whole
165
+ // of the interference — two rings arriving from opposite sides push the surface
166
+ // in opposite directions and the pixel between them stays where it was.
167
+ float4 waveAt(float2 p) {
168
+ float2 push = float2(0.0, 0.0);
169
+ float crest = 0.0;
170
+ float energy = 0.0;
171
+ for (int i = 0; i < 8; i++) {
172
+ if (i >= uCount) { break; }
173
+ float3 drop = uDrops[i];
174
+ float2 delta = p - drop.xy;
175
+ float d = length(delta);
176
+ float2 dir = delta / max(d, 0.001);
177
+ float amp = 1.0;
178
+ // The TRAIN. A drop on water does not make one ring, it makes several
179
+ // from the same point a moment apart, each fainter than the one in
180
+ // front. They cost no state: the k-th is this touch aged by k staggers,
181
+ // and one that has not started yet has a negative age and is skipped.
182
+ for (int k = 0; k < 5; k++) {
183
+ if (k >= uRings) { break; }
184
+ float t = drop.z - float(k) * uStagger;
185
+ if (t <= 0.0) { break; }
186
+ float radius = t * uSpeed;
187
+ // A Gaussian ring: the wave only exists near the front, so the rest
188
+ // of the page is sampled where it was drawn and stays sharp.
189
+ float e = (d - radius) / uWidth;
190
+ float env = exp(-e * e);
191
+ float fade = env * exp(-t * uDecay) * amp;
192
+ float w = sin((d - radius) * 0.16) * fade;
193
+ push = push + dir * w;
194
+ crest = crest + w;
195
+ // The envelope, kept apart from the signal: it is the amplitude
196
+ // with the oscillation divided out, so unlike the wave itself it
197
+ // does not pass through zero twice per wavelength.
198
+ energy = energy + fade;
199
+ amp = amp * uFalloff;
200
+ }
201
+ }
202
+ return float4(push.x, push.y, crest, energy);
203
+ }
204
+
205
+ half4 main(float2 fragCoord) {
206
+ // Device pixels to page pixels. Both spaces run y DOWN — the flips the
207
+ // WebGL host needs are a GL texture's business, not a view's.
208
+ float2 p = float2(fragCoord.x / uScale + uPanX, fragCoord.y / uScale);
209
+ float4 w = waveAt(p);
210
+
211
+ // NOTHING HAPPENING HERE, AND THAT IS MOST OF THE SCREEN.
212
+ //
213
+ // A ring is a thin annulus travelling across a page that is otherwise
214
+ // exactly what was drawn. Answering those pixels immediately skips the two
215
+ // extra wave sums the glint's gradient costs and the three extra texture
216
+ // reads the aberration costs — the difference between an effect that costs
217
+ // a ring and one that costs a screen, which on a software-rendered emulator
218
+ // is the difference between an effect and a hang.
219
+ if (w.w < 0.0015 && abs(w.z) < 0.0015) {
220
+ return uContent.eval(fragCoord);
221
+ }
222
+
223
+ // Back to device pixels to sample with.
224
+ float2 offset = w.xy * uStrength * uScale;
225
+
226
+ // A whisper of chromatic aberration along the crest. 1.08 and 0.92 rather
227
+ // than anything bolder: past about a tenth the page stops looking like
228
+ // water and starts looking like a filter.
229
+ float r = uContent.eval(fragCoord + offset * 1.08).r;
230
+ float g = uContent.eval(fragCoord + offset).g;
231
+ float b = uContent.eval(fragCoord + offset * 0.92).b;
232
+ float a = uContent.eval(fragCoord + offset).a;
233
+
234
+ // THE SURFACE'S OWN NORMAL. The wave sum is a height field, its gradient is
235
+ // the slope, and the slope is the normal — so nothing has to be stored to
236
+ // light this that the displacement did not already need. AGSL has no
237
+ // dFdx/dFdy, so the gradient is a finite difference one PAGE pixel away,
238
+ // divided by the scale to be the per-device-pixel slope the WebGL host's
239
+ // derivative gives it.
240
+ float wx = waveAt(p + float2(1.0, 0.0)).z - w.z;
241
+ float wy = waveAt(p + float2(0.0, 1.0)).z - w.z;
242
+ float2 grad = float2(wx, wy) * uBump / max(uScale, 0.001);
243
+ float3 N = normalize(float3(-grad.x, -grad.y, 1.0));
244
+
245
+ // Blinn-Phong. The eye looks straight down at a flat page, so V is +Z.
246
+ float3 L = normalize(uLight);
247
+ float3 V = float3(0.0, 0.0, 1.0);
248
+ float3 H = normalize(L + V);
249
+ float spec = pow(max(dot(N, H), 0.0), uGloss) * uShine;
250
+
251
+ // The specular lives on the FLANK of a wave rather than its top, which is
252
+ // why the glint slides along the ring as the ring travels. It is faded out
253
+ // where there is no ring — by the ENVELOPE, not by the wave, which crosses
254
+ // zero twice per wavelength and would cut a dark seam through every glint.
255
+ float alive = clamp(w.w * 3.0, 0.0, 1.0);
256
+
257
+ float3 col = float3(r, g, b) + float3(w.z * uHi + spec * alive);
258
+ return half4(half3(col), half(a));
259
+ }
260
+ """.trimIndent()
261
+ }
262
+ }
@@ -0,0 +1,238 @@
1
+ package fi.ranger.evg
2
+
3
+ import java.awt.AlphaComposite
4
+ import java.awt.BasicStroke
5
+ import java.awt.Color
6
+ import java.awt.GradientPaint
7
+ import java.awt.Graphics2D
8
+ import java.awt.RenderingHints
9
+ import java.awt.Shape
10
+ import java.awt.geom.AffineTransform
11
+ import java.awt.geom.Path2D
12
+ import java.awt.geom.RoundRectangle2D
13
+ import java.awt.image.BufferedImage
14
+ import java.io.ByteArrayInputStream
15
+ import java.io.File
16
+ import javax.imageio.ImageIO
17
+
18
+ /**
19
+ * The same display list, painted with Java2D.
20
+ *
21
+ * This exists so the port can be *checked*. An `android.graphics.Canvas`
22
+ * painter can only be run on a device or an emulator, and neither is available
23
+ * where this repository's tests run; Java2D is in every JDK. Both surfaces
24
+ * implement the same interface and are driven by the same [EvgPainter], so a
25
+ * page that renders correctly here is evidence about the walk, the geometry,
26
+ * the colours, the clip stack and the text placement — everything except the
27
+ * last page of platform delegation.
28
+ *
29
+ * It is also a usable desktop host in its own right: `RenderDeck` turns a
30
+ * `.pptx` into PNGs with it, and `CheckDashboard` does the same for the ui
31
+ * gallery's dashboard.
32
+ */
33
+ class AwtEvgSurface(
34
+ private val g: Graphics2D,
35
+ /** The deck's own picture parts, by package path. */
36
+ private val images: Map<String, ByteArray>,
37
+ /**
38
+ * The faces to draw with — the SAME [AwtFaces] the measurer was installed
39
+ * with ([AwtTextMeasurer.install]), so what the layout measured is what
40
+ * is painted. The secondary constructor takes the files and makes its
41
+ * own, for a host that measures with the table.
42
+ */
43
+ private val faces: AwtFaces,
44
+ ) : EvgSurface {
45
+
46
+ constructor(
47
+ g: Graphics2D,
48
+ images: Map<String, ByteArray>,
49
+ /** TrueType files to draw with, by the family name EVG measured under. */
50
+ fontFiles: Map<String, File>,
51
+ ) : this(g, images, AwtFaces(fontFiles))
52
+
53
+ private val stack = ArrayDeque<State>()
54
+ private val imageCache = HashMap<String, BufferedImage?>()
55
+
56
+ private data class State(val clip: Shape?, val transform: AffineTransform)
57
+
58
+ init {
59
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
60
+ g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
61
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
62
+ g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE)
63
+ g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
64
+ }
65
+
66
+ override fun save() {
67
+ stack.addLast(State(g.clip, AffineTransform(g.transform)))
68
+ }
69
+
70
+ override fun restore() {
71
+ val s = stack.removeLastOrNull() ?: return
72
+ g.transform = s.transform
73
+ g.clip = s.clip
74
+ }
75
+
76
+ override fun clipRect(x: Float, y: Float, w: Float, h: Float) {
77
+ g.clipRect(x.toInt(), y.toInt(), Math.ceil(w.toDouble()).toInt(), Math.ceil(h.toDouble()).toInt())
78
+ }
79
+
80
+ override fun rotate(degrees: Float, px: Float, py: Float) {
81
+ g.rotate(Math.toRadians(degrees.toDouble()), px.toDouble(), py.toDouble())
82
+ }
83
+
84
+ override fun fillRect(
85
+ x: Float, y: Float, w: Float, h: Float,
86
+ radius: Float, color: Int, gradient: EvgGradient?, shadow: EvgShadow?,
87
+ ) {
88
+ if (w <= 0f || h <= 0f) return
89
+ val shape = roundRect(x, y, w, h, radius)
90
+ shadow?.let { paintShadow(shape, it) }
91
+ if (gradient != null) {
92
+ g.paint = if (gradient.vertical) {
93
+ GradientPaint(x, y, awt(gradient.color1), x, y + h, awt(gradient.color2))
94
+ } else {
95
+ GradientPaint(x, y, awt(gradient.color1), x + w, y, awt(gradient.color2))
96
+ }
97
+ } else {
98
+ if (alphaOf(color) == 0) return
99
+ g.paint = awt(color)
100
+ }
101
+ g.fill(shape)
102
+ }
103
+
104
+ override fun strokeRect(
105
+ x: Float, y: Float, w: Float, h: Float,
106
+ radius: Float, thickness: Float, color: Int,
107
+ ) {
108
+ if (w <= 0f || h <= 0f) return
109
+ // EVG's border is a band of `thickness` kept INSIDE the box, so the
110
+ // stroke is centred half a thickness in from the edge.
111
+ val half = thickness / 2f
112
+ val shape = roundRect(x + half, y + half, w - thickness, h - thickness, (radius - half).coerceAtLeast(0f))
113
+ g.paint = awt(color)
114
+ g.stroke = BasicStroke(thickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)
115
+ g.draw(shape)
116
+ }
117
+
118
+ override fun fillPath(
119
+ pts: List<Double>, ringEnds: List<Int>, evenOdd: Boolean,
120
+ color: Int, shadow: EvgShadow?,
121
+ ) {
122
+ val path = buildPath(pts, ringEnds, evenOdd, close = true) ?: return
123
+ shadow?.let { paintShadow(path, it) }
124
+ g.paint = awt(color)
125
+ g.fill(path)
126
+ }
127
+
128
+ override fun strokePath(pts: List<Double>, ringEnds: List<Int>, thickness: Float, color: Int) {
129
+ val path = buildPath(pts, ringEnds, evenOdd = false, close = false) ?: return
130
+ g.paint = awt(color)
131
+ g.stroke = BasicStroke(thickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)
132
+ g.draw(path)
133
+ }
134
+
135
+ override fun drawImage(
136
+ src: String, x: Float, y: Float, w: Float, h: Float,
137
+ radius: Float, flipH: Boolean, flipV: Boolean,
138
+ ) {
139
+ if (w <= 0f || h <= 0f) return
140
+ val img = imageCache.getOrPut(src) {
141
+ images[src]?.let { bytes -> runCatching { ImageIO.read(ByteArrayInputStream(bytes)) }.getOrNull() }
142
+ }
143
+ if (img == null) {
144
+ // A missing texture and an empty slide must not look the same.
145
+ g.paint = Color(180, 198, 230)
146
+ g.fill(roundRect(x, y, w, h, radius))
147
+ return
148
+ }
149
+ val old = g.clip
150
+ if (radius > 0.5f) g.clip(roundRect(x, y, w, h, radius))
151
+ val sx = if (flipH) -1.0 else 1.0
152
+ val sy = if (flipV) -1.0 else 1.0
153
+ val at = AffineTransform()
154
+ at.translate((x + if (flipH) w else 0f).toDouble(), (y + if (flipV) h else 0f).toDouble())
155
+ at.scale(sx * w / img.width, sy * h / img.height)
156
+ g.drawImage(img, at, null)
157
+ g.clip = old
158
+ }
159
+
160
+ override fun drawTextRun(
161
+ text: String, x: Float, top: Float, sizePx: Float,
162
+ family: String, bold: Boolean, italic: Boolean, color: Int,
163
+ ) {
164
+ if (sizePx <= 0f) return
165
+ val font = fontFor(family, sizePx, bold, italic)
166
+ g.font = font
167
+ g.paint = awt(color)
168
+ // `top` is the top of the line box; the baseline is one face-ascent
169
+ // below it. This is the same rule the WebGL atlas and the software
170
+ // rasteriser place a run by.
171
+ val baseline = top + g.fontMetrics.ascent
172
+ g.drawString(text, x, baseline)
173
+ }
174
+
175
+ // --- the parts that are only arithmetic ----------------------------------
176
+
177
+ private fun roundRect(x: Float, y: Float, w: Float, h: Float, radius: Float): Shape {
178
+ if (radius <= 0.5f) {
179
+ return java.awt.geom.Rectangle2D.Float(x, y, w, h)
180
+ }
181
+ val r = radius.coerceAtMost(minOf(w, h) / 2f) * 2f
182
+ return RoundRectangle2D.Float(x, y, w, h, r, r)
183
+ }
184
+
185
+ private fun buildPath(pts: List<Double>, ringEnds: List<Int>, evenOdd: Boolean, close: Boolean): Path2D.Float? {
186
+ if (pts.size < 4) return null
187
+ val path = Path2D.Float(if (evenOdd) Path2D.WIND_EVEN_ODD else Path2D.WIND_NON_ZERO)
188
+ var start = 0
189
+ for (end in ringEnds) {
190
+ val stop = end.coerceAtMost(pts.size)
191
+ if (stop - start < 4) { start = stop; continue }
192
+ path.moveTo(pts[start], pts[start + 1])
193
+ var i = start + 2
194
+ while (i + 1 < stop) {
195
+ path.lineTo(pts[i], pts[i + 1])
196
+ i += 2
197
+ }
198
+ if (close) path.closePath()
199
+ start = stop
200
+ }
201
+ return if (path.currentPoint == null) null else path
202
+ }
203
+
204
+ /**
205
+ * Java2D has no blur, so the soft edge is faked the way a shadow was faked
206
+ * before anyone had one: the silhouette drawn a few times, growing and
207
+ * fading. Cheap, and close enough to tell a shadowed shape from a flat one
208
+ * — which is all this surface is asked to prove.
209
+ */
210
+ private fun paintShadow(shape: Shape, shadow: EvgShadow) {
211
+ val steps = if (shadow.blur > 0.5f) 4 else 1
212
+ val old = g.composite
213
+ val at = AffineTransform.getTranslateInstance(shadow.dx.toDouble(), shadow.dy.toDouble())
214
+ val moved = at.createTransformedShape(shape)
215
+ val base = alphaOf(shadow.color) / 255f
216
+ for (s in 0 until steps) {
217
+ val grow = shadow.blur * s / steps
218
+ val a = base * (1f - s.toFloat() / steps) / steps * 2f
219
+ g.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, a.coerceIn(0f, 1f))
220
+ g.paint = Color(shadow.color and 0xFFFFFF, false)
221
+ if (grow <= 0f) {
222
+ g.fill(moved)
223
+ } else {
224
+ g.stroke = BasicStroke(grow * 2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)
225
+ g.fill(moved)
226
+ g.draw(moved)
227
+ }
228
+ }
229
+ g.composite = old
230
+ }
231
+
232
+ private fun fontFor(family: String, sizePx: Float, bold: Boolean, italic: Boolean): Font =
233
+ faces.font(family, sizePx, bold, italic)
234
+
235
+ private fun awt(argb: Int): Color = Color(argb, true)
236
+
237
+ private fun alphaOf(argb: Int): Int = (argb ushr 24) and 0xFF
238
+ }
@@ -0,0 +1,85 @@
1
+ // SPDX-License-Identifier: MIT
2
+ package fi.ranger.evg
3
+
4
+ import fi.ranger.rgr.EVGDefaultMeasurer
5
+ import fi.ranger.rgr.EVGHostTextMeasurer
6
+ import java.awt.Font
7
+ import java.awt.font.FontRenderContext
8
+ import java.io.File
9
+
10
+ /**
11
+ * The faces a Java2D host draws with, resolved ONE way.
12
+ *
13
+ * [AwtEvgSurface] used to resolve a family to a `Font` privately, per
14
+ * surface — and a surface is made per `Graphics2D`, so per frame. With a
15
+ * measurer that has to resolve the same family to the same face without a
16
+ * `Graphics2D` in hand, the resolution moves here and both ask it: a family
17
+ * named in [fontFiles] is that file, anything else is the first file or the
18
+ * JDK's sans, and bold and italic are derived styles of it. Whatever the
19
+ * rule is, there is one of it.
20
+ */
21
+ class AwtFaces(fontFiles: Map<String, File>) {
22
+
23
+ private data class FontKey(val family: String, val size: Int, val bold: Boolean, val italic: Boolean)
24
+
25
+ private val fontCache = HashMap<FontKey, Font>()
26
+ private val baseFonts = HashMap<String, Font>()
27
+ private var fallback: Font = Font(Font.SANS_SERIF, Font.PLAIN, 12)
28
+
29
+ init {
30
+ for ((family, file) in fontFiles) {
31
+ val f = runCatching { Font.createFont(Font.TRUETYPE_FONT, file) }.getOrNull() ?: continue
32
+ baseFonts[family.lowercase()] = f
33
+ if (baseFonts.size == 1) fallback = f
34
+ }
35
+ }
36
+
37
+ fun font(family: String, sizePx: Float, bold: Boolean, italic: Boolean): Font {
38
+ val key = FontKey(family.lowercase(), Math.round(sizePx * 4), bold, italic)
39
+ synchronized(fontCache) {
40
+ return fontCache.getOrPut(key) {
41
+ val base = baseFonts[family.lowercase()] ?: fallback
42
+ var style = Font.PLAIN
43
+ if (bold) style = style or Font.BOLD
44
+ if (italic) style = style or Font.ITALIC
45
+ base.deriveFont(style, sizePx)
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Java2D measures, EVG breaks the lines — the desktop twin of
53
+ * [AndroidTextMeasurer], for the same reason [AwtEvgSurface] is the twin of
54
+ * [AndroidEvgSurface]: so the port can be CHECKED where there is no device.
55
+ * A check that lays out with the table and paints with Java2D is asserting
56
+ * about two faces; with this installed it asserts about one.
57
+ *
58
+ * Widths come from `Font.getStringBounds` with a fractional-metrics render
59
+ * context, which is the advance Java2D lays the string out to when the
60
+ * surface's anti-aliasing hints are on; the vertical numbers from
61
+ * `getLineMetrics`, which is the face's, not the string's.
62
+ */
63
+ object AwtTextMeasurer {
64
+
65
+ /**
66
+ * Make the measurer, attach Java2D to it through [faces] and make it the
67
+ * default every `EVGLayout` and `EVGTextEngine` in the process starts
68
+ * from. Hand the SAME [AwtFaces] to every [AwtEvgSurface] the check makes.
69
+ */
70
+ fun install(faces: AwtFaces): EVGHostTextMeasurer {
71
+ val frc = FontRenderContext(null, true, true)
72
+ val measurer = EVGHostTextMeasurer()
73
+ measurer.attach({ kind, text, family, size, bold, italic ->
74
+ val font = faces.font(family, size.toFloat(), bold, italic)
75
+ when (kind) {
76
+ 0 -> if (text.isEmpty()) 0.0 else font.getStringBounds(text, frc).width
77
+ 1 -> font.getLineMetrics("Hg", frc).ascent.toDouble()
78
+ 2 -> font.getLineMetrics("Hg", frc).descent.toDouble()
79
+ else -> font.getLineMetrics("Hg", frc).leading.toDouble()
80
+ }
81
+ }, "awt")
82
+ EVGDefaultMeasurer.install(measurer)
83
+ return measurer
84
+ }
85
+ }