diff --git a/src/data/array_types.js b/src/data/array_types.js index a63a0bd5575..31138acba54 100644 --- a/src/data/array_types.js +++ b/src/data/array_types.js @@ -478,11 +478,11 @@ register('StructArrayLayout2i2ui3ul3ui2f3ub1ul44', StructArrayLayout2i2ui3ul3ui2 * [0]: Int16[6] * [12]: Uint16[11] * [36]: Uint32[1] - * [40]: Float32[2] + * [40]: Float32[3] * * @private */ -class StructArrayLayout6i11ui1ul2f48 extends StructArray { +class StructArrayLayout6i11ui1ul3f52 extends StructArray { uint8: Uint8Array; int16: Int16Array; uint16: Uint16Array; @@ -497,15 +497,15 @@ class StructArrayLayout6i11ui1ul2f48 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) { + 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) { 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); + 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); } - 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) { - const o2 = i * 24; - const o4 = i * 12; + 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) { + const o2 = i * 26; + const o4 = i * 13; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.int16[o2 + 2] = v2; @@ -526,12 +526,13 @@ class StructArrayLayout6i11ui1ul2f48 extends StructArray { this.uint32[o4 + 9] = v17; this.float32[o4 + 10] = v18; this.float32[o4 + 11] = v19; + this.float32[o4 + 12] = v20; return i; } } -StructArrayLayout6i11ui1ul2f48.prototype.bytesPerElement = 48; -register('StructArrayLayout6i11ui1ul2f48', StructArrayLayout6i11ui1ul2f48); +StructArrayLayout6i11ui1ul3f52.prototype.bytesPerElement = 52; +register('StructArrayLayout6i11ui1ul3f52', StructArrayLayout6i11ui1ul3f52); /** * Implementation of the StructArray layout: @@ -948,7 +949,8 @@ class SymbolInstanceStruct extends Struct { numIconVertices: number; crossTileID: number; textBoxScale: number; - radialTextOffset: number; + textOffset0: number; + textOffset1: 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]; } @@ -987,18 +989,20 @@ class SymbolInstanceStruct extends Struct { set crossTileID(x: number) { this._structArray.uint32[this._pos4 + 9] = x; } get textBoxScale() { return this._structArray.float32[this._pos4 + 10]; } set textBoxScale(x: number) { this._structArray.float32[this._pos4 + 10] = x; } - get radialTextOffset() { return this._structArray.float32[this._pos4 + 11]; } - set radialTextOffset(x: number) { this._structArray.float32[this._pos4 + 11] = x; } + get textOffset0() { return this._structArray.float32[this._pos4 + 11]; } + set textOffset0(x: number) { this._structArray.float32[this._pos4 + 11] = x; } + get textOffset1() { return this._structArray.float32[this._pos4 + 12]; } + set textOffset1(x: number) { this._structArray.float32[this._pos4 + 12] = x; } } -SymbolInstanceStruct.prototype.size = 48; +SymbolInstanceStruct.prototype.size = 52; export type SymbolInstance = SymbolInstanceStruct; /** * @private */ -export class SymbolInstanceArray extends StructArrayLayout6i11ui1ul2f48 { +export class SymbolInstanceArray extends StructArrayLayout6i11ui1ul3f52 { /** * Return the SymbolInstanceStruct at the given location in the array. * @param {number} index The index of the element. @@ -1121,7 +1125,7 @@ export { StructArrayLayout2i2i2i12, StructArrayLayout2ub2f12, StructArrayLayout2i2ui3ul3ui2f3ub1ul44, - StructArrayLayout6i11ui1ul2f48, + StructArrayLayout6i11ui1ul3f52, StructArrayLayout1f4, StructArrayLayout3i6, StructArrayLayout1ul2ui8, diff --git a/src/data/bucket/symbol_attributes.js b/src/data/bucket/symbol_attributes.js index d93a011e0a0..bf78d74a098 100644 --- a/src/data/bucket/symbol_attributes.js +++ b/src/data/bucket/symbol_attributes.js @@ -96,7 +96,7 @@ export const symbolInstance = createLayout([ {type: 'Uint16', name: 'numIconVertices'}, {type: 'Uint32', name: 'crossTileID'}, {type: 'Float32', name: 'textBoxScale'}, - {type: 'Float32', name: 'radialTextOffset'} + {type: 'Float32', components: 2, name: 'textOffset'} ]); export const glyphOffset = createLayout([ diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index 96618fd5863..e27937449a7 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -16,7 +16,7 @@ import {addDynamicAttributes} from '../data/bucket/symbol_bucket'; import {getAnchorAlignment, WritingMode} from '../symbol/shaping'; import ONE_EM from '../symbol/one_em'; -import {evaluateRadialOffset} from '../symbol/symbol_layout'; +import {evaluateVariableOffset} from '../symbol/symbol_layout'; import { symbolIconUniformValues, @@ -86,14 +86,14 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt } } -function calculateVariableRenderShift(anchor, width, height, radialOffset, textBoxScale, renderTextSize): Point { +function calculateVariableRenderShift(anchor, width, height, textOffset, textBoxScale, renderTextSize): Point { const {horizontalAlign, verticalAlign} = getAnchorAlignment(anchor); const shiftX = -(horizontalAlign - 0.5) * width; const shiftY = -(verticalAlign - 0.5) * height; - const offset = evaluateRadialOffset(anchor, radialOffset); + const variableOffset = evaluateVariableOffset(anchor, textOffset); return new Point( - (shiftX / textBoxScale + offset[0]) * renderTextSize, - (shiftY / textBoxScale + offset[1]) * renderTextSize + (shiftX / textBoxScale + variableOffset[0]) * renderTextSize, + (shiftY / textBoxScale + variableOffset[1]) * renderTextSize ); } @@ -120,10 +120,10 @@ function updateVariableAnchors(bucket, rotateWithMap, pitchWithMap, variableOffs renderTextSize *= bucket.tilePixelRatio / tileScale; } - const {width, height, radialOffset, textBoxScale} = variableOffset; + const {width, height, anchor, textOffset, textBoxScale} = variableOffset; const shift = calculateVariableRenderShift( - variableOffset.anchor, width, height, radialOffset, textBoxScale, renderTextSize); + anchor, width, height, textOffset, textBoxScale, renderTextSize); // Usual case is that we take the projected anchor and add the pixel-based shift // calculated above. In the (somewhat weird) case of pitch-aligned text, we add an equivalent diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index bce16fc9948..6e998fd095a 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -1855,7 +1855,7 @@ "type": "number", "units": "ems", "default": 0, - "doc": "Radial offset of text, in the direction of the symbol's anchor. Useful in combination with `text-variable-anchor`, which doesn't support the two-dimensional `text-offset`.", + "doc": "Radial offset of text, in the direction of the symbol's anchor. Useful in combination with `text-variable-anchor`, which defaults to using the two-dimensional `text-offset` if present.", "sdk-support": { "basic functionality": { "js": "0.54.0", @@ -1922,7 +1922,7 @@ ] } ], - "doc": "To increase the chance of placing high-priority labels on the map, you can provide an array of `text-anchor` locations: the render will attempt to place the label at each location, in order, before moving onto the next label. Use `text-justify: auto` to choose justification based on anchor position. To apply an offset, use the `text-radial-offset` instead of the two-dimensional `text-offset`.", + "doc": "To increase the chance of placing high-priority labels on the map, you can provide an array of `text-anchor` locations: the render will attempt to place the label at each location, in order, before moving onto the next label. Use `text-justify: auto` to choose justification based on anchor position. To apply an offset, use the `text-radial-offset` or the two-dimensional `text-offset`.", "sdk-support": { "basic functionality": { "js": "0.54.0", @@ -2214,7 +2214,7 @@ }, "text-offset": { "type": "array", - "doc": "Offset distance of text from its anchor. Positive values indicate right and down, while negative values indicate left and up.", + "doc": "Offset distance of text from its anchor. Positive values indicate right and down, while negative values indicate left and up. If used with text-variable-anchor, input values will be taken as absolute values. Offsets along the x- and y-axis will be applied automatically based on the anchor position.", "value": "number", "units": "ems", "length": 2, @@ -2226,9 +2226,6 @@ "text-field", { "!": "text-radial-offset" - }, - { - "!": "text-variable-anchor" } ], "sdk-support": { diff --git a/src/symbol/placement.js b/src/symbol/placement.js index cf302e4eb94..8b9a65216a7 100644 --- a/src/symbol/placement.js +++ b/src/symbol/placement.js @@ -4,7 +4,7 @@ import CollisionIndex from './collision_index'; import EXTENT from '../data/extent'; import * as symbolSize from './symbol_size'; import * as projection from './projection'; -import {getAnchorJustification, evaluateRadialOffset} from './symbol_layout'; +import {getAnchorJustification, evaluateVariableOffset} from './symbol_layout'; import {getAnchorAlignment, WritingMode} from './shaping'; import assert from 'assert'; import pixelsToTileUnits from '../source/pixels_to_tile_units'; @@ -117,11 +117,11 @@ class CollisionGroups { } } -function calculateVariableLayoutOffset(anchor: TextAnchor, width: number, height: number, radialOffset: number, textBoxScale: number): Point { +function calculateVariableLayoutShift(anchor: TextAnchor, width: number, height: number, textOffset: [number, number], textBoxScale: number): Point { const {horizontalAlign, verticalAlign} = getAnchorAlignment(anchor); const shiftX = -(horizontalAlign - 0.5) * width; const shiftY = -(verticalAlign - 0.5) * height; - const offset = evaluateRadialOffset(anchor, radialOffset); + const offset = evaluateVariableOffset(anchor, textOffset); return new Point( shiftX + offset[0] * textBoxScale, shiftY + offset[1] * textBoxScale @@ -149,7 +149,7 @@ function shiftVariableCollisionBox(collisionBox: SingleCollisionBox, } export type VariableOffset = { - radialOffset: number, + textOffset: [number, number], width: number, height: number, anchor: TextAnchor, @@ -235,11 +235,12 @@ export class Placement { } attemptAnchorPlacement(anchor: TextAnchor, textBox: SingleCollisionBox, width: number, height: number, - radialTextOffset: number, textBoxScale: number, rotateWithMap: boolean, + textBoxScale: number, rotateWithMap: boolean, pitchWithMap: boolean, textPixelRatio: number, posMatrix: mat4, collisionGroup: CollisionGroup, textAllowOverlap: boolean, symbolInstance: SymbolInstance, bucket: SymbolBucket, orientation: number): ?{ box: Array, offscreen: boolean } { - const shift = calculateVariableLayoutOffset(anchor, width, height, radialTextOffset, textBoxScale); + const textOffset = [symbolInstance.textOffset0, symbolInstance.textOffset1]; + const shift = calculateVariableLayoutShift(anchor, width, height, textOffset, textBoxScale); const placedGlyphBoxes = this.collisionIndex.placeCollisionBox( shiftVariableCollisionBox( @@ -259,7 +260,7 @@ export class Placement { } assert(symbolInstance.crossTileID !== 0); this.variableOffsets[symbolInstance.crossTileID] = { - radialOffset: radialTextOffset, + textOffset, width, height, anchor, @@ -426,7 +427,7 @@ export class Placement { const anchor = anchors[i % anchors.length]; const allowOverlap = (i >= anchors.length); placedBox = this.attemptAnchorPlacement( - anchor, collisionTextBox, width, height, symbolInstance.radialTextOffset, + anchor, collisionTextBox, width, height, textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, posMatrix, collisionGroup, allowOverlap, symbolInstance, bucket, orientation); @@ -811,10 +812,10 @@ export class Placement { // successfully placed position (so you can visualize what collision // just made the symbol disappear, and the most likely place for the // symbol to come back) - shift = calculateVariableLayoutOffset(variableOffset.anchor, + shift = calculateVariableLayoutShift(variableOffset.anchor, variableOffset.width, variableOffset.height, - variableOffset.radialOffset, + variableOffset.textOffset, variableOffset.textBoxScale); if (rotateWithMap) { shift._rotate(pitchWithMap ? this.transform.angle : -this.transform.angle); diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index 7be720bc932..cc1b44bdd99 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -63,47 +63,88 @@ export type TextAnchor = 'center' | 'left' | 'right' | 'top' | 'bottom' | 'top-l // We don't actually load baseline data, but we assume an offset of ONE_EM - 17 // (see "yOffset" in shaping.js) const baselineOffset = 7; +const INVALID_TEXT_OFFSET = Number.POSITIVE_INFINITY; + +export function evaluateVariableOffset(anchor: TextAnchor, offset: [number, number]) { + + function fromRadialOffset(anchor: TextAnchor, radialOffset: number) { + let x = 0, y = 0; + if (radialOffset < 0) radialOffset = 0; // Ignore negative offset. + // solve for r where r^2 + r^2 = radialOffset^2 + const hypotenuse = radialOffset / Math.sqrt(2); + switch (anchor) { + case 'top-right': + case 'top-left': + y = hypotenuse - baselineOffset; + break; + case 'bottom-right': + case 'bottom-left': + y = -hypotenuse + baselineOffset; + break; + case 'bottom': + y = -radialOffset + baselineOffset; + break; + case 'top': + y = radialOffset - baselineOffset; + break; + } -export function evaluateRadialOffset(anchor: TextAnchor, radialOffset: number) { - let x = 0, y = 0; - // solve for r where r^2 + r^2 = radialOffset^2 - const hypotenuse = radialOffset / Math.sqrt(2); + switch (anchor) { + case 'top-right': + case 'bottom-right': + x = -hypotenuse; + break; + case 'top-left': + case 'bottom-left': + x = hypotenuse; + break; + case 'left': + x = radialOffset; + break; + case 'right': + x = -radialOffset; + break; + } - switch (anchor) { - case 'top-right': - case 'top-left': - y = hypotenuse - baselineOffset; - break; - case 'bottom-right': - case 'bottom-left': - y = -hypotenuse + baselineOffset; - break; - case 'bottom': - y = -radialOffset + baselineOffset; - break; - case 'top': - y = radialOffset - baselineOffset; - break; + return [x, y]; } - switch (anchor) { - case 'top-right': - case 'bottom-right': - x = -hypotenuse; - break; - case 'top-left': - case 'bottom-left': - x = hypotenuse; - break; - case 'left': - x = radialOffset; - break; - case 'right': - x = -radialOffset; - break; + function fromTextOffset(anchor: TextAnchor, offsetX: number, offsetY: number) { + let x = 0, y = 0; + // Use absolute offset values. + offsetX = Math.abs(offsetX); + offsetY = Math.abs(offsetY); + + switch (anchor) { + case 'top-right': + case 'top-left': + case 'top': + y = offsetY - baselineOffset; + break; + case 'bottom-right': + case 'bottom-left': + case 'bottom': + y = -offsetY + baselineOffset; + break; + } + + switch (anchor) { + case 'top-right': + case 'bottom-right': + case 'right': + x = -offsetX; + break; + case 'top-left': + case 'bottom-left': + case 'left': + x = offsetX; + break; + } + + return [x, y]; } - return [x, y]; + return (offset[1] !== INVALID_TEXT_OFFSET) ? fromTextOffset(anchor, offset[0], offset[1]) : fromRadialOffset(anchor, offset[0]); } export function performSymbolLayout(bucket: SymbolBucket, @@ -165,15 +206,15 @@ export function performSymbolLayout(bucket: SymbolBucket, const textAnchor = layout.get('text-anchor').evaluate(feature, {}); const variableTextAnchor = layout.get('text-variable-anchor'); - const radialOffset = layout.get('text-radial-offset').evaluate(feature, {}); if (!variableTextAnchor) { + const radialOffset = layout.get('text-radial-offset').evaluate(feature, {}); // Layers with variable anchors use the `text-radial-offset` property and the [x, y] offset vector // is calculated at placement time instead of layout time if (radialOffset) { // The style spec says don't use `text-offset` and `text-radial-offset` together // but doesn't actually specify what happens if you use both. We go with the radial offset. - textOffset = evaluateRadialOffset(textAnchor, radialOffset * ONE_EM); + textOffset = evaluateVariableOffset(textAnchor, [radialOffset * ONE_EM, INVALID_TEXT_OFFSET]); } else { textOffset = (layout.get('text-offset').evaluate(feature, {}).map(t => t * ONE_EM): any); } @@ -510,7 +551,15 @@ function addSymbol(bucket: SymbolBucket, let numVerticalGlyphVertices = 0; const placedTextSymbolIndices = {}; let key = murmur3(''); - const radialTextOffset = (layer.layout.get('text-radial-offset').evaluate(feature, {}) || 0) * ONE_EM; + + let textOffset0 = 0; + let textOffset1 = 0; + if (layer._unevaluatedLayout.getValue('text-radial-offset') === undefined) { + [textOffset0, textOffset1] = (layer.layout.get('text-offset').evaluate(feature, {}).map(t => t * ONE_EM): any); + } else { + textOffset0 = layer.layout.get('text-radial-offset').evaluate(feature, {}) * ONE_EM; + textOffset1 = INVALID_TEXT_OFFSET; + } if (bucket.allowVerticalPlacement && shapedTextOrientations.vertical) { const textRotation = layer.layout.get('text-rotate').evaluate(feature, {}); @@ -623,7 +672,8 @@ function addSymbol(bucket: SymbolBucket, numIconVertices, 0, textBoxScale, - radialTextOffset); + textOffset0, + textOffset1); } function anchorIsTooClose(bucket: any, text: string, repeatDistance: number, anchor: Point) { diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-offset-zero/expected.png b/test/integration/render-tests/text-variable-anchor/all-anchors-offset-zero/expected.png new file mode 100644 index 00000000000..bed8c64a7aa Binary files /dev/null and b/test/integration/render-tests/text-variable-anchor/all-anchors-offset-zero/expected.png differ diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-offset-zero/style.json b/test/integration/render-tests/text-variable-anchor/all-anchors-offset-zero/style.json new file mode 100644 index 00000000000..73fd84ade25 --- /dev/null +++ b/test/integration/render-tests/text-variable-anchor/all-anchors-offset-zero/style.json @@ -0,0 +1,122 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "x", + "text-size": 20, + "text-justify": "auto", + "text-variable-anchor": [ + "center", + "top", + "bottom", + "left", + "right", + "top-left", + "top-right", + "bottom-left", + "bottom-right" + ], + "text-radial-offset": 0, + "text-offset": [2, 2], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + }, + { + "id": "anchor", + "type": "circle", + "source": "point", + "paint" :{ + "circle-radius": 2 + } + } + ] +} diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-offset/expected.png b/test/integration/render-tests/text-variable-anchor/all-anchors-offset/expected.png index a820ca32692..784d063b996 100644 Binary files a/test/integration/render-tests/text-variable-anchor/all-anchors-offset/expected.png and b/test/integration/render-tests/text-variable-anchor/all-anchors-offset/expected.png differ diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-offset/style.json b/test/integration/render-tests/text-variable-anchor/all-anchors-offset/style.json index 254adef41cf..95d193fe95b 100644 --- a/test/integration/render-tests/text-variable-anchor/all-anchors-offset/style.json +++ b/test/integration/render-tests/text-variable-anchor/all-anchors-offset/style.json @@ -2,8 +2,8 @@ "version": 8, "metadata": { "test": { - "height": 256, - "width": 256 + "height": 128, + "width": 128 } }, "center": [ 0, 0 ], diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-radial-offset-zero/expected.png b/test/integration/render-tests/text-variable-anchor/all-anchors-radial-offset-zero/expected.png new file mode 100644 index 00000000000..31a3a20875c Binary files /dev/null and b/test/integration/render-tests/text-variable-anchor/all-anchors-radial-offset-zero/expected.png differ diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-radial-offset-zero/style.json b/test/integration/render-tests/text-variable-anchor/all-anchors-radial-offset-zero/style.json new file mode 100644 index 00000000000..4817d45a2c4 --- /dev/null +++ b/test/integration/render-tests/text-variable-anchor/all-anchors-radial-offset-zero/style.json @@ -0,0 +1,121 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "x", + "text-size": 20, + "text-justify": "auto", + "text-variable-anchor": [ + "center", + "top", + "bottom", + "left", + "right", + "top-left", + "top-right", + "bottom-left", + "bottom-right" + ], + "text-offset": [2, 1.5], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + }, + { + "id": "anchor", + "type": "circle", + "source": "point", + "paint" :{ + "circle-radius": 2 + } + } + ] +} diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-negative/expected.png b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-negative/expected.png new file mode 100644 index 00000000000..8b9cb4b8894 Binary files /dev/null and b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-negative/expected.png differ diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-negative/style.json b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-negative/style.json new file mode 100644 index 00000000000..5d8de72a017 --- /dev/null +++ b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-negative/style.json @@ -0,0 +1,121 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "x", + "text-size": 20, + "text-justify": "auto", + "text-variable-anchor": [ + "center", + "top", + "bottom", + "left", + "right", + "top-left", + "top-right", + "bottom-left", + "bottom-right" + ], + "text-offset": [2, -1], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + }, + { + "id": "anchor", + "type": "circle", + "source": "point", + "paint" :{ + "circle-radius": 2 + } + } + ] +} diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-zero/expected.png b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-zero/expected.png new file mode 100644 index 00000000000..9e71ac16b38 Binary files /dev/null and b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-zero/expected.png differ diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-zero/style.json b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-zero/style.json new file mode 100644 index 00000000000..e9e2988e1e1 --- /dev/null +++ b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset-zero/style.json @@ -0,0 +1,121 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "x", + "text-size": 20, + "text-justify": "auto", + "text-variable-anchor": [ + "center", + "top", + "bottom", + "left", + "right", + "top-left", + "top-right", + "bottom-left", + "bottom-right" + ], + "text-offset": [2, 0], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + }, + { + "id": "anchor", + "type": "circle", + "source": "point", + "paint" :{ + "circle-radius": 2 + } + } + ] +} diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset/expected.png b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset/expected.png new file mode 100644 index 00000000000..31a3a20875c Binary files /dev/null and b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset/expected.png differ diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset/style.json b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset/style.json new file mode 100644 index 00000000000..4817d45a2c4 --- /dev/null +++ b/test/integration/render-tests/text-variable-anchor/all-anchors-two-dimentional-offset/style.json @@ -0,0 +1,121 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 0, 0 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "x", + "text-size": 20, + "text-justify": "auto", + "text-variable-anchor": [ + "center", + "top", + "bottom", + "left", + "right", + "top-left", + "top-right", + "bottom-left", + "bottom-right" + ], + "text-offset": [2, 1.5], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + }, + { + "id": "anchor", + "type": "circle", + "source": "point", + "paint" :{ + "circle-radius": 2 + } + } + ] +} diff --git a/test/integration/render-tests/text-variable-anchor/text-allow-overlap/expected.png b/test/integration/render-tests/text-variable-anchor/text-allow-overlap/expected.png index 2df9b51e027..418206d25ac 100644 Binary files a/test/integration/render-tests/text-variable-anchor/text-allow-overlap/expected.png and b/test/integration/render-tests/text-variable-anchor/text-allow-overlap/expected.png differ diff --git a/test/integration/render-tests/text-variable-anchor/text-allow-overlap/style.json b/test/integration/render-tests/text-variable-anchor/text-allow-overlap/style.json index 3fe9e73524a..ac5402a1d56 100644 --- a/test/integration/render-tests/text-variable-anchor/text-allow-overlap/style.json +++ b/test/integration/render-tests/text-variable-anchor/text-allow-overlap/style.json @@ -2,8 +2,8 @@ "version": 8, "metadata": { "test": { - "height": 256, - "width": 256 + "height": 128, + "width": 128 } }, "center": [ 0, 0 ],