@chrtco/sdk 0.10.41 → 0.10.43

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.
@@ -1,5 +1,75 @@
1
1
  import maplibregl from 'maplibre-gl';
2
- import { C as ColorTableName } from '../s52-colors-CdVzqt2K.cjs';
2
+ import { C as ColorTableName, f as SymbolFamily } from '../symbol-families-D2Kx20HJ.cjs';
3
+
4
+ /**
5
+ * Every style image a chart registers for one palette and symbol family, as
6
+ * DATA: the image id, the themed SVG to rasterise, the bitmap size, and how it
7
+ * is registered.
8
+ *
9
+ * ⚠ ONE LIST, THREE READERS, AND THAT IS THE POINT.
10
+ *
11
+ * 1. The first load (`loadSymbolImageSpecs`) registers it.
12
+ * 2. A palette switch (`rethemeSymbolImages`) rewrites it IN PLACE, and an
13
+ * in-place rewrite only works when the new bitmap is exactly the size the
14
+ * first load chose — `map.updateImage` refuses anything else. Two copies
15
+ * of the sizing rules would drift, and a drifted size silently falls back
16
+ * to the slow path this file exists to retire.
17
+ * 3. `night-luminance.test.ts` reads the NIGHT list to measure the artwork
18
+ * that is actually drawn at night, not the tokens it was built from.
19
+ *
20
+ * Pure: no DOM, no MapLibre. The loaders in `*-browser.ts` turn specs into
21
+ * bitmaps. The per-family rules here are lifted verbatim from those loaders,
22
+ * which now build from this list.
23
+ */
24
+
25
+ interface SymbolImageSpec {
26
+ /** The style image id (`icon-image` / `*-pattern` value). */
27
+ id: string;
28
+ /** The SVG to rasterise: themed, stroke-boosted, sized. */
29
+ svg: string;
30
+ /** Bitmap size in device pixels. */
31
+ width: number;
32
+ height: number;
33
+ pixelRatio: number;
34
+ /** Registered as SDF — its colour is a mask and `icon-color` paints it. */
35
+ sdf: boolean;
36
+ }
37
+ /**
38
+ * The world-map hatches, registered for EVERY family (see
39
+ * `chrt-svg-loader-browser.ts`): un-supersampled, pixel ratio 1. They come
40
+ * first in every list so the house glyph list can never register them twice
41
+ * at a different size.
42
+ */
43
+ declare const BASE_PATTERN_GLYPHS: readonly string[];
44
+
45
+ /** The two world hatches, in this palette. */
46
+ declare function basePatternImageSpecs(palette: ColorTableName): SymbolImageSpec[];
47
+ /** The CHRT house glyphs (not the hatches, which `basePatternImageSpecs` owns). */
48
+ declare function chrtImageSpecs(palette: ColorTableName): SymbolImageSpec[];
49
+ /** The CHRTraster paper-chart variants (`raster:` namespace). */
50
+ declare function rasterImageSpecs(palette: ColorTableName): SymbolImageSpec[];
51
+ /** The vendored S-101 symbols (`s101:` namespace). */
52
+ declare function s101ImageSpecs(palette: ColorTableName): SymbolImageSpec[];
53
+ /**
54
+ * Everything a chart in `family` registers, in registration order: the S-52
55
+ * set, the world hatches, then the family's own set. This is what
56
+ * `ChartCanvas`'s first load registers and what a palette switch re-themes.
57
+ */
58
+ declare function symbolImageSpecs(palette: ColorTableName, family: SymbolFamily): SymbolImageSpec[];
59
+ interface StyleImageIds {
60
+ /** Every image id the layers can draw. */
61
+ readonly ids: ReadonlySet<string>;
62
+ /**
63
+ * True when some image name is built at render time (`["concat"]`) and so
64
+ * is NOT in `ids`. A caller that skips images outside `ids` must not, then.
65
+ */
66
+ readonly dynamic: boolean;
67
+ }
68
+ /** The image ids a set of style layers can draw. */
69
+ declare function styleImageIds(layers: ReadonlyArray<{
70
+ layout?: unknown;
71
+ paint?: unknown;
72
+ }>): StyleImageIds;
3
73
 
4
74
  /**
5
75
  * Browser-only S-52 SVG image loaders. Uses `new Image()` and `map.addImage()`
@@ -8,12 +78,23 @@ import { C as ColorTableName } from '../s52-colors-CdVzqt2K.cjs';
8
78
  * don't pull DOM-typed code into their bundle.
9
79
  */
10
80
 
81
+ /**
82
+ * The S-52 set as data — one `SymbolImageSpec` per image, built from the same
83
+ * helpers the bitmaps were always built from. Defined HERE, beside them, so
84
+ * `symbol-image-specs.ts` (which re-exports it) imports this file and not the
85
+ * other way round.
86
+ */
87
+ declare function s52ImageSpecs(colorTable: ColorTableName): SymbolImageSpec[];
11
88
  /**
12
89
  * Load all S-52 symbol images into a MapLibre map with the given color theme.
13
90
  */
14
91
  declare function loadS52SymbolImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
15
92
  /**
16
- * Reload all S-52 symbol images with a new color theme.
93
+ * Re-theme every S-52 symbol image to a new colour table — IN PLACE.
94
+ *
95
+ * This used to remove each image and add it back, which reloads every tile
96
+ * that draws it (see `symbol-images-browser.ts`). Same name and signature, so
97
+ * every caller gets the in-place path.
17
98
  */
18
99
  declare function reloadS52SymbolImages(map: maplibregl.Map, colorTable: ColorTableName): Promise<void>;
19
100
 
@@ -72,8 +153,14 @@ declare function getS52AtlasVersion(): string;
72
153
  */
73
154
  declare function loadS52SymbolImagesCached(map: maplibregl.Map, colorTable?: ColorTableName, opts?: LoadCachedOptions): Promise<void>;
74
155
  /**
75
- * Theme-switch equivalent of `reloadS52SymbolImages` that routes through
76
- * the IDB-backed path.
156
+ * Theme-switch equivalent of `reloadS52SymbolImages`.
157
+ *
158
+ * ⚠ IT NO LONGER GOES THROUGH THE CACHE, AND THAT IS DELIBERATE. It used to
159
+ * remove every image and re-add it from IndexedDB, and a remove/add pair
160
+ * reloads every tile that draws the image — the cache made the bitmaps cheap
161
+ * and the switch no cheaper. A switch now rewrites the pixels in place
162
+ * (`symbol-images-browser.ts`), which needs no cache. The cache keeps doing
163
+ * the job it was built for: a COLD START's first load.
77
164
  */
78
165
  declare function reloadS52SymbolImagesCached(map: maplibregl.Map, colorTable: ColorTableName): Promise<void>;
79
166
  /** Test/debug helper — wipes the entire bitmap store. */
@@ -107,7 +194,10 @@ declare function loadThemedBridgeImages(map: maplibregl.Map, colorTable: ColorTa
107
194
  * a missing icon renders as a gap, never breaks the map.
108
195
  */
109
196
  declare function loadS101SymbolImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
110
- /** Reload the S-101 set with a new colour profile (theme switch). */
197
+ /**
198
+ * Re-theme the S-101 set to a new colour profile (theme switch) — IN PLACE.
199
+ * See `symbol-images-browser.ts` for why it no longer removes and re-adds.
200
+ */
111
201
  declare function reloadS101SymbolImages(map: maplibregl.Map, colorTable: ColorTableName): Promise<void>;
112
202
 
113
203
  /**
@@ -117,8 +207,26 @@ declare function reloadS101SymbolImages(map: maplibregl.Map, colorTable: ColorTa
117
207
  * from the feature's COLOUR attribute (see chrt-style.ts).
118
208
  */
119
209
 
120
- declare function loadBasePatternImages(map: maplibregl.Map): Promise<void>;
121
- declare function loadChrtSymbolImages(map: maplibregl.Map): Promise<void>;
210
+ /**
211
+ * The world-map hatch patterns, registered REGARDLESS of symbol family.
212
+ *
213
+ * `loadChrtSymbolImages` below runs only when the active family is "chrt" —
214
+ * correct for the ATON glyphs it owns, and exactly wrong for the two hatch
215
+ * patterns the CHRT_BASE layers reference: under the default s52 family they
216
+ * were never registered, and a fill-pattern whose image is missing renders as
217
+ * NOTHING with no error anywhere (the crosshatch silently absent from the
218
+ * whole world map). Two images, loaded beside every family's loader.
219
+ *
220
+ * Themed per palette (`themeChrtGlyph`): the hatches cover the whole uncharted
221
+ * world, and at NIGHT they were the one grey-blue texture on a dark chart.
222
+ */
223
+ declare function loadBasePatternImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
224
+ /**
225
+ * Load the house glyphs into the map as `chrt:{name}` images. Tintable bodies
226
+ * are SDF (the style's `icon-color` paints them per palette); the
227
+ * fixed-palette glyphs are themed here (`themeChrtGlyph`).
228
+ */
229
+ declare function loadChrtSymbolImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
122
230
 
123
231
  /**
124
232
  * Browser-only CHRTraster image loader. Registers the synthesized
@@ -132,7 +240,93 @@ declare function loadChrtSymbolImages(map: maplibregl.Map): Promise<void>;
132
240
  /** Load every synthesized CHRTraster variant as `raster:{NAME}`. Individual
133
241
  * image failures resolve silently (matching the S-52 loader). */
134
242
  declare function loadChrtRasterSymbolImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
135
- /** Reload the CHRTraster set with a new colour table (theme switch). */
243
+ /**
244
+ * Re-theme the CHRTraster set to a new colour table (theme switch) — IN
245
+ * PLACE. See `symbol-images-browser.ts` for why it no longer removes and
246
+ * re-adds.
247
+ */
136
248
  declare function reloadChrtRasterSymbolImages(map: maplibregl.Map, colorTable: ColorTableName): Promise<void>;
137
249
 
138
- export { type LoadCachedOptions, clearS52SymbolImageCache, getS52AtlasVersion, loadBasePatternImages, loadChrtRasterSymbolImages, loadChrtSymbolImages, loadS101SymbolImages, loadS52SymbolImages, loadS52SymbolImagesCached, loadSymbolImages, loadThemedBridgeImages, reloadChrtRasterSymbolImages, reloadS101SymbolImages, reloadS52SymbolImages, reloadS52SymbolImagesCached };
250
+ /**
251
+ * Browser-only: turn `SymbolImageSpec`s into MapLibre style images.
252
+ *
253
+ * ===========================================================================
254
+ * ⚠ A PALETTE SWITCH MUST NOT REMOVE AN IMAGE. IT REWRITES ITS PIXELS.
255
+ * ===========================================================================
256
+ *
257
+ * The old switch (`reloadS52SymbolImages` and its siblings) removed every
258
+ * image, re-decoded every SVG and added it back. In MapLibre 5.24 both
259
+ * `removeImage` and `addImage` go through `Style._afterImageUpdated`, which:
260
+ *
261
+ * · marks the id CHANGED, so the next frame's `_updateTilesForChangedImages`
262
+ * RELOADS every tile that uses it (`reloadTilesForDependencies(['icons',
263
+ * 'patterns'])` — a worker re-parse of the whole tile, symbol layout
264
+ * included), and
265
+ * · broadcasts the full image list to every worker (`setImages`).
266
+ *
267
+ * ~570 images arriving over many frames meant the tiles were re-parsed again
268
+ * and again while the icons blinked out and back, which on a Raspberry Pi is
269
+ * the "takes forever … only some switched" the owner reported on 2026-09-30.
270
+ *
271
+ * `map.updateImage` does neither. It swaps the pixels behind the same id
272
+ * (`ImageManager.updateImage` bumps a version) and each tile patches its own
273
+ * atlas texture on the next frame (`ImageAtlas.patchUpdatedImages`). No tile
274
+ * reload, no broadcast, and the icon never leaves the screen. Its one
275
+ * condition is that the new bitmap is the SAME SIZE, which a palette change
276
+ * never alters — `symbol-image-specs.ts` is the single source of sizes so
277
+ * the first load and the switch cannot disagree.
278
+ *
279
+ * The rasterising itself (SVG → canvas → pixels) is main-thread work, so it
280
+ * runs in slices with a yield between them and reports progress; the chart
281
+ * keeps drawing while the icons come over.
282
+ */
283
+
284
+ interface SymbolImageProgress {
285
+ done: number;
286
+ total: number;
287
+ }
288
+ interface RethemeOptions {
289
+ /**
290
+ * Main-thread budget per slice, in ms. Rasterising stops at the first image
291
+ * past it and yields a task so the map can draw a frame. Default 8.
292
+ */
293
+ sliceMs?: number;
294
+ /**
295
+ * At most how often the re-theme asks MapLibre for a frame, in ms. Default
296
+ * 250. Asking once per slice is what made a 6x-throttled re-theme take 7 s:
297
+ * a frame of this chart costs 100–300 ms there, so a repaint per 8 ms slice
298
+ * spent the switch drawing intermediate states. The pixels are swapped either
299
+ * way; this only paces how often the progress is SHOWN.
300
+ */
301
+ repaintEveryMs?: number;
302
+ /** Abort a re-theme that a newer switch has made moot. */
303
+ signal?: AbortSignal;
304
+ onProgress?: (p: SymbolImageProgress) => void;
305
+ /** Called once per image whose new pixels are on the map. */
306
+ onApplied?: (id: string) => void;
307
+ }
308
+ interface RethemeResult {
309
+ /** Rewritten in place (the fast path). */
310
+ updated: number;
311
+ /** Not registered yet, or registered at another size — added fresh. */
312
+ added: number;
313
+ /** Failed to decode — left as they were. */
314
+ failed: number;
315
+ aborted: boolean;
316
+ }
317
+ /**
318
+ * Register every spec the map does not already hold. A first load, so there is
319
+ * nothing to update in place; images the map already has are left alone.
320
+ */
321
+ declare function loadSymbolImageSpecs(map: maplibregl.Map, specs: readonly SymbolImageSpec[]): Promise<void>;
322
+ /**
323
+ * Rewrite every spec's pixels in place, a slice at a time.
324
+ *
325
+ * An image the map holds at the spec's size is `updateImage`d (no tile
326
+ * reload). One it does not hold, or holds at another size, is added — the
327
+ * slow path, which only a first load or a drifted spec should ever take, and
328
+ * `RethemeResult.added` says how often it did.
329
+ */
330
+ declare function rethemeSymbolImages(map: maplibregl.Map, specs: readonly SymbolImageSpec[], opts?: RethemeOptions): Promise<RethemeResult>;
331
+
332
+ export { BASE_PATTERN_GLYPHS, type LoadCachedOptions, type RethemeOptions, type RethemeResult, type StyleImageIds, type SymbolImageProgress, type SymbolImageSpec, basePatternImageSpecs, chrtImageSpecs, clearS52SymbolImageCache, getS52AtlasVersion, loadBasePatternImages, loadChrtRasterSymbolImages, loadChrtSymbolImages, loadS101SymbolImages, loadS52SymbolImages, loadS52SymbolImagesCached, loadSymbolImageSpecs, loadSymbolImages, loadThemedBridgeImages, rasterImageSpecs, reloadChrtRasterSymbolImages, reloadS101SymbolImages, reloadS52SymbolImages, reloadS52SymbolImagesCached, rethemeSymbolImages, s101ImageSpecs, s52ImageSpecs, styleImageIds, symbolImageSpecs };
@@ -1,5 +1,75 @@
1
1
  import maplibregl from 'maplibre-gl';
2
- import { C as ColorTableName } from '../s52-colors-CdVzqt2K.js';
2
+ import { C as ColorTableName, f as SymbolFamily } from '../symbol-families-D2Kx20HJ.js';
3
+
4
+ /**
5
+ * Every style image a chart registers for one palette and symbol family, as
6
+ * DATA: the image id, the themed SVG to rasterise, the bitmap size, and how it
7
+ * is registered.
8
+ *
9
+ * ⚠ ONE LIST, THREE READERS, AND THAT IS THE POINT.
10
+ *
11
+ * 1. The first load (`loadSymbolImageSpecs`) registers it.
12
+ * 2. A palette switch (`rethemeSymbolImages`) rewrites it IN PLACE, and an
13
+ * in-place rewrite only works when the new bitmap is exactly the size the
14
+ * first load chose — `map.updateImage` refuses anything else. Two copies
15
+ * of the sizing rules would drift, and a drifted size silently falls back
16
+ * to the slow path this file exists to retire.
17
+ * 3. `night-luminance.test.ts` reads the NIGHT list to measure the artwork
18
+ * that is actually drawn at night, not the tokens it was built from.
19
+ *
20
+ * Pure: no DOM, no MapLibre. The loaders in `*-browser.ts` turn specs into
21
+ * bitmaps. The per-family rules here are lifted verbatim from those loaders,
22
+ * which now build from this list.
23
+ */
24
+
25
+ interface SymbolImageSpec {
26
+ /** The style image id (`icon-image` / `*-pattern` value). */
27
+ id: string;
28
+ /** The SVG to rasterise: themed, stroke-boosted, sized. */
29
+ svg: string;
30
+ /** Bitmap size in device pixels. */
31
+ width: number;
32
+ height: number;
33
+ pixelRatio: number;
34
+ /** Registered as SDF — its colour is a mask and `icon-color` paints it. */
35
+ sdf: boolean;
36
+ }
37
+ /**
38
+ * The world-map hatches, registered for EVERY family (see
39
+ * `chrt-svg-loader-browser.ts`): un-supersampled, pixel ratio 1. They come
40
+ * first in every list so the house glyph list can never register them twice
41
+ * at a different size.
42
+ */
43
+ declare const BASE_PATTERN_GLYPHS: readonly string[];
44
+
45
+ /** The two world hatches, in this palette. */
46
+ declare function basePatternImageSpecs(palette: ColorTableName): SymbolImageSpec[];
47
+ /** The CHRT house glyphs (not the hatches, which `basePatternImageSpecs` owns). */
48
+ declare function chrtImageSpecs(palette: ColorTableName): SymbolImageSpec[];
49
+ /** The CHRTraster paper-chart variants (`raster:` namespace). */
50
+ declare function rasterImageSpecs(palette: ColorTableName): SymbolImageSpec[];
51
+ /** The vendored S-101 symbols (`s101:` namespace). */
52
+ declare function s101ImageSpecs(palette: ColorTableName): SymbolImageSpec[];
53
+ /**
54
+ * Everything a chart in `family` registers, in registration order: the S-52
55
+ * set, the world hatches, then the family's own set. This is what
56
+ * `ChartCanvas`'s first load registers and what a palette switch re-themes.
57
+ */
58
+ declare function symbolImageSpecs(palette: ColorTableName, family: SymbolFamily): SymbolImageSpec[];
59
+ interface StyleImageIds {
60
+ /** Every image id the layers can draw. */
61
+ readonly ids: ReadonlySet<string>;
62
+ /**
63
+ * True when some image name is built at render time (`["concat"]`) and so
64
+ * is NOT in `ids`. A caller that skips images outside `ids` must not, then.
65
+ */
66
+ readonly dynamic: boolean;
67
+ }
68
+ /** The image ids a set of style layers can draw. */
69
+ declare function styleImageIds(layers: ReadonlyArray<{
70
+ layout?: unknown;
71
+ paint?: unknown;
72
+ }>): StyleImageIds;
3
73
 
4
74
  /**
5
75
  * Browser-only S-52 SVG image loaders. Uses `new Image()` and `map.addImage()`
@@ -8,12 +78,23 @@ import { C as ColorTableName } from '../s52-colors-CdVzqt2K.js';
8
78
  * don't pull DOM-typed code into their bundle.
9
79
  */
10
80
 
81
+ /**
82
+ * The S-52 set as data — one `SymbolImageSpec` per image, built from the same
83
+ * helpers the bitmaps were always built from. Defined HERE, beside them, so
84
+ * `symbol-image-specs.ts` (which re-exports it) imports this file and not the
85
+ * other way round.
86
+ */
87
+ declare function s52ImageSpecs(colorTable: ColorTableName): SymbolImageSpec[];
11
88
  /**
12
89
  * Load all S-52 symbol images into a MapLibre map with the given color theme.
13
90
  */
14
91
  declare function loadS52SymbolImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
15
92
  /**
16
- * Reload all S-52 symbol images with a new color theme.
93
+ * Re-theme every S-52 symbol image to a new colour table — IN PLACE.
94
+ *
95
+ * This used to remove each image and add it back, which reloads every tile
96
+ * that draws it (see `symbol-images-browser.ts`). Same name and signature, so
97
+ * every caller gets the in-place path.
17
98
  */
18
99
  declare function reloadS52SymbolImages(map: maplibregl.Map, colorTable: ColorTableName): Promise<void>;
19
100
 
@@ -72,8 +153,14 @@ declare function getS52AtlasVersion(): string;
72
153
  */
73
154
  declare function loadS52SymbolImagesCached(map: maplibregl.Map, colorTable?: ColorTableName, opts?: LoadCachedOptions): Promise<void>;
74
155
  /**
75
- * Theme-switch equivalent of `reloadS52SymbolImages` that routes through
76
- * the IDB-backed path.
156
+ * Theme-switch equivalent of `reloadS52SymbolImages`.
157
+ *
158
+ * ⚠ IT NO LONGER GOES THROUGH THE CACHE, AND THAT IS DELIBERATE. It used to
159
+ * remove every image and re-add it from IndexedDB, and a remove/add pair
160
+ * reloads every tile that draws the image — the cache made the bitmaps cheap
161
+ * and the switch no cheaper. A switch now rewrites the pixels in place
162
+ * (`symbol-images-browser.ts`), which needs no cache. The cache keeps doing
163
+ * the job it was built for: a COLD START's first load.
77
164
  */
78
165
  declare function reloadS52SymbolImagesCached(map: maplibregl.Map, colorTable: ColorTableName): Promise<void>;
79
166
  /** Test/debug helper — wipes the entire bitmap store. */
@@ -107,7 +194,10 @@ declare function loadThemedBridgeImages(map: maplibregl.Map, colorTable: ColorTa
107
194
  * a missing icon renders as a gap, never breaks the map.
108
195
  */
109
196
  declare function loadS101SymbolImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
110
- /** Reload the S-101 set with a new colour profile (theme switch). */
197
+ /**
198
+ * Re-theme the S-101 set to a new colour profile (theme switch) — IN PLACE.
199
+ * See `symbol-images-browser.ts` for why it no longer removes and re-adds.
200
+ */
111
201
  declare function reloadS101SymbolImages(map: maplibregl.Map, colorTable: ColorTableName): Promise<void>;
112
202
 
113
203
  /**
@@ -117,8 +207,26 @@ declare function reloadS101SymbolImages(map: maplibregl.Map, colorTable: ColorTa
117
207
  * from the feature's COLOUR attribute (see chrt-style.ts).
118
208
  */
119
209
 
120
- declare function loadBasePatternImages(map: maplibregl.Map): Promise<void>;
121
- declare function loadChrtSymbolImages(map: maplibregl.Map): Promise<void>;
210
+ /**
211
+ * The world-map hatch patterns, registered REGARDLESS of symbol family.
212
+ *
213
+ * `loadChrtSymbolImages` below runs only when the active family is "chrt" —
214
+ * correct for the ATON glyphs it owns, and exactly wrong for the two hatch
215
+ * patterns the CHRT_BASE layers reference: under the default s52 family they
216
+ * were never registered, and a fill-pattern whose image is missing renders as
217
+ * NOTHING with no error anywhere (the crosshatch silently absent from the
218
+ * whole world map). Two images, loaded beside every family's loader.
219
+ *
220
+ * Themed per palette (`themeChrtGlyph`): the hatches cover the whole uncharted
221
+ * world, and at NIGHT they were the one grey-blue texture on a dark chart.
222
+ */
223
+ declare function loadBasePatternImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
224
+ /**
225
+ * Load the house glyphs into the map as `chrt:{name}` images. Tintable bodies
226
+ * are SDF (the style's `icon-color` paints them per palette); the
227
+ * fixed-palette glyphs are themed here (`themeChrtGlyph`).
228
+ */
229
+ declare function loadChrtSymbolImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
122
230
 
123
231
  /**
124
232
  * Browser-only CHRTraster image loader. Registers the synthesized
@@ -132,7 +240,93 @@ declare function loadChrtSymbolImages(map: maplibregl.Map): Promise<void>;
132
240
  /** Load every synthesized CHRTraster variant as `raster:{NAME}`. Individual
133
241
  * image failures resolve silently (matching the S-52 loader). */
134
242
  declare function loadChrtRasterSymbolImages(map: maplibregl.Map, colorTable?: ColorTableName): Promise<void>;
135
- /** Reload the CHRTraster set with a new colour table (theme switch). */
243
+ /**
244
+ * Re-theme the CHRTraster set to a new colour table (theme switch) — IN
245
+ * PLACE. See `symbol-images-browser.ts` for why it no longer removes and
246
+ * re-adds.
247
+ */
136
248
  declare function reloadChrtRasterSymbolImages(map: maplibregl.Map, colorTable: ColorTableName): Promise<void>;
137
249
 
138
- export { type LoadCachedOptions, clearS52SymbolImageCache, getS52AtlasVersion, loadBasePatternImages, loadChrtRasterSymbolImages, loadChrtSymbolImages, loadS101SymbolImages, loadS52SymbolImages, loadS52SymbolImagesCached, loadSymbolImages, loadThemedBridgeImages, reloadChrtRasterSymbolImages, reloadS101SymbolImages, reloadS52SymbolImages, reloadS52SymbolImagesCached };
250
+ /**
251
+ * Browser-only: turn `SymbolImageSpec`s into MapLibre style images.
252
+ *
253
+ * ===========================================================================
254
+ * ⚠ A PALETTE SWITCH MUST NOT REMOVE AN IMAGE. IT REWRITES ITS PIXELS.
255
+ * ===========================================================================
256
+ *
257
+ * The old switch (`reloadS52SymbolImages` and its siblings) removed every
258
+ * image, re-decoded every SVG and added it back. In MapLibre 5.24 both
259
+ * `removeImage` and `addImage` go through `Style._afterImageUpdated`, which:
260
+ *
261
+ * · marks the id CHANGED, so the next frame's `_updateTilesForChangedImages`
262
+ * RELOADS every tile that uses it (`reloadTilesForDependencies(['icons',
263
+ * 'patterns'])` — a worker re-parse of the whole tile, symbol layout
264
+ * included), and
265
+ * · broadcasts the full image list to every worker (`setImages`).
266
+ *
267
+ * ~570 images arriving over many frames meant the tiles were re-parsed again
268
+ * and again while the icons blinked out and back, which on a Raspberry Pi is
269
+ * the "takes forever … only some switched" the owner reported on 2026-09-30.
270
+ *
271
+ * `map.updateImage` does neither. It swaps the pixels behind the same id
272
+ * (`ImageManager.updateImage` bumps a version) and each tile patches its own
273
+ * atlas texture on the next frame (`ImageAtlas.patchUpdatedImages`). No tile
274
+ * reload, no broadcast, and the icon never leaves the screen. Its one
275
+ * condition is that the new bitmap is the SAME SIZE, which a palette change
276
+ * never alters — `symbol-image-specs.ts` is the single source of sizes so
277
+ * the first load and the switch cannot disagree.
278
+ *
279
+ * The rasterising itself (SVG → canvas → pixels) is main-thread work, so it
280
+ * runs in slices with a yield between them and reports progress; the chart
281
+ * keeps drawing while the icons come over.
282
+ */
283
+
284
+ interface SymbolImageProgress {
285
+ done: number;
286
+ total: number;
287
+ }
288
+ interface RethemeOptions {
289
+ /**
290
+ * Main-thread budget per slice, in ms. Rasterising stops at the first image
291
+ * past it and yields a task so the map can draw a frame. Default 8.
292
+ */
293
+ sliceMs?: number;
294
+ /**
295
+ * At most how often the re-theme asks MapLibre for a frame, in ms. Default
296
+ * 250. Asking once per slice is what made a 6x-throttled re-theme take 7 s:
297
+ * a frame of this chart costs 100–300 ms there, so a repaint per 8 ms slice
298
+ * spent the switch drawing intermediate states. The pixels are swapped either
299
+ * way; this only paces how often the progress is SHOWN.
300
+ */
301
+ repaintEveryMs?: number;
302
+ /** Abort a re-theme that a newer switch has made moot. */
303
+ signal?: AbortSignal;
304
+ onProgress?: (p: SymbolImageProgress) => void;
305
+ /** Called once per image whose new pixels are on the map. */
306
+ onApplied?: (id: string) => void;
307
+ }
308
+ interface RethemeResult {
309
+ /** Rewritten in place (the fast path). */
310
+ updated: number;
311
+ /** Not registered yet, or registered at another size — added fresh. */
312
+ added: number;
313
+ /** Failed to decode — left as they were. */
314
+ failed: number;
315
+ aborted: boolean;
316
+ }
317
+ /**
318
+ * Register every spec the map does not already hold. A first load, so there is
319
+ * nothing to update in place; images the map already has are left alone.
320
+ */
321
+ declare function loadSymbolImageSpecs(map: maplibregl.Map, specs: readonly SymbolImageSpec[]): Promise<void>;
322
+ /**
323
+ * Rewrite every spec's pixels in place, a slice at a time.
324
+ *
325
+ * An image the map holds at the spec's size is `updateImage`d (no tile
326
+ * reload). One it does not hold, or holds at another size, is added — the
327
+ * slow path, which only a first load or a drifted spec should ever take, and
328
+ * `RethemeResult.added` says how often it did.
329
+ */
330
+ declare function rethemeSymbolImages(map: maplibregl.Map, specs: readonly SymbolImageSpec[], opts?: RethemeOptions): Promise<RethemeResult>;
331
+
332
+ export { BASE_PATTERN_GLYPHS, type LoadCachedOptions, type RethemeOptions, type RethemeResult, type StyleImageIds, type SymbolImageProgress, type SymbolImageSpec, basePatternImageSpecs, chrtImageSpecs, clearS52SymbolImageCache, getS52AtlasVersion, loadBasePatternImages, loadChrtRasterSymbolImages, loadChrtSymbolImages, loadS101SymbolImages, loadS52SymbolImages, loadS52SymbolImagesCached, loadSymbolImageSpecs, loadSymbolImages, loadThemedBridgeImages, rasterImageSpecs, reloadChrtRasterSymbolImages, reloadS101SymbolImages, reloadS52SymbolImages, reloadS52SymbolImagesCached, rethemeSymbolImages, s101ImageSpecs, s52ImageSpecs, styleImageIds, symbolImageSpecs };