From e28df21641c0423d96120123e0b4a98d9d41f605 Mon Sep 17 00:00:00 2001 From: Mat Sutcliffe Date: Tue, 24 Nov 2020 22:15:33 +0000 Subject: [PATCH] [xswiftbus] Fix UTF-8 bug when displaying text messages std::string is not UTF-8 aware, so it was possible to create malformed strings by splitting in the middle of a code point. The splitting code also did read beyond the end of the input string. Now we use a Unicode-aware iterator adaptor to help find the correct place to split the string across multiple lines. --- src/blackmisc/simulation/xplane/qtfreeutils.h | 54 +++++++++++++++++++ src/xswiftbus/service.cpp | 22 ++++---- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/blackmisc/simulation/xplane/qtfreeutils.h b/src/blackmisc/simulation/xplane/qtfreeutils.h index 791603c72..63acb69fd 100644 --- a/src/blackmisc/simulation/xplane/qtfreeutils.h +++ b/src/blackmisc/simulation/xplane/qtfreeutils.h @@ -17,6 +17,7 @@ #include #include #include +#include // Strict header only X-Plane model parser utils shared between BlackMisc and XSwiftBus. // Header only is necessary to no require XSwiftBus to link against BlackMisc. @@ -249,6 +250,59 @@ namespace BlackMisc acfProperties.modelString = simplifyWhitespace(stringForFlyableModel(acfProperties, filePath)); return acfProperties; } + + //! Encoding-aware iterator adaptor for std::u8string + template + struct Utf8Iterator + { + //! STL compatibility + //! @{ + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = std::forward_iterator_tag; + //! @} + + //! Default constructor + Utf8Iterator() = default; + + //! Constructor + Utf8Iterator(I base, I end) : base(base), end(end) {} + + //! Equality + //! @{ + friend bool operator ==(Utf8Iterator a, Utf8Iterator b) { return a.base == b.base; } + friend bool operator !=(Utf8Iterator a, Utf8Iterator b) { return a.base != b.base; } + friend bool operator ==(Utf8Iterator a, I b) { return a.base == b; } + friend bool operator !=(Utf8Iterator a, I b) { return a.base != b; } + friend bool operator ==(I a, Utf8Iterator b) { return a == b.base; } + friend bool operator !=(I a, Utf8Iterator b) { return a != b.base; } + //! @} + + //! Dereference (not encoding-aware) + reference operator *() const { return *base; } + + //! Pointer indirection (not encoding-aware) + pointer operator ->() const { return base.operator ->(); } + + //! Pre-increment + Utf8Iterator &operator ++() + { + constexpr auto isContinuation = [](auto c) + { + return (c & static_cast(0b11000000)) == static_cast(0b10000000); + }; + do { ++base; } while (base != end && isContinuation(*base)); + return *this; + } + + //! Post-increment + Utf8Iterator operator ++(int) { auto copy = *this; ++*this; return copy; } + + I base; //!< Underlying iterator + I end; //!< Underlying end iterator + }; } } // namespace } // namespace diff --git a/src/xswiftbus/service.cpp b/src/xswiftbus/service.cpp index 64c9496a1..f863af0a7 100644 --- a/src/xswiftbus/service.cpp +++ b/src/xswiftbus/service.cpp @@ -146,21 +146,23 @@ namespace XSwiftBus { if (text.empty()) { return; } static const CMessage::string ellipsis = u8"\u2026"; - const int lineLength = m_messages.maxLineLength() - static_cast(ellipsis.size()); + const unsigned lineLength = m_messages.maxLineLength() - 1; + + using U8It = Utf8Iterator; + U8It begin(text.begin(), text.end()); + auto characters = std::distance(begin, U8It(text.end(), text.end())); std::vector wrappedLines; - for (size_t i = 0; i < text.size(); i += static_cast(lineLength)) + + for (; characters > lineLength; characters -= lineLength) { - wrappedLines.emplace_back(text.begin() + i, text.begin() + i + static_cast(lineLength)); + auto end = std::next(begin, lineLength); + wrappedLines.emplace_back(begin.base, end.base); wrappedLines.back() += ellipsis; + begin = end; } - wrappedLines.back().erase(wrappedLines.back().size() - ellipsis.size()); - if (wrappedLines.back().empty()) { wrappedLines.pop_back(); } - else if (wrappedLines.back().size() == ellipsis.size() && wrappedLines.size() > 1) + if (characters > 0) { - auto secondLastLine = wrappedLines.end() - 2; - secondLastLine->erase(wrappedLines.back().size() - ellipsis.size()); - secondLastLine->append(wrappedLines.back()); - wrappedLines.pop_back(); + wrappedLines.emplace_back(begin.base, text.end()); } for (const auto &line : wrappedLines) {