ts-maps 0.3.3 → 0.3.5

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
@@ -1428,6 +1428,23 @@ function registerLogicalOps() {
1428
1428
  registerOperator("in", (args, compile, path) => {
1429
1429
  if (args.length < 1)
1430
1430
  throw new ExpressionError('"in" expects at least 1 argument', ["in", ...args], path);
1431
+ if (args.length === 2 && Array.isArray(args[1])) {
1432
+ const needle = compile(args[0], "value", path.concat(1));
1433
+ const haystack2 = compile(args[1], "value", path.concat(2));
1434
+ return {
1435
+ evaluate: (ctx) => {
1436
+ const x = needle.evaluate(ctx);
1437
+ const hay = haystack2.evaluate(ctx);
1438
+ if (Array.isArray(hay))
1439
+ return hay.some((value) => strictEq(x, value));
1440
+ if (typeof hay === "string")
1441
+ return typeof x === "string" && hay.includes(x);
1442
+ return false;
1443
+ },
1444
+ returnType: "boolean",
1445
+ ...mergeDeps4([needle, haystack2])
1446
+ };
1447
+ }
1431
1448
  const input = compile(args[0], "value", path.concat(1));
1432
1449
  const haystack = args.slice(1);
1433
1450
  return {
@@ -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
@@ -4725,6 +4725,23 @@ function registerLogicalOps() {
4725
4725
  registerOperator("in", (args, compile, path) => {
4726
4726
  if (args.length < 1)
4727
4727
  throw new ExpressionError('"in" expects at least 1 argument', ["in", ...args], path);
4728
+ if (args.length === 2 && Array.isArray(args[1])) {
4729
+ const needle = compile(args[0], "value", path.concat(1));
4730
+ const haystack2 = compile(args[1], "value", path.concat(2));
4731
+ return {
4732
+ evaluate: (ctx) => {
4733
+ const x = needle.evaluate(ctx);
4734
+ const hay = haystack2.evaluate(ctx);
4735
+ if (Array.isArray(hay))
4736
+ return hay.some((value) => strictEq(x, value));
4737
+ if (typeof hay === "string")
4738
+ return typeof x === "string" && hay.includes(x);
4739
+ return false;
4740
+ },
4741
+ returnType: "boolean",
4742
+ ...mergeDeps4([needle, haystack2])
4743
+ };
4744
+ }
4728
4745
  const input = compile(args[0], "value", path.concat(1));
4729
4746
  const haystack = args.slice(1);
4730
4747
  return {
@@ -8302,6 +8319,23 @@ var exports_GlyphAtlas = {};
8302
8319
  __export(exports_GlyphAtlas, {
8303
8320
  GlyphAtlas: () => GlyphAtlas
8304
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
+ }
8305
8339
  function styleKey(k) {
8306
8340
  if (!k)
8307
8341
  return "r";
@@ -8364,8 +8398,9 @@ class GlyphAtlas {
8364
8398
  if (ctx) {
8365
8399
  ctx.save();
8366
8400
  ctx.font = this.fontString(size, style);
8401
+ const tracking = style?.letterSpacing ?? 0;
8367
8402
  for (const ch of text)
8368
- out.push({ char: ch, advance: ctx.measureText(ch).width });
8403
+ out.push({ char: ch, advance: ctx.measureText(ch).width + tracking });
8369
8404
  ctx.restore();
8370
8405
  return out;
8371
8406
  }
@@ -8383,15 +8418,18 @@ class GlyphAtlas {
8383
8418
  const atlas = this.measure(text, style);
8384
8419
  const scale = size / this._fontSize;
8385
8420
  const height = atlas.height * scale;
8386
- 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 };
8387
8423
  }
8388
8424
  ctx.save();
8389
8425
  ctx.font = this.fontString(size, style);
8426
+ const spaced = applyTracking(ctx, style?.letterSpacing);
8390
8427
  const m = ctx.measureText(text);
8428
+ const width = spaced ? m.width : m.width + trackingWidth(text, style?.letterSpacing);
8391
8429
  ctx.restore();
8392
8430
  const ascent = m.fontBoundingBoxAscent || m.actualBoundingBoxAscent || size * 0.8;
8393
8431
  const descent = m.fontBoundingBoxDescent || m.actualBoundingBoxDescent || size * 0.2;
8394
- return { width: m.width, height: ascent + descent, ascent, descent };
8432
+ return { width, height: ascent + descent, ascent, descent };
8395
8433
  }
8396
8434
  fontString(size, style) {
8397
8435
  const parts = [];
@@ -8446,6 +8484,7 @@ class GlyphAtlas {
8446
8484
  const style = { italic: !!options.italic, bold: !!options.bold, family: options.family };
8447
8485
  ctx.save();
8448
8486
  ctx.font = this.fontString(options.size, style);
8487
+ applyTracking(ctx, options.letterSpacing);
8449
8488
  ctx.textAlign = "left";
8450
8489
  ctx.textBaseline = "alphabetic";
8451
8490
  if (options.haloColor && options.haloWidth && options.haloWidth > 0) {
@@ -11535,8 +11574,9 @@ function drawSymbol(ctx, rings, styleLayer, feature, zoom, featureState, glyphAt
11535
11574
  return !collision.hits({ ...box, priority });
11536
11575
  return collision.tryInsert({ ...box, priority });
11537
11576
  };
11538
- const textStyle = { italic, bold, family: font.family };
11539
- 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 };
11540
11580
  const placeAlongLine = layout?.["symbol-placement"] === "line" || layout?.["symbol-placement"] === "line-center";
11541
11581
  if (placeAlongLine && text && feature.type !== 1) {
11542
11582
  drawLineLabel(ctx, rings, project, {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ts-maps",
3
3
  "type": "module",
4
- "version": "0.3.3",
4
+ "version": "0.3.5",
5
5
  "description": "A modern vector map library.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",