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,209 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Pan and zoom on an EVG canvas, from the pointers the browser reports.
4
+ //
5
+ // Every page that draws a scene bigger than its canvas needs the same four
6
+ // gestures, and each of them had been written again in its own standalone:
7
+ // drag to pan, wheel to zoom, a press that does not travel is a click, and —
8
+ // missing everywhere — two fingers to pinch. This is that handling, once.
9
+ //
10
+ // WHAT IT DOES NOT DO. It never touches the scene: it reads the view the host
11
+ // gives it (`view()`) and hands back another (`setView()`), so the host keeps
12
+ // deciding what a view is, when to paint one, and how to coalesce them. The
13
+ // host's own `setView` is expected to schedule rather than paint — a pinch
14
+ // reports far more moves than a display can show.
15
+ //
16
+ // THE ANCHOR is what makes a zoom feel like a zoom: the point under the
17
+ // cursor, or under the midpoint of two fingers, stays where it is. The world
18
+ // point below it is `(screen - pan) / scale`, and keeping it there after the
19
+ // scale changes gives `pan' = screen - (screen - pan) * (scale' / scale)` —
20
+ // which is also what moves the picture when the fingers move, so a pinch pans
21
+ // and zooms in one expression rather than two that fight.
22
+ //
23
+ // A trackpad pinch is not a touch: browsers report it as a wheel with `ctrl`
24
+ // held, at a fraction of a notch per event. It gets its own factor, or a
25
+ // pinch crawls where a wheel flies. Safari's own `gesture*` events are read
26
+ // too, because a Safari trackpad may send those instead.
27
+
28
+ /** How far a press may travel and still be a click, in CSS pixels. */
29
+ const DRAG_SLOP = 4;
30
+
31
+ const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
32
+ const mid = (a, b) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
33
+
34
+ /**
35
+ * Attach pan and zoom to a canvas.
36
+ *
37
+ * attachViewGestures(canvas, {
38
+ * view: () => ({ x, y, sc }), // the pan and scale in force
39
+ * setView: (x, y, sc) => {}, // schedule this one
40
+ * minZoom, maxZoom, // optional clamps
41
+ * onTap: (clientX, clientY) => {}, // a press that did not travel
42
+ * onCursor: (name) => {}, // "grab" / "grabbing", for the host's CSS
43
+ * })
44
+ *
45
+ * Returns a function that removes every listener again.
46
+ */
47
+ export function attachViewGestures(el, opts) {
48
+ const view = opts.view;
49
+ const setView = opts.setView;
50
+ const minZoom = opts.minZoom ?? 0.05;
51
+ const maxZoom = opts.maxZoom ?? 16;
52
+ const onTap = opts.onTap || (() => {});
53
+ const onCursor = opts.onCursor || (() => {});
54
+
55
+ // Every pointer currently down on the canvas, in canvas coordinates.
56
+ const points = new Map();
57
+ let drag = null; // one pointer: a pan, or a click that has not moved yet
58
+ let pinch = null; // two pointers: the scale and the midpoint they started at
59
+ let gestureScale = 1;
60
+
61
+ const at = (ev) => {
62
+ const r = el.getBoundingClientRect();
63
+ return { x: ev.clientX - r.left, y: ev.clientY - r.top };
64
+ };
65
+
66
+ /** Scale by `factor` about a point on the canvas, which stays put. */
67
+ function zoomAbout(factor, px, py) {
68
+ const now = view();
69
+ const next = Math.min(maxZoom, Math.max(minZoom, now.sc * factor));
70
+ if (next === now.sc) return;
71
+ const k = next / now.sc;
72
+ setView(px - (px - now.x) * k, py - (py - now.y) * k, next);
73
+ }
74
+
75
+ function beginPinch() {
76
+ const [a, b] = [...points.values()];
77
+ pinch = { d0: dist(a, b), m0: mid(a, b), view: { ...view() } };
78
+ drag = null;
79
+ onCursor("grabbing");
80
+ }
81
+
82
+ function movePinch() {
83
+ const [a, b] = [...points.values()];
84
+ const d = dist(a, b);
85
+ if (!pinch.d0 || !d) return;
86
+ const m = mid(a, b);
87
+ const want = pinch.view.sc * (d / pinch.d0);
88
+ const sc = Math.min(maxZoom, Math.max(minZoom, want));
89
+ const k = sc / pinch.view.sc;
90
+ // The world point under the fingers' midpoint when the pinch began is
91
+ // put back under the midpoint where they are now: the spread scales,
92
+ // the travel pans.
93
+ setView(m.x - (pinch.m0.x - pinch.view.x) * k, m.y - (pinch.m0.y - pinch.view.y) * k, sc);
94
+ }
95
+
96
+ // Capture keeps the moves coming when a finger leaves the canvas, and is
97
+ // not worth a broken gesture when the browser will not give it: a pointer
98
+ // the host synthesised has no capture to take.
99
+ const capture = (id) => { try { el.setPointerCapture(id); } catch (_) { /* no capture */ } };
100
+ const release = (id) => { try { if (el.hasPointerCapture(id)) el.releasePointerCapture(id); } catch (_) { /* gone */ } };
101
+
102
+ const onDown = (ev) => {
103
+ points.set(ev.pointerId, at(ev));
104
+ capture(ev.pointerId);
105
+ if (points.size === 2) {
106
+ beginPinch();
107
+ return;
108
+ }
109
+ if (points.size > 2) return;
110
+ const now = view();
111
+ drag = { id: ev.pointerId, from: at(ev), ox: now.x, oy: now.y, moved: false, button: ev.button };
112
+ };
113
+
114
+ const onMove = (ev) => {
115
+ if (!points.has(ev.pointerId)) return;
116
+ points.set(ev.pointerId, at(ev));
117
+ if (pinch && points.size >= 2) {
118
+ movePinch();
119
+ return;
120
+ }
121
+ if (!drag || ev.pointerId !== drag.id) return;
122
+ const p = at(ev);
123
+ const dx = p.x - drag.from.x;
124
+ const dy = p.y - drag.from.y;
125
+ if (!drag.moved && Math.abs(dx) + Math.abs(dy) < DRAG_SLOP) return;
126
+ if (!drag.moved) onCursor("grabbing");
127
+ drag.moved = true;
128
+ // The scene is laid out in CSS pixels and so is the pan: the pointer's
129
+ // travel goes in as it comes. Scaling it by the device pixel ratio makes
130
+ // the page slide at twice the cursor's speed on a HiDPI screen.
131
+ setView(drag.ox + dx, drag.oy + dy, view().sc);
132
+ };
133
+
134
+ const onUp = (ev) => {
135
+ const had = points.delete(ev.pointerId);
136
+ release(ev.pointerId);
137
+ if (!had) return;
138
+ onCursor("grab");
139
+ if (pinch) {
140
+ pinch = null;
141
+ // One finger left on the glass goes on panning from where it is,
142
+ // rather than the picture stopping until it is lifted and put down.
143
+ if (points.size === 1) {
144
+ const [id] = [...points.keys()];
145
+ const now = view();
146
+ drag = { id, from: points.get(id), ox: now.x, oy: now.y, moved: true, button: 0 };
147
+ }
148
+ return;
149
+ }
150
+ if (!drag || ev.pointerId !== drag.id) return;
151
+ const wasDrag = drag.moved;
152
+ const button = drag.button;
153
+ drag = null;
154
+ if (!wasDrag && button === 0) onTap(ev.clientX, ev.clientY);
155
+ };
156
+
157
+ const onWheel = (ev) => {
158
+ ev.preventDefault();
159
+ // Firefox reports wheel deltas in lines and Chrome in pixels: one notch
160
+ // of the same wheel arrives as 3 there and as 100 here. Counting a line
161
+ // as 33 pixels makes a notch a notch in both, which is the point — a
162
+ // line's real height would make Firefox scroll at half speed.
163
+ const perUnit = ev.deltaMode === 1 ? 33 : ev.deltaMode === 2 ? 400 : 1;
164
+ const dy = Math.max(-240, Math.min(240, ev.deltaY * perUnit));
165
+ // A trackpad pinch is a wheel with ctrl held, and it arrives a fraction
166
+ // of a notch at a time: on the wheel's own scale a pinch would crawl.
167
+ const rate = ev.ctrlKey ? 0.01 : 0.0015;
168
+ const p = at(ev);
169
+ zoomAbout(Math.exp(-dy * rate), p.x, p.y);
170
+ };
171
+
172
+ // Safari's trackpad pinch, which is not a wheel. Preventing the default is
173
+ // what keeps it from zooming the whole page instead of the canvas.
174
+ const onGestureStart = (ev) => { ev.preventDefault(); gestureScale = ev.scale || 1; };
175
+ const onGestureChange = (ev) => {
176
+ ev.preventDefault();
177
+ const s = ev.scale || 1;
178
+ if (!gestureScale) gestureScale = s;
179
+ const p = at(ev);
180
+ zoomAbout(s / gestureScale, p.x, p.y);
181
+ gestureScale = s;
182
+ };
183
+ const onGestureEnd = (ev) => { ev.preventDefault(); gestureScale = 1; };
184
+
185
+ el.addEventListener("pointerdown", onDown);
186
+ el.addEventListener("pointermove", onMove);
187
+ el.addEventListener("pointerup", onUp);
188
+ el.addEventListener("pointercancel", onUp);
189
+ el.addEventListener("wheel", onWheel, { passive: false });
190
+ el.addEventListener("gesturestart", onGestureStart);
191
+ el.addEventListener("gesturechange", onGestureChange);
192
+ el.addEventListener("gestureend", onGestureEnd);
193
+
194
+ return function detach() {
195
+ el.removeEventListener("pointerdown", onDown);
196
+ el.removeEventListener("pointermove", onMove);
197
+ el.removeEventListener("pointerup", onUp);
198
+ el.removeEventListener("pointercancel", onUp);
199
+ el.removeEventListener("wheel", onWheel);
200
+ el.removeEventListener("gesturestart", onGestureStart);
201
+ el.removeEventListener("gesturechange", onGestureChange);
202
+ el.removeEventListener("gestureend", onGestureEnd);
203
+ points.clear();
204
+ drag = null;
205
+ pinch = null;
206
+ };
207
+ }
208
+
209
+ export { DRAG_SLOP };
@@ -0,0 +1,167 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // The display list as the painter wants it, read straight off the Ranger
4
+ // object — no JSON in between.
5
+ //
6
+ // A host that shares a process with the app used to ask for `toJson()` and
7
+ // parse it back: a string of every coordinate on the page written and read
8
+ // on every frame, and a heap of garbage to collect after. That is the whole
9
+ // of what this file removes. It has to produce exactly what `toJson` would,
10
+ // key for key — `rt:scroll` holds the two against each other on every frame
11
+ // it draws — because the painter was written against the JSON and nothing in
12
+ // it should have to know which way the list arrived.
13
+ //
14
+ // Numbers come through as they are rather than rounded to the hundredth the
15
+ // JSON writer spells, which is the one difference: the painter gets what the
16
+ // layout computed, not its decimal form.
17
+
18
+ /** The commands of an `EVGDisplayList` in the JSON writer's shape. */
19
+ /** The camera the list carries, or null. Mirrors `viewOfBinary`. */
20
+ export function viewOf(dl) {
21
+ if (!dl || !dl.hasView) return null;
22
+ return { x: dl.viewX || 0, y: dl.viewY || 0, scale: dl.viewScale === undefined ? 1 : dl.viewScale };
23
+ }
24
+
25
+ export function cmdsOf(dl) {
26
+ const attribute = !!dl.attribute;
27
+ const out = new Array(dl.cmds.length);
28
+ for (let i = 0; i < dl.cmds.length; i += 1) {
29
+ const c = dl.cmds[i];
30
+ const o = { k: c.kind };
31
+ if (attribute) o.n = c.node;
32
+ if (c.layer > 0) o.layer = c.layer;
33
+ o.x = c.x;
34
+ o.y = c.y;
35
+ o.w = c.w;
36
+ o.h = c.h;
37
+ if (c.perCorner) o.rc = [c.radius, c.radiusTR, c.radiusBR, c.radiusBL];
38
+ if (c.radius > 0) o.r = c.radius;
39
+ if (c.thickness > 0) {
40
+ o.t = c.thickness;
41
+ // Guarded, not just compared: this reads the Ranger object off a
42
+ // BUILT bundle, and a bundle compiled before a field existed has no
43
+ // such property. `undefined !== 0` is true, so an unguarded test
44
+ // would hand the painter `cap: undefined` rather than no cap.
45
+ if (c.strokeCap) o.cap = c.strokeCap;
46
+ if (c.strokeJoin) o.join = c.strokeJoin;
47
+ if (c.strokeDash) {
48
+ o.dash = c.strokeDash;
49
+ if (c.strokeDashOffset) o.dashoff = c.strokeDashOffset;
50
+ }
51
+ }
52
+ o.c = [c.r, c.g, c.b, c.a];
53
+ if (c.hasGrad) {
54
+ o.gd = c.gradDir;
55
+ o.c2 = [c.r2, c.g2, c.b2, c.a2];
56
+ }
57
+ if (c.text.length > 0) {
58
+ o.text = c.text;
59
+ o.font = c.fontFamily;
60
+ o.size = c.fontSize;
61
+ if (c.fontWeight.length > 0) o.weight = c.fontWeight;
62
+ if (c.letterSpacing !== 0) o.ls = c.letterSpacing;
63
+ if (c.textAlign === "italic") o.italic = true;
64
+ }
65
+ if (c.src.length > 0) o.src = c.src;
66
+ if (c.flipH) o.fx = true;
67
+ if (c.flipV) o.fy = true;
68
+ // The crop window into the source: a picture is not always shown whole,
69
+ // and a reader that drops this draws the whole frame where a detail
70
+ // belongs.
71
+ if (c.hasCrop) o.cu = [c.cropX, c.cropY, c.cropX + c.cropW, c.cropY + c.cropH];
72
+ if (c.rotate !== 0) {
73
+ o.rot = c.rotate;
74
+ if (c.hasRotOrigin) {
75
+ o.rox = c.rotOriginX;
76
+ o.roy = c.rotOriginY;
77
+ }
78
+ }
79
+ if (c.backdropBlur > 0) o.bb = c.backdropBlur;
80
+ // The effect instance this rectangle carries, by name — the display list's
81
+ // `efx`. Guarded like the stroke fields above: a bundle compiled before
82
+ // the field existed has no such property.
83
+ if (c.effectId) o.efx = c.effectId;
84
+ // The shadow, which the JSON never carried and the binary now does
85
+ // (`evg-binary.js`): written here too so the two readers agree key for
86
+ // key, and so a painter that wants it has it whichever way the list came.
87
+ if (c.hasShadow) {
88
+ o.sh = { x: c.shadowX, y: c.shadowY, blur: c.shadowBlur, c: [c.shadowR, c.shadowG, c.shadowB, c.shadowA] };
89
+ }
90
+ if (c.pts.length > 0) {
91
+ o.pts = c.pts.slice();
92
+ o.ends = c.ringEnds.length === 0 ? [c.pts.length] : c.ringEnds.slice();
93
+ if (c.evenOdd) o.eo = 1;
94
+ }
95
+ out[i] = o;
96
+ }
97
+ return out;
98
+ }
99
+
100
+ /** The surface effect, or undefined — the JSON's `effect`. */
101
+ export function effectOf(dl) {
102
+ if (!dl.effectKind || dl.effectKind.length === 0) return undefined;
103
+ const drops = [];
104
+ for (let i = 0; i < dl.effectAges.length; i += 1) {
105
+ drops.push([dl.effectXs[i], dl.effectYs[i], dl.effectAges[i]]);
106
+ }
107
+ return {
108
+ kind: dl.effectKind,
109
+ drops,
110
+ speed: dl.effectSpeed,
111
+ width: dl.effectWidth,
112
+ strength: dl.effectStrength,
113
+ decay: dl.effectDecay,
114
+ highlight: dl.effectHighlight,
115
+ rings: dl.effectRings,
116
+ stagger: dl.effectStagger,
117
+ falloff: dl.effectFalloff,
118
+ shine: dl.effectShine,
119
+ gloss: dl.effectGloss,
120
+ bump: dl.effectBump,
121
+ light: [dl.effectLightX, dl.effectLightY, dl.effectLightZ],
122
+ };
123
+ }
124
+
125
+ /** The element-scoped effects — the JSON's `effects`. One per element that
126
+ * declared `evg-surface-effect`, with the box the layout gave it, the
127
+ * trigger the sheet wrote and whatever `evg-fx-*` parameters came with it.
128
+ * Undefined when the page declares none, which keeps a list that has no
129
+ * effects on it exactly the object it was. */
130
+ export function effectsOf(dl) {
131
+ const ids = dl.fxIds;
132
+ if (!ids || ids.length === 0) return undefined;
133
+ const out = new Array(ids.length);
134
+ for (let i = 0; i < ids.length; i += 1) {
135
+ const p = {};
136
+ const start = dl.fxParamStart[i];
137
+ for (let k = 0; k < dl.fxParamCount[i]; k += 1) p[dl.fxParamNames[start + k]] = dl.fxParamValues[start + k];
138
+ // Written in the order `toJson` writes them, so the two readers produce
139
+ // objects a person can diff as well as a comparison can.
140
+ const e = { id: ids[i], kind: dl.fxKinds[i] };
141
+ if (dl.fxTriggers[i]) e.on = dl.fxTriggers[i];
142
+ e.box = [dl.fxX[i], dl.fxY[i], dl.fxW[i], dl.fxH[i]];
143
+ if (dl.fxRadius[i] > 0) e.r = dl.fxRadius[i];
144
+ e.p = p;
145
+ out[i] = e;
146
+ }
147
+ return out;
148
+ }
149
+
150
+ /** How far each scroll layer's content has moved since the list was built —
151
+ * the JSON's `shifts`. Read on every frame: it is the one thing about a kept
152
+ * list that changes. */
153
+ export function shiftsOf(dl) {
154
+ const out = new Array(dl.layerShiftX.length);
155
+ for (let i = 0; i < dl.layerShiftX.length; i += 1) out[i] = [dl.layerShiftX[i], dl.layerShiftY[i]];
156
+ return out;
157
+ }
158
+
159
+ /** The whole list, as `JSON.parse(dl.toJson())` would give it. */
160
+ export function listOf(dl) {
161
+ const out = { seq: dl.buildSeq, shifts: shiftsOf(dl), cmds: cmdsOf(dl) };
162
+ const effect = effectOf(dl);
163
+ if (effect) out.effect = effect;
164
+ const effects = effectsOf(dl);
165
+ if (effects) out.effects = effects;
166
+ return out;
167
+ }
@@ -0,0 +1,186 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // The browser measures, EVG breaks the lines.
4
+ //
5
+ // PLAN_NATIVE_HOSTS.md S0, the web half. `EVGHostTextMeasurer` (Ranger) takes
6
+ // ONE function from the platform — width of a run, and a face's ascent,
7
+ // descent and line gap — and every layout in the compiled module then
8
+ // measures with it. This file is that function for a browser: a 2D canvas
9
+ // context and `measureText`, with the font shorthand built EXACTLY as the
10
+ // painters build theirs (`evg-webgl.js` and `evg-html.js` `fontSpec`), so the
11
+ // face the layout measured with is the face the atlas or the `<text>` node
12
+ // draws with. Before this, the layout measured with the advance table — one
13
+ // browser's sans, snapshotted once — and the painter drew with whatever this
14
+ // browser resolved, which is the gap S0 exists to close.
15
+ //
16
+ // It works in a Worker too (`OffscreenCanvas`), which is what S1 wants: the
17
+ // engine off the main thread, measuring where it lays out.
18
+ //
19
+ // WHAT IT DOES NOT DO. It does not decide which module to install into: a
20
+ // page that bundles several generated modules has several `EVGDefaultMeasurer`
21
+ // singletons — two copies of a class are two classes — and installs into
22
+ // each. `installCanvasMeasurer(modules)` takes the list.
23
+ //
24
+ // ONE FACT WORTH KNOWING. Canvas has no line gap. `fontBoundingBoxAscent` and
25
+ // `Descent` are the face's, but `line-height: normal` in CSS is ascent +
26
+ // descent + the face's lineGap, and the sans fallback's gap is real: 1.15em
27
+ // of line box against 1.117em of ascent + descent. So in a document the gap
28
+ // is measured off a hidden element with `line-height: normal`, once per face;
29
+ // in a worker there is no element, and the host says "unknown" (a negative
30
+ // number), which the Ranger side reads as zero.
31
+
32
+ const LRO = "\u202D", PDF = "\u202C";
33
+
34
+ /**
35
+ * The family a command names, stripped of the `-Bold` suffix
36
+ * `EVGElement.effectiveFontFamily` adds for a bold element. The suffix is a
37
+ * convention for the TTF measurers (`FontManager` reads `Family-Bold` as the
38
+ * bold face); a browser reads it as a family nobody has and falls to the
39
+ * stack. The weight arrives separately, and that is what the browser uses.
40
+ */
41
+ export function familyOf(name) {
42
+ const f = name || "";
43
+ return f.endsWith("-Bold") ? f.slice(0, -5) : f;
44
+ }
45
+
46
+ /**
47
+ * Make a measurer for one compiled module and attach the browser to it.
48
+ *
49
+ * @param {object} mod the generated module: `EVGHostTextMeasurer` and
50
+ * `EVGDefaultMeasurer` are read off it
51
+ * @param {object} [opts]
52
+ * @param {string[]} [opts.fallback] the families the browser may fall back
53
+ * to, in order — the same list handed to the painter's
54
+ * `setFontFallback`, so the two walks agree
55
+ * @param {boolean} [opts.install=true] make it the module's default
56
+ * @returns {{ measurer: object, refresh: () => void, detach: () => void }}
57
+ */
58
+ export function createCanvasMeasurer(mod, opts = {}) {
59
+ if (!mod || typeof mod.EVGHostTextMeasurer !== "function") {
60
+ throw new Error("evg-measure: the module has no EVGHostTextMeasurer — import EVGLayout.rgr (or EVGTextEngine.rgr) in the Ranger program");
61
+ }
62
+ const stackList = (opts.fallback || []).filter((f) => f && f.length).map((f) => `"${f}"`);
63
+ stackList.push("sans-serif");
64
+ const fallbackStack = stackList.join(", ");
65
+
66
+ const ctx = makeContext();
67
+ // Face metrics by font spec: the vertical numbers are a property of the
68
+ // face at a size, not of any string, so three answers serve every run.
69
+ const faces = new Map();
70
+ let currentFont = "";
71
+
72
+ const spec = (family, size, bold, italic) => {
73
+ const fam = family && family.length ? `"${family}", ${fallbackStack}` : fallbackStack;
74
+ return `${italic ? "italic " : ""}${bold ? "bold " : ""}${size}px ${fam}`;
75
+ };
76
+
77
+ const setFont = (s) => {
78
+ if (s !== currentFont) {
79
+ ctx.font = s;
80
+ currentFont = s;
81
+ }
82
+ };
83
+
84
+ const faceOf = (s, size) => {
85
+ let f = faces.get(s);
86
+ if (f) return f;
87
+ setFont(s);
88
+ // "Hg" rather than any run: the face's box is the same for every string.
89
+ const m = ctx.measureText("Hg");
90
+ const asc = m.fontBoundingBoxAscent || 0;
91
+ const desc = m.fontBoundingBoxDescent || 0;
92
+ f = { asc, desc, gap: normalGap(s, asc + desc, size) };
93
+ faces.set(s, f);
94
+ return f;
95
+ };
96
+
97
+ const metric = (kind, text, family, size, bold, italic) => {
98
+ const s = spec(family, size, bold, italic);
99
+ if (kind === 0) {
100
+ if (!text) return 0;
101
+ setFont(s);
102
+ // The same bidi override the painters apply: EVG has already ordered
103
+ // the runs, and a measurement that reorders inside one is a different
104
+ // width from the one that will be drawn.
105
+ return ctx.measureText(LRO + text + PDF).width;
106
+ }
107
+ const f = faceOf(s, size);
108
+ if (kind === 1) return f.asc;
109
+ if (kind === 2) return f.desc;
110
+ return f.gap;
111
+ };
112
+
113
+ const measurer = new mod.EVGHostTextMeasurer();
114
+ measurer.attach(metric, "canvas");
115
+ if (opts.install !== false) mod.EVGDefaultMeasurer.install(measurer);
116
+
117
+ return {
118
+ measurer,
119
+ /** A face finished loading (`document.fonts.ready`): forget what was
120
+ * measured with the fallback and move the key, so every engine holding
121
+ * this measurer measures again. */
122
+ refresh() {
123
+ faces.clear();
124
+ currentFont = "";
125
+ measurer.invalidate();
126
+ },
127
+ /** Back to the table. */
128
+ detach() {
129
+ measurer.detach();
130
+ if (opts.install !== false) mod.EVGDefaultMeasurer.uninstall();
131
+ },
132
+ };
133
+ }
134
+
135
+ /**
136
+ * The same, for every module a page bundles. Returns one handle whose
137
+ * `refresh` refreshes them all — a page has one `document.fonts.ready`.
138
+ */
139
+ export function installCanvasMeasurer(modules, opts = {}) {
140
+ const list = Array.isArray(modules) ? modules : [modules];
141
+ const made = list.map((m) => createCanvasMeasurer(m, opts));
142
+ return {
143
+ measurers: made.map((h) => h.measurer),
144
+ refresh() { for (const h of made) h.refresh(); },
145
+ detach() { for (const h of made) h.detach(); },
146
+ };
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+
151
+ function makeContext() {
152
+ if (typeof document !== "undefined" && document.createElement) {
153
+ const canvas = document.createElement("canvas");
154
+ canvas.width = 8;
155
+ canvas.height = 8;
156
+ const ctx = canvas.getContext("2d");
157
+ if (ctx) return ctx;
158
+ }
159
+ if (typeof OffscreenCanvas !== "undefined") {
160
+ const ctx = new OffscreenCanvas(8, 8).getContext("2d");
161
+ if (ctx) return ctx;
162
+ }
163
+ throw new Error("evg-measure: no 2D canvas context available here");
164
+ }
165
+
166
+ // The line gap, from the one place a browser states it: the height of a
167
+ // line box at `line-height: normal`. Measured at the face's own size — the
168
+ // gap scales with the em, and rounding at a small size is what it is.
169
+ let probe = null;
170
+ function normalGap(fontSpec, ascPlusDesc, size) {
171
+ if (typeof document === "undefined" || !document.body) return -1;
172
+ if (!probe) {
173
+ probe = document.createElement("div");
174
+ probe.setAttribute("aria-hidden", "true");
175
+ probe.style.cssText = "position:absolute;left:-10000px;top:0;visibility:hidden;white-space:nowrap;line-height:normal;padding:0;border:0;margin:0;pointer-events:none";
176
+ probe.textContent = "Hg";
177
+ document.body.appendChild(probe);
178
+ }
179
+ probe.style.font = fontSpec;
180
+ const h = probe.getBoundingClientRect().height;
181
+ if (!(h > 0) || !(ascPlusDesc > 0)) return -1;
182
+ const gap = h - ascPlusDesc;
183
+ // A negative gap is a rounding artefact of a box measured in whole pixels
184
+ // against metrics measured in fractions; a face has no negative gap.
185
+ return gap > 0 ? gap : 0;
186
+ }