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 + } + } + ] +}