[core] Implement support for "text-writing-mode" layout property
This commit is contained in:
@@ -32,6 +32,7 @@ public:
|
||||
optional<std::string> icon;
|
||||
float sortKey = 0.0f;
|
||||
std::size_t index;
|
||||
bool allowsVerticalWritingMode = false;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{
|
||||
symbolInstance.radialTextOffset,
|
||||
width,
|
||||
height,
|
||||
anchor,
|
||||
textBoxScale,
|
||||
prevAnchor
|
||||
}));
|
||||
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;
|
||||
|
||||
placeText = placed.first;
|
||||
offscreen &= placed.second;
|
||||
break;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{
|
||||
symbolInstance.radialTextOffset,
|
||||
width,
|
||||
height,
|
||||
anchor,
|
||||
textBoxScale,
|
||||
prevAnchor
|
||||
}));
|
||||
|
||||
if (bucket.allowVerticalPlacement) {
|
||||
placedOrientations.emplace(symbolInstance.crossTileID, orientation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
textBoxes.clear();
|
||||
}
|
||||
|
||||
return placedFeature;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
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,11 +596,23 @@ 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;
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -98,10 +98,12 @@ struct TaggedString {
|
||||
void trim();
|
||||
|
||||
void verticalizePunctuation();
|
||||
bool allowsVerticalWritingMode();
|
||||
|
||||
private:
|
||||
StyledText styledText;
|
||||
std::vector<SectionOptions> sections;
|
||||
optional<bool> supportsVerticalWritingMode;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
Reference in New Issue
Block a user