@tangram.gl/tangram-renderer 0.0.0-stage → 0.0.1

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 (321) hide show
  1. package/README.md +60 -2
  2. package/dist/core.js +36923 -0
  3. package/dist/core.js.map +6 -0
  4. package/dist/index.js +32 -0
  5. package/dist/loaders-gl-worker.js +82 -0
  6. package/dist/loaders-gl-worker.js.map +6 -0
  7. package/dist/map-logic.js +2 -0
  8. package/dist/map-logic.js.map +6 -0
  9. package/dist/projected-basemaps-worker.js +3 -0
  10. package/dist/projected-basemaps-worker.js.map +6 -0
  11. package/dist/shader-modules.js +141 -0
  12. package/dist/shader-modules.js.map +6 -0
  13. package/dist/style-schema.js +126 -0
  14. package/dist/style-schema.js.map +6 -0
  15. package/dist/tangram-style.schema.json +882 -0
  16. package/dist/tangram.debug.mjs +37139 -0
  17. package/dist/tangram.debug.mjs.map +6 -0
  18. package/dist/tangram.min.mjs +4950 -0
  19. package/dist/types/builders/earcut.d.ts +15 -0
  20. package/dist/types/core.d.ts +29 -0
  21. package/dist/types/gl/constants.d.ts +2 -0
  22. package/dist/types/gl/globe_mesh.d.ts +48 -0
  23. package/dist/types/gl/glsl.d.ts +17 -0
  24. package/dist/types/gl/vertex-constants.d.ts +4 -0
  25. package/dist/types/gl/vertex-types.d.ts +34 -0
  26. package/dist/types/gl/vertex_data.d.ts +32 -0
  27. package/dist/types/gl/vertex_elements.d.ts +8 -0
  28. package/dist/types/gl/vertex_layout.d.ts +32 -0
  29. package/dist/types/glsl.d.ts +14 -0
  30. package/dist/types/gpu/luma_device_renderer.d.ts +66 -0
  31. package/dist/types/gpu/resource_lifecycle.d.ts +4 -0
  32. package/dist/types/gpu/tangram_gpu_backend.d.ts +151 -0
  33. package/dist/types/index.d.ts +155 -0
  34. package/dist/types/lights/light-definitions.d.ts +121 -0
  35. package/dist/types/map-logic/draped-terrain-bounds.d.ts +41 -0
  36. package/dist/types/map-logic/globe-terrain-preparation.d.ts +85 -0
  37. package/dist/types/map-logic/globe-terrain.d.ts +54 -0
  38. package/dist/types/map-logic/index.d.ts +33 -0
  39. package/dist/types/map-logic/label-identity.d.ts +22 -0
  40. package/dist/types/map-logic/label-placement.d.ts +32 -0
  41. package/dist/types/map-logic/screen-bounds.d.ts +15 -0
  42. package/dist/types/map-logic/terrain-data.d.ts +55 -0
  43. package/dist/types/map-logic/terrain-error.d.ts +51 -0
  44. package/dist/types/map-logic/terrain-ground-topology.d.ts +26 -0
  45. package/dist/types/map-logic/terrain-height-field.d.ts +59 -0
  46. package/dist/types/map-logic/terrain-navigation.d.ts +27 -0
  47. package/dist/types/map-logic/terrain-neighborhood.d.ts +5 -0
  48. package/dist/types/map-logic/terrain-query.d.ts +33 -0
  49. package/dist/types/map-logic/terrain-resource-budget.d.ts +51 -0
  50. package/dist/types/map-logic/terrain-transition-topology.d.ts +23 -0
  51. package/dist/types/map-logic/tile-build-queue.d.ts +51 -0
  52. package/dist/types/map-logic/tile-cache-policy.d.ts +46 -0
  53. package/dist/types/map-logic/tile-residency.d.ts +17 -0
  54. package/dist/types/map-logic/triangle-query-index.d.ts +87 -0
  55. package/dist/types/procedures/js-yaml.d.ts +15 -0
  56. package/dist/types/procedures/mesh-projector.d.ts +107 -0
  57. package/dist/types/procedures/mvt-dependencies.d.ts +20 -0
  58. package/dist/types/procedures/projected-coordinate-transform.d.ts +27 -0
  59. package/dist/types/procedures/projection-batch-executor.d.ts +53 -0
  60. package/dist/types/procedures/scene-yaml-legacy.d.ts +6 -0
  61. package/dist/types/procedures/terrain-draping.d.ts +165 -0
  62. package/dist/types/procedures/terrain-globe.d.ts +6 -0
  63. package/dist/types/procedures/terrain-styled-topology.d.ts +34 -0
  64. package/dist/types/procedures/web-mercator-legacy.d.ts +4 -0
  65. package/dist/types/procedures/web-mercator-math.d.ts +4 -0
  66. package/dist/types/scene/globals.d.ts +11 -0
  67. package/dist/types/scene/ground_footprint.d.ts +16 -0
  68. package/dist/types/scene/host_frame.d.ts +54 -0
  69. package/dist/types/scene/projection_math.d.ts +52 -0
  70. package/dist/types/scene/scene-resource-types.d.ts +177 -0
  71. package/dist/types/scene/scene_bundle.d.ts +69 -0
  72. package/dist/types/scene/visibility_adapter.d.ts +106 -0
  73. package/dist/types/selection/draped-terrain-surface.d.ts +78 -0
  74. package/dist/types/selection/terrain_surface.d.ts +149 -0
  75. package/dist/types/shader-modules/globe-horizon.d.ts +15 -0
  76. package/dist/types/shader-modules/height-decode.d.ts +13 -0
  77. package/dist/types/shader-modules/hillshade.d.ts +13 -0
  78. package/dist/types/shader-modules/index.d.ts +6 -0
  79. package/dist/types/shader-modules/planar.d.ts +12 -0
  80. package/dist/types/shader-modules/sphere-map.d.ts +13 -0
  81. package/dist/types/shader-modules/triplanar.d.ts +15 -0
  82. package/dist/types/sources/decoded_tile_store.d.ts +95 -0
  83. package/dist/types/sources/tile_source_metadata.d.ts +32 -0
  84. package/dist/types/styles/text/fontfaceobserver.d.ts +10 -0
  85. package/dist/types/tile/tile_id.d.ts +37 -0
  86. package/dist/types/tile/tile_traversal_adapter.d.ts +63 -0
  87. package/dist/types/types.d.ts +293 -0
  88. package/dist/types/utils/geo.d.ts +57 -0
  89. package/dist/types/utils/hash.d.ts +1 -0
  90. package/dist/types/utils/log.d.ts +27 -0
  91. package/dist/types/utils/props.d.ts +6 -0
  92. package/dist/types/utils/subscribe.d.ts +10 -0
  93. package/dist/types/utils/thread.d.ts +6 -0
  94. package/dist/types/utils/urls.d.ts +26 -0
  95. package/dist/types/utils/utils.d.ts +26 -0
  96. package/dist/types/utils/version.d.ts +2 -0
  97. package/dist/types/utils/worker-types.d.ts +68 -0
  98. package/dist/types/utils/worker_broker.d.ts +3 -0
  99. package/package.json +103 -3
  100. package/src/builders/common.ts +41 -0
  101. package/src/builders/earcut.d.ts +15 -0
  102. package/src/builders/geometry-types.ts +60 -0
  103. package/src/builders/points.ts +115 -0
  104. package/src/builders/polygons.ts +225 -0
  105. package/src/builders/polylines.ts +682 -0
  106. package/src/builders/wireframe.ts +31 -0
  107. package/src/core.d.ts +29 -0
  108. package/src/core.ts +33 -0
  109. package/src/experimental/loaders-gl-worker.ts +19 -0
  110. package/src/experimental/projected-basemaps-worker.ts +13 -0
  111. package/src/experimental/projected-mesh-cache.ts +137 -0
  112. package/src/experimental/projected-mesh-domain.ts +110 -0
  113. package/src/experimental/projected-mesh-refinement.ts +193 -0
  114. package/src/experimental/projected-mesh.ts +399 -0
  115. package/src/experimental/projected-normal.ts +26 -0
  116. package/src/experimental/projected-symbol-mesh.ts +74 -0
  117. package/src/gl/constants.ts +18 -0
  118. package/src/gl/context.ts +103 -0
  119. package/src/gl/extensions.ts +30 -0
  120. package/src/gl/globe_mesh.ts +285 -0
  121. package/src/gl/glsl.ts +257 -0
  122. package/src/gl/mesh-types.ts +56 -0
  123. package/src/gl/render_state.ts +111 -0
  124. package/src/gl/shader_program.ts +1310 -0
  125. package/src/gl/texture.ts +814 -0
  126. package/src/gl/uniform_buffer.ts +340 -0
  127. package/src/gl/vao.ts +154 -0
  128. package/src/gl/vbo_mesh.ts +349 -0
  129. package/src/gl/vertex-constants.ts +19 -0
  130. package/src/gl/vertex-types.ts +35 -0
  131. package/src/gl/vertex_data.ts +126 -0
  132. package/src/gl/vertex_elements.ts +54 -0
  133. package/src/gl/vertex_layout.ts +241 -0
  134. package/src/glsl.d.ts +14 -0
  135. package/src/gpu/luma_device_renderer.ts +579 -0
  136. package/src/gpu/pixel_readback.ts +51 -0
  137. package/src/gpu/resource_lifecycle.ts +32 -0
  138. package/src/gpu/tangram_gpu_backend.ts +195 -0
  139. package/src/index.d.ts +155 -0
  140. package/src/index.ts +105 -0
  141. package/src/labels/collision-types.ts +94 -0
  142. package/src/labels/collision.ts +273 -0
  143. package/src/labels/collision_grid.ts +61 -0
  144. package/src/labels/intersect.ts +30 -0
  145. package/src/labels/label-types.ts +91 -0
  146. package/src/labels/label.ts +207 -0
  147. package/src/labels/label_line.ts +801 -0
  148. package/src/labels/label_point.ts +131 -0
  149. package/src/labels/main-pass-types.ts +73 -0
  150. package/src/labels/main_pass.ts +221 -0
  151. package/src/labels/point_anchor.ts +100 -0
  152. package/src/labels/point_placement.ts +150 -0
  153. package/src/labels/projected-pass.ts +259 -0
  154. package/src/labels/repeat_group.ts +72 -0
  155. package/src/lights/ambient_light.glsl +18 -0
  156. package/src/lights/directional_light.glsl +39 -0
  157. package/src/lights/geographic-lights.ts +21 -0
  158. package/src/lights/light-definitions.ts +285 -0
  159. package/src/lights/light.ts +590 -0
  160. package/src/lights/lighting-uniforms.ts +80 -0
  161. package/src/lights/lighting-wgsl.ts +116 -0
  162. package/src/lights/material.glsl +237 -0
  163. package/src/lights/material.ts +145 -0
  164. package/src/lights/native-falloff.ts +37 -0
  165. package/src/lights/point_light.glsl +96 -0
  166. package/src/lights/spot_light.glsl +111 -0
  167. package/src/map-logic/draped-terrain-bounds.ts +71 -0
  168. package/src/map-logic/globe-terrain-preparation.ts +217 -0
  169. package/src/map-logic/globe-terrain.ts +296 -0
  170. package/src/map-logic/index.ts +37 -0
  171. package/src/map-logic/label-identity.ts +36 -0
  172. package/src/map-logic/label-placement.ts +117 -0
  173. package/src/map-logic/screen-bounds.ts +34 -0
  174. package/src/map-logic/terrain-data.ts +107 -0
  175. package/src/map-logic/terrain-error.ts +134 -0
  176. package/src/map-logic/terrain-ground-topology.ts +164 -0
  177. package/src/map-logic/terrain-height-field.ts +254 -0
  178. package/src/map-logic/terrain-navigation.ts +50 -0
  179. package/src/map-logic/terrain-neighborhood.ts +43 -0
  180. package/src/map-logic/terrain-query.ts +51 -0
  181. package/src/map-logic/terrain-resource-budget.ts +124 -0
  182. package/src/map-logic/terrain-transition-topology.ts +164 -0
  183. package/src/map-logic/tile-build-queue.ts +110 -0
  184. package/src/map-logic/tile-cache-policy.ts +89 -0
  185. package/src/map-logic/tile-residency.ts +50 -0
  186. package/src/map-logic/triangle-query-index.ts +265 -0
  187. package/src/procedures/js-yaml.d.ts +15 -0
  188. package/src/procedures/mesh-projector.ts +191 -0
  189. package/src/procedures/mlt-loaders.ts +55 -0
  190. package/src/procedures/mvt-dependencies.d.ts +20 -0
  191. package/src/procedures/mvt-legacy.ts +150 -0
  192. package/src/procedures/mvt-loaders.ts +53 -0
  193. package/src/procedures/mvt-parser.ts +48 -0
  194. package/src/procedures/mvt-properties.ts +36 -0
  195. package/src/procedures/mvt-tile-provider.ts +72 -0
  196. package/src/procedures/pmtiles-loader.ts +75 -0
  197. package/src/procedures/projected-coordinate-transform.ts +79 -0
  198. package/src/procedures/projection-batch-executor.ts +140 -0
  199. package/src/procedures/scene-yaml-legacy.ts +16 -0
  200. package/src/procedures/scene-yaml-loaders.ts +10 -0
  201. package/src/procedures/terrain-draping.ts +713 -0
  202. package/src/procedures/terrain-globe.ts +32 -0
  203. package/src/procedures/terrain-styled-topology.ts +184 -0
  204. package/src/procedures/web-mercator-legacy.ts +24 -0
  205. package/src/procedures/web-mercator-math.ts +50 -0
  206. package/src/scene/camera.ts +298 -0
  207. package/src/scene/camera_base.ts +116 -0
  208. package/src/scene/classic_scene.ts +20 -0
  209. package/src/scene/external_camera.ts +101 -0
  210. package/src/scene/globals.ts +120 -0
  211. package/src/scene/ground_footprint.ts +139 -0
  212. package/src/scene/host_frame.ts +326 -0
  213. package/src/scene/projected_tile_lod.ts +125 -0
  214. package/src/scene/projection_math.ts +142 -0
  215. package/src/scene/projection_shaders.ts +183 -0
  216. package/src/scene/renderer.ts +516 -0
  217. package/src/scene/scene-resource-types.ts +174 -0
  218. package/src/scene/scene.ts +2378 -0
  219. package/src/scene/scene_bundle.ts +304 -0
  220. package/src/scene/scene_debug.ts +129 -0
  221. package/src/scene/scene_loader.ts +402 -0
  222. package/src/scene/scene_worker.ts +528 -0
  223. package/src/scene/view.ts +683 -0
  224. package/src/scene/visibility_adapter.ts +319 -0
  225. package/src/scene/worker_url.ts +15 -0
  226. package/src/selection/draped-terrain-surface.ts +377 -0
  227. package/src/selection/selection-types.ts +79 -0
  228. package/src/selection/selection.ts +542 -0
  229. package/src/selection/selection_fragment.glsl +18 -0
  230. package/src/selection/selection_globals.glsl +9 -0
  231. package/src/selection/selection_pixels.ts +59 -0
  232. package/src/selection/selection_vertex.glsl +15 -0
  233. package/src/selection/terrain_surface.ts +298 -0
  234. package/src/shader-modules/globe-horizon.ts +52 -0
  235. package/src/shader-modules/height-decode.ts +41 -0
  236. package/src/shader-modules/hillshade.ts +70 -0
  237. package/src/shader-modules/index.ts +10 -0
  238. package/src/shader-modules/planar.ts +24 -0
  239. package/src/shader-modules/sphere-map.ts +38 -0
  240. package/src/shader-modules/triplanar.ts +45 -0
  241. package/src/sources/aligned_tile_source.ts +122 -0
  242. package/src/sources/data_source.ts +655 -0
  243. package/src/sources/decoded_tile_store.ts +245 -0
  244. package/src/sources/geojson.ts +300 -0
  245. package/src/sources/mvt.ts +247 -0
  246. package/src/sources/raster.ts +341 -0
  247. package/src/sources/shared_tile_source_adapter.ts +117 -0
  248. package/src/sources/sources.ts +10 -0
  249. package/src/sources/tile_source_adapter.ts +84 -0
  250. package/src/sources/tile_source_metadata.ts +65 -0
  251. package/src/sources/tile_source_state.ts +112 -0
  252. package/src/sources/topojson.ts +90 -0
  253. package/src/styles/feature_annotations.ts +16 -0
  254. package/src/styles/filter.ts +190 -0
  255. package/src/styles/geometry-style-types.ts +209 -0
  256. package/src/styles/globe_visibility_wgsl.ts +22 -0
  257. package/src/styles/layer-types.ts +45 -0
  258. package/src/styles/layer.ts +586 -0
  259. package/src/styles/lines/dasharray.ts +49 -0
  260. package/src/styles/lines/lines.ts +707 -0
  261. package/src/styles/lines/lines_wgsl.ts +224 -0
  262. package/src/styles/points/point-runtime-types.ts +125 -0
  263. package/src/styles/points/points.ts +1079 -0
  264. package/src/styles/points/points_fragment.glsl +126 -0
  265. package/src/styles/points/points_vertex.glsl +230 -0
  266. package/src/styles/points/points_wgsl.ts +189 -0
  267. package/src/styles/polygons/polygons.ts +268 -0
  268. package/src/styles/polygons/polygons_fragment.glsl +173 -0
  269. package/src/styles/polygons/polygons_vertex.glsl +213 -0
  270. package/src/styles/polygons/polygons_wgsl.ts +127 -0
  271. package/src/styles/property-types.ts +125 -0
  272. package/src/styles/raster/raster.ts +33 -0
  273. package/src/styles/raster/raster_globals.glsl +46 -0
  274. package/src/styles/style-mixing-types.ts +101 -0
  275. package/src/styles/style-runtime-types.ts +220 -0
  276. package/src/styles/style-schema.ts +133 -0
  277. package/src/styles/style.ts +1033 -0
  278. package/src/styles/style_globals.glsl +69 -0
  279. package/src/styles/style_manager.ts +464 -0
  280. package/src/styles/style_parser.ts +620 -0
  281. package/src/styles/text/font_manager.ts +180 -0
  282. package/src/styles/text/fontfaceobserver.d.ts +10 -0
  283. package/src/styles/text/text-types.ts +220 -0
  284. package/src/styles/text/text.ts +378 -0
  285. package/src/styles/text/text_canvas.ts +773 -0
  286. package/src/styles/text/text_labels.ts +436 -0
  287. package/src/styles/text/text_segments.ts +142 -0
  288. package/src/styles/text/text_settings.ts +228 -0
  289. package/src/styles/text/text_wgsl.ts +159 -0
  290. package/src/styles/text/text_wrap.ts +194 -0
  291. package/src/tile/globe_tile_preload.ts +40 -0
  292. package/src/tile/tangram_tileset_2d.ts +108 -0
  293. package/src/tile/tile-build-types.ts +155 -0
  294. package/src/tile/tile.ts +807 -0
  295. package/src/tile/tile_id.ts +172 -0
  296. package/src/tile/tile_manager.ts +889 -0
  297. package/src/tile/tile_pyramid.ts +119 -0
  298. package/src/tile/tile_resource_cache.ts +91 -0
  299. package/src/tile/tile_traversal_adapter.ts +132 -0
  300. package/src/types.ts +332 -0
  301. package/src/utils/debounce.ts +17 -0
  302. package/src/utils/debug_settings.ts +43 -0
  303. package/src/utils/errors.ts +12 -0
  304. package/src/utils/functions.ts +95 -0
  305. package/src/utils/geo.ts +364 -0
  306. package/src/utils/hash.ts +14 -0
  307. package/src/utils/log.ts +108 -0
  308. package/src/utils/media_capture.ts +193 -0
  309. package/src/utils/merge.ts +44 -0
  310. package/src/utils/obb.ts +134 -0
  311. package/src/utils/props.ts +43 -0
  312. package/src/utils/slice.ts +14 -0
  313. package/src/utils/subscribe.ts +58 -0
  314. package/src/utils/task.ts +154 -0
  315. package/src/utils/thread.ts +26 -0
  316. package/src/utils/urls.ts +223 -0
  317. package/src/utils/utils.ts +310 -0
  318. package/src/utils/vector.ts +197 -0
  319. package/src/utils/version.ts +8 -0
  320. package/src/utils/worker-types.ts +79 -0
  321. package/src/utils/worker_broker.ts +523 -0
@@ -0,0 +1,773 @@
1
+ // Tangram
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) 2013-2016 Brett Camper and Mapzen
4
+ // Copyright (c) 2026 vis.gl contributors
5
+
6
+ import type {TaskRecord} from '../../utils/task';
7
+ import type {TextSettingsResult} from './text_settings';
8
+ import type {LabelPointCoordinate} from '../../labels/label-types';
9
+ import type {TextTable, TextSize, TextLine, TextMeasurement, TextAtlas, TextAtlasCursor, TextSizesTask, TextRasterTask} from './text-types';
10
+ import log from '../../utils/log';
11
+ import Utils from '../../utils/utils';
12
+ import Texture from '../../gl/texture';
13
+ import FontManager from './font_manager';
14
+ import Task from '../../utils/task';
15
+ import StyleParser from '../style_parser';
16
+ /** Temporary parser boundary until the full style parser is checked. */
17
+ const typedStyleParser = StyleParser as unknown as {parsePositiveNumber(value: unknown): number};
18
+ /** Texture registry boundary; GPU ownership remains with the existing implementation. */
19
+ const typedTexture = Texture as unknown as {
20
+ getTexcoordsForSprite(origin: LabelPointCoordinate, size: LabelPointCoordinate, atlas: LabelPointCoordinate): number[];
21
+ create(context: unknown, name: string, options: {element: HTMLCanvasElement; filtering: string; UNPACK_PREMULTIPLY_ALPHA_WEBGL: boolean}): unknown;
22
+ retain(name: string): void;
23
+ release(name: string): void;
24
+ };
25
+ import MultiLine from './text_wrap';
26
+ import { splitLabelText, isTextRTL, isTextNeutral, isTextCurveBlacklisted } from './text_segments';
27
+ import debugSettings from '../../utils/debug_settings';
28
+
29
+ /** Measures, packs, and rasterizes per-tile label atlases on the main thread. */
30
+ export default class TextCanvas {
31
+ /** Canvas and context installed by createCanvas. */
32
+ declare canvas: HTMLCanvasElement;
33
+ declare context: CanvasRenderingContext2D;
34
+ /** Pixel paddings around rendered text. */
35
+ declare vertical_text_buffer: number;
36
+ declare horizontal_text_buffer: number;
37
+ declare background_size: number;
38
+ /** Device-pixel font size installed by setFont before measurement. */
39
+ declare px_size: number;
40
+ /** CSS size parser and shared bounded measurement/segmentation cache. */
41
+ declare static font_size_re: RegExp;
42
+ declare static cache: {
43
+ text: Record<string, Record<string, TextMeasurement>>;
44
+ text_count: number;
45
+ text_count_max: number;
46
+ segment: Record<string, string[]>;
47
+ segment_count_max: number;
48
+ stats: {text_hits: number; text_misses: number; segment_hits: number; segment_misses: number};
49
+ };
50
+
51
+
52
+ constructor () {
53
+ this.createCanvas(); // create initial canvas and context
54
+ this.vertical_text_buffer = 8; // vertical pixel padding around text
55
+ this.horizontal_text_buffer = 4; // text styling such as italic emphasis is not measured by the Canvas API, so padding is necessary
56
+ this.background_size = 4; // padding around label for optional background box (TODO: make configurable?)
57
+ }
58
+
59
+ createCanvas () {
60
+ this.canvas = document.createElement('canvas');
61
+ this.canvas.style.backgroundColor = 'transparent'; // render text on transparent background
62
+ this.context = this.canvas.getContext('2d')!;
63
+ }
64
+
65
+ resize (width: number, height: number): void {
66
+ this.canvas.width = width;
67
+ this.canvas.height = height;
68
+ this.context.clearRect(0, 0, width, height);
69
+ }
70
+
71
+ // Set font style params for canvas drawing
72
+ setFont ({ font_css, fill, stroke, stroke_width, px_size, supersample }: TextSettingsResult): void {
73
+ this.px_size = px_size!;
74
+ let ctx = this.context;
75
+ let dpr = Utils.device_pixel_ratio! * supersample;
76
+
77
+ if (stroke && stroke_width! > 0) {
78
+ ctx.strokeStyle = stroke;
79
+ ctx.lineWidth = stroke_width! * dpr;
80
+ }
81
+ ctx.fillStyle = fill!;
82
+
83
+ ctx.font = font_css!;
84
+ ctx.miterLimit = 2;
85
+ }
86
+
87
+ async textSizes (tile_id: string, texts: TextTable): Promise<TextTable | undefined> {
88
+ await FontManager.loadFonts();
89
+ return Task.add({
90
+ type: 'textSizes',
91
+ run: this.processTextSizesTask.bind(this),
92
+ texts,
93
+ tile_id,
94
+ cursor: {
95
+ styles: Object.keys(texts),
96
+ texts: null,
97
+ style_idx: null,
98
+ text_idx: null
99
+ }
100
+ });
101
+ }
102
+
103
+ processTextSizesTask (task: TaskRecord<TextTable>): boolean {
104
+ let { cursor, texts } = task as TextSizesTask;
105
+ cursor.style_idx = cursor.style_idx || 0;
106
+
107
+ while (cursor.style_idx! < cursor.styles.length) {
108
+ let style = cursor.styles[cursor.style_idx!];
109
+ if (cursor.text_idx == null) {
110
+ cursor.text_idx = 0;
111
+ cursor.texts = Object.keys(texts[style]);
112
+ }
113
+
114
+ let text_infos = texts[style];
115
+ let first = true;
116
+
117
+ while (cursor.text_idx < cursor.texts!.length) {
118
+ let text = cursor.texts![cursor.text_idx];
119
+ let text_info = text_infos[text];
120
+ let text_settings = text_info.text_settings;
121
+
122
+ if (first) {
123
+ this.setFont(text_settings);
124
+ first = false;
125
+ }
126
+
127
+ // add size of full text string
128
+ text_info.size = this.textSize(style, text, text_settings).size;
129
+
130
+ // if text may curve, calculate per-segment as well
131
+ if (text_settings.can_articulate) {
132
+ let rtl = false;
133
+ let bidi = false;
134
+ if (isTextRTL(text)) {
135
+ if (!isTextNeutral(text)) {
136
+ bidi = true;
137
+ }
138
+ else {
139
+ rtl = true;
140
+ }
141
+ }
142
+
143
+ text_info.isRTL = rtl;
144
+ text_info.no_curving = bidi || isTextCurveBlacklisted(text); // used in LabelLine to prevent curved labels
145
+ text_info.vertical_buffer = this.vertical_text_buffer;
146
+ text_info.segment_sizes = [];
147
+
148
+ if (!text_info.no_curving) {
149
+ let segments = splitLabelText(text, rtl, TextCanvas.cache);
150
+ text_info.segments = segments;
151
+ for (let i = 0; i < segments.length; i++){
152
+ text_info.segment_sizes!.push(this.textSize(style, segments[i], text_settings).size);
153
+ }
154
+ }
155
+ }
156
+
157
+ cursor.text_idx++;
158
+
159
+ if (!Task.shouldContinue(task)) {
160
+ return false;
161
+ }
162
+ }
163
+ cursor.text_idx = null;
164
+ cursor.style_idx!++;
165
+ }
166
+
167
+ Task.finish(task, texts);
168
+ return true;
169
+ }
170
+
171
+ // Computes width and height of text based on current font style
172
+ // Includes word wrapping, returns size info for whole text block and individual lines
173
+ textSize(style: string, text: string, { transform, text_wrap, max_lines, stroke_width = 0, background_color, background_stroke_width = 0, background_width, underline_width = 0, supersample }: TextSettingsResult): TextMeasurement {
174
+ // Check cache first
175
+ TextCanvas.cache.text[style] = TextCanvas.cache.text[style] || {};
176
+ if (TextCanvas.cache.text[style][text]) {
177
+ TextCanvas.cache.stats.text_hits++;
178
+ return TextCanvas.cache.text[style][text];
179
+ }
180
+ TextCanvas.cache.stats.text_misses++;
181
+ TextCanvas.cache.text_count++;
182
+
183
+ // Calc and store in cache
184
+ const dpr = Utils.device_pixel_ratio! * supersample;
185
+ const str = this.applyTextTransform(text, transform);
186
+ const ctx = this.context;
187
+ const vertical_buffer = this.vertical_text_buffer * dpr;
188
+ const horizontal_buffer = (stroke_width + this.horizontal_text_buffer) * dpr;
189
+
190
+ background_width = background_width != null ? background_width : this.background_size; // apply default background width
191
+ const background_size = (background_color || background_stroke_width) ? (background_width + background_stroke_width) * dpr : 0;
192
+
193
+ const leading = (2 + underline_width + (underline_width ? (stroke_width + 1) : 0)) * dpr; // adjust for underline and text stroke
194
+ const line_height = this.px_size + leading; // px_size already in device pixels
195
+
196
+ // Parse string into series of lines if it exceeds the text wrapping value or contains line breaks
197
+ // const multiline = MultiLine.parse(str, text_wrap!, max_lines!, line_height, ctx);
198
+ let { width, height, lines } = MultiLine.parse(str, text_wrap!, max_lines!, line_height, ctx);
199
+ width += background_size * 2;
200
+ height += background_size * 2;
201
+
202
+ let collision_size: LabelPointCoordinate = [
203
+ width / dpr,
204
+ height / dpr
205
+ ];
206
+
207
+ let texture_size: LabelPointCoordinate = [
208
+ width + 2 * horizontal_buffer,
209
+ height + 2 * vertical_buffer
210
+ ];
211
+
212
+ let logical_size: LabelPointCoordinate = [
213
+ texture_size[0] / dpr,
214
+ texture_size[1] / dpr,
215
+ ];
216
+
217
+ // Returns lines (w/per-line info for drawing) and text's overall bounding box + canvas size
218
+ TextCanvas.cache.text[style][text] = {
219
+ lines,
220
+ size: {
221
+ collision_size, texture_size, logical_size,
222
+ horizontal_buffer, vertical_buffer, dpr, line_height, background_size
223
+ }
224
+ };
225
+ return TextCanvas.cache.text[style][text];
226
+ }
227
+
228
+ // Draw multiple lines of text
229
+ drawTextMultiLine (lines: TextLine[], [x, y]: LabelPointCoordinate, size: TextSize, text_settings: TextSettingsResult & {align?: string}, label_type?: string): void {
230
+ const { dpr, collision_size, texture_size, line_height, horizontal_buffer, vertical_buffer } = size;
231
+
232
+ // draw optional background box
233
+ if (text_settings.background_color || text_settings.background_stroke_color) {
234
+ const background_stroke_color = text_settings.background_stroke_color;
235
+ const background_stroke_width = (text_settings.background_stroke_width || 0) * dpr;
236
+
237
+ this.context.save();
238
+
239
+ if (text_settings.background_color) {
240
+ this.context.fillStyle = text_settings.background_color;
241
+ this.context.fillRect(
242
+ // shift to "foreground" stroke texture for curved labels (separate stroke and fill textures)
243
+ x + horizontal_buffer + (label_type === 'curved' ? texture_size[0] : 0) + background_stroke_width,
244
+ y + vertical_buffer + background_stroke_width,
245
+ dpr * collision_size[0] - background_stroke_width * 2,
246
+ dpr * collision_size[1] - background_stroke_width * 2
247
+ );
248
+ }
249
+
250
+ // optional stroke around background box
251
+ if (background_stroke_color && background_stroke_width) {
252
+ this.context.strokeStyle = background_stroke_color;
253
+ this.context.lineWidth = background_stroke_width;
254
+ this.context.strokeRect(
255
+ // shift to "foreground" stroke texture for curved labels (separate stroke and fill textures)
256
+ x + horizontal_buffer + (label_type === 'curved' ? texture_size[0] : 0) + background_stroke_width! * 0.5,
257
+ y + vertical_buffer + background_stroke_width * 0.5,
258
+ dpr * collision_size[0] - background_stroke_width,
259
+ dpr * collision_size[1] - background_stroke_width
260
+ );
261
+ }
262
+
263
+ this.context.restore();
264
+ }
265
+
266
+ // draw text
267
+ const underline_width = text_settings.underline_width || 0;
268
+ const stroke_width = text_settings.stroke_width || 0;
269
+ const voffset = underline_width ? // offset text position to account for underline and text stroke
270
+ ((underline_width + stroke_width + 1) * 0.5 * dpr) : 0;
271
+ let ty = y - voffset;
272
+ for (let line_num=0; line_num < lines.length; line_num++) {
273
+ let line = lines[line_num];
274
+ this.drawTextLine(line, [x, ty], size, text_settings, label_type);
275
+ ty += line_height;
276
+ }
277
+
278
+ this.drawTextDebug([x, y], size, label_type);
279
+ }
280
+
281
+ // Draw single line of text at specified location, adjusting for buffer and baseline
282
+ drawTextLine(line: TextLine, [x, y]: LabelPointCoordinate, size: TextSize, text_settings: TextSettingsResult & {align?: string}, type?: string): void {
283
+ const { stroke, stroke_width, transform, align = 'center' } = text_settings;
284
+ const { horizontal_buffer, vertical_buffer, texture_size, background_size, line_height, dpr } = size;
285
+ const underline_width = (text_settings.underline_width || 0) * dpr;
286
+ const text = this.applyTextTransform(line.text, transform);
287
+
288
+ // Text alignment
289
+ let tx!: number;
290
+ if (align === 'left') {
291
+ tx = x + horizontal_buffer + background_size;
292
+ }
293
+ else if (align === 'center') {
294
+ tx = x + texture_size[0] / 2 - line.width / 2;
295
+ }
296
+ else if (align === 'right') {
297
+ tx = x + texture_size[0] - line.width - horizontal_buffer - background_size;
298
+ }
299
+
300
+ // In the absence of better Canvas TextMetrics (not supported by browsers yet),
301
+ // 0.75 buffer produces a better approximate vertical centering of text
302
+ const ty = y + vertical_buffer * 0.75 + line_height + background_size - underline_width * 0.5;
303
+
304
+ // Draw stroke and fill separately for curved text. Offset stroke in texture atlas by shift.
305
+ const shift = (stroke && stroke_width! > 0 && type === 'curved') ? texture_size[0] : 0;
306
+
307
+ // optional text underline
308
+ if (underline_width) {
309
+ this.context.save();
310
+ this.context.strokeStyle = this.context.fillStyle;
311
+ this.context.lineWidth = underline_width;
312
+
313
+ // adjust the underline to account for the text stroke
314
+ const uy = ty + ((stroke_width! * 0.5 + 2) * dpr) + this.context.lineWidth * 0.5;
315
+
316
+ this.context.beginPath();
317
+ this.context.moveTo(tx + shift, uy);
318
+ this.context.lineTo(tx + shift + line.width, uy);
319
+ this.context.stroke();
320
+ this.context.restore();
321
+ }
322
+
323
+ if (stroke && stroke_width! > 0) {
324
+ this.context.strokeText(text, tx + shift, ty);
325
+ }
326
+ this.context.fillText(text, tx, ty);
327
+ }
328
+
329
+ // Draw optional text debug boxes
330
+ drawTextDebug ([x, y]: LabelPointCoordinate, size: TextSize, label_type?: string): void {
331
+ const { dpr, horizontal_buffer, vertical_buffer, texture_size, collision_size } = size;
332
+ const line_width = 2;
333
+
334
+ if (debugSettings.draw_label_collision_boxes) {
335
+ this.context.save();
336
+ this.context.strokeStyle = 'blue';
337
+ this.context.lineWidth = line_width;
338
+ this.context.strokeRect(x + horizontal_buffer, y + vertical_buffer, dpr * collision_size[0], dpr * collision_size[1]);
339
+ if (label_type === 'curved'){
340
+ this.context.strokeRect(x + texture_size[0] + horizontal_buffer, y + vertical_buffer, dpr * collision_size[0], dpr * collision_size[1]);
341
+ }
342
+ this.context.restore();
343
+ }
344
+
345
+ if (debugSettings.draw_label_texture_boxes) {
346
+ this.context.save();
347
+ this.context.strokeStyle = 'green';
348
+ this.context.lineWidth = line_width;
349
+ // stroke is applied internally, so the outer border is the edge of the texture
350
+ this.context.strokeRect(x + line_width, y + line_width, texture_size[0] - 2 * line_width, texture_size[1] - 2 * line_width);
351
+ if (label_type === 'curved') {
352
+ this.context.strokeRect(x + line_width + texture_size[0], y + line_width, texture_size[0] - 2 * line_width, texture_size[1] - 2 * line_width);
353
+ }
354
+ this.context.restore();
355
+ }
356
+ }
357
+
358
+ rasterize (texts: TextTable, textures: TextAtlas[], tile_id: string, texture_prefix: string, resource_context: unknown): Promise<string[] | undefined> {
359
+ return Task.add({
360
+ type: 'rasterizeLabels',
361
+ run: this.processRasterizeTask.bind(this),
362
+ cancel: this.cancelRasterizeTask.bind(this),
363
+ pause_factor: 2, // pause 2 frames when task run past allowed time
364
+ user_moving_view: false, // don't run task when user is moving view
365
+ texts,
366
+ textures,
367
+ texture_prefix,
368
+ resource_context,
369
+ tile_id,
370
+ cursor: {
371
+ styles: Object.keys(texts),
372
+ texts: null,
373
+ style_idx: 0,
374
+ text_idx: null,
375
+ texture_idx: 0,
376
+ texture_resize: true,
377
+ texture_names: []
378
+ }
379
+ });
380
+ }
381
+
382
+ processRasterizeTask (task: TaskRecord<string[]>): boolean {
383
+ let { cursor, texts, textures } = task as TextRasterTask;
384
+ let texture;
385
+
386
+ // Rasterize one texture at a time, so we only have to keep one canvas in memory (they can be large)
387
+ while (cursor.texture_idx < (task as TextRasterTask).textures.length) {
388
+ texture = textures[cursor.texture_idx];
389
+
390
+ if (cursor.texture_resize) {
391
+ cursor.texture_resize = false;
392
+ this.resize(...texture.texture_size);
393
+ }
394
+
395
+ while (cursor.style_idx! < cursor.styles.length) {
396
+ let style = cursor.styles[cursor.style_idx!];
397
+ if (cursor.text_idx == null) {
398
+ cursor.text_idx = 0;
399
+ cursor.texts = Object.keys(texts[style]);
400
+ }
401
+
402
+ let text_infos = texts[style];
403
+ let first = true;
404
+
405
+ while (cursor.text_idx < cursor.texts!.length) {
406
+ let text = cursor.texts![cursor.text_idx];
407
+ let text_info = text_infos[text];
408
+ let text_settings = text_info.text_settings;
409
+
410
+ // set font on first occurence of new font style
411
+ if (first) {
412
+ this.setFont(text_settings);
413
+ first = false;
414
+ }
415
+
416
+ if (text_settings.can_articulate) {
417
+ text_info.texcoords = text_info.texcoords || {};
418
+ for (let t = 0; t < text_info.type!.length; t++) {
419
+ let type = text_info.type![t];
420
+ if (type === 'straight') {
421
+ // Only render for current texture
422
+ if (text_info.textures![t] !== cursor.texture_idx) {
423
+ continue;
424
+ }
425
+
426
+ let word = (text_info.isRTL) ? text.split(undefined!).reverse().join() : text;
427
+ let cache = texture.texcoord_cache[style][word];
428
+
429
+ let texcoord;
430
+ if (cache.texcoord) {
431
+ texcoord = cache.texcoord;
432
+ }
433
+ else {
434
+ let texture_position = cache.texture_position;
435
+ let { size, lines } = this.textSize(style, word, text_settings);
436
+
437
+ this.drawTextMultiLine(lines, texture_position, size, text_settings, type);
438
+
439
+ texcoord = typedTexture.getTexcoordsForSprite(
440
+ texture_position,
441
+ size.texture_size,
442
+ texture.texture_size
443
+ );
444
+
445
+ cache.texcoord = texcoord;
446
+ }
447
+
448
+ text_info.texcoords[type] = {
449
+ texcoord,
450
+ texture_id: cache.texture_id
451
+ };
452
+ }
453
+ else if (type === 'curved') {
454
+ let words = text_info.segments!;
455
+ text_info.texcoords.curved = text_info.texcoords.curved || [];
456
+ text_info.texcoords_stroke = text_info.texcoords_stroke || [];
457
+
458
+ for (let w = 0; w < words.length; w++){
459
+ // Only render for current texture
460
+ if ((text_info.textures![t] as number[])[w] !== cursor.texture_idx) {
461
+ continue;
462
+ }
463
+
464
+ let word = words[w];
465
+ let cache = texture.texcoord_cache[style][word];
466
+
467
+ let texcoord;
468
+ let texcoord_stroke;
469
+ if (cache.texcoord){
470
+ texcoord = cache.texcoord;
471
+ texcoord_stroke = cache.texcoord_stroke!;
472
+ text_info.texcoords_stroke.push(texcoord_stroke!);
473
+ }
474
+ else {
475
+ let texture_position = cache.texture_position;
476
+ let { size, lines } = this.textSize(style, word, text_settings);
477
+
478
+ this.drawTextMultiLine(lines, texture_position, size, text_settings, type);
479
+
480
+ texcoord = typedTexture.getTexcoordsForSprite(
481
+ texture_position,
482
+ size.texture_size,
483
+ texture.texture_size
484
+ );
485
+
486
+ let texture_position_stroke: LabelPointCoordinate = [
487
+ texture_position[0] + size.texture_size[0],
488
+ texture_position[1]
489
+ ];
490
+
491
+ texcoord_stroke = typedTexture.getTexcoordsForSprite(
492
+ texture_position_stroke,
493
+ size.texture_size,
494
+ texture.texture_size
495
+ );
496
+
497
+ cache.texcoord = texcoord;
498
+ cache.texcoord_stroke = texcoord_stroke;
499
+
500
+ // NB: texture_id is the same between stroke and fill, so it's not duplicated here
501
+ text_info.texcoords_stroke.push(texcoord_stroke!);
502
+ }
503
+
504
+ text_info.texcoords.curved.push({
505
+ texcoord,
506
+ texture_id: cache.texture_id
507
+ });
508
+ }
509
+ }
510
+ }
511
+ }
512
+ else {
513
+ let lines = this.textSize(style, text, text_settings).lines;
514
+ const aligned_text_settings: TextSettingsResult & {align?: string} = { ...text_settings };
515
+
516
+ for (let align in text_info.align) {
517
+ // Only render for current texture
518
+ if (text_info.align![align].texture_id !== cursor.texture_idx) {
519
+ continue;
520
+ }
521
+
522
+ aligned_text_settings.align = align;
523
+ this.drawTextMultiLine(lines, text_info.align![align].texture_position!, text_info.size!, aligned_text_settings);
524
+
525
+ text_info.align![align].texcoords = typedTexture.getTexcoordsForSprite(
526
+ text_info.align![align].texture_position!,
527
+ text_info.size!.texture_size,
528
+ texture.texture_size
529
+ );
530
+ }
531
+ }
532
+
533
+ cursor.text_idx++;
534
+
535
+ if (!Task.shouldContinue(task)) {
536
+ return false;
537
+ }
538
+ }
539
+ cursor.text_idx = null;
540
+ cursor.style_idx!++;
541
+ }
542
+
543
+ // Create GL texture (canvas element will be reused for next texture)
544
+ let tname = (task as TextRasterTask).texture_prefix + cursor.texture_idx;
545
+ typedTexture.create(task.resource_context, tname, {
546
+ element: this.canvas,
547
+ filtering: 'linear',
548
+ UNPACK_PREMULTIPLY_ALPHA_WEBGL: true
549
+ });
550
+ typedTexture.retain(tname);
551
+ cursor.texture_names.push(tname);
552
+
553
+ cursor.texture_idx++;
554
+ cursor.texture_resize = true;
555
+ cursor.style_idx = 0;
556
+ }
557
+
558
+ Task.finish(task, cursor.texture_names);
559
+ return true;
560
+ }
561
+
562
+ // Free any textures that have been allocated part-way through label rasterization for a tile
563
+ cancelRasterizeTask (task: TaskRecord<string[]>): undefined {
564
+ log('trace', `RasterizeTask: release textures [${(task as TextRasterTask).cursor.texture_names.join(', ')}]`);
565
+ (task as TextRasterTask).cursor.texture_names.forEach(t => typedTexture.release(t));
566
+ }
567
+
568
+ // Place text labels within an atlas of the given max size
569
+ setTextureTextPositions (texts: TextTable, max_texture_size: number): TextAtlas[] {
570
+ let texture: TextAtlasCursor = {
571
+ cx: 0,
572
+ cy: 0,
573
+ width: 0,
574
+ height: 0,
575
+ column_width: 0,
576
+ texture_id: 0,
577
+ texcoord_cache: {}
578
+ },
579
+ textures: TextAtlas[] = [];
580
+
581
+ for (let style in texts) {
582
+ let text_infos = texts[style];
583
+
584
+ for (let text in text_infos) {
585
+ let text_info = text_infos[text];
586
+ let texture_position: LabelPointCoordinate;
587
+
588
+ if (text_info.text_settings.can_articulate) {
589
+ text_info.textures = [];
590
+ texture.texcoord_cache[style] = texture.texcoord_cache[style] || {};
591
+
592
+ for (let t = 0; t < text_info.type!.length; t++) {
593
+ let type = text_info.type![t];
594
+
595
+ if (type === 'straight') {
596
+ let word = (text_info.isRTL) ? text.split(undefined!).reverse().join() : text;
597
+
598
+ if (!texture.texcoord_cache[style][word]) {
599
+ let size = text_info.size!.texture_size;
600
+ texture_position = this.placeText(size[0], size[1], style, texture, textures, max_texture_size);
601
+ texture.texcoord_cache[style][word] = {
602
+ texture_id: texture.texture_id,
603
+ texture_position
604
+ };
605
+ }
606
+
607
+ text_info.textures![t] = texture.texture_id;
608
+ }
609
+ else if (type === 'curved') {
610
+ text_info.textures![t] = [];
611
+
612
+ for (let w = 0; w < text_info.segment_sizes!.length; w++) {
613
+ let word = text_info.segments![w];
614
+
615
+ if (!texture.texcoord_cache[style][word]) {
616
+ let size = text_info.segment_sizes![w].texture_size;
617
+ let width = 2 * size[0]; // doubled to account for side-by-side rendering of fill and stroke
618
+ texture_position = this.placeText(width, size[1], style, texture, textures, max_texture_size);
619
+ texture.texcoord_cache[style][word] = {
620
+ texture_id: texture.texture_id,
621
+ texture_position
622
+ };
623
+ }
624
+
625
+ (text_info.textures![t] as number[]).push(texture.texture_id);
626
+ }
627
+
628
+ }
629
+ }
630
+ }
631
+ else {
632
+ // rendered size is same for all alignments
633
+ let size = text_info.size!.texture_size;
634
+
635
+ // but each alignment needs to be rendered separately
636
+ for (let align in text_info.align) {
637
+ texture_position = this.placeText (size[0], size[1], style, texture, textures, max_texture_size);
638
+ text_info.align![align].texture_id = texture.texture_id;
639
+ text_info.align![align].texture_position = texture_position;
640
+ }
641
+ }
642
+ }
643
+ }
644
+
645
+ // save final texture
646
+ if (texture.column_width > 0 && texture.height > 0) {
647
+ textures[texture.texture_id] = {
648
+ texture_size: [texture.width, texture.height],
649
+ texcoord_cache: texture.texcoord_cache
650
+ };
651
+ }
652
+
653
+ // return computed texture sizes and UV cache
654
+ return textures;
655
+ }
656
+
657
+ // Place text sprite in texture atlas, enlarging current texture, or starting new one if max texture size reached
658
+ placeText (text_width: number, text_height: number, style: string, texture: TextAtlasCursor, textures: TextAtlas[], max_texture_size: number): LabelPointCoordinate {
659
+ let texture_position: LabelPointCoordinate;
660
+
661
+ // TODO: what if first label is wider than entire max texture?
662
+
663
+ if (texture.cy + text_height > max_texture_size) {
664
+ // start new column
665
+ texture.cx += texture.column_width;
666
+ texture.cy = 0;
667
+ texture.column_width = text_width;
668
+ }
669
+ else {
670
+ // expand current column
671
+ texture.column_width = Math.max(texture.column_width, text_width);
672
+ }
673
+
674
+ if (texture.cx + texture.column_width <= max_texture_size) {
675
+ // add label to current texture
676
+ texture_position = [texture.cx, texture.cy];
677
+
678
+ texture.cy += text_height;
679
+
680
+ // expand texture if needed
681
+ texture.height = Math.max(texture.height, texture.cy);
682
+ texture.width = Math.max(texture.width, texture.cx + texture.column_width);
683
+ }
684
+ else {
685
+ // start new texture
686
+ // save size and cache of last texture
687
+ textures[texture.texture_id] = {
688
+ texture_size: [texture.width, texture.height],
689
+ texcoord_cache: texture.texcoord_cache
690
+ };
691
+ texture.texcoord_cache = {}; // reset cache
692
+ texture.texcoord_cache[style] = {};
693
+
694
+ texture.texture_id++;
695
+ texture.cx = 0;
696
+ texture.cy = text_height;
697
+ texture.column_width = text_width;
698
+ texture.width = text_width;
699
+ texture.height = text_height;
700
+ texture_position = [0, 0]; // TODO: allocate zero array once
701
+ }
702
+
703
+ return texture_position;
704
+ }
705
+
706
+ // Called before rasterization
707
+ applyTextTransform (text: string, transform?: string): string {
708
+ if (transform === 'capitalize') {
709
+ return text.replace(/\w\S*/g, function (txt) {
710
+ return txt.charAt(0).toUpperCase() + txt.substr(1);
711
+ });
712
+ }
713
+ else if (transform === 'uppercase') {
714
+ return text.toUpperCase();
715
+ }
716
+ else if (transform === 'lowercase') {
717
+ return text.toLowerCase();
718
+ }
719
+ return text;
720
+ }
721
+
722
+ // Convert font CSS-style size ('12px', '14pt', '1.5em', etc.) to pixel size (adjusted for device pixel ratio)
723
+ // Defaults units to pixels if not specified
724
+ static fontPixelSize (size: unknown): number | undefined {
725
+ if (size == null) {
726
+ return;
727
+ }
728
+ size = (typeof size === 'string') ? size : String(size); // need a string for regex
729
+
730
+ // The regexp captures a numeric string; preserve it until the legacy unit conversion/parser runs.
731
+ let [, px_size, units] = ((size as string).match(TextCanvas.font_size_re) || []) as [string?, (string | number)?, string?];
732
+ units = units || 'px';
733
+
734
+ if (units === 'em') {
735
+ px_size = Number(px_size) * 16;
736
+ } else if (units === 'pt') {
737
+ px_size = Number(px_size) / 0.75;
738
+ } else if (units === '%') {
739
+ px_size = Number(px_size) / 6.25;
740
+ }
741
+
742
+ px_size = typedStyleParser.parsePositiveNumber(px_size);
743
+ px_size *= Utils.device_pixel_ratio!;
744
+ return px_size;
745
+ }
746
+
747
+ static pruneTextCache () {
748
+ if (TextCanvas.cache.text_count > TextCanvas.cache.text_count_max) {
749
+ TextCanvas.cache.text = {};
750
+ TextCanvas.cache.text_count = 0;
751
+ log('debug', 'TextCanvas: pruning text cache');
752
+ }
753
+
754
+ if (Object.keys(TextCanvas.cache.segment).length > TextCanvas.cache.segment_count_max) {
755
+ TextCanvas.cache.segment = {};
756
+ log('debug', 'TextCanvas: pruning segment cache');
757
+ }
758
+ }
759
+
760
+ }
761
+
762
+ // Extract font size and units
763
+ TextCanvas.font_size_re = /((?:[0-9]*\.)?[0-9]+)\s*(px|pt|em|%)?/;
764
+
765
+ // Cache sizes of rendered text
766
+ TextCanvas.cache = {
767
+ text: {}, // size and line parsing, by text style, then text string
768
+ text_count: 0, // current size of cache (measured as # of entries)
769
+ text_count_max: 2000, // prune cache when it exceeds this size
770
+ segment: {}, // segmentation of text (by run of characters or grapheme clusters), by text string
771
+ segment_count_max: 2000, // prune cache when it exceeds this size
772
+ stats: { text_hits: 0, text_misses: 0, segment_hits: 0, segment_misses: 0 }
773
+ };