diff options
author | Alexander Shalamov <alexander.shalamov@mapbox.com> | 2019-06-07 15:38:08 +0300 |
---|---|---|
committer | Alexander Shalamov <alexander.shalamov@mapbox.com> | 2019-08-13 13:32:56 +0300 |
commit | 9aa529576f4d0565bb81369f03a1fecdb8dd0886 (patch) | |
tree | 22c66df95c47aa8ad7f1e76ba4497adcb471a892 /src/mbgl/text/placement.cpp | |
parent | 075d458403ba2c7367f06e5fc66c2a00c788634a (diff) | |
download | qtlocation-mapboxgl-9aa529576f4d0565bb81369f03a1fecdb8dd0886.tar.gz |
[core] Implement support for "text-writing-mode" layout property
Diffstat (limited to 'src/mbgl/text/placement.cpp')
-rw-r--r-- | src/mbgl/text/placement.cpp | 277 |
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)) { |