diff --git a/src/mbgl/text/glyph_set.cpp b/src/mbgl/text/glyph_set.cpp index 0875a83850d..d2b7f65e978 100644 --- a/src/mbgl/text/glyph_set.cpp +++ b/src/mbgl/text/glyph_set.cpp @@ -71,6 +71,85 @@ void align(Shaping &shaping, const float justify, const float horizontalAlign, } } +// Returns true if the glyph is a strong right-to-left glyph. +bool isRTL(uint32_t glyph) { + // Use the major RTL Unicode code blocks as a rough approximation. + return ((glyph >= 0x600 && glyph <= 0x6ff && (glyph < 0x660 || glyph > 0x669) && glyph != 0x66b && glyph != 0x66c && (glyph < 0x6f0 || glyph > 0x6f9)) /* Arabic */ + || (glyph >= 0x750 && glyph <= 0x77f) /* Arabic Supplement */ + || (glyph >= 0x8a0 && glyph <= 0x8ff) /* Arabic Extended-A */ + || (glyph >= 0x590 && glyph <= 0x5ff) /* Hebrew */ + || (glyph >= 0x700 && glyph <= 0x74f) /* Syriac */ + || (glyph >= 0x780 && glyph <= 0x7bf) /* Thaana */); +} + +// Reverse the string to right-to-left orientation if necessary. +void reverse(std::vector &positionedGlyphs, const std::map &sdfs) { + size_t numRTLChars = 0; + for (auto& positionedGlyph : positionedGlyphs) { + auto glyph = positionedGlyph.glyph; + if (isRTL(glyph)) { + numRTLChars++; + } + } + // Treat the entire string as right-to-left if most characters are strong right-to-left characters. + bool isMostlyRTL = numRTLChars >= static_cast(positionedGlyphs.size()) / 2.0f; + if (isMostlyRTL) { + // Reverse the entire string. + for (auto& glyph : positionedGlyphs) { + glyph.x *= -1; + auto it = sdfs.find(glyph.glyph); + if (it != sdfs.end()) { + glyph.x -= it->second.metrics.advance; + } + } + // For bilingual labels, unreverse short spans of LTR text in predominantly RTL strings. + for (auto start = positionedGlyphs.begin(), end = start; end != positionedGlyphs.end(); ++end) { + if (start != end && (isRTL(end->glyph) || end->y != (end - 1)->y)) { + // End of LTR run or line wrap. + auto left = (end - 1)->x; + auto right = start->x; + auto leftSDF = sdfs.find((end - 1)->glyph); + if (leftSDF != sdfs.end()) { + right -= leftSDF->second.metrics.advance; + } + for (; start != end; ++start) { + start->x = left + right - start->x; + auto sdf = sdfs.find(start->glyph); + if (sdf != sdfs.end()) { + start->x += sdf->second.metrics.advance; + } + } + ++start; + } else if (isRTL(end->glyph)) { + ++start; + } + } + } else { + // For bilingual labels, reverse short spans of RTL text in predominantly LTR strings. + for (auto start = positionedGlyphs.begin(), end = start; end != positionedGlyphs.end(); ++end) { + if (start != end && (!isRTL(end->glyph) || end->y != (end - 1)->y)) { + // End of RTL run or line wrap. + auto left = start->x; + auto right = (end - 1)->x; + auto rightSDF = sdfs.find((end - 1)->glyph); + if (rightSDF != sdfs.end()) { + right += rightSDF->second.metrics.advance; + } + for (; start != end; ++start) { + start->x = left + right - start->x; + auto sdf = sdfs.find(start->glyph); + if (sdf != sdfs.end()) { + start->x -= sdf->second.metrics.advance; + } + } + ++start; + } else if (!isRTL(end->glyph)) { + ++start; + } + } + } +} + void justifyLine(std::vector &positionedGlyphs, const std::map &sdfs, uint32_t start, uint32_t end, float justify) { PositionedGlyph &glyph = positionedGlyphs[end]; @@ -159,6 +238,7 @@ void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, const float ma justifyLine(positionedGlyphs, sdfs, lineStartIndex, uint32_t(positionedGlyphs.size()) - 1, justify); align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line, translate); + reverse(positionedGlyphs, sdfs); // Calculate the bounding box shaping.top += -verticalAlign * height;