From 045462a6aed2796976a2f5cf0042f9a0ac1493f7 Mon Sep 17 00:00:00 2001 From: Yuqian Yang Date: Fri, 17 Oct 2025 20:50:53 +0800 Subject: Implement Utf8/16 funcs. --- src/base/StringUtil.cpp | 258 +++++++++++++++-------------- src/ui/controls/TextHostControlService.cpp | 39 +++-- 2 files changed, 153 insertions(+), 144 deletions(-) (limited to 'src') diff --git a/src/base/StringUtil.cpp b/src/base/StringUtil.cpp index 4e622dab..581ebcab 100644 --- a/src/base/StringUtil.cpp +++ b/src/base/StringUtil.cpp @@ -7,8 +7,7 @@ #include #include -namespace cru { -namespace string { +namespace cru::string { std::weak_ordering CaseInsensitiveCompare(std::string_view left, std::string_view right) { @@ -78,18 +77,58 @@ std::vector Split(std::string_view str, std::string_view sep, return result; } -} // namespace string + +namespace { + +template NextCodePointFunction> +Index Until(const CharType* ptr, Index size, Index position, + const std::function& predicate) { + if (position <= 0) return position; + while (true) { + Index p = position; + auto c = NextCodePointFunction(ptr, size, p, &position); + if (predicate(c)) return p; + if (c == k_invalid_code_point) return p; + } + UnreachableCode(); +} + +static bool IsSpace(CodePoint c) { return c == 0x20 || c == 0xA; } + +template +using UntilFunctionType = Index (*)(const CharType*, Index, Index, + const std::function&); + +template NextCodePointFunction, + UntilFunctionType UntilFunction> +Index Word(const CharType* ptr, Index size, Index position, bool* is_space) { + if (position <= 0) return position; + auto c = NextCodePointFunction(ptr, size, position, nullptr); + if (IsSpace(c)) { // TODO: Currently only test against 0x20(space). + if (is_space) *is_space = true; + return UntilFunction(ptr, size, position, + [](CodePoint c) { return !IsSpace(c); }); + } else { + if (is_space) *is_space = false; + return UntilFunction(ptr, size, position, + [](CodePoint c) { return IsSpace(c); }); + } +} + +} // namespace using details::ExtractBits; -CodePoint Utf8NextCodePoint(const char* ptr, Index size, Index current, +CodePoint Utf8NextCodePoint(const Utf8CodeUnit* ptr, Index size, Index current, Index* next_position) { CodePoint result; if (current >= size) { result = k_invalid_code_point; } else { - const auto cu0 = static_cast(ptr[current++]); + const auto cu0 = static_cast(ptr[current++]); auto read_next_folowing_code = [ptr, size, ¤t]() -> CodePoint { if (current == size) @@ -97,14 +136,14 @@ CodePoint Utf8NextCodePoint(const char* ptr, Index size, Index current, "Unexpected end when read continuing byte of multi-byte code " "point."); - const auto u = static_cast(ptr[current]); + const auto u = static_cast(ptr[current]); if (!(u & (1u << 7)) || (u & (1u << 6))) { throw TextEncodeException( "Unexpected bad-format (not 0b10xxxxxx) continuing byte of " "multi-byte code point."); } - return ExtractBits(ptr[current++]); + return ExtractBits(ptr[current++]); }; if ((1u << 7) & cu0) { @@ -117,21 +156,21 @@ CodePoint Utf8NextCodePoint(const char* ptr, Index size, Index current, "code point."); } - const CodePoint s0 = ExtractBits(cu0) + const CodePoint s0 = ExtractBits(cu0) << (6 * 3); const CodePoint s1 = read_next_folowing_code() << (6 * 2); const CodePoint s2 = read_next_folowing_code() << 6; const CodePoint s3 = read_next_folowing_code(); result = s0 + s1 + s2 + s3; } else { // 3-length code point - const CodePoint s0 = ExtractBits(cu0) + const CodePoint s0 = ExtractBits(cu0) << (6 * 2); const CodePoint s1 = read_next_folowing_code() << 6; const CodePoint s2 = read_next_folowing_code(); result = s0 + s1 + s2; } } else { // 2-length code point - const CodePoint s0 = ExtractBits(cu0) + const CodePoint s0 = ExtractBits(cu0) << 6; const CodePoint s1 = read_next_folowing_code(); result = s0 + s1; @@ -149,8 +188,67 @@ CodePoint Utf8NextCodePoint(const char* ptr, Index size, Index current, return result; } -CodePoint Utf16NextCodePoint(const char16_t* ptr, Index size, Index current, - Index* next_position) { +CodePoint Utf8PreviousCodePoint(const Utf8CodeUnit* ptr, Index size, + Index current, Index* previous_position) { + CRU_UNUSED(size) + + CodePoint result; + if (current <= 0) { + result = k_invalid_code_point; + } else { + current--; + int i; + for (i = 0; i < 4; i++) { + if (IsUtf8LeadingByte(ptr[current])) { + break; + } + current--; + } + if (i == 4) { + throw TextEncodeException( + "Failed to find UTF-8 leading byte in 4 previous bytes."); + } + + result = Utf8NextCodePoint(ptr, size, current, nullptr); + } + + if (previous_position != nullptr) *previous_position = current; + return result; +} + +bool Utf8IsValidInsertPosition(const Utf8CodeUnit* ptr, Index size, + Index position) { + return position == 0 || position == size || + (position > 0 && position < size && IsUtf8LeadingByte(ptr[position])); +} + +Index Utf8BackwardUntil(const Utf8CodeUnit* ptr, Index size, Index position, + const std::function& predicate) { + return Until(ptr, size, position, + predicate); +} + +Index Utf8ForwardUntil(const Utf8CodeUnit* ptr, Index size, Index position, + const std::function& predicate) { + return Until(ptr, size, position, predicate); +} + +static bool IsSpace(CodePoint c) { return c == 0x20 || c == 0xA; } + +Index Utf8PreviousWord(const Utf8CodeUnit* ptr, Index size, Index position, + bool* is_space) { + return Word( + ptr, size, position, is_space); +} + +Index Utf8NextWord(const Utf8CodeUnit* ptr, Index size, Index position, + bool* is_space) { + return Word( + ptr, size, position, is_space); +} + +CodePoint Utf16NextCodePoint(const Utf16CodeUnit* ptr, Index size, + Index current, Index* next_position) { CodePoint result; if (current >= size) { @@ -172,8 +270,8 @@ CodePoint Utf16NextCodePoint(const char16_t* ptr, Index size, Index current, "Unexpected bad-range second code unit of surrogate pair."); } - const auto s0 = ExtractBits(cu0) << 10; - const auto s1 = ExtractBits(cu1); + const auto s0 = ExtractBits(cu0) << 10; + const auto s1 = ExtractBits(cu1); result = s0 + s1 + 0x10000; @@ -187,8 +285,8 @@ CodePoint Utf16NextCodePoint(const char16_t* ptr, Index size, Index current, return result; } -CodePoint Utf16PreviousCodePoint(const char16_t* ptr, Index size, Index current, - Index* previous_position) { +CodePoint Utf16PreviousCodePoint(const Utf16CodeUnit* ptr, Index size, + Index current, Index* previous_position) { CRU_UNUSED(size) CodePoint result; @@ -211,8 +309,8 @@ CodePoint Utf16PreviousCodePoint(const char16_t* ptr, Index size, Index current, "Unexpected bad-range first code unit of surrogate pair."); } - const auto s0 = ExtractBits(cu1) << 10; - const auto s1 = ExtractBits(cu0); + const auto s0 = ExtractBits(cu1) << 10; + const auto s1 = ExtractBits(cu0); result = s0 + s1 + 0x10000; @@ -226,7 +324,7 @@ CodePoint Utf16PreviousCodePoint(const char16_t* ptr, Index size, Index current, return result; } -bool Utf16IsValidInsertPosition(const char16_t* ptr, Index size, +bool Utf16IsValidInsertPosition(const Utf16CodeUnit* ptr, Index size, Index position) { if (position < 0) return false; if (position > size) return false; @@ -235,124 +333,28 @@ bool Utf16IsValidInsertPosition(const char16_t* ptr, Index size, return !IsUtf16SurrogatePairTrailing(ptr[position]); } -Index Utf16BackwardUntil(const char16_t* ptr, Index size, Index position, +Index Utf16BackwardUntil(const Utf16CodeUnit* ptr, Index size, Index position, const std::function& predicate) { - if (position <= 0) return position; - while (true) { - Index p = position; - auto c = Utf16PreviousCodePoint(ptr, size, p, &position); - if (predicate(c)) return p; - if (c == k_invalid_code_point) return p; - } - UnreachableCode(); + return Until(ptr, size, position, + predicate); } -Index Utf16ForwardUntil(const char16_t* ptr, Index size, Index position, +Index Utf16ForwardUntil(const Utf16CodeUnit* ptr, Index size, Index position, const std::function& predicate) { - if (position >= size) return position; - while (true) { - Index p = position; - auto c = Utf16NextCodePoint(ptr, size, p, &position); - if (predicate(c)) return p; - if (c == k_invalid_code_point) return p; - } - UnreachableCode(); + return Until(ptr, size, position, + predicate); } -inline bool IsSpace(CodePoint c) { return c == 0x20 || c == 0xA; } - -Index Utf16PreviousWord(const char16_t* ptr, Index size, Index position, +Index Utf16PreviousWord(const Utf16CodeUnit* ptr, Index size, Index position, bool* is_space) { - if (position <= 0) return position; - auto c = Utf16PreviousCodePoint(ptr, size, position, nullptr); - if (IsSpace(c)) { // TODO: Currently only test against 0x20(space). - if (is_space) *is_space = true; - return Utf16BackwardUntil(ptr, size, position, - [](CodePoint c) { return !IsSpace(c); }); - } else { - if (is_space) *is_space = false; - return Utf16BackwardUntil(ptr, size, position, IsSpace); - } + return Word( + ptr, size, position, is_space); } -Index Utf16NextWord(const char16_t* ptr, Index size, Index position, +Index Utf16NextWord(const Utf16CodeUnit* ptr, Index size, Index position, bool* is_space) { - if (position >= size) return position; - auto c = Utf16NextCodePoint(ptr, size, position, nullptr); - if (IsSpace(c)) { // TODO: Currently only test against 0x20(space). - if (is_space) *is_space = true; - return Utf16ForwardUntil(ptr, size, position, - [](CodePoint c) { return !IsSpace(c); }); - } else { - if (is_space) *is_space = false; - return Utf16ForwardUntil(ptr, size, position, IsSpace); - } -} - -char16_t ToLower(char16_t c) { - if (c >= u'A' && c <= u'Z') { - return c - u'A' + u'a'; - } - return c; -} - -char16_t ToUpper(char16_t c) { - if (c >= u'a' && c <= u'z') { - return c - u'a' + u'A'; - } - return c; -} - -bool IsWhitespace(char16_t c) { - return c == u' ' || c == u'\t' || c == u'\n' || c == u'\r'; -} - -bool IsDigit(char16_t c) { return c >= u'0' && c <= u'9'; } - -Utf8CodePointIterator CreateUtf8Iterator(const std::byte* buffer, Index size) { - return Utf8CodePointIterator(reinterpret_cast(buffer), size); -} - -Utf8CodePointIterator CreateUtf8Iterator(const std::vector& buffer) { - return CreateUtf8Iterator(buffer.data(), buffer.size()); -} - -CodePoint Utf8NextCodePoint(std::string_view str, Index current, - Index* next_position) { - NotImplemented(); -} - -CodePoint Utf8PreviousCodePoint(std::string_view str, Index current, - Index* next_position) { - NotImplemented(); -} - -// Return position after the character making predicate returns true or 0 if no -// character doing so. -Index Utf8BackwardUntil(std::string_view str, Index position, - const std::function& predicate) { - NotImplemented(); -} - -// Return position before the character making predicate returns true or -// str.size() if no character doing so. -Index Utf8ForwardUntil(std::string_view str, Index position, - const std::function& predicate) { - NotImplemented(); -} - -bool Utf8IsValidInsertPosition(std::string_view str, Index position) { - NotImplemented(); -} - -Index Utf8PreviousWord(std::string_view str, Index position, - bool* is_space) { - NotImplemented(); -} - -Index Utf8NextWord(std::string_view str, Index position, - bool* is_space) { - NotImplemented(); + return Word( + ptr, size, position, is_space); } -} // namespace cru +} // namespace cru::string diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp index bb723e3f..3c38c454 100644 --- a/src/ui/controls/TextHostControlService.cpp +++ b/src/ui/controls/TextHostControlService.cpp @@ -5,8 +5,6 @@ #include "cru/base/Base.h" #include "cru/base/StringUtil.h" #include "cru/base/log/Logger.h" -#include "cru/platform/graphics/Font.h" -#include "cru/platform/gui/Base.h" #include "cru/platform/gui/Clipboard.h" #include "cru/platform/gui/Cursor.h" #include "cru/platform/gui/InputMethod.h" @@ -16,7 +14,6 @@ #include "cru/ui/DebugFlags.h" #include "cru/ui/DeleteLater.h" #include "cru/ui/components/Menu.h" -#include "cru/ui/events/UiEvents.h" #include "cru/ui/helper/ShortcutHub.h" #include "cru/ui/host/WindowHost.h" #include "cru/ui/render/ScrollRenderObject.h" @@ -25,12 +22,15 @@ #include namespace cru::ui::controls { +using namespace cru::string; + TextControlMovePattern TextControlMovePattern::kLeft( "Left", helper::ShortcutKeyBind(platform::gui::KeyCode::Left), [](TextHostControlService* service, std::string_view text, Index current_position) { CRU_UNUSED(service) - Utf8PreviousCodePoint(text, current_position, ¤t_position); + Utf8PreviousCodePoint(text.data(), text.size(), current_position, + ¤t_position); return current_position; }); TextControlMovePattern TextControlMovePattern::kRight( @@ -38,7 +38,8 @@ TextControlMovePattern TextControlMovePattern::kRight( [](TextHostControlService* service, std::string_view text, Index current_position) { CRU_UNUSED(service) - Utf8NextCodePoint(text, current_position, ¤t_position); + Utf8NextCodePoint(text.data(), text.size(), current_position, + ¤t_position); return current_position; }); TextControlMovePattern TextControlMovePattern::kCtrlLeft( @@ -48,7 +49,7 @@ TextControlMovePattern TextControlMovePattern::kCtrlLeft( [](TextHostControlService* service, std::string_view text, Index current_position) { CRU_UNUSED(service) - return Utf8PreviousWord(text, current_position); + return Utf8PreviousWord(text.data(), text.size(), current_position); }); TextControlMovePattern TextControlMovePattern::kCtrlRight( "Ctrl+Right(Next Word)", @@ -57,7 +58,7 @@ TextControlMovePattern TextControlMovePattern::kCtrlRight( [](TextHostControlService* service, std::string_view text, Index current_position) { CRU_UNUSED(service) - return Utf8NextWord(text, current_position); + return Utf8NextWord(text.data(), text.size(), current_position); }); TextControlMovePattern TextControlMovePattern::kUp( "Up", helper::ShortcutKeyBind(platform::gui::KeyCode::Up), @@ -86,7 +87,7 @@ TextControlMovePattern TextControlMovePattern::kHome( [](TextHostControlService* service, std::string_view text, Index current_position) { CRU_UNUSED(service) - return Utf8BackwardUntil(text, current_position, + return Utf8BackwardUntil(text.data(), text.size(), current_position, [](CodePoint c) { return c == u'\n'; }); }); TextControlMovePattern TextControlMovePattern::kEnd( @@ -94,7 +95,7 @@ TextControlMovePattern TextControlMovePattern::kEnd( [](TextHostControlService* service, std::string_view text, Index current_position) { CRU_UNUSED(service) - return Utf8ForwardUntil(text, current_position, + return Utf8ForwardUntil(text.data(), text.size(), current_position, [](CodePoint c) { return c == u'\n'; }); }); TextControlMovePattern TextControlMovePattern::kCtrlHome( @@ -225,7 +226,8 @@ void TextHostControlService::SetText(std::string text, bool stop_composition) { void TextHostControlService::InsertText(Index position, std::string_view text, bool stop_composition) { - if (!Utf8IsValidInsertPosition(this->text_, position)) { + if (!Utf8IsValidInsertPosition(this->text_.data(), this->text_.size(), + position)) { CRU_LOG_TAG_ERROR("Invalid text insert position."); return; } @@ -239,26 +241,29 @@ void TextHostControlService::InsertText(Index position, std::string_view text, } void TextHostControlService::DeleteChar(Index position, bool stop_composition) { - if (!Utf8IsValidInsertPosition(this->text_, position)) { + if (!Utf8IsValidInsertPosition(this->text_.data(), this->text_.size(), + position)) { CRU_LOG_TAG_ERROR("Invalid text delete position."); return; } if (position == static_cast(this->text_.size())) return; Index next; - Utf8NextCodePoint(this->text_, position, &next); + Utf8NextCodePoint(this->text_.data(), this->text_.size(), position, &next); this->DeleteText(TextRange::FromTwoSides(position, next), stop_composition); } // Return the position of deleted character. Index TextHostControlService::DeleteCharPrevious(Index position, bool stop_composition) { - if (!Utf8IsValidInsertPosition(this->text_, position)) { + if (!Utf8IsValidInsertPosition(this->text_.data(), this->text_.size(), + position)) { CRU_LOG_TAG_ERROR("Invalid text delete position."); return 0; } if (position == 0) return 0; Index previous; - Utf8PreviousCodePoint(this->text_, position, &previous); + Utf8PreviousCodePoint(this->text_.data(), this->text_.size(), position, + &previous); this->DeleteText(TextRange::FromTwoSides(previous, position), stop_composition); return previous; @@ -268,11 +273,13 @@ void TextHostControlService::DeleteText(TextRange range, bool stop_composition) { if (range.count == 0) return; range = range.Normalize(); - if (!Utf8IsValidInsertPosition(this->text_, range.GetStart())) { + if (!Utf8IsValidInsertPosition(this->text_.data(), this->text_.size(), + range.GetStart())) { CRU_LOG_TAG_ERROR("Invalid text delete start position."); return; } - if (!Utf8IsValidInsertPosition(this->text_, range.GetStart())) { + if (!Utf8IsValidInsertPosition(this->text_.data(), this->text_.size(), + range.GetStart())) { CRU_LOG_TAG_ERROR("Invalid text delete end position."); return; } -- cgit v1.2.3