diff --git a/debug/variable-anchor-with-icon-text-fit.html b/debug/variable-anchor-with-icon-text-fit.html
new file mode 100644
index 00000000000..9ade7ac7172
--- /dev/null
+++ b/debug/variable-anchor-with-icon-text-fit.html
@@ -0,0 +1,132 @@
+
+
+
+
+ Mapbox GL JS debug page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/data/array_types.js b/src/data/array_types.js
index 31138acba54..b5114e89d7a 100644
--- a/src/data/array_types.js
+++ b/src/data/array_types.js
@@ -422,10 +422,11 @@ register('StructArrayLayout2ub2f12', StructArrayLayout2ub2f12);
* [28]: Float32[2]
* [36]: Uint8[3]
* [40]: Uint32[1]
+ * [44]: Int16[1]
*
* @private
*/
-class StructArrayLayout2i2ui3ul3ui2f3ub1ul44 extends StructArray {
+class StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48 extends StructArray {
uint8: Uint8Array;
int16: Int16Array;
uint16: Uint16Array;
@@ -440,16 +441,16 @@ class StructArrayLayout2i2ui3ul3ui2f3ub1ul44 extends StructArray {
this.float32 = new Float32Array(this.arrayBuffer);
}
- emplaceBack(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) {
+ emplaceBack(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) {
const i = this.length;
this.resize(i + 1);
- return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15);
+ return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16);
}
- 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) {
- const o2 = i * 22;
- const o4 = i * 11;
- const o1 = i * 44;
+ 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) {
+ const o2 = i * 24;
+ const o4 = i * 12;
+ const o1 = i * 48;
this.int16[o2 + 0] = v0;
this.int16[o2 + 1] = v1;
this.uint16[o2 + 2] = v2;
@@ -466,23 +467,24 @@ class StructArrayLayout2i2ui3ul3ui2f3ub1ul44 extends StructArray {
this.uint8[o1 + 37] = v13;
this.uint8[o1 + 38] = v14;
this.uint32[o4 + 10] = v15;
+ this.int16[o2 + 22] = v16;
return i;
}
}
-StructArrayLayout2i2ui3ul3ui2f3ub1ul44.prototype.bytesPerElement = 44;
-register('StructArrayLayout2i2ui3ul3ui2f3ub1ul44', StructArrayLayout2i2ui3ul3ui2f3ub1ul44);
+StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48.prototype.bytesPerElement = 48;
+register('StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48', StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48);
/**
* Implementation of the StructArray layout:
- * [0]: Int16[6]
- * [12]: Uint16[11]
+ * [0]: Int16[7]
+ * [14]: Uint16[11]
* [36]: Uint32[1]
* [40]: Float32[3]
*
* @private
*/
-class StructArrayLayout6i11ui1ul3f52 extends StructArray {
+class StructArrayLayout7i11ui1ul3f52 extends StructArray {
uint8: Uint8Array;
int16: Int16Array;
uint16: Uint16Array;
@@ -497,13 +499,13 @@ class StructArrayLayout6i11ui1ul3f52 extends StructArray {
this.float32 = new Float32Array(this.arrayBuffer);
}
- emplaceBack(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) {
+ emplaceBack(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) {
const i = this.length;
this.resize(i + 1);
- return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20);
+ return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21);
}
- 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) {
+ 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) {
const o2 = i * 26;
const o4 = i * 13;
this.int16[o2 + 0] = v0;
@@ -512,7 +514,7 @@ class StructArrayLayout6i11ui1ul3f52 extends StructArray {
this.int16[o2 + 3] = v3;
this.int16[o2 + 4] = v4;
this.int16[o2 + 5] = v5;
- this.uint16[o2 + 6] = v6;
+ this.int16[o2 + 6] = v6;
this.uint16[o2 + 7] = v7;
this.uint16[o2 + 8] = v8;
this.uint16[o2 + 9] = v9;
@@ -523,16 +525,17 @@ class StructArrayLayout6i11ui1ul3f52 extends StructArray {
this.uint16[o2 + 14] = v14;
this.uint16[o2 + 15] = v15;
this.uint16[o2 + 16] = v16;
- this.uint32[o4 + 9] = v17;
- this.float32[o4 + 10] = v18;
- this.float32[o4 + 11] = v19;
- this.float32[o4 + 12] = v20;
+ this.uint16[o2 + 17] = v17;
+ this.uint32[o4 + 9] = v18;
+ this.float32[o4 + 10] = v19;
+ this.float32[o4 + 11] = v20;
+ this.float32[o4 + 12] = v21;
return i;
}
}
-StructArrayLayout6i11ui1ul3f52.prototype.bytesPerElement = 52;
-register('StructArrayLayout6i11ui1ul3f52', StructArrayLayout6i11ui1ul3f52);
+StructArrayLayout7i11ui1ul3f52.prototype.bytesPerElement = 52;
+register('StructArrayLayout7i11ui1ul3f52', StructArrayLayout7i11ui1ul3f52);
/**
* Implementation of the StructArray layout:
@@ -874,6 +877,7 @@ class PlacedSymbolStruct extends Struct {
placedOrientation: number;
hidden: number;
crossTileID: number;
+ associatedIconIndex: number;
get anchorX() { return this._structArray.int16[this._pos2 + 0]; }
set anchorX(x: number) { this._structArray.int16[this._pos2 + 0] = x; }
get anchorY() { return this._structArray.int16[this._pos2 + 1]; }
@@ -906,16 +910,18 @@ class PlacedSymbolStruct extends Struct {
set hidden(x: number) { this._structArray.uint8[this._pos1 + 38] = x; }
get crossTileID() { return this._structArray.uint32[this._pos4 + 10]; }
set crossTileID(x: number) { this._structArray.uint32[this._pos4 + 10] = x; }
+ get associatedIconIndex() { return this._structArray.int16[this._pos2 + 22]; }
+ set associatedIconIndex(x: number) { this._structArray.int16[this._pos2 + 22] = x; }
}
-PlacedSymbolStruct.prototype.size = 44;
+PlacedSymbolStruct.prototype.size = 48;
export type PlacedSymbol = PlacedSymbolStruct;
/**
* @private
*/
-export class PlacedSymbolArray extends StructArrayLayout2i2ui3ul3ui2f3ub1ul44 {
+export class PlacedSymbolArray extends StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48 {
/**
* Return the PlacedSymbolStruct at the given location in the array.
* @param {number} index The index of the element.
@@ -936,6 +942,7 @@ class SymbolInstanceStruct extends Struct {
centerJustifiedTextSymbolIndex: number;
leftJustifiedTextSymbolIndex: number;
verticalPlacedTextSymbolIndex: number;
+ placedIconSymbolIndex: number;
key: number;
textBoxStartIndex: number;
textBoxEndIndex: number;
@@ -963,28 +970,30 @@ class SymbolInstanceStruct extends Struct {
set leftJustifiedTextSymbolIndex(x: number) { this._structArray.int16[this._pos2 + 4] = x; }
get verticalPlacedTextSymbolIndex() { return this._structArray.int16[this._pos2 + 5]; }
set verticalPlacedTextSymbolIndex(x: number) { this._structArray.int16[this._pos2 + 5] = x; }
- get key() { return this._structArray.uint16[this._pos2 + 6]; }
- set key(x: number) { this._structArray.uint16[this._pos2 + 6] = x; }
- get textBoxStartIndex() { return this._structArray.uint16[this._pos2 + 7]; }
- set textBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 7] = x; }
- get textBoxEndIndex() { return this._structArray.uint16[this._pos2 + 8]; }
- set textBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 8] = x; }
- get verticalTextBoxStartIndex() { return this._structArray.uint16[this._pos2 + 9]; }
- set verticalTextBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 9] = x; }
- get verticalTextBoxEndIndex() { return this._structArray.uint16[this._pos2 + 10]; }
- set verticalTextBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 10] = x; }
- get iconBoxStartIndex() { return this._structArray.uint16[this._pos2 + 11]; }
- set iconBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 11] = x; }
- get iconBoxEndIndex() { return this._structArray.uint16[this._pos2 + 12]; }
- set iconBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 12] = x; }
- get featureIndex() { return this._structArray.uint16[this._pos2 + 13]; }
- set featureIndex(x: number) { this._structArray.uint16[this._pos2 + 13] = x; }
- get numHorizontalGlyphVertices() { return this._structArray.uint16[this._pos2 + 14]; }
- set numHorizontalGlyphVertices(x: number) { this._structArray.uint16[this._pos2 + 14] = x; }
- get numVerticalGlyphVertices() { return this._structArray.uint16[this._pos2 + 15]; }
- set numVerticalGlyphVertices(x: number) { this._structArray.uint16[this._pos2 + 15] = x; }
- get numIconVertices() { return this._structArray.uint16[this._pos2 + 16]; }
- set numIconVertices(x: number) { this._structArray.uint16[this._pos2 + 16] = x; }
+ get placedIconSymbolIndex() { return this._structArray.int16[this._pos2 + 6]; }
+ set placedIconSymbolIndex(x: number) { this._structArray.int16[this._pos2 + 6] = x; }
+ get key() { return this._structArray.uint16[this._pos2 + 7]; }
+ set key(x: number) { this._structArray.uint16[this._pos2 + 7] = x; }
+ get textBoxStartIndex() { return this._structArray.uint16[this._pos2 + 8]; }
+ set textBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 8] = x; }
+ get textBoxEndIndex() { return this._structArray.uint16[this._pos2 + 9]; }
+ set textBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 9] = x; }
+ get verticalTextBoxStartIndex() { return this._structArray.uint16[this._pos2 + 10]; }
+ set verticalTextBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 10] = x; }
+ get verticalTextBoxEndIndex() { return this._structArray.uint16[this._pos2 + 11]; }
+ set verticalTextBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 11] = x; }
+ get iconBoxStartIndex() { return this._structArray.uint16[this._pos2 + 12]; }
+ set iconBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 12] = x; }
+ get iconBoxEndIndex() { return this._structArray.uint16[this._pos2 + 13]; }
+ set iconBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 13] = x; }
+ get featureIndex() { return this._structArray.uint16[this._pos2 + 14]; }
+ set featureIndex(x: number) { this._structArray.uint16[this._pos2 + 14] = x; }
+ get numHorizontalGlyphVertices() { return this._structArray.uint16[this._pos2 + 15]; }
+ set numHorizontalGlyphVertices(x: number) { this._structArray.uint16[this._pos2 + 15] = x; }
+ get numVerticalGlyphVertices() { return this._structArray.uint16[this._pos2 + 16]; }
+ set numVerticalGlyphVertices(x: number) { this._structArray.uint16[this._pos2 + 16] = x; }
+ get numIconVertices() { return this._structArray.uint16[this._pos2 + 17]; }
+ set numIconVertices(x: number) { this._structArray.uint16[this._pos2 + 17] = x; }
get crossTileID() { return this._structArray.uint32[this._pos4 + 9]; }
set crossTileID(x: number) { this._structArray.uint32[this._pos4 + 9] = x; }
get textBoxScale() { return this._structArray.float32[this._pos4 + 10]; }
@@ -1002,7 +1011,7 @@ export type SymbolInstance = SymbolInstanceStruct;
/**
* @private
*/
-export class SymbolInstanceArray extends StructArrayLayout6i11ui1ul3f52 {
+export class SymbolInstanceArray extends StructArrayLayout7i11ui1ul3f52 {
/**
* Return the SymbolInstanceStruct at the given location in the array.
* @param {number} index The index of the element.
@@ -1124,8 +1133,8 @@ export {
StructArrayLayout6i1ul2ui2i24,
StructArrayLayout2i2i2i12,
StructArrayLayout2ub2f12,
- StructArrayLayout2i2ui3ul3ui2f3ub1ul44,
- StructArrayLayout6i11ui1ul3f52,
+ StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48,
+ StructArrayLayout7i11ui1ul3f52,
StructArrayLayout1f4,
StructArrayLayout3i6,
StructArrayLayout1ul2ui8,
diff --git a/src/data/bucket/symbol_attributes.js b/src/data/bucket/symbol_attributes.js
index bf78d74a098..d3384f3cb24 100644
--- a/src/data/bucket/symbol_attributes.js
+++ b/src/data/bucket/symbol_attributes.js
@@ -73,7 +73,8 @@ export const placement = createLayout([
{type: 'Uint8', name: 'writingMode'},
{type: 'Uint8', name: 'placedOrientation'},
{type: 'Uint8', name: 'hidden'},
- {type: 'Uint32', name: 'crossTileID'}
+ {type: 'Uint32', name: 'crossTileID'},
+ {type: 'Int16', name: 'associatedIconIndex'}
]);
export const symbolInstance = createLayout([
@@ -83,6 +84,7 @@ export const symbolInstance = createLayout([
{type: 'Int16', name: 'centerJustifiedTextSymbolIndex'},
{type: 'Int16', name: 'leftJustifiedTextSymbolIndex'},
{type: 'Int16', name: 'verticalPlacedTextSymbolIndex'},
+ {type: 'Int16', name: 'placedIconSymbolIndex'},
{type: 'Uint16', name: 'key'},
{type: 'Uint16', name: 'textBoxStartIndex'},
{type: 'Uint16', name: 'textBoxEndIndex'},
diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js
index 91a7b0aff06..3c5655fb3f9 100644
--- a/src/data/bucket/symbol_bucket.js
+++ b/src/data/bucket/symbol_bucket.js
@@ -539,7 +539,8 @@ class SymbolBucket implements Bucket {
writingMode: any,
labelAnchor: Anchor,
lineStartIndex: number,
- lineLength: number) {
+ lineLength: number,
+ associatedIconIndex: number) {
const indexArray = arrays.indexArray;
const layoutVertexArray = arrays.layoutVertexArray;
const dynamicLayoutVertexArray = arrays.dynamicLayoutVertexArray;
@@ -619,7 +620,9 @@ class SymbolBucket implements Bucket {
0,
(false: any),
// The crossTileID is only filled/used on the foreground for dynamic text anchors
- 0);
+ 0,
+ associatedIconIndex
+ );
}
_addCollisionDebugVertex(layoutVertexArray: StructArray, collisionVertexArray: StructArray, point: Point, anchorX: number, anchorY: number, extrude: Point) {
diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js
index e27937449a7..e7279f92633 100644
--- a/src/render/draw_symbol.js
+++ b/src/render/draw_symbol.js
@@ -55,6 +55,17 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt
// Disable the stencil test so that labels aren't clipped to tile boundaries.
const stencilMode = StencilMode.disabled;
const colorMode = painter.colorModeForRenderPass();
+ const variablePlacement = layer.layout.get('text-variable-anchor');
+
+ //Compute variable-offsets before painting since icons and text data positioning
+ //depend on each other in this case.
+ if (variablePlacement) {
+ updateVariableAnchors(coords, painter, layer, sourceCache,
+ layer.layout.get('text-rotation-alignment'),
+ layer.layout.get('text-pitch-alignment'),
+ variableOffsets
+ );
+ }
if (layer.paint.get('icon-opacity').constantOr(1) !== 0) {
drawLayerSymbols(painter, sourceCache, layer, coords, false,
@@ -63,7 +74,7 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt
layer.layout.get('icon-rotation-alignment'),
layer.layout.get('icon-pitch-alignment'),
layer.layout.get('icon-keep-upright'),
- stencilMode, colorMode, variableOffsets
+ stencilMode, colorMode
);
}
@@ -74,7 +85,7 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt
layer.layout.get('text-rotation-alignment'),
layer.layout.get('text-pitch-alignment'),
layer.layout.get('text-keep-upright'),
- stencilMode, colorMode, variableOffsets
+ stencilMode, colorMode
);
}
@@ -97,19 +108,48 @@ function calculateVariableRenderShift(anchor, width, height, textOffset, textBox
);
}
-function updateVariableAnchors(bucket, rotateWithMap, pitchWithMap, variableOffsets, symbolSize,
- transform, labelPlaneMatrix, posMatrix, tileScale, size) {
+function updateVariableAnchors(coords, painter, layer, sourceCache, rotationAlignment, pitchAlignment, variableOffsets) {
+ const tr = painter.transform;
+ const rotateWithMap = rotationAlignment === 'map';
+ const pitchWithMap = pitchAlignment === 'map';
+
+ for (const coord of coords) {
+ const tile = sourceCache.getTile(coord);
+ const bucket: SymbolBucket = (tile.getBucket(layer): any);
+ if (!bucket || !bucket.text || !bucket.text.segments.get().length) continue;
+
+ const sizeData = bucket.textSizeData;
+ const size = symbolSize.evaluateSizeForZoom(sizeData, tr.zoom);
+
+ const pixelToTileScale = pixelsToTileUnits(tile, 1, painter.transform.zoom);
+ const labelPlaneMatrix = symbolProjection.getLabelPlaneMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, pixelToTileScale);
+ const updateTextFitIcon = layer.layout.get('icon-text-fit') !== 'none' && bucket.hasIconData();
+
+ if (size) {
+ const tileScale = Math.pow(2, tr.zoom - tile.tileID.overscaledZ);
+ updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, symbolSize,
+ tr, labelPlaneMatrix, coord.posMatrix, tileScale, size, updateTextFitIcon);
+ }
+ }
+}
+
+function updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, symbolSize,
+ transform, labelPlaneMatrix, posMatrix, tileScale, size, updateTextFitIcon) {
const placedSymbols = bucket.text.placedSymbolArray;
- const dynamicLayoutVertexArray = bucket.text.dynamicLayoutVertexArray;
- dynamicLayoutVertexArray.clear();
+ const dynamicTextLayoutVertexArray = bucket.text.dynamicLayoutVertexArray;
+ const dynamicIconLayoutVertexArray = bucket.icon.dynamicLayoutVertexArray;
+ const placedTextShifts = {};
+
+ dynamicTextLayoutVertexArray.clear();
for (let s = 0; s < placedSymbols.length; s++) {
const symbol: any = placedSymbols.get(s);
const skipOrientation = bucket.allowVerticalPlacement && !symbol.placedOrientation;
const variableOffset = (!symbol.hidden && symbol.crossTileID && !skipOrientation) ? variableOffsets[symbol.crossTileID] : null;
+
if (!variableOffset) {
// These symbols are from a justification that is not being used, or a label that wasn't placed
// so we don't need to do the extra math to figure out what incremental shift to apply.
- symbolProjection.hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray);
+ symbolProjection.hideGlyphs(symbol.numGlyphs, dynamicTextLayoutVertexArray);
} else {
const tileAnchor = new Point(symbol.anchorX, symbol.anchorY);
const projectedAnchor = symbolProjection.project(tileAnchor, pitchWithMap ? posMatrix : labelPlaneMatrix);
@@ -136,11 +176,36 @@ function updateVariableAnchors(bucket, rotateWithMap, pitchWithMap, variableOffs
const angle = (bucket.allowVerticalPlacement && symbol.placedOrientation === WritingMode.vertical) ? Math.PI / 2 : 0;
for (let g = 0; g < symbol.numGlyphs; g++) {
- addDynamicAttributes(dynamicLayoutVertexArray, shiftedAnchor, angle);
+ addDynamicAttributes(dynamicTextLayoutVertexArray, shiftedAnchor, angle);
+ }
+ //Only offset horizontal text icons
+ if (updateTextFitIcon && symbol.associatedIconIndex >= 0) {
+ placedTextShifts[symbol.associatedIconIndex] = {shiftedAnchor, angle};
}
}
}
- bucket.text.dynamicLayoutVertexBuffer.updateData(dynamicLayoutVertexArray);
+
+ if (updateTextFitIcon) {
+ dynamicIconLayoutVertexArray.clear();
+ const placedIcons = bucket.icon.placedSymbolArray;
+ for (let i = 0; i < placedIcons.length; i++) {
+ const placedIcon = placedIcons.get(i);
+ if (placedIcon.hidden) {
+ symbolProjection.hideGlyphs(placedIcon.numGlyphs, dynamicIconLayoutVertexArray);
+ } else {
+ const shift = placedTextShifts[i];
+ if (!shift) {
+ symbolProjection.hideGlyphs(placedIcon.numGlyphs, dynamicIconLayoutVertexArray);
+ } else {
+ for (let g = 0; g < placedIcon.numGlyphs; g++) {
+ addDynamicAttributes(dynamicIconLayoutVertexArray, shift.shiftedAnchor, shift.angle);
+ }
+ }
+ }
+ }
+ bucket.icon.dynamicLayoutVertexBuffer.updateData(dynamicIconLayoutVertexArray);
+ }
+ bucket.text.dynamicLayoutVertexBuffer.updateData(dynamicTextLayoutVertexArray);
}
function updateVerticalLabels(bucket) {
@@ -167,7 +232,7 @@ function updateVerticalLabels(bucket) {
}
function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate, translateAnchor,
- rotationAlignment, pitchAlignment, keepUpright, stencilMode, colorMode, variableOffsets) {
+ rotationAlignment, pitchAlignment, keepUpright, stencilMode, colorMode) {
const context = painter.context;
const gl = context.gl;
@@ -233,18 +298,19 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
const labelPlaneMatrix = symbolProjection.getLabelPlaneMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s);
const glCoordMatrix = symbolProjection.getGlCoordMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s);
+ const hasVariableAnchors = variablePlacement && bucket.hasTextData();
+ const updateTextFitIcon = layer.layout.get('icon-text-fit') !== 'none' &&
+ hasVariableAnchors &&
+ bucket.hasIconData();
+
if (alongLine) {
symbolProjection.updateLineLabels(bucket, coord.posMatrix, painter, isText, labelPlaneMatrix, glCoordMatrix, pitchWithMap, keepUpright);
- } else if (isText && size && variablePlacement) {
- const tileScale = Math.pow(2, tr.zoom - tile.tileID.overscaledZ);
- updateVariableAnchors(bucket, rotateWithMap, pitchWithMap, variableOffsets, symbolSize,
- tr, labelPlaneMatrix, coord.posMatrix, tileScale, size);
- } else if (isText && size && bucket.allowVerticalPlacement) {
+ } else if (isText && size && bucket.allowVerticalPlacement && !variablePlacement) {
updateVerticalLabels(bucket);
}
const matrix = painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor),
- uLabelPlaneMatrix = (alongLine || (isText && variablePlacement)) ? identityMat4 : labelPlaneMatrix,
+ uLabelPlaneMatrix = (alongLine || (isText && variablePlacement) || updateTextFitIcon) ? identityMat4 : labelPlaneMatrix,
uglCoordMatrix = painter.translatePosMatrix(glCoordMatrix, tile, translate, translateAnchor, true);
const hasHalo = isSDF && layer.paint.get(isText ? 'text-halo-width' : 'icon-halo-width').constantOr(1) !== 0;
diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js
index cc1b44bdd99..982d517b384 100644
--- a/src/symbol/symbol_layout.js
+++ b/src/symbol/symbol_layout.js
@@ -462,6 +462,7 @@ function addTextVertices(bucket: SymbolBucket,
placementTypes: Array<'vertical' | 'center' | 'left' | 'right'>,
placedTextSymbolIndices: {[string]: number},
glyphPositionMap: {[string]: {[number]: GlyphPosition}},
+ placedIconIndex: number,
sizes: Sizes) {
const glyphQuads = getGlyphQuads(anchor, shapedText, textOffset,
layer, textAlongLine, feature, glyphPositionMap, bucket.allowVerticalPlacement);
@@ -496,7 +497,8 @@ function addTextVertices(bucket: SymbolBucket,
writingMode,
anchor,
lineArray.lineStartIndex,
- lineArray.lineLength);
+ lineArray.lineLength,
+ placedIconIndex);
// The placedSymbolArray is used at render time in drawTileSymbols
// These indices allow access to the array at collision detection time
@@ -549,6 +551,7 @@ function addSymbol(bucket: SymbolBucket,
let numIconVertices = 0;
let numHorizontalGlyphVertices = 0;
let numVerticalGlyphVertices = 0;
+ let placedIconSymbolIndex = -1;
const placedTextSymbolIndices = {};
let key = murmur3('');
@@ -568,41 +571,10 @@ function addSymbol(bucket: SymbolBucket,
verticalTextCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, verticalShaping, textBoxScale, textPadding, textAlongLine, bucket.overscaling, verticalTextRotation);
}
- for (const justification: any in shapedTextOrientations.horizontal) {
- const shaping = shapedTextOrientations.horizontal[justification];
-
- if (!textCollisionFeature) {
- key = murmur3(shaping.text);
- const textRotate = layer.layout.get('text-rotate').evaluate(feature, {});
- // As a collision approximation, we can use either the vertical or any of the horizontal versions of the feature
- // We're counting on all versions having similar dimensions
- textCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shaping, textBoxScale, textPadding, textAlongLine, bucket.overscaling, textRotate);
- }
-
- const singleLine = shaping.lineCount === 1;
- numHorizontalGlyphVertices += addTextVertices(
- bucket, anchor, shaping, layer, textAlongLine, feature, textOffset, lineArray,
- shapedTextOrientations.vertical ? WritingMode.horizontal : WritingMode.horizontalOnly,
- singleLine ? (Object.keys(shapedTextOrientations.horizontal): any) : [justification],
- placedTextSymbolIndices, glyphPositionMap, sizes);
-
- if (singleLine) {
- break;
- }
- }
-
- if (shapedTextOrientations.vertical) {
- numVerticalGlyphVertices += addTextVertices(
- bucket, anchor, shapedTextOrientations.vertical, layer, textAlongLine, feature,
- textOffset, lineArray, WritingMode.vertical, ['vertical'], placedTextSymbolIndices, glyphPositionMap, sizes);
- }
-
- const textBoxStartIndex = textCollisionFeature ? textCollisionFeature.boxStartIndex : bucket.collisionBoxArray.length;
- const textBoxEndIndex = textCollisionFeature ? textCollisionFeature.boxEndIndex : bucket.collisionBoxArray.length;
-
- const verticalTextBoxStartIndex = verticalTextCollisionFeature ? verticalTextCollisionFeature.boxStartIndex : bucket.collisionBoxArray.length;
- const verticalTextBoxEndIndex = verticalTextCollisionFeature ? verticalTextCollisionFeature.boxEndIndex : bucket.collisionBoxArray.length;
-
+ //Place icon first, so text can have a reference to its index in the placed symbol array.
+ //Text symbols can lazily shift at render-time because of variable anchor placement.
+ //If the style specifies an `icon-text-fit` then the icon would have to shift along with it.
+ // For more info check `updateVariableAnchors` in `draw_symbol.js` .
if (shapedIcon) {
const iconQuads = getIconQuads(anchor, shapedIcon, layer,
iconAlongLine, getDefaultHorizontalShaping(shapedTextOrientations.horizontal),
@@ -642,9 +614,48 @@ function addSymbol(bucket: SymbolBucket,
false,
anchor,
lineArray.lineStartIndex,
- lineArray.lineLength);
+ lineArray.lineLength,
+ // The icon itself does not have an associated symbol since the text isnt placed yet
+ -1);
+
+ placedIconSymbolIndex = bucket.icon.placedSymbolArray.length - 1;
}
+ for (const justification: any in shapedTextOrientations.horizontal) {
+ const shaping = shapedTextOrientations.horizontal[justification];
+
+ if (!textCollisionFeature) {
+ key = murmur3(shaping.text);
+ const textRotate = layer.layout.get('text-rotate').evaluate(feature, {});
+ // As a collision approximation, we can use either the vertical or any of the horizontal versions of the feature
+ // We're counting on all versions having similar dimensions
+ textCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shaping, textBoxScale, textPadding, textAlongLine, bucket.overscaling, textRotate);
+ }
+
+ const singleLine = shaping.lineCount === 1;
+ numHorizontalGlyphVertices += addTextVertices(
+ bucket, anchor, shaping, layer, textAlongLine, feature, textOffset, lineArray,
+ shapedTextOrientations.vertical ? WritingMode.horizontal : WritingMode.horizontalOnly,
+ singleLine ? (Object.keys(shapedTextOrientations.horizontal): any) : [justification],
+ placedTextSymbolIndices, glyphPositionMap, placedIconSymbolIndex, sizes);
+
+ if (singleLine) {
+ break;
+ }
+ }
+
+ if (shapedTextOrientations.vertical) {
+ numVerticalGlyphVertices += addTextVertices(
+ bucket, anchor, shapedTextOrientations.vertical, layer, textAlongLine, feature,
+ textOffset, lineArray, WritingMode.vertical, ['vertical'], placedTextSymbolIndices, glyphPositionMap, placedIconSymbolIndex, sizes);
+ }
+
+ const textBoxStartIndex = textCollisionFeature ? textCollisionFeature.boxStartIndex : bucket.collisionBoxArray.length;
+ const textBoxEndIndex = textCollisionFeature ? textCollisionFeature.boxEndIndex : bucket.collisionBoxArray.length;
+
+ const verticalTextBoxStartIndex = verticalTextCollisionFeature ? verticalTextCollisionFeature.boxStartIndex : bucket.collisionBoxArray.length;
+ const verticalTextBoxEndIndex = verticalTextCollisionFeature ? verticalTextCollisionFeature.boxEndIndex : bucket.collisionBoxArray.length;
+
const iconBoxStartIndex = iconCollisionFeature ? iconCollisionFeature.boxStartIndex : bucket.collisionBoxArray.length;
const iconBoxEndIndex = iconCollisionFeature ? iconCollisionFeature.boxEndIndex : bucket.collisionBoxArray.length;
@@ -659,6 +670,7 @@ function addSymbol(bucket: SymbolBucket,
placedTextSymbolIndices.center >= 0 ? placedTextSymbolIndices.center : -1,
placedTextSymbolIndices.left >= 0 ? placedTextSymbolIndices.left : -1,
placedTextSymbolIndices.vertical || -1,
+ placedIconSymbolIndex,
key,
textBoxStartIndex,
textBoxEndIndex,
diff --git a/test/integration/render-tests/icon-text-fit/text-variable-anchor-overlap/expected.png b/test/integration/render-tests/icon-text-fit/text-variable-anchor-overlap/expected.png
new file mode 100644
index 00000000000..f161f2259eb
Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/text-variable-anchor-overlap/expected.png differ
diff --git a/test/integration/render-tests/icon-text-fit/text-variable-anchor-overlap/style.json b/test/integration/render-tests/icon-text-fit/text-variable-anchor-overlap/style.json
new file mode 100644
index 00000000000..73c869feb54
--- /dev/null
+++ b/test/integration/render-tests/icon-text-fit/text-variable-anchor-overlap/style.json
@@ -0,0 +1,61 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "height": 256,
+ "allowed": 0.0005
+ }
+ },
+ "center": [
+ 13.417,
+ 52.502
+ ],
+ "zoom": 16,
+ "sources": {
+ "mapbox": {
+ "type": "vector",
+ "maxzoom": 14,
+ "tiles": [
+ "local://tiles/{z}-{x}-{y}.mvt"
+ ]
+ }
+ },
+ "sprite": "local://sprites/icon-text-fit",
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "white"
+ }
+ },
+ {
+ "id": "road",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "road_label",
+ "layout": {
+ "text-variable-anchor": ["left", "right", "bottom", "top"],
+ "text-field": "{name}",
+ "text-allow-overlap": true,
+ "icon-allow-overlap": true,
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ],
+ "icon-image": "label",
+ "icon-text-fit": "both",
+ "icon-text-fit-padding": [
+ 5,
+ 10,
+ 5,
+ 10
+ ]
+ },
+ "paint": {
+ "icon-opacity": 1
+ }
+ }
+ ]
+}
diff --git a/test/integration/render-tests/icon-text-fit/text-variable-anchor/expected.png b/test/integration/render-tests/icon-text-fit/text-variable-anchor/expected.png
new file mode 100644
index 00000000000..93b68ba6490
Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/text-variable-anchor/expected.png differ
diff --git a/test/integration/render-tests/icon-text-fit/text-variable-anchor/style.json b/test/integration/render-tests/icon-text-fit/text-variable-anchor/style.json
new file mode 100644
index 00000000000..e27f39374e8
--- /dev/null
+++ b/test/integration/render-tests/icon-text-fit/text-variable-anchor/style.json
@@ -0,0 +1,59 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "height": 256,
+ "allowed": 0.0005
+ }
+ },
+ "center": [
+ 13.417,
+ 52.502
+ ],
+ "zoom": 14,
+ "sources": {
+ "mapbox": {
+ "type": "vector",
+ "maxzoom": 14,
+ "tiles": [
+ "local://tiles/{z}-{x}-{y}.mvt"
+ ]
+ }
+ },
+ "sprite": "local://sprites/icon-text-fit",
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "white"
+ }
+ },
+ {
+ "id": "road",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "road_label",
+ "layout": {
+ "text-variable-anchor": ["left", "right", "bottom", "top"],
+ "text-field": "{name}",
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ],
+ "icon-image": "label",
+ "icon-text-fit": "both",
+ "icon-text-fit-padding": [
+ 5,
+ 10,
+ 5,
+ 10
+ ]
+ },
+ "paint": {
+ "icon-opacity": 1
+ }
+ }
+ ]
+}