From e6bdd4235589dfbaf692d2fbfd8706736aa31060 Mon Sep 17 00:00:00 2001 From: Molly Lloyd Date: Tue, 18 Dec 2018 15:52:38 -0800 Subject: implement dynamic-text-anchor property --- include/mbgl/style/types.hpp | 4 +- include/mbgl/util/constants.hpp | 1 + mapbox-gl-js | 2 +- src/mbgl/layout/symbol_instance.cpp | 33 +++-- src/mbgl/layout/symbol_instance.hpp | 13 +- src/mbgl/layout/symbol_layout.cpp | 83 +++++++------ src/mbgl/layout/symbol_layout.hpp | 2 +- src/mbgl/layout/symbol_projection.hpp | 8 +- src/mbgl/renderer/buckets/symbol_bucket.cpp | 6 +- src/mbgl/renderer/buckets/symbol_bucket.hpp | 5 +- src/mbgl/renderer/layers/render_symbol_layer.cpp | 21 ++++ src/mbgl/text/collision_index.cpp | 10 +- src/mbgl/text/collision_index.hpp | 2 + src/mbgl/text/placement.cpp | 146 +++++++++++++++++++++-- src/mbgl/text/placement.hpp | 11 ++ src/mbgl/text/quads.cpp | 5 +- src/mbgl/text/shaping.cpp | 54 +-------- src/mbgl/text/shaping.hpp | 50 ++++++++ 18 files changed, 331 insertions(+), 125 deletions(-) diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index 2a7635ff2d..19fc2f115f 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -99,7 +99,6 @@ enum class SymbolAnchorType : uint8_t { }; enum class DynamicTextAnchorType : uint8_t { - Auto, Center, Left, Right, @@ -108,7 +107,8 @@ enum class DynamicTextAnchorType : uint8_t { TopLeft, TopRight, BottomLeft, - BottomRight + BottomRight, + Auto }; enum class TextTransformType : uint8_t { diff --git a/include/mbgl/util/constants.hpp b/include/mbgl/util/constants.hpp index 7110d9e26b..6ac9280fba 100644 --- a/include/mbgl/util/constants.hpp +++ b/include/mbgl/util/constants.hpp @@ -39,6 +39,7 @@ constexpr double MAX_ZOOM = 25.5; constexpr float MIN_ZOOM_F = MIN_ZOOM; constexpr float MAX_ZOOM_F = MAX_ZOOM; constexpr uint8_t DEFAULT_MAX_ZOOM = 22; +constexpr float ONE_EM = 24.0f; constexpr uint8_t DEFAULT_PREFETCH_ZOOM_DELTA = 4; diff --git a/mapbox-gl-js b/mapbox-gl-js index 8e267fe230..825d4ee5ad 160000 --- a/mapbox-gl-js +++ b/mapbox-gl-js @@ -1 +1 @@ -Subproject commit 8e267fe230dbafc75914e437d26efab2f81adf18 +Subproject commit 825d4ee5add1c2028a39141e8d4d5cfe70140220 diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index 139a42113c..c4cc04578a 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -7,10 +7,10 @@ using namespace style; SymbolInstance::SymbolInstance(Anchor& anchor_, GeometryCoordinates line_, - const std::pair& shapedTextOrientations, + const std::tuple& shapedTextOrientations, optional shapedIcon, const SymbolLayoutProperties::Evaluated& layout, - const float layoutTextSize, + const float layoutTextSize_, const float textBoxScale, const float textPadding, const SymbolPlacementType textPlacement, @@ -31,32 +31,39 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, hasIcon(shapedIcon), // Create the collision features that will be used to check whether this symbol instance can be placed - textCollisionFeature(line_, anchor, shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature, overscaling, rotate), + textCollisionFeature(line_, anchor, std::get<0>(shapedTextOrientations), textBoxScale, textPadding, textPlacement, indexedFeature, overscaling, rotate), iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, indexedFeature, rotate), layoutFeatureIndex(layoutFeatureIndex_), dataFeatureIndex(dataFeatureIndex_), textOffset(textOffset_), iconOffset(iconOffset_), - key(key_) { + key(key_), + layoutTextSize(layoutTextSize_) { // Create the quads used for rendering the icon and glyphs. if (shapedIcon) { - iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first); + iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, std::get<0>(shapedTextOrientations)); } - if (shapedTextOrientations.first) { - horizontalGlyphQuads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions); + if (std::get<0>(shapedTextOrientations)) { + rightJustifiedGlyphQuads = getGlyphQuads(std::get<0>(shapedTextOrientations), layout, textPlacement, positions); } - if (shapedTextOrientations.second) { - verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions); + if (std::get<1>(shapedTextOrientations)) { + centerJustifiedGlyphQuads = getGlyphQuads(std::get<1>(shapedTextOrientations), layout, textPlacement, positions); + } + if (std::get<2>(shapedTextOrientations)) { + leftJustifiedGlyphQuads = getGlyphQuads(std::get<2>(shapedTextOrientations), layout, textPlacement, positions); + } + if (std::get<3>(shapedTextOrientations)) { + verticalGlyphQuads = getGlyphQuads(std::get<3>(shapedTextOrientations), layout, textPlacement, positions); } // 'hasText' depends on finding at least one glyph in the shaping that's also in the GlyphPositionMap - hasText = horizontalGlyphQuads.size() > 0 || verticalGlyphQuads.size() > 0; + hasText = rightJustifiedGlyphQuads.size() > 0 || centerJustifiedGlyphQuads.size() > 0 || leftJustifiedGlyphQuads.size() > 0 || verticalGlyphQuads.size() > 0; - if (shapedTextOrientations.first && shapedTextOrientations.second) { + if (std::get<0>(shapedTextOrientations) && std::get<3>(shapedTextOrientations)) { writingModes = WritingModeType::Horizontal | WritingModeType::Vertical; - } else if (shapedTextOrientations.first) { + } else if (std::get<0>(shapedTextOrientations)) { writingModes = WritingModeType::Horizontal; - } else if (shapedTextOrientations.second) { + } else if (std::get<3>(shapedTextOrientations)) { writingModes = WritingModeType::Vertical; } else { writingModes = WritingModeType::None; diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index 6148d7fe88..a4d688f4d5 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -15,7 +15,9 @@ class SymbolInstance { public: SymbolInstance(Anchor& anchor, GeometryCoordinates line, - const std::pair& shapedTextOrientations, + // When dynamic-text-anchor is used, order is right, center, left, vertical. + // Otherwise order is horizontal, empty, empty, vertical. + const std::tuple& shapedTextOrientations, optional shapedIcon, const style::SymbolLayoutProperties::Evaluated&, const float layoutTextSize, @@ -38,7 +40,9 @@ public: GeometryCoordinates line; bool hasText; bool hasIcon; - SymbolQuads horizontalGlyphQuads; + SymbolQuads rightJustifiedGlyphQuads; + SymbolQuads leftJustifiedGlyphQuads; + SymbolQuads centerJustifiedGlyphQuads; SymbolQuads verticalGlyphQuads; optional iconQuad; CollisionFeature textCollisionFeature; @@ -49,8 +53,11 @@ public: std::array textOffset; std::array iconOffset; std::u16string key; + float layoutTextSize; bool isDuplicate; - optional placedTextIndex; + optional placedRightTextIndex; + optional placedCenterTextIndex; + optional placedLeftTextIndex; optional placedVerticalTextIndex; optional placedIconIndex; uint32_t crossTileID = 0; diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 332fb3f46a..fc086f0f1d 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -180,35 +180,42 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions auto& feature = *it; if (feature.geometry.empty()) continue; - std::pair shapedTextOrientations; + std::tuple shapedTextOrientations; optional shapedIcon; - + const SymbolAnchorType textAnchor = layout.evaluate(zoom, feature); + const TextJustifyType textJustify = layout.evaluate(zoom, feature); // if feature has text, shape the text if (feature.formattedText) { - auto applyShaping = [&] (const TaggedString& formattedText, WritingModeType writingMode) { - const float oneEm = 24.0f; + auto applyShaping = [&] (const TaggedString& formattedText, WritingModeType writingMode, const SymbolAnchorType anchor, const TextJustifyType justify) { const Shaping result = getShaping( /* string */ formattedText, /* maxWidth: ems */ layout.get() == SymbolPlacementType::Point ? - layout.evaluate(zoom, feature) * oneEm : 0, - /* lineHeight: ems */ layout.get() * oneEm, - /* anchor */ layout.evaluate(zoom, feature), - /* justify */ layout.evaluate(zoom, feature), - /* spacing: ems */ util::i18n::allowsLetterSpacing(feature.formattedText->rawText()) ? layout.evaluate(zoom, feature) * oneEm : 0.0f, - /* translate */ Point(layout.evaluate(zoom, feature)[0] * oneEm, layout.evaluate(zoom, feature)[1] * oneEm), - /* verticalHeight */ oneEm, + layout.evaluate(zoom, feature) * util::ONE_EM : 0, + /* lineHeight: ems */ layout.get() * util::ONE_EM, + /* anchor */ anchor, + /* justify */ justify, + /* spacing: ems */ util::i18n::allowsLetterSpacing(feature.formattedText->rawText()) ? layout.evaluate(zoom, feature) * util::ONE_EM : 0.0f, + /* translate */ Point(layout.evaluate(zoom, feature)[0] * util::ONE_EM, layout.evaluate(zoom, feature)[1] * util::ONE_EM), + /* verticalHeight */ util::ONE_EM, /* writingMode */ writingMode, /* bidirectional algorithm object */ bidi, /* glyphs */ glyphMap); return result; }; + + if (layout.evaluate(zoom, feature).size() > 0 && !textAlongLine) { + std::get<0>(shapedTextOrientations) = applyShaping(*feature.formattedText, WritingModeType::Horizontal, SymbolAnchorType::TopLeft, TextJustifyType::Right); + std::get<1>(shapedTextOrientations) = applyShaping(*feature.formattedText, WritingModeType::Horizontal, SymbolAnchorType::TopLeft, TextJustifyType::Center); + std::get<1>(shapedTextOrientations) = applyShaping(*feature.formattedText, WritingModeType::Horizontal, SymbolAnchorType::TopLeft, TextJustifyType::Left); + } else { + std::get<0>(shapedTextOrientations) = applyShaping(*feature.formattedText, WritingModeType::Horizontal, textAnchor, textJustify); + } - shapedTextOrientations.first = applyShaping(*feature.formattedText, WritingModeType::Horizontal); if (util::i18n::allowsVerticalWritingMode(feature.formattedText->rawText()) && textAlongLine) { feature.formattedText->verticalizePunctuation(); - shapedTextOrientations.second = applyShaping(*feature.formattedText, WritingModeType::Vertical); + std::get<3>(shapedTextOrientations) = applyShaping(*feature.formattedText, WritingModeType::Vertical, textAnchor, textJustify); } } @@ -233,7 +240,7 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions } // if either shapedText or icon position is present, add the feature - if (shapedTextOrientations.first || shapedIcon) { + if (std::get<0>(shapedTextOrientations) || shapedIcon) { addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositions); } @@ -245,7 +252,7 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, const SymbolFeature& feature, - const std::pair& shapedTextOrientations, + const std::tuple& shapedTextOrientations, optional shapedIcon, const GlyphPositions& glyphPositions) { const float minScale = 0.5f; @@ -304,8 +311,8 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, Anchors anchors = getAnchors(line, symbolSpacing, textMaxAngle, - (shapedTextOrientations.second ?: shapedTextOrientations.first).left, - (shapedTextOrientations.second ?: shapedTextOrientations.first).right, + (std::get<3>(shapedTextOrientations) ?: std::get<0>(shapedTextOrientations)).left, + (std::get<3>(shapedTextOrientations) ?: std::get<0>(shapedTextOrientations)).right, (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), glyphSize, @@ -325,8 +332,8 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, if (line.size() > 1) { optional anchor = getCenterAnchor(line, textMaxAngle, - (shapedTextOrientations.second ?: shapedTextOrientations.first).left, - (shapedTextOrientations.second ?: shapedTextOrientations.first).right, + (std::get<3>(shapedTextOrientations) ?: std::get<0>(shapedTextOrientations)).left, + (std::get<3>(shapedTextOrientations) ?: std::get<0>(shapedTextOrientations)).right, (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), glyphSize, @@ -410,7 +417,7 @@ void SymbolLayout::createBucket(const ImagePositions&, std::unique_ptr() || layout.get() || layout.get() || layout.get()); - auto bucket = std::make_shared(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances)); + auto bucket = std::make_shared(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, sortFeaturesByY, tilePixelRatio, bucketLeaderID, std::move(symbolInstances)); for (SymbolInstance &symbolInstance : bucket->symbolInstances) { @@ -423,21 +430,25 @@ void SymbolLayout::createBucket(const ImagePositions&, std::unique_ptr sizeData = bucket->textSizeBinder->getVertexSizeData(feature); - bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, - symbolInstance.textOffset, symbolInstance.writingModes, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor)); - symbolInstance.placedTextIndex = bucket->text.placedSymbols.size() - 1; - PlacedSymbol& horizontalSymbol = bucket->text.placedSymbols.back(); - - bool firstHorizontal = true; - for (const auto& symbol : symbolInstance.horizontalGlyphQuads) { - size_t index = addSymbol( - bucket->text, sizeData, symbol, - symbolInstance.anchor, horizontalSymbol); - if (firstHorizontal) { - horizontalSymbol.vertexStartIndex = index; - firstHorizontal = false; + auto placeSymbol = [&] (SymbolQuads glyphQuads) { + bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max,symbolInstance.textOffset, symbolInstance.writingModes, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor)); + + PlacedSymbol& horizontalSymbol = bucket->text.placedSymbols.back(); + + bool firstHorizontal = true; + for (const auto& symbol : glyphQuads) { + size_t index = addSymbol( + bucket->text, sizeData, symbol, + symbolInstance.anchor, horizontalSymbol); + if (firstHorizontal) { + horizontalSymbol.vertexStartIndex = index; + firstHorizontal = false; + } } - } + return bucket->text.placedSymbols.size() - 1; + }; + + symbolInstance.placedRightTextIndex = placeSymbol(symbolInstance.rightJustifiedGlyphQuads); if (symbolInstance.writingModes & WritingModeType::Vertical) { bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, @@ -458,6 +469,10 @@ void SymbolLayout::createBucket(const ImagePositions&, std::unique_ptr& shapedTextOrientations, + const std::tuple& shapedTextOrientations, optional shapedIcon, const GlyphPositions&); diff --git a/src/mbgl/layout/symbol_projection.hpp b/src/mbgl/layout/symbol_projection.hpp index 3e57d162fd..ea7fec7d32 100644 --- a/src/mbgl/layout/symbol_projection.hpp +++ b/src/mbgl/layout/symbol_projection.hpp @@ -13,7 +13,13 @@ namespace mbgl { namespace style { class SymbolPropertyValues; } // end namespace style - + + void hideGlyphs(size_t numGlyphs, gl::VertexVector& dynamicVertexArray); + void addDynamicAttributes( + const Point& anchorPoint, + const float angle, + gl::VertexVector& dynamicVertexArray + ); struct TileDistance { TileDistance(float prevTileDistance_, float lastSegmentViewportDistance_) : prevTileDistance(prevTileDistance_), lastSegmentViewportDistance(lastSegmentViewportDistance_) diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index a3f652fc6e..a6fdcb7371 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -18,6 +18,7 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo bool sdfIcons_, bool iconsNeedLinear_, bool sortFeaturesByY_, + const float tilePixelRatio_, const std::string bucketName_, const std::vector&& symbolInstances_) : Bucket(LayerType::Symbol), @@ -25,6 +26,7 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo sdfIcons(sdfIcons_), iconsNeedLinear(iconsNeedLinear_ || iconSize.isDataDriven() || !iconSize.isZoomConstant()), sortFeaturesByY(sortFeaturesByY_), + tilePixelRatio(tilePixelRatio_), bucketLeaderID(std::move(bucketName_)), symbolInstances(std::move(symbolInstances_)), textSizeBinder(SymbolSizeBinder::create(zoom, textSize, TextSize::defaultValue())), @@ -210,8 +212,8 @@ void SymbolBucket::sortFeatures(const float angle) { const SymbolInstance& symbolInstance = symbolInstances[i]; featureSortOrder->push_back(symbolInstance.dataFeatureIndex); - if (symbolInstance.placedTextIndex) { - addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedTextIndex]); + if (symbolInstance.placedRightTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedRightTextIndex]); } if (symbolInstance.placedVerticalTextIndex) { addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedVerticalTextIndex]); diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index 5addff40b2..cfcfceff93 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -20,11 +20,12 @@ class PlacedSymbol { public: PlacedSymbol(Point anchorPoint_, uint16_t segment_, float lowerSize_, float upperSize_, std::array lineOffset_, WritingModeType writingModes_, GeometryCoordinates line_, std::vector tileDistances_) : - anchorPoint(anchorPoint_), segment(segment_), lowerSize(lowerSize_), upperSize(upperSize_), + anchorPoint(anchorPoint_), dynamicShift(0, 0), segment(segment_), lowerSize(lowerSize_), upperSize(upperSize_), lineOffset(lineOffset_), writingModes(writingModes_), line(std::move(line_)), tileDistances(std::move(tileDistances_)), hidden(false), vertexStartIndex(0) { } Point anchorPoint; + Point dynamicShift; uint16_t segment; float lowerSize; float upperSize; @@ -47,6 +48,7 @@ public: bool sdfIcons, bool iconsNeedLinear, bool sortFeaturesByY, + const float tilePixelRatio, const std::string bucketLeaderID, const std::vector&&); @@ -64,6 +66,7 @@ public: const bool sdfIcons; const bool iconsNeedLinear; const bool sortFeaturesByY; + const float tilePixelRatio; const std::string bucketLeaderID; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 11ffd74b10..6da536e27b 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -229,6 +229,27 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { parameters.state); parameters.context.updateVertexBuffer(*bucket.text.dynamicVertexBuffer, std::move(bucket.text.dynamicVertices)); + } else if (layout.get().size()) { + auto partiallyEvaluatedSize = bucket.textSizeBinder->evaluateForZoom(parameters.state.getZoom()); + bucket.text.dynamicVertices.clear(); + for (auto& symbol : bucket.text.placedSymbols) { + if (symbol.hidden || symbol.dynamicShift.x == -INFINITY) { + // These symbols are from a justification that is not being used, or a label that wasn't placed + // so we don't need to do the extra math to figure out what incremental shift to apply. + hideGlyphs(symbol.glyphOffsets.size(), bucket.text.dynamicVertices); + } else { + auto renderTextSize = evaluateSizeForFeature(partiallyEvaluatedSize, symbol); + auto renderTimeTextScale = renderTextSize * bucket.tilePixelRatio / 24; + + const float shiftX = (symbol.dynamicShift.x * renderTimeTextScale) / pow(2, parameters.state.getZoom() - tile.tile.id.overscaledZ); + const float shiftY = (symbol.dynamicShift.y * renderTimeTextScale) / pow(2, parameters.state.getZoom() - tile.tile.id.overscaledZ); + const Point shiftedAnchor = Point(symbol.anchorPoint.x + shiftX, symbol.anchorPoint.y + shiftY); + for (size_t i = 0; i < symbol.glyphOffsets.size(); i++) { + addDynamicAttributes(shiftedAnchor, 0, bucket.text.dynamicVertices); + } + } + } + parameters.context.updateVertexBuffer(*bucket.text.dynamicVertexBuffer, std::move(bucket.text.dynamicVertices)); } const Size texsize = geometryTile.glyphAtlasTexture->size; diff --git a/src/mbgl/text/collision_index.cpp b/src/mbgl/text/collision_index.cpp index 90acb2b441..f81a8a4f0f 100644 --- a/src/mbgl/text/collision_index.cpp +++ b/src/mbgl/text/collision_index.cpp @@ -80,6 +80,8 @@ bool CollisionIndex::isInsideTile(const CollisionBox& box, const CollisionTileBo std::pair CollisionIndex::placeFeature(CollisionFeature& feature, + const float shiftX, + const float shiftY, const mat4& posMatrix, const mat4& labelPlaneMatrix, const float textPixelRatio, @@ -95,10 +97,10 @@ std::pair CollisionIndex::placeFeature(CollisionFeature& feature, CollisionBox& box = feature.boxes.front(); const auto projectedPoint = projectAndGetPerspectiveRatio(posMatrix, box.anchor); const float tileToViewport = textPixelRatio * projectedPoint.second; - box.px1 = box.x1 * tileToViewport + projectedPoint.first.x; - box.py1 = box.y1 * tileToViewport + projectedPoint.first.y; - box.px2 = box.x2 * tileToViewport + projectedPoint.first.x; - box.py2 = box.y2 * tileToViewport + projectedPoint.first.y; + box.px1 = (box.x1 + shiftX) * tileToViewport + projectedPoint.first.x; + box.py1 = (box.y1 + shiftY) * tileToViewport + projectedPoint.first.y; + box.px2 = (box.x2 + shiftX) * tileToViewport + projectedPoint.first.x; + box.py2 = (box.y2 + shiftY) * tileToViewport + projectedPoint.first.y; if ((avoidEdges && !isInsideTile(box, *avoidEdges)) || diff --git a/src/mbgl/text/collision_index.hpp b/src/mbgl/text/collision_index.hpp index dac0aa0bf7..c33f4b2199 100644 --- a/src/mbgl/text/collision_index.hpp +++ b/src/mbgl/text/collision_index.hpp @@ -23,6 +23,8 @@ public: explicit CollisionIndex(const TransformState&); std::pair placeFeature(CollisionFeature& feature, + const float shiftX, + const float shiftY, const mat4& posMatrix, const mat4& labelPlaneMatrix, const float textPixelRatio, diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index a39106a43d..8227a01e2b 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -22,6 +22,54 @@ bool OpacityState::isHidden() const { return opacity == 0 && !placed; } +DynamicTextOffsets::DynamicTextOffsets(Point right_, Point center_, Point left_): + right(right_), + center(center_), + left(left_) {} + +const std::vector AUTO_DYNAMIC_TEXT_ANCHORS = std::vector { + style::DynamicTextAnchorType::Center, + style::DynamicTextAnchorType::Top, + style::DynamicTextAnchorType::Bottom, + style::DynamicTextAnchorType::Left, + style::DynamicTextAnchorType::Right, + style::DynamicTextAnchorType::TopLeft, + style::DynamicTextAnchorType::TopRight, + style::DynamicTextAnchorType::BottomLeft, + style::DynamicTextAnchorType::BottomRight +}; + +style::TextJustifyType getAnchorJustification(const style::DynamicTextAnchorType anchor) { + switch (anchor) { + case style::DynamicTextAnchorType::Right: + case style::DynamicTextAnchorType::BottomRight: + case style::DynamicTextAnchorType::TopRight: + return style::TextJustifyType::Right; + break; + + case style::DynamicTextAnchorType::Left : + case style::DynamicTextAnchorType::TopLeft : + case style::DynamicTextAnchorType::BottomLeft : + return style::TextJustifyType::Left; + break; + + default: + return style::TextJustifyType::Center; + break; + } +}; + +void hideUnplacedJustifications(SymbolBucket& bucket, style::TextJustifyType placedJustification, std::map>& placedSymbols) { + for (auto const& symbol : placedSymbols ) { + if (symbol.first != placedJustification) { + if (symbol.second) { + PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbol.second); + placedSymbol.dynamicShift = {-INFINITY, -INFINITY}; + } + } + } +}; + JointOpacityState::JointOpacityState(bool placedText, bool placedIcon, bool skipFade) : icon(OpacityState(placedIcon, skipFade)), text(OpacityState(placedText, skipFade)) {} @@ -161,7 +209,9 @@ void Placement::placeLayerBucket( // See https://github.com/mapbox/mapbox-gl-native/issues/12683 const bool alwaysShowText = textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || bucket.layout.get()); const bool alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || bucket.layout.get()); - + const std::vector dynamicTextAnchors = bucket.layout.get(); + const std::vector dynamicTextAnchorOrder = dynamicTextAnchors.size() && dynamicTextAnchors[0] == style::DynamicTextAnchorType::Auto ? AUTO_DYNAMIC_TEXT_ANCHORS : dynamicTextAnchors; + for (auto& symbolInstance : bucket.symbolInstances) { if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) { @@ -175,12 +225,13 @@ void Placement::placeLayerBucket( bool placeText = false; bool placeIcon = false; bool offscreen = true; + CollisionFeature placedCollisionFeature(symbolInstance.textCollisionFeature); - if (symbolInstance.placedTextIndex) { - PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedTextIndex); + if (!dynamicTextAnchors.size() && symbolInstance.placedRightTextIndex) { + PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedRightTextIndex); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol); - auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature, + auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature, 0, 0, posMatrix, textLabelPlaneMatrix, textPixelRatio, placedSymbol, scale, fontSize, bucket.layout.get(), @@ -188,13 +239,54 @@ void Placement::placeLayerBucket( showCollisionBoxes, avoidEdges, collisionGroup.second); placeText = placed.first; offscreen &= placed.second; + } else { + const float textBoxScale = symbolInstance.layoutTextSize / util::ONE_EM * bucket.tilePixelRatio; + std::map> justifications { + {style::TextJustifyType::Right, symbolInstance.placedRightTextIndex}, + {style::TextJustifyType::Center, symbolInstance.placedCenterTextIndex}, + {style::TextJustifyType::Left, symbolInstance.placedLeftTextIndex}, + }; + + for (const auto anchor : dynamicTextAnchorOrder) { + if (!placeText) { + if (symbolInstance.placedIconIndex && anchor == style::DynamicTextAnchorType::Center) continue; + // Auto is only valid as the first element in a DynamicTextAnchor which should be replaced with the + // AUTO_DYNAMIC_TEXT_ANCHORS value above. + if (anchor == style::DynamicTextAnchorType::Auto) continue; + style::TextJustifyType justification = getAnchorJustification(anchor); + const auto placedIndex = justifications[justification]; + if (!placedIndex) continue; + PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*placedIndex); + const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol); + AnchorAlignment anchorAlign = AnchorAlignment::getAnchorAlignment((style::SymbolAnchorType) anchor); + CollisionBox& box = symbolInstance.textCollisionFeature.boxes[0]; + + const auto shiftX = -anchorAlign.horizontalAlign * (box.x2 - box.x1); + const auto shiftY = -anchorAlign.verticalAlign * (box.y2 - box.y1); + + auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature, shiftX, shiftY, + posMatrix, textLabelPlaneMatrix, textPixelRatio, + placedSymbol, scale, fontSize, + bucket.layout.get(), + bucket.layout.get() == style::AlignmentType::Map, + showCollisionBoxes, avoidEdges, collisionGroup.second); + if (placed.first) { + placeText = placed.first; + offscreen &= placed.second; + placedSymbol.dynamicShift = {shiftX/textBoxScale, shiftY/textBoxScale}; + hideUnplacedJustifications(bucket, justification, justifications); + break; + } + } + } } + if (symbolInstance.placedIconIndex) { PlacedSymbol& placedSymbol = bucket.icon.placedSymbols.at(*symbolInstance.placedIconIndex); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol); - auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, + auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, 0, 0, posMatrix, iconLabelPlaneMatrix, textPixelRatio, placedSymbol, scale, fontSize, bucket.layout.get(), @@ -310,6 +402,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& const bool textAllowOverlap = bucket.layout.get(); const bool iconAllowOverlap = bucket.layout.get(); + const bool dynamicText = bucket.layout.get().size(); // If allow-overlap is true, we can show symbols before placement runs on them // But we have to wait for placement if we potentially depend on a paired icon/text @@ -319,18 +412,33 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || bucket.layout.get()), iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || bucket.layout.get()), true); + DynamicTextOffsets duplicateTextOffset(Point(-INFINITY, -INFINITY), Point(-INFINITY, -INFINITY), Point(-INFINITY, -INFINITY)); for (SymbolInstance& symbolInstance : bucket.symbolInstances) { bool isDuplicate = seenCrossTileIDs.count(symbolInstance.crossTileID) > 0; auto it = opacities.find(symbolInstance.crossTileID); + auto offsetIt = dynamicOffsets.find(symbolInstance.crossTileID); auto opacityState = defaultOpacityState; + auto dynamicOffsetState = duplicateTextOffset; if (isDuplicate) { opacityState = duplicateOpacityState; } else if (it != opacities.end()) { opacityState = it->second; } + if (!isDuplicate && dynamicText && offsetIt != dynamicOffsets.end()){ + dynamicOffsetState = offsetIt->second; + } + + if (dynamicText && offsetIt == dynamicOffsets.end()) { + auto rightPlaced = bucket.text.placedSymbols.at(*symbolInstance.placedRightTextIndex); + auto centerPlaced = bucket.text.placedSymbols.at(*symbolInstance.placedCenterTextIndex); + auto leftPlaced = bucket.text.placedSymbols.at(*symbolInstance.placedLeftTextIndex); + dynamicOffsetState = DynamicTextOffsets(rightPlaced.dynamicShift, centerPlaced.dynamicShift, leftPlaced.dynamicShift); + dynamicOffsets.emplace(symbolInstance.crossTileID, dynamicOffsetState); + } + if (it == opacities.end()) { opacities.emplace(symbolInstance.crossTileID, defaultOpacityState); } @@ -339,14 +447,36 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& if (symbolInstance.hasText) { auto opacityVertex = SymbolOpacityAttributes::vertex(opacityState.text.placed, opacityState.text.opacity); - for (size_t i = 0; i < symbolInstance.horizontalGlyphQuads.size() * 4; i++) { + for (size_t i = 0; i < symbolInstance.rightJustifiedGlyphQuads.size() * 4; i++) { bucket.text.opacityVertices.emplace_back(opacityVertex); } + if (symbolInstance.placedCenterTextIndex) { + for (size_t i = 0; i < symbolInstance.centerJustifiedGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } + } + if (symbolInstance.placedLeftTextIndex) { + for (size_t i = 0; i < symbolInstance.leftJustifiedGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } + } for (size_t i = 0; i < symbolInstance.verticalGlyphQuads.size() * 4; i++) { bucket.text.opacityVertices.emplace_back(opacityVertex); } - if (symbolInstance.placedTextIndex) { - bucket.text.placedSymbols[*symbolInstance.placedTextIndex].hidden = opacityState.isHidden(); + if (symbolInstance.placedRightTextIndex) { + PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedRightTextIndex]; + placed.hidden = opacityState.isHidden(); + placed.dynamicShift = dynamicOffsetState.right; + } + if (symbolInstance.placedCenterTextIndex) { + PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedCenterTextIndex]; + placed.hidden = opacityState.isHidden(); + placed.dynamicShift = dynamicOffsetState.center; + } + if (symbolInstance.placedLeftTextIndex) { + PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedLeftTextIndex]; + placed.hidden = opacityState.isHidden(); + placed.dynamicShift = dynamicOffsetState.left; } if (symbolInstance.placedVerticalTextIndex) { bucket.text.placedSymbols[*symbolInstance.placedVerticalTextIndex].hidden = opacityState.isHidden(); diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp index cc23110e54..ae8e432b8e 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include namespace mbgl { @@ -31,6 +33,14 @@ public: OpacityState text; }; +class DynamicTextOffsets { +public: + DynamicTextOffsets(Point right, Point center, Point left); + Point right; + Point center; + Point left; +}; + class JointPlacement { public: JointPlacement(bool text_, bool icon_, bool skipFade_) @@ -121,6 +131,7 @@ private: std::unordered_map placements; std::unordered_map opacities; + std::unordered_map dynamicOffsets; bool stale = false; diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp index 9d582f14d6..4d33ef1a0c 100644 --- a/src/mbgl/text/quads.cpp +++ b/src/mbgl/text/quads.cpp @@ -97,10 +97,9 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText, const GlyphPositions& positions) { const float textRotate = layout.get() * util::DEG2RAD; - const float oneEm = 24.0; std::array textOffset = layout.get(); - textOffset[0] *= oneEm; - textOffset[1] *= oneEm; + textOffset[0] *= util::ONE_EM; + textOffset[1] *= util::ONE_EM; SymbolQuads quads; diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 3a6335955b..eb7a0a848d 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -10,58 +10,8 @@ namespace mbgl { -struct AnchorAlignment { - AnchorAlignment(float horizontal_, float vertical_) - : horizontalAlign(horizontal_), verticalAlign(vertical_) { - } - - float horizontalAlign; - float verticalAlign; -}; - -AnchorAlignment getAnchorAlignment(style::SymbolAnchorType anchor) { - float horizontalAlign = 0.5; - float verticalAlign = 0.5; - - switch (anchor) { - case style::SymbolAnchorType::Top: - case style::SymbolAnchorType::Bottom: - case style::SymbolAnchorType::Center: - break; - case style::SymbolAnchorType::Right: - case style::SymbolAnchorType::TopRight: - case style::SymbolAnchorType::BottomRight: - horizontalAlign = 1; - break; - case style::SymbolAnchorType::Left: - case style::SymbolAnchorType::TopLeft: - case style::SymbolAnchorType::BottomLeft: - horizontalAlign = 0; - break; - } - - switch (anchor) { - case style::SymbolAnchorType::Left: - case style::SymbolAnchorType::Right: - case style::SymbolAnchorType::Center: - break; - case style::SymbolAnchorType::Bottom: - case style::SymbolAnchorType::BottomLeft: - case style::SymbolAnchorType::BottomRight: - verticalAlign = 1; - break; - case style::SymbolAnchorType::Top: - case style::SymbolAnchorType::TopLeft: - case style::SymbolAnchorType::TopRight: - verticalAlign = 0; - break; - } - - return AnchorAlignment(horizontalAlign, verticalAlign); -} - PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, const std::array& iconOffset, style::SymbolAnchorType iconAnchor, const float iconRotation) { - AnchorAlignment anchorAlign = getAnchorAlignment(iconAnchor); + AnchorAlignment anchorAlign = AnchorAlignment::getAnchorAlignment(iconAnchor); float dx = iconOffset[0]; float dy = iconOffset[1]; float x1 = dx - image.displaySize()[0] * anchorAlign.horizontalAlign; @@ -339,7 +289,7 @@ void shapeLines(Shaping& shaping, y += lineHeight * lineMaxScale; } - auto anchorAlign = getAnchorAlignment(textAnchor); + auto anchorAlign = AnchorAlignment::getAnchorAlignment(textAnchor); align(shaping, justify, anchorAlign.horizontalAlign, anchorAlign.verticalAlign, maxLineLength, lineHeight, lines.size()); diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index 50ac893098..3a688b1756 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -7,6 +7,56 @@ namespace mbgl { +struct AnchorAlignment { + AnchorAlignment(float horizontal_, float vertical_) + : horizontalAlign(horizontal_), verticalAlign(vertical_) { + } + + static AnchorAlignment getAnchorAlignment(style::SymbolAnchorType anchor) { + float horizontalAlign = 0.5; + float verticalAlign = 0.5; + + switch (anchor) { + case style::SymbolAnchorType::Top: + case style::SymbolAnchorType::Bottom: + case style::SymbolAnchorType::Center: + break; + case style::SymbolAnchorType::Right: + case style::SymbolAnchorType::TopRight: + case style::SymbolAnchorType::BottomRight: + horizontalAlign = 1; + break; + case style::SymbolAnchorType::Left: + case style::SymbolAnchorType::TopLeft: + case style::SymbolAnchorType::BottomLeft: + horizontalAlign = 0; + break; + } + + switch (anchor) { + case style::SymbolAnchorType::Left: + case style::SymbolAnchorType::Right: + case style::SymbolAnchorType::Center: + break; + case style::SymbolAnchorType::Bottom: + case style::SymbolAnchorType::BottomLeft: + case style::SymbolAnchorType::BottomRight: + verticalAlign = 1; + break; + case style::SymbolAnchorType::Top: + case style::SymbolAnchorType::TopLeft: + case style::SymbolAnchorType::TopRight: + verticalAlign = 0; + break; + } + + return AnchorAlignment(horizontalAlign, verticalAlign); + } + + float horizontalAlign; + float verticalAlign; +}; + class SymbolFeature; class BiDi; -- cgit v1.2.1