ts-maps 0.3.4 → 0.3.6

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.
@@ -86,6 +86,7 @@ export declare interface VectorTileLayoutProperties {
86
86
  'text-font'?: string | string[]
87
87
  'text-italic'?: boolean
88
88
  'text-bold'?: boolean
89
+ 'text-letter-spacing'?: number | unknown
89
90
  'icon-image'?: string | unknown
90
91
  'icon-size'?: number
91
92
  'icon-rotate'?: number
@@ -1,10 +1,3 @@
1
- // GlyphAtlas — rasterises font glyphs to a single backing canvas and packs
2
- // them via a simple shelf-packer. Each glyph gets an approximate signed
3
- // distance field generated by a cheap 2-pass Chamfer 3-4 scan so callers can
4
- // resample the atlas at any target size with tolerably smooth edges.
5
- //
6
- // Zero runtime deps. Designed to live alongside Canvas2D rendering today and
7
- // graduate to WebGL sampling later without changing the public API.
8
1
  export declare interface GlyphMetrics {
9
2
  codePoint: number
10
3
  x: number
@@ -26,8 +19,8 @@ export declare class GlyphAtlas {
26
19
  constructor(opts?: GlyphAtlasOptions);
27
20
  getOrAddGlyph(codePoint: number, style?: { italic?: boolean, bold?: boolean }): GlyphMetrics;
28
21
  measure(text: string, style?: { italic?: boolean, bold?: boolean, family?: string }): { width: number, height: number };
29
- advances(text: string, size: number, style?: { italic?: boolean, bold?: boolean, family?: string }): Array<{ char: string, advance: number }>;
30
- measureText(text: string, size: number, style?: { italic?: boolean, bold?: boolean, family?: string }): { width: number, height: number, ascent: number, descent: number };
22
+ advances(text: string, size: number, style?: { italic?: boolean, bold?: boolean, family?: string, letterSpacing?: number }): Array<{ char: string, advance: number }>;
23
+ measureText(text: string, size: number, style?: { italic?: boolean, bold?: boolean, family?: string, letterSpacing?: number }): { width: number, height: number, ascent: number, descent: number };
31
24
  fontString(size: number, style?: { italic?: boolean, bold?: boolean, family?: string }): string;
32
25
  resolveFont(textFont: string | string[] | undefined): { family: string, bold: boolean, italic: boolean };
33
26
  drawText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, options: {
@@ -38,6 +31,7 @@ export declare class GlyphAtlas {
38
31
  italic?: boolean
39
32
  bold?: boolean
40
33
  family?: string
34
+ letterSpacing?: number
41
35
  }): void;
42
36
  _buildSDF(imageData: ImageData): ImageData;
43
37
  }
@@ -168,6 +168,23 @@ var exports_GlyphAtlas = {};
168
168
  __export(exports_GlyphAtlas, {
169
169
  GlyphAtlas: () => GlyphAtlas
170
170
  });
171
+ function applyTracking(ctx, spacing) {
172
+ if (!spacing)
173
+ return true;
174
+ if (!("letterSpacing" in ctx))
175
+ return false;
176
+ const tracked = ctx;
177
+ tracked.letterSpacing = `${spacing}px`;
178
+ return true;
179
+ }
180
+ function trackingWidth(text, spacing) {
181
+ if (!spacing)
182
+ return 0;
183
+ let count = 0;
184
+ for (const _ of text)
185
+ count++;
186
+ return count * spacing;
187
+ }
171
188
  function styleKey(k) {
172
189
  if (!k)
173
190
  return "r";
@@ -230,8 +247,9 @@ class GlyphAtlas {
230
247
  if (ctx) {
231
248
  ctx.save();
232
249
  ctx.font = this.fontString(size, style);
250
+ const tracking = style?.letterSpacing ?? 0;
233
251
  for (const ch of text)
234
- out.push({ char: ch, advance: ctx.measureText(ch).width });
252
+ out.push({ char: ch, advance: ctx.measureText(ch).width + tracking });
235
253
  ctx.restore();
236
254
  return out;
237
255
  }
@@ -249,15 +267,18 @@ class GlyphAtlas {
249
267
  const atlas = this.measure(text, style);
250
268
  const scale = size / this._fontSize;
251
269
  const height = atlas.height * scale;
252
- return { width: atlas.width * scale, height, ascent: height * 0.8, descent: height * 0.2 };
270
+ const tracking = trackingWidth(text, style?.letterSpacing);
271
+ return { width: atlas.width * scale + tracking, height, ascent: height * 0.8, descent: height * 0.2 };
253
272
  }
254
273
  ctx.save();
255
274
  ctx.font = this.fontString(size, style);
275
+ const spaced = applyTracking(ctx, style?.letterSpacing);
256
276
  const m = ctx.measureText(text);
277
+ const width = spaced ? m.width : m.width + trackingWidth(text, style?.letterSpacing);
257
278
  ctx.restore();
258
279
  const ascent = m.fontBoundingBoxAscent || m.actualBoundingBoxAscent || size * 0.8;
259
280
  const descent = m.fontBoundingBoxDescent || m.actualBoundingBoxDescent || size * 0.2;
260
- return { width: m.width, height: ascent + descent, ascent, descent };
281
+ return { width, height: ascent + descent, ascent, descent };
261
282
  }
262
283
  fontString(size, style) {
263
284
  const parts = [];
@@ -312,6 +333,7 @@ class GlyphAtlas {
312
333
  const style = { italic: !!options.italic, bold: !!options.bold, family: options.family };
313
334
  ctx.save();
314
335
  ctx.font = this.fontString(options.size, style);
336
+ applyTracking(ctx, options.letterSpacing);
315
337
  ctx.textAlign = "left";
316
338
  ctx.textBaseline = "alphabetic";
317
339
  if (options.haloColor && options.haloWidth && options.haloWidth > 0) {
package/dist/index.js CHANGED
@@ -8319,6 +8319,23 @@ var exports_GlyphAtlas = {};
8319
8319
  __export(exports_GlyphAtlas, {
8320
8320
  GlyphAtlas: () => GlyphAtlas
8321
8321
  });
8322
+ function applyTracking(ctx, spacing) {
8323
+ if (!spacing)
8324
+ return true;
8325
+ if (!("letterSpacing" in ctx))
8326
+ return false;
8327
+ const tracked = ctx;
8328
+ tracked.letterSpacing = `${spacing}px`;
8329
+ return true;
8330
+ }
8331
+ function trackingWidth(text, spacing) {
8332
+ if (!spacing)
8333
+ return 0;
8334
+ let count = 0;
8335
+ for (const _ of text)
8336
+ count++;
8337
+ return count * spacing;
8338
+ }
8322
8339
  function styleKey(k) {
8323
8340
  if (!k)
8324
8341
  return "r";
@@ -8381,8 +8398,9 @@ class GlyphAtlas {
8381
8398
  if (ctx) {
8382
8399
  ctx.save();
8383
8400
  ctx.font = this.fontString(size, style);
8401
+ const tracking = style?.letterSpacing ?? 0;
8384
8402
  for (const ch of text)
8385
- out.push({ char: ch, advance: ctx.measureText(ch).width });
8403
+ out.push({ char: ch, advance: ctx.measureText(ch).width + tracking });
8386
8404
  ctx.restore();
8387
8405
  return out;
8388
8406
  }
@@ -8400,15 +8418,18 @@ class GlyphAtlas {
8400
8418
  const atlas = this.measure(text, style);
8401
8419
  const scale = size / this._fontSize;
8402
8420
  const height = atlas.height * scale;
8403
- return { width: atlas.width * scale, height, ascent: height * 0.8, descent: height * 0.2 };
8421
+ const tracking = trackingWidth(text, style?.letterSpacing);
8422
+ return { width: atlas.width * scale + tracking, height, ascent: height * 0.8, descent: height * 0.2 };
8404
8423
  }
8405
8424
  ctx.save();
8406
8425
  ctx.font = this.fontString(size, style);
8426
+ const spaced = applyTracking(ctx, style?.letterSpacing);
8407
8427
  const m = ctx.measureText(text);
8428
+ const width = spaced ? m.width : m.width + trackingWidth(text, style?.letterSpacing);
8408
8429
  ctx.restore();
8409
8430
  const ascent = m.fontBoundingBoxAscent || m.actualBoundingBoxAscent || size * 0.8;
8410
8431
  const descent = m.fontBoundingBoxDescent || m.actualBoundingBoxDescent || size * 0.2;
8411
- return { width: m.width, height: ascent + descent, ascent, descent };
8432
+ return { width, height: ascent + descent, ascent, descent };
8412
8433
  }
8413
8434
  fontString(size, style) {
8414
8435
  const parts = [];
@@ -8463,6 +8484,7 @@ class GlyphAtlas {
8463
8484
  const style = { italic: !!options.italic, bold: !!options.bold, family: options.family };
8464
8485
  ctx.save();
8465
8486
  ctx.font = this.fontString(options.size, style);
8487
+ applyTracking(ctx, options.letterSpacing);
8466
8488
  ctx.textAlign = "left";
8467
8489
  ctx.textBaseline = "alphabetic";
8468
8490
  if (options.haloColor && options.haloWidth && options.haloWidth > 0) {
@@ -11552,8 +11574,9 @@ function drawSymbol(ctx, rings, styleLayer, feature, zoom, featureState, glyphAt
11552
11574
  return !collision.hits({ ...box, priority });
11553
11575
  return collision.tryInsert({ ...box, priority });
11554
11576
  };
11555
- const textStyle = { italic, bold, family: font.family };
11556
- const drawOptions = { color: textColor, haloColor, haloWidth, size: textSize, italic, bold, family: font.family };
11577
+ const letterSpacing = (resolveLayoutExpression(layout?.["text-letter-spacing"], zoom, feature, featureState) ?? 0) * textSize;
11578
+ const textStyle = { italic, bold, family: font.family, letterSpacing };
11579
+ const drawOptions = { color: textColor, haloColor, haloWidth, size: textSize, italic, bold, family: font.family, letterSpacing };
11557
11580
  const placeAlongLine = layout?.["symbol-placement"] === "line" || layout?.["symbol-placement"] === "line-center";
11558
11581
  if (placeAlongLine && text && feature.type !== 1) {
11559
11582
  drawLineLabel(ctx, rings, project, {
@@ -11773,7 +11796,7 @@ function evaluateFilterLegacy(filter, feature) {
11773
11796
  const key3 = rest[0];
11774
11797
  return typeof key3 === "string" && !(key3 in feature.properties);
11775
11798
  }
11776
- const left = resolveOperand(rest[0], feature);
11799
+ const left = resolveLegacyKey(rest[0], feature);
11777
11800
  if (op === "==" || op === "!=") {
11778
11801
  const right = resolveOperand(rest[1], feature);
11779
11802
  return op === "==" ? left === right : left !== right;
@@ -11794,6 +11817,19 @@ function evaluateFilterLegacy(filter, feature) {
11794
11817
  }
11795
11818
  return true;
11796
11819
  }
11820
+ function resolveLegacyKey(node, feature) {
11821
+ if (Array.isArray(node))
11822
+ return resolveOperand(node, feature);
11823
+ if (node === "$type") {
11824
+ const t = feature.type;
11825
+ return t === 1 ? "Point" : t === 2 ? "LineString" : t === 3 ? "Polygon" : "Unknown";
11826
+ }
11827
+ if (node === "$id")
11828
+ return feature.id ?? null;
11829
+ if (typeof node === "string")
11830
+ return feature.properties[node] ?? null;
11831
+ return node;
11832
+ }
11797
11833
  function resolveOperand(node, feature) {
11798
11834
  if (Array.isArray(node)) {
11799
11835
  if (node[0] === "get" && typeof node[1] === "string")
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ts-maps",
3
3
  "type": "module",
4
- "version": "0.3.4",
4
+ "version": "0.3.6",
5
5
  "description": "A modern vector map library.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",