Skip to content

Commit

Permalink
Enable text-offset with variable label placement
Browse files Browse the repository at this point in the history
This PR allows using of `text-offset` together with `text-variable-anchor`, if `text-radial-offset` is not provided.
Assuming we got `text-offset` [x, y] the offset is calculated based on the `text-variable-anchor` values as following:

"left": [-x, 0]
"right": [x, 0]
"top": [0, -y]
"bottom": [0, y]
"top-left": [-x, -y]
"top-right": [x, -y]
"bottom-left": [-x, y]
"bottom-right": [x, y]

We allow only positive `text-offset` values for simplicity.
  • Loading branch information
pozdnyakov committed Aug 19, 2019
1 parent 9a3934c commit cfc3284
Show file tree
Hide file tree
Showing 10 changed files with 474 additions and 61 deletions.
34 changes: 19 additions & 15 deletions src/data/array_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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:
Expand Down Expand Up @@ -948,7 +949,8 @@ class SymbolInstanceStruct extends Struct {
numIconVertices: number;
crossTileID: number;
textBoxScale: number;
radialTextOffset: number;
radialTextOffset0: number;
radialTextOffset1: 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]; }
Expand Down Expand Up @@ -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 radialTextOffset0() { return this._structArray.float32[this._pos4 + 11]; }
set radialTextOffset0(x: number) { this._structArray.float32[this._pos4 + 11] = x; }
get radialTextOffset1() { return this._structArray.float32[this._pos4 + 12]; }
set radialTextOffset1(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.
Expand Down Expand Up @@ -1121,7 +1125,7 @@ export {
StructArrayLayout2i2i2i12,
StructArrayLayout2ub2f12,
StructArrayLayout2i2ui3ul3ui2f3ub1ul44,
StructArrayLayout6i11ui1ul2f48,
StructArrayLayout6i11ui1ul3f52,
StructArrayLayout1f4,
StructArrayLayout3i6,
StructArrayLayout1ul2ui8,
Expand Down
2 changes: 1 addition & 1 deletion src/data/bucket/symbol_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: 'radialTextOffset'},
]);

export const glyphOffset = createLayout([
Expand Down
13 changes: 7 additions & 6 deletions src/symbol/placement.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class CollisionGroups {
}
}

function calculateVariableLayoutOffset(anchor: TextAnchor, width: number, height: number, radialOffset: number, textBoxScale: number): Point {
function calculateVariableLayoutOffset(anchor: TextAnchor, width: number, height: number, radialOffset: [number, number], textBoxScale: number): Point {
const {horizontalAlign, verticalAlign} = getAnchorAlignment(anchor);
const shiftX = -(horizontalAlign - 0.5) * width;
const shiftY = -(verticalAlign - 0.5) * height;
Expand Down Expand Up @@ -149,7 +149,7 @@ function shiftVariableCollisionBox(collisionBox: SingleCollisionBox,
}

export type VariableOffset = {
radialOffset: number,
radialOffset: [number, number],
width: number,
height: number,
anchor: TextAnchor,
Expand Down Expand Up @@ -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<number>, offscreen: boolean } {

const shift = calculateVariableLayoutOffset(anchor, width, height, radialTextOffset, textBoxScale);
const radialOffset = [symbolInstance.radialTextOffset0, symbolInstance.radialTextOffset1];
const shift = calculateVariableLayoutOffset(anchor, width, height, radialOffset, textBoxScale);

const placedGlyphBoxes = this.collisionIndex.placeCollisionBox(
shiftVariableCollisionBox(
Expand All @@ -259,7 +260,7 @@ export class Placement {
}
assert(symbolInstance.crossTileID !== 0);
this.variableOffsets[symbolInstance.crossTileID] = {
radialOffset: radialTextOffset,
radialOffset,
width,
height,
anchor,
Expand Down Expand Up @@ -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);

Expand Down
123 changes: 84 additions & 39 deletions src/symbol/symbol_layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,46 +64,86 @@ export type TextAnchor = 'center' | 'left' | 'right' | 'top' | 'bottom' | 'top-l
// (see "yOffset" in shaping.js)
const baselineOffset = 7;

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);
export function evaluateRadialOffset(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;
}

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;
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;
}

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;
if (offsetX < 0 || offsetY < 0) { // Ignore negative offset.
offsetX = offsetY = 0;
}

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] !== Number.POSITIVE_INFINITY) ? fromTextOffset(anchor, offset[0], offset[1]) : fromRadialOffset(anchor, offset[0]);
}

export function performSymbolLayout(bucket: SymbolBucket,
Expand Down Expand Up @@ -165,15 +205,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 = evaluateRadialOffset(textAnchor, [radialOffset * ONE_EM, Number.POSITIVE_INFINITY]);
} else {
textOffset = (layout.get('text-offset').evaluate(feature, {}).map(t => t * ONE_EM): any);
}
Expand Down Expand Up @@ -510,7 +550,11 @@ 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 radialTextOffset0 = (layer.layout.get('text-radial-offset').evaluate(feature, {}) || 0) * ONE_EM;
let radialTextOffset1 = Number.POSITIVE_INFINITY;
if (!radialTextOffset0) {
[radialTextOffset0, radialTextOffset1] = (layer.layout.get('text-offset').evaluate(feature, {}).map(t => t * ONE_EM): any);
}

if (bucket.allowVerticalPlacement && shapedTextOrientations.vertical) {
const textRotation = layer.layout.get('text-rotate').evaluate(feature, {});
Expand Down Expand Up @@ -623,7 +667,8 @@ function addSymbol(bucket: SymbolBucket,
numIconVertices,
0,
textBoxScale,
radialTextOffset);
radialTextOffset0,
radialTextOffset1);
}

function anchorIsTooClose(bucket: any, text: string, repeatDistance: number, anchor: Point) {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit cfc3284

Please sign in to comment.