summaryrefslogtreecommitdiff
path: root/src/mbgl/text/glyph_set.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/text/glyph_set.cpp')
-rw-r--r--src/mbgl/text/glyph_set.cpp254
1 files changed, 155 insertions, 99 deletions
diff --git a/src/mbgl/text/glyph_set.cpp b/src/mbgl/text/glyph_set.cpp
index 0875a83850..f1cb85a03a 100644
--- a/src/mbgl/text/glyph_set.cpp
+++ b/src/mbgl/text/glyph_set.cpp
@@ -1,7 +1,11 @@
-#include <mbgl/text/glyph_set.hpp>
-#include <mbgl/platform/log.hpp>
#include <mbgl/math/minmax.hpp>
+#include <mbgl/text/glyph_set.hpp>
+#include <mbgl/util/i18n.hpp>
+#include <mbgl/util/logging.hpp>
+
+#include <boost/algorithm/string.hpp>
+#include <algorithm>
#include <cassert>
namespace mbgl {
@@ -26,44 +30,47 @@ void GlyphSet::insert(uint32_t id, SDFGlyph&& glyph) {
}
}
-const std::map<uint32_t, SDFGlyph> &GlyphSet::getSDFs() const {
+const std::map<uint32_t, SDFGlyph>& GlyphSet::getSDFs() const {
return sdfs;
}
-const Shaping GlyphSet::getShaping(const std::u32string &string, const float maxWidth,
- const float lineHeight, const float horizontalAlign,
- const float verticalAlign, const float justify,
- const float spacing, const Point<float> &translate) const {
- Shaping shaping(translate.x * 24, translate.y * 24, string);
+const Shaping GlyphSet::getShaping(const std::u16string& logicalInput,
+ const float maxWidth,
+ const float lineHeight,
+ const float horizontalAlign,
+ const float verticalAlign,
+ const float justify,
+ const float spacing,
+ const Point<float>& translate,
+ BiDi& bidi) const {
- // the y offset *should* be part of the font metadata
- const int32_t yOffset = -17;
+ // The string stored in shaping.text is used for finding duplicates, but may end up quite
+ // different from the glyphs that get shown
+ Shaping shaping(translate.x * 24, translate.y * 24, logicalInput);
- float x = 0;
- const float y = yOffset;
+ ProcessedBiDiText processedText = bidi.processText(logicalInput);
- // Loop through all characters of this label and shape.
- for (uint32_t chr : string) {
- auto it = sdfs.find(chr);
- if (it != sdfs.end()) {
- shaping.positionedGlyphs.emplace_back(chr, x, y);
- x += it->second.metrics.advance + spacing;
- }
- }
+ std::vector<std::u16string> reorderedLines =
+ processedText.applyLineBreaking(determineLineBreaks(logicalInput, spacing, maxWidth));
- if (shaping.positionedGlyphs.empty())
- return shaping;
-
- lineWrap(shaping, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify, translate);
+ shapeLines(shaping, reorderedLines, spacing, lineHeight, horizontalAlign, verticalAlign,
+ justify, translate);
return shaping;
}
-void align(Shaping &shaping, const float justify, const float horizontalAlign,
- const float verticalAlign, const uint32_t maxLineLength, const float lineHeight,
- const uint32_t line, const Point<float> &translate) {
- const float shiftX = (justify - horizontalAlign) * maxLineLength + ::round(translate.x * 24/* one em */);
- const float shiftY = (-verticalAlign * (line + 1) + 0.5) * lineHeight + ::round(translate.y * 24/* one em */);
+void align(Shaping& shaping,
+ const float justify,
+ const float horizontalAlign,
+ const float verticalAlign,
+ const float maxLineLength,
+ const float lineHeight,
+ const uint32_t lineCount,
+ const Point<float>& translate) {
+ const float shiftX =
+ (justify - horizontalAlign) * maxLineLength + ::round(translate.x * 24 /* one em */);
+ const float shiftY =
+ (-verticalAlign * lineCount + 0.5) * lineHeight + ::round(translate.y * 24 /* one em */);
for (auto& glyph : shaping.positionedGlyphs) {
glyph.x += shiftX;
@@ -71,9 +78,16 @@ void align(Shaping &shaping, const float justify, const float horizontalAlign,
}
}
-void justifyLine(std::vector<PositionedGlyph> &positionedGlyphs, const std::map<uint32_t, SDFGlyph> &sdfs, uint32_t start,
- uint32_t end, float justify) {
- PositionedGlyph &glyph = positionedGlyphs[end];
+// justify left = 0, right = 1, center = .5
+void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
+ const std::map<uint32_t, SDFGlyph>& sdfs,
+ uint32_t start,
+ uint32_t end,
+ float justify) {
+ if (!justify)
+ return;
+
+ PositionedGlyph& glyph = positionedGlyphs[end];
auto it = sdfs.find(glyph.glyph);
if (it != sdfs.end()) {
const uint32_t lastAdvance = it->second.metrics.advance;
@@ -85,80 +99,122 @@ void justifyLine(std::vector<PositionedGlyph> &positionedGlyphs, const std::map<
}
}
-void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, const float maxWidth,
- const float horizontalAlign, const float verticalAlign,
- const float justify, const Point<float> &translate) const {
+float GlyphSet::determineIdeographicLineWidth(const std::u16string& logicalInput,
+ const float spacing,
+ float maxWidth) const {
+ float totalWidth = 0;
+
+ // totalWidth doesn't include the last character for magical tuning reasons. This makes the
+ // algorithm a little
+ // more agressive about trying to fit the text into fewer lines, taking advantage of the
+ // tolerance for going a little
+ // over maxWidth
+ for (uint32_t i = 0; i < logicalInput.size() - 1; i++) {
+ auto it = sdfs.find(logicalInput[i]);
+ if (it != sdfs.end())
+ totalWidth += it->second.metrics.advance + spacing;
+ }
+
+ int32_t lineCount = std::fmax(1, std::ceil(totalWidth / maxWidth));
+ return totalWidth / lineCount;
+}
+
+// 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<int32_t> GlyphSet::determineLineBreaks(const std::u16string& logicalInput,
+ const float spacing,
+ float maxWidth) const {
+ if (!maxWidth)
+ return {};
+
+ if (logicalInput.empty())
+ return {};
+
+ if (util::i18n::allowsIdeographicBreaking(logicalInput))
+ maxWidth = determineIdeographicLineWidth(logicalInput, spacing, maxWidth);
+
+ std::set<int32_t> lineBreakPoints;
+ float currentX = 0;
uint32_t lastSafeBreak = 0;
+ float lastSafeBreakX = 0;
+
+ for (uint32_t i = 0; i < logicalInput.size(); i++) {
+ auto it = sdfs.find(logicalInput[i]);
+ if (it == sdfs.end())
+ continue;
+
+ const SDFGlyph& glyph = it->second;
- uint32_t lengthBeforeCurrentLine = 0;
- uint32_t lineStartIndex = 0;
- uint32_t line = 0;
-
- uint32_t maxLineLength = 0;
-
- std::vector<PositionedGlyph> &positionedGlyphs = shaping.positionedGlyphs;
-
- if (maxWidth) {
- for (uint32_t i = 0; i < positionedGlyphs.size(); i++) {
- PositionedGlyph &shape = positionedGlyphs[i];
-
- shape.x -= lengthBeforeCurrentLine;
- shape.y += lineHeight * line;
-
- if (shape.x > maxWidth && lastSafeBreak > 0) {
-
- uint32_t lineLength = positionedGlyphs[lastSafeBreak + 1].x;
- maxLineLength = util::max(lineLength, maxLineLength);
-
- for (uint32_t k = lastSafeBreak + 1; k <= i; k++) {
- positionedGlyphs[k].y += lineHeight;
- positionedGlyphs[k].x -= lineLength;
- }
-
- if (justify) {
- // Collapse invisible characters.
- uint32_t breakGlyph = positionedGlyphs[lastSafeBreak].glyph;
- uint32_t lineEnd = lastSafeBreak;
- if (breakGlyph == 0x20 /* space */
- || breakGlyph == 0x200b /* zero-width space */) {
- lineEnd--;
- }
-
- justifyLine(positionedGlyphs, sdfs, lineStartIndex, lineEnd, justify);
- }
-
- lineStartIndex = lastSafeBreak + 1;
- lastSafeBreak = 0;
- lengthBeforeCurrentLine += lineLength;
- line++;
- }
-
- // Spaces, plus word-breaking punctuation that often appears without surrounding spaces.
- if (shape.glyph == 0x20 /* space */
- || shape.glyph == 0x26 /* ampersand */
- || shape.glyph == 0x2b /* plus sign */
- || shape.glyph == 0x2d /* hyphen-minus */
- || shape.glyph == 0x2f /* solidus */
- || shape.glyph == 0xad /* soft hyphen */
- || shape.glyph == 0xb7 /* middle dot */
- || shape.glyph == 0x200b /* zero-width space */
- || shape.glyph == 0x2010 /* hyphen */
- || shape.glyph == 0x2013 /* en dash */) {
- lastSafeBreak = i;
- }
+ // Ideographic characters, spaces, and word-breaking punctuation that often appear without
+ // surrounding spaces.
+ if (util::i18n::allowsWordBreaking(glyph.id) ||
+ util::i18n::allowsIdeographicBreaking(glyph.id)) {
+ lastSafeBreak = i;
+ lastSafeBreakX = currentX;
}
+
+ if (currentX > maxWidth && lastSafeBreak > 0) {
+ lineBreakPoints.insert(lastSafeBreak);
+ currentX -= lastSafeBreakX;
+ lastSafeBreakX = 0;
+ }
+
+ currentX += glyph.metrics.advance + spacing;
}
- const PositionedGlyph& lastPositionedGlyph = positionedGlyphs.back();
- const auto lastGlyphIt = sdfs.find(lastPositionedGlyph.glyph);
- assert(lastGlyphIt != sdfs.end());
- const uint32_t lastLineLength = lastPositionedGlyph.x + lastGlyphIt->second.metrics.advance;
- maxLineLength = std::max(maxLineLength, lastLineLength);
+ return lineBreakPoints;
+}
+
+void GlyphSet::shapeLines(Shaping& shaping,
+ const std::vector<std::u16string>& lines,
+ const float spacing,
+ const float lineHeight,
+ const float horizontalAlign,
+ const float verticalAlign,
+ const float justify,
+ const Point<float>& translate) const {
+
+ // the y offset *should* be part of the font metadata
+ const int32_t yOffset = -17;
+
+ float x = 0;
+ float y = yOffset;
+
+ float maxLineLength = 0;
- const uint32_t height = (line + 1) * lineHeight;
+ for (std::u16string 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"));
+
+ if (line.empty())
+ continue;
+
+ uint32_t lineStartIndex = static_cast<uint32_t>(shaping.positionedGlyphs.size());
+ for (char16_t chr : line) {
+ auto it = sdfs.find(chr);
+ if (it == sdfs.end())
+ continue;
+
+ const SDFGlyph& glyph = it->second;
+ shaping.positionedGlyphs.emplace_back(chr, x, y);
+ x += glyph.metrics.advance + spacing;
+ }
+
+ if (static_cast<uint32_t>(shaping.positionedGlyphs.size()) == lineStartIndex)
+ continue;
+
+ maxLineLength = util::max(x, maxLineLength);
+
+ justifyLine(shaping.positionedGlyphs, sdfs, lineStartIndex,
+ static_cast<uint32_t>(shaping.positionedGlyphs.size()) - 1, justify);
+
+ x = 0;
+ y += lineHeight; // Move to next line
+ }
- justifyLine(positionedGlyphs, sdfs, lineStartIndex, uint32_t(positionedGlyphs.size()) - 1, justify);
- align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line, translate);
+ align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight,
+ static_cast<uint32_t>(lines.size()), translate);
+ const uint32_t height = lines.size() * lineHeight;
// Calculate the bounding box
shaping.top += -verticalAlign * height;