summaryrefslogtreecommitdiff
path: root/src/mbgl/text/placement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/text/placement.cpp')
-rw-r--r--src/mbgl/text/placement.cpp277
1 files changed, 210 insertions, 67 deletions
diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp
index f7d13dcb26..de282acf79 100644
--- a/src/mbgl/text/placement.cpp
+++ b/src/mbgl/text/placement.cpp
@@ -172,32 +172,80 @@ void Placement::placeBucket(
placements.emplace(symbolInstance.crossTileID, JointPlacement(false, false, false));
return;
}
- textBoxes.clear();
iconBoxes.clear();
bool placeText = false;
bool placeIcon = false;
bool offscreen = true;
+ std::pair<bool, bool> placed{ false, false };
+ std::pair<bool, bool> placedVertical{ false, false };
optional<size_t> horizontalTextIndex = symbolInstance.getDefaultHorizontalPlacedTextIndex();
if (horizontalTextIndex) {
- const CollisionFeature& textCollisionFeature = symbolInstance.textCollisionFeature;
const PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*horizontalTextIndex);
const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol);
+ const CollisionFeature& textCollisionFeature = symbolInstance.textCollisionFeature;
+
+ const auto updatePreviousOrientationIfNotPlaced = [&](bool isPlaced) {
+ if (bucket.allowVerticalPlacement && !isPlaced && prevPlacement) {
+ auto prevOrientation = prevPlacement->placedOrientations.find(symbolInstance.crossTileID);
+ if (prevOrientation != prevPlacement->placedOrientations.end()) {
+ placedOrientations[symbolInstance.crossTileID] = prevOrientation->second;
+ }
+ }
+ };
+
+ const auto placeTextForPlacementModes = [&] (auto& placeHorizontalFn, auto& placeVerticalFn) {
+ if (bucket.allowVerticalPlacement && symbolInstance.writingModes & WritingModeType::Vertical) {
+ assert(!bucket.placementModes.empty());
+ for (auto& placementMode : bucket.placementModes) {
+ if (placementMode == style::TextWritingModeType::Vertical) {
+ placedVertical = placed = placeVerticalFn();
+ } else {
+ placed = placeHorizontalFn();
+ }
+
+ if (placed.first) {
+ break;
+ }
+ }
+ } else {
+ placed = placeHorizontalFn();
+ }
+ };
+
+ // Line or point label placement
if (variableTextAnchors.empty()) {
- auto placed = collisionIndex.placeFeature(textCollisionFeature, {},
- posMatrix, textLabelPlaneMatrix, pixelRatio,
- placedSymbol, scale, fontSize,
- layout.get<style::TextAllowOverlap>(),
- pitchWithMap,
- params.showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes);
+ const auto placeFeature = [&] (const CollisionFeature& collisionFeature, style::TextWritingModeType orientation) {
+ textBoxes.clear();
+ auto placedFeature = collisionIndex.placeFeature(collisionFeature, {},
+ posMatrix, textLabelPlaneMatrix, pixelRatio,
+ placedSymbol, scale, fontSize,
+ layout.get<style::TextAllowOverlap>(),
+ pitchWithMap,
+ params.showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes);
+ if (placedFeature.first) {
+ placedOrientations.emplace(symbolInstance.crossTileID, orientation);
+ }
+ return placedFeature;
+ };
+
+ const auto placeHorizontal = [&] {
+ return placeFeature(symbolInstance.textCollisionFeature, style::TextWritingModeType::Horizontal);
+ };
+
+ const auto placeVertical = [&] {
+ if (bucket.allowVerticalPlacement && symbolInstance.verticalTextCollisionFeature) {
+ return placeFeature(*symbolInstance.verticalTextCollisionFeature, style::TextWritingModeType::Vertical);
+ }
+ return std::pair<bool, bool>{false, false};
+ };
+
+ placeTextForPlacementModes(placeHorizontal, placeVertical);
+ updatePreviousOrientationIfNotPlaced(placed.first);
+
placeText = placed.first;
offscreen &= placed.second;
} else if (!textCollisionFeature.alongLine && !textCollisionFeature.boxes.empty()) {
- const CollisionBox& textBox = symbolInstance.textCollisionFeature.boxes[0];
- const float width = textBox.x2 - textBox.x1;
- const float height = textBox.y2 - textBox.y1;
- const float textBoxScale = symbolInstance.textBoxScale;
-
// If this symbol was in the last placement, shift the previously used
// anchor to the front of the anchor list, only if the previous anchor
// is still in the anchor list.
@@ -220,55 +268,83 @@ void Placement::placeBucket(
}
}
- for (auto anchor : variableTextAnchors) {
- Point<float> shift = calculateVariableLayoutOffset(anchor, width, height, symbolInstance.radialTextOffset, textBoxScale);
- if (rotateWithMap) {
- float angle = pitchWithMap ? state.getBearing() : -state.getBearing();
- shift = util::rotate(shift, angle);
- }
+ const auto placeFeatureForVariableAnchors = [&] (const CollisionFeature& collisionFeature, style::TextWritingModeType orientation) {
+ const CollisionBox& textBox = collisionFeature.boxes[0];
+ const float width = textBox.x2 - textBox.x1;
+ const float height = textBox.y2 - textBox.y1;
+ const float textBoxScale = symbolInstance.textBoxScale;
+ std::pair<bool, bool> placedFeature = {false, false};
+ for (auto anchor : variableTextAnchors) {
+ Point<float> shift = calculateVariableLayoutOffset(anchor, width, height, symbolInstance.radialTextOffset, textBoxScale);
+ if (rotateWithMap) {
+ float angle = pitchWithMap ? state.getBearing() : -state.getBearing();
+ shift = util::rotate(shift, angle);
+ }
+
+ textBoxes.clear();
+ placedFeature = collisionIndex.placeFeature(collisionFeature, shift,
+ posMatrix, mat4(), pixelRatio,
+ placedSymbol, scale, fontSize,
+ layout.get<style::TextAllowOverlap>(),
+ pitchWithMap,
+ params.showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes);
+ if (placedFeature.first) {
+ assert(symbolInstance.crossTileID != 0u);
+ optional<style::TextVariableAnchorType> prevAnchor;
+
+ // If this label was placed in the previous placement, record the anchor position
+ // to allow us to animate the transition
+ if (prevPlacement) {
+ auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID);
+ auto prevPlacements = prevPlacement->placements.find(symbolInstance.crossTileID);
+ if (prevOffset != prevPlacement->variableOffsets.end() &&
+ prevPlacements != prevPlacement->placements.end() &&
+ prevPlacements->second.text) {
+ // TODO: The prevAnchor seems to be unused, needs to be fixed.
+ prevAnchor = prevOffset->second.anchor;
+ }
+ }
- auto placed = collisionIndex.placeFeature(textCollisionFeature, shift,
- posMatrix, mat4(), pixelRatio,
- placedSymbol, scale, fontSize,
- layout.get<style::TextAllowOverlap>(),
- pitchWithMap,
- params.showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes);
-
- if (placed.first) {
- assert(symbolInstance.crossTileID != 0u);
- optional<style::TextVariableAnchorType> prevAnchor;
-
- // If this label was placed in the previous placement, record the anchor position
- // to allow us to animate the transition
- if (prevPlacement) {
- auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID);
- auto prevPlacements = prevPlacement->placements.find(symbolInstance.crossTileID);
- if (prevOffset != prevPlacement->variableOffsets.end() &&
- prevPlacements != prevPlacement->placements.end() &&
- prevPlacements->second.text) {
- prevAnchor = prevOffset->second.anchor;
+ variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{
+ symbolInstance.radialTextOffset,
+ width,
+ height,
+ anchor,
+ textBoxScale,
+ prevAnchor
+ }));
+
+ if (bucket.allowVerticalPlacement) {
+ placedOrientations.emplace(symbolInstance.crossTileID, orientation);
}
+ break;
}
+ }
+
+ return placedFeature;
+ };
- variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{
- symbolInstance.radialTextOffset,
- width,
- height,
- anchor,
- textBoxScale,
- prevAnchor
- }));
-
- placeText = placed.first;
- offscreen &= placed.second;
- break;
+ const auto placeHorizontal = [&] {
+ return placeFeatureForVariableAnchors(symbolInstance.textCollisionFeature, style::TextWritingModeType::Horizontal);
+ };
+
+ const auto placeVertical = [&] {
+ if (bucket.allowVerticalPlacement && !placed.first && symbolInstance.verticalTextCollisionFeature) {
+ return placeFeatureForVariableAnchors(*symbolInstance.verticalTextCollisionFeature, style::TextWritingModeType::Vertical);
}
- textBoxes.clear();
- }
+ return std::pair<bool, bool>{false, false};
+ };
+
+ placeTextForPlacementModes(placeHorizontal, placeVertical);
+
+ placeText = placed.first;
+ offscreen &= placed.second;
+
+ updatePreviousOrientationIfNotPlaced(placed.first);
// If we didn't get placed, we still need to copy our position from the last placement for
// fade animations
- if (prevPlacement && variableOffsets.find(symbolInstance.crossTileID) == variableOffsets.end()) {
+ if (!placeText && prevPlacement) {
auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID);
if (prevOffset != prevPlacement->variableOffsets.end()) {
variableOffsets[symbolInstance.crossTileID] = prevOffset->second;
@@ -281,14 +357,14 @@ void Placement::placeBucket(
const PlacedSymbol& placedSymbol = bucket.icon.placedSymbols.at(*symbolInstance.placedIconIndex);
const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol);
- auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, {},
+ auto placedIcon = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, {},
posMatrix, iconLabelPlaneMatrix, pixelRatio,
placedSymbol, scale, fontSize,
layout.get<style::IconAllowOverlap>(),
pitchWithMap,
params.showCollisionBoxes, avoidEdges, collisionGroup.second, iconBoxes);
- placeIcon = placed.first;
- offscreen &= placed.second;
+ placeIcon = placedIcon.first;
+ offscreen &= placedIcon.second;
}
const bool iconWithoutText = !symbolInstance.hasText || layout.get<style::TextOptional>();
@@ -304,7 +380,11 @@ void Placement::placeBucket(
}
if (placeText) {
- collisionIndex.insertFeature(symbolInstance.textCollisionFeature, textBoxes, layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
+ if (placedVertical.first && symbolInstance.verticalTextCollisionFeature) {
+ collisionIndex.insertFeature(*symbolInstance.verticalTextCollisionFeature, textBoxes, layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
+ } else {
+ collisionIndex.insertFeature(symbolInstance.textCollisionFeature, textBoxes, layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
+ }
}
if (placeIcon) {
@@ -399,6 +479,15 @@ void Placement::commit(TimePoint now) {
}
}
+ for (auto& prevOrientation : prevPlacement->placedOrientations) {
+ const uint32_t crossTileID = prevOrientation.first;
+ auto foundOrientation = placedOrientations.find(crossTileID);
+ auto foundOpacity = opacities.find(crossTileID);
+ if (foundOrientation == placedOrientations.end() && foundOpacity != opacities.end() && !foundOpacity->second.isHidden()) {
+ placedOrientations[prevOrientation.first] = prevOrientation.second;
+ }
+ }
+
fadeStartTime = placementChanged ? commitTime : prevPlacement->fadeStartTime;
}
@@ -457,7 +546,8 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor
for (const PlacedSymbol& symbol : bucket.text.placedSymbols) {
optional<VariableOffset> variableOffset;
- if (!symbol.hidden && symbol.crossTileID != 0u) {
+ const bool skipOrientation = bucket.allowVerticalPlacement && !symbol.placedOrientation;
+ if (!symbol.hidden && symbol.crossTileID != 0u && !skipOrientation) {
auto it = variableOffsets.find(symbol.crossTileID);
if (it != variableOffsets.end()) {
bucket.hasVariablePlacement = true;
@@ -506,12 +596,24 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor
}
for (std::size_t i = 0; i < symbol.glyphOffsets.size(); ++i) {
- addDynamicAttributes(shiftedAnchor, 0, bucket.text.dynamicVertices);
+ addDynamicAttributes(shiftedAnchor, symbol.angle, bucket.text.dynamicVertices);
}
}
}
result = true;
+ } else if (bucket.allowVerticalPlacement && bucket.hasTextData()) {
+ bucket.text.dynamicVertices.clear();
+ for (const PlacedSymbol& symbol : bucket.text.placedSymbols) {
+ if (symbol.hidden || !symbol.placedOrientation) {
+ hideGlyphs(symbol.glyphOffsets.size(), bucket.text.dynamicVertices);
+ } else {
+ for (std::size_t i = 0; i < symbol.glyphOffsets.size(); ++i) {
+ addDynamicAttributes(symbol.anchorPoint, symbol.angle, bucket.text.dynamicVertices);
+ }
+ }
+ }
+ result = true;
}
return result;
@@ -582,9 +684,18 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState
bucket.text.opacityVertices.extend(textOpacityVerticesSize, opacityVertex);
- auto offset = variableOffsets.find(symbolInstance.crossTileID);
- if (offset != variableOffsets.end()) {
- markUsedJustification(bucket, offset->second.anchor, symbolInstance);
+ style::TextWritingModeType previousOrientation = style::TextWritingModeType::Horizontal;
+ if (bucket.allowVerticalPlacement) {
+ auto prevOrientation = placedOrientations.find(symbolInstance.crossTileID);
+ if (prevOrientation != placedOrientations.end()) {
+ previousOrientation = prevOrientation->second;
+ markUsedOrientation(bucket, prevOrientation->second, symbolInstance);
+ }
+ }
+
+ auto prevOffset = variableOffsets.find(symbolInstance.crossTileID);
+ if (prevOffset != variableOffsets.end()) {
+ markUsedJustification(bucket, prevOffset->second.anchor, symbolInstance, previousOrientation);
}
}
if (symbolInstance.hasIcon) {
@@ -654,7 +765,11 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState
};
if (bucket.hasCollisionBoxData()) {
+ // TODO: update collision box opacity based on selected text variant (horizontal | vertical).
updateCollisionTextBox(symbolInstance.textCollisionFeature, opacityState.text.placed);
+ if (bucket.allowVerticalPlacement && symbolInstance.verticalTextCollisionFeature) {
+ updateCollisionTextBox(*symbolInstance.verticalTextCollisionFeature, opacityState.text.placed);
+ }
updateCollisionBox(symbolInstance.iconCollisionFeature, opacityState.icon.placed);
}
if (bucket.hasCollisionCircleData()) {
@@ -671,7 +786,12 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState
}
namespace {
-optional<size_t> justificationToIndex(style::TextJustifyType justify, const SymbolInstance& symbolInstance) {
+optional<size_t> justificationToIndex(style::TextJustifyType justify, const SymbolInstance& symbolInstance, style::TextWritingModeType orientation) {
+ // Vertical symbol has just one justification, style::TextJustifyType::Left.
+ if (orientation == style::TextWritingModeType::Vertical) {
+ return symbolInstance.placedVerticalTextIndex;
+ }
+
switch(justify) {
case style::TextJustifyType::Right: return symbolInstance.placedRightTextIndex;
case style::TextJustifyType::Center: return symbolInstance.placedCenterTextIndex;
@@ -686,13 +806,13 @@ const style::TextJustifyType justifyTypes[] = {style::TextJustifyType::Right, st
} // namespace
-void Placement::markUsedJustification(SymbolBucket& bucket, style::TextVariableAnchorType placedAnchor, SymbolInstance& symbolInstance) {
+void Placement::markUsedJustification(SymbolBucket& bucket, style::TextVariableAnchorType placedAnchor, SymbolInstance& symbolInstance, style::TextWritingModeType orientation) {
style::TextJustifyType anchorJustify = getAnchorJustification(placedAnchor);
assert(anchorJustify != style::TextJustifyType::Auto);
- const optional<size_t>& autoIndex = justificationToIndex(anchorJustify, symbolInstance);
+ const optional<size_t>& autoIndex = justificationToIndex(anchorJustify, symbolInstance, orientation);
for (auto& justify : justifyTypes) {
- const optional<size_t> index = justificationToIndex(justify, symbolInstance);
+ const optional<size_t> index = justificationToIndex(justify, symbolInstance, orientation);
if (index) {
assert(bucket.text.placedSymbols.size() > *index);
if (autoIndex && *index != *autoIndex) {
@@ -706,6 +826,29 @@ void Placement::markUsedJustification(SymbolBucket& bucket, style::TextVariableA
}
}
+void Placement::markUsedOrientation(SymbolBucket& bucket, style::TextWritingModeType orientation, SymbolInstance& symbolInstance) {
+ auto horizontal = orientation == style::TextWritingModeType::Horizontal ?
+ optional<style::TextWritingModeType>(orientation) : nullopt;
+ auto vertical = orientation == style::TextWritingModeType::Vertical ?
+ optional<style::TextWritingModeType>(orientation) : nullopt;
+
+ if (symbolInstance.placedRightTextIndex) {
+ bucket.text.placedSymbols.at(*symbolInstance.placedRightTextIndex).placedOrientation = horizontal;
+ }
+
+ if (symbolInstance.placedCenterTextIndex && !symbolInstance.singleLine) {
+ bucket.text.placedSymbols.at(*symbolInstance.placedCenterTextIndex).placedOrientation = horizontal;
+ }
+
+ if (symbolInstance.placedLeftTextIndex && !symbolInstance.singleLine) {
+ bucket.text.placedSymbols.at(*symbolInstance.placedLeftTextIndex).placedOrientation = horizontal;
+ }
+
+ if (symbolInstance.placedVerticalTextIndex) {
+ bucket.text.placedSymbols.at(*symbolInstance.placedVerticalTextIndex).placedOrientation = vertical;
+ }
+}
+
float Placement::symbolFadeChange(TimePoint now) const {
if (mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions &&
transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) > Milliseconds(0)) {