@chrtco/sdk 0.10.43 → 0.10.45
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.
- package/CHANGELOG.md +12 -0
- package/dist/atlas/browser.cjs +135 -36
- package/dist/atlas/browser.cjs.map +1 -1
- package/dist/atlas/browser.d.cts +26 -0
- package/dist/atlas/browser.d.ts +26 -0
- package/dist/atlas/browser.js +135 -36
- package/dist/atlas/browser.js.map +1 -1
- package/dist/index.cjs +106 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +98 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,18 @@ All notable changes to `@chrtco/sdk` will be documented in this file.
|
|
|
14
14
|
source themselves, such as the tile-worker's TileJSON.
|
|
15
15
|
|
|
16
16
|
### Fixed
|
|
17
|
+
- **A palette switch held the map's tile answers behind its own SVG decodes.**
|
|
18
|
+
`rethemeSymbolImages` decoded every symbol as its own SVG `<img>`, and each
|
|
19
|
+
is a whole browser document whose many small main-thread tasks queued ahead
|
|
20
|
+
of MapLibre's worker messages. A tile reload started by the switch's
|
|
21
|
+
data-driven paint (two-shade soundings, light circles) landed only after
|
|
22
|
+
them, and until it did those kept their previous palette's colours. Symbols
|
|
23
|
+
are now decoded up to 32 to a document, and each is
|
|
24
|
+
drawn from its sheet shifted onto a canvas its own size. The pixels are
|
|
25
|
+
byte-identical to the per-symbol path for every family and palette. A
|
|
26
|
+
symbol that cannot safely share a document (a prolog, styles, a reference
|
|
27
|
+
to an id it does not define) still gets its own, as does every symbol of a
|
|
28
|
+
sheet that fails to decode. `loadSymbolImageSpecs` is unchanged.
|
|
17
29
|
- **`offline.download()` left a second copy of every pack in a browser's HTTP
|
|
18
30
|
cache.** In a browser, an Electron renderer or Electron `net.fetch`, the
|
|
19
31
|
bundle GET was stored in the profile's disk cache as well as written through
|
package/dist/atlas/browser.cjs
CHANGED
|
@@ -1176,17 +1176,110 @@ async function decode(spec) {
|
|
|
1176
1176
|
}
|
|
1177
1177
|
}
|
|
1178
1178
|
var scratch = null;
|
|
1179
|
-
function
|
|
1179
|
+
function scratchContext() {
|
|
1180
1180
|
if (!scratch) {
|
|
1181
|
-
const
|
|
1182
|
-
scratch =
|
|
1181
|
+
const canvas = document.createElement("canvas");
|
|
1182
|
+
scratch = canvas.getContext("2d", { willReadFrequently: true });
|
|
1183
1183
|
if (!scratch) throw new Error("symbol-images: no 2d canvas context");
|
|
1184
1184
|
}
|
|
1185
|
-
|
|
1185
|
+
return scratch;
|
|
1186
|
+
}
|
|
1187
|
+
function rasterise(src, spec) {
|
|
1188
|
+
const ctx = scratchContext();
|
|
1189
|
+
const canvas = ctx.canvas;
|
|
1186
1190
|
canvas.width = spec.width;
|
|
1187
1191
|
canvas.height = spec.height;
|
|
1188
|
-
|
|
1189
|
-
return
|
|
1192
|
+
ctx.drawImage(src.img, 0, -src.y, src.width, src.height);
|
|
1193
|
+
return ctx.getImageData(0, 0, spec.width, spec.height);
|
|
1194
|
+
}
|
|
1195
|
+
var SHEET_MAX_SYMBOLS = 32;
|
|
1196
|
+
var SHEET_MAX_HEIGHT = 4096;
|
|
1197
|
+
var ROOT = /^\s*<svg\b([^>]*)>[\s\S]*<\/svg>\s*$/;
|
|
1198
|
+
var NOT_SHAREABLE = /<\?xml|<!DOCTYPE|<style\b|<script\b|<foreignObject\b|<svg\b[\s\S]*<svg\b/i;
|
|
1199
|
+
function rootSize(attrs) {
|
|
1200
|
+
const w = /\swidth="(\d+(?:\.\d+)?)"/.exec(attrs);
|
|
1201
|
+
const h = /\sheight="(\d+(?:\.\d+)?)"/.exec(attrs);
|
|
1202
|
+
return w && h ? { w: Number(w[1]), h: Number(h[1]) } : null;
|
|
1203
|
+
}
|
|
1204
|
+
function shareable(spec) {
|
|
1205
|
+
if (!(spec.width > 0 && spec.height > 0) || spec.height > SHEET_MAX_HEIGHT) return null;
|
|
1206
|
+
const root = ROOT.exec(spec.svg);
|
|
1207
|
+
const own = root ? rootSize(root[1]) : null;
|
|
1208
|
+
if (!own || NOT_SHAREABLE.test(spec.svg)) return null;
|
|
1209
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1210
|
+
for (const m of spec.svg.matchAll(/\sid="([^"]*)"/g)) {
|
|
1211
|
+
if (ids.has(m[1])) return null;
|
|
1212
|
+
ids.add(m[1]);
|
|
1213
|
+
}
|
|
1214
|
+
for (const m of spec.svg.matchAll(/url\(\s*['"]?#([^'")\s]*)|href="#([^"]*)"/g)) {
|
|
1215
|
+
if (!ids.has(m[1] ?? m[2])) return null;
|
|
1216
|
+
}
|
|
1217
|
+
return { spec, own, ids };
|
|
1218
|
+
}
|
|
1219
|
+
function sheetOf(run) {
|
|
1220
|
+
let y = 0;
|
|
1221
|
+
let width = 0;
|
|
1222
|
+
const parts = [];
|
|
1223
|
+
const slots = [];
|
|
1224
|
+
for (const { spec, own } of run) {
|
|
1225
|
+
parts.push(
|
|
1226
|
+
`<svg x="0" y="${y}" width="${spec.width}" height="${spec.height}" viewBox="0 0 ${own.w} ${own.h}" preserveAspectRatio="none">${spec.svg}</svg>`
|
|
1227
|
+
);
|
|
1228
|
+
slots.push({ spec, y });
|
|
1229
|
+
y += spec.height;
|
|
1230
|
+
width = Math.max(width, spec.width);
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
svg: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${y}">${parts.join("")}</svg>`,
|
|
1234
|
+
width,
|
|
1235
|
+
height: y,
|
|
1236
|
+
slots
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
function planRasterUnits(specs) {
|
|
1240
|
+
const units = [];
|
|
1241
|
+
let run = [];
|
|
1242
|
+
let runIds = /* @__PURE__ */ new Set();
|
|
1243
|
+
let runHeight = 0;
|
|
1244
|
+
const flush = () => {
|
|
1245
|
+
if (run.length === 1) units.push({ one: run[0].spec });
|
|
1246
|
+
else if (run.length > 1) units.push({ sheet: sheetOf(run) });
|
|
1247
|
+
run = [];
|
|
1248
|
+
runIds = /* @__PURE__ */ new Set();
|
|
1249
|
+
runHeight = 0;
|
|
1250
|
+
};
|
|
1251
|
+
for (const spec of specs) {
|
|
1252
|
+
const s = shareable(spec);
|
|
1253
|
+
if (!s) {
|
|
1254
|
+
flush();
|
|
1255
|
+
units.push({ one: spec });
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
if (run.length >= SHEET_MAX_SYMBOLS || runHeight + spec.height > SHEET_MAX_HEIGHT || [...s.ids].some((id) => runIds.has(id))) {
|
|
1259
|
+
flush();
|
|
1260
|
+
}
|
|
1261
|
+
run.push(s);
|
|
1262
|
+
runHeight += spec.height;
|
|
1263
|
+
for (const id of s.ids) runIds.add(id);
|
|
1264
|
+
}
|
|
1265
|
+
flush();
|
|
1266
|
+
return units;
|
|
1267
|
+
}
|
|
1268
|
+
async function sourcesFor(unit, decoded) {
|
|
1269
|
+
const img = await decoded;
|
|
1270
|
+
if ("one" in unit) {
|
|
1271
|
+
return [img ? { img, y: 0, width: unit.one.width, height: unit.one.height } : null];
|
|
1272
|
+
}
|
|
1273
|
+
const { sheet } = unit;
|
|
1274
|
+
if (img) return sheet.slots.map(({ y }) => ({ img, y, width: sheet.width, height: sheet.height }));
|
|
1275
|
+
const singles = sheet.slots.map(({ spec }) => decode(spec));
|
|
1276
|
+
const out = [];
|
|
1277
|
+
for (let i = 0; i < singles.length; i++) {
|
|
1278
|
+
const one = await singles[i];
|
|
1279
|
+
const { spec } = sheet.slots[i];
|
|
1280
|
+
out.push(one ? { img: one, y: 0, width: spec.width, height: spec.height } : null);
|
|
1281
|
+
}
|
|
1282
|
+
return out;
|
|
1190
1283
|
}
|
|
1191
1284
|
function yieldToBrowser() {
|
|
1192
1285
|
const scheduler = globalThis.scheduler;
|
|
@@ -1221,45 +1314,51 @@ async function rethemeSymbolImages(map, specs, opts = {}) {
|
|
|
1221
1314
|
const total = specs.length;
|
|
1222
1315
|
const result = { updated: 0, added: 0, failed: 0, aborted: false };
|
|
1223
1316
|
let done = 0;
|
|
1224
|
-
const
|
|
1317
|
+
const units = planRasterUnits(specs);
|
|
1318
|
+
const decoded = units.map((unit) => decode("one" in unit ? unit.one : unit.sheet));
|
|
1225
1319
|
let sliceStart = performance.now();
|
|
1226
1320
|
let lastRepaint = sliceStart;
|
|
1227
|
-
for (let
|
|
1321
|
+
outer: for (let u = 0; u < units.length; u++) {
|
|
1322
|
+
const unit = units[u];
|
|
1323
|
+
const unitSpecs = "one" in unit ? [unit.one] : unit.sheet.slots.map((slot) => slot.spec);
|
|
1228
1324
|
if (opts.signal?.aborted) {
|
|
1229
1325
|
result.aborted = true;
|
|
1230
1326
|
break;
|
|
1231
1327
|
}
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
const have = existingSize(map, spec.id);
|
|
1243
|
-
if (have && have.w === spec.width && have.h === spec.height) {
|
|
1244
|
-
map.updateImage(spec.id, pixels);
|
|
1245
|
-
result.updated++;
|
|
1328
|
+
const sources = await sourcesFor(unit, decoded[u]);
|
|
1329
|
+
for (let i = 0; i < unitSpecs.length; i++) {
|
|
1330
|
+
if (opts.signal?.aborted) {
|
|
1331
|
+
result.aborted = true;
|
|
1332
|
+
break outer;
|
|
1333
|
+
}
|
|
1334
|
+
const spec = unitSpecs[i];
|
|
1335
|
+
const src = sources[i];
|
|
1336
|
+
if (!src) {
|
|
1337
|
+
result.failed++;
|
|
1246
1338
|
} else {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1339
|
+
const px = rasterise(src, spec);
|
|
1340
|
+
const have = existingSize(map, spec.id);
|
|
1341
|
+
if (have && have.w === spec.width && have.h === spec.height) {
|
|
1342
|
+
map.updateImage(spec.id, px);
|
|
1343
|
+
result.updated++;
|
|
1344
|
+
} else {
|
|
1345
|
+
if (map.hasImage(spec.id)) map.removeImage(spec.id);
|
|
1346
|
+
map.addImage(spec.id, px, { sdf: spec.sdf, pixelRatio: spec.pixelRatio });
|
|
1347
|
+
result.added++;
|
|
1348
|
+
}
|
|
1349
|
+
opts.onApplied?.(spec.id);
|
|
1250
1350
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1351
|
+
done++;
|
|
1352
|
+
const now = performance.now();
|
|
1353
|
+
if (now - sliceStart >= sliceMs) {
|
|
1354
|
+
if (now - lastRepaint >= repaintEveryMs) {
|
|
1355
|
+
map.triggerRepaint();
|
|
1356
|
+
opts.onProgress?.({ done, total });
|
|
1357
|
+
lastRepaint = now;
|
|
1358
|
+
}
|
|
1359
|
+
await yieldToBrowser();
|
|
1360
|
+
sliceStart = performance.now();
|
|
1260
1361
|
}
|
|
1261
|
-
await yieldToBrowser();
|
|
1262
|
-
sliceStart = performance.now();
|
|
1263
1362
|
}
|
|
1264
1363
|
}
|
|
1265
1364
|
map.triggerRepaint();
|