From bc718257748f1ad87658e85f8c31b574afca57a9 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Wed, 19 Sep 2018 15:20:03 -0700 Subject: [core] Bidi support for styled text. Remove use of QString from non-ICU Qt stub bidi implementation since we weren't making use of it. --- platform/default/bidi.cpp | 102 ++++++++++++++++++++++++++++++++++++++++++++++ platform/qt/src/bidi.cpp | 43 ++++++++++--------- src/mbgl/text/bidi.hpp | 4 ++ 3 files changed, 127 insertions(+), 22 deletions(-) diff --git a/platform/default/bidi.cpp b/platform/default/bidi.cpp index d475c387b3..f972e43cc1 100644 --- a/platform/default/bidi.cpp +++ b/platform/default/bidi.cpp @@ -100,6 +100,108 @@ std::vector BiDi::processText(const std::u16string& input, return applyLineBreaking(lineBreakPoints); } + +std::vector BiDi::processStyledText(const StyledText& input, std::set lineBreakPoints) { + std::vector lines; + const auto& inputText = input.first; + const auto& styleIndices = input.second; + + UErrorCode errorCode = U_ZERO_ERROR; + + ubidi_setPara(impl->bidiText, mbgl::utf16char_cast(inputText.c_str()), static_cast(inputText.size()), + UBIDI_DEFAULT_LTR, nullptr, &errorCode); + + if (U_FAILURE(errorCode)) { + throw std::runtime_error(std::string("BiDi::processStyledText: ") + u_errorName(errorCode)); + } + + mergeParagraphLineBreaks(lineBreakPoints); + + std::size_t lineStartIndex = 0; + + for (std::size_t lineBreakPoint : lineBreakPoints) { + StyledText line; + line.second.reserve(lineBreakPoint - lineStartIndex); + + errorCode = U_ZERO_ERROR; + ubidi_setLine(impl->bidiText, static_cast(lineStartIndex), static_cast(lineBreakPoint), impl->bidiLine, &errorCode); + if (U_FAILURE(errorCode)) { + throw std::runtime_error(std::string("BiDi::processStyledText (setLine): ") + u_errorName(errorCode)); + } + + errorCode = U_ZERO_ERROR; + uint32_t runCount = ubidi_countRuns(impl->bidiLine, &errorCode); + if (U_FAILURE(errorCode)) { + throw std::runtime_error(std::string("BiDi::processStyledText (countRuns): ") + u_errorName(errorCode)); + } + + for (uint32_t runIndex = 0; runIndex < runCount; runIndex++) { + int32_t runLogicalStart; + int32_t runLength; + UBiDiDirection direction = ubidi_getVisualRun(impl->bidiLine, runIndex, &runLogicalStart, &runLength); + const bool isReversed = direction == UBIDI_RTL; + + std::size_t logicalStart = lineStartIndex + runLogicalStart; + std::size_t logicalEnd = logicalStart + runLength; + if (isReversed) { + // Within this reversed section, iterate logically backwards + // Each time we see a change in style, render a reversed chunk + // of everything since the last change + std::size_t styleRunStart = logicalEnd; + uint8_t currentStyleIndex = styleIndices.at(styleRunStart - 1); + for (std::size_t i = logicalEnd - 1; i >= logicalStart; i--) { + if (currentStyleIndex != styleIndices.at(i) || i == logicalStart) { + std::size_t styleRunEnd = i == logicalStart ? i : i + 1; + std::u16string reversed = writeReverse(inputText, styleRunEnd, styleRunStart); + line.first += reversed; + for (std::size_t j = 0; j < reversed.size(); j++) { + line.second.push_back(currentStyleIndex); + } + currentStyleIndex = styleIndices.at(i); + styleRunStart = styleRunEnd; + } + if (i == 0) { + break; + } + } + + } else { + line.first += input.first.substr(logicalStart, runLength); + line.second.insert(line.second.end(), styleIndices.begin() + logicalStart, styleIndices.begin() + logicalStart + runLength); + } + } + + lines.push_back(line); + lineStartIndex = lineBreakPoint; + } + + return lines; +} + +std::u16string BiDi::writeReverse(const std::u16string& input, std::size_t logicalStart, std::size_t logicalEnd) { + UErrorCode errorCode = U_ZERO_ERROR; + int32_t logicalLength = static_cast(logicalEnd - logicalStart); + std::u16string outputText(logicalLength + 1, 0); + + // UBIDI_DO_MIRRORING: Apply unicode mirroring of characters like parentheses + // UBIDI_REMOVE_BIDI_CONTROLS: Now that all the lines are set, remove control characters so that + // they don't show up on screen (some fonts have glyphs representing them) + int32_t outputLength = + ubidi_writeReverse(mbgl::utf16char_cast(&input[logicalStart]), + logicalLength, + mbgl::utf16char_cast(&outputText[0]), + logicalLength + 1, // Extra room for null terminator, although we don't really need to have ICU write it for us + UBIDI_DO_MIRRORING | UBIDI_REMOVE_BIDI_CONTROLS, + &errorCode); + + if (U_FAILURE(errorCode)) { + throw std::runtime_error(std::string("BiDi::writeReverse: ") + u_errorName(errorCode)); + } + + outputText.resize(outputLength); // REMOVE_BIDI_CONTROLS may have shrunk the string + + return outputText; +} std::u16string BiDi::getLine(std::size_t start, std::size_t end) { UErrorCode errorCode = U_ZERO_ERROR; diff --git a/platform/qt/src/bidi.cpp b/platform/qt/src/bidi.cpp index b75d038d6b..6b680a9769 100644 --- a/platform/qt/src/bidi.cpp +++ b/platform/qt/src/bidi.cpp @@ -2,51 +2,50 @@ #include -#include - namespace mbgl { +// This stub implementation is stateless and doesn't implement the private +// methods used by the ICU BiDi class BiDiImpl { -public: - QString string; + // Used by the ICU implementation to hold onto internal BiDi state }; std::u16string applyArabicShaping(const std::u16string& input) { return input; } -void BiDi::mergeParagraphLineBreaks(std::set& lineBreakPoints) { - lineBreakPoints.insert(static_cast(impl->string.length())); +BiDi::BiDi() : impl(std::make_unique()) +{ } -std::vector -BiDi::applyLineBreaking(std::set lineBreakPoints) { - mergeParagraphLineBreaks(lineBreakPoints); +BiDi::~BiDi() = default; + +std::vector BiDi::processText(const std::u16string& input, std::set lineBreakPoints) { + lineBreakPoints.insert(input.length()); std::vector transformedLines; std::size_t start = 0; for (std::size_t lineBreakPoint : lineBreakPoints) { - transformedLines.push_back(getLine(start, lineBreakPoint)); + transformedLines.push_back(input.substr(start, lineBreakPoint - start)); start = lineBreakPoint; } return transformedLines; } -BiDi::BiDi() : impl(std::make_unique()) -{ -} - -BiDi::~BiDi() = default; +std::vector BiDi::processStyledText(const StyledText& input, std::set lineBreakPoints) { + lineBreakPoints.insert(input.first.length()); -std::vector BiDi::processText(const std::u16string& input, std::set lineBreakPoints) { - impl->string = QString::fromUtf16(reinterpret_cast(input.data()), int(input.size())); - return applyLineBreaking(lineBreakPoints); -} + std::vector transformedLines; + std::size_t start = 0; + for (std::size_t lineBreakPoint : lineBreakPoints) { + transformedLines.emplace_back( + input.first.substr(start, lineBreakPoint - start), + std::vector(input.second.begin() + start, input.second.begin() + lineBreakPoint)); + start = lineBreakPoint; + } -std::u16string BiDi::getLine(std::size_t start, std::size_t end) { - auto utf16 = impl->string.mid(static_cast(start), static_cast(end - start)); - return std::u16string(reinterpret_cast(utf16.utf16()), utf16.length()); + return transformedLines; } } // end namespace mbgl diff --git a/src/mbgl/text/bidi.hpp b/src/mbgl/text/bidi.hpp index 59d306489c..d90f3e5d1b 100644 --- a/src/mbgl/text/bidi.hpp +++ b/src/mbgl/text/bidi.hpp @@ -14,17 +14,21 @@ class BiDiImpl; std::u16string applyArabicShaping(const std::u16string&); +using StyledText = std::pair>; + class BiDi : private util::noncopyable { public: BiDi(); ~BiDi(); std::vector processText(const std::u16string&, std::set); + std::vector processStyledText(const StyledText&, std::set); private: void mergeParagraphLineBreaks(std::set&); std::vector applyLineBreaking(std::set); std::u16string getLine(std::size_t start, std::size_t end); + std::u16string writeReverse(const std::u16string&, std::size_t, std::size_t); std::unique_ptr impl; }; -- cgit v1.2.1