@jbrowse/render-core 5.0.0-beta.1 → 5.0.0-beta.11

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 (353) hide show
  1. package/esm/RenderCanvas.d.ts +3 -3
  2. package/esm/RenderCanvas.js +1 -1
  3. package/esm/RenderLifecycleMixin.d.ts +52 -20
  4. package/esm/RenderLifecycleMixin.js +89 -23
  5. package/esm/canvas2dUtils.d.ts +90 -23
  6. package/esm/canvas2dUtils.js +120 -37
  7. package/esm/colorRampLut.d.ts +5 -5
  8. package/esm/colorRampLut.js +6 -6
  9. package/esm/computePipeline.d.ts +14 -0
  10. package/esm/computePipeline.js +62 -0
  11. package/esm/contractReports.d.ts +54 -0
  12. package/esm/contractReports.js +112 -0
  13. package/esm/coverageBand.d.ts +13 -8
  14. package/esm/coverageBand.js +15 -5
  15. package/esm/coverageBandBuffers.d.ts +2 -9
  16. package/esm/coverageBandBuffers.js +1 -13
  17. package/esm/createRenderingBackend.d.ts +16 -20
  18. package/esm/createRenderingBackend.js +6 -4
  19. package/esm/displayPhase.d.ts +83 -104
  20. package/esm/displayPhase.js +42 -82
  21. package/esm/encodeMemo.d.ts +14 -0
  22. package/esm/encodeMemo.js +48 -0
  23. package/esm/gpuContextLostError.d.ts +30 -0
  24. package/esm/gpuContextLostError.js +40 -0
  25. package/esm/gpuDevice.d.ts +29 -10
  26. package/esm/gpuDevice.js +35 -16
  27. package/esm/graphicsCapabilities.d.ts +27 -19
  28. package/esm/graphicsCapabilities.js +41 -19
  29. package/esm/hal/createHal.d.ts +4 -5
  30. package/esm/hal/createHal.js +15 -5
  31. package/esm/hal/deviceGpuCache.d.ts +25 -30
  32. package/esm/hal/deviceGpuCache.js +85 -97
  33. package/esm/hal/gpuHalBase.d.ts +5 -4
  34. package/esm/hal/gpuHalBase.js +25 -14
  35. package/esm/hal/index.d.ts +2 -2
  36. package/esm/hal/index.js +1 -1
  37. package/esm/hal/mockHal.d.ts +15 -10
  38. package/esm/hal/mockHal.js +31 -18
  39. package/esm/hal/oomReporter.d.ts +7 -0
  40. package/esm/hal/oomReporter.js +10 -0
  41. package/esm/hal/passIds.d.ts +8 -7
  42. package/esm/hal/passIds.js +11 -10
  43. package/esm/hal/regionRegistry.d.ts +0 -1
  44. package/esm/hal/regionRegistry.js +3 -15
  45. package/esm/hal/types.d.ts +53 -18
  46. package/esm/hal/webgl2Hal.d.ts +23 -6
  47. package/esm/hal/webgl2Hal.js +158 -155
  48. package/esm/hal/webgpuHal.d.ts +29 -13
  49. package/esm/hal/webgpuHal.js +220 -153
  50. package/esm/installUpload.d.ts +16 -3
  51. package/esm/installUpload.js +13 -41
  52. package/esm/instanceCache.d.ts +10 -7
  53. package/esm/instanceCache.js +14 -15
  54. package/esm/instancePass.d.ts +2 -1
  55. package/esm/mapUploadSync.d.ts +2 -10
  56. package/esm/mapUploadSync.js +2 -3
  57. package/esm/marks/barMark.d.ts +30 -0
  58. package/esm/marks/barMark.js +149 -0
  59. package/esm/marks/colorFill.d.ts +17 -0
  60. package/esm/marks/colorFill.js +26 -0
  61. package/esm/marks/drawAgainstHit.d.ts +113 -0
  62. package/esm/marks/drawAgainstHit.js +407 -0
  63. package/esm/marks/ellipseDistance.d.ts +10 -0
  64. package/esm/marks/ellipseDistance.js +136 -0
  65. package/esm/marks/glyphPaint.d.ts +36 -0
  66. package/esm/marks/glyphPaint.js +77 -0
  67. package/esm/marks/index.d.ts +33 -0
  68. package/esm/marks/index.js +20 -0
  69. package/esm/marks/lineMark.d.ts +37 -0
  70. package/esm/marks/lineMark.js +416 -0
  71. package/esm/marks/linkMark.d.ts +76 -0
  72. package/esm/marks/linkMark.js +585 -0
  73. package/esm/marks/markBackend.d.ts +118 -0
  74. package/esm/marks/markBackend.js +196 -0
  75. package/esm/marks/markHit.d.ts +32 -0
  76. package/esm/marks/markHit.js +61 -0
  77. package/esm/marks/markInk.d.ts +18 -0
  78. package/esm/marks/markInk.js +71 -0
  79. package/esm/marks/markPaint.d.ts +14 -0
  80. package/esm/marks/markPaint.js +32 -0
  81. package/esm/marks/markPlan.d.ts +25 -0
  82. package/esm/marks/markPlan.js +20 -0
  83. package/esm/marks/markRamp.d.ts +74 -0
  84. package/esm/marks/markRamp.js +205 -0
  85. package/esm/marks/nearestMarkHit.d.ts +55 -0
  86. package/esm/marks/nearestMarkHit.js +158 -0
  87. package/esm/marks/pathRecorder.d.ts +14 -0
  88. package/esm/marks/pathRecorder.js +80 -0
  89. package/esm/marks/pointMark.d.ts +42 -0
  90. package/esm/marks/pointMark.js +125 -0
  91. package/esm/marks/rowLane.d.ts +39 -0
  92. package/esm/marks/rowLane.js +41 -0
  93. package/esm/marks/rowSpanIndex.d.ts +30 -0
  94. package/esm/marks/rowSpanIndex.js +151 -0
  95. package/esm/marks/rowTable.d.ts +41 -0
  96. package/esm/marks/rowTable.js +75 -0
  97. package/esm/marks/ruleMark.d.ts +21 -0
  98. package/esm/marks/ruleMark.js +91 -0
  99. package/esm/marks/spanMark.d.ts +42 -0
  100. package/esm/marks/spanMark.js +81 -0
  101. package/esm/marks/types.d.ts +261 -0
  102. package/esm/marks/types.js +175 -0
  103. package/esm/marks/valueScale.d.ts +23 -0
  104. package/esm/marks/valueScale.js +11 -0
  105. package/esm/namedReactions.d.ts +3 -4
  106. package/esm/namedReactions.js +3 -4
  107. package/esm/perRegionRenderingBackend.d.ts +21 -3
  108. package/esm/perRegionRenderingBackend.js +37 -3
  109. package/esm/regionDataMap.d.ts +2 -2
  110. package/esm/regionDataMap.js +25 -23
  111. package/esm/renderBlock.d.ts +14 -0
  112. package/esm/renderBlock.js +25 -0
  113. package/esm/renderingBackendBase.d.ts +20 -14
  114. package/esm/renderingBackendBase.js +12 -7
  115. package/esm/scoreScale.d.ts +27 -0
  116. package/esm/scoreScale.js +88 -0
  117. package/esm/shaders/barMark.generated.d.ts +3 -0
  118. package/esm/shaders/barMark.generated.js +7 -0
  119. package/esm/shaders/barMark.glsl.generated.d.ts +2 -0
  120. package/esm/shaders/barMark.glsl.generated.js +4 -0
  121. package/esm/shaders/barMark.iface.generated.d.ts +90 -0
  122. package/esm/shaders/barMark.iface.generated.js +205 -0
  123. package/esm/shaders/barMark.wgsl.generated.d.ts +1 -0
  124. package/esm/shaders/barMark.wgsl.generated.js +3 -0
  125. package/esm/shaders/capsule.js.generated.d.ts +1 -0
  126. package/esm/shaders/capsule.js.generated.js +11 -0
  127. package/esm/shaders/clipStrip.generated.d.ts +2 -0
  128. package/esm/shaders/clipStrip.generated.js +4 -0
  129. package/esm/shaders/clipStrip.js.generated.d.ts +1 -0
  130. package/esm/shaders/clipStrip.js.generated.js +21 -0
  131. package/esm/shaders/colorRampLut.js.generated.d.ts +1 -0
  132. package/esm/shaders/colorRampLut.js.generated.js +17 -0
  133. package/esm/shaders/coverageBar.generated.d.ts +2 -3
  134. package/esm/shaders/coverageBar.generated.js +4 -3
  135. package/esm/shaders/coverageBar.glsl.generated.d.ts +2 -0
  136. package/esm/shaders/coverageBar.glsl.generated.js +4 -0
  137. package/esm/shaders/coverageBar.iface.generated.js +1 -1
  138. package/esm/shaders/coverageBar.wgsl.generated.d.ts +1 -0
  139. package/esm/shaders/coverageBar.wgsl.generated.js +3 -0
  140. package/esm/shaders/coverageClip.generated.d.ts +3 -0
  141. package/esm/shaders/coverageClip.generated.js +7 -0
  142. package/esm/shaders/coverageClip.glsl.generated.d.ts +2 -0
  143. package/esm/shaders/coverageClip.glsl.generated.js +4 -0
  144. package/esm/shaders/coverageClip.iface.generated.d.ts +85 -0
  145. package/esm/shaders/coverageClip.iface.generated.js +114 -0
  146. package/esm/shaders/coverageClip.wgsl.generated.d.ts +1 -0
  147. package/esm/shaders/coverageClip.wgsl.generated.js +3 -0
  148. package/esm/shaders/coverageIndicator.generated.d.ts +2 -3
  149. package/esm/shaders/coverageIndicator.generated.js +4 -3
  150. package/esm/shaders/coverageIndicator.glsl.generated.d.ts +2 -0
  151. package/esm/shaders/coverageIndicator.glsl.generated.js +4 -0
  152. package/esm/shaders/coverageIndicator.iface.generated.js +1 -1
  153. package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +1 -0
  154. package/esm/shaders/coverageIndicator.wgsl.generated.js +3 -0
  155. package/esm/shaders/coverageInterbase.generated.d.ts +2 -3
  156. package/esm/shaders/coverageInterbase.generated.js +4 -3
  157. package/esm/shaders/coverageInterbase.glsl.generated.d.ts +2 -0
  158. package/esm/shaders/coverageInterbase.glsl.generated.js +4 -0
  159. package/esm/shaders/coverageInterbase.iface.generated.js +1 -1
  160. package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -0
  161. package/esm/shaders/coverageInterbase.wgsl.generated.js +3 -0
  162. package/esm/shaders/coverageMod.generated.d.ts +2 -3
  163. package/esm/shaders/coverageMod.generated.js +4 -3
  164. package/esm/shaders/coverageMod.glsl.generated.d.ts +2 -0
  165. package/esm/shaders/coverageMod.glsl.generated.js +4 -0
  166. package/esm/shaders/coverageMod.iface.generated.js +1 -1
  167. package/esm/shaders/coverageMod.wgsl.generated.d.ts +1 -0
  168. package/esm/shaders/coverageMod.wgsl.generated.js +3 -0
  169. package/esm/shaders/coverageSnp.generated.d.ts +2 -3
  170. package/esm/shaders/coverageSnp.generated.js +4 -3
  171. package/esm/shaders/coverageSnp.glsl.generated.d.ts +2 -0
  172. package/esm/shaders/coverageSnp.glsl.generated.js +4 -0
  173. package/esm/shaders/coverageSnp.iface.generated.js +1 -1
  174. package/esm/shaders/coverageSnp.wgsl.generated.d.ts +1 -0
  175. package/esm/shaders/coverageSnp.wgsl.generated.js +3 -0
  176. package/esm/shaders/curveDistance.js.generated.d.ts +5 -0
  177. package/esm/shaders/curveDistance.js.generated.js +87 -0
  178. package/esm/shaders/hpmath.js.generated.d.ts +2 -1
  179. package/esm/shaders/hpmath.js.generated.js +5 -2
  180. package/esm/shaders/lineCenterMark.generated.d.ts +3 -0
  181. package/esm/shaders/lineCenterMark.generated.js +7 -0
  182. package/esm/shaders/lineCenterMark.glsl.generated.d.ts +2 -0
  183. package/esm/shaders/lineCenterMark.glsl.generated.js +4 -0
  184. package/esm/shaders/lineCenterMark.iface.generated.d.ts +104 -0
  185. package/esm/shaders/lineCenterMark.iface.generated.js +237 -0
  186. package/esm/shaders/lineCenterMark.wgsl.generated.d.ts +1 -0
  187. package/esm/shaders/lineCenterMark.wgsl.generated.js +3 -0
  188. package/esm/shaders/lineCommon.generated.d.ts +2 -0
  189. package/esm/shaders/lineCommon.generated.js +4 -0
  190. package/esm/shaders/lineStepMark.generated.d.ts +3 -0
  191. package/esm/shaders/lineStepMark.generated.js +7 -0
  192. package/esm/shaders/lineStepMark.glsl.generated.d.ts +2 -0
  193. package/esm/shaders/lineStepMark.glsl.generated.js +4 -0
  194. package/esm/shaders/lineStepMark.iface.generated.d.ts +98 -0
  195. package/esm/shaders/lineStepMark.iface.generated.js +225 -0
  196. package/esm/shaders/lineStepMark.wgsl.generated.d.ts +1 -0
  197. package/esm/shaders/lineStepMark.wgsl.generated.js +3 -0
  198. package/esm/shaders/linkMark.consts.generated.d.ts +14 -0
  199. package/esm/shaders/linkMark.consts.generated.js +16 -0
  200. package/esm/shaders/linkMark.generated.d.ts +4 -0
  201. package/esm/shaders/linkMark.generated.js +8 -0
  202. package/esm/shaders/linkMark.glsl.generated.d.ts +2 -0
  203. package/esm/shaders/linkMark.glsl.generated.js +4 -0
  204. package/esm/shaders/linkMark.iface.generated.d.ts +142 -0
  205. package/esm/shaders/linkMark.iface.generated.js +1829 -0
  206. package/esm/shaders/linkMark.js.generated.d.ts +7 -0
  207. package/esm/shaders/linkMark.js.generated.js +159 -0
  208. package/esm/shaders/linkMark.wgsl.generated.d.ts +1 -0
  209. package/esm/shaders/linkMark.wgsl.generated.js +3 -0
  210. package/esm/shaders/markColor.generated.d.ts +9 -0
  211. package/esm/shaders/markColor.generated.js +11 -0
  212. package/esm/shaders/pointGlyph.js.generated.d.ts +2 -0
  213. package/esm/shaders/pointGlyph.js.generated.js +15 -0
  214. package/esm/shaders/pointMark.consts.generated.d.ts +7 -0
  215. package/esm/shaders/pointMark.consts.generated.js +9 -0
  216. package/esm/shaders/pointMark.generated.d.ts +4 -0
  217. package/esm/shaders/pointMark.generated.js +8 -0
  218. package/esm/shaders/pointMark.glsl.generated.d.ts +2 -0
  219. package/esm/shaders/pointMark.glsl.generated.js +4 -0
  220. package/esm/shaders/pointMark.iface.generated.d.ts +98 -0
  221. package/esm/shaders/pointMark.iface.generated.js +219 -0
  222. package/esm/shaders/pointMark.js.generated.d.ts +3 -0
  223. package/esm/shaders/pointMark.js.generated.js +93 -0
  224. package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -0
  225. package/esm/shaders/pointMark.wgsl.generated.js +3 -0
  226. package/esm/shaders/rowRect.generated.d.ts +0 -1
  227. package/esm/shaders/rowRect.generated.js +0 -1
  228. package/esm/shaders/rowTable.generated.d.ts +1 -0
  229. package/esm/shaders/rowTable.generated.js +3 -0
  230. package/esm/shaders/rowTable.js.generated.d.ts +5 -0
  231. package/esm/shaders/rowTable.js.generated.js +29 -0
  232. package/esm/shaders/scoreScale.generated.d.ts +2 -0
  233. package/esm/shaders/scoreScale.generated.js +4 -0
  234. package/esm/shaders/scoreScale.js.generated.d.ts +1 -0
  235. package/esm/shaders/scoreScale.js.generated.js +50 -23
  236. package/esm/shaders/spanMark.generated.d.ts +3 -0
  237. package/esm/shaders/spanMark.generated.js +7 -0
  238. package/esm/shaders/spanMark.glsl.generated.d.ts +2 -0
  239. package/esm/shaders/spanMark.glsl.generated.js +4 -0
  240. package/esm/shaders/spanMark.iface.generated.d.ts +81 -0
  241. package/esm/shaders/spanMark.iface.generated.js +215 -0
  242. package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -0
  243. package/esm/shaders/spanMark.wgsl.generated.js +3 -0
  244. package/esm/sharedBackendKey.d.ts +18 -0
  245. package/esm/{keyedRenderingBackend.js → sharedBackendKey.js} +5 -0
  246. package/esm/slangPass.d.ts +17 -11
  247. package/esm/slangPass.js +12 -4
  248. package/esm/useGraphicsCapabilities.d.ts +1 -1
  249. package/esm/useGraphicsCapabilities.js +1 -1
  250. package/esm/useRenderingBackend.d.ts +13 -13
  251. package/esm/useRenderingBackend.js +58 -20
  252. package/esm/webgpuUtils.d.ts +3 -11
  253. package/esm/webgpuUtils.js +12 -33
  254. package/package.json +32 -7
  255. package/src/shaders/antialias.slang +46 -0
  256. package/src/shaders/arcHull.test.ts +267 -0
  257. package/src/shaders/barMark.generated.ts +11 -0
  258. package/src/shaders/barMark.glsl.generated.ts +6 -0
  259. package/src/shaders/barMark.iface.generated.ts +285 -0
  260. package/src/shaders/barMark.slang +167 -0
  261. package/src/shaders/barMark.wgsl.generated.ts +4 -0
  262. package/src/shaders/capsule.js.generated.ts +14 -0
  263. package/src/shaders/capsule.slang +13 -5
  264. package/src/shaders/clipStrip.generated.ts +6 -0
  265. package/src/shaders/clipStrip.js.generated.ts +22 -0
  266. package/src/shaders/clipStrip.slang +34 -0
  267. package/src/shaders/clipStrip.test.ts +39 -0
  268. package/src/shaders/colorPack.slang +5 -1
  269. package/src/shaders/colorRampLut.js.generated.ts +22 -0
  270. package/src/shaders/colorRampLut.slang +15 -1
  271. package/src/shaders/coverageBand.slang +64 -32
  272. package/src/shaders/coverageBar.generated.ts +6 -5
  273. package/src/shaders/coverageBar.glsl.generated.ts +6 -0
  274. package/src/shaders/coverageBar.iface.generated.ts +1 -1
  275. package/src/shaders/coverageBar.slang +10 -10
  276. package/src/shaders/coverageBar.wgsl.generated.ts +4 -0
  277. package/src/shaders/coverageClip.generated.ts +11 -0
  278. package/src/shaders/coverageClip.glsl.generated.ts +6 -0
  279. package/src/shaders/coverageClip.iface.generated.ts +172 -0
  280. package/src/shaders/coverageClip.slang +38 -0
  281. package/src/shaders/coverageClip.wgsl.generated.ts +4 -0
  282. package/src/shaders/coverageIndicator.generated.ts +6 -5
  283. package/src/shaders/coverageIndicator.glsl.generated.ts +6 -0
  284. package/src/shaders/coverageIndicator.iface.generated.ts +1 -1
  285. package/src/shaders/coverageIndicator.wgsl.generated.ts +4 -0
  286. package/src/shaders/coverageInterbase.generated.ts +6 -5
  287. package/src/shaders/coverageInterbase.glsl.generated.ts +6 -0
  288. package/src/shaders/coverageInterbase.iface.generated.ts +1 -1
  289. package/src/shaders/coverageInterbase.wgsl.generated.ts +4 -0
  290. package/src/shaders/coverageMod.generated.ts +6 -5
  291. package/src/shaders/coverageMod.glsl.generated.ts +6 -0
  292. package/src/shaders/coverageMod.iface.generated.ts +1 -1
  293. package/src/shaders/coverageMod.wgsl.generated.ts +4 -0
  294. package/src/shaders/coverageSnp.generated.ts +6 -5
  295. package/src/shaders/coverageSnp.glsl.generated.ts +6 -0
  296. package/src/shaders/coverageSnp.iface.generated.ts +1 -1
  297. package/src/shaders/coverageSnp.wgsl.generated.ts +4 -0
  298. package/src/shaders/curveDistance.js.generated.ts +91 -0
  299. package/src/shaders/curveDistance.slang +265 -0
  300. package/src/shaders/hpClampFloor.test.ts +87 -0
  301. package/src/shaders/hpmath.js.generated.ts +6 -2
  302. package/src/shaders/hpmath.slang +100 -48
  303. package/src/shaders/insetValueScale.test.ts +67 -0
  304. package/src/shaders/lineCenterMark.generated.ts +11 -0
  305. package/src/shaders/lineCenterMark.glsl.generated.ts +6 -0
  306. package/src/shaders/lineCenterMark.iface.generated.ts +328 -0
  307. package/src/shaders/lineCenterMark.slang +127 -0
  308. package/src/shaders/lineCenterMark.wgsl.generated.ts +4 -0
  309. package/src/shaders/lineCommon.generated.ts +6 -0
  310. package/src/shaders/lineCommon.slang +102 -0
  311. package/src/shaders/lineStepMark.generated.ts +11 -0
  312. package/src/shaders/lineStepMark.glsl.generated.ts +6 -0
  313. package/src/shaders/lineStepMark.iface.generated.ts +311 -0
  314. package/src/shaders/lineStepMark.slang +125 -0
  315. package/src/shaders/lineStepMark.wgsl.generated.ts +4 -0
  316. package/src/shaders/linkMark.consts.generated.ts +30 -0
  317. package/src/shaders/linkMark.generated.ts +12 -0
  318. package/src/shaders/linkMark.glsl.generated.ts +6 -0
  319. package/src/shaders/linkMark.iface.generated.ts +1954 -0
  320. package/src/shaders/linkMark.js.generated.ts +167 -0
  321. package/src/shaders/linkMark.slang +439 -0
  322. package/src/shaders/linkMark.wgsl.generated.ts +4 -0
  323. package/src/shaders/markColor.generated.ts +20 -0
  324. package/src/shaders/markColor.slang +128 -0
  325. package/src/shaders/pointGlyph.js.generated.ts +20 -0
  326. package/src/shaders/pointGlyph.slang +23 -7
  327. package/src/shaders/pointGlyphPad.test.ts +242 -0
  328. package/src/shaders/pointMark.consts.generated.ts +16 -0
  329. package/src/shaders/pointMark.generated.ts +12 -0
  330. package/src/shaders/pointMark.glsl.generated.ts +6 -0
  331. package/src/shaders/pointMark.iface.generated.ts +304 -0
  332. package/src/shaders/pointMark.js.generated.ts +99 -0
  333. package/src/shaders/pointMark.slang +227 -0
  334. package/src/shaders/pointMark.wgsl.generated.ts +4 -0
  335. package/src/shaders/rowRect.generated.ts +0 -2
  336. package/src/shaders/rowRect.slang +39 -58
  337. package/src/shaders/rowTable.generated.ts +4 -0
  338. package/src/shaders/rowTable.js.generated.ts +37 -0
  339. package/src/shaders/rowTable.slang +98 -0
  340. package/src/shaders/scoreScale.generated.ts +6 -0
  341. package/src/shaders/scoreScale.js.generated.ts +48 -23
  342. package/src/shaders/scoreScale.slang +49 -55
  343. package/src/shaders/sdEllipse.test.ts +211 -0
  344. package/src/shaders/spanMark.generated.ts +11 -0
  345. package/src/shaders/spanMark.glsl.generated.ts +6 -0
  346. package/src/shaders/spanMark.iface.generated.ts +289 -0
  347. package/src/shaders/spanMark.slang +77 -0
  348. package/src/shaders/spanMark.wgsl.generated.ts +4 -0
  349. package/src/shaders/valueScale.slang +26 -0
  350. package/src/shaders/wideCircleLeg.test.ts +85 -0
  351. package/esm/globalRenderingBackend.d.ts +0 -90
  352. package/esm/globalRenderingBackend.js +0 -53
  353. package/esm/keyedRenderingBackend.d.ts +0 -41
@@ -0,0 +1,407 @@
1
+ import { inkOnRect } from './markHit.js';
2
+ const IDENTITY = [1, 0, 0, 1, 0, 0];
3
+ /**
4
+ * A `MarkContext2D` that records every rect a painter fills, in paint order and
5
+ * in CANVAS coordinates — `fillRect` and path `rect` alike, so a shape that
6
+ * batches by colour records the same list as one that fills per instance. A
7
+ * path traced with `moveTo`/`lineTo`/`bezierCurveTo` and filled records its
8
+ * bounding box, the extent a shape whose hit test answers a polygon's box
9
+ * (the variant inversion triangle) is checked against; a curve contributes its control
10
+ * points, whose hull contains it.
11
+ *
12
+ * A **stroked** path records one rect per `moveTo`/`lineTo` edge, each the
13
+ * edge's bounding box widened by half the current `lineWidth` — the extent a
14
+ * round cap reaches on all four sides. Per edge rather than per `stroke()`,
15
+ * because a painter that batches a colour run into one path (dotplot's) would
16
+ * otherwise record the run's hull as a single instance. `fillStyle` on such a
17
+ * record is the `strokeStyle` it went down in; the field is the colour of the
18
+ * ink either way.
19
+ *
20
+ * `save`/`restore` bracket the transform and the styles the way a real context
21
+ * does, and every recorded point goes through the CTM. The half-widths — a
22
+ * stroke's and a `strokeRect`'s — are measured AFTER it, so they are exact under
23
+ * a translation and approximate under a scale; no shape in tree strokes under
24
+ * one, and a rotated CTM records the ink's axis-aligned box rather than its
25
+ * silhouette.
26
+ */
27
+ const CURVE_SEGMENTS = 64;
28
+ export function recordingContext() {
29
+ const calls = [];
30
+ const points = [];
31
+ const edges = [];
32
+ let m = [...IDENTITY];
33
+ const stack = [];
34
+ const px = (x, y) => m[0] * x + m[2] * y + m[4];
35
+ const py = (x, y) => m[1] * x + m[3] * y + m[5];
36
+ const pushPoint = (x, y) => {
37
+ points.push([px(x, y), py(x, y)]);
38
+ };
39
+ // The transformed box of an axis-aligned rect. An axis-aligned CTM scales the
40
+ // extents directly rather than differencing two mapped corners, so an
41
+ // untranslated rect records the width the painter asked for and not a value
42
+ // one ulp off it; only a rotation falls back to the four-corner hull.
43
+ const boxOf = (x, y, w, h) => {
44
+ if (m[1] === 0 && m[2] === 0) {
45
+ const x0 = px(x, y);
46
+ const y0 = py(x, y);
47
+ const dx = m[0] * w;
48
+ const dy = m[3] * h;
49
+ return {
50
+ x: Math.min(x0, x0 + dx),
51
+ y: Math.min(y0, y0 + dy),
52
+ w: Math.abs(dx),
53
+ h: Math.abs(dy),
54
+ };
55
+ }
56
+ const xs = [px(x, y), px(x + w, y), px(x, y + h), px(x + w, y + h)];
57
+ const ys = [py(x, y), py(x + w, y), py(x, y + h), py(x + w, y + h)];
58
+ const left = Math.min(...xs);
59
+ const top = Math.min(...ys);
60
+ return {
61
+ x: left,
62
+ y: top,
63
+ w: Math.max(...xs) - left,
64
+ h: Math.max(...ys) - top,
65
+ };
66
+ };
67
+ const ctx = {
68
+ fillStyle: '',
69
+ strokeStyle: '',
70
+ lineWidth: 1,
71
+ lineCap: 'butt',
72
+ lineJoin: 'miter',
73
+ save() {
74
+ const { fillStyle, strokeStyle, lineWidth, lineCap } = this;
75
+ stack.push({ m: [...m], fillStyle, strokeStyle, lineWidth, lineCap });
76
+ },
77
+ restore() {
78
+ const saved = stack.pop();
79
+ if (saved) {
80
+ m = saved.m;
81
+ this.fillStyle = saved.fillStyle;
82
+ this.strokeStyle = saved.strokeStyle;
83
+ this.lineWidth = saved.lineWidth;
84
+ this.lineCap = saved.lineCap;
85
+ }
86
+ },
87
+ translate(x, y) {
88
+ m = [m[0], m[1], m[2], m[3], px(x, y), py(x, y)];
89
+ },
90
+ scale(x, y) {
91
+ m = [m[0] * x, m[1] * x, m[2] * y, m[3] * y, m[4], m[5]];
92
+ },
93
+ rotate(angle) {
94
+ const c = Math.cos(angle);
95
+ const s = Math.sin(angle);
96
+ m = [
97
+ m[0] * c + m[2] * s,
98
+ m[1] * c + m[3] * s,
99
+ m[2] * c - m[0] * s,
100
+ m[3] * c - m[1] * s,
101
+ m[4],
102
+ m[5],
103
+ ];
104
+ },
105
+ beginPath() {
106
+ points.length = 0;
107
+ edges.length = 0;
108
+ },
109
+ clip() { },
110
+ setLineDash(_segments) { },
111
+ moveTo(x, y) {
112
+ pushPoint(x, y);
113
+ },
114
+ lineTo(x, y) {
115
+ const from = points.at(-1);
116
+ pushPoint(x, y);
117
+ const to = points.at(-1);
118
+ if (from) {
119
+ edges.push([from[0], from[1], to[0], to[1]]);
120
+ }
121
+ },
122
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
123
+ pushPoint(cp1x, cp1y);
124
+ pushPoint(cp2x, cp2y);
125
+ pushPoint(x, y);
126
+ },
127
+ arc(x, y, radius, start = 0, end = 2 * Math.PI) {
128
+ this.ellipse(x, y, radius, radius, 0, start, end);
129
+ },
130
+ // Flattened into edges, so a stroked curve records the boxes its
131
+ // segments cover the way a polyline does; a filled one still records its
132
+ // point hull.
133
+ ellipse(x, y, radiusX, radiusY, _rotation = 0, start = 0, end = 2 * Math.PI) {
134
+ for (let k = 0; k <= CURVE_SEGMENTS; k++) {
135
+ const a = start + ((end - start) * k) / CURVE_SEGMENTS;
136
+ this.lineTo(x + radiusX * Math.cos(a), y + radiusY * Math.sin(a));
137
+ }
138
+ },
139
+ closePath() { },
140
+ fill() {
141
+ if (points.length > 0) {
142
+ const xs = points.map(p => p[0]);
143
+ const ys = points.map(p => p[1]);
144
+ const x = Math.min(...xs);
145
+ const y = Math.min(...ys);
146
+ calls.push({
147
+ x,
148
+ y,
149
+ w: Math.max(...xs) - x,
150
+ h: Math.max(...ys) - y,
151
+ fillStyle: this.fillStyle,
152
+ });
153
+ points.length = 0;
154
+ edges.length = 0;
155
+ }
156
+ },
157
+ stroke() {
158
+ const half = this.lineWidth / 2;
159
+ for (const [ax, ay, bx, by] of edges) {
160
+ const x = Math.min(ax, bx) - half;
161
+ const y = Math.min(ay, by) - half;
162
+ calls.push({
163
+ x,
164
+ y,
165
+ w: Math.max(ax, bx) + half - x,
166
+ h: Math.max(ay, by) + half - y,
167
+ fillStyle: this.strokeStyle,
168
+ });
169
+ }
170
+ points.length = 0;
171
+ edges.length = 0;
172
+ },
173
+ rect(x, y, w, h) {
174
+ calls.push({ ...boxOf(x, y, w, h), fillStyle: this.fillStyle });
175
+ },
176
+ fillRect(x, y, w, h) {
177
+ calls.push({ ...boxOf(x, y, w, h), fillStyle: this.fillStyle });
178
+ },
179
+ strokeRect(x, y, w, h) {
180
+ const half = this.lineWidth / 2;
181
+ const box = boxOf(x, y, w, h);
182
+ calls.push({
183
+ x: box.x - half,
184
+ y: box.y - half,
185
+ w: box.w + this.lineWidth,
186
+ h: box.h + this.lineWidth,
187
+ fillStyle: this.strokeStyle,
188
+ });
189
+ },
190
+ };
191
+ return { ctx, calls };
192
+ }
193
+ function contains(r, x, y, slack = 0) {
194
+ return (x >= r.x - slack &&
195
+ x <= r.x + r.w + slack &&
196
+ y >= r.y - slack &&
197
+ y <= r.y + r.h + slack);
198
+ }
199
+ function unionBox(rs) {
200
+ let box;
201
+ for (const r of rs) {
202
+ box = box
203
+ ? {
204
+ x: Math.min(box.x, r.x),
205
+ y: Math.min(box.y, r.y),
206
+ w: Math.max(box.x + box.w, r.x + r.w) - Math.min(box.x, r.x),
207
+ h: Math.max(box.y + box.h, r.y + r.h) - Math.min(box.y, r.y),
208
+ }
209
+ : { x: r.x, y: r.y, w: r.w, h: r.h };
210
+ }
211
+ return box;
212
+ }
213
+ // The float slack the two distance comparisons take. Both sides are px
214
+ // arithmetic over the same projection, so a real disagreement is orders of
215
+ // magnitude above this and a rounding one is below it.
216
+ const EPS = 1e-9;
217
+ const MAX_VIOLATIONS = 10;
218
+ /**
219
+ * The ink clause of {@link sweepMarkAgainstHit} alone, for a mark with no
220
+ * `hitNearest`: `boxes[i]` is what the painter recorded for instance `i`.
221
+ */
222
+ export function inkViolations(mark, channels, block, state, boxes, inkSlackPx) {
223
+ const violations = [];
224
+ if (!mark.ink) {
225
+ return violations;
226
+ }
227
+ for (let i = 0; i < boxes.length; i++) {
228
+ const r = mark.ink(channels, block, state, i);
229
+ const b = boxes[i];
230
+ if (r && !b) {
231
+ violations.push(`${i} painted nothing but has ink`);
232
+ }
233
+ else if (b && !r) {
234
+ violations.push(`${i} painted but has no ink`);
235
+ }
236
+ else if (b && r) {
237
+ const gaps = [
238
+ b.x - r.left,
239
+ r.left + r.width - (b.x + b.w),
240
+ b.y - r.top,
241
+ r.top + r.height - (b.y + b.h),
242
+ ];
243
+ if (gaps.some(g => g < -EPS)) {
244
+ violations.push(`${i} painted (${b.x}, ${b.y}, ${b.w}, ${b.h}) outside its ink (${r.left}, ${r.top}, ${r.width}, ${r.height})`);
245
+ }
246
+ else if (gaps.some(g => g > inkSlackPx + EPS)) {
247
+ violations.push(`${i}'s ink (${r.left}, ${r.top}, ${r.width}, ${r.height}) is more than ${inkSlackPx}px outside its painting (${b.x}, ${b.y}, ${b.w}, ${b.h})`);
248
+ }
249
+ }
250
+ if (violations.length >= MAX_VIOLATIONS) {
251
+ return violations;
252
+ }
253
+ }
254
+ return violations;
255
+ }
256
+ function hitViolations(hit, boxes, x, y) {
257
+ const violations = [];
258
+ const box = hit && boxes[hit.index];
259
+ if (hit && !box) {
260
+ violations.push(`(${x}, ${y}) answered ${hit.index}, which painted nothing`);
261
+ }
262
+ else if (hit && box) {
263
+ if (!contains(box, hit.x, hit.y, EPS)) {
264
+ violations.push(`(${x}, ${y}) answered ${hit.index} with ink at (${hit.x}, ${hit.y}), off its painting`);
265
+ }
266
+ const toBox = inkOnRect(x, y, box.x, box.y, box.w, box.h).distSq;
267
+ if (hit.distSq < toBox - EPS) {
268
+ violations.push(`(${x}, ${y}) answered ${hit.index} at ${hit.distSq}, nearer than its painting (${toBox})`);
269
+ }
270
+ const toPoint = (x - hit.x) ** 2 + (y - hit.y) ** 2;
271
+ if (Math.abs(hit.distSq - toPoint) > EPS * Math.max(1, toPoint)) {
272
+ violations.push(`(${x}, ${y}) answered ${hit.index} at ${hit.distSq}, which is not the distance to the ink it named (${toPoint})`);
273
+ }
274
+ }
275
+ return violations;
276
+ }
277
+ /**
278
+ * The draw-against-hit gate: paint the block, then walk it in `step` px and
279
+ * hold `hitNearest` to what the painter actually put on the canvas.
280
+ *
281
+ * **What the painter drew for instance `i` has to be attributed to `i`,** and
282
+ * there are two ways to get that. A mark whose every instance is one `fillRect`
283
+ * gets it positionally, which is the default and needs nothing. A mark that
284
+ * batches a colour run into one path (`point`'s glyphs), or that skips an
285
+ * instance the view has scrolled past (`cell`'s off-canvas rows), cannot be read
286
+ * that way at all — the batch has fewer rects than instances, or the wrong ones.
287
+ * `sliceOne` is the way in for those: hand back one instance's channels and the
288
+ * sweep paints it alone, so the attribution holds by construction and a culled
289
+ * instance is visibly an empty painting.
290
+ *
291
+ * Four claims, all of them one-directional and true of every mark:
292
+ *
293
+ * - an instance that painted nothing is never the answer,
294
+ * - `hit.x`/`hit.y` — where the mark says its ink is — lies inside the box the
295
+ * painter drew for the instance the hit names,
296
+ * - `hit.distSq` is no smaller than the distance to that box, since the box
297
+ * contains the ink and cannot be farther than it,
298
+ * - `hit.distSq` IS the distance to `hit.x`/`hit.y`, which is `MarkHit`'s own
299
+ * contract rather than a fact about the painting: a mark that answers a
300
+ * distance its own reported point does not support has two spellings of where
301
+ * its ink is, and the nearest-wins walk is resolved by the one the caller
302
+ * cannot see.
303
+ *
304
+ * The fifth is the containment rule: **a point the rule contains answers the
305
+ * nearest painted instance it contains**, the last-painted where tied, since the
306
+ * sweep hands candidates back to front. The default rule is the painted box, so
307
+ * a point on a box answers that box, and off every box a hit target wider than
308
+ * its ink may still answer. The default holds only where the box IS the hit
309
+ * target — every `fillRect` mark, and `cell`'s inversion triangle, which answers
310
+ * as its bounding box on purpose — and not for a glyph, whose box is a superset
311
+ * of its ink, so it runs exactly when the batch painted one rect per instance. A
312
+ * mark whose hit rule is not its box (the pileup's bp containment) passes
313
+ * `contains`, which is then the whole rule: the sweep covers the whole canvas,
314
+ * and a point the rule contains nowhere answers nothing.
315
+ *
316
+ * A mark declaring `ink` is held to one more clause, per instance: the box the
317
+ * painter recorded for `i` lies inside `ink(i)`, and each edge of `ink(i)` lies
318
+ * within `inkSlackPx` (default 1) of the painting's — so the rect is tight to
319
+ * the ink within a pixel on every side, which admits a seam overdraw or a pixel
320
+ * snap and rejects a rect that is the wrong row, the wrong width or the whole
321
+ * canvas. An instance that painted nothing must answer no ink, and one with ink
322
+ * must have painted.
323
+ *
324
+ * Returns the violations rather than asserting, so a test reads as
325
+ * `expect(sweep(...)).toEqual([])`. `maxDistSq` is the bound the hit test is
326
+ * asked with, `Infinity` unless the caller's differs.
327
+ */
328
+ export function sweepMarkAgainstHit(mark, channels, block, state, { maxDistSq = Infinity, step = 0.5, sliceOne, inkSlackPx = 1, contains: rule, } = {}) {
329
+ const { count } = channels;
330
+ const paint = (c) => {
331
+ const { ctx, calls } = recordingContext();
332
+ mark.paintBlock(ctx, c, block, state);
333
+ return calls;
334
+ };
335
+ const rects = paint(channels);
336
+ const perRect = rects.length === count;
337
+ if (!perRect && !sliceOne) {
338
+ return [`painted ${rects.length} rects for ${count} instances`];
339
+ }
340
+ const boxes = sliceOne
341
+ ? Array.from({ length: count }, (_, i) => unionBox(paint(sliceOne(channels, i))))
342
+ : rects.map(r => ({ x: r.x, y: r.y, w: r.w, h: r.h }));
343
+ let yMin = Infinity;
344
+ let yMax = -Infinity;
345
+ for (const b of boxes) {
346
+ if (b) {
347
+ yMin = Math.min(yMin, b.y);
348
+ yMax = Math.max(yMax, b.y + b.h);
349
+ }
350
+ }
351
+ if (yMin === Infinity) {
352
+ return [`painted nothing for ${count} instances`];
353
+ }
354
+ const violations = inkViolations(mark, channels, block, state, boxes, inkSlackPx);
355
+ if (violations.length >= MAX_VIOLATIONS) {
356
+ return violations;
357
+ }
358
+ if (!mark.hitNearest) {
359
+ return ['no hit test'];
360
+ }
361
+ const claims = rule ??
362
+ (perRect
363
+ ? (k, x, y) => contains(boxes[k], x, y)
364
+ : undefined);
365
+ const candidates = Array.from({ length: count }, (_, k) => count - 1 - k);
366
+ const x0 = Math.min(block.screenStartPx, block.screenEndPx) - 1;
367
+ const x1 = Math.max(block.screenStartPx, block.screenEndPx) + 1;
368
+ const y0 = rule ? -1 : Math.floor(yMin) - 1;
369
+ const y1 = rule ? state.canvasHeight + 1 : yMax + 1;
370
+ let answered = 0;
371
+ for (let y = y0; y <= y1; y += step) {
372
+ for (let x = x0; x <= x1; x += step) {
373
+ let expected = -1;
374
+ let expectedDistSq = maxDistSq;
375
+ if (claims) {
376
+ for (const k of candidates) {
377
+ const b = boxes[k];
378
+ if (b && claims(k, x, y)) {
379
+ const distSq = inkOnRect(x, y, b.x, b.y, b.w, b.h).distSq;
380
+ if (expected === -1
381
+ ? distSq < maxDistSq
382
+ : distSq < expectedDistSq - EPS) {
383
+ expected = k;
384
+ expectedDistSq = distSq;
385
+ }
386
+ }
387
+ }
388
+ }
389
+ const hit = mark.hitNearest(channels, block, state, x, y, candidates, maxDistSq);
390
+ if (hit) {
391
+ answered++;
392
+ }
393
+ violations.push(...hitViolations(hit, boxes, x, y));
394
+ if (expected === -1
395
+ ? rule !== undefined && hit !== undefined
396
+ : hit?.index !== expected) {
397
+ violations.push(`(${x}, ${y}) should answer ${expected === -1 ? 'nothing' : expected}, answered ${hit?.index ?? 'nothing'}`);
398
+ }
399
+ if (violations.length >= MAX_VIOLATIONS) {
400
+ return violations;
401
+ }
402
+ }
403
+ }
404
+ return answered === 0 && violations.length === 0
405
+ ? [`no swept point answered any of ${count} instances`]
406
+ : violations;
407
+ }
@@ -0,0 +1,10 @@
1
+ export declare function ellipseDistance(px: number, py: number, rx: number, ry: number): number;
2
+ /**
3
+ * The point of the ellipse nearest (px, py), in the ellipse's own frame, and
4
+ * how far it is: what a hit test hands back as the ink it found.
5
+ */
6
+ export declare function ellipseNearest(px: number, py: number, rx: number, ry: number): {
7
+ x: number;
8
+ y: number;
9
+ dist: number;
10
+ };
@@ -0,0 +1,136 @@
1
+ // Exact distance from a point to the axis-aligned ellipse |p/ab| = 1 — Inigo
2
+ // Quilez's closed-form solve of the quartic, in float64.
3
+ //
4
+ // THIS IS A SECOND COPY of `sdEllipse` in curveDistance.slang, and the header
5
+ // there carries the argument for why it is not generated instead. The short
6
+ // version: making it liftable also enrolls it in `check-shader-oracle`, which
7
+ // referees the float64 twin against slangc's float32 C++ — and a quartic solve
8
+ // is exactly the shape that comparison cannot referee, because float32 loses
9
+ // the solver before the transliteration is in question.
10
+ //
11
+ // So the two copies are NOT held together by parity with each other. What has to
12
+ // agree is which ellipse, and that is the radii pair `linkMark.slang` lifts
13
+ // (`linkRadiiPx`), which both the shader and this file's callers read. Given the same two radii, this is geometry with one right answer, and
14
+ // `ellipseDistance.test.ts` pins it against a brute-force minimum over the
15
+ // parametric angle — a stronger check than agreeing with a float32 evaluation
16
+ // of the same formula would have been.
17
+ //
18
+ // The closed form rather than an iterative solve, for the reason the shader
19
+ // gives: the arc band produces very flat ellipses (a 25px band under a 1500px
20
+ // pair is 44:1, and it goes past 88:1), and a seeded Newton on the parametric
21
+ // angle runs away entirely there.
22
+ // The first-quadrant solve: `co` is the cosine of the parametric angle of the
23
+ // nearest point to (qx, qy), radii (ax, ay), with the point and radii already
24
+ // mirrored into the half where qx <= qy.
25
+ function nearestCos(qx, qy, ax, ay) {
26
+ const l = ay * ay - ax * ax;
27
+ const m = (ax * qx) / l;
28
+ const m2 = m * m;
29
+ const n = (ay * qy) / l;
30
+ const n2 = n * n;
31
+ const c = (m2 + n2 - 1) / 3;
32
+ const c3 = c * c * c;
33
+ const d = c3 + m2 * n2;
34
+ const g = m + m * n2;
35
+ let co;
36
+ if (d < 0) {
37
+ const h = Math.acos(clamp1((c3 + m2 * n2 * 2) / c3)) / 3;
38
+ const s = Math.cos(h);
39
+ const t = Math.sin(h) * Math.sqrt(3);
40
+ const ux = Math.sqrt(Math.max(-c * (s + t + 2) + m2, 0));
41
+ const uy = Math.sqrt(Math.max(-c * (s - t + 2) + m2, 0));
42
+ co =
43
+ (uy + Math.sign(l) * ux + Math.abs(g) / Math.max(ux * uy, 1e-20) - m) / 2;
44
+ }
45
+ else {
46
+ const qq = c3 + m2 * n2 * 2;
47
+ const h = 2 * m * n * Math.sqrt(d);
48
+ const s = Math.sign(qq + h) * Math.cbrt(Math.abs(qq + h));
49
+ const t = Math.sign(qq - h) * Math.cbrt(Math.abs(qq - h));
50
+ const ux = -s - t - c * 4 + 2 * m2;
51
+ const uy = (s - t) * Math.sqrt(3);
52
+ const rm = Math.hypot(ux, uy);
53
+ co =
54
+ (uy / Math.sqrt(Math.max(rm - ux, 1e-20)) +
55
+ (2 * g) / Math.max(rm, 1e-20) -
56
+ m) /
57
+ 2;
58
+ }
59
+ return clamp1(co);
60
+ }
61
+ export function ellipseDistance(px, py, rx, ry) {
62
+ let qx = Math.abs(px);
63
+ let qy = Math.abs(py);
64
+ let ax = rx;
65
+ let ay = ry;
66
+ // Solve in the half where q.x <= q.y, mirroring point and radii together.
67
+ if (qx > qy) {
68
+ ;
69
+ [qx, qy] = [qy, qx];
70
+ [ax, ay] = [ay, ax];
71
+ }
72
+ // Degenerate to the point at the origin, which the circle test below cannot
73
+ // catch: its threshold is `ay * ay`, so it reads `abs(l) < 0` and lets a zero
74
+ // divisor through. Same guard as curveDistance.slang's.
75
+ // Degenerate to the point at the origin, which the circle test below cannot
76
+ // catch: its threshold is `ay * ay`, so it reads `abs(l) < 0` and lets a zero
77
+ // divisor through. Same guard as curveDistance.slang's.
78
+ if (ax * ax + ay * ay <= 0) {
79
+ return Math.hypot(qx, qy);
80
+ }
81
+ const l = ay * ay - ax * ax;
82
+ // Circle: `l` is the solver's divisor, so hand those back the exact answer
83
+ // rather than a division by ~0. Reachable — a clamped dome's ry is pinned to
84
+ // the band while rx tracks the pair's width, so they cross.
85
+ if (Math.abs(l) < 1e-6 * ay * ay) {
86
+ return Math.abs(Math.hypot(qx, qy) - ax);
87
+ }
88
+ const co = nearestCos(qx, qy, ax, ay);
89
+ return Math.hypot(ax * co - qx, ay * Math.sqrt(Math.max(1 - co * co, 0)) - qy);
90
+ }
91
+ /**
92
+ * The point of the ellipse nearest (px, py), in the ellipse's own frame, and
93
+ * how far it is: what a hit test hands back as the ink it found.
94
+ */
95
+ export function ellipseNearest(px, py, rx, ry) {
96
+ let qx = Math.abs(px);
97
+ let qy = Math.abs(py);
98
+ let ax = rx;
99
+ let ay = ry;
100
+ const swapped = qx > qy;
101
+ if (swapped) {
102
+ ;
103
+ [qx, qy] = [qy, qx];
104
+ [ax, ay] = [ay, ax];
105
+ }
106
+ const l = ay * ay - ax * ax;
107
+ let nx;
108
+ let ny;
109
+ if (ax * ax + ay * ay <= 0) {
110
+ nx = 0;
111
+ ny = 0;
112
+ }
113
+ else if (Math.abs(l) < 1e-6 * ay * ay) {
114
+ const len = Math.hypot(qx, qy);
115
+ nx = len > 0 ? (qx / len) * ax : ax;
116
+ ny = len > 0 ? (qy / len) * ax : 0;
117
+ }
118
+ else {
119
+ const co = nearestCos(qx, qy, ax, ay);
120
+ nx = ax * co;
121
+ ny = ay * Math.sqrt(Math.max(1 - co * co, 0));
122
+ }
123
+ const dist = Math.hypot(nx - qx, ny - qy);
124
+ if (swapped) {
125
+ ;
126
+ [nx, ny] = [ny, nx];
127
+ }
128
+ return {
129
+ x: px < 0 ? -nx : nx,
130
+ y: py < 0 ? -ny : ny,
131
+ dist,
132
+ };
133
+ }
134
+ function clamp1(x) {
135
+ return x < -1 ? -1 : x > 1 ? 1 : x;
136
+ }
@@ -0,0 +1,36 @@
1
+ import type { InkRect, MarkContext2D } from './types.ts';
2
+ /** The path calls a glyph is built from — what a recorder has to answer. */
3
+ export type GlyphPath = Pick<MarkContext2D, 'moveTo' | 'lineTo' | 'closePath' | 'rect' | 'arc'>;
4
+ /**
5
+ * Append one of the `point` shape's glyphs to the current path, centred on
6
+ * `(cx, y)`.
7
+ *
8
+ * The disc/crisp-square split and the square's snap are pointGlyph.slang's
9
+ * (adr-051), so the painter cannot drift from the shader about where the split
10
+ * is. The caller owns `beginPath`/`fill`, so a run of one colour can batch into
11
+ * a single fill.
12
+ *
13
+ * Its own module, not `pointMark.ts`'s: this is the half of the shape a
14
+ * consumer can want without the pass, and importing it through the shape would
15
+ * hand that consumer the shader's WGSL and GLSL strings. Wiggle's scatter mode
16
+ * is that consumer.
17
+ */
18
+ export declare function appendGlyph(ctx: GlyphPath, glyph: number, cx: number, y: number, diameter: number): void;
19
+ /**
20
+ * Room a glyph needs at each end of a plot whose display wants its topmost and
21
+ * bottommost points drawn whole: a bar's datum is its top edge and never
22
+ * overshoots, a glyph's is its centre, so one at domain max loses its top half
23
+ * off the canvas. The diamond's scale, since that is the largest thing the
24
+ * shape draws and `glyphBox` measures it the same way.
25
+ *
26
+ * A display passes this as the point shape's `insetPx` and gives its axis the
27
+ * same number as an offset, so domain max goes on labelling the row the
28
+ * topmost glyph centres on.
29
+ */
30
+ export declare function pointInsetPx(diameter: number): number;
31
+ /**
32
+ * The box `appendGlyph` paints inside, by the same split and snap: the
33
+ * diamond is scaled by `DIAMOND_GLYPH_SCALE` and the small square snaps to
34
+ * the pixel grid, so neither is the diameter about the centre.
35
+ */
36
+ export declare function glyphBox(glyph: number, cx: number, y: number, diameter: number): InkRect;
@@ -0,0 +1,77 @@
1
+ import { SMALL_POINT_MAX_DIAMETER } from '../shaders/pointGlyph.generated.js';
2
+ import { crispSquareTopLeftPx } from '../shaders/pointGlyph.js.generated.js';
3
+ import { DIAMOND_GLYPH_SCALE, GLYPH_DIAMOND, GLYPH_SQUARE, GLYPH_TRIANGLE, } from '../shaders/pointMark.consts.generated.js';
4
+ /**
5
+ * Append one of the `point` shape's glyphs to the current path, centred on
6
+ * `(cx, y)`.
7
+ *
8
+ * The disc/crisp-square split and the square's snap are pointGlyph.slang's
9
+ * (adr-051), so the painter cannot drift from the shader about where the split
10
+ * is. The caller owns `beginPath`/`fill`, so a run of one colour can batch into
11
+ * a single fill.
12
+ *
13
+ * Its own module, not `pointMark.ts`'s: this is the half of the shape a
14
+ * consumer can want without the pass, and importing it through the shape would
15
+ * hand that consumer the shader's WGSL and GLSL strings. Wiggle's scatter mode
16
+ * is that consumer.
17
+ */
18
+ export function appendGlyph(ctx, glyph, cx, y, diameter) {
19
+ const r = diameter / 2;
20
+ if (glyph === GLYPH_TRIANGLE) {
21
+ ctx.moveTo(cx - r, y - r);
22
+ ctx.lineTo(cx + r, y - r);
23
+ ctx.lineTo(cx, y + r);
24
+ ctx.closePath();
25
+ }
26
+ else if (glyph === GLYPH_DIAMOND) {
27
+ const ri = r * DIAMOND_GLYPH_SCALE;
28
+ ctx.moveTo(cx, y - ri);
29
+ ctx.lineTo(cx + ri, y);
30
+ ctx.lineTo(cx, y + ri);
31
+ ctx.lineTo(cx - ri, y);
32
+ ctx.closePath();
33
+ }
34
+ else if (glyph === GLYPH_SQUARE || diameter <= SMALL_POINT_MAX_DIAMETER) {
35
+ ctx.rect(crispSquareTopLeftPx(cx, diameter), crispSquareTopLeftPx(y, diameter), diameter, diameter);
36
+ }
37
+ else {
38
+ ctx.moveTo(cx + r, y);
39
+ ctx.arc(cx, y, r, 0, Math.PI * 2);
40
+ }
41
+ }
42
+ /**
43
+ * Room a glyph needs at each end of a plot whose display wants its topmost and
44
+ * bottommost points drawn whole: a bar's datum is its top edge and never
45
+ * overshoots, a glyph's is its centre, so one at domain max loses its top half
46
+ * off the canvas. The diamond's scale, since that is the largest thing the
47
+ * shape draws and `glyphBox` measures it the same way.
48
+ *
49
+ * A display passes this as the point shape's `insetPx` and gives its axis the
50
+ * same number as an offset, so domain max goes on labelling the row the
51
+ * topmost glyph centres on.
52
+ */
53
+ export function pointInsetPx(diameter) {
54
+ return (diameter / 2) * DIAMOND_GLYPH_SCALE;
55
+ }
56
+ /**
57
+ * The box `appendGlyph` paints inside, by the same split and snap: the
58
+ * diamond is scaled by `DIAMOND_GLYPH_SCALE` and the small square snaps to
59
+ * the pixel grid, so neither is the diameter about the centre.
60
+ */
61
+ export function glyphBox(glyph, cx, y, diameter) {
62
+ const r = diameter / 2;
63
+ if (glyph === GLYPH_DIAMOND) {
64
+ const ri = r * DIAMOND_GLYPH_SCALE;
65
+ return { left: cx - ri, top: y - ri, width: 2 * ri, height: 2 * ri };
66
+ }
67
+ if (glyph === GLYPH_SQUARE ||
68
+ (glyph !== GLYPH_TRIANGLE && diameter <= SMALL_POINT_MAX_DIAMETER)) {
69
+ return {
70
+ left: crispSquareTopLeftPx(cx, diameter),
71
+ top: crispSquareTopLeftPx(y, diameter),
72
+ width: diameter,
73
+ height: diameter,
74
+ };
75
+ }
76
+ return { left: cx - r, top: y - r, width: diameter, height: diameter };
77
+ }