diff options
author | Molly Lloyd <molly@mapbox.com> | 2018-12-18 15:52:38 -0800 |
---|---|---|
committer | Molly Lloyd <molly@mapbox.com> | 2018-12-18 15:52:38 -0800 |
commit | e6bdd4235589dfbaf692d2fbfd8706736aa31060 (patch) | |
tree | 2689c3fab87ec9d3e7dd973fc247d6f3fef2cc2f /src/mbgl | |
parent | 7db9ce37e648e8145816f11dcdb3251f0ce81d74 (diff) | |
download | qtlocation-mapboxgl-e6bdd4235589dfbaf692d2fbfd8706736aa31060.tar.gz |
implement dynamic-text-anchor property
Diffstat (limited to 'src/mbgl')
-rw-r--r-- | src/mbgl/layout/symbol_instance.cpp | 33 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_instance.hpp | 13 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_layout.cpp | 83 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_layout.hpp | 2 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_projection.hpp | 8 | ||||
-rw-r--r-- | src/mbgl/renderer/buckets/symbol_bucket.cpp | 6 | ||||
-rw-r--r-- | src/mbgl/renderer/buckets/symbol_bucket.hpp | 5 | ||||
-rw-r--r-- | src/mbgl/renderer/layers/render_symbol_layer.cpp | 21 | ||||
-rw-r--r-- | src/mbgl/text/collision_index.cpp | 10 | ||||
-rw-r--r-- | src/mbgl/text/collision_index.hpp | 2 | ||||
-rw-r--r-- | src/mbgl/text/placement.cpp | 146 | ||||
-rw-r--r-- | src/mbgl/text/placement.hpp | 11 | ||||
-rw-r--r-- | src/mbgl/text/quads.cpp | 5 | ||||
-rw-r--r-- | src/mbgl/text/shaping.cpp | 54 | ||||
-rw-r--r-- | src/mbgl/text/shaping.hpp | 50 |
15 files changed, 327 insertions, 122 deletions
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<Shaping, Shaping>& shapedTextOrientations, + const std::tuple<Shaping, Shaping, Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> 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<Shaping, Shaping>& shapedTextOrientations, + // When dynamic-text-anchor is used, order is right, center, left, vertical. + // Otherwise order is horizontal, empty, empty, vertical. + const std::tuple<Shaping, Shaping, Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> 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<SymbolQuad> iconQuad; CollisionFeature textCollisionFeature; @@ -49,8 +53,11 @@ public: std::array<float, 2> textOffset; std::array<float, 2> iconOffset; std::u16string key; + float layoutTextSize; bool isDuplicate; - optional<size_t> placedTextIndex; + optional<size_t> placedRightTextIndex; + optional<size_t> placedCenterTextIndex; + optional<size_t> placedLeftTextIndex; optional<size_t> placedVerticalTextIndex; optional<size_t> 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<Shaping, Shaping> shapedTextOrientations; + std::tuple<Shaping, Shaping, Shaping, Shaping> shapedTextOrientations; optional<PositionedIcon> shapedIcon; - + const SymbolAnchorType textAnchor = layout.evaluate<TextAnchor>(zoom, feature); + const TextJustifyType textJustify = layout.evaluate<TextJustify>(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<SymbolPlacement>() == SymbolPlacementType::Point ? - layout.evaluate<TextMaxWidth>(zoom, feature) * oneEm : 0, - /* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm, - /* anchor */ layout.evaluate<TextAnchor>(zoom, feature), - /* justify */ layout.evaluate<TextJustify>(zoom, feature), - /* spacing: ems */ util::i18n::allowsLetterSpacing(feature.formattedText->rawText()) ? layout.evaluate<TextLetterSpacing>(zoom, feature) * oneEm : 0.0f, - /* translate */ Point<float>(layout.evaluate<TextOffset>(zoom, feature)[0] * oneEm, layout.evaluate<TextOffset>(zoom, feature)[1] * oneEm), - /* verticalHeight */ oneEm, + layout.evaluate<TextMaxWidth>(zoom, feature) * util::ONE_EM : 0, + /* lineHeight: ems */ layout.get<TextLineHeight>() * util::ONE_EM, + /* anchor */ anchor, + /* justify */ justify, + /* spacing: ems */ util::i18n::allowsLetterSpacing(feature.formattedText->rawText()) ? layout.evaluate<TextLetterSpacing>(zoom, feature) * util::ONE_EM : 0.0f, + /* translate */ Point<float>(layout.evaluate<TextOffset>(zoom, feature)[0] * util::ONE_EM, layout.evaluate<TextOffset>(zoom, feature)[1] * util::ONE_EM), + /* verticalHeight */ util::ONE_EM, /* writingMode */ writingMode, /* bidirectional algorithm object */ bidi, /* glyphs */ glyphMap); return result; }; + + if (layout.evaluate<DynamicTextAnchor>(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<Shaping, Shaping>& shapedTextOrientations, + const std::tuple<Shaping, Shaping, Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> 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> 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<FeatureIn const bool sortFeaturesByY = zOrderByViewport && (layout.get<TextAllowOverlap>() || layout.get<IconAllowOverlap>() || layout.get<TextIgnorePlacement>() || layout.get<IconIgnorePlacement>()); - auto bucket = std::make_shared<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances)); + auto bucket = std::make_shared<SymbolBucket>(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<FeatureIn if (hasText) { const Range<float> 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<FeatureIn } } } + // didn't have time to figure out why the order matters here, + // but if I place these before the vertical text the glyphs don't render - merp + symbolInstance.placedCenterTextIndex = placeSymbol(symbolInstance.centerJustifiedGlyphQuads); + symbolInstance.placedLeftTextIndex = placeSymbol(symbolInstance.leftJustifiedGlyphQuads); } if (hasIcon) { diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index 7045eebc22..2365e82aab 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -53,7 +53,7 @@ public: private: void addFeature(const size_t, const SymbolFeature&, - const std::pair<Shaping, Shaping>& shapedTextOrientations, + const std::tuple<Shaping, Shaping, Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> 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<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray); + void addDynamicAttributes( + const Point<float>& anchorPoint, + const float angle, + gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& 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<SymbolInstance>&& 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<float> anchorPoint_, uint16_t segment_, float lowerSize_, float upperSize_, std::array<float, 2> lineOffset_, WritingModeType writingModes_, GeometryCoordinates line_, std::vector<float> 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<float> anchorPoint; + Point<float> 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<SymbolInstance>&&); @@ -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<DynamicTextAnchor>().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<float> shiftedAnchor = Point<float>(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<bool,bool> 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<bool,bool> 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<bool,bool> 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<float> right_, Point<float> center_, Point<float> left_): + right(right_), + center(center_), + left(left_) {} + +const std::vector<style::DynamicTextAnchorType> AUTO_DYNAMIC_TEXT_ANCHORS = std::vector<style::DynamicTextAnchorType> { + 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<style::TextJustifyType, optional<size_t>>& 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<style::IconOptional>()); const bool alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || bucket.layout.get<style::TextOptional>()); - + const std::vector<style::DynamicTextAnchorType> dynamicTextAnchors = bucket.layout.get<style::DynamicTextAnchor>(); + const std::vector<style::DynamicTextAnchorType> 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<style::TextAllowOverlap>(), @@ -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<style::TextJustifyType, optional<size_t>> 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<style::TextAllowOverlap>(), + bucket.layout.get<style::TextPitchAlignment>() == 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<style::IconAllowOverlap>(), @@ -310,6 +402,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& const bool textAllowOverlap = bucket.layout.get<style::TextAllowOverlap>(); const bool iconAllowOverlap = bucket.layout.get<style::IconAllowOverlap>(); + const bool dynamicText = bucket.layout.get<style::DynamicTextAnchor>().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<uint32_t>& textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || bucket.layout.get<style::IconOptional>()), iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || bucket.layout.get<style::TextOptional>()), true); + DynamicTextOffsets duplicateTextOffset(Point<float>(-INFINITY, -INFINITY), Point<float>(-INFINITY, -INFINITY), Point<float>(-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<uint32_t>& 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 <unordered_map> #include <mbgl/util/chrono.hpp> #include <mbgl/text/collision_index.hpp> +#include <mbgl/text/shaping.hpp> #include <mbgl/layout/symbol_projection.hpp> #include <mbgl/style/transition_options.hpp> +#include <mbgl/util/geometry.hpp> #include <unordered_set> namespace mbgl { @@ -31,6 +33,14 @@ public: OpacityState text; }; +class DynamicTextOffsets { +public: + DynamicTextOffsets(Point<float> right, Point<float> center, Point<float> left); + Point<float> right; + Point<float> center; + Point<float> left; +}; + class JointPlacement { public: JointPlacement(bool text_, bool icon_, bool skipFade_) @@ -121,6 +131,7 @@ private: std::unordered_map<uint32_t, JointPlacement> placements; std::unordered_map<uint32_t, JointOpacityState> opacities; + std::unordered_map<uint32_t, DynamicTextOffsets> 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<TextRotate>() * util::DEG2RAD; - const float oneEm = 24.0; std::array<float, 2> textOffset = layout.get<TextOffset>(); - 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<float, 2>& 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; |