Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[core] Enable text-offset with variable label placement
Browse files Browse the repository at this point in the history
  • Loading branch information
pozdnyakov committed Sep 3, 2019
1 parent 0fa5944 commit acfbdda
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 56 deletions.
2 changes: 1 addition & 1 deletion mapbox-gl-js
Submodule mapbox-gl-js updated 356 files
Original file line number Diff line number Diff line change
Expand Up @@ -2296,7 +2296,7 @@ public static PropertyValue<Expression> textJustify(Expression value) {
}

/**
* Radial offset of text, in the direction of the symbol's anchor. Useful in combination with {@link PropertyFactory#textVariableAnchor}, which doesn't support the two-dimensional {@link PropertyFactory#textOffset}.
* Radial offset of text, in the direction of the symbol's anchor. Useful in combination with {@link PropertyFactory#textVariableAnchor}, which defaults to using the two-dimensional {@link PropertyFactory#textOffset} if present.
*
* @param value a Float value
* @return property wrapper around Float
Expand All @@ -2306,7 +2306,7 @@ public static PropertyValue<Float> textRadialOffset(Float value) {
}

/**
* Radial offset of text, in the direction of the symbol's anchor. Useful in combination with {@link PropertyFactory#textVariableAnchor}, which doesn't support the two-dimensional {@link PropertyFactory#textOffset}.
* Radial offset of text, in the direction of the symbol's anchor. Useful in combination with {@link PropertyFactory#textVariableAnchor}, which defaults to using the two-dimensional {@link PropertyFactory#textOffset} if present.
*
* @param value a Float value
* @return property wrapper around Float
Expand All @@ -2316,7 +2316,7 @@ public static PropertyValue<Expression> textRadialOffset(Expression value) {
}

/**
* To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.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 {@link PropertyFactory#textRadialOffset} instead of the two-dimensional {@link PropertyFactory#textOffset}.
* To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.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 {@link PropertyFactory#textRadialOffset} or the two-dimensional {@link PropertyFactory#textOffset}.
*
* @param value a String[] value
* @return property wrapper around String[]
Expand All @@ -2326,7 +2326,7 @@ public static PropertyValue<String[]> textVariableAnchor(String[] value) {
}

/**
* To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.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 {@link PropertyFactory#textRadialOffset} instead of the two-dimensional {@link PropertyFactory#textOffset}.
* To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.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 {@link PropertyFactory#textRadialOffset} or the two-dimensional {@link PropertyFactory#textOffset}.
*
* @param value a String[] value
* @return property wrapper around String[]
Expand Down Expand Up @@ -2476,7 +2476,7 @@ public static PropertyValue<Expression> textTransform(Expression value) {
}

/**
* Offset distance of text from its anchor. Positive values indicate right and down, while negative values indicate left and up.
* 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.
*
* @param value a Float[] value
* @return property wrapper around Float[]
Expand All @@ -2486,7 +2486,7 @@ public static PropertyValue<Float[]> textOffset(Float[] value) {
}

/**
* Offset distance of text from its anchor. Positive values indicate right and down, while negative values indicate left and up.
* 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.
*
* @param value a Float[] value
* @return property wrapper around Float[]
Expand Down
4 changes: 2 additions & 2 deletions src/mbgl/layout/symbol_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
const float overscaling,
const float iconRotation,
const float textRotation,
float radialTextOffset_,
const std::array<float, 2>& variableTextOffset_,
bool allowVerticalPlacement,
const SymbolContent iconType) :
sharedData(std::move(sharedData_)),
Expand All @@ -104,7 +104,7 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
iconOffset(iconOffset_),
key(std::move(key_)),
textBoxScale(textBoxScale_),
radialTextOffset(radialTextOffset_),
variableTextOffset(variableTextOffset_),
singleLine(shapedTextOrientations.singleLine) {
// 'hasText' depends on finding at least one glyph in the shaping that's also in the GlyphPositionMap
if(!sharedData->empty()) symbolContent |= SymbolContent::Text;
Expand Down
4 changes: 2 additions & 2 deletions src/mbgl/layout/symbol_instance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class SymbolInstance {
const float overscaling,
const float iconRotation,
const float textRotation,
float radialTextOffset,
const std::array<float, 2>& variableTextOffset,
bool allowVerticalPlacement,
const SymbolContent iconType = SymbolContent::None);

Expand Down Expand Up @@ -130,7 +130,7 @@ class SymbolInstance {
optional<size_t> placedIconIndex;
optional<size_t> placedVerticalIconIndex;
float textBoxScale;
float radialTextOffset;
std::array<float, 2> variableTextOffset;
bool singleLine;
uint32_t crossTileID = 0;
};
Expand Down
101 changes: 78 additions & 23 deletions src/mbgl/layout/symbol_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,13 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
pixelRatio(parameters.pixelRatio),
tileSize(util::tileSize * overscaling),
tilePixelRatio(float(util::EXTENT) / tileSize),
textSize(toSymbolLayerProperties(layers.at(0)).layerImpl().layout.get<TextSize>()),
iconSize(toSymbolLayerProperties(layers.at(0)).layerImpl().layout.get<IconSize>()),
layout(createLayout(toSymbolLayerProperties(layers.at(0)).layerImpl().layout, zoom)) {
const SymbolLayer::Impl& leader = toSymbolLayerProperties(layers.at(0)).layerImpl();

textSize = leader.layout.get<TextSize>();
iconSize = leader.layout.get<IconSize>();
textRadialOffset = leader.layout.get<TextRadialOffset>();

const bool hasText = has<TextField>(*layout) && has<TextFont>(*layout);
const bool hasIcon = has<IconImage>(*layout);

Expand Down Expand Up @@ -235,29 +237,27 @@ Shaping& shapingForTextJustifyType(ShapedTextOrientations& shapedTextOrientation
}
}

} // namespace

// static
Point<float> SymbolLayout::evaluateRadialOffset(SymbolAnchorType anchor, float radialOffset) {
Point<float> result{};
std::array<float, 2> evaluateRadialOffset(style::SymbolAnchorType anchor, float radialOffset) {
std::array<float, 2> result{{0.0f, 0.0f}};
if (radialOffset < 0.0f) radialOffset = 0.0f; // Ignore negative offset.
// solve for r where r^2 + r^2 = radialOffset^2
const float sqrt2 = 1.41421356237f;
const float hypotenuse = radialOffset / sqrt2;

switch (anchor) {
case SymbolAnchorType::TopRight:
case SymbolAnchorType::TopLeft:
result.y = hypotenuse - baselineOffset;
result[1] = hypotenuse - baselineOffset;
break;
case SymbolAnchorType::BottomRight:
case SymbolAnchorType::BottomLeft:
result.y = -hypotenuse + baselineOffset;
result[1] = -hypotenuse + baselineOffset;
break;
case SymbolAnchorType::Bottom:
result.y = -radialOffset + baselineOffset;
result[1] = -radialOffset + baselineOffset;
break;
case SymbolAnchorType::Top:
result.y = radialOffset - baselineOffset;
result[1] = radialOffset - baselineOffset;
break;
default:
break;
Expand All @@ -266,17 +266,17 @@ Point<float> SymbolLayout::evaluateRadialOffset(SymbolAnchorType anchor, float r
switch (anchor) {
case SymbolAnchorType::TopRight:
case SymbolAnchorType::BottomRight:
result.x = -hypotenuse;
result[0] = -hypotenuse;
break;
case SymbolAnchorType::TopLeft:
case SymbolAnchorType::BottomLeft:
result.x = hypotenuse;
result[0] = hypotenuse;
break;
case SymbolAnchorType::Left:
result.x = radialOffset;
result[0] = radialOffset;
break;
case SymbolAnchorType::Right:
result.x = -radialOffset;
result[0] = -radialOffset;
break;
default:
break;
Expand All @@ -285,6 +285,54 @@ Point<float> SymbolLayout::evaluateRadialOffset(SymbolAnchorType anchor, float r
return result;
}

} // namespace

// static
std::array<float, 2> SymbolLayout::evaluateVariableOffset(style::SymbolAnchorType anchor, std::array<float, 2> offset) {
if (offset[1] == INVALID_OFFSET_VALUE) {
return evaluateRadialOffset(anchor, offset[0]);
}
std::array<float, 2> result{{0.0f, 0.0f}};
offset[0] = std::abs(offset[0]);
offset[1] = std::abs(offset[1]);

switch (anchor) {
case SymbolAnchorType::TopRight:
case SymbolAnchorType::TopLeft:
case SymbolAnchorType::Top:
result[1] = offset[1] - baselineOffset;
break;
case SymbolAnchorType::BottomRight:
case SymbolAnchorType::BottomLeft:
case SymbolAnchorType::Bottom:
result[1] = -offset[1] + baselineOffset;
break;
case SymbolAnchorType::Center:
case SymbolAnchorType::Left:
case SymbolAnchorType::Right:
break;
}

switch (anchor) {
case SymbolAnchorType::TopRight:
case SymbolAnchorType::BottomRight:
case SymbolAnchorType::Right:
result[0] = -offset[0];
break;
case SymbolAnchorType::TopLeft:
case SymbolAnchorType::BottomLeft:
case SymbolAnchorType::Left:
result[0] = offset[0];
break;
case SymbolAnchorType::Center:
case SymbolAnchorType::Top:
case SymbolAnchorType::Bottom:
break;
}

return result;
}

void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions,
const ImageMap& imageMap, const ImagePositions& imagePositions) {
const bool isPointPlacement = layout->get<SymbolPlacement>() == SymbolPlacementType::Point;
Expand All @@ -296,7 +344,7 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions

ShapedTextOrientations shapedTextOrientations;
optional<PositionedIcon> shapedIcon;
Point<float> textOffset;
std::array<float, 2> textOffset{{0.0f, 0.0f}};

// if feature has text, shape the text
if (feature.formattedText) {
Expand All @@ -320,18 +368,18 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions
return result;
};
const std::vector<style::TextVariableAnchorType> variableTextAnchor = layout->evaluate<TextVariableAnchor>(zoom, feature);
const float radialOffset = layout->evaluate<TextRadialOffset>(zoom, feature);
const SymbolAnchorType textAnchor = layout->evaluate<TextAnchor>(zoom, feature);
if (variableTextAnchor.empty()) {
// 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
const float radialOffset = layout->evaluate<TextRadialOffset>(zoom, feature);
if (radialOffset > 0.0f) {
// 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 * util::ONE_EM);
} else {
textOffset = { layout->evaluate<TextOffset>(zoom, feature)[0] * util::ONE_EM,
layout->evaluate<TextOffset>(zoom, feature)[1] * util::ONE_EM};
textOffset = {{layout->evaluate<TextOffset>(zoom, feature)[0] * util::ONE_EM,
layout->evaluate<TextOffset>(zoom, feature)[1] * util::ONE_EM}};
}
}
TextJustifyType textJustify = textAlongLine ? TextJustifyType::Center : layout->evaluate<TextJustify>(zoom, feature);
Expand Down Expand Up @@ -435,14 +483,13 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
const ShapedTextOrientations& shapedTextOrientations,
optional<PositionedIcon> shapedIcon,
const GlyphPositions& glyphPositions,
Point<float> offset,
std::array<float, 2> textOffset,
const SymbolContent iconType) {
const float minScale = 0.5f;
const float glyphSize = 24.0f;

const float layoutTextSize = layout->evaluate<TextSize>(zoom + 1, feature);
const float layoutIconSize = layout->evaluate<IconSize>(zoom + 1, feature);
const std::array<float, 2> textOffset = {{ offset.x, offset.y }};

const std::array<float, 2> iconOffset = layout->evaluate<IconOffset>(zoom, feature);

Expand All @@ -462,7 +509,15 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
const float textMaxAngle = layout->get<TextMaxAngle>() * util::DEG2RAD;
const float iconRotation = layout->evaluate<IconRotate>(zoom, feature);
const float textRotation = layout->evaluate<TextRotate>(zoom, feature);
const float radialTextOffset = layout->evaluate<TextRadialOffset>(zoom, feature) * util::ONE_EM;
std::array<float, 2> variableTextOffset;
if (!textRadialOffset.isUndefined()) {
variableTextOffset = {{layout->evaluate<TextRadialOffset>(zoom, feature) * util::ONE_EM,
INVALID_OFFSET_VALUE}};
} else {
variableTextOffset = {{layout->evaluate<TextOffset>(zoom, feature)[0] * util::ONE_EM,
layout->evaluate<TextOffset>(zoom, feature)[1] * util::ONE_EM}};
}

const SymbolPlacementType textPlacement = layout->get<TextRotationAlignment>() != AlignmentType::Map
? SymbolPlacementType::Point
: layout->get<SymbolPlacement>();
Expand Down Expand Up @@ -503,7 +558,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
iconBoxScale, iconPadding, iconOffset, indexedFeature,
layoutFeatureIndex, feature.index,
feature.formattedText ? feature.formattedText->rawText() : std::u16string(),
overscaling, iconRotation, textRotation, radialTextOffset, allowVerticalPlacement, iconType);
overscaling, iconRotation, textRotation, variableTextOffset, allowVerticalPlacement, iconType);
}
};

Expand Down
14 changes: 12 additions & 2 deletions src/mbgl/layout/symbol_layout.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,24 @@ class SymbolLayout final : public Layout {
const std::string bucketLeaderID;
std::vector<SymbolInstance> symbolInstances;

static Point<float> evaluateRadialOffset(style::SymbolAnchorType anchor, float radialOffset);
static constexpr float INVALID_OFFSET_VALUE = std::numeric_limits<float>::max();
/**
* @brief Calculates variable text offset.
*
* @param anchor text anchor
* @param textOffset Either `text-offset` or [ `text-radial-offset`, INVALID_OFFSET_VALUE ]
* @return std::array<float, 2> offset along x- and y- axis correspondingly.
*/
static std::array<float, 2> evaluateVariableOffset(style::SymbolAnchorType anchor, std::array<float, 2> textOffset);


private:
void addFeature(const size_t,
const SymbolFeature&,
const ShapedTextOrientations& shapedTextOrientations,
optional<PositionedIcon> shapedIcon,
const GlyphPositions&,
Point<float> textOffset,
std::array<float, 2> textOffset,
const SymbolContent iconType);

bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&);
Expand Down Expand Up @@ -101,6 +110,7 @@ class SymbolLayout final : public Layout {

style::TextSize::UnevaluatedType textSize;
style::IconSize::UnevaluatedType iconSize;
style::TextRadialOffset::UnevaluatedType textRadialOffset;
Immutable<style::SymbolLayoutProperties::PossiblyEvaluated> layout;
std::vector<SymbolFeature> features;

Expand Down
Loading

0 comments on commit acfbdda

Please sign in to comment.