@react-x11/components 0.8.0 → 0.9.0

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 (323) hide show
  1. package/README.md +95 -34
  2. package/dist/charts/node.d.ts +1 -1
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +6 -2
  5. package/dist/charts/node.js.map +1 -1
  6. package/dist/embed/client.d.ts +48 -3
  7. package/dist/embed/client.d.ts.map +1 -1
  8. package/dist/embed/client.js +79 -5
  9. package/dist/embed/client.js.map +1 -1
  10. package/dist/embed/index.d.ts +1 -1
  11. package/dist/embed/index.d.ts.map +1 -1
  12. package/dist/embed/index.js +1 -1
  13. package/dist/embed/index.js.map +1 -1
  14. package/dist/html/node.d.ts +3 -1
  15. package/dist/html/node.d.ts.map +1 -1
  16. package/dist/html/node.js +7 -5
  17. package/dist/html/node.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/{markdown → internal/markdown}/ast.d.ts +12 -0
  23. package/dist/internal/markdown/ast.d.ts.map +1 -0
  24. package/dist/internal/markdown/ast.js +22 -0
  25. package/dist/internal/markdown/ast.js.map +1 -0
  26. package/dist/internal/markdown/parse.d.ts.map +1 -0
  27. package/dist/{markdown → internal/markdown}/parse.js +112 -22
  28. package/dist/internal/markdown/parse.js.map +1 -0
  29. package/dist/internal/markdown/stringify.d.ts +4 -0
  30. package/dist/internal/markdown/stringify.d.ts.map +1 -0
  31. package/dist/internal/markdown/stringify.js +684 -0
  32. package/dist/internal/markdown/stringify.js.map +1 -0
  33. package/dist/internal/markdown/tags.d.ts.map +1 -0
  34. package/dist/internal/markdown/tags.js.map +1 -0
  35. package/dist/internal/scroll.d.ts +3 -2
  36. package/dist/internal/scroll.d.ts.map +1 -1
  37. package/dist/internal/scroll.js +1 -0
  38. package/dist/internal/scroll.js.map +1 -1
  39. package/dist/maps/anchors.d.ts +98 -0
  40. package/dist/maps/anchors.d.ts.map +1 -0
  41. package/dist/maps/anchors.js +531 -0
  42. package/dist/maps/anchors.js.map +1 -0
  43. package/dist/maps/controller.d.ts +283 -0
  44. package/dist/maps/controller.d.ts.map +1 -0
  45. package/dist/maps/controller.js +743 -0
  46. package/dist/maps/controller.js.map +1 -0
  47. package/dist/maps/gl/buckets.d.ts +95 -0
  48. package/dist/maps/gl/buckets.d.ts.map +1 -0
  49. package/dist/maps/gl/buckets.js +362 -0
  50. package/dist/maps/gl/buckets.js.map +1 -0
  51. package/dist/maps/gl/build-worker.d.ts +13 -0
  52. package/dist/maps/gl/build-worker.d.ts.map +1 -0
  53. package/dist/maps/gl/build-worker.js +44 -0
  54. package/dist/maps/gl/build-worker.js.map +1 -0
  55. package/dist/maps/gl/color.d.ts +8 -0
  56. package/dist/maps/gl/color.d.ts.map +1 -0
  57. package/dist/maps/gl/color.js +57 -0
  58. package/dist/maps/gl/color.js.map +1 -0
  59. package/dist/maps/gl/cover.d.ts +51 -0
  60. package/dist/maps/gl/cover.d.ts.map +1 -0
  61. package/dist/maps/gl/cover.js +124 -0
  62. package/dist/maps/gl/cover.js.map +1 -0
  63. package/dist/maps/gl/index.d.ts +3 -0
  64. package/dist/maps/gl/index.d.ts.map +1 -0
  65. package/dist/maps/gl/index.js +20 -0
  66. package/dist/maps/gl/index.js.map +1 -0
  67. package/dist/maps/gl/markers.d.ts +28 -0
  68. package/dist/maps/gl/markers.d.ts.map +1 -0
  69. package/dist/maps/gl/markers.js +60 -0
  70. package/dist/maps/gl/markers.js.map +1 -0
  71. package/dist/maps/gl/overlays.d.ts +68 -0
  72. package/dist/maps/gl/overlays.d.ts.map +1 -0
  73. package/dist/maps/gl/overlays.js +300 -0
  74. package/dist/maps/gl/overlays.js.map +1 -0
  75. package/dist/maps/gl/pane.d.ts +12 -0
  76. package/dist/maps/gl/pane.d.ts.map +1 -0
  77. package/dist/maps/gl/pane.js +37 -0
  78. package/dist/maps/gl/pane.js.map +1 -0
  79. package/dist/maps/gl/placement.d.ts +111 -0
  80. package/dist/maps/gl/placement.d.ts.map +1 -0
  81. package/dist/maps/gl/placement.js +567 -0
  82. package/dist/maps/gl/placement.js.map +1 -0
  83. package/dist/maps/gl/renderer.d.ts +352 -0
  84. package/dist/maps/gl/renderer.d.ts.map +1 -0
  85. package/dist/maps/gl/renderer.js +1127 -0
  86. package/dist/maps/gl/renderer.js.map +1 -0
  87. package/dist/maps/gl/shaders.d.ts +120 -0
  88. package/dist/maps/gl/shaders.d.ts.map +1 -0
  89. package/dist/maps/gl/shaders.js +451 -0
  90. package/dist/maps/gl/shaders.js.map +1 -0
  91. package/dist/maps/gl/store.d.ts +115 -0
  92. package/dist/maps/gl/store.d.ts.map +1 -0
  93. package/dist/maps/gl/store.js +469 -0
  94. package/dist/maps/gl/store.js.map +1 -0
  95. package/dist/maps/gl/text.d.ts +154 -0
  96. package/dist/maps/gl/text.d.ts.map +1 -0
  97. package/dist/maps/gl/text.js +382 -0
  98. package/dist/maps/gl/text.js.map +1 -0
  99. package/dist/maps/gl/view.d.ts +54 -0
  100. package/dist/maps/gl/view.d.ts.map +1 -0
  101. package/dist/maps/gl/view.js +974 -0
  102. package/dist/maps/gl/view.js.map +1 -0
  103. package/dist/maps/index.d.ts +1 -1
  104. package/dist/maps/index.d.ts.map +1 -1
  105. package/dist/maps/index.js +216 -52
  106. package/dist/maps/index.js.map +1 -1
  107. package/dist/maps/labels.d.ts +37 -16
  108. package/dist/maps/labels.d.ts.map +1 -1
  109. package/dist/maps/labels.js +188 -119
  110. package/dist/maps/labels.js.map +1 -1
  111. package/dist/maps/node.d.ts +162 -56
  112. package/dist/maps/node.d.ts.map +1 -1
  113. package/dist/maps/node.js +607 -515
  114. package/dist/maps/node.js.map +1 -1
  115. package/dist/maps/overlay.d.ts +72 -9
  116. package/dist/maps/overlay.d.ts.map +1 -1
  117. package/dist/maps/overlay.js +108 -28
  118. package/dist/maps/overlay.js.map +1 -1
  119. package/dist/maps/paint.d.ts +3 -0
  120. package/dist/maps/paint.d.ts.map +1 -1
  121. package/dist/maps/paint.js.map +1 -1
  122. package/dist/maps/proj.d.ts +14 -0
  123. package/dist/maps/proj.d.ts.map +1 -1
  124. package/dist/maps/proj.js +28 -0
  125. package/dist/maps/proj.js.map +1 -1
  126. package/dist/maps/renderer.d.ts +69 -0
  127. package/dist/maps/renderer.d.ts.map +1 -0
  128. package/dist/maps/renderer.js +66 -0
  129. package/dist/maps/renderer.js.map +1 -0
  130. package/dist/maps/sources.d.ts +25 -3
  131. package/dist/maps/sources.d.ts.map +1 -1
  132. package/dist/maps/sources.js +17 -0
  133. package/dist/maps/sources.js.map +1 -1
  134. package/dist/maps/theme.d.ts +19 -0
  135. package/dist/maps/theme.d.ts.map +1 -0
  136. package/dist/maps/theme.js +44 -0
  137. package/dist/maps/theme.js.map +1 -0
  138. package/dist/maps/tiles.d.ts +117 -17
  139. package/dist/maps/tiles.d.ts.map +1 -1
  140. package/dist/maps/tiles.js +163 -30
  141. package/dist/maps/tiles.js.map +1 -1
  142. package/dist/maps/types.d.ts +188 -20
  143. package/dist/maps/types.d.ts.map +1 -1
  144. package/dist/markdown/expressions.d.ts +1 -1
  145. package/dist/markdown/expressions.d.ts.map +1 -1
  146. package/dist/markdown/expressions.js.map +1 -1
  147. package/dist/markdown/index.d.ts +7 -6
  148. package/dist/markdown/index.d.ts.map +1 -1
  149. package/dist/markdown/index.js +9 -7
  150. package/dist/markdown/index.js.map +1 -1
  151. package/dist/markdown/spans.d.ts +1 -1
  152. package/dist/markdown/spans.d.ts.map +1 -1
  153. package/dist/media-player/index.d.ts +11 -3
  154. package/dist/media-player/index.d.ts.map +1 -1
  155. package/dist/media-player/index.js +13 -2
  156. package/dist/media-player/index.js.map +1 -1
  157. package/dist/rich-text-editor/clipboard.d.ts +23 -0
  158. package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
  159. package/dist/rich-text-editor/clipboard.js +307 -0
  160. package/dist/rich-text-editor/clipboard.js.map +1 -0
  161. package/dist/rich-text-editor/collab.d.ts +32 -0
  162. package/dist/rich-text-editor/collab.d.ts.map +1 -0
  163. package/dist/rich-text-editor/collab.js +55 -0
  164. package/dist/rich-text-editor/collab.js.map +1 -0
  165. package/dist/rich-text-editor/commands.d.ts +50 -0
  166. package/dist/rich-text-editor/commands.d.ts.map +1 -0
  167. package/dist/rich-text-editor/commands.js +376 -0
  168. package/dist/rich-text-editor/commands.js.map +1 -0
  169. package/dist/rich-text-editor/drag.d.ts +68 -0
  170. package/dist/rich-text-editor/drag.d.ts.map +1 -0
  171. package/dist/rich-text-editor/drag.js +148 -0
  172. package/dist/rich-text-editor/drag.js.map +1 -0
  173. package/dist/rich-text-editor/html.d.ts +145 -0
  174. package/dist/rich-text-editor/html.d.ts.map +1 -0
  175. package/dist/rich-text-editor/html.js +516 -0
  176. package/dist/rich-text-editor/html.js.map +1 -0
  177. package/dist/rich-text-editor/index.d.ts +263 -0
  178. package/dist/rich-text-editor/index.d.ts.map +1 -0
  179. package/dist/rich-text-editor/index.js +822 -0
  180. package/dist/rich-text-editor/index.js.map +1 -0
  181. package/dist/rich-text-editor/inline.d.ts +94 -0
  182. package/dist/rich-text-editor/inline.d.ts.map +1 -0
  183. package/dist/rich-text-editor/inline.js +252 -0
  184. package/dist/rich-text-editor/inline.js.map +1 -0
  185. package/dist/rich-text-editor/keymap.d.ts +71 -0
  186. package/dist/rich-text-editor/keymap.d.ts.map +1 -0
  187. package/dist/rich-text-editor/keymap.js +393 -0
  188. package/dist/rich-text-editor/keymap.js.map +1 -0
  189. package/dist/rich-text-editor/keys.d.ts +29 -0
  190. package/dist/rich-text-editor/keys.d.ts.map +1 -0
  191. package/dist/rich-text-editor/keys.js +93 -0
  192. package/dist/rich-text-editor/keys.js.map +1 -0
  193. package/dist/rich-text-editor/look.d.ts +71 -0
  194. package/dist/rich-text-editor/look.d.ts.map +1 -0
  195. package/dist/rich-text-editor/look.js +342 -0
  196. package/dist/rich-text-editor/look.js.map +1 -0
  197. package/dist/rich-text-editor/markdown.d.ts +18 -0
  198. package/dist/rich-text-editor/markdown.d.ts.map +1 -0
  199. package/dist/rich-text-editor/markdown.js +496 -0
  200. package/dist/rich-text-editor/markdown.js.map +1 -0
  201. package/dist/rich-text-editor/nodes.d.ts +135 -0
  202. package/dist/rich-text-editor/nodes.d.ts.map +1 -0
  203. package/dist/rich-text-editor/nodes.js +397 -0
  204. package/dist/rich-text-editor/nodes.js.map +1 -0
  205. package/dist/rich-text-editor/render.d.ts +68 -0
  206. package/dist/rich-text-editor/render.d.ts.map +1 -0
  207. package/dist/rich-text-editor/render.js +500 -0
  208. package/dist/rich-text-editor/render.js.map +1 -0
  209. package/dist/rich-text-editor/schema.d.ts +16 -0
  210. package/dist/rich-text-editor/schema.d.ts.map +1 -0
  211. package/dist/rich-text-editor/schema.js +386 -0
  212. package/dist/rich-text-editor/schema.js.map +1 -0
  213. package/dist/rich-text-editor/store.d.ts +11 -0
  214. package/dist/rich-text-editor/store.d.ts.map +1 -0
  215. package/dist/rich-text-editor/store.js +43 -0
  216. package/dist/rich-text-editor/store.js.map +1 -0
  217. package/dist/rich-text-editor/suggest.d.ts +127 -0
  218. package/dist/rich-text-editor/suggest.d.ts.map +1 -0
  219. package/dist/rich-text-editor/suggest.js +0 -0
  220. package/dist/rich-text-editor/suggest.js.map +1 -0
  221. package/dist/rich-text-editor/tables.d.ts +41 -0
  222. package/dist/rich-text-editor/tables.d.ts.map +1 -0
  223. package/dist/rich-text-editor/tables.js +204 -0
  224. package/dist/rich-text-editor/tables.js.map +1 -0
  225. package/dist/rich-text-editor/toolbar.d.ts +58 -0
  226. package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
  227. package/dist/rich-text-editor/toolbar.js +548 -0
  228. package/dist/rich-text-editor/toolbar.js.map +1 -0
  229. package/dist/rich-text-editor/view.d.ts +368 -0
  230. package/dist/rich-text-editor/view.d.ts.map +1 -0
  231. package/dist/rich-text-editor/view.js +1968 -0
  232. package/dist/rich-text-editor/view.js.map +1 -0
  233. package/dist/rich-text-editor/virtual.d.ts +54 -0
  234. package/dist/rich-text-editor/virtual.d.ts.map +1 -0
  235. package/dist/rich-text-editor/virtual.js +262 -0
  236. package/dist/rich-text-editor/virtual.js.map +1 -0
  237. package/dist/richtext/node.d.ts +25 -2
  238. package/dist/richtext/node.d.ts.map +1 -1
  239. package/dist/richtext/node.js +46 -24
  240. package/dist/richtext/node.js.map +1 -1
  241. package/dist/terminal/backends.d.ts +4 -1
  242. package/dist/terminal/backends.d.ts.map +1 -1
  243. package/dist/terminal/backends.js.map +1 -1
  244. package/dist/terminal/index.d.ts +18 -7
  245. package/dist/terminal/index.d.ts.map +1 -1
  246. package/dist/terminal/index.js +28 -11
  247. package/dist/terminal/index.js.map +1 -1
  248. package/package.json +23 -4
  249. package/src/charts/node.ts +6 -2
  250. package/src/embed/client.ts +102 -8
  251. package/src/embed/index.ts +2 -0
  252. package/src/html/node.ts +7 -5
  253. package/src/index.ts +2 -0
  254. package/src/{markdown → internal/markdown}/ast.ts +24 -7
  255. package/src/{markdown → internal/markdown}/parse.ts +134 -24
  256. package/src/internal/markdown/stringify.ts +772 -0
  257. package/src/internal/scroll.ts +5 -3
  258. package/src/maps/anchors.ts +607 -0
  259. package/src/maps/controller.ts +999 -0
  260. package/src/maps/gl/buckets.ts +457 -0
  261. package/src/maps/gl/build-worker.ts +55 -0
  262. package/src/maps/gl/color.ts +62 -0
  263. package/src/maps/gl/cover.ts +170 -0
  264. package/src/maps/gl/index.ts +23 -0
  265. package/src/maps/gl/markers.ts +83 -0
  266. package/src/maps/gl/overlays.ts +379 -0
  267. package/src/maps/gl/pane.ts +42 -0
  268. package/src/maps/gl/placement.ts +816 -0
  269. package/src/maps/gl/renderer.ts +1576 -0
  270. package/src/maps/gl/shaders.ts +486 -0
  271. package/src/maps/gl/store.ts +566 -0
  272. package/src/maps/gl/text.ts +495 -0
  273. package/src/maps/gl/view.ts +1224 -0
  274. package/src/maps/index.ts +270 -81
  275. package/src/maps/labels.ts +214 -121
  276. package/src/maps/node.ts +763 -629
  277. package/src/maps/overlay.ts +148 -43
  278. package/src/maps/paint.ts +3 -0
  279. package/src/maps/proj.ts +31 -0
  280. package/src/maps/renderer.ts +155 -0
  281. package/src/maps/sources.ts +38 -3
  282. package/src/maps/theme.ts +51 -0
  283. package/src/maps/tiles.ts +191 -33
  284. package/src/maps/types.ts +188 -20
  285. package/src/markdown/expressions.ts +5 -1
  286. package/src/markdown/index.ts +14 -11
  287. package/src/markdown/spans.ts +1 -1
  288. package/src/media-player/index.ts +18 -4
  289. package/src/rich-text-editor/clipboard.ts +439 -0
  290. package/src/rich-text-editor/collab.ts +91 -0
  291. package/src/rich-text-editor/commands.ts +448 -0
  292. package/src/rich-text-editor/dom-globals.d.ts +23 -0
  293. package/src/rich-text-editor/drag.ts +211 -0
  294. package/src/rich-text-editor/html.ts +619 -0
  295. package/src/rich-text-editor/index.ts +1331 -0
  296. package/src/rich-text-editor/inline.ts +345 -0
  297. package/src/rich-text-editor/keymap.ts +510 -0
  298. package/src/rich-text-editor/keys.ts +119 -0
  299. package/src/rich-text-editor/look.ts +427 -0
  300. package/src/rich-text-editor/markdown.ts +653 -0
  301. package/src/rich-text-editor/nodes.ts +497 -0
  302. package/src/rich-text-editor/render.ts +800 -0
  303. package/src/rich-text-editor/schema.ts +431 -0
  304. package/src/rich-text-editor/store.ts +39 -0
  305. package/src/rich-text-editor/suggest.ts +597 -0
  306. package/src/rich-text-editor/tables.ts +272 -0
  307. package/src/rich-text-editor/toolbar.ts +724 -0
  308. package/src/rich-text-editor/view.ts +2287 -0
  309. package/src/rich-text-editor/virtual.ts +347 -0
  310. package/src/richtext/node.ts +59 -30
  311. package/src/terminal/backends.ts +4 -1
  312. package/src/terminal/index.ts +43 -15
  313. package/dist/markdown/ast.d.ts.map +0 -1
  314. package/dist/markdown/ast.js +0 -17
  315. package/dist/markdown/ast.js.map +0 -1
  316. package/dist/markdown/parse.d.ts.map +0 -1
  317. package/dist/markdown/parse.js.map +0 -1
  318. package/dist/markdown/tags.d.ts.map +0 -1
  319. package/dist/markdown/tags.js.map +0 -1
  320. /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
  321. /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
  322. /package/dist/{markdown → internal/markdown}/tags.js +0 -0
  323. /package/src/{markdown → internal/markdown}/tags.ts +0 -0
@@ -9,6 +9,12 @@
9
9
  // is the one thing on a map nobody accepts blurred. And a tile's labels
10
10
  // would be **clipped at its edge**, which is where half of them sit.
11
11
  //
12
+ // **Where a label can go is the GL renderer's answer too**: both renderers
13
+ // take their anchors from `./anchors.ts` — a place name at its point, a
14
+ // street's name on a straight stretch of the street, its pieces merged
15
+ // first — so a map switched from one renderer to the other names the same
16
+ // streets in the same places, at the same angles.
17
+ //
12
18
  // The placement itself is done in **world pixels** rather than screen
13
19
  // pixels, and that is the load-bearing decision in this file. A placement
14
20
  // computed against the viewport changes whenever the viewport does, so a
@@ -17,12 +23,21 @@
17
23
  // scrolled away. Placed in world pixels, whether a label wins depends only
18
24
  // on the labels near it and the zoom, so a pan is a translation of a
19
25
  // placement that is already correct, and the blit stands.
26
+ import {
27
+ LABEL_STRIDE,
28
+ LEVEL_SNAP,
29
+ LINE_MARGIN,
30
+ LabelField,
31
+ STRAIGHT_FRACTION,
32
+ buildTileLabels,
33
+ estimateWidth,
34
+ } from './anchors.js';
35
+ import type { GlLabelData } from './anchors.js';
20
36
  import type { MapCanvas } from './paint.js';
21
- import { GeomType, GeometryBuffer } from './mvt.js';
22
37
  import type { VectorTile } from './mvt.js';
23
38
  import type { PreparedStyle, PreparedLayer } from './paint.js';
24
39
  import { resolveZoomed } from './style.js';
25
- import type { SymbolLayer, Zoomed } from './style.js';
40
+ import type { MapStyleLayer, SymbolLayer, Zoomed } from './style.js';
26
41
  import type { TileId, Transform } from './proj.js';
27
42
  import { tileCountAt, tileKey } from './proj.js';
28
43
 
@@ -53,7 +68,7 @@ export interface ShapedLabel {
53
68
  *
54
69
  * `mx`/`my` are normalized mercator, so a candidate outlives every camera
55
70
  * move — it is a property of the data, and collected once per tile per
56
- * zoom.
71
+ * zoom. So are the lengths a name along a street is fitted with.
57
72
  */
58
73
  export interface LabelCandidate {
59
74
  /** Stable for the life of the tile, which is what lets a placement be
@@ -66,8 +81,23 @@ export interface LabelCandidate {
66
81
  text: string;
67
82
  mx: number;
68
83
  my: number;
84
+ /** The baseline's direction, radians, y down: `0` for a point label, and
85
+ * along its street — always upright — for a street name. */
86
+ angle: number;
87
+ /** A street name's room: the straight run either side of the anchor, the
88
+ * distance to the nearest junction, the run's length and how far it
89
+ * bends from straight — normalized mercator, so a placement at any zoom
90
+ * reads them in its own pixels. `Infinity`, `Infinity`, `0`, `0` for a
91
+ * point label, which fits anywhere. */
92
+ avail: number;
93
+ clear: number;
94
+ length: number;
95
+ dev: number;
69
96
  /** Layer-wide priority; higher wins a collision. */
70
97
  rank: number;
98
+ /** Importance within the layer — a road's class, a place's population —
99
+ * which breaks a tie in rank. */
100
+ priority: number;
71
101
  /** Logical pixels. */
72
102
  size: number;
73
103
  color: string;
@@ -78,7 +108,7 @@ export interface LabelCandidate {
78
108
  }
79
109
 
80
110
  /** A candidate that won its place, with the box it occupies in world
81
- * pixels at the zoom it was placed for. */
111
+ * pixels at the zoom it was placed for — its turned text's bounding box. */
82
112
  export interface PlacedLabel extends LabelCandidate {
83
113
  /** World pixels: `mercator × worldSize`. */
84
114
  wx: number;
@@ -96,115 +126,120 @@ function num<T extends number>(
96
126
  return value === undefined ? fallback : resolveZoomed(value, zoom);
97
127
  }
98
128
 
129
+ /** The tile units anchors are worked out in: the GL buckets', so the two
130
+ * renderers' anchors are the same numbers. */
131
+ const EXTENT = 4096;
132
+
99
133
  /**
100
- * The label candidates one tile contributes.
101
- *
102
- * Point geometry anchors at the point. Line and polygon geometry anchor at
103
- * the **midpoint of the longest part** — which is not a true label
104
- * placement (a street name should follow its street, and a country name
105
- * should sit at the pole of inaccessibility of its border) but is the
106
- * anchor those two would start from, and is right often enough to be worth
107
- * having while line placement is unwritten. See `docs/prd-maps.md`.
134
+ * Anchors, per tile and style. They depend on neither the zoom nor the
135
+ * camera, and merging a tile's street pieces is the one costly step, so a
136
+ * zoom that collects a tile's candidates again does not work them out
137
+ * again.
138
+ */
139
+ const anchorCache = new WeakMap<
140
+ VectorTile,
141
+ { prepared: PreparedStyle; data: GlLabelData }
142
+ >();
143
+
144
+ function anchorsOf(tile: VectorTile, prepared: PreparedStyle): GlLabelData {
145
+ const hit = anchorCache.get(tile);
146
+ if (hit && hit.prepared === prepared) return hit.data;
147
+ const data = buildTileLabels(tile, prepared, EXTENT);
148
+ anchorCache.set(tile, { prepared, data });
149
+ return data;
150
+ }
151
+
152
+ /** A symbol layer's labelling at a zoom, or null where it does not label. */
153
+ interface LayerPaint {
154
+ id: string;
155
+ size: number;
156
+ color: string;
157
+ halo: string | undefined;
158
+ haloWidth: number;
159
+ rank: number;
160
+ repeat: number;
161
+ }
162
+
163
+ function paintAt(
164
+ layer: MapStyleLayer | undefined,
165
+ zoom: number,
166
+ ): LayerPaint | null {
167
+ if (!layer || layer.type !== 'symbol' || layer.visible === false) {
168
+ return null;
169
+ }
170
+ if (layer.minZoom !== undefined && zoom < layer.minZoom) return null;
171
+ if (layer.maxZoom !== undefined && zoom >= layer.maxZoom) return null;
172
+ const symbol = layer as SymbolLayer;
173
+ return {
174
+ id: layer.id,
175
+ size: num(symbol.textSize, zoom, 12),
176
+ color: symbol.textColor ? resolveZoomed(symbol.textColor, zoom) : '#000000',
177
+ halo: symbol.textHaloColor
178
+ ? resolveZoomed(symbol.textHaloColor, zoom)
179
+ : undefined,
180
+ haloWidth: num(symbol.textHaloWidth, zoom, 1),
181
+ rank: symbol.rank ?? 0,
182
+ repeat: symbol.repeatDistance ?? 250,
183
+ };
184
+ }
185
+
186
+ /**
187
+ * The label candidates one tile contributes: its anchors (`./anchors.ts`),
188
+ * with this zoom's size and colours. A point label anchors at its point,
189
+ * an area's at the middle of its largest ring, and a street's name at the
190
+ * middle of a straight stretch of the street — or of a block of it —
191
+ * turned to lie along it.
108
192
  */
109
193
  export function collectLabels(
110
194
  tile: VectorTile,
111
195
  id: TileId,
112
196
  prepared: PreparedStyle,
113
197
  zoom: number,
114
- buffer: GeometryBuffer,
115
198
  ): LabelCandidate[] {
116
199
  const out: LabelCandidate[] = [];
200
+ const data = anchorsOf(tile, prepared);
117
201
  const n = tileCountAt(id.z);
118
202
  const key = tileKey(id);
119
- for (let i = 0; i < prepared.layers.length; i++) {
120
- const { layer, filter } = prepared.layers[i];
121
- if (layer.type !== 'symbol' || layer.visible === false) continue;
122
- if (layer.minZoom !== undefined && zoom < layer.minZoom) continue;
123
- if (layer.maxZoom !== undefined && zoom >= layer.maxZoom) continue;
124
- const source = tile.layers.get(layer.sourceLayer);
125
- if (!source || source.length === 0) continue;
126
- const symbol = layer as SymbolLayer;
127
- const size = num(symbol.textSize, zoom, 12);
128
- const color = symbol.textColor
129
- ? resolveZoomed(symbol.textColor, zoom)
130
- : '#000000';
131
- const halo = symbol.textHaloColor
132
- ? resolveZoomed(symbol.textHaloColor, zoom)
133
- : undefined;
134
- const haloWidth = num(symbol.textHaloWidth, zoom, 1);
135
- const rank = symbol.rank ?? 0;
136
- const repeat = symbol.repeatDistance ?? 250;
137
- const extent = source.extent;
138
- const cursor = source.feature(0);
139
- for (let f = 0; f < source.length; f++) {
140
- source.seek(f, cursor);
141
- if (!filter(cursor)) continue;
142
- const raw = cursor.get(symbol.textField);
143
- // The schema's fallback: a translated name field is absent for most
144
- // features, and the local `name` is what a map without a translation
145
- // shows rather than nothing.
146
- const text =
147
- typeof raw === 'string' && raw.length > 0
148
- ? raw
149
- : symbol.textField !== 'name'
150
- ? typeof cursor.get('name') === 'string'
151
- ? String(cursor.get('name'))
152
- : ''
153
- : '';
154
- if (text.length === 0) continue;
155
- cursor.readGeometry(buffer);
156
- const anchor = anchorOf(buffer, cursor.type);
157
- if (!anchor) continue;
158
- out.push({
159
- id: `${key}:${layer.id}:${f}`,
160
- key: `${layer.id}|${text}`,
161
- text,
162
- // Tile-local to normalized mercator, the same arithmetic the
163
- // rasterizer's transform does and for the same precision reason.
164
- mx: (id.x + anchor.x / extent) / n,
165
- my: (id.y + anchor.y / extent) / n,
166
- rank,
167
- size,
168
- color,
169
- halo,
170
- haloWidth,
171
- repeat,
172
- });
203
+ // Tile units to normalized mercator.
204
+ const unit = 1 / (EXTENT * n);
205
+ const paints = new Map<number, LayerPaint | null>();
206
+ const a = data.anchors;
207
+ for (let r = 0; r < data.count; r++) {
208
+ const at = r * LABEL_STRIDE;
209
+ const index = a[at + LabelField.layer];
210
+ let paint = paints.get(index);
211
+ if (paint === undefined) {
212
+ paint = paintAt(prepared.layers[index]?.layer, zoom);
213
+ paints.set(index, paint);
173
214
  }
215
+ if (!paint) continue;
216
+ const text = data.texts[a[at + LabelField.text]];
217
+ const point = a[at + LabelField.avail] < 0;
218
+ out.push({
219
+ id: `${key}:${paint.id}:${r}`,
220
+ key: `${paint.id}|${text}`,
221
+ text,
222
+ // Tile-local to normalized mercator, the same arithmetic the
223
+ // rasterizer's transform does and for the same precision reason.
224
+ mx: (id.x + a[at + LabelField.x] / EXTENT) / n,
225
+ my: (id.y + a[at + LabelField.y] / EXTENT) / n,
226
+ angle: point ? 0 : a[at + LabelField.angle],
227
+ avail: point ? Infinity : a[at + LabelField.avail] * unit,
228
+ clear: point ? Infinity : a[at + LabelField.clear] * unit,
229
+ length: a[at + LabelField.length] * unit,
230
+ dev: a[at + LabelField.dev] * unit,
231
+ rank: paint.rank,
232
+ priority: a[at + LabelField.priority],
233
+ size: paint.size,
234
+ color: paint.color,
235
+ halo: paint.halo,
236
+ haloWidth: paint.haloWidth,
237
+ repeat: paint.repeat,
238
+ });
174
239
  }
175
240
  return out;
176
241
  }
177
242
 
178
- function anchorOf(
179
- buffer: GeometryBuffer,
180
- type: GeomType,
181
- ): { x: number; y: number } | null {
182
- if (buffer.points === 0) return null;
183
- if (type === GeomType.Point) {
184
- return { x: buffer.coords[0], y: buffer.coords[1] };
185
- }
186
- // The longest part, by its box — a cheap stand-in for arc length that
187
- // does not need a second pass over the vertices.
188
- let best = 0;
189
- let bestSpan = -1;
190
- for (let part = 0; part < buffer.parts; part++) {
191
- const at = part * 4;
192
- const span =
193
- buffer.partBounds[at + 2] -
194
- buffer.partBounds[at] +
195
- (buffer.partBounds[at + 3] - buffer.partBounds[at + 1]);
196
- if (span > bestSpan) {
197
- bestSpan = span;
198
- best = part;
199
- }
200
- }
201
- const from = buffer.starts[best];
202
- const to = buffer.starts[best + 1];
203
- if (to <= from) return null;
204
- const mid = from + ((to - from) >> 1);
205
- return { x: buffer.coords[mid * 2], y: buffer.coords[mid * 2 + 1] };
206
- }
207
-
208
243
  /** Cache of shaped strings, owned by the element. */
209
244
  export class LabelShaper {
210
245
  private readonly _cache = new Map<string, ShapedLabel>();
@@ -267,13 +302,25 @@ export class LabelShaper {
267
302
  }
268
303
  }
269
304
 
305
+ /** A street name is set level — on whole pixels, which is sharper — when
306
+ * its street is this close to level. */
307
+ function levelled(angle: number): number {
308
+ return Math.abs(angle) < LEVEL_SNAP ? 0 : angle;
309
+ }
310
+
270
311
  /**
271
312
  * Place candidates against each other, in world pixels.
272
313
  *
273
- * Greedy by rank, which is what every map does: sort, and take a label if
274
- * nothing already taken overlaps it. The order has to be **total and
275
- * stable** — rank, then the candidate's id — because a tie broken by array
276
- * order would flicker as tiles arrived in a different order.
314
+ * Greedy, which is what every map does: sort, and take a label if it fits
315
+ * where it is and nothing already taken overlaps it. The order is the GL
316
+ * renderer's, less what depends on the view: rank, then importance within
317
+ * the layer, then a street name that clears its junctions over one that
318
+ * would cover a crossing street, then the longer straight run. It has to be
319
+ * **total and stable** — the candidate's id last — because a tie broken by
320
+ * array order would flicker as tiles arrived in a different order.
321
+ *
322
+ * A street name fits where the GL renderer says one does: on a stretch
323
+ * straight enough for its size, and long enough for all of it.
277
324
  */
278
325
  export function placeLabels(
279
326
  candidates: readonly LabelCandidate[],
@@ -282,9 +329,19 @@ export function placeLabels(
282
329
  options?: { padding?: number },
283
330
  ): PlacedLabel[] {
284
331
  const padding = options?.padding ?? 2;
285
- const sorted = [...candidates].sort(
286
- (a, b) => b.rank - a.rank || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
287
- );
332
+ const clean = (c: LabelCandidate): number =>
333
+ c.clear * worldSize >= estimateWidth(c.text, c.size) / 2 ? 1 : 0;
334
+ const sorted = [...candidates]
335
+ .map((c) => ({ c, clean: clean(c) }))
336
+ .sort(
337
+ (a, b) =>
338
+ b.c.rank - a.c.rank ||
339
+ b.c.priority - a.c.priority ||
340
+ b.clean - a.clean ||
341
+ b.c.length - a.c.length ||
342
+ (a.c.id < b.c.id ? -1 : a.c.id > b.c.id ? 1 : 0),
343
+ )
344
+ .map((entry) => entry.c);
288
345
  const placed: PlacedLabel[] = [];
289
346
  // A sparse grid over world pixels. The cell is sized for the labels
290
347
  // themselves, so a box touches one or two cells and a collision test is a
@@ -297,18 +354,26 @@ export function placeLabels(
297
354
  const byText = new Map<string, { wx: number; wy: number }[]>();
298
355
 
299
356
  for (const candidate of sorted) {
357
+ // Bent under its size at this zoom: not a stretch to set a name on.
358
+ if (candidate.dev * worldSize > candidate.size * STRAIGHT_FRACTION) {
359
+ continue;
360
+ }
300
361
  const shaped = shaper.shape(
301
362
  candidate.text,
302
363
  candidate.size,
303
364
  candidate.color,
304
365
  );
305
366
  if (!shaped) return placed; // no font manager: nothing can be measured
367
+ // Longer than the straight stretch it would sit on, with a margin past
368
+ // each end: it would hang off the street, over whatever the street
369
+ // turns into.
370
+ const need = shaped.width / 2 + shaped.height * LINE_MARGIN;
371
+ if (candidate.avail * worldSize < need) continue;
306
372
  const wx = candidate.mx * worldSize;
307
373
  const wy = candidate.my * worldSize;
308
374
  // The repeat test before the overlap test, because it is the cheaper
309
- // one and because it is what rejects most of a street layer: a street
310
- // is one feature per segment, so the same name arrives a dozen times
311
- // strung out along the road, none of them overlapping any other.
375
+ // one and because it is what rejects most of a street layer: a long
376
+ // street offers its name every block, none of them overlapping.
312
377
  if (candidate.repeat > 0) {
313
378
  const already = byText.get(candidate.key);
314
379
  if (already) {
@@ -325,8 +390,15 @@ export function placeLabels(
325
390
  if (tooClose) continue;
326
391
  }
327
392
  }
328
- const width = shaped.width + padding * 2;
329
- const height = shaped.height + padding * 2;
393
+ // The turned text's bounding box: what it covers, as far as another
394
+ // label can tell.
395
+ const angle = levelled(candidate.angle);
396
+ const w = shaped.width + padding * 2;
397
+ const h = shaped.height + padding * 2;
398
+ const cos = Math.abs(Math.cos(angle));
399
+ const sin = Math.abs(Math.sin(angle));
400
+ const width = w * cos + h * sin;
401
+ const height = w * sin + h * cos;
330
402
  const left = wx - width / 2;
331
403
  const top = wy - height / 2;
332
404
  const x0 = Math.floor(left / CELL);
@@ -354,6 +426,7 @@ export function placeLabels(
354
426
  if (!free) continue;
355
427
  const entry: PlacedLabel = {
356
428
  ...candidate,
429
+ angle,
357
430
  wx,
358
431
  wy,
359
432
  width,
@@ -387,6 +460,10 @@ export function placeLabels(
387
460
  * composites of two shaped runs. That is cheap for the few dozen labels a
388
461
  * pane holds and it is the difference between a place name that is
389
462
  * readable over a motorway and one that is not.
463
+ *
464
+ * A level label is drawn on whole device pixels. A street name along its
465
+ * street is drawn turned about its centre, where the context can turn —
466
+ * and level where it cannot, on a context with no transform.
390
467
  */
391
468
  export function drawLabels(
392
469
  ctx: MapCanvas,
@@ -401,28 +478,43 @@ export function drawLabels(
401
478
  const world = transform.world;
402
479
  const originX = pane.x + transform.paneX - transform.centerX * world;
403
480
  const originY = pane.y + transform.paneY - transform.centerY * world;
481
+ const turns = typeof ctx.rotate === 'function' && !!ctx.translate;
404
482
  for (const label of placed) {
405
- const x = originX + label.wx - label.shaped.width / 2;
406
- const y = originY + label.wy - label.shaped.height / 2;
483
+ const cx = originX + label.wx;
484
+ const cy = originY + label.wy;
485
+ const halfW = label.width / 2;
486
+ const halfH = label.height / 2;
407
487
  if (
408
- x + label.shaped.width < pane.x ||
409
- y + label.shaped.height < pane.y ||
410
- x > pane.x + pane.width ||
411
- y > pane.y + pane.height
488
+ cx + halfW < pane.x ||
489
+ cy + halfH < pane.y ||
490
+ cx - halfW > pane.x + pane.width ||
491
+ cy - halfH > pane.y + pane.height
412
492
  ) {
413
493
  continue;
414
494
  }
415
495
  if (
416
496
  clip &&
417
- (x + label.shaped.width < clip.x ||
418
- y + label.shaped.height < clip.y ||
419
- x > clip.x + clip.width ||
420
- y > clip.y + clip.height)
497
+ (cx + halfW < clip.x ||
498
+ cy + halfH < clip.y ||
499
+ cx - halfW > clip.x + clip.width ||
500
+ cy - halfH > clip.y + clip.height)
421
501
  ) {
422
502
  continue;
423
503
  }
424
- const dx = Math.round(x * scale);
425
- const dy = Math.round(y * scale);
504
+ const { width, height } = label.shaped;
505
+ const turned = label.angle !== 0 && turns;
506
+ let dx: number;
507
+ let dy: number;
508
+ if (turned) {
509
+ ctx.save();
510
+ ctx.translate!(cx * scale, cy * scale);
511
+ ctx.rotate!(label.angle);
512
+ dx = (-width * scale) / 2;
513
+ dy = (-height * scale) / 2;
514
+ } else {
515
+ dx = Math.round((cx - width / 2) * scale);
516
+ dy = Math.round((cy - height / 2) * scale);
517
+ }
426
518
  if (label.halo !== undefined && label.haloWidth > 0) {
427
519
  const halo = shaper.shape(label.text, label.size, label.halo);
428
520
  if (halo) {
@@ -434,6 +526,7 @@ export function drawLabels(
434
526
  }
435
527
  }
436
528
  label.shaped.layout.draw(ctx, dx, dy);
529
+ if (turned) ctx.restore();
437
530
  drawn++;
438
531
  }
439
532
  return drawn;