From ff074c579a6412484dfe69421dd97c323bc89eee Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Wed, 20 Mar 2019 18:08:01 +0200 Subject: [core] Introduce variable text placement for point labels - Layout part --- include/mbgl/util/constants.hpp | 3 + src/mbgl/layout/symbol_instance.cpp | 68 +++++++--- src/mbgl/layout/symbol_instance.hpp | 27 +++- src/mbgl/layout/symbol_layout.cpp | 203 +++++++++++++++++++++++----- src/mbgl/layout/symbol_layout.hpp | 7 +- src/mbgl/layout/symbol_projection.cpp | 6 +- src/mbgl/renderer/buckets/symbol_bucket.cpp | 20 ++- src/mbgl/renderer/buckets/symbol_bucket.hpp | 6 +- src/mbgl/text/glyph.hpp | 8 +- src/mbgl/text/placement.cpp | 11 +- src/mbgl/text/placement.hpp | 12 +- src/mbgl/text/quads.cpp | 6 +- src/mbgl/text/quads.hpp | 1 + src/mbgl/text/shaping.cpp | 71 +++++----- src/mbgl/text/shaping.hpp | 15 +- test/gl/bucket.test.cpp | 2 +- test/text/cross_tile_symbol_index.test.cpp | 24 ++-- 17 files changed, 361 insertions(+), 129 deletions(-) diff --git a/include/mbgl/util/constants.hpp b/include/mbgl/util/constants.hpp index b39b3a83e9..f7799a953f 100644 --- a/include/mbgl/util/constants.hpp +++ b/include/mbgl/util/constants.hpp @@ -41,6 +41,9 @@ constexpr float MIN_ZOOM_F = MIN_ZOOM; constexpr float MAX_ZOOM_F = MAX_ZOOM; constexpr uint8_t DEFAULT_MAX_ZOOM = 22; +// ONE_EM constant used to go between "em" units used in style spec and "points" used internally for layout. +constexpr float ONE_EM = 24.0f; + constexpr uint8_t DEFAULT_PREFETCH_ZOOM_DELTA = 4; constexpr uint64_t DEFAULT_MAX_CACHE_SIZE = 50 * 1024 * 1024; diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index 139a42113c..fddaaf7c2d 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -5,13 +5,25 @@ namespace mbgl { using namespace style; +namespace { + +const Shaping& getAnyShaping(const ShapedTextOrientations& shapedTextOrientations) { + if (shapedTextOrientations.right) return shapedTextOrientations.right; + if (shapedTextOrientations.center) return shapedTextOrientations.center; + if (shapedTextOrientations.left) return shapedTextOrientations.left; + if (shapedTextOrientations.vertical) return shapedTextOrientations.vertical; + return shapedTextOrientations.horizontal; +} + +} // namespace + SymbolInstance::SymbolInstance(Anchor& anchor_, GeometryCoordinates line_, - const std::pair& shapedTextOrientations, + const ShapedTextOrientations& shapedTextOrientations, optional shapedIcon, const SymbolLayoutProperties::Evaluated& layout, const float layoutTextSize, - const float textBoxScale, + const float textBoxScale_, const float textPadding, const SymbolPlacementType textPlacement, const std::array textOffset_, @@ -24,43 +36,59 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, const std::size_t dataFeatureIndex_, const std::u16string& key_, const float overscaling, - const float rotate) : + const float rotate, + float radialTextOffset_) : anchor(anchor_), line(line_), hasText(false), 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), + // As a collision approximation, we can use either the vertical or any of the horizontal versions of the feature + textCollisionFeature(line_, anchor, getAnyShaping(shapedTextOrientations), textBoxScale_, textPadding, textPlacement, indexedFeature, overscaling, rotate), iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, indexedFeature, rotate), + writingModes(WritingModeType::None), layoutFeatureIndex(layoutFeatureIndex_), dataFeatureIndex(dataFeatureIndex_), textOffset(textOffset_), iconOffset(iconOffset_), - key(key_) { + key(key_), + textBoxScale(textBoxScale_), + radialTextOffset(radialTextOffset_) { // Create the quads used for rendering the icon and glyphs. if (shapedIcon) { - iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first); + iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.horizontal); + } + + if (shapedTextOrientations.right) { + writingModes |= WritingModeType::Horizontal; + rightJustifiedGlyphQuads = getGlyphQuads(shapedTextOrientations.right, textOffset, layout, textPlacement, positions); } - if (shapedTextOrientations.first) { - horizontalGlyphQuads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions); + + if (shapedTextOrientations.center) { + writingModes |= WritingModeType::Horizontal; + centerJustifiedGlyphQuads = getGlyphQuads(shapedTextOrientations.center, textOffset, layout, textPlacement, positions); } - if (shapedTextOrientations.second) { - verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions); + + if (shapedTextOrientations.left) { + writingModes |= WritingModeType::Horizontal; + leftJustifiedGlyphQuads = getGlyphQuads(shapedTextOrientations.left, textOffset, 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; - if (shapedTextOrientations.first && shapedTextOrientations.second) { - writingModes = WritingModeType::Horizontal | WritingModeType::Vertical; - } else if (shapedTextOrientations.first) { - writingModes = WritingModeType::Horizontal; - } else if (shapedTextOrientations.second) { - writingModes = WritingModeType::Vertical; - } else { - writingModes = WritingModeType::None; + if (shapedTextOrientations.vertical) { + writingModes |= WritingModeType::Vertical; + verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.vertical, textOffset, layout, textPlacement, positions); } + + // 'hasText' depends on finding at least one glyph in the shaping that's also in the GlyphPositionMap + hasText = !rightJustifiedGlyphQuads.empty() || !centerJustifiedGlyphQuads.empty() || !leftJustifiedGlyphQuads.empty() || !verticalGlyphQuads.empty(); } +optional SymbolInstance::getDefaultHorizontalPlacedTextIndex() const { + if (placedRightTextIndex) return placedRightTextIndex; + if (placedCenterTextIndex) return placedCenterTextIndex; + if (placedLeftTextIndex) return placedLeftTextIndex; + return nullopt; +} } // namespace mbgl diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index 6148d7fe88..44d81ae1e5 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -11,11 +11,20 @@ namespace mbgl { class Anchor; class IndexedSubfeature; +struct ShapedTextOrientations { + Shaping horizontal; + Shaping vertical; + // The following are used with variable text placement on. + Shaping& right = horizontal; + Shaping center; + Shaping left; +}; + class SymbolInstance { public: SymbolInstance(Anchor& anchor, GeometryCoordinates line, - const std::pair& shapedTextOrientations, + const ShapedTextOrientations& shapedTextOrientations, optional shapedIcon, const style::SymbolLayoutProperties::Evaluated&, const float layoutTextSize, @@ -32,14 +41,20 @@ public: const std::size_t dataFeatureIndex, const std::u16string& key, const float overscaling, - const float rotate); + const float rotate, + float radialTextOffset); + + optional getDefaultHorizontalPlacedTextIndex() const; Anchor anchor; GeometryCoordinates line; bool hasText; bool hasIcon; - SymbolQuads horizontalGlyphQuads; + SymbolQuads rightJustifiedGlyphQuads; + SymbolQuads centerJustifiedGlyphQuads; + SymbolQuads leftJustifiedGlyphQuads; SymbolQuads verticalGlyphQuads; + optional iconQuad; CollisionFeature textCollisionFeature; CollisionFeature iconCollisionFeature; @@ -50,9 +65,13 @@ public: std::array iconOffset; std::u16string key; bool isDuplicate; - optional placedTextIndex; + optional placedRightTextIndex; + optional placedCenterTextIndex; + optional placedLeftTextIndex; optional placedVerticalTextIndex; optional placedIconIndex; + float textBoxScale; + float radialTextOffset; uint32_t crossTileID = 0; }; diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index c40a705d7f..52ec2aa843 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -174,44 +174,171 @@ bool SymbolLayout::hasSymbolInstances() const { return !symbolInstances.empty(); } +namespace { + +// The radial offset is to the edge of the text box +// In the horizontal direction, the edge of the text box is where glyphs start +// But in the vertical direction, the glyphs appear to "start" at the baseline +// We don't actually load baseline data, but we assume an offset of ONE_EM - 17 +// (see "yOffset" in shaping.js) +const float baselineOffset = 7.0f; + +// We don't care which shaping we get because this is used for collision purposes +// and all the justifications have the same collision box. +const Shaping& getDefaultHorizontalShaping(const ShapedTextOrientations& shapedTextOrientations) { + if (shapedTextOrientations.right) return shapedTextOrientations.right; + if (shapedTextOrientations.center) return shapedTextOrientations.center; + if (shapedTextOrientations.left) return shapedTextOrientations.left; + return shapedTextOrientations.horizontal; +} + +Shaping& shapingForTextJustifyType(ShapedTextOrientations& shapedTextOrientations, style::TextJustifyType type) { + switch(type) { + case style::TextJustifyType::Right: return shapedTextOrientations.right; + case style::TextJustifyType::Left: return shapedTextOrientations.left; + case style::TextJustifyType::Center: return shapedTextOrientations.center; + default: + assert(false); + return shapedTextOrientations.horizontal; + } +} + +} // namespace + +// static +Point SymbolLayout::evaluateRadialOffset(SymbolAnchorType anchor, float radialOffset) { + Point result{}; + // solve for r where r^2 + r^2 = radialOffset^2 + const float sqrt2 = 1.41421356237f; + const float hypotenuse = radialOffset / sqrt2; + + switch (anchor) { + case SymbolAnchorType::TopRight: + case SymbolAnchorType::TopLeft: + result.y = hypotenuse - baselineOffset; + break; + case SymbolAnchorType::BottomRight: + case SymbolAnchorType::BottomLeft: + result.y = -hypotenuse + baselineOffset; + break; + case SymbolAnchorType::Bottom: + result.y = -radialOffset + baselineOffset; + break; + case SymbolAnchorType::Top: + result.y = radialOffset - baselineOffset; + break; + default: + break; + } + + switch (anchor) { + case SymbolAnchorType::TopRight: + case SymbolAnchorType::BottomRight: + result.x = -hypotenuse; + break; + case SymbolAnchorType::TopLeft: + case SymbolAnchorType::BottomLeft: + result.x = hypotenuse; + break; + case SymbolAnchorType::Left: + result.x = radialOffset; + break; + case SymbolAnchorType::Right: + result.x = -radialOffset; + break; + default: + break; + } + + return result; +} + void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions, const ImageMap& imageMap, const ImagePositions& imagePositions) { - const bool textAlongLine = layout.get() == AlignmentType::Map && - layout.get() != SymbolPlacementType::Point; + const bool isPointPlacement = layout.get() == SymbolPlacementType::Point; + const bool textAlongLine = layout.get() == AlignmentType::Map && !isPointPlacement; for (auto it = features.begin(); it != features.end(); ++it) { auto& feature = *it; if (feature.geometry.empty()) continue; - std::pair shapedTextOrientations; + ShapedTextOrientations shapedTextOrientations; optional shapedIcon; + Point textOffset; // if feature has text, shape the text if (feature.formattedText) { - auto applyShaping = [&] (const TaggedString& formattedText, WritingModeType writingMode) { - const float oneEm = 24.0f; + const float lineHeight = layout.get() * util::ONE_EM; + const float spacing = util::i18n::allowsLetterSpacing(feature.formattedText->rawText()) ? layout.evaluate(zoom, feature) * util::ONE_EM : 0.0f; + + auto applyShaping = [&] (const TaggedString& formattedText, WritingModeType writingMode, SymbolAnchorType textAnchor, TextJustifyType textJustify) { 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, + /* maxWidth: ems */ isPointPlacement ? layout.evaluate(zoom, feature) * util::ONE_EM : 0.0f, + /* ems */ lineHeight, + textAnchor, + textJustify, + /* ems */ spacing, + /* translate */ textOffset, /* writingMode */ writingMode, /* bidirectional algorithm object */ bidi, /* glyphs */ glyphMap); return result; }; + const std::vector variableTextAnchor = layout.evaluate(zoom, feature); + const float radialOffset = layout.evaluate(zoom, feature); + const SymbolAnchorType textAnchor = layout.evaluate(zoom, feature); + if (variableTextAnchor.empty()) { + // Layers with variable anchors use the `text-radial-offset` property and the [x, y] offset vector + // is calculated at placement time instead of layout time + if (radialOffset > 0.0f) { + // The style spec says don't use `text-offset` and `text-radial-offset` together + // but doesn't actually specify what happens if you use both. We go with the radial offset. + textOffset = evaluateRadialOffset(textAnchor, radialOffset * util::ONE_EM); + } else { + textOffset = { layout.evaluate(zoom, feature)[0] * util::ONE_EM, + layout.evaluate(zoom, feature)[1] * util::ONE_EM}; + } + } + TextJustifyType textJustify = textAlongLine ? TextJustifyType::Center : layout.evaluate(zoom, feature); + // If this layer uses text-variable-anchor, generate shapings for all justification possibilities. + if (!textAlongLine && !variableTextAnchor.empty()) { + std::vector justifications; + if (textJustify != TextJustifyType::Auto) { + justifications.push_back(textJustify); + } else { + for (auto anchor : variableTextAnchor) { + justifications.push_back(getAnchorJustification(anchor)); + } + } - shapedTextOrientations.first = applyShaping(*feature.formattedText, WritingModeType::Horizontal); + for (TextJustifyType justification: justifications) { + Shaping& shapingForJustification = shapingForTextJustifyType(shapedTextOrientations, justification); + if (shapingForJustification) { + continue; + } + // If using text-variable-anchor for the layer, we use a center anchor for all shapings and apply + // the offsets for the anchor in the placement step. + Shaping shaping = applyShaping(*feature.formattedText, WritingModeType::Horizontal, SymbolAnchorType::Center, justification); + if (shaping) { + shapingForJustification = std::move(shaping); + } + // TODO: use 'singleLine' optimization. + } + } else { + if (textJustify == TextJustifyType::Auto) { + textJustify = getAnchorJustification(textAnchor); + } + Shaping shaping = applyShaping(*feature.formattedText, WritingModeType::Horizontal, textAnchor, textJustify); + if (shaping) { + shapedTextOrientations.horizontal = std::move(shaping); + } - if (util::i18n::allowsVerticalWritingMode(feature.formattedText->rawText()) && textAlongLine) { - feature.formattedText->verticalizePunctuation(); - shapedTextOrientations.second = applyShaping(*feature.formattedText, WritingModeType::Vertical); + if (util::i18n::allowsVerticalWritingMode(feature.formattedText->rawText()) && textAlongLine) { + feature.formattedText->verticalizePunctuation(); + shapedTextOrientations.vertical = applyShaping(*feature.formattedText, WritingModeType::Vertical, textAnchor, textJustify); + } } } @@ -236,8 +363,8 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions } // if either shapedText or icon position is present, add the feature - if (shapedTextOrientations.first || shapedIcon) { - addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositions); + if (getDefaultHorizontalShaping(shapedTextOrientations) || shapedIcon) { + addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositions, textOffset); } feature.geometry.clear(); @@ -248,15 +375,17 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, const SymbolFeature& feature, - const std::pair& shapedTextOrientations, + const ShapedTextOrientations& shapedTextOrientations, optional shapedIcon, - const GlyphPositions& glyphPositions) { + const GlyphPositions& glyphPositions, + Point offset) { const float minScale = 0.5f; const float glyphSize = 24.0f; const float layoutTextSize = layout.evaluate(zoom + 1, feature); const float layoutIconSize = layout.evaluate(zoom + 1, feature); - const std::array textOffset = layout.evaluate(zoom, feature); + const std::array textOffset = {{ offset.x, offset.y }}; + const std::array iconOffset = layout.evaluate(zoom, feature); // To reduce the number of labels that jump around when zooming we need @@ -274,6 +403,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, const float iconPadding = layout.get() * tilePixelRatio; const float textMaxAngle = layout.get() * util::DEG2RAD; const float rotation = layout.evaluate(zoom, feature); + const float radialTextOffset = layout.evaluate(zoom, feature) * util::ONE_EM; const SymbolPlacementType textPlacement = layout.get() != AlignmentType::Map ? SymbolPlacementType::Point : layout.get(); @@ -296,7 +426,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, textBoxScale, textPadding, textPlacement, textOffset, iconBoxScale, iconPadding, iconOffset, glyphPositions, indexedFeature, layoutFeatureIndex, feature.index, - feature.formattedText ? feature.formattedText->rawText() : std::u16string(), overscaling, rotation); + feature.formattedText ? feature.formattedText->rawText() : std::u16string(), overscaling, rotation, radialTextOffset); } }; @@ -308,8 +438,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, + (shapedTextOrientations.vertical ?: getDefaultHorizontalShaping(shapedTextOrientations)).left, + (shapedTextOrientations.vertical ?: getDefaultHorizontalShaping(shapedTextOrientations)).right, (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), glyphSize, @@ -329,8 +459,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, + (shapedTextOrientations.vertical ?: getDefaultHorizontalShaping(shapedTextOrientations)).left, + (shapedTextOrientations.vertical ?: getDefaultHorizontalShaping(shapedTextOrientations)).right, (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), glyphSize, @@ -414,7 +544,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, bucketLeaderID, std::move(symbolInstances), tilePixelRatio); for (SymbolInstance &symbolInstance : bucket->symbolInstances) { @@ -426,13 +556,22 @@ void SymbolLayout::createBucket(const ImagePositions&, std::unique_ptr lastAddedSection; + if (!symbolInstance.rightJustifiedGlyphQuads.empty()) { + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedRightTextIndex, symbolInstance.rightJustifiedGlyphQuads, lastAddedSection); + } + if (!symbolInstance.centerJustifiedGlyphQuads.empty()) { + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedCenterTextIndex, symbolInstance.centerJustifiedGlyphQuads, lastAddedSection); + } + if (!symbolInstance.leftJustifiedGlyphQuads.empty()) { + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedLeftTextIndex, symbolInstance.leftJustifiedGlyphQuads, lastAddedSection); + } if (symbolInstance.writingModes & WritingModeType::Vertical) { - index = addSymbolGlyphQuads(*bucket, symbolInstance, feature, WritingModeType::Vertical, symbolInstance.placedVerticalTextIndex, symbolInstance.verticalGlyphQuads, index); + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, WritingModeType::Vertical, symbolInstance.placedVerticalTextIndex, symbolInstance.verticalGlyphQuads, lastAddedSection); } - - updatePaintPropertiesForSection(*bucket, feature, index); + assert(lastAddedSection); // True, as hasText == true; + updatePaintPropertiesForSection(*bucket, feature, *lastAddedSection); } if (hasIcon) { diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index 53c66d31fe..d88c79c552 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -46,12 +46,15 @@ public: const std::string bucketLeaderID; std::vector symbolInstances; + static Point evaluateRadialOffset(style::SymbolAnchorType anchor, float radialOffset); + private: void addFeature(const size_t, const SymbolFeature&, - const std::pair& shapedTextOrientations, + const ShapedTextOrientations& shapedTextOrientations, optional shapedIcon, - const GlyphPositions&); + const GlyphPositions&, + Point textOffset); bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&); std::map> compareText; diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp index dff2a569ac..b7858f8deb 100644 --- a/src/mbgl/layout/symbol_projection.cpp +++ b/src/mbgl/layout/symbol_projection.cpp @@ -291,9 +291,9 @@ namespace mbgl { gfx::VertexVector>& dynamicVertexArray, const Point& projectedAnchorPoint, const float aspectRatio) { - const float fontScale = fontSize / 24.0; - const float lineOffsetX = symbol.lineOffset[0] * fontSize; - const float lineOffsetY = symbol.lineOffset[1] * fontSize; + const float fontScale = fontSize / util::ONE_EM; + const float lineOffsetX = symbol.lineOffset[0] * fontScale; + const float lineOffsetY = symbol.lineOffset[1] * fontScale; std::vector placedGlyphs; if (symbol.glyphOffsets.size() > 1) { diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index 9220235f1d..38342b44ee 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -17,7 +17,8 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo bool iconsNeedLinear_, bool sortFeaturesByY_, const std::string bucketName_, - const std::vector&& symbolInstances_) + const std::vector&& symbolInstances_, + float tilePixelRatio_) : layout(std::move(layout_)), sdfIcons(sdfIcons_), iconsNeedLinear(iconsNeedLinear_ || iconSize.isDataDriven() || !iconSize.isZoomConstant()), @@ -25,7 +26,8 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo bucketLeaderID(std::move(bucketName_)), symbolInstances(std::move(symbolInstances_)), textSizeBinder(SymbolSizeBinder::create(zoom, textSize, TextSize::defaultValue())), - iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())) { + iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())), + tilePixelRatio(tilePixelRatio_) { for (const auto& pair : paintProperties_) { auto layerPaintProperties = pair.second; @@ -218,12 +220,22 @@ 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.placedCenterTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedCenterTextIndex]); + } + + if (symbolInstance.placedLeftTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedLeftTextIndex]); + } + if (symbolInstance.placedVerticalTextIndex) { addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedVerticalTextIndex]); } + if (symbolInstance.placedIconIndex) { addPlacedSymbol(icon.triangles, icon.placedSymbols[*symbolInstance.placedIconIndex]); } diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index fafa2592fe..f8ffc1a8eb 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -35,6 +35,8 @@ public: std::vector glyphOffsets; bool hidden; size_t vertexStartIndex; + // The crossTileID is only filled/used on the foreground for variable text anchors + uint32_t crossTileID = 0u; }; class SymbolBucket final : public Bucket { @@ -48,7 +50,8 @@ public: bool iconsNeedLinear, bool sortFeaturesByY, const std::string bucketLeaderID, - const std::vector&&); + const std::vector&&, + const float tilePixelRatio); ~SymbolBucket() override; void upload(gfx::Context&) override; @@ -130,6 +133,7 @@ public: optional indexBuffer; } collisionCircle; + const float tilePixelRatio; uint32_t bucketInstanceId = 0; bool justReloaded = false; optional hasFormatSectionOverrides_; diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp index c97b242c10..7d6415c057 100644 --- a/src/mbgl/text/glyph.hpp +++ b/src/mbgl/text/glyph.hpp @@ -78,15 +78,17 @@ enum class WritingModeType : uint8_t; class Shaping { public: - explicit Shaping() = default; - explicit Shaping(float x, float y, WritingModeType writingMode_) - : top(y), bottom(y), left(x), right(x), writingMode(writingMode_) {} + Shaping() = default; + explicit Shaping(float x, float y, WritingModeType writingMode_, std::size_t lineCount_) + : top(y), bottom(y), left(x), right(x), writingMode(writingMode_), lineCount(lineCount_) {} std::vector positionedGlyphs; float top = 0; float bottom = 0; float left = 0; float right = 0; WritingModeType writingMode; + std::size_t lineCount = 0u; + std::string text = {}; explicit operator bool() const { return !positionedGlyphs.empty(); } }; diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index 4cc12b0980..bb7e2d1a6c 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -176,8 +176,8 @@ void Placement::placeLayerBucket( bool placeIcon = false; bool offscreen = true; - if (symbolInstance.placedTextIndex) { - PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedTextIndex); + if (symbolInstance.placedRightTextIndex) { + PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedRightTextIndex); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol); auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature, @@ -339,14 +339,15 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& if (symbolInstance.hasText) { auto opacityVertex = SymbolSDFTextProgram::opacityVertex(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); } 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(); } 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..32310f723e 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -31,6 +31,16 @@ public: OpacityState text; }; +class VariableOffset { +public: + float radialOffset; + float width; + float height; + style::TextVariableAnchorType anchor; + float textBoxScale; + optional prevAnchor; +}; + class JointPlacement { public: JointPlacement(bool text_, bool icon_, bool skipFade_) @@ -45,7 +55,7 @@ public: // visible right away. const bool skipFade; }; - + struct RetainedQueryData { uint32_t bucketInstanceId; std::shared_ptr featureIndex; diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp index ec0045caad..6be5d8c01e 100644 --- a/src/mbgl/text/quads.cpp +++ b/src/mbgl/text/quads.cpp @@ -92,16 +92,12 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, } SymbolQuads getGlyphQuads(const Shaping& shapedText, + const std::array textOffset, const SymbolLayoutProperties::Evaluated& layout, const style::SymbolPlacementType placement, 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; - SymbolQuads quads; for (const PositionedGlyph &positionedGlyph: shapedText.positionedGlyphs) { diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp index f41a4fec66..0bb892e4d1 100644 --- a/src/mbgl/text/quads.hpp +++ b/src/mbgl/text/quads.hpp @@ -49,6 +49,7 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, const Shaping& shapedText); SymbolQuads getGlyphQuads(const Shaping& shapedText, + const std::array textOffset, const style::SymbolLayoutProperties::Evaluated&, style::SymbolPlacementType placement, const GlyphPositions& positions); diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 02dbf146e1..348c2ddccc 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -10,58 +11,61 @@ 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; +// static +AnchorAlignment AnchorAlignment::getAnchorAlignment(style::SymbolAnchorType anchor) { + AnchorAlignment result(0.5f, 0.5f); 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; + result.horizontalAlign = 1.0f; break; case style::SymbolAnchorType::Left: case style::SymbolAnchorType::TopLeft: case style::SymbolAnchorType::BottomLeft: - horizontalAlign = 0; + result.horizontalAlign = 0.0f; break; + default: + 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; + result.verticalAlign = 1.0f; break; case style::SymbolAnchorType::Top: case style::SymbolAnchorType::TopLeft: case style::SymbolAnchorType::TopRight: - verticalAlign = 0; + result.verticalAlign = 0.0f; + break; + default: break; } - return AnchorAlignment(horizontalAlign, verticalAlign); + return result; +} + +style::TextJustifyType getAnchorJustification(style::SymbolAnchorType anchor) { + switch (anchor) { + case style::SymbolAnchorType::Right: + case style::SymbolAnchorType::TopRight: + case style::SymbolAnchorType::BottomRight: + return style::TextJustifyType::Right; + case style::SymbolAnchorType::Left: + case style::SymbolAnchorType::TopLeft: + case style::SymbolAnchorType::BottomLeft: + return style::TextJustifyType::Left; + default: + return style::TextJustifyType::Center; + } } 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; @@ -269,7 +273,6 @@ void shapeLines(Shaping& shaping, const float lineHeight, const style::SymbolAnchorType textAnchor, const style::TextJustifyType textJustify, - const float verticalHeight, const WritingModeType writingMode, const GlyphMap& glyphMap) { @@ -314,7 +317,7 @@ void shapeLines(Shaping& shaping, // We don't know the baseline, but since we're laying out // at 24 points, we can calculate how much it will move when // we scale up or down. - const double baselineOffset = (lineMaxScale - section.scale) * 24; + const double baselineOffset = (lineMaxScale - section.scale) * util::ONE_EM; const Glyph& glyph = **it->second; @@ -323,7 +326,7 @@ void shapeLines(Shaping& shaping, x += glyph.metrics.advance * section.scale + spacing; } else { shaping.positionedGlyphs.emplace_back(codePoint, x, baselineOffset, true, section.fontStackHash, section.scale, sectionIndex); - x += verticalHeight * section.scale + spacing; + x += util::ONE_EM * section.scale + spacing; } } @@ -340,7 +343,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()); @@ -360,12 +363,10 @@ const Shaping getShaping(const TaggedString& formattedString, const style::TextJustifyType textJustify, const float spacing, const Point& translate, - const float verticalHeight, + //const float verticalHeight, const WritingModeType writingMode, BiDi& bidi, - const GlyphMap& glyphs) { - Shaping shaping(translate.x, translate.y, writingMode); - + const GlyphMap& glyphs) { std::vector reorderedLines; if (formattedString.sectionCount() == 1) { auto untaggedLines = bidi.processText(formattedString.rawText(), @@ -380,9 +381,9 @@ const Shaping getShaping(const TaggedString& formattedString, reorderedLines.emplace_back(line, formattedString.getSections()); } } - + Shaping shaping(translate.x, translate.y, writingMode, reorderedLines.size()); shapeLines(shaping, reorderedLines, spacing, lineHeight, textAnchor, - textJustify, verticalHeight, writingMode, glyphs); + textJustify, writingMode, glyphs); return shaping; } diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index 50ac893098..766b1ce233 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -7,6 +7,20 @@ namespace mbgl { +struct AnchorAlignment { + AnchorAlignment(float horizontal, float vertical) + : horizontalAlign(horizontal), verticalAlign(vertical) { + } + + static AnchorAlignment getAnchorAlignment(style::SymbolAnchorType anchor); + + float horizontalAlign; + float verticalAlign; +}; + +// Choose the justification that matches the direction of the TextAnchor +style::TextJustifyType getAnchorJustification(style::SymbolAnchorType anchor); + class SymbolFeature; class BiDi; @@ -53,7 +67,6 @@ const Shaping getShaping(const TaggedString& string, style::TextJustifyType textJustify, float spacing, const Point& translate, - float verticalHeight, const WritingModeType, BiDi& bidi, const GlyphMap& glyphs); diff --git a/test/gl/bucket.test.cpp b/test/gl/bucket.test.cpp index bc9f6aac5a..2be224382a 100644 --- a/test/gl/bucket.test.cpp +++ b/test/gl/bucket.test.cpp @@ -116,7 +116,7 @@ TEST(Buckets, SymbolBucket) { std::vector symbolInstances; gl::Context context; - SymbolBucket bucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances) }; + SymbolBucket bucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances), 1.0f }; ASSERT_FALSE(bucket.hasIconData()); ASSERT_FALSE(bucket.hasTextData()); ASSERT_FALSE(bucket.hasCollisionBoxData()); diff --git a/test/text/cross_tile_symbol_index.test.cpp b/test/text/cross_tile_symbol_index.test.cpp index 67f82413b2..f121781766 100644 --- a/test/text/cross_tile_symbol_index.test.cpp +++ b/test/text/cross_tile_symbol_index.test.cpp @@ -7,11 +7,11 @@ using namespace mbgl; SymbolInstance makeSymbolInstance(float x, float y, std::u16string key) { GeometryCoordinates line; GlyphPositions positions; - const std::pair shaping(Shaping{}, Shaping{}); + const ShapedTextOrientations shaping{}; style::SymbolLayoutProperties::Evaluated layout_; IndexedSubfeature subfeature(0, "", "", 0); Anchor anchor(x, y, 0, 0); - return {anchor, line, shaping, {}, layout_, 0, 0, 0, style::SymbolPlacementType::Point, {{0, 0}}, 0, 0, {{0, 0}}, positions, subfeature, 0, 0, key, 0, 0}; + return SymbolInstance(anchor, line, shaping, {}, layout_, 0, 0, 0, style::SymbolPlacementType::Point, {{0, 0}}, 0, 0, {{0, 0}}, positions, subfeature, 0, 0, key, 0, 0, 0.0f); } @@ -31,7 +31,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { std::vector mainInstances; mainInstances.push_back(makeSymbolInstance(1000, 1000, u"Detroit")); mainInstances.push_back(makeSymbolInstance(2000, 2000, u"Toronto")); - SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances) }; + SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances), 1.0f }; mainBucket.bucketInstanceId = ++maxBucketInstanceId; index.addBucket(mainID, mainBucket, maxCrossTileID); @@ -46,7 +46,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { childInstances.push_back(makeSymbolInstance(2000, 2000, u"Windsor")); childInstances.push_back(makeSymbolInstance(3000, 3000, u"Toronto")); childInstances.push_back(makeSymbolInstance(4001, 4001, u"Toronto")); - SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances) }; + SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances), 1.0f }; childBucket.bucketInstanceId = ++maxBucketInstanceId; index.addBucket(childID, childBucket, maxCrossTileID); @@ -62,7 +62,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { OverscaledTileID parentID(5, 0, 5, 4, 4); std::vector parentInstances; parentInstances.push_back(makeSymbolInstance(500, 500, u"Detroit")); - SymbolBucket parentBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(parentInstances) }; + SymbolBucket parentBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(parentInstances), 1.0f }; parentBucket.bucketInstanceId = ++maxBucketInstanceId; index.addBucket(parentID, parentBucket, maxCrossTileID); @@ -78,7 +78,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { std::vector grandchildInstances; grandchildInstances.push_back(makeSymbolInstance(4000, 4000, u"Detroit")); grandchildInstances.push_back(makeSymbolInstance(4000, 4000, u"Windsor")); - SymbolBucket grandchildBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(grandchildInstances) }; + SymbolBucket grandchildBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(grandchildInstances), 1.0f }; grandchildBucket.bucketInstanceId = ++maxBucketInstanceId; index.addBucket(grandchildID, grandchildBucket, maxCrossTileID); @@ -104,13 +104,13 @@ TEST(CrossTileSymbolLayerIndex, resetIDs) { OverscaledTileID mainID(6, 0, 6, 8, 8); std::vector mainInstances; mainInstances.push_back(makeSymbolInstance(1000, 1000, u"Detroit")); - SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances) }; + SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances), 1.0f }; mainBucket.bucketInstanceId = ++maxBucketInstanceId; OverscaledTileID childID(7, 0, 7, 16, 16); std::vector childInstances; childInstances.push_back(makeSymbolInstance(2000, 2000, u"Detroit")); - SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances) }; + SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances), 1.0f }; childBucket.bucketInstanceId = ++maxBucketInstanceId; // assigns a new id @@ -145,7 +145,7 @@ TEST(CrossTileSymbolLayerIndex, noDuplicatesWithinZoomLevel) { std::vector mainInstances; mainInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // A mainInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // B - SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances) }; + SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances), 1.0f }; mainBucket.bucketInstanceId = ++maxBucketInstanceId; OverscaledTileID childID(7, 0, 7, 16, 16); @@ -153,7 +153,7 @@ TEST(CrossTileSymbolLayerIndex, noDuplicatesWithinZoomLevel) { childInstances.push_back(makeSymbolInstance(2000, 2000, u"")); // A' childInstances.push_back(makeSymbolInstance(2000, 2000, u"")); // B' childInstances.push_back(makeSymbolInstance(2000, 2000, u"")); // C' - SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances) }; + SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances), 1.0f }; childBucket.bucketInstanceId = ++maxBucketInstanceId; // assigns new ids @@ -183,14 +183,14 @@ TEST(CrossTileSymbolLayerIndex, bucketReplacement) { std::vector firstInstances; firstInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // A firstInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // B - SymbolBucket firstBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(firstInstances) }; + SymbolBucket firstBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(firstInstances), 1.0f }; firstBucket.bucketInstanceId = ++maxBucketInstanceId; std::vector secondInstances; secondInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // A' secondInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // B' secondInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // C' - SymbolBucket secondBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(secondInstances) }; + SymbolBucket secondBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(secondInstances), 1.0f }; secondBucket.bucketInstanceId = ++maxBucketInstanceId; // assigns new ids -- cgit v1.2.1