summaryrefslogtreecommitdiff
path: root/src/mbgl/text
diff options
context:
space:
mode:
authorChris Loer <chris.loer@gmail.com>2018-10-02 17:03:50 -0700
committerChris Loer <chris.loer@mapbox.com>2018-10-15 13:15:46 -0700
commitce76bde13d0f4381ee861f81daf636defaff0bc5 (patch)
treee37d93d14fc64620069bac5488bae871af2fa431 /src/mbgl/text
parentbc718257748f1ad87658e85f8c31b574afca57a9 (diff)
downloadqtlocation-mapboxgl-ce76bde13d0f4381ee861f81daf636defaff0bc5.tar.gz
[core] Initial implementation of 'format' expression
Diffstat (limited to 'src/mbgl/text')
-rw-r--r--src/mbgl/text/bidi.hpp7
-rw-r--r--src/mbgl/text/glyph.hpp10
-rw-r--r--src/mbgl/text/glyph_atlas.cpp2
-rw-r--r--src/mbgl/text/glyph_atlas.hpp2
-rw-r--r--src/mbgl/text/glyph_manager.cpp2
-rw-r--r--src/mbgl/text/glyph_manager.hpp2
-rw-r--r--src/mbgl/text/quads.cpp20
-rw-r--r--src/mbgl/text/quads.hpp2
-rw-r--r--src/mbgl/text/shaping.cpp136
-rw-r--r--src/mbgl/text/shaping.hpp5
-rw-r--r--src/mbgl/text/tagged_string.cpp42
-rw-r--r--src/mbgl/text/tagged_string.hpp97
12 files changed, 262 insertions, 65 deletions
diff --git a/src/mbgl/text/bidi.hpp b/src/mbgl/text/bidi.hpp
index d90f3e5d1b..5ce2887db8 100644
--- a/src/mbgl/text/bidi.hpp
+++ b/src/mbgl/text/bidi.hpp
@@ -14,6 +14,10 @@ class BiDiImpl;
std::u16string applyArabicShaping(const std::u16string&);
+// StyledText pairs each code point in a string with an integer indicating
+// the styling options to use for rendering that code point
+// The data structure is intended to accomodate the reordering/interleaving
+// of formatting that can happen when BiDi rearranges inputs
using StyledText = std::pair<std::u16string, std::vector<uint8_t>>;
class BiDi : private util::noncopyable {
@@ -21,7 +25,10 @@ public:
BiDi();
~BiDi();
+ // Given text in logical ordering and a set of line break points,
+ // return a set of lines in visual order with bidi and line breaking applied
std::vector<std::u16string> processText(const std::u16string&, std::set<std::size_t>);
+ // Same as processText but preserves per-code-point formatting information
std::vector<StyledText> processStyledText(const StyledText&, std::set<std::size_t>);
private:
diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp
index 2c03da308a..55cd50fd9b 100644
--- a/src/mbgl/text/glyph.hpp
+++ b/src/mbgl/text/glyph.hpp
@@ -55,17 +55,21 @@ public:
};
using Glyphs = std::map<GlyphID, optional<Immutable<Glyph>>>;
-using GlyphMap = std::map<FontStack, Glyphs>;
+using GlyphMap = std::map<FontStackHash, Glyphs>;
class PositionedGlyph {
public:
- explicit PositionedGlyph(GlyphID glyph_, float x_, float y_, bool vertical_)
- : glyph(glyph_), x(x_), y(y_), vertical(vertical_) {}
+ explicit PositionedGlyph(GlyphID glyph_, float x_, float y_, bool vertical_, FontStackHash font_, float scale_)
+ : glyph(glyph_), x(x_), y(y_), vertical(vertical_), font(font_), scale(scale_)
+ {}
GlyphID glyph = 0;
float x = 0;
float y = 0;
bool vertical = false;
+
+ FontStackHash font = 0;
+ float scale = 0.0;
};
enum class WritingModeType : uint8_t;
diff --git a/src/mbgl/text/glyph_atlas.cpp b/src/mbgl/text/glyph_atlas.cpp
index 1b98ea36bf..da65aea8a9 100644
--- a/src/mbgl/text/glyph_atlas.cpp
+++ b/src/mbgl/text/glyph_atlas.cpp
@@ -14,7 +14,7 @@ GlyphAtlas makeGlyphAtlas(const GlyphMap& glyphs) {
mapbox::ShelfPack pack(0, 0, options);
for (const auto& glyphMapEntry : glyphs) {
- const FontStack& fontStack = glyphMapEntry.first;
+ FontStackHash fontStack = glyphMapEntry.first;
GlyphPositionMap& positions = result.positions[fontStack];
for (const auto& entry : glyphMapEntry.second) {
diff --git a/src/mbgl/text/glyph_atlas.hpp b/src/mbgl/text/glyph_atlas.hpp
index bb9115e4b4..9dd063ef69 100644
--- a/src/mbgl/text/glyph_atlas.hpp
+++ b/src/mbgl/text/glyph_atlas.hpp
@@ -12,7 +12,7 @@ struct GlyphPosition {
};
using GlyphPositionMap = std::map<GlyphID, GlyphPosition>;
-using GlyphPositions = std::map<FontStack, GlyphPositionMap>;
+using GlyphPositions = std::map<FontStackHash, GlyphPositionMap>;
class GlyphAtlas {
public:
diff --git a/src/mbgl/text/glyph_manager.cpp b/src/mbgl/text/glyph_manager.cpp
index 8e7cfe5ba7..c4a7a2de66 100644
--- a/src/mbgl/text/glyph_manager.cpp
+++ b/src/mbgl/text/glyph_manager.cpp
@@ -130,7 +130,7 @@ void GlyphManager::notify(GlyphRequestor& requestor, const GlyphDependencies& gl
const FontStack& fontStack = dependency.first;
const GlyphIDs& glyphIDs = dependency.second;
- Glyphs& glyphs = response[fontStack];
+ Glyphs& glyphs = response[FontStackHasher()(fontStack)];
Entry& entry = entries[fontStack];
for (const auto& glyphID : glyphIDs) {
diff --git a/src/mbgl/text/glyph_manager.hpp b/src/mbgl/text/glyph_manager.hpp
index 642471bbf2..831d84719c 100644
--- a/src/mbgl/text/glyph_manager.hpp
+++ b/src/mbgl/text/glyph_manager.hpp
@@ -62,7 +62,7 @@ private:
std::map<GlyphID, Immutable<Glyph>> glyphs;
};
- std::unordered_map<FontStack, Entry, FontStackHash> entries;
+ std::unordered_map<FontStack, Entry, FontStackHasher> entries;
void requestRange(GlyphRequest&, const FontStack&, const GlyphRange&);
void processResponse(const Response&, const FontStack&, const GlyphRange&);
diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp
index ec4461ac6d..9d582f14d6 100644
--- a/src/mbgl/text/quads.cpp
+++ b/src/mbgl/text/quads.cpp
@@ -94,7 +94,7 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon,
SymbolQuads getGlyphQuads(const Shaping& shapedText,
const SymbolLayoutProperties::Evaluated& layout,
const style::SymbolPlacementType placement,
- const GlyphPositionMap& positions) {
+ const GlyphPositions& positions) {
const float textRotate = layout.get<TextRotate>() * util::DEG2RAD;
const float oneEm = 24.0;
@@ -105,8 +105,12 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
SymbolQuads quads;
for (const PositionedGlyph &positionedGlyph: shapedText.positionedGlyphs) {
- auto positionsIt = positions.find(positionedGlyph.glyph);
- if (positionsIt == positions.end())
+ auto fontPositions = positions.find(positionedGlyph.font);
+ if (fontPositions == positions.end())
+ continue;
+
+ auto positionsIt = fontPositions->second.find(positionedGlyph.glyph);
+ if (positionsIt == fontPositions->second.end())
continue;
const GlyphPosition& glyph = positionsIt->second;
@@ -116,7 +120,7 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
const float glyphPadding = 1.0f;
const float rectBuffer = 3.0f + glyphPadding;
- const float halfAdvance = glyph.metrics.advance / 2.0;
+ 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 ?
@@ -128,10 +132,10 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
Point<float>{ positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1] };
- const float x1 = glyph.metrics.left - rectBuffer - halfAdvance + builtInOffset.x;
- const float y1 = -glyph.metrics.top - rectBuffer + builtInOffset.y;
- const float x2 = x1 + rect.w;
- const float y2 = y1 + rect.h;
+ const float x1 = (glyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset.x;
+ const float y1 = (-glyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset.y;
+ const float x2 = x1 + rect.w * positionedGlyph.scale;
+ const float y2 = y1 + rect.h * positionedGlyph.scale;
Point<float> tl{x1, y1};
Point<float> tr{x2, y1};
diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp
index 33d003c935..44a35a5014 100644
--- a/src/mbgl/text/quads.hpp
+++ b/src/mbgl/text/quads.hpp
@@ -48,6 +48,6 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon,
SymbolQuads getGlyphQuads(const Shaping& shapedText,
const style::SymbolLayoutProperties::Evaluated&,
style::SymbolPlacementType placement,
- const GlyphPositionMap& positions);
+ const GlyphPositions& positions);
} // namespace mbgl
diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp
index a8232836b6..d6cbb2a4f8 100644
--- a/src/mbgl/text/shaping.cpp
+++ b/src/mbgl/text/shaping.cpp
@@ -91,7 +91,7 @@ void align(Shaping& shaping,
// justify left = 0, right = 1, center = .5
void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
- const Glyphs& glyphs,
+ const GlyphMap& glyphMap,
std::size_t start,
std::size_t end,
float justify) {
@@ -100,9 +100,13 @@ void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
}
PositionedGlyph& glyph = positionedGlyphs[end];
- auto it = glyphs.find(glyph.glyph);
- if (it != glyphs.end() && it->second) {
- const uint32_t lastAdvance = (*it->second)->metrics.advance;
+ auto glyphs = glyphMap.find(glyph.font);
+ if (glyphs == glyphMap.end()) {
+ return;
+ }
+ auto it = glyphs->second.find(glyph.glyph);
+ if (it != glyphs->second.end() && it->second) {
+ const float lastAdvance = (*it->second)->metrics.advance * glyph.scale;
const float lineIndent = float(glyph.x + lastAdvance) * justify;
for (std::size_t j = start; j <= end; j++) {
@@ -111,17 +115,25 @@ void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
}
}
-float determineAverageLineWidth(const std::u16string& logicalInput,
+float determineAverageLineWidth(const TaggedString& logicalInput,
const float spacing,
float maxWidth,
- const Glyphs& glyphs) {
+ const GlyphMap& glyphMap) {
float totalWidth = 0;
- for (char16_t chr : logicalInput) {
- auto it = glyphs.find(chr);
- if (it != glyphs.end() && it->second) {
- totalWidth += (*it->second)->metrics.advance + spacing;
+ for (std::size_t i = 0; i < logicalInput.length(); i++) {
+ const SectionOptions& section = logicalInput.getSection(i);
+ char16_t codePoint = logicalInput.getCharCodeAt(i);
+ auto glyphs = glyphMap.find(section.fontStackHash);
+ if (glyphs == glyphMap.end()) {
+ continue;
+ }
+ auto it = glyphs->second.find(codePoint);
+ if (it == glyphs->second.end() || !it->second) {
+ continue;
}
+
+ totalWidth += (*it->second)->metrics.advance * section.scale + spacing;
}
int32_t targetLineCount = ::fmax(1, std::ceil(totalWidth / maxWidth));
@@ -209,11 +221,11 @@ std::set<std::size_t> leastBadBreaks(const PotentialBreak& lastLineBreak) {
// We determine line breaks based on shaped text in logical order. Working in visual order would be
// more intuitive, but we can't do that because the visual order may be changed by line breaks!
-std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput,
+std::set<std::size_t> determineLineBreaks(const TaggedString& logicalInput,
const float spacing,
float maxWidth,
const WritingModeType writingMode,
- const Glyphs& glyphs) {
+ const GlyphMap& glyphMap) {
if (!maxWidth || writingMode != WritingModeType::Horizontal) {
return {};
}
@@ -222,40 +234,45 @@ std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput,
return {};
}
- const float targetWidth = determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphs);
+ const float targetWidth = determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphMap);
std::list<PotentialBreak> potentialBreaks;
float currentX = 0;
- for (std::size_t i = 0; i < logicalInput.size(); i++) {
- const char16_t codePoint = logicalInput[i];
- auto it = glyphs.find(codePoint);
- if (it != glyphs.end() && it->second && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) {
- currentX += (*it->second)->metrics.advance + spacing;
+ for (std::size_t i = 0; i < logicalInput.length(); i++) {
+ const SectionOptions& section = logicalInput.getSection(i);
+ char16_t codePoint = logicalInput.getCharCodeAt(i);
+ auto glyphs = glyphMap.find(section.fontStackHash);
+ if (glyphs == glyphMap.end()) {
+ continue;
+ }
+ auto it = glyphs->second.find(codePoint);
+ if (it != glyphs->second.end() && it->second && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) {
+ currentX += (*it->second)->metrics.advance * section.scale + spacing;
}
// Ideographic characters, spaces, and word-breaking punctuation that often appear without
// surrounding spaces.
- if ((i < logicalInput.size() - 1) &&
+ if ((i < logicalInput.length() - 1) &&
(util::i18n::allowsWordBreaking(codePoint) || util::i18n::allowsIdeographicBreaking(codePoint))) {
potentialBreaks.push_back(evaluateBreak(i+1, currentX, targetWidth, potentialBreaks,
- calculatePenalty(codePoint, logicalInput[i+1]),
+ calculatePenalty(codePoint, logicalInput.getCharCodeAt(i+1)),
false));
}
}
- return leastBadBreaks(evaluateBreak(logicalInput.size(), currentX, targetWidth, potentialBreaks, 0, true));
+ return leastBadBreaks(evaluateBreak(logicalInput.length(), currentX, targetWidth, potentialBreaks, 0, true));
}
void shapeLines(Shaping& shaping,
- const std::vector<std::u16string>& lines,
- const float spacing,
- const float lineHeight,
- const style::SymbolAnchorType textAnchor,
- const style::TextJustifyType textJustify,
- const float verticalHeight,
- const WritingModeType writingMode,
- const Glyphs& glyphs) {
+ std::vector<TaggedString>& lines,
+ const float spacing,
+ const float lineHeight,
+ const style::SymbolAnchorType textAnchor,
+ const style::TextJustifyType textJustify,
+ const float verticalHeight,
+ const WritingModeType writingMode,
+ const GlyphMap& glyphMap) {
// the y offset *should* be part of the font metadata
const int32_t yOffset = -17;
@@ -265,13 +282,16 @@ void shapeLines(Shaping& shaping,
float maxLineLength = 0;
+
const float justify = textJustify == style::TextJustifyType::Right ? 1 :
textJustify == style::TextJustifyType::Left ? 0 :
0.5;
- for (std::u16string line : lines) {
+ for (TaggedString& line : lines) {
// Collapse whitespace so it doesn't throw off justification
- boost::algorithm::trim_if(line, boost::algorithm::is_any_of(u" \t\n\v\f\r"));
+ line.trim();
+
+ const double lineMaxScale = line.getMaxScale();
if (line.empty()) {
y += lineHeight; // Still need a line feed after empty line
@@ -279,20 +299,31 @@ void shapeLines(Shaping& shaping,
}
std::size_t lineStartIndex = shaping.positionedGlyphs.size();
- for (char16_t chr : line) {
- auto it = glyphs.find(chr);
- if (it == glyphs.end() || !it->second) {
+ for (std::size_t i = 0; i < line.length(); i++) {
+ const SectionOptions& section = line.getSection(i);
+ char16_t codePoint = line.getCharCodeAt(i);
+ auto glyphs = glyphMap.find(section.fontStackHash);
+ if (glyphs == glyphMap.end()) {
+ continue;
+ }
+ auto it = glyphs->second.find(codePoint);
+ if (it == glyphs->second.end() || !it->second) {
continue;
}
+ // 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 Glyph& glyph = **it->second;
- if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(chr)) {
- shaping.positionedGlyphs.emplace_back(chr, x, y, false);
- x += glyph.metrics.advance + spacing;
+ if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(codePoint)) {
+ shaping.positionedGlyphs.emplace_back(codePoint, x, y + baselineOffset, false, section.fontStackHash, section.scale);
+ x += glyph.metrics.advance * section.scale + spacing;
} else {
- shaping.positionedGlyphs.emplace_back(chr, x, 0, true);
- x += verticalHeight + spacing;
+ shaping.positionedGlyphs.emplace_back(codePoint, x, baselineOffset, true, section.fontStackHash, section.scale);
+ x += verticalHeight * section.scale + spacing;
}
}
@@ -301,19 +332,19 @@ void shapeLines(Shaping& shaping,
float lineLength = x - spacing; // Don't count trailing spacing
maxLineLength = util::max(lineLength, maxLineLength);
- justifyLine(shaping.positionedGlyphs, glyphs, lineStartIndex,
+ justifyLine(shaping.positionedGlyphs, glyphMap, lineStartIndex,
shaping.positionedGlyphs.size() - 1, justify);
}
x = 0;
- y += lineHeight;
+ y += lineHeight * lineMaxScale;
}
auto anchorAlign = getAnchorAlignment(textAnchor);
align(shaping, justify, anchorAlign.horizontalAlign, anchorAlign.verticalAlign, maxLineLength,
lineHeight, lines.size());
- const float height = lines.size() * lineHeight;
+ const float height = y - yOffset;
// Calculate the bounding box
shaping.top += -anchorAlign.verticalAlign * height;
@@ -322,7 +353,7 @@ void shapeLines(Shaping& shaping,
shaping.right = shaping.left + maxLineLength;
}
-const Shaping getShaping(const std::u16string& logicalInput,
+const Shaping getShaping(const TaggedString& formattedString,
const float maxWidth,
const float lineHeight,
const style::SymbolAnchorType textAnchor,
@@ -332,12 +363,23 @@ const Shaping getShaping(const std::u16string& logicalInput,
const float verticalHeight,
const WritingModeType writingMode,
BiDi& bidi,
- const Glyphs& glyphs) {
+ const GlyphMap& glyphs) {
Shaping shaping(translate.x, translate.y, writingMode);
- std::vector<std::u16string> reorderedLines =
- bidi.processText(logicalInput,
- determineLineBreaks(logicalInput, spacing, maxWidth, writingMode, glyphs));
+ std::vector<TaggedString> reorderedLines;
+ if (formattedString.sectionCount() == 1) {
+ auto untaggedLines = bidi.processText(formattedString.rawText(),
+ determineLineBreaks(formattedString, spacing, maxWidth, writingMode, glyphs));
+ for (const auto& line : untaggedLines) {
+ reorderedLines.emplace_back(line, formattedString.sectionAt(0));
+ }
+ } else {
+ auto processedLines = bidi.processStyledText(formattedString.getStyledText(),
+ determineLineBreaks(formattedString, spacing, maxWidth, writingMode, glyphs));
+ for (const auto& line : processedLines) {
+ reorderedLines.emplace_back(line, formattedString.getSections());
+ }
+ }
shapeLines(shaping, reorderedLines, spacing, lineHeight, textAnchor,
textJustify, verticalHeight, writingMode, glyphs);
diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp
index 0a961849e5..50ac893098 100644
--- a/src/mbgl/text/shaping.hpp
+++ b/src/mbgl/text/shaping.hpp
@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/text/glyph.hpp>
+#include <mbgl/text/tagged_string.hpp>
#include <mbgl/renderer/image_atlas.hpp>
#include <mbgl/style/types.hpp>
@@ -45,7 +46,7 @@ public:
float angle() const { return _angle; }
};
-const Shaping getShaping(const std::u16string& string,
+const Shaping getShaping(const TaggedString& string,
float maxWidth,
float lineHeight,
style::SymbolAnchorType textAnchor,
@@ -55,6 +56,6 @@ const Shaping getShaping(const std::u16string& string,
float verticalHeight,
const WritingModeType,
BiDi& bidi,
- const Glyphs& glyphs);
+ const GlyphMap& glyphs);
} // namespace mbgl
diff --git a/src/mbgl/text/tagged_string.cpp b/src/mbgl/text/tagged_string.cpp
new file mode 100644
index 0000000000..e199a7c962
--- /dev/null
+++ b/src/mbgl/text/tagged_string.cpp
@@ -0,0 +1,42 @@
+#include <mbgl/text/tagged_string.hpp>
+#include <mbgl/util/i18n.hpp>
+
+#include <boost/algorithm/string.hpp>
+
+namespace mbgl {
+
+void TaggedString::addSection(const std::u16string& sectionText, double scale, FontStackHash fontStack) {
+ styledText.first += sectionText;
+ sections.emplace_back(scale, fontStack);
+ styledText.second.resize(styledText.first.size(), sections.size() - 1);
+}
+
+void TaggedString::trim() {
+ auto whiteSpace = boost::algorithm::is_any_of(u" \t\n\v\f\r");
+ std::size_t beginningWhitespace = styledText.first.find_first_not_of(u" \t\n\v\f\r");
+ if (beginningWhitespace == std::u16string::npos) {
+ // Entirely whitespace
+ styledText.first.clear();
+ styledText.second.clear();
+ } else {
+ std::size_t trailingWhitespace = styledText.first.find_last_not_of(u" \t\n\v\f\r") + 1;
+
+ styledText.first = styledText.first.substr(beginningWhitespace, trailingWhitespace - beginningWhitespace);
+ styledText.second = std::vector<uint8_t>(styledText.second.begin() + beginningWhitespace, styledText.second.begin() + trailingWhitespace);
+ }
+}
+
+double TaggedString::getMaxScale() const {
+ double maxScale = 0.0;
+ for (std::size_t i = 0; i < styledText.first.length(); i++) {
+ maxScale = std::max(maxScale, getSection(i).scale);
+ }
+ return maxScale;
+}
+
+void TaggedString::verticalizePunctuation() {
+ // Relies on verticalization changing characters in place so that style indices don't need updating
+ styledText.first = util::i18n::verticalizePunctuation(styledText.first);
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/text/tagged_string.hpp b/src/mbgl/text/tagged_string.hpp
new file mode 100644
index 0000000000..476c2225f0
--- /dev/null
+++ b/src/mbgl/text/tagged_string.hpp
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/text/bidi.hpp>
+
+namespace mbgl {
+
+struct SectionOptions {
+ SectionOptions(double scale_, FontStackHash fontStackHash_)
+ : scale(scale_), fontStackHash(fontStackHash_)
+ {}
+
+ double scale;
+ FontStackHash fontStackHash;
+};
+
+/**
+ * A TaggedString is the shaping-code counterpart of the Formatted type
+ * Whereas Formatted matches the logical structure of a 'format' expression,
+ * a TaggedString represents the same data at a per-character level so that
+ * character-rearranging operations (e.g. BiDi) preserve formatting.
+ * Text is represented as:
+ * - A string of characters
+ * - A matching array of indices, pointing to:
+ * - An array of SectionsOptions, representing the evaluated formatting
+ * options of the original sections.
+ *
+ * Once the guts of a TaggedString have been re-arranged by BiDi, you can
+ * iterate over the contents in order, using getCharCodeAt and getSection
+ * to get the formatting options for each character in turn.
+ */
+struct TaggedString {
+ TaggedString() = default;
+
+ TaggedString(std::u16string text_, SectionOptions options)
+ : styledText(std::move(text_),
+ std::vector<uint8_t>(text_.size(), 0)) {
+ sections.push_back(std::move(options));
+ }
+
+ TaggedString(StyledText styledText_, std::vector<SectionOptions> sections_)
+ : styledText(std::move(styledText_))
+ , sections(std::move(sections_)) {
+ }
+
+ std::size_t length() const {
+ return styledText.first.length();
+ }
+
+ std::size_t sectionCount() const {
+ return sections.size();
+ }
+
+ bool empty() const {
+ return styledText.first.empty();
+ }
+
+ const SectionOptions& getSection(std::size_t index) const {
+ return sections.at(styledText.second.at(index));
+ }
+
+ char16_t getCharCodeAt(std::size_t index) const {
+ return styledText.first[index];
+ }
+
+ const std::u16string& rawText() const {
+ return styledText.first;
+ }
+
+ const StyledText& getStyledText() const {
+ return styledText;
+ }
+
+ void addSection(const std::u16string& text, double scale, FontStackHash fontStack);
+ const SectionOptions& sectionAt(std::size_t index) const {
+ return sections.at(index);
+ }
+
+ const std::vector<SectionOptions>& getSections() const {
+ return sections;
+ }
+
+ uint8_t getSectionIndex(std::size_t characterIndex) const {
+ return styledText.second.at(characterIndex);
+ }
+
+ double getMaxScale() const;
+ void trim();
+
+ void verticalizePunctuation();
+
+private:
+ StyledText styledText;
+ std::vector<SectionOptions> sections;
+};
+
+} // namespace mbgl