diff --git a/src/ftxui/component/animation_test.cpp b/src/ftxui/component/animation_test.cpp index 9a33ef5..0f9c83a 100644 --- a/src/ftxui/component/animation_test.cpp +++ b/src/ftxui/component/animation_test.cpp @@ -26,8 +26,8 @@ TEST(AnimationTest, StartAndEnd) { animation::easing::BounceInOut, }; for (auto& it : functions) { - EXPECT_NEAR(0.f, it(0.f), 1.0e-4); - EXPECT_NEAR(1.f, it(1.f), 1.0e-4); + EXPECT_NEAR(0.F, it(0.F), 1.0e-4); + EXPECT_NEAR(1.F, it(1.F), 1.0e-4); } } diff --git a/src/ftxui/component/loop.cpp b/src/ftxui/component/loop.cpp index 2646428..8da8094 100644 --- a/src/ftxui/component/loop.cpp +++ b/src/ftxui/component/loop.cpp @@ -3,8 +3,9 @@ namespace ftxui { +// NOLINTNEXTLINE Loop::Loop(ScreenInteractive* screen, Component component) - : screen_(screen), component_(component) { + : screen_(screen), component_(std::move(component)) { screen_->PreMain(); } diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 657487d..90df36d 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -366,7 +366,7 @@ CapturedMouse ScreenInteractive::CaptureMouse() { } void ScreenInteractive::Loop(Component component) { // NOLINT - class Loop loop(this, component); + class Loop loop(this, std::move(component)); loop.Run(); } @@ -559,12 +559,13 @@ void ScreenInteractive::RunOnceBlocking(Component component) { RunOnce(component); } +// NOLINTNEXTLINE void ScreenInteractive::RunOnce(Component component) { Task task; while (task_receiver_->ReceiveNonBlocking(&task)) { HandleTask(component, task); } - Draw(component); + Draw(std::move(component)); } void ScreenInteractive::HandleTask(Component component, Task& task) { @@ -620,8 +621,9 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { // NOLINTNEXTLINE void ScreenInteractive::Draw(Component component) { - if (frame_valid_) + if (frame_valid_) { return; + } auto document = component->Render(); int dimx = 0; int dimy = 0; diff --git a/src/ftxui/dom/frame.cpp b/src/ftxui/dom/frame.cpp index 8aff60d..e57d83f 100644 --- a/src/ftxui/dom/frame.cpp +++ b/src/ftxui/dom/frame.cpp @@ -158,7 +158,7 @@ class FocusCursor : public Focus { private: void Render(Screen& screen) override { - Select::Render(screen); + Select::Render(screen); // NOLINT screen.SetCursor(Screen::Cursor{ box_.x_min, box_.y_min, diff --git a/src/ftxui/screen/string.cpp b/src/ftxui/screen/string.cpp index 175f02c..318279a 100644 --- a/src/ftxui/screen/string.cpp +++ b/src/ftxui/screen/string.cpp @@ -1,29 +1,16 @@ -// Most of this code is borrowed from: -// Markus Kuhn -- 2007-05-26 (Unicode 5.0) -// Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c +// Content of this file was created thanks to: +// - https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt +// - Markus Kuhn -- 2007-05-26 (Unicode 5.0) +// http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c // Thanks you! -// -// Modified by Arthur Sonzogni for FTXUI. #include "ftxui/screen/string.hpp" #include // for array -#include // for codecvt_utf8_utf16 #include // for uint32_t, uint8_t -#include // for wstring_convert #include // for string, basic_string, wstring #include // for std::ignore -// `codecvt_utf8_utf16 is deprecated in C++17. However there are no replacement. -// Microsoft provides one, but that's not standardized. Hence the two code path. -#if defined(_WIN32) -#include -#include -#else -#include // for codecvt_utf8_utf16 -#include // for wstring_convert -#endif - namespace { struct Interval { @@ -1587,56 +1574,56 @@ bool EatCodePoint(const std::string& input, *end = start + 1; return false; } - uint8_t byte_1 = input[start]; + uint8_t C0 = input[start]; // 1 byte string. - if ((byte_1 & 0b1000'0000) == 0b0000'0000) { // NOLINT - *ucs = byte_1 & 0b0111'1111; // NOLINT + if ((C0 & 0b1000'0000) == 0b0000'0000) { // NOLINT + *ucs = C0 & 0b0111'1111; // NOLINT *end = start + 1; return true; } // 2 byte string. - if ((byte_1 & 0b1110'0000) == 0b1100'0000 && // NOLINT + if ((C0 & 0b1110'0000) == 0b1100'0000 && // NOLINT start + 1 < input.size()) { - uint8_t byte_2 = input[start + 1]; + uint8_t C1 = input[start + 1]; *ucs = 0; - *ucs += byte_1 & 0b0001'1111; // NOLINT - *ucs <<= 6; // NOLINT - *ucs += byte_2 & 0b0011'1111; // NOLINT + *ucs += C0 & 0b0001'1111; // NOLINT + *ucs <<= 6; // NOLINT + *ucs += C1 & 0b0011'1111; // NOLINT *end = start + 2; return true; } // 3 byte string. - if ((byte_1 & 0b1111'0000) == 0b1110'0000 && // NOLINT + if ((C0 & 0b1111'0000) == 0b1110'0000 && // NOLINT start + 2 < input.size()) { - uint8_t byte_2 = input[start + 1]; - uint8_t byte_3 = input[start + 2]; + uint8_t C1 = input[start + 1]; + uint8_t C2 = input[start + 2]; *ucs = 0; - *ucs += byte_1 & 0b0000'1111; // NOLINT - *ucs <<= 6; // NOLINT - *ucs += byte_2 & 0b0011'1111; // NOLINT - *ucs <<= 6; // NOLINT - *ucs += byte_3 & 0b0011'1111; // NOLINT + *ucs += C0 & 0b0000'1111; // NOLINT + *ucs <<= 6; // NOLINT + *ucs += C1 & 0b0011'1111; // NOLINT + *ucs <<= 6; // NOLINT + *ucs += C2 & 0b0011'1111; // NOLINT *end = start + 3; return true; } // 4 byte string. - if ((byte_1 & 0b1111'1000) == 0b1111'0000 && // NOLINT + if ((C0 & 0b1111'1000) == 0b1111'0000 && // NOLINT start + 3 < input.size()) { - uint8_t byte_2 = input[start + 1]; - uint8_t byte_3 = input[start + 2]; - uint8_t byte_4 = input[start + 3]; + uint8_t C1 = input[start + 1]; + uint8_t C2 = input[start + 2]; + uint8_t C3 = input[start + 3]; *ucs = 0; - *ucs += byte_1 & 0b0000'0111; // NOLINT - *ucs <<= 6; // NOLINT - *ucs += byte_2 & 0b0011'1111; // NOLINT - *ucs <<= 6; // NOLINT - *ucs += byte_3 & 0b0011'1111; // NOLINT - *ucs <<= 6; // NOLINT - *ucs += byte_4 & 0b0011'1111; // NOLINT + *ucs += C0 & 0b0000'0111; // NOLINT + *ucs <<= 6; // NOLINT + *ucs += C1 & 0b0011'1111; // NOLINT + *ucs <<= 6; // NOLINT + *ucs += C2 & 0b0011'1111; // NOLINT + *ucs <<= 6; // NOLINT + *ucs += C3 & 0b0011'1111; // NOLINT *end = start + 4; return true; } @@ -1645,6 +1632,49 @@ bool EatCodePoint(const std::string& input, return false; } +// From UTF16 encoded string |input|, eat in between 1 and 4 byte representing +// one codepoint. Put the codepoint into |ucs|. Start at |start| and update +// |end| to represent the beginning of the next byte to eat for consecutive +// executions. +bool EatCodePoint(const std::wstring& input, + size_t start, + size_t* end, + uint32_t* ucs) { + if (start >= input.size()) { + *end = start + 1; + return false; + } + + + // On linux wstring uses the UTF32 encoding: + if constexpr (sizeof(wchar_t) == 4) { + *ucs = input[start]; // NOLINT + *end = start + 1; + return true; + } + + // On windows, wstring uses the UTF16 encoding: + int32_t C0 = input[start]; // NOLINT + + // 1 word size: + if (C0 < 0xd800 || C0 >= 0xdc00) { // NOLINT + *ucs = C0; + *end = start + 1; + return true; + } + + // 2 word size: + if (start + 1 >= input.size()) { + *end = start + 2; + return false; + } + + int32_t C1 = input[start + 1]; // NOLINT + *ucs = ((C0 & 0x3ff) << 10) + (C1 & 0x3ff) + 0x10000; // NOLINT + *end = start + 2; + return true; +} + } // namespace namespace ftxui { @@ -1865,32 +1895,107 @@ std::vector Utf8ToWordBreakProperty( /// Convert a UTF8 std::string into a std::wstring. std::string to_string(const std::wstring& s) { -#if defined(_WIN32) - if (s.empty()) - return std::string(); - int size = WideCharToMultiByte(CP_UTF8, 0, &s[0], (int)s.size(), nullptr, 0, - nullptr, nullptr); - std::string out(size, 0); - WideCharToMultiByte(CP_UTF8, 0, &s[0], (int)s.size(), &out[0], size, nullptr, - nullptr); + std::string out; + + size_t i = 0; + uint32_t codepoint = 0; + while (EatCodePoint(s, i, &i, &codepoint)) { + // Code point <-> UTF-8 conversion + // + // ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ + // ┃Byte 1 ┃Byte 2 ┃Byte 3 ┃Byte 4 ┃ + // ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ + // │0xxxxxxx│ │ │ │ + // ├────────┼────────┼────────┼────────┤ + // │110xxxxx│10xxxxxx│ │ │ + // ├────────┼────────┼────────┼────────┤ + // │1110xxxx│10xxxxxx│10xxxxxx│ │ + // ├────────┼────────┼────────┼────────┤ + // │11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│ + // └────────┴────────┴────────┴────────┘ + + // 1 byte UTF8 + if (codepoint <= 0b000'0000'0111'1111) { // NOLINT + uint8_t p1 = codepoint; + out.push_back(p1); // NOLINT + continue; + } + + // 2 bytes UTF8 + if (codepoint <= 0b000'0111'1111'1111) { // NOLINT + uint8_t p2 = codepoint & 0b111111; // NOLINT + codepoint >>= 6; // NOLINT + uint8_t p1 = codepoint; // NOLINT + out.push_back(0b11000000 + p1); // NOLINT + out.push_back(0b10000000 + p2); // NOLINT + continue; + } + + // 3 bytes UTF8 + if (codepoint <= 0b1111'1111'1111'1111) { // NOLINT + uint8_t p3 = codepoint & 0b111111; // NOLINT + codepoint >>= 6; // NOLINT + uint8_t p2 = codepoint & 0b111111; // NOLINT + codepoint >>= 6; // NOLINT + uint8_t p1 = codepoint; // NOLINT + out.push_back(0b11100000 + p1); // NOLINT + out.push_back(0b10000000 + p2); // NOLINT + out.push_back(0b10000000 + p3); // NOLINT + continue; + } + + // 4 bytes UTF8 + if (codepoint <= 0b1'0000'1111'1111'1111'1111) { // NOLINT + uint8_t p4 = codepoint & 0b111111; // NOLINT + codepoint >>= 6; // NOLINT + uint8_t p3 = codepoint & 0b111111; // NOLINT + codepoint >>= 6; // NOLINT + uint8_t p2 = codepoint & 0b111111; // NOLINT + codepoint >>= 6; // NOLINT + uint8_t p1 = codepoint; // NOLINT + out.push_back(0b11110000 + p1); // NOLINT + out.push_back(0b10000000 + p2); // NOLINT + out.push_back(0b10000000 + p3); // NOLINT + out.push_back(0b10000000 + p4); // NOLINT + continue; + } + + // Something else? + } return out; -#else - return std::wstring_convert>().to_bytes(s); -#endif } /// Convert a std::wstring into a UTF8 std::string. std::wstring to_wstring(const std::string& s) { -#if defined(_WIN32) - if (s.empty()) - return std::wstring(); - int size = MultiByteToWideChar(CP_UTF8, 0, &s[0], (int)s.size(), nullptr, 0); - std::wstring out(size, 0); - MultiByteToWideChar(CP_UTF8, 0, &s[0], (int)s.size(), &out[0], size); + std::wstring out; + + size_t i = 0; + uint32_t codepoint = 0; + while (EatCodePoint(s, i, &i, &codepoint)) { + // On linux wstring are UTF32 encoded: + if constexpr (sizeof(wchar_t) == 4) { + out.push_back(codepoint); // NOLINT + continue; + } + + // On Windows, wstring are UTF16 encoded: + + // Codepoint encoded using 1 word: + // NOLINTNEXTLINE + if (codepoint < 0xD800 || (codepoint > 0xDFFF && codepoint < 0x10000)) { + uint16_t p0 = codepoint; // NOLINT + out.push_back(p0); // NOLINT + continue; + } + + // Codepoint encoded using 2 words: + codepoint -= 0x010000; // NOLINT + uint16_t p0 = (((codepoint << 12) >> 22) + 0xD800); // NOLINT + uint16_t p1 = (((codepoint << 22) >> 22) + 0xDC00); // NOLINT + out.push_back(p0); // NOLINT + out.push_back(p1); // NOLINT + } return out; -#else - return std::wstring_convert>().from_bytes(s); -#endif } } // namespace ftxui diff --git a/src/ftxui/screen/string_test.cpp b/src/ftxui/screen/string_test.cpp index fd1a222..d75af1d 100644 --- a/src/ftxui/screen/string_test.cpp +++ b/src/ftxui/screen/string_test.cpp @@ -138,6 +138,28 @@ TEST(StringTest, Utf8ToWordBreakProperty) { EXPECT_EQ(Utf8ToWordBreakProperty("\n"), T({})); // FIXME } +TEST(StringTest, to_string) { + EXPECT_EQ(to_string(L"hello"), "hello"); + EXPECT_EQ(to_string(L"€"), "€"); + EXPECT_EQ(to_string(L"ÿ"), "ÿ"); + EXPECT_EQ(to_string(L"߿"), "߿"); + EXPECT_EQ(to_string(L"ɰɱ"), "ɰɱ"); + EXPECT_EQ(to_string(L"«»"), "«»"); + EXPECT_EQ(to_string(L"嵰嵲嵫"), "嵰嵲嵫"); + EXPECT_EQ(to_string(L"🎅🎄"), "🎅🎄"); +} + +TEST(StringTest, to_wstring) { + EXPECT_EQ(to_wstring(std::string("hello")), L"hello"); + EXPECT_EQ(to_wstring(std::string("€")), L"€"); + EXPECT_EQ(to_wstring(std::string("ÿ")), L"ÿ"); + EXPECT_EQ(to_wstring(std::string("߿")), L"߿"); + EXPECT_EQ(to_wstring(std::string("ɰɱ")), L"ɰɱ"); + EXPECT_EQ(to_wstring(std::string("«»")), L"«»"); + EXPECT_EQ(to_wstring(std::string("嵰嵲嵫")), L"嵰嵲嵫"); + EXPECT_EQ(to_wstring(std::string("🎅🎄")), L"🎅🎄"); +} + } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved. // Use of this source code is governed by the MIT license that can be found in