diff options
Diffstat (limited to 'src/mbgl/text')
-rw-r--r-- | src/mbgl/text/cross_tile_symbol_index.cpp | 19 | ||||
-rw-r--r-- | src/mbgl/text/cross_tile_symbol_index.hpp | 9 | ||||
-rw-r--r-- | src/mbgl/text/glyph.hpp | 21 | ||||
-rw-r--r-- | src/mbgl/text/glyph_manager.cpp | 7 | ||||
-rw-r--r-- | src/mbgl/text/placement.cpp | 304 | ||||
-rw-r--r-- | src/mbgl/text/placement.hpp | 13 | ||||
-rw-r--r-- | src/mbgl/text/quads.cpp | 48 | ||||
-rw-r--r-- | src/mbgl/text/quads.hpp | 4 | ||||
-rw-r--r-- | src/mbgl/text/shaping.cpp | 49 | ||||
-rw-r--r-- | src/mbgl/text/shaping.hpp | 9 |
10 files changed, 299 insertions, 184 deletions
diff --git a/src/mbgl/text/cross_tile_symbol_index.cpp b/src/mbgl/text/cross_tile_symbol_index.cpp index 43ed85d957..55ab2cc3c5 100644 --- a/src/mbgl/text/cross_tile_symbol_index.cpp +++ b/src/mbgl/text/cross_tile_symbol_index.cpp @@ -163,10 +163,10 @@ bool CrossTileSymbolLayerIndex::removeStaleBuckets(const std::unordered_set<uint CrossTileSymbolIndex::CrossTileSymbolIndex() = default; -bool CrossTileSymbolIndex::addLayer(const RenderLayer& layer, float lng) { +auto CrossTileSymbolIndex::addLayer(const RenderLayer& layer, float lng) -> AddLayerResult { auto& layerIndex = layerIndexes[layer.getID()]; - bool symbolBucketsChanged = false; + AddLayerResult result = AddLayerResult::NoChanges; std::unordered_set<uint32_t> currentBucketIDs; layerIndex.handleWrapJump(lng); @@ -174,16 +174,15 @@ bool CrossTileSymbolIndex::addLayer(const RenderLayer& layer, float lng) { for (const auto& item : layer.getPlacementData()) { const RenderTile& renderTile = item.tile; Bucket& bucket = item.bucket; - auto result = bucket.registerAtCrossTileIndex(layerIndex, renderTile.getOverscaledTileID(), maxCrossTileID); - assert(result.first != 0u); - symbolBucketsChanged = symbolBucketsChanged || result.second; - currentBucketIDs.insert(result.first); + auto pair = bucket.registerAtCrossTileIndex(layerIndex, renderTile.getOverscaledTileID(), maxCrossTileID); + assert(pair.first != 0u); + if (pair.second) result |= AddLayerResult::BucketsAdded; + currentBucketIDs.insert(pair.first); } - if (layerIndex.removeStaleBuckets(currentBucketIDs)) { - symbolBucketsChanged = true; - } - return symbolBucketsChanged; + if (layerIndex.removeStaleBuckets(currentBucketIDs)) result |= AddLayerResult::BucketsRemoved; + + return result; } void CrossTileSymbolIndex::pruneUnusedLayers(const std::set<std::string>& usedLayers) { diff --git a/src/mbgl/text/cross_tile_symbol_index.hpp b/src/mbgl/text/cross_tile_symbol_index.hpp index d905aeb569..4e32698b3e 100644 --- a/src/mbgl/text/cross_tile_symbol_index.hpp +++ b/src/mbgl/text/cross_tile_symbol_index.hpp @@ -1,6 +1,7 @@ #pragma once #include <mbgl/tile/tile_id.hpp> +#include <mbgl/util/bitmask_operations.hpp> #include <mbgl/util/geometry.hpp> #include <mbgl/util/constants.hpp> #include <mbgl/util/optional.hpp> @@ -58,7 +59,13 @@ class CrossTileSymbolIndex { public: CrossTileSymbolIndex(); - bool addLayer(const RenderLayer& layer, float lng); + enum class AddLayerResult : uint8_t { + NoChanges = 0, + BucketsAdded = 1 << 0, + BucketsRemoved = 1 << 1 + }; + + AddLayerResult addLayer(const RenderLayer& layer, float lng); void pruneUnusedLayers(const std::set<std::string>&); void reset(); diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp index 234f718975..ba9c521f77 100644 --- a/src/mbgl/text/glyph.hpp +++ b/src/mbgl/text/glyph.hpp @@ -1,6 +1,7 @@ #pragma once #include <mbgl/text/glyph_range.hpp> +#include <mbgl/util/bitmask_operations.hpp> #include <mbgl/util/font_stack.hpp> #include <mbgl/util/rect.hpp> #include <mbgl/util/traits.hpp> @@ -99,26 +100,6 @@ enum class WritingModeType : uint8_t { Vertical = 1 << 1, }; -MBGL_CONSTEXPR WritingModeType operator|(WritingModeType a, WritingModeType b) { - return WritingModeType(mbgl::underlying_type(a) | mbgl::underlying_type(b)); -} - -MBGL_CONSTEXPR WritingModeType& operator|=(WritingModeType& a, WritingModeType b) { - return (a = a | b); -} - -MBGL_CONSTEXPR bool operator&(WritingModeType lhs, WritingModeType rhs) { - return mbgl::underlying_type(lhs) & mbgl::underlying_type(rhs); -} - -MBGL_CONSTEXPR WritingModeType& operator&=(WritingModeType& lhs, WritingModeType rhs) { - return (lhs = WritingModeType(mbgl::underlying_type(lhs) & mbgl::underlying_type(rhs))); -} - -MBGL_CONSTEXPR WritingModeType operator~(WritingModeType value) { - return WritingModeType(~mbgl::underlying_type(value)); -} - using GlyphDependencies = std::map<FontStack, GlyphIDs>; using GlyphRangeDependencies = std::map<FontStack, std::unordered_set<GlyphRange>>; diff --git a/src/mbgl/text/glyph_manager.cpp b/src/mbgl/text/glyph_manager.cpp index daa142e38f..35ea1031d5 100644 --- a/src/mbgl/text/glyph_manager.cpp +++ b/src/mbgl/text/glyph_manager.cpp @@ -98,8 +98,11 @@ void GlyphManager::processResponse(const Response& res, const FontStack& fontSta } for (auto& glyph : glyphs) { - entry.glyphs.erase(glyph.id); - entry.glyphs.emplace(glyph.id, makeMutable<Glyph>(std::move(glyph))); + auto id = glyph.id; + if (!localGlyphRasterizer->canRasterizeGlyph(fontStack, id)) { + entry.glyphs.erase(id); + entry.glyphs.emplace(id, makeMutable<Glyph>(std::move(glyph))); + } } } diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index 27c4913c63..06777dea44 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -62,6 +62,7 @@ Placement::Placement(const TransformState& state_, MapMode mapMode_, style::Tran : collisionIndex(state_) , mapMode(mapMode_) , transitionOptions(std::move(transitionOptions_)) + , placementZoom(state_.getZoom()) , collisionGroups(crossSourceCollisions) , prevPlacement(std::move(prevPlacement_)) { @@ -85,15 +86,13 @@ void Placement::placeLayer(const RenderLayer& layer, const mat4& projMatrix, boo } namespace { -Point<float> calculateVariableLayoutOffset(style::SymbolAnchorType anchor, float width, float height, float radialOffset, float textBoxScale) { +Point<float> calculateVariableLayoutOffset(style::SymbolAnchorType anchor, float width, float height, std::array<float, 2> offset, float textBoxScale) { AnchorAlignment alignment = AnchorAlignment::getAnchorAlignment(anchor); float shiftX = -(alignment.horizontalAlign - 0.5f) * width; float shiftY = -(alignment.verticalAlign - 0.5f) * height; - Point<float> offset = SymbolLayout::evaluateRadialOffset(anchor, radialOffset); - return Point<float>( - shiftX + offset.x * textBoxScale, - shiftY + offset.y * textBoxScale - ); + auto variableOffset = SymbolLayout::evaluateVariableOffset(anchor, offset); + return { shiftX + variableOffset[0] * textBoxScale, + shiftY + variableOffset[1] * textBoxScale }; } } // namespace @@ -152,12 +151,12 @@ void Placement::placeBucket( // This is the reverse of our normal policy of "fade in on pan", but should look like any other // collision and hopefully not be too noticeable. // See https://github.com/mapbox/mapbox-gl-native/issues/12683 - const bool alwaysShowText = textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || layout.get<style::IconOptional>()); + const bool alwaysShowText = textAllowOverlap && (iconAllowOverlap || !(bucket.hasIconData() || bucket.hasSdfIconData()) || layout.get<style::IconOptional>()); const bool alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || layout.get<style::TextOptional>()); std::vector<style::TextVariableAnchorType> variableTextAnchors = layout.get<style::TextVariableAnchor>(); const bool rotateWithMap = layout.get<style::TextRotationAlignment>() == style::AlignmentType::Map; const bool pitchWithMap = layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map; - const bool hasCollisionCircleData = bucket.hasCollisionCircleData(); + const bool hasIconTextFit = layout.get<style::IconTextFit>() != style::IconTextFitType::None; const bool zOrderByViewportY = layout.get<style::SymbolZOrder>() == style::SymbolZOrderType::ViewportY; std::vector<ProjectedCollisionBox> textBoxes; @@ -179,7 +178,9 @@ void Placement::placeBucket( bool placeIcon = false; bool offscreen = true; std::pair<bool, bool> placed{ false, false }; - std::pair<bool, bool> placedVertical{ false, false }; + std::pair<bool, bool> placedVerticalText{ false, false }; + std::pair<bool, bool> placedVerticalIcon{ false, false }; + Point<float> shift{0.0f, 0.0f}; optional<size_t> horizontalTextIndex = symbolInstance.getDefaultHorizontalPlacedTextIndex(); if (horizontalTextIndex) { const PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*horizontalTextIndex); @@ -200,7 +201,7 @@ void Placement::placeBucket( assert(!bucket.placementModes.empty()); for (auto& placementMode : bucket.placementModes) { if (placementMode == style::TextWritingModeType::Vertical) { - placedVertical = placed = placeVerticalFn(); + placedVerticalText = placed = placeVerticalFn(); } else { placed = placeHorizontalFn(); } @@ -280,7 +281,7 @@ void Placement::placeBucket( for (size_t i = 0u; i < placementAttempts; ++i) { auto anchor = variableTextAnchors[i % anchorsSize]; const bool allowOverlap = (i >= anchorsSize); - Point<float> shift = calculateVariableLayoutOffset(anchor, width, height, symbolInstance.radialTextOffset, textBoxScale); + shift = calculateVariableLayoutOffset(anchor, width, height, symbolInstance.variableTextOffset, textBoxScale); if (rotateWithMap) { float angle = pitchWithMap ? state.getBearing() : -state.getBearing(); shift = util::rotate(shift, angle); @@ -311,7 +312,7 @@ void Placement::placeBucket( } variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{ - symbolInstance.radialTextOffset, + symbolInstance.variableTextOffset, width, height, anchor, @@ -359,21 +360,35 @@ void Placement::placeBucket( } if (symbolInstance.placedIconIndex) { - const PlacedSymbol& placedSymbol = bucket.icon.placedSymbols.at(*symbolInstance.placedIconIndex); + if (!hasIconTextFit || !placeText || variableTextAnchors.empty()) { + shift = {0.0f, 0.0f}; + } + + const auto& iconBuffer = symbolInstance.hasSdfIcon() ? bucket.sdfIcon : bucket.icon; + const PlacedSymbol& placedSymbol = iconBuffer.placedSymbols.at(*symbolInstance.placedIconIndex); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol); + const auto& placeIconFeature = [&] (const CollisionFeature& collisionFeature) { + return collisionIndex.placeFeature(collisionFeature, shift, + posMatrix, iconLabelPlaneMatrix, pixelRatio, + placedSymbol, scale, fontSize, + layout.get<style::IconAllowOverlap>(), + pitchWithMap, + params.showCollisionBoxes, avoidEdges, + collisionGroup.second, iconBoxes); + }; - auto placedIcon = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, {}, - posMatrix, iconLabelPlaneMatrix, pixelRatio, - placedSymbol, scale, fontSize, - layout.get<style::IconAllowOverlap>(), - pitchWithMap, - params.showCollisionBoxes, avoidEdges, collisionGroup.second, iconBoxes); + std::pair<bool, bool> placedIcon = {false, false}; + if (placedVerticalText.first && symbolInstance.verticalIconCollisionFeature) { + placedIcon = placedVerticalIcon = placeIconFeature(*symbolInstance.verticalIconCollisionFeature); + } else { + placedIcon = placeIconFeature(symbolInstance.iconCollisionFeature); + } placeIcon = placedIcon.first; offscreen &= placedIcon.second; } - const bool iconWithoutText = !symbolInstance.hasText || layout.get<style::TextOptional>(); - const bool textWithoutIcon = !symbolInstance.hasIcon || layout.get<style::IconOptional>(); + const bool iconWithoutText = !symbolInstance.hasText() || layout.get<style::TextOptional>(); + const bool textWithoutIcon = !symbolInstance.hasIcon() || layout.get<style::IconOptional>(); // combine placements for icon and text if (!iconWithoutText && !textWithoutIcon) { @@ -385,7 +400,7 @@ void Placement::placeBucket( } if (placeText) { - if (placedVertical.first && symbolInstance.verticalTextCollisionFeature) { + if (placedVerticalText.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); @@ -393,16 +408,21 @@ void Placement::placeBucket( } if (placeIcon) { - collisionIndex.insertFeature(symbolInstance.iconCollisionFeature, iconBoxes, layout.get<style::IconIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first); + if (placedVerticalIcon.first && symbolInstance.verticalIconCollisionFeature) { + collisionIndex.insertFeature(*symbolInstance.verticalIconCollisionFeature, iconBoxes, layout.get<style::IconIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first); + } else { + collisionIndex.insertFeature(symbolInstance.iconCollisionFeature, iconBoxes, layout.get<style::IconIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first); + } } - if (hasCollisionCircleData) { - if (symbolInstance.iconCollisionFeature.alongLine && !iconBoxes.empty()) { - collisionCircles[&symbolInstance.iconCollisionFeature] = iconBoxes; - } - if (symbolInstance.textCollisionFeature.alongLine && !textBoxes.empty()) { - collisionCircles[&symbolInstance.textCollisionFeature] = textBoxes; - } + const bool hasIconCollisionCircleData = bucket.hasIconCollisionCircleData(); + const bool hasTextCollisionCircleData = bucket.hasTextCollisionCircleData(); + + if (hasIconCollisionCircleData && symbolInstance.iconCollisionFeature.alongLine && !iconBoxes.empty()) { + collisionCircles[&symbolInstance.iconCollisionFeature] = iconBoxes; + } + if (hasTextCollisionCircleData && symbolInstance.textCollisionFeature.alongLine && !textBoxes.empty()) { + collisionCircles[&symbolInstance.textCollisionFeature] = textBoxes; } assert(symbolInstance.crossTileID != 0); @@ -438,17 +458,15 @@ void Placement::placeBucket( std::forward_as_tuple(bucket.bucketInstanceId, params.featureIndex, overscaledID)); } -void Placement::commit(TimePoint now) { +void Placement::commit(TimePoint now, const double zoom) { assert(prevPlacement); commitTime = now; bool placementChanged = false; - float increment = mapMode == MapMode::Continuous && - transitionOptions.enablePlacementTransitions && - transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) > Milliseconds(0) ? - std::chrono::duration<float>(commitTime - prevPlacement->commitTime) / transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) : - 1.0; + prevZoomAdjustment = prevPlacement->zoomAdjustment(zoom); + + float increment = prevPlacement->symbolFadeChange(commitTime); // add the opacities from the current placement, and copy their current values from the previous placement for (auto& jointPlacement : placements) { @@ -504,13 +522,13 @@ void Placement::updateLayerBuckets(const RenderLayer& layer, const TransformStat } namespace { -Point<float> calculateVariableRenderShift(style::SymbolAnchorType anchor, float width, float height, float radialOffset, float textBoxScale, float renderTextSize) { +Point<float> calculateVariableRenderShift(style::SymbolAnchorType anchor, float width, float height, std::array<float, 2> textOffset, float textBoxScale, float renderTextSize) { AnchorAlignment alignment = AnchorAlignment::getAnchorAlignment(anchor); float shiftX = -(alignment.horizontalAlign - 0.5f) * width; float shiftY = -(alignment.verticalAlign - 0.5f) * height; - Point<float> offset = SymbolLayout::evaluateRadialOffset(anchor, radialOffset); - return { (shiftX / textBoxScale + offset.x) * renderTextSize, - (shiftY / textBoxScale + offset.y) * renderTextSize }; + auto variablOffset = SymbolLayout::evaluateVariableOffset(anchor, textOffset); + return { (shiftX / textBoxScale + variablOffset[0]) * renderTextSize, + (shiftY / textBoxScale + variablOffset[1]) * renderTextSize }; } } // namespace @@ -518,16 +536,26 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor using namespace style; const auto& layout = *bucket.layout; const bool alongLine = layout.get<SymbolPlacement>() != SymbolPlacementType::Point; + const bool hasVariableAnchors = !layout.get<TextVariableAnchor>().empty() && bucket.hasTextData(); + const bool updateTextFitIcon = layout.get<IconTextFit>() != IconTextFitType::None && (bucket.allowVerticalPlacement || hasVariableAnchors) && (bucket.hasIconData() || bucket.hasSdfIconData()); bool result = false; if (alongLine) { - if (bucket.hasIconData() && layout.get<IconRotationAlignment>() == AlignmentType::Map) { + if (layout.get<IconRotationAlignment>() == AlignmentType::Map) { const bool pitchWithMap = layout.get<style::IconPitchAlignment>() == style::AlignmentType::Map; const bool keepUpright = layout.get<style::IconKeepUpright>(); - reprojectLineLabels(bucket.icon.dynamicVertices, bucket.icon.placedSymbols, - tile.matrix, pitchWithMap, true /*rotateWithMap*/, keepUpright, - tile, *bucket.iconSizeBinder, state); - result = true; + if (bucket.hasSdfIconData()) { + reprojectLineLabels(bucket.sdfIcon.dynamicVertices, bucket.sdfIcon.placedSymbols, + tile.matrix, pitchWithMap, true /*rotateWithMap*/, keepUpright, + tile, *bucket.iconSizeBinder, state); + result = true; + } + if (bucket.hasIconData()) { + reprojectLineLabels(bucket.icon.dynamicVertices, bucket.icon.placedSymbols, + tile.matrix, pitchWithMap, true /*rotateWithMap*/, keepUpright, + tile, *bucket.iconSizeBinder, state); + result = true; + } } if (bucket.hasTextData() && layout.get<TextRotationAlignment>() == AlignmentType::Map) { @@ -538,7 +566,7 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor tile, *bucket.textSizeBinder, state); result = true; } - } else if (!layout.get<TextVariableAnchor>().empty() && bucket.hasTextData()) { + } else if (hasVariableAnchors) { bucket.text.dynamicVertices.clear(); bucket.hasVariablePlacement = false; @@ -548,8 +576,10 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor const bool pitchWithMap = layout.get<TextPitchAlignment>() == AlignmentType::Map; const float pixelsToTileUnits = tile.id.pixelsToTileUnits(1.0, state.getZoom()); const auto labelPlaneMatrix = getLabelPlaneMatrix(tile.matrix, pitchWithMap, rotateWithMap, state, pixelsToTileUnits); + std::unordered_map<std::size_t, std::pair<std::size_t, Point<float>>> placedTextShifts; - for (const PlacedSymbol& symbol : bucket.text.placedSymbols) { + for (std::size_t i = 0; i < bucket.text.placedSymbols.size(); ++i) { + const PlacedSymbol& symbol = bucket.text.placedSymbols[i]; optional<VariableOffset> variableOffset; const bool skipOrientation = bucket.allowVerticalPlacement && !symbol.placedOrientation; if (!symbol.hidden && symbol.crossTileID != 0u && !skipOrientation) { @@ -578,7 +608,7 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor (*variableOffset).anchor, (*variableOffset).width, (*variableOffset).height, - (*variableOffset).radialOffset, + (*variableOffset).offset, (*variableOffset).textBoxScale, renderTextSize); @@ -598,24 +628,62 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor projectedAnchor.first.y + shift.y); } - for (std::size_t i = 0; i < symbol.glyphOffsets.size(); ++i) { + if (updateTextFitIcon && symbol.placedIconIndex) { + placedTextShifts.emplace(*symbol.placedIconIndex, + std::pair<std::size_t, Point<float>>{i, shiftedAnchor}); + } + + for (std::size_t j = 0; j < symbol.glyphOffsets.size(); ++j) { addDynamicAttributes(shiftedAnchor, symbol.angle, bucket.text.dynamicVertices); } } } + if (updateTextFitIcon && bucket.hasVariablePlacement) { + auto updateIcon = [&](SymbolBucket::Buffer& iconBuffer) { + iconBuffer.dynamicVertices.clear(); + for (std::size_t i = 0; i < iconBuffer.placedSymbols.size(); ++i) { + const PlacedSymbol& placedIcon = iconBuffer.placedSymbols[i]; + if (placedIcon.hidden || (!placedIcon.placedOrientation && bucket.allowVerticalPlacement)) { + hideGlyphs(placedIcon.glyphOffsets.size(), iconBuffer.dynamicVertices); + } else { + const auto& pair = placedTextShifts.find(i); + if (pair == placedTextShifts.end()) { + hideGlyphs(placedIcon.glyphOffsets.size(), iconBuffer.dynamicVertices); + } else { + for (std::size_t j = 0; j < placedIcon.glyphOffsets.size(); ++j) { + addDynamicAttributes(pair->second.second, placedIcon.angle, iconBuffer.dynamicVertices); + } + } + } + } + }; + updateIcon(bucket.icon); + updateIcon(bucket.sdfIcon); + } + 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); + const auto updateDynamicVertices = [](SymbolBucket::Buffer& buffer) { + buffer.dynamicVertices.clear(); + for (const PlacedSymbol& symbol : buffer.placedSymbols) { + if (symbol.hidden || !symbol.placedOrientation) { + hideGlyphs(symbol.glyphOffsets.size(), buffer.dynamicVertices); + } else { + for (std::size_t j = 0; j < symbol.glyphOffsets.size(); ++j) { + addDynamicAttributes(symbol.anchorPoint, symbol.angle, buffer.dynamicVertices); + } } } + }; + + updateDynamicVertices(bucket.text); + // When text box is rotated, icon-text-fit icon must be rotated as well. + if (updateTextFitIcon) { + updateDynamicVertices(bucket.icon); + updateDynamicVertices(bucket.sdfIcon); } + result = true; } @@ -625,23 +693,27 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState& state, std::set<uint32_t>& seenCrossTileIDs) { if (bucket.hasTextData()) bucket.text.opacityVertices.clear(); if (bucket.hasIconData()) bucket.icon.opacityVertices.clear(); - if (bucket.hasCollisionBoxData()) bucket.collisionBox->dynamicVertices.clear(); - if (bucket.hasCollisionCircleData()) bucket.collisionCircle->dynamicVertices.clear(); + if (bucket.hasSdfIconData()) bucket.sdfIcon.opacityVertices.clear(); + if (bucket.hasIconCollisionBoxData()) bucket.iconCollisionBox->dynamicVertices.clear(); + if (bucket.hasIconCollisionCircleData()) bucket.iconCollisionCircle->dynamicVertices.clear(); + if (bucket.hasTextCollisionBoxData()) bucket.textCollisionBox->dynamicVertices.clear(); + if (bucket.hasTextCollisionCircleData()) bucket.textCollisionCircle->dynamicVertices.clear(); - JointOpacityState duplicateOpacityState(false, false, true); + const JointOpacityState duplicateOpacityState(false, false, true); const bool textAllowOverlap = bucket.layout->get<style::TextAllowOverlap>(); const bool iconAllowOverlap = bucket.layout->get<style::IconAllowOverlap>(); const bool variablePlacement = !bucket.layout->get<style::TextVariableAnchor>().empty(); const bool rotateWithMap = bucket.layout->get<style::TextRotationAlignment>() == style::AlignmentType::Map; const bool pitchWithMap = bucket.layout->get<style::TextPitchAlignment>() == style::AlignmentType::Map; + const bool hasIconTextFit = bucket.layout->get<style::IconTextFit>() != style::IconTextFitType::None; // 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 // with allow-overlap: false. // See https://github.com/mapbox/mapbox-gl-native/issues/12483 - JointOpacityState defaultOpacityState( - textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || bucket.layout->get<style::IconOptional>()), + const JointOpacityState defaultOpacityState( + textAllowOverlap && (iconAllowOverlap || !(bucket.hasIconData() || bucket.hasSdfIconData()) || bucket.layout->get<style::IconOptional>()), iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || bucket.layout->get<style::TextOptional>()), true); @@ -656,13 +728,9 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState opacityState = it->second; } - if (it == opacities.end()) { - opacities.emplace(symbolInstance.crossTileID, defaultOpacityState); - } - seenCrossTileIDs.insert(symbolInstance.crossTileID); - if (symbolInstance.hasText) { + if (symbolInstance.hasText()) { size_t textOpacityVerticesSize = 0u; const auto& opacityVertex = SymbolSDFTextProgram::opacityVertex(opacityState.text.placed, opacityState.text.opacity); if (symbolInstance.placedRightTextIndex) { @@ -701,27 +769,34 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState markUsedJustification(bucket, prevOffset->second.anchor, symbolInstance, previousOrientation); } } - if (symbolInstance.hasIcon) { + if (symbolInstance.hasIcon()) { const auto& opacityVertex = SymbolIconProgram::opacityVertex(opacityState.icon.placed, opacityState.icon.opacity); - bucket.icon.opacityVertices.extend(4, opacityVertex); + auto& iconBuffer = symbolInstance.hasSdfIcon() ? bucket.sdfIcon : bucket.icon; + if (symbolInstance.placedIconIndex) { - bucket.icon.placedSymbols[*symbolInstance.placedIconIndex].hidden = opacityState.isHidden(); + iconBuffer.opacityVertices.extend(4, opacityVertex); + iconBuffer.placedSymbols[*symbolInstance.placedIconIndex].hidden = opacityState.isHidden(); + } + + if (symbolInstance.placedVerticalIconIndex) { + iconBuffer.opacityVertices.extend(4, opacityVertex); + iconBuffer.placedSymbols[*symbolInstance.placedVerticalIconIndex].hidden = opacityState.isHidden(); } } - auto updateCollisionBox = [&](const auto& feature, const bool placed) { + auto updateIconCollisionBox = [&](const auto& feature, const bool placed, const Point<float>& shift) { if (feature.alongLine) { return; } - const auto& dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, false, {}); - bucket.collisionBox->dynamicVertices.extend(feature.boxes.size() * 4, dynamicVertex); + const auto& dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, false, shift); + bucket.iconCollisionBox->dynamicVertices.extend(feature.boxes.size() * 4, dynamicVertex); }; - auto updateCollisionTextBox = [this, &bucket, &symbolInstance, &state, variablePlacement, rotateWithMap, pitchWithMap](const auto& feature, const bool placed) { + auto updateTextCollisionBox = [this, &bucket, &symbolInstance, &state, variablePlacement, rotateWithMap, pitchWithMap](const auto& feature, const bool placed) { + Point<float> shift{0.0f, 0.0f}; if (feature.alongLine) { - return; + return shift; } - Point<float> shift; bool used = true; if (variablePlacement) { auto foundOffset = variableOffsets.find(symbolInstance.crossTileID); @@ -734,7 +809,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState shift = calculateVariableLayoutOffset(variableOffset.anchor, variableOffset.width, variableOffset.height, - variableOffset.radialOffset, + variableOffset.offset, variableOffset.textBoxScale); if (rotateWithMap) { shift = util::rotate(shift, pitchWithMap ? state.getBearing() : -state.getBearing()); @@ -747,10 +822,11 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState } } const auto& dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !used, shift); - bucket.collisionBox->dynamicVertices.extend(feature.boxes.size() * 4, dynamicVertex); + bucket.textCollisionBox->dynamicVertices.extend(feature.boxes.size() * 4, dynamicVertex); + return shift; }; - auto updateCollisionCircles = [&](const auto& feature, const bool placed) { + auto updateCollisionCircles = [&](const auto& feature, const bool placed, bool isText) { if (!feature.alongLine) { return; } @@ -758,26 +834,36 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState if (circles != collisionCircles.end()) { for (const auto& circle : circles->second) { const auto& dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !circle.isCircle(), {}); - bucket.collisionCircle->dynamicVertices.extend(4, dynamicVertex); + isText ? bucket.textCollisionCircle->dynamicVertices.extend(4, dynamicVertex): + bucket.iconCollisionCircle->dynamicVertices.extend(4, dynamicVertex); } } else { // This feature was not placed, because it was not loaded or from a fading tile. Apply default values. static const auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, false /*not used*/, {}); - bucket.collisionCircle->dynamicVertices.extend(4 * feature.boxes.size(), dynamicVertex); + isText ? bucket.textCollisionCircle->dynamicVertices.extend(4 * feature.boxes.size(), dynamicVertex): + bucket.iconCollisionCircle->dynamicVertices.extend(4 * feature.boxes.size(), dynamicVertex); } }; - - if (bucket.hasCollisionBoxData()) { - // TODO: update collision box opacity based on selected text variant (horizontal | vertical). - updateCollisionTextBox(symbolInstance.textCollisionFeature, opacityState.text.placed); + Point<float> textShift{0.0f, 0.0f}; + Point<float> verticalTextShift{0.0f, 0.0f}; + if (bucket.hasTextCollisionBoxData()) { + textShift = updateTextCollisionBox(symbolInstance.textCollisionFeature, opacityState.text.placed); if (bucket.allowVerticalPlacement && symbolInstance.verticalTextCollisionFeature) { - updateCollisionTextBox(*symbolInstance.verticalTextCollisionFeature, opacityState.text.placed); + verticalTextShift = updateTextCollisionBox(*symbolInstance.verticalTextCollisionFeature, opacityState.text.placed); } - updateCollisionBox(symbolInstance.iconCollisionFeature, opacityState.icon.placed); } - if (bucket.hasCollisionCircleData()) { - updateCollisionCircles(symbolInstance.textCollisionFeature, opacityState.text.placed); - updateCollisionCircles(symbolInstance.iconCollisionFeature, opacityState.icon.placed); + if (bucket.hasIconCollisionBoxData()) { + updateIconCollisionBox(symbolInstance.iconCollisionFeature, opacityState.icon.placed, hasIconTextFit ? textShift : Point<float>{0.0f, 0.0f}); + if (bucket.allowVerticalPlacement && symbolInstance.verticalIconCollisionFeature) { + updateIconCollisionBox(*symbolInstance.verticalIconCollisionFeature, opacityState.text.placed, hasIconTextFit ? verticalTextShift : Point<float>{0.0f, 0.0f}); + } + } + + if (bucket.hasIconCollisionCircleData()) { + updateCollisionCircles(symbolInstance.iconCollisionFeature, opacityState.icon.placed, false); + } + if (bucket.hasTextCollisionCircleData()) { + updateCollisionCircles(symbolInstance.textCollisionFeature, opacityState.text.placed, true); } } @@ -850,17 +936,45 @@ void Placement::markUsedOrientation(SymbolBucket& bucket, style::TextWritingMode if (symbolInstance.placedVerticalTextIndex) { bucket.text.placedSymbols.at(*symbolInstance.placedVerticalTextIndex).placedOrientation = vertical; } + + auto& iconBuffer = symbolInstance.hasSdfIcon() ? bucket.sdfIcon : bucket.icon; + if (symbolInstance.placedIconIndex) { + iconBuffer.placedSymbols.at(*symbolInstance.placedIconIndex).placedOrientation = horizontal; + } + + if (symbolInstance.placedVerticalIconIndex) { + iconBuffer.placedSymbols.at(*symbolInstance.placedVerticalIconIndex).placedOrientation = vertical; + } } float Placement::symbolFadeChange(TimePoint now) const { if (mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions && transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) > Milliseconds(0)) { - return std::chrono::duration<float>(now - commitTime) / transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION); + return std::chrono::duration<float>(now - commitTime) / transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) + prevZoomAdjustment; } else { return 1.0; } } +float Placement::zoomAdjustment(const float zoom) const { + // When zooming out labels can overlap each other quickly. This + // adjustment is used to reduce the fade duration for symbols while zooming out quickly. + // It is also used to reduce the interval between placement calculations. Reducing the + // interval between placements means collisions are discovered and eliminated sooner. + return std::max(0.0, (placementZoom - zoom) / 1.5); +} + +Duration Placement::getUpdatePeriod(const float zoom) const { + // Even if transitionOptions.duration is set to a value < 300ms, we still wait for this default transition duration + // before attempting another placement operation. + const auto fadeDuration = std::max(util::DEFAULT_TRANSITION_DURATION, transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION)); + const auto adjustedDuration = std::chrono::duration_cast<Duration>(fadeDuration * (1.0 - zoomAdjustment(zoom))); + if (maximumUpdatePeriod) { + return std::min(*maximumUpdatePeriod, adjustedDuration); + } + return adjustedDuration; +} + bool Placement::hasTransitions(TimePoint now) const { if (mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions) { return stale || std::chrono::duration<float>(now - fadeStartTime) < transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION); @@ -869,12 +983,14 @@ bool Placement::hasTransitions(TimePoint now) const { } } -bool Placement::stillRecent(TimePoint now) const { - // Even if transitionOptions.duration is set to a value < 300ms, we still wait for this default transition duration - // before attempting another placement operation. +bool Placement::stillRecent(TimePoint now, const float zoom) const { return mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions && - commitTime + std::max(util::DEFAULT_TRANSITION_DURATION, transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION)) > now; + commitTime + getUpdatePeriod(zoom) > now; +} + +void Placement::setMaximumUpdatePeriod(Duration duration) { + maximumUpdatePeriod = duration; } void Placement::setStale() { diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp index 722a4a0926..b5405cbcd7 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -34,7 +34,7 @@ public: class VariableOffset { public: - float radialOffset; + std::array<float, 2> offset; float width; float height; style::TextVariableAnchorType anchor; @@ -102,15 +102,15 @@ class Placement { public: Placement(const TransformState&, MapMode, style::TransitionOptions, const bool crossSourceCollisions, std::unique_ptr<Placement> prevPlacementOrNull = nullptr); void placeLayer(const RenderLayer&, const mat4&, bool showCollisionBoxes); - void commit(TimePoint); + void commit(TimePoint, const double zoom); void updateLayerBuckets(const RenderLayer&, const TransformState&, bool updateOpacities); float symbolFadeChange(TimePoint now) const; bool hasTransitions(TimePoint now) const; const CollisionIndex& getCollisionIndex() const; - bool stillRecent(TimePoint now) const; - void setRecent(TimePoint now); + bool stillRecent(TimePoint now, const float zoom) const; + void setMaximumUpdatePeriod(Duration); void setStale(); const RetainedQueryData& getQueryData(uint32_t bucketInstanceId) const; @@ -125,6 +125,8 @@ private: void updateBucketOpacities(SymbolBucket&, const TransformState&, std::set<uint32_t>&); void markUsedJustification(SymbolBucket&, style::TextVariableAnchorType, const SymbolInstance&, style::TextWritingModeType orientation); void markUsedOrientation(SymbolBucket&, style::TextWritingModeType, const SymbolInstance&); + float zoomAdjustment(const float zoom) const; + Duration getUpdatePeriod(const float zoom) const; CollisionIndex collisionIndex; @@ -133,6 +135,8 @@ private: TimePoint fadeStartTime; TimePoint commitTime; + float placementZoom; + float prevZoomAdjustment = 0; std::unordered_map<uint32_t, JointPlacement> placements; std::unordered_map<uint32_t, JointOpacityState> opacities; @@ -144,6 +148,7 @@ private: std::unordered_map<uint32_t, RetainedQueryData> retainedQueryData; CollisionGroups collisionGroups; std::unique_ptr<Placement> prevPlacement; + optional<Duration> maximumUpdatePeriod; // Used for debug purposes. std::unordered_map<const CollisionFeature*, std::vector<ProjectedCollisionBox>> collisionCircles; diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp index b08c2bc0ba..281c5d99de 100644 --- a/src/mbgl/text/quads.cpp +++ b/src/mbgl/text/quads.cpp @@ -14,9 +14,7 @@ namespace mbgl { using namespace style; SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, - const SymbolLayoutProperties::Evaluated& layout, - const float layoutTextSize, - const Shaping& shapedText) { + WritingModeType writingMode) { const ImagePosition& image = shapedIcon.image(); // If you have a 10px icon that isn't perfectly aligned to the pixel grid it will cover 11 actual @@ -28,43 +26,11 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, float left = shapedIcon.left() - border / image.pixelRatio; float bottom = shapedIcon.bottom() + border / image.pixelRatio; float right = shapedIcon.right() + border / image.pixelRatio; - Point<float> tl; - Point<float> tr; - Point<float> br; - Point<float> bl; - - if (layout.get<IconTextFit>() != IconTextFitType::None && shapedText) { - auto iconWidth = right - left; - auto iconHeight = bottom - top; - auto size = layoutTextSize / 24.0f; - auto textLeft = shapedText.left * size; - auto textRight = shapedText.right * size; - auto textTop = shapedText.top * size; - auto textBottom = shapedText.bottom * size; - auto textWidth = textRight - textLeft; - auto textHeight = textBottom - textTop; - auto padT = layout.get<IconTextFitPadding>()[0]; - auto padR = layout.get<IconTextFitPadding>()[1]; - auto padB = layout.get<IconTextFitPadding>()[2]; - auto padL = layout.get<IconTextFitPadding>()[3]; - auto offsetY = layout.get<IconTextFit>() == IconTextFitType::Width ? (textHeight - iconHeight) * 0.5 : 0; - auto offsetX = layout.get<IconTextFit>() == IconTextFitType::Height ? (textWidth - iconWidth) * 0.5 : 0; - auto width = layout.get<IconTextFit>() == IconTextFitType::Width || layout.get<IconTextFit>() == IconTextFitType::Both ? textWidth : iconWidth; - auto height = layout.get<IconTextFit>() == IconTextFitType::Height || layout.get<IconTextFit>() == IconTextFitType::Both ? textHeight : iconHeight; - left = textLeft + offsetX - padL; - top = textTop + offsetY - padT; - right = textLeft + offsetX + padR + width; - bottom = textTop + offsetY + padB + height; - tl = {left, top}; - tr = {right, top}; - br = {right, bottom}; - bl = {left, bottom}; - } else { - tl = {left, top}; - tr = {right, top}; - br = {right, bottom}; - bl = {left, bottom}; - } + + Point<float> tl{left, top}; + Point<float> tr{right, top}; + Point<float> br{right, bottom}; + Point<float> bl{left, bottom}; const float angle = shapedIcon.angle(); @@ -88,7 +54,7 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, static_cast<uint16_t>(image.textureRect.h + border * 2) }; - return SymbolQuad { tl, tr, bl, br, textureRect, shapedText.writingMode, { 0.0f, 0.0f } }; + return SymbolQuad { tl, tr, bl, br, textureRect, writingMode, { 0.0f, 0.0f } }; } SymbolQuads getGlyphQuads(const Shaping& shapedText, diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp index 1ec68189af..145fd2b153 100644 --- a/src/mbgl/text/quads.hpp +++ b/src/mbgl/text/quads.hpp @@ -44,9 +44,7 @@ public: using SymbolQuads = std::vector<SymbolQuad>; SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, - const style::SymbolLayoutProperties::Evaluated&, - const float layoutTextSize, - const Shaping& shapedText); + WritingModeType writingMode); SymbolQuads getGlyphQuads(const Shaping& shapedText, const std::array<float, 2> textOffset, diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 7bf0e14f80..d6d9a3d34e 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -68,16 +68,49 @@ style::TextJustifyType getAnchorJustification(style::SymbolAnchorType anchor) { } } -PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, const std::array<float, 2>& iconOffset, style::SymbolAnchorType iconAnchor, const float iconRotation) { +PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, + const std::array<float, 2>& iconOffset, + style::SymbolAnchorType iconAnchor, + const float iconRotation) { AnchorAlignment anchorAlign = AnchorAlignment::getAnchorAlignment(iconAnchor); float dx = iconOffset[0]; float dy = iconOffset[1]; - float x1 = dx - image.displaySize()[0] * anchorAlign.horizontalAlign; - float x2 = x1 + image.displaySize()[0]; - float y1 = dy - image.displaySize()[1] * anchorAlign.verticalAlign; - float y2 = y1 + image.displaySize()[1]; + float left = dx - image.displaySize()[0] * anchorAlign.horizontalAlign; + float right = left + image.displaySize()[0]; + float top = dy - image.displaySize()[1] * anchorAlign.verticalAlign; + float bottom = top + image.displaySize()[1]; - return PositionedIcon { image, y1, y2, x1, x2, iconRotation }; + return PositionedIcon { image, top, bottom, left, right, iconRotation }; +} + +void PositionedIcon::fitIconToText(const style::SymbolLayoutProperties::Evaluated& layout, + const Shaping& shapedText, + float layoutTextSize) { + using namespace style; + assert(layout.get<IconTextFit>() != IconTextFitType::None); + if (shapedText) { + auto iconWidth = _right - _left; + auto iconHeight = _bottom - _top; + auto size = layoutTextSize / 24.0f; + auto textLeft = shapedText.left * size; + auto textRight = shapedText.right * size; + auto textTop = shapedText.top * size; + auto textBottom = shapedText.bottom * size; + auto textWidth = textRight - textLeft; + auto textHeight = textBottom - textTop; + auto padT = layout.get<IconTextFitPadding>()[0]; + auto padR = layout.get<IconTextFitPadding>()[1]; + auto padB = layout.get<IconTextFitPadding>()[2]; + auto padL = layout.get<IconTextFitPadding>()[3]; + auto offsetY = layout.get<IconTextFit>() == IconTextFitType::Width ? (textHeight - iconHeight) * 0.5 : 0; + auto offsetX = layout.get<IconTextFit>() == IconTextFitType::Height ? (textWidth - iconWidth) * 0.5 : 0; + auto width = layout.get<IconTextFit>() == IconTextFitType::Width || layout.get<IconTextFit>() == IconTextFitType::Both ? textWidth : iconWidth; + auto height = layout.get<IconTextFit>() == IconTextFitType::Height || layout.get<IconTextFit>() == IconTextFitType::Both ? textHeight : iconHeight; + _left = textLeft + offsetX - padL; + _top = textTop + offsetY - padT; + _right = textLeft + offsetX + padR + width; + _bottom = textTop + offsetY + padB + height; + } } void align(Shaping& shaping, @@ -380,7 +413,7 @@ const Shaping getShaping(const TaggedString& formattedString, const style::SymbolAnchorType textAnchor, const style::TextJustifyType textJustify, const float spacing, - const Point<float>& translate, + const std::array<float, 2>& translate, const WritingModeType writingMode, BiDi& bidi, const GlyphMap& glyphs, @@ -399,7 +432,7 @@ const Shaping getShaping(const TaggedString& formattedString, reorderedLines.emplace_back(line, formattedString.getSections()); } } - Shaping shaping(translate.x, translate.y, writingMode, reorderedLines.size()); + Shaping shaping(translate[0], translate[1], writingMode, reorderedLines.size()); shapeLines(shaping, reorderedLines, spacing, lineHeight, textAnchor, textJustify, writingMode, glyphs, allowVerticalPlacement); diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index f3a01e3caf..0e2f5515fe 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -4,6 +4,7 @@ #include <mbgl/text/tagged_string.hpp> #include <mbgl/renderer/image_atlas.hpp> #include <mbgl/style/types.hpp> +#include <mbgl/style/layers/symbol_layer_properties.hpp> namespace mbgl { @@ -52,6 +53,12 @@ public: style::SymbolAnchorType iconAnchor, const float iconRotation); + // Updates shaped icon's bounds based on shaped text's bounds and provided + // layout properties. + void fitIconToText(const style::SymbolLayoutProperties::Evaluated& layout, + const Shaping& shapedText, + float layoutTextSize); + const ImagePosition& image() const { return _image; } float top() const { return _top; } float bottom() const { return _bottom; } @@ -66,7 +73,7 @@ const Shaping getShaping(const TaggedString& string, style::SymbolAnchorType textAnchor, style::TextJustifyType textJustify, float spacing, - const Point<float>& translate, + const std::array<float, 2>& translate, const WritingModeType, BiDi& bidi, const GlyphMap& glyphs, |