summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mbgl/layout/symbol_feature.hpp1
-rw-r--r--src/mbgl/layout/symbol_instance.cpp17
-rw-r--r--src/mbgl/layout/symbol_instance.hpp7
-rw-r--r--src/mbgl/layout/symbol_layout.cpp57
-rw-r--r--src/mbgl/layout/symbol_layout.hpp2
-rw-r--r--src/mbgl/layout/symbol_projection.cpp6
-rw-r--r--src/mbgl/renderer/buckets/symbol_bucket.cpp8
-rw-r--r--src/mbgl/renderer/buckets/symbol_bucket.hpp11
-rw-r--r--src/mbgl/text/glyph.hpp2
-rw-r--r--src/mbgl/text/placement.cpp277
-rw-r--r--src/mbgl/text/placement.hpp5
-rw-r--r--src/mbgl/text/quads.cpp34
-rw-r--r--src/mbgl/text/quads.hpp3
-rw-r--r--src/mbgl/text/shaping.cpp10
-rw-r--r--src/mbgl/text/tagged_string.cpp8
-rw-r--r--src/mbgl/text/tagged_string.hpp2
16 files changed, 343 insertions, 107 deletions
diff --git a/src/mbgl/layout/symbol_feature.hpp b/src/mbgl/layout/symbol_feature.hpp
index ed9c0783d0..03d8e5018d 100644
--- a/src/mbgl/layout/symbol_feature.hpp
+++ b/src/mbgl/layout/symbol_feature.hpp
@@ -32,6 +32,7 @@ public:
optional<std::string> icon;
float sortKey = 0.0f;
std::size_t index;
+ bool allowsVerticalWritingMode = false;
};
} // namespace mbgl
diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp
index 133cdec698..8e8286bbd7 100644
--- a/src/mbgl/layout/symbol_instance.cpp
+++ b/src/mbgl/layout/symbol_instance.cpp
@@ -25,7 +25,8 @@ SymbolInstanceSharedData::SymbolInstanceSharedData(GeometryCoordinates line_,
const float layoutTextSize,
const style::SymbolPlacementType textPlacement,
const std::array<float, 2>& textOffset,
- const GlyphPositions& positions) : line(std::move(line_)) {
+ const GlyphPositions& positions,
+ bool allowVerticalPlacement) : line(std::move(line_)) {
// Create the quads used for rendering the icon and glyphs.
if (shapedIcon) {
iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.horizontal);
@@ -34,11 +35,11 @@ SymbolInstanceSharedData::SymbolInstanceSharedData(GeometryCoordinates line_,
bool singleLineInitialized = false;
const auto initHorizontalGlyphQuads = [&] (SymbolQuads& quads, const Shaping& shaping) {
if (!shapedTextOrientations.singleLine) {
- quads = getGlyphQuads(shaping, textOffset, layout, textPlacement, positions);
+ quads = getGlyphQuads(shaping, textOffset, layout, textPlacement, positions, allowVerticalPlacement);
return;
}
if (!singleLineInitialized) {
- rightJustifiedGlyphQuads = getGlyphQuads(shaping, textOffset, layout, textPlacement, positions);
+ rightJustifiedGlyphQuads = getGlyphQuads(shaping, textOffset, layout, textPlacement, positions, allowVerticalPlacement);
singleLineInitialized = true;
}
};
@@ -56,7 +57,7 @@ SymbolInstanceSharedData::SymbolInstanceSharedData(GeometryCoordinates line_,
}
if (shapedTextOrientations.vertical) {
- verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.vertical, textOffset, layout, textPlacement, positions);
+ verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.vertical, textOffset, layout, textPlacement, positions, allowVerticalPlacement);
}
}
@@ -81,7 +82,8 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
std::u16string key_,
const float overscaling,
const float rotate,
- float radialTextOffset_) :
+ float radialTextOffset_,
+ bool allowVerticalPlacement) :
sharedData(std::move(sharedData_)),
anchor(anchor_),
// 'hasText' depends on finding at least one glyph in the shaping that's also in the GlyphPositionMap
@@ -101,6 +103,11 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
radialTextOffset(radialTextOffset_),
singleLine(shapedTextOrientations.singleLine) {
+ if (allowVerticalPlacement && shapedTextOrientations.vertical) {
+ const float verticalPointLabelAngle = 90.0f;
+ verticalTextCollisionFeature = CollisionFeature(line(), anchor, shapedTextOrientations.vertical, textBoxScale_, textPadding, textPlacement, indexedFeature, overscaling, rotate + verticalPointLabelAngle);
+ }
+
rightJustifiedGlyphQuadsSize = sharedData->rightJustifiedGlyphQuads.size();
centerJustifiedGlyphQuadsSize = sharedData->centerJustifiedGlyphQuads.size();
leftJustifiedGlyphQuadsSize = sharedData->leftJustifiedGlyphQuads.size();
diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp
index 48bb2f0cbc..693d8917c7 100644
--- a/src/mbgl/layout/symbol_instance.hpp
+++ b/src/mbgl/layout/symbol_instance.hpp
@@ -29,7 +29,8 @@ struct SymbolInstanceSharedData {
const float layoutTextSize,
const style::SymbolPlacementType textPlacement,
const std::array<float, 2>& textOffset,
- const GlyphPositions& positions);
+ const GlyphPositions& positions,
+ bool allowVerticalPlacement);
bool empty() const;
GeometryCoordinates line;
// Note: When singleLine == true, only `rightJustifiedGlyphQuads` is populated.
@@ -59,7 +60,8 @@ public:
std::u16string key,
const float overscaling,
const float rotate,
- float radialTextOffset);
+ float radialTextOffset,
+ bool allowVerticalPlacement);
optional<size_t> getDefaultHorizontalPlacedTextIndex() const;
const GeometryCoordinates& line() const;
@@ -85,6 +87,7 @@ public:
CollisionFeature textCollisionFeature;
CollisionFeature iconCollisionFeature;
+ optional<CollisionFeature> verticalTextCollisionFeature = nullopt;
WritingModeType writingModes;
std::size_t layoutFeatureIndex; // Index into the set of features included at layout time
std::size_t dataFeatureIndex; // Index into the underlying tile data feature set
diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp
index d269ca4144..be6444bb16 100644
--- a/src/mbgl/layout/symbol_layout.cpp
+++ b/src/mbgl/layout/symbol_layout.cpp
@@ -109,6 +109,19 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
const bool zOrderByViewportY = symbolZOrder == SymbolZOrderType::ViewportY || (symbolZOrder == SymbolZOrderType::Auto && !sortFeaturesByKey);
sortFeaturesByY = zOrderByViewportY && (layout->get<TextAllowOverlap>() || layout->get<IconAllowOverlap>() ||
layout->get<TextIgnorePlacement>() || layout->get<IconIgnorePlacement>());
+ if (layout->get<SymbolPlacement>() == SymbolPlacementType::Point) {
+ auto modes = layout->get<TextWritingMode>();
+ // Remove duplicates and preserve order.
+ std::set<style::TextWritingModeType> seen;
+ auto end = std::remove_if(modes.begin(),
+ modes.end(),
+ [&seen, this](const auto& placementMode) {
+ allowVerticalPlacement = allowVerticalPlacement || placementMode == style::TextWritingModeType::Vertical;
+ return !seen.insert(placementMode).second;
+ });
+ modes.erase(end, modes.end());
+ placementModes = std::move(modes);
+ }
for (const auto& layer : layers) {
layerPaintProperties.emplace(layer->baseImpl->id, layer);
@@ -148,7 +161,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
const bool canVerticalizeText = layout->get<TextRotationAlignment>() == AlignmentType::Map
&& layout->get<SymbolPlacement>() != SymbolPlacementType::Point
- && util::i18n::allowsVerticalWritingMode(ft.formattedText->rawText());
+ && ft.formattedText->allowsVerticalWritingMode();
// Loop through all characters of this text and collect unique codepoints.
for (std::size_t j = 0; j < ft.formattedText->length(); j++) {
@@ -156,7 +169,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
GlyphIDs& dependencies = glyphDependencies[sectionFontStack ? *sectionFontStack : baseFontStack];
char16_t codePoint = ft.formattedText->getCharCodeAt(j);
dependencies.insert(codePoint);
- if (canVerticalizeText) {
+ if (canVerticalizeText || (allowVerticalPlacement && ft.formattedText->allowsVerticalWritingMode())) {
if (char16_t verticalChr = util::i18n::verticalizePunctuation(codePoint)) {
dependencies.insert(verticalChr);
}
@@ -321,7 +334,18 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions
}
}
TextJustifyType textJustify = textAlongLine ? TextJustifyType::Center : layout->evaluate<TextJustify>(zoom, feature);
- // If this layer uses text-variable-anchor, generate shapings for all justification possibilities.
+
+ const auto addVerticalShapingForPointLabelIfNeeded = [&] {
+ if (allowVerticalPlacement && feature.formattedText->allowsVerticalWritingMode()) {
+ feature.formattedText->verticalizePunctuation();
+ // Vertical POI label placement is meant to be used for scripts that support vertical
+ // writing mode, thus, default style::TextJustifyType::Left justification is used. If Latin
+ // scripts would need to be supported, this should take into account other justifications.
+ shapedTextOrientations.vertical = applyShaping(*feature.formattedText, WritingModeType::Vertical, textAnchor, style::TextJustifyType::Left);
+ }
+ };
+
+ // If this layer uses text-variable-anchor, generate shapings for all justification possibilities.
if (!textAlongLine && !variableTextAnchor.empty()) {
std::vector<TextJustifyType> justifications;
if (textJustify != TextJustifyType::Auto) {
@@ -347,16 +371,25 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions
}
}
}
+
+ // Vertical point label shaping if allowVerticalPlacement is enabled.
+ addVerticalShapingForPointLabelIfNeeded();
} else {
if (textJustify == TextJustifyType::Auto) {
textJustify = getAnchorJustification(textAnchor);
}
+
+ // Horizontal point or line label.
Shaping shaping = applyShaping(*feature.formattedText, WritingModeType::Horizontal, textAnchor, textJustify);
if (shaping) {
shapedTextOrientations.horizontal = std::move(shaping);
}
- if (util::i18n::allowsVerticalWritingMode(feature.formattedText->rawText()) && textAlongLine) {
+ // Vertical point label shaping if allowVerticalPlacement is enabled.
+ addVerticalShapingForPointLabelIfNeeded();
+
+ // Verticalized line label.
+ if (textAlongLine && feature.formattedText->allowsVerticalWritingMode()) {
feature.formattedText->verticalizePunctuation();
shapedTextOrientations.vertical = applyShaping(*feature.formattedText, WritingModeType::Vertical, textAnchor, textJustify);
}
@@ -423,7 +456,8 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
const float textPadding = layout->get<TextPadding>() * tilePixelRatio;
const float iconPadding = layout->get<IconPadding>() * tilePixelRatio;
const float textMaxAngle = layout->get<TextMaxAngle>() * util::DEG2RAD;
- const float rotation = layout->evaluate<IconRotate>(zoom, feature);
+ const float iconRotation = layout->evaluate<IconRotate>(zoom, feature);
+ const float textRotation = layout->evaluate<TextRotate>(zoom, feature);
const float radialTextOffset = layout->evaluate<TextRadialOffset>(zoom, feature) * util::ONE_EM;
const SymbolPlacementType textPlacement = layout->get<TextRotationAlignment>() != AlignmentType::Map
? SymbolPlacementType::Point
@@ -448,14 +482,14 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
iconBoxScale, iconPadding, iconOffset, indexedFeature,
layoutFeatureIndex, feature.index,
feature.formattedText ? feature.formattedText->rawText() : std::u16string(),
- overscaling, rotation, radialTextOffset);
+ overscaling, iconRotation, textRotation, radialTextOffset, allowVerticalPlacement);
}
};
const auto createSymbolInstanceSharedData = [&] (GeometryCoordinates line) {
return std::make_shared<SymbolInstanceSharedData>(std::move(line),
shapedTextOrientations, shapedIcon, evaluatedLayoutProperties, layoutTextSize,
- textPlacement, textOffset, glyphPositions);
+ textPlacement, textOffset, glyphPositions, allowVerticalPlacement);
};
const auto& type = feature.getType();
@@ -568,7 +602,10 @@ std::vector<float> CalculateTileDistances(const GeometryCoordinates& line, const
}
void SymbolLayout::createBucket(const ImagePositions&, std::unique_ptr<FeatureIndex>&, std::unordered_map<std::string, LayerRenderData>& renderData, const bool firstLoad, const bool showCollisionBoxes) {
- auto bucket = std::make_shared<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances), tilePixelRatio);
+ auto bucket = std::make_shared<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear,
+ sortFeaturesByY, bucketLeaderID, std::move(symbolInstances), tilePixelRatio,
+ allowVerticalPlacement,
+ std::move(placementModes));
for (SymbolInstance &symbolInstance : bucket->symbolInstances) {
const bool hasText = symbolInstance.hasText;
@@ -660,6 +697,7 @@ std::size_t SymbolLayout::addSymbolGlyphQuads(SymbolBucket& bucket,
symbolInstance.textOffset, writingMode, symbolInstance.line(), CalculateTileDistances(symbolInstance.line(), symbolInstance.anchor));
placedIndex = bucket.text.placedSymbols.size() - 1;
PlacedSymbol& placedSymbol = bucket.text.placedSymbols.back();
+ placedSymbol.angle = (allowVerticalPlacement && writingMode == WritingModeType::Vertical) ? M_PI_2 : 0;
bool firstSymbol = true;
for (const auto& symbolQuad : glyphQuads) {
@@ -795,6 +833,9 @@ void SymbolLayout::addToDebugBuffers(SymbolBucket& bucket) {
}
};
populateCollisionBox(symbolInstance.textCollisionFeature);
+ if (symbolInstance.verticalTextCollisionFeature) {
+ populateCollisionBox(*symbolInstance.verticalTextCollisionFeature);
+ }
populateCollisionBox(symbolInstance.iconCollisionFeature);
}
}
diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp
index 581c3ccb04..70a3482644 100644
--- a/src/mbgl/layout/symbol_layout.hpp
+++ b/src/mbgl/layout/symbol_layout.hpp
@@ -96,6 +96,8 @@ private:
bool sdfIcons = false;
bool iconsNeedLinear = false;
bool sortFeaturesByY = false;
+ bool allowVerticalPlacement = false;
+ std::vector<style::TextWritingModeType> placementModes;
style::TextSize::UnevaluatedType textSize;
style::IconSize::UnevaluatedType iconSize;
diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp
index aeea10ffa4..745c1c1d77 100644
--- a/src/mbgl/layout/symbol_projection.cpp
+++ b/src/mbgl/layout/symbol_projection.cpp
@@ -319,7 +319,11 @@ namespace mbgl {
const float glyphOffsetX = symbol.glyphOffsets[glyphIndex];
// Since first and last glyph fit on the line, we're sure that the rest of the glyphs can be placed
auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, false);
- placedGlyphs.push_back(*placedGlyph);
+ if (placedGlyph) {
+ placedGlyphs.push_back(*placedGlyph);
+ } else {
+ placedGlyphs.emplace_back(Point<float>{-INFINITY, -INFINITY}, 0.0f, nullopt);
+ }
}
placedGlyphs.push_back(firstAndLastGlyph->second);
} else if (symbol.glyphOffsets.size() == 1) {
diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp
index 2a9f5df9c0..21a7870473 100644
--- a/src/mbgl/renderer/buckets/symbol_bucket.cpp
+++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp
@@ -23,7 +23,9 @@ SymbolBucket::SymbolBucket(Immutable<style::SymbolLayoutProperties::PossiblyEval
bool sortFeaturesByY_,
const std::string bucketName_,
const std::vector<SymbolInstance>&& symbolInstances_,
- float tilePixelRatio_)
+ float tilePixelRatio_,
+ bool allowVerticalPlacement_,
+ std::vector<style::TextWritingModeType> placementModes_)
: layout(std::move(layout_)),
bucketLeaderID(std::move(bucketName_)),
sdfIcons(sdfIcons_),
@@ -39,7 +41,9 @@ SymbolBucket::SymbolBucket(Immutable<style::SymbolLayoutProperties::PossiblyEval
textSizeBinder(SymbolSizeBinder::create(zoom, textSize, TextSize::defaultValue())),
iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())),
tilePixelRatio(tilePixelRatio_),
- bucketInstanceId(++maxBucketInstanceId) {
+ bucketInstanceId(++maxBucketInstanceId),
+ allowVerticalPlacement(allowVerticalPlacement_),
+ placementModes(std::move(placementModes_)) {
for (const auto& pair : paintProperties_) {
const auto& evaluated = getEvaluated<SymbolLayerProperties>(pair.second);
diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp
index a94073f7d0..f47ced8331 100644
--- a/src/mbgl/renderer/buckets/symbol_bucket.hpp
+++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp
@@ -39,6 +39,10 @@ public:
size_t vertexStartIndex;
// The crossTileID is only filled/used on the foreground for variable text anchors
uint32_t crossTileID = 0u;
+ // The placedOrientation is only used when symbol layer's property is set to support
+ // placement for orientation variants.
+ optional<style::TextWritingModeType> placedOrientation;
+ float angle = 0;
};
class SymbolBucket final : public Bucket {
@@ -53,7 +57,9 @@ public:
bool sortFeaturesByY,
const std::string bucketLeaderID,
const std::vector<SymbolInstance>&&,
- const float tilePixelRatio);
+ const float tilePixelRatio,
+ bool allowVerticalPlacement,
+ std::vector<style::TextWritingModeType> placementModes);
~SymbolBucket() override;
void upload(gfx::UploadPass&) override;
@@ -149,7 +155,8 @@ public:
const float tilePixelRatio;
uint32_t bucketInstanceId;
-
+ const bool allowVerticalPlacement;
+ const std::vector<style::TextWritingModeType> placementModes;
mutable optional<bool> hasFormatSectionOverrides_;
std::shared_ptr<std::vector<size_t>> featureSortOrder;
diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp
index 5105528512..234f718975 100644
--- a/src/mbgl/text/glyph.hpp
+++ b/src/mbgl/text/glyph.hpp
@@ -89,6 +89,8 @@ class Shaping {
WritingModeType writingMode;
std::size_t lineCount = 0u;
explicit operator bool() const { return !positionedGlyphs.empty(); }
+ // The y offset *should* be part of the font metadata.
+ static constexpr int32_t yOffset = -17;
};
enum class WritingModeType : uint8_t {
diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp
index f7d13dcb26..de282acf79 100644
--- a/src/mbgl/text/placement.cpp
+++ b/src/mbgl/text/placement.cpp
@@ -172,32 +172,80 @@ void Placement::placeBucket(
placements.emplace(symbolInstance.crossTileID, JointPlacement(false, false, false));
return;
}
- textBoxes.clear();
iconBoxes.clear();
bool placeText = false;
bool placeIcon = false;
bool offscreen = true;
+ std::pair<bool, bool> placed{ false, false };
+ std::pair<bool, bool> placedVertical{ false, false };
optional<size_t> horizontalTextIndex = symbolInstance.getDefaultHorizontalPlacedTextIndex();
if (horizontalTextIndex) {
- const CollisionFeature& textCollisionFeature = symbolInstance.textCollisionFeature;
const PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*horizontalTextIndex);
const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol);
+ const CollisionFeature& textCollisionFeature = symbolInstance.textCollisionFeature;
+
+ const auto updatePreviousOrientationIfNotPlaced = [&](bool isPlaced) {
+ if (bucket.allowVerticalPlacement && !isPlaced && prevPlacement) {
+ auto prevOrientation = prevPlacement->placedOrientations.find(symbolInstance.crossTileID);
+ if (prevOrientation != prevPlacement->placedOrientations.end()) {
+ placedOrientations[symbolInstance.crossTileID] = prevOrientation->second;
+ }
+ }
+ };
+
+ const auto placeTextForPlacementModes = [&] (auto& placeHorizontalFn, auto& placeVerticalFn) {
+ if (bucket.allowVerticalPlacement && symbolInstance.writingModes & WritingModeType::Vertical) {
+ assert(!bucket.placementModes.empty());
+ for (auto& placementMode : bucket.placementModes) {
+ if (placementMode == style::TextWritingModeType::Vertical) {
+ placedVertical = placed = placeVerticalFn();
+ } else {
+ placed = placeHorizontalFn();
+ }
+
+ if (placed.first) {
+ break;
+ }
+ }
+ } else {
+ placed = placeHorizontalFn();
+ }
+ };
+
+ // Line or point label placement
if (variableTextAnchors.empty()) {
- auto placed = collisionIndex.placeFeature(textCollisionFeature, {},
- posMatrix, textLabelPlaneMatrix, pixelRatio,
- placedSymbol, scale, fontSize,
- layout.get<style::TextAllowOverlap>(),
- pitchWithMap,
- params.showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes);
+ const auto placeFeature = [&] (const CollisionFeature& collisionFeature, style::TextWritingModeType orientation) {
+ textBoxes.clear();
+ auto placedFeature = collisionIndex.placeFeature(collisionFeature, {},
+ posMatrix, textLabelPlaneMatrix, pixelRatio,
+ placedSymbol, scale, fontSize,
+ layout.get<style::TextAllowOverlap>(),
+ pitchWithMap,
+ params.showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes);
+ if (placedFeature.first) {
+ placedOrientations.emplace(symbolInstance.crossTileID, orientation);
+ }
+ return placedFeature;
+ };
+
+ const auto placeHorizontal = [&] {
+ return placeFeature(symbolInstance.textCollisionFeature, style::TextWritingModeType::Horizontal);
+ };
+
+ const auto placeVertical = [&] {
+ if (bucket.allowVerticalPlacement && symbolInstance.verticalTextCollisionFeature) {
+ return placeFeature(*symbolInstance.verticalTextCollisionFeature, style::TextWritingModeType::Vertical);
+ }
+ return std::pair<bool, bool>{false, false};
+ };
+
+ placeTextForPlacementModes(placeHorizontal, placeVertical);
+ updatePreviousOrientationIfNotPlaced(placed.first);
+
placeText = placed.first;
offscreen &= placed.second;
} else if (!textCollisionFeature.alongLine && !textCollisionFeature.boxes.empty()) {
- const CollisionBox& textBox = symbolInstance.textCollisionFeature.boxes[0];
- const float width = textBox.x2 - textBox.x1;
- const float height = textBox.y2 - textBox.y1;
- const float textBoxScale = symbolInstance.textBoxScale;
-
// If this symbol was in the last placement, shift the previously used
// anchor to the front of the anchor list, only if the previous anchor
// is still in the anchor list.
@@ -220,55 +268,83 @@ void Placement::placeBucket(
}
}
- for (auto anchor : variableTextAnchors) {
- Point<float> shift = calculateVariableLayoutOffset(anchor, width, height, symbolInstance.radialTextOffset, textBoxScale);
- if (rotateWithMap) {
- float angle = pitchWithMap ? state.getBearing() : -state.getBearing();
- shift = util::rotate(shift, angle);
- }
+ const auto placeFeatureForVariableAnchors = [&] (const CollisionFeature& collisionFeature, style::TextWritingModeType orientation) {
+ const CollisionBox& textBox = collisionFeature.boxes[0];
+ const float width = textBox.x2 - textBox.x1;
+ const float height = textBox.y2 - textBox.y1;
+ const float textBoxScale = symbolInstance.textBoxScale;
+ std::pair<bool, bool> placedFeature = {false, false};
+ for (auto anchor : variableTextAnchors) {
+ Point<float> shift = calculateVariableLayoutOffset(anchor, width, height, symbolInstance.radialTextOffset, textBoxScale);
+ if (rotateWithMap) {
+ float angle = pitchWithMap ? state.getBearing() : -state.getBearing();
+ shift = util::rotate(shift, angle);
+ }
+
+ textBoxes.clear();
+ placedFeature = collisionIndex.placeFeature(collisionFeature, shift,
+ posMatrix, mat4(), pixelRatio,
+ placedSymbol, scale, fontSize,
+ layout.get<style::TextAllowOverlap>(),
+ pitchWithMap,
+ params.showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes);
+ if (placedFeature.first) {
+ assert(symbolInstance.crossTileID != 0u);
+ optional<style::TextVariableAnchorType> prevAnchor;
+
+ // If this label was placed in the previous placement, record the anchor position
+ // to allow us to animate the transition
+ if (prevPlacement) {
+ auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID);
+ auto prevPlacements = prevPlacement->placements.find(symbolInstance.crossTileID);
+ if (prevOffset != prevPlacement->variableOffsets.end() &&
+ prevPlacements != prevPlacement->placements.end() &&
+ prevPlacements->second.text) {
+ // TODO: The prevAnchor seems to be unused, needs to be fixed.
+ prevAnchor = prevOffset->second.anchor;
+ }
+ }
- auto placed = collisionIndex.placeFeature(textCollisionFeature, shift,
- posMatrix, mat4(), pixelRatio,
- placedSymbol, scale, fontSize,
- layout.get<style::TextAllowOverlap>(),
- pitchWithMap,
- params.showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes);
-
- if (placed.first) {
- assert(symbolInstance.crossTileID != 0u);
- optional<style::TextVariableAnchorType> prevAnchor;
-
- // If this label was placed in the previous placement, record the anchor position
- // to allow us to animate the transition
- if (prevPlacement) {
- auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID);
- auto prevPlacements = prevPlacement->placements.find(symbolInstance.crossTileID);
- if (prevOffset != prevPlacement->variableOffsets.end() &&
- prevPlacements != prevPlacement->placements.end() &&
- prevPlacements->second.text) {
- prevAnchor = prevOffset->second.anchor;
+ variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{
+ symbolInstance.radialTextOffset,
+ width,
+ height,
+ anchor,
+ textBoxScale,
+ prevAnchor
+ }));
+
+ if (bucket.allowVerticalPlacement) {
+ placedOrientations.emplace(symbolInstance.crossTileID, orientation);
}
+ break;
}
+ }
+
+ return placedFeature;
+ };
- variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{
- symbolInstance.radialTextOffset,
- width,
- height,
- anchor,
- textBoxScale,
- prevAnchor
- }));
-
- placeText = placed.first;
- offscreen &= placed.second;
- break;
+ const auto placeHorizontal = [&] {
+ return placeFeatureForVariableAnchors(symbolInstance.textCollisionFeature, style::TextWritingModeType::Horizontal);
+ };
+
+ const auto placeVertical = [&] {
+ if (bucket.allowVerticalPlacement && !placed.first && symbolInstance.verticalTextCollisionFeature) {
+ return placeFeatureForVariableAnchors(*symbolInstance.verticalTextCollisionFeature, style::TextWritingModeType::Vertical);
}
- textBoxes.clear();
- }
+ return std::pair<bool, bool>{false, false};
+ };
+
+ placeTextForPlacementModes(placeHorizontal, placeVertical);
+
+ placeText = placed.first;
+ offscreen &= placed.second;
+
+ updatePreviousOrientationIfNotPlaced(placed.first);
// If we didn't get placed, we still need to copy our position from the last placement for
// fade animations
- if (prevPlacement && variableOffsets.find(symbolInstance.crossTileID) == variableOffsets.end()) {
+ if (!placeText && prevPlacement) {
auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID);
if (prevOffset != prevPlacement->variableOffsets.end()) {
variableOffsets[symbolInstance.crossTileID] = prevOffset->second;
@@ -281,14 +357,14 @@ void Placement::placeBucket(
const PlacedSymbol& placedSymbol = bucket.icon.placedSymbols.at(*symbolInstance.placedIconIndex);
const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol);
- auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, {},
+ auto placedIcon = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, {},
posMatrix, iconLabelPlaneMatrix, pixelRatio,
placedSymbol, scale, fontSize,
layout.get<style::IconAllowOverlap>(),
pitchWithMap,
params.showCollisionBoxes, avoidEdges, collisionGroup.second, iconBoxes);
- placeIcon = placed.first;
- offscreen &= placed.second;
+ placeIcon = placedIcon.first;
+ offscreen &= placedIcon.second;
}
const bool iconWithoutText = !symbolInstance.hasText || layout.get<style::TextOptional>();
@@ -304,7 +380,11 @@ void Placement::placeBucket(
}
if (placeText) {
- collisionIndex.insertFeature(symbolInstance.textCollisionFeature, textBoxes, layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
+ if (placedVertical.first && symbolInstance.verticalTextCollisionFeature) {
+ collisionIndex.insertFeature(*symbolInstance.verticalTextCollisionFeature, textBoxes, layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
+ } else {
+ collisionIndex.insertFeature(symbolInstance.textCollisionFeature, textBoxes, layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
+ }
}
if (placeIcon) {
@@ -399,6 +479,15 @@ void Placement::commit(TimePoint now) {
}
}
+ for (auto& prevOrientation : prevPlacement->placedOrientations) {
+ const uint32_t crossTileID = prevOrientation.first;
+ auto foundOrientation = placedOrientations.find(crossTileID);
+ auto foundOpacity = opacities.find(crossTileID);
+ if (foundOrientation == placedOrientations.end() && foundOpacity != opacities.end() && !foundOpacity->second.isHidden()) {
+ placedOrientations[prevOrientation.first] = prevOrientation.second;
+ }
+ }
+
fadeStartTime = placementChanged ? commitTime : prevPlacement->fadeStartTime;
}
@@ -457,7 +546,8 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor
for (const PlacedSymbol& symbol : bucket.text.placedSymbols) {
optional<VariableOffset> variableOffset;
- if (!symbol.hidden && symbol.crossTileID != 0u) {
+ const bool skipOrientation = bucket.allowVerticalPlacement && !symbol.placedOrientation;
+ if (!symbol.hidden && symbol.crossTileID != 0u && !skipOrientation) {
auto it = variableOffsets.find(symbol.crossTileID);
if (it != variableOffsets.end()) {
bucket.hasVariablePlacement = true;
@@ -506,12 +596,24 @@ bool Placement::updateBucketDynamicVertices(SymbolBucket& bucket, const Transfor
}
for (std::size_t i = 0; i < symbol.glyphOffsets.size(); ++i) {
- addDynamicAttributes(shiftedAnchor, 0, bucket.text.dynamicVertices);
+ addDynamicAttributes(shiftedAnchor, symbol.angle, bucket.text.dynamicVertices);
}
}
}
result = true;
+ } else if (bucket.allowVerticalPlacement && bucket.hasTextData()) {
+ bucket.text.dynamicVertices.clear();
+ for (const PlacedSymbol& symbol : bucket.text.placedSymbols) {
+ if (symbol.hidden || !symbol.placedOrientation) {
+ hideGlyphs(symbol.glyphOffsets.size(), bucket.text.dynamicVertices);
+ } else {
+ for (std::size_t i = 0; i < symbol.glyphOffsets.size(); ++i) {
+ addDynamicAttributes(symbol.anchorPoint, symbol.angle, bucket.text.dynamicVertices);
+ }
+ }
+ }
+ result = true;
}
return result;
@@ -582,9 +684,18 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState
bucket.text.opacityVertices.extend(textOpacityVerticesSize, opacityVertex);
- auto offset = variableOffsets.find(symbolInstance.crossTileID);
- if (offset != variableOffsets.end()) {
- markUsedJustification(bucket, offset->second.anchor, symbolInstance);
+ style::TextWritingModeType previousOrientation = style::TextWritingModeType::Horizontal;
+ if (bucket.allowVerticalPlacement) {
+ auto prevOrientation = placedOrientations.find(symbolInstance.crossTileID);
+ if (prevOrientation != placedOrientations.end()) {
+ previousOrientation = prevOrientation->second;
+ markUsedOrientation(bucket, prevOrientation->second, symbolInstance);
+ }
+ }
+
+ auto prevOffset = variableOffsets.find(symbolInstance.crossTileID);
+ if (prevOffset != variableOffsets.end()) {
+ markUsedJustification(bucket, prevOffset->second.anchor, symbolInstance, previousOrientation);
}
}
if (symbolInstance.hasIcon) {
@@ -654,7 +765,11 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState
};
if (bucket.hasCollisionBoxData()) {
+ // TODO: update collision box opacity based on selected text variant (horizontal | vertical).
updateCollisionTextBox(symbolInstance.textCollisionFeature, opacityState.text.placed);
+ if (bucket.allowVerticalPlacement && symbolInstance.verticalTextCollisionFeature) {
+ updateCollisionTextBox(*symbolInstance.verticalTextCollisionFeature, opacityState.text.placed);
+ }
updateCollisionBox(symbolInstance.iconCollisionFeature, opacityState.icon.placed);
}
if (bucket.hasCollisionCircleData()) {
@@ -671,7 +786,12 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, const TransformState
}
namespace {
-optional<size_t> justificationToIndex(style::TextJustifyType justify, const SymbolInstance& symbolInstance) {
+optional<size_t> justificationToIndex(style::TextJustifyType justify, const SymbolInstance& symbolInstance, style::TextWritingModeType orientation) {
+ // Vertical symbol has just one justification, style::TextJustifyType::Left.
+ if (orientation == style::TextWritingModeType::Vertical) {
+ return symbolInstance.placedVerticalTextIndex;
+ }
+
switch(justify) {
case style::TextJustifyType::Right: return symbolInstance.placedRightTextIndex;
case style::TextJustifyType::Center: return symbolInstance.placedCenterTextIndex;
@@ -686,13 +806,13 @@ const style::TextJustifyType justifyTypes[] = {style::TextJustifyType::Right, st
} // namespace
-void Placement::markUsedJustification(SymbolBucket& bucket, style::TextVariableAnchorType placedAnchor, SymbolInstance& symbolInstance) {
+void Placement::markUsedJustification(SymbolBucket& bucket, style::TextVariableAnchorType placedAnchor, SymbolInstance& symbolInstance, style::TextWritingModeType orientation) {
style::TextJustifyType anchorJustify = getAnchorJustification(placedAnchor);
assert(anchorJustify != style::TextJustifyType::Auto);
- const optional<size_t>& autoIndex = justificationToIndex(anchorJustify, symbolInstance);
+ const optional<size_t>& autoIndex = justificationToIndex(anchorJustify, symbolInstance, orientation);
for (auto& justify : justifyTypes) {
- const optional<size_t> index = justificationToIndex(justify, symbolInstance);
+ const optional<size_t> index = justificationToIndex(justify, symbolInstance, orientation);
if (index) {
assert(bucket.text.placedSymbols.size() > *index);
if (autoIndex && *index != *autoIndex) {
@@ -706,6 +826,29 @@ void Placement::markUsedJustification(SymbolBucket& bucket, style::TextVariableA
}
}
+void Placement::markUsedOrientation(SymbolBucket& bucket, style::TextWritingModeType orientation, SymbolInstance& symbolInstance) {
+ auto horizontal = orientation == style::TextWritingModeType::Horizontal ?
+ optional<style::TextWritingModeType>(orientation) : nullopt;
+ auto vertical = orientation == style::TextWritingModeType::Vertical ?
+ optional<style::TextWritingModeType>(orientation) : nullopt;
+
+ if (symbolInstance.placedRightTextIndex) {
+ bucket.text.placedSymbols.at(*symbolInstance.placedRightTextIndex).placedOrientation = horizontal;
+ }
+
+ if (symbolInstance.placedCenterTextIndex && !symbolInstance.singleLine) {
+ bucket.text.placedSymbols.at(*symbolInstance.placedCenterTextIndex).placedOrientation = horizontal;
+ }
+
+ if (symbolInstance.placedLeftTextIndex && !symbolInstance.singleLine) {
+ bucket.text.placedSymbols.at(*symbolInstance.placedLeftTextIndex).placedOrientation = horizontal;
+ }
+
+ if (symbolInstance.placedVerticalTextIndex) {
+ bucket.text.placedSymbols.at(*symbolInstance.placedVerticalTextIndex).placedOrientation = vertical;
+ }
+}
+
float Placement::symbolFadeChange(TimePoint now) const {
if (mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions &&
transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) > Milliseconds(0)) {
diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp
index 2a6a2e1d6e..33bfbd6527 100644
--- a/src/mbgl/text/placement.hpp
+++ b/src/mbgl/text/placement.hpp
@@ -12,6 +12,7 @@ namespace mbgl {
class SymbolBucket;
class SymbolInstance;
+enum class PlacedSymbolOrientation : bool;
class OpacityState {
public:
@@ -122,7 +123,8 @@ private:
// Returns `true` if bucket vertices were updated; returns `false` otherwise.
bool updateBucketDynamicVertices(SymbolBucket&, const TransformState&, const RenderTile& tile) const;
void updateBucketOpacities(SymbolBucket&, const TransformState&, std::set<uint32_t>&);
- void markUsedJustification(SymbolBucket&, style::TextVariableAnchorType, SymbolInstance&);
+ void markUsedJustification(SymbolBucket&, style::TextVariableAnchorType, SymbolInstance&, style::TextWritingModeType orientation);
+ void markUsedOrientation(SymbolBucket&, style::TextWritingModeType, SymbolInstance&);
CollisionIndex collisionIndex;
@@ -135,6 +137,7 @@ private:
std::unordered_map<uint32_t, JointPlacement> placements;
std::unordered_map<uint32_t, JointOpacityState> opacities;
std::unordered_map<uint32_t, VariableOffset> variableOffsets;
+ std::unordered_map<uint32_t, style::TextWritingModeType> placedOrientations;
bool stale = false;
diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp
index 6be5d8c01e..9ff26ddd8d 100644
--- a/src/mbgl/text/quads.cpp
+++ b/src/mbgl/text/quads.cpp
@@ -95,8 +95,10 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
const std::array<float, 2> textOffset,
const SymbolLayoutProperties::Evaluated& layout,
const style::SymbolPlacementType placement,
- const GlyphPositions& positions) {
+ const GlyphPositions& positions,
+ bool allowVerticalPlacement) {
const float textRotate = layout.get<TextRotate>() * util::DEG2RAD;
+ const bool alongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map && placement != SymbolPlacementType::Point;
SymbolQuads quads;
@@ -117,16 +119,23 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
const float rectBuffer = 3.0f + glyphPadding;
const float halfAdvance = glyph.metrics.advance * positionedGlyph.scale / 2.0;
- const bool alongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map && placement != SymbolPlacementType::Point;
const Point<float> glyphOffset = alongLine ?
Point<float>{ positionedGlyph.x + halfAdvance, positionedGlyph.y } :
Point<float>{ 0.0f, 0.0f };
- const Point<float> builtInOffset = alongLine ?
+ Point<float> builtInOffset = alongLine ?
Point<float>{ 0.0f, 0.0f } :
Point<float>{ positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1] };
+ Point<float> verticalizedLabelOffset = { 0.0f, 0.0f };
+ const bool rotateVerticalGlyph = (alongLine || allowVerticalPlacement) && positionedGlyph.vertical;
+ if (rotateVerticalGlyph) {
+ // Vertical POI labels, that are rotated 90deg CW and whose glyphs must preserve upright orientation
+ // need to be rotated 90deg CCW. After quad is rotated, it is translated to the original built-in offset.
+ verticalizedLabelOffset = builtInOffset;
+ builtInOffset = { 0.0f, 0.0f };
+ }
const float x1 = (glyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset.x;
const float y1 = (-glyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset.y;
@@ -138,22 +147,25 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
Point<float> bl{x1, y2};
Point<float> br{x2, y2};
- if (alongLine && positionedGlyph.vertical) {
+ if (rotateVerticalGlyph) {
// Vertical-supporting glyphs are laid out in 24x24 point boxes (1 square em)
// In horizontal orientation, the y values for glyphs are below the midline
// and we use a "yOffset" of -17 to pull them up to the middle.
// By rotating counter-clockwise around the point at the center of the left
// edge of a 24x24 layout box centered below the midline, we align the center
// of the glyphs with the horizontal midline, so the yOffset is no longer
- // necessary, but we also pull the glyph to the left along the x axis
- const Point<float> center{-halfAdvance, halfAdvance};
+ // necessary, but we also pull the glyph to the left along the x axis.
+ // The y coordinate includes baseline yOffset, therefore, needs to be accounted
+ // for when glyph is rotated and translated.
+
+ const Point<float> center{ -halfAdvance, halfAdvance - Shaping::yOffset };
const float verticalRotation = -M_PI_2;
- const Point<float> xOffsetCorrection{5, 0};
+ const Point<float> xOffsetCorrection{ 5.0f - Shaping::yOffset, 0.0f };
- tl = util::rotate(tl - center, verticalRotation) + center + xOffsetCorrection;
- tr = util::rotate(tr - center, verticalRotation) + center + xOffsetCorrection;
- bl = util::rotate(bl - center, verticalRotation) + center + xOffsetCorrection;
- br = util::rotate(br - center, verticalRotation) + center + xOffsetCorrection;
+ tl = util::rotate(tl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
+ tr = util::rotate(tr - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
+ bl = util::rotate(bl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
+ br = util::rotate(br - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
}
if (textRotate) {
diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp
index 0bb892e4d1..1ec68189af 100644
--- a/src/mbgl/text/quads.hpp
+++ b/src/mbgl/text/quads.hpp
@@ -52,6 +52,7 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
const std::array<float, 2> textOffset,
const style::SymbolLayoutProperties::Evaluated&,
style::SymbolPlacementType placement,
- const GlyphPositions& positions);
+ const GlyphPositions& positions,
+ bool allowVerticalPlacement);
} // namespace mbgl
diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp
index 1d95376b04..132f74661e 100644
--- a/src/mbgl/text/shaping.cpp
+++ b/src/mbgl/text/shaping.cpp
@@ -292,12 +292,8 @@ void shapeLines(Shaping& shaping,
const style::TextJustifyType textJustify,
const WritingModeType writingMode,
const GlyphMap& glyphMap) {
-
- // the y offset *should* be part of the font metadata
- const int32_t yOffset = -17;
-
float x = 0;
- float y = yOffset;
+ float y = Shaping::yOffset;
float maxLineLength = 0;
@@ -342,7 +338,7 @@ void shapeLines(Shaping& shaping,
shaping.positionedGlyphs.emplace_back(codePoint, x, y + baselineOffset, false, section.fontStackHash, section.scale, sectionIndex);
x += glyph.metrics.advance * section.scale + spacing;
} else {
- shaping.positionedGlyphs.emplace_back(codePoint, x, baselineOffset, true, section.fontStackHash, section.scale, sectionIndex);
+ shaping.positionedGlyphs.emplace_back(codePoint, x, y + baselineOffset, true, section.fontStackHash, section.scale, sectionIndex);
x += util::ONE_EM * section.scale + spacing;
}
}
@@ -364,7 +360,7 @@ void shapeLines(Shaping& shaping,
align(shaping, justify, anchorAlign.horizontalAlign, anchorAlign.verticalAlign, maxLineLength,
lineHeight, lines.size());
- const float height = y - yOffset;
+ const float height = y - Shaping::yOffset;
// Calculate the bounding box
shaping.top += -anchorAlign.verticalAlign * height;
diff --git a/src/mbgl/text/tagged_string.cpp b/src/mbgl/text/tagged_string.cpp
index 8c4e3b02e8..e8a1c6f51f 100644
--- a/src/mbgl/text/tagged_string.cpp
+++ b/src/mbgl/text/tagged_string.cpp
@@ -8,6 +8,7 @@ void TaggedString::addSection(const std::u16string& sectionText, double scale, F
styledText.first += sectionText;
sections.emplace_back(scale, fontStack, std::move(textColor));
styledText.second.resize(styledText.first.size(), sections.size() - 1);
+ supportsVerticalWritingMode = nullopt;
}
void TaggedString::trim() {
@@ -37,4 +38,11 @@ void TaggedString::verticalizePunctuation() {
styledText.first = util::i18n::verticalizePunctuation(styledText.first);
}
+bool TaggedString::allowsVerticalWritingMode() {
+ if (!supportsVerticalWritingMode) {
+ supportsVerticalWritingMode = util::i18n::allowsVerticalWritingMode(rawText());
+ }
+ return *supportsVerticalWritingMode;
+}
+
} // namespace mbgl
diff --git a/src/mbgl/text/tagged_string.hpp b/src/mbgl/text/tagged_string.hpp
index 2607e10889..698e539a45 100644
--- a/src/mbgl/text/tagged_string.hpp
+++ b/src/mbgl/text/tagged_string.hpp
@@ -98,10 +98,12 @@ struct TaggedString {
void trim();
void verticalizePunctuation();
+ bool allowsVerticalWritingMode();
private:
StyledText styledText;
std::vector<SectionOptions> sections;
+ optional<bool> supportsVerticalWritingMode;
};
} // namespace mbgl