maplibre-gl 3.2.2 → 3.3.0

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 (30) hide show
  1. package/build/generate-struct-arrays.ts +3 -1
  2. package/build/generate-style-code.ts +7 -8
  3. package/dist/maplibre-gl-csp-worker.js +1 -1
  4. package/dist/maplibre-gl-csp-worker.js.map +1 -1
  5. package/dist/maplibre-gl-csp.js +1 -1
  6. package/dist/maplibre-gl-csp.js.map +1 -1
  7. package/dist/maplibre-gl-dev.js +424 -168
  8. package/dist/maplibre-gl-dev.js.map +1 -1
  9. package/dist/maplibre-gl.d.ts +44 -7
  10. package/dist/maplibre-gl.js +3 -3
  11. package/dist/maplibre-gl.js.map +1 -1
  12. package/package.json +10 -10
  13. package/src/data/array_types.g.ts +78 -14
  14. package/src/data/bucket/symbol_attributes.ts +7 -1
  15. package/src/data/bucket/symbol_bucket.ts +4 -1
  16. package/src/render/draw_symbol.ts +8 -9
  17. package/src/style/properties.ts +4 -0
  18. package/src/style/style_layer/background_style_layer_properties.g.ts +1 -6
  19. package/src/style/style_layer/circle_style_layer_properties.g.ts +1 -6
  20. package/src/style/style_layer/fill_extrusion_style_layer_properties.g.ts +1 -6
  21. package/src/style/style_layer/fill_style_layer_properties.g.ts +1 -6
  22. package/src/style/style_layer/heatmap_style_layer_properties.g.ts +1 -6
  23. package/src/style/style_layer/hillshade_style_layer_properties.g.ts +1 -6
  24. package/src/style/style_layer/line_style_layer_properties.g.ts +1 -6
  25. package/src/style/style_layer/raster_style_layer_properties.g.ts +1 -6
  26. package/src/style/style_layer/symbol_style_layer_properties.g.ts +4 -6
  27. package/src/style/style_layer/variable_text_anchor.test.ts +117 -0
  28. package/src/style/style_layer/variable_text_anchor.ts +163 -0
  29. package/src/symbol/placement.ts +52 -40
  30. package/src/symbol/symbol_layout.ts +42 -116
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "maplibre-gl",
3
3
  "description": "BSD licensed community fork of mapbox-gl, a WebGL interactive maps library",
4
- "version": "3.2.2",
4
+ "version": "3.3.0",
5
5
  "main": "dist/maplibre-gl.js",
6
6
  "style": "dist/maplibre-gl.css",
7
7
  "license": "BSD-3-Clause",
@@ -20,7 +20,7 @@
20
20
  "@mapbox/unitbezier": "^0.0.1",
21
21
  "@mapbox/vector-tile": "^1.3.1",
22
22
  "@mapbox/whoots-js": "^3.1.0",
23
- "@maplibre/maplibre-gl-style-spec": "^19.2.2",
23
+ "@maplibre/maplibre-gl-style-spec": "^19.3.0",
24
24
  "@types/geojson": "^7946.0.10",
25
25
  "@types/mapbox__point-geometry": "^0.1.2",
26
26
  "@types/mapbox__vector-tile": "^1.3.0",
@@ -54,7 +54,7 @@
54
54
  "@types/d3": "^7.4.0",
55
55
  "@types/diff": "^5.0.3",
56
56
  "@types/earcut": "^2.1.1",
57
- "@types/eslint": "^8.44.1",
57
+ "@types/eslint": "^8.44.2",
58
58
  "@types/gl": "^6.0.2",
59
59
  "@types/glob": "^8.1.0",
60
60
  "@types/jest": "^29.5.3",
@@ -62,24 +62,24 @@
62
62
  "@types/minimist": "^1.2.2",
63
63
  "@types/murmurhash-js": "^1.0.4",
64
64
  "@types/nise": "^1.4.1",
65
- "@types/node": "^20.4.5",
65
+ "@types/node": "^20.4.8",
66
66
  "@types/offscreencanvas": "^2019.7.0",
67
67
  "@types/pixelmatch": "^5.2.4",
68
68
  "@types/pngjs": "^6.0.1",
69
- "@types/react": "^18.2.17",
69
+ "@types/react": "^18.2.18",
70
70
  "@types/react-dom": "^18.2.7",
71
71
  "@types/request": "^2.48.8",
72
72
  "@types/shuffle-seed": "^1.1.0",
73
73
  "@types/window-or-global": "^1.0.4",
74
- "@typescript-eslint/eslint-plugin": "^6.2.0",
75
- "@typescript-eslint/parser": "^6.2.0",
74
+ "@typescript-eslint/eslint-plugin": "^6.2.1",
75
+ "@typescript-eslint/parser": "^6.2.1",
76
76
  "address": "^1.2.2",
77
77
  "benchmark": "^2.1.4",
78
78
  "canvas": "^2.11.2",
79
79
  "cssnano": "^6.0.1",
80
80
  "d3": "^7.8.5",
81
81
  "d3-queue": "^3.0.7",
82
- "devtools-protocol": "^0.0.1173815",
82
+ "devtools-protocol": "^0.0.1179426",
83
83
  "diff": "^5.1.0",
84
84
  "dts-bundle-generator": "^8.0.1",
85
85
  "eslint": "^8.46.0",
@@ -111,10 +111,10 @@
111
111
  "postcss-cli": "^10.1.0",
112
112
  "postcss-inline-svg": "^6.0.0",
113
113
  "pretty-bytes": "^6.1.1",
114
- "puppeteer": "^20.9.0",
114
+ "puppeteer": "^21.0.1",
115
115
  "react": "^18.2.0",
116
116
  "react-dom": "^18.2.0",
117
- "rollup": "^3.27.0",
117
+ "rollup": "^3.27.2",
118
118
  "rollup-plugin-sourcemaps": "^0.6.3",
119
119
  "rw": "^1.3.3",
120
120
  "semver": "^7.5.4",
@@ -621,10 +621,11 @@ register('StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48', StructArrayLayout2i2ui3ul3u
621
621
  * [0]: Int16[8]
622
622
  * [16]: Uint16[15]
623
623
  * [48]: Uint32[1]
624
- * [52]: Float32[4]
624
+ * [52]: Float32[2]
625
+ * [60]: Uint16[2]
625
626
  *
626
627
  */
627
- class StructArrayLayout8i15ui1ul4f68 extends StructArray {
628
+ class StructArrayLayout8i15ui1ul2f2ui64 extends StructArray {
628
629
  uint8: Uint8Array;
629
630
  int16: Int16Array;
630
631
  uint16: Uint16Array;
@@ -646,8 +647,8 @@ class StructArrayLayout8i15ui1ul4f68 extends StructArray {
646
647
  }
647
648
 
648
649
  public emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number, v16: number, v17: number, v18: number, v19: number, v20: number, v21: number, v22: number, v23: number, v24: number, v25: number, v26: number, v27: number) {
649
- const o2 = i * 34;
650
- const o4 = i * 17;
650
+ const o2 = i * 32;
651
+ const o4 = i * 16;
651
652
  this.int16[o2 + 0] = v0;
652
653
  this.int16[o2 + 1] = v1;
653
654
  this.int16[o2 + 2] = v2;
@@ -674,14 +675,14 @@ class StructArrayLayout8i15ui1ul4f68 extends StructArray {
674
675
  this.uint32[o4 + 12] = v23;
675
676
  this.float32[o4 + 13] = v24;
676
677
  this.float32[o4 + 14] = v25;
677
- this.float32[o4 + 15] = v26;
678
- this.float32[o4 + 16] = v27;
678
+ this.uint16[o2 + 30] = v26;
679
+ this.uint16[o2 + 31] = v27;
679
680
  return i;
680
681
  }
681
682
  }
682
683
 
683
- StructArrayLayout8i15ui1ul4f68.prototype.bytesPerElement = 68;
684
- register('StructArrayLayout8i15ui1ul4f68', StructArrayLayout8i15ui1ul4f68);
684
+ StructArrayLayout8i15ui1ul2f2ui64.prototype.bytesPerElement = 64;
685
+ register('StructArrayLayout8i15ui1ul2f2ui64', StructArrayLayout8i15ui1ul2f2ui64);
685
686
 
686
687
  /**
687
688
  * @internal
@@ -714,6 +715,43 @@ class StructArrayLayout1f4 extends StructArray {
714
715
  StructArrayLayout1f4.prototype.bytesPerElement = 4;
715
716
  register('StructArrayLayout1f4', StructArrayLayout1f4);
716
717
 
718
+ /**
719
+ * @internal
720
+ * Implementation of the StructArray layout:
721
+ * [0]: Uint16[1]
722
+ * [4]: Float32[2]
723
+ *
724
+ */
725
+ class StructArrayLayout1ui2f12 extends StructArray {
726
+ uint8: Uint8Array;
727
+ uint16: Uint16Array;
728
+ float32: Float32Array;
729
+
730
+ _refreshViews() {
731
+ this.uint8 = new Uint8Array(this.arrayBuffer);
732
+ this.uint16 = new Uint16Array(this.arrayBuffer);
733
+ this.float32 = new Float32Array(this.arrayBuffer);
734
+ }
735
+
736
+ public emplaceBack(v0: number, v1: number, v2: number) {
737
+ const i = this.length;
738
+ this.resize(i + 1);
739
+ return this.emplace(i, v0, v1, v2);
740
+ }
741
+
742
+ public emplace(i: number, v0: number, v1: number, v2: number) {
743
+ const o2 = i * 6;
744
+ const o4 = i * 3;
745
+ this.uint16[o2 + 0] = v0;
746
+ this.float32[o4 + 1] = v1;
747
+ this.float32[o4 + 2] = v2;
748
+ return i;
749
+ }
750
+ }
751
+
752
+ StructArrayLayout1ui2f12.prototype.bytesPerElement = 12;
753
+ register('StructArrayLayout1ui2f12', StructArrayLayout1ui2f12);
754
+
717
755
  /**
718
756
  * @internal
719
757
  * Implementation of the StructArray layout:
@@ -951,17 +989,17 @@ class SymbolInstanceStruct extends Struct {
951
989
  get crossTileID() { return this._structArray.uint32[this._pos4 + 12]; }
952
990
  set crossTileID(x: number) { this._structArray.uint32[this._pos4 + 12] = x; }
953
991
  get textBoxScale() { return this._structArray.float32[this._pos4 + 13]; }
954
- get textOffset0() { return this._structArray.float32[this._pos4 + 14]; }
955
- get textOffset1() { return this._structArray.float32[this._pos4 + 15]; }
956
- get collisionCircleDiameter() { return this._structArray.float32[this._pos4 + 16]; }
992
+ get collisionCircleDiameter() { return this._structArray.float32[this._pos4 + 14]; }
993
+ get textAnchorOffsetStartIndex() { return this._structArray.uint16[this._pos2 + 30]; }
994
+ get textAnchorOffsetEndIndex() { return this._structArray.uint16[this._pos2 + 31]; }
957
995
  }
958
996
 
959
- SymbolInstanceStruct.prototype.size = 68;
997
+ SymbolInstanceStruct.prototype.size = 64;
960
998
 
961
999
  export type SymbolInstance = SymbolInstanceStruct;
962
1000
 
963
1001
  /** @internal */
964
- export class SymbolInstanceArray extends StructArrayLayout8i15ui1ul4f68 {
1002
+ export class SymbolInstanceArray extends StructArrayLayout8i15ui1ul2f2ui64 {
965
1003
  /**
966
1004
  * Return the SymbolInstanceStruct at the given location in the array.
967
1005
  * @param index The index of the element.
@@ -989,6 +1027,31 @@ export class SymbolLineVertexArray extends StructArrayLayout3i6 {
989
1027
 
990
1028
  register('SymbolLineVertexArray', SymbolLineVertexArray);
991
1029
 
1030
+ /** @internal */
1031
+ class TextAnchorOffsetStruct extends Struct {
1032
+ _structArray: TextAnchorOffsetArray;
1033
+ get textAnchor() { return this._structArray.uint16[this._pos2 + 0]; }
1034
+ get textOffset0() { return this._structArray.float32[this._pos4 + 1]; }
1035
+ get textOffset1() { return this._structArray.float32[this._pos4 + 2]; }
1036
+ }
1037
+
1038
+ TextAnchorOffsetStruct.prototype.size = 12;
1039
+
1040
+ export type TextAnchorOffset = TextAnchorOffsetStruct;
1041
+
1042
+ /** @internal */
1043
+ export class TextAnchorOffsetArray extends StructArrayLayout1ui2f12 {
1044
+ /**
1045
+ * Return the TextAnchorOffsetStruct at the given location in the array.
1046
+ * @param index The index of the element.
1047
+ */
1048
+ get(index: number): TextAnchorOffsetStruct {
1049
+ return new TextAnchorOffsetStruct(this, index);
1050
+ }
1051
+ }
1052
+
1053
+ register('TextAnchorOffsetArray', TextAnchorOffsetArray);
1054
+
992
1055
  /** @internal */
993
1056
  class FeatureIndexStruct extends Struct {
994
1057
  _structArray: FeatureIndexArray;
@@ -1051,8 +1114,9 @@ export {
1051
1114
  StructArrayLayout2ub2f12,
1052
1115
  StructArrayLayout3ui6,
1053
1116
  StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48,
1054
- StructArrayLayout8i15ui1ul4f68,
1117
+ StructArrayLayout8i15ui1ul2f2ui64,
1055
1118
  StructArrayLayout1f4,
1119
+ StructArrayLayout1ui2f12,
1056
1120
  StructArrayLayout1ul2ui8,
1057
1121
  StructArrayLayout2ui4,
1058
1122
  StructArrayLayout1ui2,
@@ -100,8 +100,9 @@ export const symbolInstance = createLayout([
100
100
  {type: 'Uint16', name: 'useRuntimeCollisionCircles'},
101
101
  {type: 'Uint32', name: 'crossTileID'},
102
102
  {type: 'Float32', name: 'textBoxScale'},
103
- {type: 'Float32', components: 2, name: 'textOffset'},
104
103
  {type: 'Float32', name: 'collisionCircleDiameter'},
104
+ {type: 'Uint16', name: 'textAnchorOffsetStartIndex'},
105
+ {type: 'Uint16', name: 'textAnchorOffsetEndIndex'}
105
106
  ]);
106
107
 
107
108
  export const glyphOffset = createLayout([
@@ -113,3 +114,8 @@ export const lineVertex = createLayout([
113
114
  {type: 'Int16', name: 'y'},
114
115
  {type: 'Int16', name: 'tileUnitDistanceFromAnchor'}
115
116
  ]);
117
+
118
+ export const textAnchorOffset = createLayout([
119
+ {type: 'Uint16', name: 'textAnchor'},
120
+ {type: 'Float32', components: 2, name: 'textOffset'}
121
+ ]);
@@ -13,7 +13,8 @@ import {SymbolLayoutArray,
13
13
  PlacedSymbolArray,
14
14
  SymbolInstanceArray,
15
15
  GlyphOffsetArray,
16
- SymbolLineVertexArray
16
+ SymbolLineVertexArray,
17
+ TextAnchorOffsetArray
17
18
  } from '../array_types.g';
18
19
 
19
20
  import Point from '@mapbox/point-geometry';
@@ -333,6 +334,7 @@ export class SymbolBucket implements Bucket {
333
334
  lineVertexArray: SymbolLineVertexArray;
334
335
  features: Array<SymbolFeature>;
335
336
  symbolInstances: SymbolInstanceArray;
337
+ textAnchorOffsets: TextAnchorOffsetArray;
336
338
  collisionArrays: Array<CollisionArrays>;
337
339
  sortKeyRanges: Array<SortKeyRange>;
338
340
  pixelRatio: number;
@@ -412,6 +414,7 @@ export class SymbolBucket implements Bucket {
412
414
  this.glyphOffsetArray = new GlyphOffsetArray();
413
415
  this.lineVertexArray = new SymbolLineVertexArray();
414
416
  this.symbolInstances = new SymbolInstanceArray();
417
+ this.textAnchorOffsets = new TextAnchorOffsetArray();
415
418
  }
416
419
 
417
420
  calculateGlyphDependencies(text: string, stack: {[_: number]: boolean}, textAlongLine: boolean, allowVerticalPlacement: boolean, doesAllowVerticalWritingMode: boolean) {
@@ -13,7 +13,6 @@ import {addDynamicAttributes} from '../data/bucket/symbol_bucket';
13
13
 
14
14
  import {getAnchorAlignment, WritingMode} from '../symbol/shaping';
15
15
  import ONE_EM from '../symbol/one_em';
16
- import {evaluateVariableOffset, TextAnchor} from '../symbol/symbol_layout';
17
16
 
18
17
  import {
19
18
  SymbolIconUniformsType,
@@ -37,6 +36,7 @@ import type {SymbolLayerSpecification} from '@maplibre/maplibre-gl-style-spec';
37
36
  import type {Transform} from '../geo/transform';
38
37
  import type {ColorMode} from '../gl/color_mode';
39
38
  import type {Program} from './program';
39
+ import type {TextAnchor} from '../style/style_layer/variable_text_anchor';
40
40
 
41
41
  type SymbolTileRenderState = {
42
42
  segments: SegmentVector;
@@ -65,11 +65,11 @@ export function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: S
65
65
  // Disable the stencil test so that labels aren't clipped to tile boundaries.
66
66
  const stencilMode = StencilMode.disabled;
67
67
  const colorMode = painter.colorModeForRenderPass();
68
- const variablePlacement = layer.layout.get('text-variable-anchor');
68
+ const hasVariablePlacement = layer._unevaluatedLayout.hasValue('text-variable-anchor') || layer._unevaluatedLayout.hasValue('text-variable-anchor-offset');
69
69
 
70
70
  //Compute variable-offsets before painting since icons and text data positioning
71
71
  //depend on each other in this case.
72
- if (variablePlacement) {
72
+ if (hasVariablePlacement) {
73
73
  updateVariableAnchors(coords, painter, layer, sourceCache,
74
74
  layer.layout.get('text-rotation-alignment'),
75
75
  layer.layout.get('text-pitch-alignment'),
@@ -117,10 +117,9 @@ function calculateVariableRenderShift(
117
117
  const {horizontalAlign, verticalAlign} = getAnchorAlignment(anchor);
118
118
  const shiftX = -(horizontalAlign - 0.5) * width;
119
119
  const shiftY = -(verticalAlign - 0.5) * height;
120
- const variableOffset = evaluateVariableOffset(anchor, textOffset);
121
120
  return new Point(
122
- (shiftX / textBoxScale + variableOffset[0]) * renderTextSize,
123
- (shiftY / textBoxScale + variableOffset[1]) * renderTextSize
121
+ (shiftX / textBoxScale + textOffset[0]) * renderTextSize,
122
+ (shiftY / textBoxScale + textOffset[1]) * renderTextSize
124
123
  );
125
124
  }
126
125
 
@@ -281,7 +280,7 @@ function drawLayerSymbols(
281
280
 
282
281
  const depthMode = painter.depthModeForSublayer(0, DepthMode.ReadOnly);
283
282
 
284
- const variablePlacement = layer.layout.get('text-variable-anchor');
283
+ const hasVariablePlacement = layer._unevaluatedLayout.hasValue('text-variable-anchor') || layer._unevaluatedLayout.hasValue('text-variable-anchor-offset');
285
284
 
286
285
  const tileRenderState: Array<SymbolTileRenderState> = [];
287
286
 
@@ -332,7 +331,7 @@ function drawLayerSymbols(
332
331
  const labelPlaneMatrix = symbolProjection.getLabelPlaneMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s);
333
332
  const glCoordMatrix = symbolProjection.getGlCoordMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s);
334
333
 
335
- const hasVariableAnchors = variablePlacement && bucket.hasTextData();
334
+ const hasVariableAnchors = hasVariablePlacement && bucket.hasTextData();
336
335
  const updateTextFitIcon = layer.layout.get('icon-text-fit') !== 'none' &&
337
336
  hasVariableAnchors &&
338
337
  bucket.hasIconData();
@@ -344,7 +343,7 @@ function drawLayerSymbols(
344
343
  }
345
344
 
346
345
  const matrix = painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor),
347
- uLabelPlaneMatrix = (alongLine || (isText && variablePlacement) || updateTextFitIcon) ? identityMat4 : labelPlaneMatrix,
346
+ uLabelPlaneMatrix = (alongLine || (isText && hasVariablePlacement) || updateTextFitIcon) ? identityMat4 : labelPlaneMatrix,
348
347
  uglCoordMatrix = painter.translatePosMatrix(glCoordMatrix, tile, translate, translateAnchor, true);
349
348
 
350
349
  const hasHalo = isSDF && layer.paint.get(isText ? 'text-halo-width' : 'icon-halo-width').constantOr(1) !== 0;
@@ -317,6 +317,10 @@ export class Layout<Props> {
317
317
  this._values = (Object.create(properties.defaultPropertyValues) as any);
318
318
  }
319
319
 
320
+ hasValue<S extends keyof Props>(name: S) {
321
+ return this._values[name].value !== undefined;
322
+ }
323
+
320
324
  getValue<S extends keyof Props>(name: S) {
321
325
  return clone(this._values[name].value);
322
326
  }
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
  export type CircleLayoutProps = {
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
  export type FillLayoutProps = {
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
  export type LineLayoutProps = {
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
  import {
@@ -58,6 +53,7 @@ export type SymbolLayoutProps = {
58
53
  "text-justify": DataDrivenProperty<"auto" | "left" | "center" | "right">,
59
54
  "text-radial-offset": DataDrivenProperty<number>,
60
55
  "text-variable-anchor": DataConstantProperty<Array<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">>,
56
+ "text-variable-anchor-offset": DataDrivenProperty<VariableAnchorOffsetCollection>,
61
57
  "text-anchor": DataDrivenProperty<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">,
62
58
  "text-max-angle": DataConstantProperty<number>,
63
59
  "text-writing-mode": DataConstantProperty<Array<"horizontal" | "vertical">>,
@@ -104,6 +100,7 @@ export type SymbolLayoutPropsPossiblyEvaluated = {
104
100
  "text-justify": PossiblyEvaluatedPropertyValue<"auto" | "left" | "center" | "right">,
105
101
  "text-radial-offset": PossiblyEvaluatedPropertyValue<number>,
106
102
  "text-variable-anchor": Array<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">,
103
+ "text-variable-anchor-offset": PossiblyEvaluatedPropertyValue<VariableAnchorOffsetCollection>,
107
104
  "text-anchor": PossiblyEvaluatedPropertyValue<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">,
108
105
  "text-max-angle": number,
109
106
  "text-writing-mode": Array<"horizontal" | "vertical">,
@@ -151,6 +148,7 @@ const getLayout = () => layout = layout || new Properties({
151
148
  "text-justify": new DataDrivenProperty(styleSpec["layout_symbol"]["text-justify"] as any as StylePropertySpecification),
152
149
  "text-radial-offset": new DataDrivenProperty(styleSpec["layout_symbol"]["text-radial-offset"] as any as StylePropertySpecification),
153
150
  "text-variable-anchor": new DataConstantProperty(styleSpec["layout_symbol"]["text-variable-anchor"] as any as StylePropertySpecification),
151
+ "text-variable-anchor-offset": new DataDrivenProperty(styleSpec["layout_symbol"]["text-variable-anchor-offset"] as any as StylePropertySpecification),
154
152
  "text-anchor": new DataDrivenProperty(styleSpec["layout_symbol"]["text-anchor"] as any as StylePropertySpecification),
155
153
  "text-max-angle": new DataConstantProperty(styleSpec["layout_symbol"]["text-max-angle"] as any as StylePropertySpecification),
156
154
  "text-writing-mode": new DataConstantProperty(styleSpec["layout_symbol"]["text-writing-mode"] as any as StylePropertySpecification),
@@ -0,0 +1,117 @@
1
+ import {EvaluationParameters} from '../evaluation_parameters';
2
+ import {ZoomHistory} from '../zoom_history';
3
+ import {SymbolStyleLayer} from './symbol_style_layer';
4
+ import {INVALID_TEXT_OFFSET, evaluateVariableOffset, getTextVariableAnchorOffset} from './variable_text_anchor';
5
+
6
+ describe('evaluateVariableOffset', () => {
7
+ test('fromRadialOffset', () => {
8
+ // Radial offset mode is invoked by using INVALID_TEXT_OFFSET as the Y value
9
+ const srcOffset = [10, INVALID_TEXT_OFFSET] as [number, number];
10
+
11
+ expect(evaluateVariableOffset('center', srcOffset)).toEqual([0, 0]);
12
+
13
+ // Top/bottom offsets are shifted by the default baseline (7)
14
+ expect(evaluateVariableOffset('top', srcOffset)).toEqual([0, 3]);
15
+ expect(evaluateVariableOffset('bottom', srcOffset)).toEqual([0, -3]);
16
+ expect(evaluateVariableOffset('left', srcOffset)).toEqual([10, 0]);
17
+ expect(evaluateVariableOffset('right', srcOffset)).toEqual([-10, 0]);
18
+
19
+ const hypotenuse = 10 / Math.SQRT2;
20
+ expect(evaluateVariableOffset('top-left', srcOffset)).toEqual([expect.closeTo(hypotenuse), expect.closeTo(hypotenuse - 7)]);
21
+ expect(evaluateVariableOffset('top-right', srcOffset)).toEqual([expect.closeTo(-hypotenuse), expect.closeTo(hypotenuse - 7)]);
22
+ expect(evaluateVariableOffset('bottom-left', srcOffset)).toEqual([expect.closeTo(hypotenuse), expect.closeTo(-hypotenuse + 7)]);
23
+ expect(evaluateVariableOffset('bottom-right', srcOffset)).toEqual([expect.closeTo(-hypotenuse), expect.closeTo(-hypotenuse + 7)]);
24
+ });
25
+
26
+ test('fromTextOffset', () => {
27
+ const srcOffset = [10, -10] as [number, number];
28
+
29
+ expect(evaluateVariableOffset('center', srcOffset)).toEqual([0, 0]);
30
+
31
+ // Top/bottom offsets are shifted by the default baseline (7)
32
+ expect(evaluateVariableOffset('top', srcOffset)).toEqual([0, 3]);
33
+ expect(evaluateVariableOffset('bottom', srcOffset)).toEqual([0, -3]);
34
+ expect(evaluateVariableOffset('left', srcOffset)).toEqual([10, 0]);
35
+ expect(evaluateVariableOffset('right', srcOffset)).toEqual([-10, 0]);
36
+ expect(evaluateVariableOffset('top-left', srcOffset)).toEqual([10, 3]);
37
+ expect(evaluateVariableOffset('top-right', srcOffset)).toEqual([-10, 3]);
38
+ expect(evaluateVariableOffset('bottom-left', srcOffset)).toEqual([10, -3]);
39
+ expect(evaluateVariableOffset('bottom-right', srcOffset)).toEqual([-10, -3]);
40
+ });
41
+ });
42
+
43
+ function createSymbolLayer(layerProperties) {
44
+ const layer = new SymbolStyleLayer(layerProperties);
45
+ layer.recalculate({zoom: 0, zoomHistory: {} as ZoomHistory} as EvaluationParameters, []);
46
+ return layer;
47
+ }
48
+
49
+ describe('getTextVariableAnchorOffset', () => {
50
+ test('defaults - no props set', () => {
51
+ const props = {};
52
+ const layer = createSymbolLayer(props);
53
+
54
+ expect(getTextVariableAnchorOffset(layer, null, null)).toBeNull();
55
+ });
56
+
57
+ test('text-variable-anchor-offset set', () => {
58
+ const props = {layout: {'text-variable-anchor-offset': ['top', [1, 1], 'bottom', [2, 2]]}};
59
+ const layer = createSymbolLayer(props);
60
+
61
+ const offset = getTextVariableAnchorOffset(layer, null, null);
62
+ expect(offset).toBeDefined();
63
+ // Offset converted to EMs, accounting for baseline shift on Y axis
64
+ expect(offset.toString()).toBe('["top",[24,17],"bottom",[48,55]]');
65
+ });
66
+
67
+ test('text-variable-anchor set', () => {
68
+ const props = {layout: {'text-variable-anchor': ['top']}};
69
+ const layer = createSymbolLayer(props);
70
+
71
+ const offset = getTextVariableAnchorOffset(layer, null, null);
72
+ expect(offset).toBeDefined();
73
+ // Default offset (0, 0) converted to EMs, accounting for baseline shift on Y axis
74
+ expect(offset.toString()).toBe('["top",[0,-7]]');
75
+ });
76
+
77
+ test('text-variable-anchor and text-offset set', () => {
78
+ const props = {layout: {'text-variable-anchor': ['top'], 'text-offset': [1, 1]}};
79
+ const layer = createSymbolLayer(props);
80
+
81
+ const offset = getTextVariableAnchorOffset(layer, null, null);
82
+ expect(offset).toBeDefined();
83
+ // Offset converted to EMs, accounting for baseline shift on Y axis
84
+ expect(offset.toString()).toBe('["top",[0,17]]');
85
+ });
86
+
87
+ test('text-variable-anchor and text-radial-offset set', () => {
88
+ const props = {layout: {'text-variable-anchor': ['top'], 'text-radial-offset': 2}};
89
+ const layer = createSymbolLayer(props);
90
+
91
+ const offset = getTextVariableAnchorOffset(layer, null, null);
92
+ expect(offset).toBeDefined();
93
+ // Offset converted to EMs, accounting for baseline shift on Y axis
94
+ expect(offset.toString()).toBe('["top",[0,41]]');
95
+ });
96
+
97
+ test('text-variable-anchor, text-offset, and text-radial-offset set', () => {
98
+ const props = {layout: {'text-variable-anchor': ['top'], 'text-offset': [1, 1], 'text-radial-offset': 2}};
99
+ const layer = createSymbolLayer(props);
100
+
101
+ const offset = getTextVariableAnchorOffset(layer, null, null);
102
+ expect(offset).toBeDefined();
103
+ // Offset converted to EMs, accounting for baseline shift on Y axis
104
+ expect(offset.toString()).toBe('["top",[0,41]]');
105
+ });
106
+
107
+ test('text-variable-anchor and text-variable-anchor-offset set', () => {
108
+ const props = {layout: {'text-variable-anchor-offset': ['top', [1, 1]], 'text-variable-anchor': ['bottom']}};
109
+ const layer = createSymbolLayer(props);
110
+
111
+ const offset = getTextVariableAnchorOffset(layer, null, null);
112
+ expect(offset).toBeDefined();
113
+ // Offset converted to EMs, accounting for baseline shift on Y axis
114
+ expect(offset.toString()).toBe('["top",[24,17]]');
115
+ });
116
+ });
117
+