diff options
Diffstat (limited to 'src/mbgl/text/glyph_set.cpp')
-rw-r--r-- | src/mbgl/text/glyph_set.cpp | 254 |
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; |