From a5ebad99523f86e03330630d8ed217b3528b3332 Mon Sep 17 00:00:00 2001 From: Alex Alabuzhev Date: Mon, 7 Jun 2021 22:28:52 +0100 Subject: [PATCH] Continue fullwidth-aware rendering --- far/changelog | 5 ++ far/common/2d/algorithm.hpp | 8 +- far/console.cpp | 8 +- far/console.hpp | 1 + far/edit.cpp | 125 ++++++++++++++++++-------------- far/edit.hpp | 4 +- far/encoding.cpp | 18 +++++ far/interf.cpp | 8 +- far/interf.hpp | 4 +- far/scrbuf.cpp | 141 ++++++++++++++++++++++++++++++++++-- far/scrbuf.hpp | 15 +--- far/vbuild.m4 | 2 +- 12 files changed, 250 insertions(+), 89 deletions(-) diff --git a/far/changelog b/far/changelog index d49d3c5956..7e6fb80370 100644 --- a/far/changelog +++ b/far/changelog @@ -1,3 +1,8 @@ +-------------------------------------------------------------------------------- +drkns 07.06.2021 22:28:28 +0100 - build 5815 + +1. Continue fullwidth-aware rendering. + -------------------------------------------------------------------------------- drkns 06.06.2021 21:01:06 +0100 - build 5814 diff --git a/far/common/2d/algorithm.hpp b/far/common/2d/algorithm.hpp index debc16b657..97ee3f2f7b 100644 --- a/far/common/2d/algorithm.hpp +++ b/far/common/2d/algorithm.hpp @@ -41,10 +41,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace detail { template - using try_3_args = decltype(std::declval()(std::declval(), std::declval(), std::declval())); + using try_2_args = decltype(std::declval()(std::declval(), std::declval())); template - inline constexpr bool has_3_args = is_detected_v; + inline constexpr bool has_2_args = is_detected_v; } template @@ -54,8 +54,8 @@ void for_submatrix(T& Matrix, rectangle Rect, P Predicate) { for (auto j = Rect.left; j <= Rect.right; ++j) { - if constexpr (detail::has_3_args) - Predicate(Matrix[i][j], i - Rect.top, j - Rect.left); + if constexpr (detail::has_2_args) + Predicate(Matrix[i][j], point{ j - Rect.left, i - Rect.top }); else Predicate(Matrix[i][j]); } diff --git a/far/console.cpp b/far/console.cpp index 040866958b..c9886f337d 100644 --- a/far/console.cpp +++ b/far/console.cpp @@ -1035,9 +1035,9 @@ namespace console_detail if (char_width::is_enabled()) { - for_submatrix(Buffer, SubRect, [&](FAR_CHAR_INFO& Cell, int const Row, int const Col) + for_submatrix(Buffer, SubRect, [&](FAR_CHAR_INFO& Cell, point const Point) { - if (!Col) + if (!Point.x) { if (Cell.Attributes.Flags & COMMON_LVB_TRAILING_BYTE) { @@ -1050,9 +1050,9 @@ namespace console_detail } } - if (Col != SubRect.width() - 1) + if (Point.x != SubRect.width() - 1) { - sanitise_pair(Cell, Buffer[SubRect.top + Row][SubRect.left + Col + 1]); + sanitise_pair(Cell, Buffer[SubRect.top + Point.y][SubRect.left + Point.x + 1]); } else { diff --git a/far/console.hpp b/far/console.hpp index ade973ab32..46f4b89dec 100644 --- a/far/console.hpp +++ b/far/console.hpp @@ -59,6 +59,7 @@ enum CLEAR_REGION }; wchar_t ReplaceControlCharacter(wchar_t Char); +void sanitise_pair(FAR_CHAR_INFO& First, FAR_CHAR_INFO& Second); namespace console_detail { diff --git a/far/edit.cpp b/far/edit.cpp index ab877a2082..68e5748d95 100644 --- a/far/edit.cpp +++ b/far/edit.cpp @@ -1823,7 +1823,8 @@ void Edit::SetTabCurPos(int NewPos) NewPos=Pos; } - m_CurPos = VisualPosToReal(NewPos); + if (NewPos != RealPosToVisual(m_CurPos)) + m_CurPos = VisualPosToReal(NewPos); } int Edit::RealPosToVisual(int Pos) const @@ -1831,12 +1832,12 @@ int Edit::RealPosToVisual(int Pos) const return RealPosToVisual(0, 0, Pos); } -static size_t real_pos_to_visual_tab(string_view const Str, size_t const TabSize, size_t Pos, size_t const PrevLength, size_t const PrevPos, int* CorrectPos) +static size_t real_pos_to_visual_tab(string_view const Str, size_t const TabSize, size_t Pos, size_t const PrevTabPos, size_t const PrevRealPos, int* CorrectPos) { - auto TabPos = PrevLength; + auto TabPos = PrevTabPos; // Начинаем вычисление с предыдущей позиции - auto Index = PrevPos; + auto Index = PrevRealPos; const auto Size = Str.size(); // Корректировка табов @@ -1872,17 +1873,24 @@ static size_t real_pos_to_visual_tab(string_view const Str, size_t const TabSize return TabPos; } -int Edit::RealPosToVisual(int PrevLength, int PrevPos, int Pos, int* CorrectPos) const +int Edit::RealPosToVisual(int const PrevVisualPos, int const PrevRealPos, int const Pos, int* const CorrectPos) const { const auto StringPart = string_view(m_Str).substr(0, Pos); - const auto WidthCorrection = static_cast(string_length_to_visual(StringPart)) - static_cast(StringPart.size()); + const auto WidthCorrection = static_cast(string_length_to_visual(StringPart, space_glyph(), tab_glyph())) - static_cast(StringPart.size()); if (CorrectPos) *CorrectPos = 0; - const auto Result = GetTabExpandMode() == EXPAND_ALLTABS || PrevPos >= m_Str.size()? - PrevLength + Pos - PrevPos : - static_cast(real_pos_to_visual_tab(m_Str, GetTabSize(), Pos, PrevLength, PrevPos, CorrectPos)); + const auto Result = GetTabExpandMode() == EXPAND_ALLTABS || PrevRealPos >= m_Str.size()? + PrevVisualPos + Pos - PrevRealPos : + static_cast(real_pos_to_visual_tab( + m_Str, + GetTabSize(), + Pos, + WidthCorrection? 0 : PrevVisualPos, // BUGBUG + WidthCorrection? 0 : PrevRealPos, // BUGBUG + CorrectPos + )); return Result + WidthCorrection; } @@ -1919,7 +1927,7 @@ static size_t visual_tab_pos_to_real(string_view const Str, size_t const TabSize int Edit::VisualPosToReal(int Pos) const { - const auto RealPos = visual_pos_to_string_pos(m_Str, Pos); + const auto RealPos = visual_pos_to_string_pos(m_Str, Pos, space_glyph(), tab_glyph()); const auto WidthCorrection = static_cast(Pos) - static_cast(RealPos); const auto Result = GetTabExpandMode() == EXPAND_ALLTABS? @@ -2094,8 +2102,12 @@ bool Edit::GetColor(ColorItem& col, size_t Item) const void Edit::ApplyColor(int XPos, int FocusedLeftPos) { + // BUGBUG optimise + + const auto Width = ObjWidth(); + // Для оптимизации сохраняем вычисленные позиции между итерациями цикла - int Pos = INT_MIN, TabPos = INT_MIN, TabEditorPos = INT_MIN; + int RealPos = INT_MIN, VisualPos = INT_MIN, TabEditorPos = INT_MIN; // Обрабатываем элементы раскраски for (const auto& CurItem: ColorList) @@ -2104,39 +2116,37 @@ void Edit::ApplyColor(int XPos, int FocusedLeftPos) if (CurItem.StartPos > CurItem.EndPos) continue; - const auto Width = ObjWidth(); - // Получаем начальную позицию - int RealStart, Start; + int AbsoluteVisualStart, VisualStart; // Если предыдущая позиция равна текущей, то ничего не вычисляем // и сразу берём ранее вычисленное значение - if (Pos == CurItem.StartPos) + if (RealPos == CurItem.StartPos) { - RealStart = TabPos; - Start = TabEditorPos; + AbsoluteVisualStart = VisualPos; + VisualStart = TabEditorPos; } // Если вычисление идёт первый раз или предыдущая позиция больше текущей, // то производим вычисление с начала строки - else if (Pos == INT_MIN || CurItem.StartPos < Pos) + else if (RealPos == INT_MIN || CurItem.StartPos < RealPos) { - RealStart = RealPosToVisual(CurItem.StartPos); - Start = RealStart-FocusedLeftPos; + AbsoluteVisualStart = RealPosToVisual(CurItem.StartPos); + VisualStart = AbsoluteVisualStart - FocusedLeftPos; } // Для оптимизации делаем вычисление относительно предыдущей позиции else { - RealStart = RealPosToVisual(TabPos, Pos, CurItem.StartPos); - Start = RealStart-FocusedLeftPos; + AbsoluteVisualStart = RealPosToVisual(VisualPos, RealPos, CurItem.StartPos); + VisualStart = AbsoluteVisualStart -FocusedLeftPos; } // Запоминаем вычисленные значения для их дальнейшего повторного использования - Pos = CurItem.StartPos; - TabPos = RealStart; - TabEditorPos = Start; + RealPos = CurItem.StartPos; + VisualPos = AbsoluteVisualStart; + TabEditorPos = VisualStart; // Пропускаем элементы раскраски у которых начальная позиция за экраном - if (Start >= Width) + if (VisualStart >= Width) continue; // Корректировка относительно табов (отключается, если присутствует флаг ECF_TABMARKFIRST) @@ -2144,38 +2154,37 @@ void Edit::ApplyColor(int XPos, int FocusedLeftPos) // Получаем конечную позицию int EndPos = CurItem.EndPos; - int RealEnd, End; + int AbsoluteVisualEnd, VisualEnd; bool TabMarkCurrent=false; // Обрабатываем случай, когда предыдущая позиция равна текущей, то есть // длина раскрашиваемой строки равна 1 - if (Pos == EndPos) + if (RealPos == EndPos) { // Если необходимо делать корректировку относительно табов и единственный // символ строки -- это таб, то делаем расчёт с учётом корректировки, - // иначе ничего не вычисляем и берём старые значения if (CorrectPos && EndPos < m_Str.size() && m_Str[EndPos] == L'\t') { - RealEnd = RealPosToVisual(TabPos, Pos, ++EndPos); - End = RealEnd-FocusedLeftPos; - TabMarkCurrent = (CurItem.Flags & ECF_TABMARKCURRENT) && XPos>=Start && XPos= VisualStart && XPos < VisualEnd; } else { - RealEnd = TabPos; + AbsoluteVisualEnd = RealPosToVisual(VisualPos, RealPos, EndPos + 1) - 1; + VisualEnd = AbsoluteVisualEnd - FocusedLeftPos; CorrectPos = 0; - End = TabEditorPos; } } // Если предыдущая позиция больше текущей, то производим вычисление // с начала строки (с учётом корректировки относительно табов) - else if (EndPos < Pos) + else if (EndPos < RealPos) { // TODO: возможно так же нужна коррекция с учетом табов (на предмет Mantis#0001718) - RealEnd = RealPosToVisual(0, 0, EndPos, &CorrectPos); + AbsoluteVisualEnd = RealPosToVisual(0, 0, EndPos, &CorrectPos); EndPos += CorrectPos; - End = RealEnd-FocusedLeftPos; + VisualEnd = AbsoluteVisualEnd - FocusedLeftPos; } // Для оптимизации делаем вычисление относительно предыдущей позиции (с учётом // корректировки относительно табов) @@ -2184,45 +2193,45 @@ void Edit::ApplyColor(int XPos, int FocusedLeftPos) // Mantis#0001718: Отсутствие ECF_TABMARKFIRST не всегда корректно отрабатывает // Коррекция с учетом последнего таба if (CorrectPos && EndPos < m_Str.size() && m_Str[EndPos] == L'\t') - RealEnd = RealPosToVisual(TabPos, Pos, ++EndPos); + AbsoluteVisualEnd = RealPosToVisual(VisualPos, RealPos, ++EndPos); else { - RealEnd = RealPosToVisual(TabPos, Pos, EndPos, &CorrectPos); + AbsoluteVisualEnd = RealPosToVisual(VisualPos, RealPos, EndPos, &CorrectPos); EndPos += CorrectPos; } - End = RealEnd-FocusedLeftPos; + VisualEnd = AbsoluteVisualEnd - FocusedLeftPos; } // Запоминаем вычисленные значения для их дальнейшего повторного использования - Pos = EndPos; - TabPos = RealEnd; - TabEditorPos = End; + RealPos = EndPos; + VisualPos = AbsoluteVisualEnd; + TabEditorPos = VisualEnd; if(TabMarkCurrent) { - Start = XPos; - End = XPos; + VisualStart = XPos; + VisualEnd = XPos; } else { // Пропускаем элементы раскраски у которых конечная позиция меньше левой границы экрана - if (End < 0) + if (VisualEnd < 0) continue; // Обрезаем раскраску элемента по экрану - if (Start < 0) - Start = 0; + if (VisualStart < 0) + VisualStart = 0; - if (End >= Width) - End = Width-1; + if (VisualEnd >= Width) + VisualEnd = Width-1; else - End -= CorrectPos; + VisualEnd -= CorrectPos; } // Раскрашиваем элемент, если есть что раскрашивать - if (End >= Start) + if (VisualEnd >= VisualStart) { - Global->ScrBuf->ApplyColor({ m_Where.left + Start, m_Where.top, m_Where.left + End, m_Where.top }, CurItem.GetColor()); + Global->ScrBuf->ApplyColor({ m_Where.left + VisualStart, m_Where.top, m_Where.left + VisualEnd, m_Where.top }, CurItem.GetColor()); } } } @@ -2404,6 +2413,16 @@ bool Edit::is_valid_surrogate_pair_at(size_t const Position) const return Position < Str.size() && is_valid_surrogate_pair(Str.substr(Position)); } +wchar_t Edit::tab_glyph() const +{ + return m_Flags.Check(FEDITLINE_SHOWWHITESPACE)? L'→' : L' '; +} + +wchar_t Edit::space_glyph() const +{ + return m_Flags.Check(FEDITLINE_SHOWWHITESPACE)? L'·' : L' '; +} + #ifdef ENABLE_TESTS #include "testing.hpp" diff --git a/far/edit.hpp b/far/edit.hpp index 5b7eb8aa4d..c504c84705 100644 --- a/far/edit.hpp +++ b/far/edit.hpp @@ -226,12 +226,14 @@ class Edit: public SimpleScreenObject static bool CharInMask(wchar_t Char, wchar_t Mask); bool ProcessCtrlQ(); bool ProcessInsPath(unsigned int Key,int PrevSelStart=-1,int PrevSelEnd=0); - int RealPosToVisual(int PrevLength, int PrevPos, int Pos, int* CorrectPos = {}) const; + int RealPosToVisual(int PrevVisualPos, int PrevRealPos, int Pos, int* CorrectPos = {}) const; void FixLeftPos(int TabCurPos=-1); void SetRightCoord(int Value) { SetPosition({ m_Where.left, m_Where.top, Value, m_Where.bottom }); } Editor* GetEditor() const; bool is_valid_surrogate_pair_at(size_t Position) const; + wchar_t tab_glyph() const; + wchar_t space_glyph() const; protected: // BUGBUG: the whole purpose of this class is to avoid zillions of casts in existing code by returning size() as int diff --git a/far/encoding.cpp b/far/encoding.cpp index 93881df35e..cfdca9faca 100644 --- a/far/encoding.cpp +++ b/far/encoding.cpp @@ -1585,6 +1585,24 @@ TEST_CASE("encoding.utf8") ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ ᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ ᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬ +)"sv }, + + { true, false, R"( +぀ ぁ あ ぃ い ぅ う ぇ え ぉ お か が き ぎ く +ぐ け げ こ ご さ ざ し じ す ず せ ぜ そ ぞ た +だ ち ぢ っ つ づ て で と ど な に ぬ ね の は +ば ぱ ひ び ぴ ふ ぶ ぷ へ べ ぺ ほ ぼ ぽ ま み +む め も ゃ や ゅ ゆ ょ よ ら り る れ ろ ゎ わ +ゐ ゑ を ん ゔ ゕ ゖ ゗ ゘ ゙ ゚ ゛ ゜ ゝ ゞ ゟ +)"sv }, + + { true, false, R"( +゠ ァ ア ィ イ ゥ ウ ェ エ ォ オ カ ガ キ ギ ク +グ ケ ゲ コ ゴ サ ザ シ ジ ス ズ セ ゼ ソ ゾ タ +ダ チ ヂ ッ ツ ヅ テ デ ト ド ナ ニ ヌ ネ ノ ハ +バ パ ヒ ビ ピ フ ブ プ ヘ ベ ペ ホ ボ ポ マ ミ +ム メ モ ャ ヤ ュ ユ ョ ヨ ラ リ ル レ ロ ヮ ワ +ヰ ヱ ヲ ン ヴ ヵ ヶ ヷ ヸ ヹ ヺ ・ ー ヽ ヾ ヿ )"sv }, { true, false, R"( diff --git a/far/interf.cpp b/far/interf.cpp index 92df8001cd..761f03744b 100644 --- a/far/interf.cpp +++ b/far/interf.cpp @@ -1159,7 +1159,7 @@ bool DoWeReallyHaveToScroll(short Rows) return !std::all_of(ALL_CONST_RANGE(BufferBlock.vector()), [](const FAR_CHAR_INFO& i) { return i.Char == L' '; }); } -size_t string_length_to_visual(string_view Str) +size_t string_length_to_visual(string_view Str, wchar_t const SpaceGlyph, wchar_t const TabGlyph) { if (!char_width::is_enabled()) return Str.size(); @@ -1170,13 +1170,13 @@ size_t string_length_to_visual(string_view Str) { const auto Codepoint = encoding::utf16::extract_codepoint(Str); Str.remove_prefix(Codepoint > std::numeric_limits::max()? 2 : 1); - Result += char_width::is_wide(Codepoint)? 2 : 1; + Result += char_width::is_wide(Codepoint == L'\t'? TabGlyph : Codepoint == ' '? SpaceGlyph : Codepoint)? 2 : 1; } return Result; } -size_t visual_pos_to_string_pos(string_view Str, size_t const Pos) +size_t visual_pos_to_string_pos(string_view Str, size_t const Pos, wchar_t const SpaceGlyph, wchar_t const TabGlyph) { if (!char_width::is_enabled()) return Pos; @@ -1188,7 +1188,7 @@ size_t visual_pos_to_string_pos(string_view Str, size_t const Pos) { const auto Codepoint = encoding::utf16::extract_codepoint(Str); Str.remove_prefix(Codepoint > std::numeric_limits::max() ? 2 : 1); - Size += char_width::is_wide(Codepoint)? 2 : 1; + Size += char_width::is_wide(Codepoint == L'\t'? TabGlyph : Codepoint == ' '? SpaceGlyph : Codepoint)? 2 : 1; } return StrSize - Str.size() + (Pos > Size? Pos - Size : 0); diff --git a/far/interf.hpp b/far/interf.hpp index 95cc62f9b7..dba7234705 100644 --- a/far/interf.hpp +++ b/far/interf.hpp @@ -158,8 +158,8 @@ void MoveRealCursor(int X,int Y); void ScrollScreen(int Count); bool DoWeReallyHaveToScroll(short Rows); -size_t string_length_to_visual(string_view Str); -size_t visual_pos_to_string_pos(string_view Str, size_t Pos); +size_t string_length_to_visual(string_view Str, wchar_t SpaceGlyph, wchar_t TabGlyph); +size_t visual_pos_to_string_pos(string_view Str, size_t Pos, wchar_t SpaceGlyph, wchar_t TabGlyph); bool is_valid_surrogate_pair(string_view Str); bool is_valid_surrogate_pair(wchar_t First, wchar_t Second); diff --git a/far/scrbuf.cpp b/far/scrbuf.cpp index 0dde9cd45a..038342d7d2 100644 --- a/far/scrbuf.cpp +++ b/far/scrbuf.cpp @@ -47,6 +47,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "colormix.hpp" #include "global.hpp" #include "char_width.hpp" +#include "encoding.hpp" // Platform: @@ -77,6 +78,37 @@ static bool is_visible(rectangle const& Where) return Where.left <= ScrX && Where.top <= ScrY && Where.right >= 0 && Where.bottom >= 0; } +void invalidate_broken_pairs_in_cache(matrixconst& Buf, matrix& Shadow, rectangle const Where, point const Point) +{ + const auto + IsLeft = !Point.x && Where.left, + IsRight = Point.x == Where.width() - 1 && Where.right != ScrX; + + if (!IsLeft && !IsRight) + return; + + const auto X1X2 = IsLeft? + std::pair{ Where.left - 1, Where.left } : + std::pair{ Where.right, Where.right + 1 }; + + const auto + BufRowData = Buf[Where.top + Point.y], + ShadowRowData = Shadow[Where.top + Point.y]; + + const auto + &Buf0 = BufRowData[X1X2.first], + &Buf1 = BufRowData[X1X2.second]; + + std::array Pair{ Buf0, Buf1 }; + sanitise_pair(Pair[0], Pair[1]); + + if (Pair[0] != Buf0) + ShadowRowData[X1X2.first] = {}; + + if (Pair[1] != Buf1) + ShadowRowData[X1X2.second] = {}; +} + ScreenBuf::ScreenBuf(): SBFlags(SBFLAGS_FLUSHED | SBFLAGS_FLUSHEDCURPOS | SBFLAGS_FLUSHEDCURTYPE | SBFLAGS_FLUSHEDTITLE) { @@ -147,6 +179,13 @@ void ScreenBuf::Write(int X, int Y, span Text) Buf[Y][X + i] = Text[i]; } + if (char_width::is_enabled()) + { + rectangle const Where = { X, Y, static_cast(X + Text.size() - 1), Y }; + invalidate_broken_pairs_in_cache(Buf, Shadow, Where, { 0, 0 }); + invalidate_broken_pairs_in_cache(Buf, Shadow, Where, { Where.right - X, 0 }); + } + SBFlags.Clear(SBFLAGS_FLUSHED); debug_flush(); @@ -180,7 +219,9 @@ void ScreenBuf::ApplyShadow(rectangle Where) fix_coordinates(Where); - for_submatrix(Buf, Where, [](FAR_CHAR_INFO& Element) + const auto CharWidthEnabled = char_width::is_enabled(); + + for_submatrix(Buf, Where, [&](FAR_CHAR_INFO& Element, point const Point) { Element.Attributes.BackgroundColor = 0; @@ -191,6 +232,9 @@ void ScreenBuf::ApplyShadow(rectangle Where) { Element.Attributes.ForegroundColor = Mask; } + + if (CharWidthEnabled) + invalidate_broken_pairs_in_cache(Buf, Shadow, Where, Point); }); debug_flush(); @@ -198,7 +242,7 @@ void ScreenBuf::ApplyShadow(rectangle Where) /* Непосредственное изменение цветовых атрибутов */ -void ScreenBuf::ApplyColor(rectangle Where, const FarColor& Color, apply_mode const ApplyMode) +void ScreenBuf::ApplyColor(rectangle Where, const FarColor& Color) { if (!is_visible(Where)) return; @@ -207,9 +251,14 @@ void ScreenBuf::ApplyColor(rectangle Where, const FarColor& Color, apply_mode co fix_coordinates(Where); - for_submatrix(Buf, Where, [&](FAR_CHAR_INFO& Element) + const auto CharWidthEnabled = char_width::is_enabled(); + + for_submatrix(Buf, Where, [&](FAR_CHAR_INFO& Element, point const Point) { Element.Attributes = colors::merge(Element.Attributes, Color); + + if (CharWidthEnabled) + invalidate_broken_pairs_in_cache(Buf, Shadow, Where, Point); }); debug_flush(); @@ -226,9 +275,14 @@ void ScreenBuf::FillRect(rectangle Where, const FAR_CHAR_INFO& Info) fix_coordinates(Where); - for_submatrix(Buf, Where, [&Info](FAR_CHAR_INFO& Element) + const auto CharWidthEnabled = char_width::is_enabled(); + + for_submatrix(Buf, Where, [&](FAR_CHAR_INFO& Element, point const Point) { Element = Info; + + if (CharWidthEnabled) + invalidate_broken_pairs_in_cache(Buf, Shadow, Where, Point); }); SBFlags.Clear(SBFLAGS_FLUSHED); @@ -246,6 +300,63 @@ void ScreenBuf::Invalidate(flush_type const FlushType) SBFlags.Clear(SBFLAGS_FLUSHEDTITLE); } +static void expand_write_region_if_needed(matrix& Buf, rectangle& WriteRegion) +{ + enum class border + { + unchecked, + expanded, + checked + }; + + auto + Left = border::unchecked, + Right = border::unchecked; + + for (;;) + { + auto + LeftChanged = false, + RightChanged = false; + + for (auto Row = WriteRegion.top; Row <= WriteRegion.bottom; ++Row) + { + const auto RowData = Buf[Row]; + + if ( + const auto& First = RowData[WriteRegion.left]; + Left != border::checked && WriteRegion.left && (First.Attributes.Flags & COMMON_LVB_TRAILING_BYTE || encoding::utf16::is_low_surrogate(First.Char)) + ) + { + --WriteRegion.left; + Left = border::expanded; + LeftChanged = true; + break; + } + + if ( + const auto& Last = RowData[WriteRegion.right]; + Right != border::checked && WriteRegion.right != ScrX && (Last.Attributes.Flags & COMMON_LVB_LEADING_BYTE || encoding::utf16::is_high_surrogate(Last.Char)) + ) + { + ++WriteRegion.right; + Right = border::expanded; + RightChanged = true; + break; + } + } + + if (!LeftChanged && !RightChanged) + break; + + if (!LeftChanged) + Left = border::checked; + + if (!RightChanged) + Right = border::checked; + } +} + /* "Сбросить" виртуальный буфер на консоль */ void ScreenBuf::Flush(flush_type FlushType) @@ -339,6 +450,8 @@ void ScreenBuf::Flush(flush_type FlushType) bool Started=false; rectangle WriteRegion = { static_cast(Buf.width() - 1), static_cast(Buf.height() - 1), 0, 0 }; + const auto CharWidthEnabled = char_width::is_enabled(); + auto PtrBuf = Buf.data(), PtrShadow = Shadow.data(); for (size_t I = 0, Height = Buf.height(); I < Height; ++I) { @@ -369,18 +482,31 @@ void ScreenBuf::Flush(flush_type FlushType) if (!WriteList.empty()) { auto& Last = WriteList.back(); - const int MAX_DELTA = 5; - if (WriteRegion.top - 1 == Last.bottom && ((WriteRegion.left >= Last.left && WriteRegion.left - Last.left < MAX_DELTA) || (Last.right >= WriteRegion.right && Last.right - WriteRegion.right < MAX_DELTA))) + const int MAX_DELTA = 1; + if ( + WriteRegion.top - Last.bottom < 1 + MAX_DELTA && + std::abs(WriteRegion.left - Last.left) < MAX_DELTA && + std::abs(WriteRegion.right - Last.right < MAX_DELTA) + ) { Last.bottom = WriteRegion.bottom; Last.left = std::min(Last.left, WriteRegion.left); Last.right = std::max(Last.right, WriteRegion.right); + + if (CharWidthEnabled) + expand_write_region_if_needed(Buf, Last); + Merge=true; } } if (!Merge) + { + if (CharWidthEnabled) + expand_write_region_if_needed(Buf, WriteRegion); + WriteList.emplace_back(WriteRegion); + } WriteRegion.left = static_cast(Buf.width() - 1); WriteRegion.top = static_cast(Buf.height() - 1); @@ -393,6 +519,9 @@ void ScreenBuf::Flush(flush_type FlushType) if (Started) { + if (CharWidthEnabled) + expand_write_region_if_needed(Buf, WriteRegion); + WriteList.emplace_back(WriteRegion); } } diff --git a/far/scrbuf.hpp b/far/scrbuf.hpp index 20d40f1752..d2e7e2038c 100644 --- a/far/scrbuf.hpp +++ b/far/scrbuf.hpp @@ -98,20 +98,7 @@ class ScreenBuf: noncopyable void RestoreElevationChar(); void ApplyShadow(rectangle Where); - - enum apply_mode - { - foreground = 0_bit, - background = 1_bit, - - color = foreground | background, - - style = 2_bit, - - all = color | style - }; - - void ApplyColor(rectangle Where, const FarColor& Color, apply_mode ApplyMode = apply_mode::all); + void ApplyColor(rectangle Where, const FarColor& Color); void FillRect(rectangle Where, const FAR_CHAR_INFO& Info); void Scroll(size_t Count); diff --git a/far/vbuild.m4 b/far/vbuild.m4 index 5c22f3f453..a734b82fb6 100644 --- a/far/vbuild.m4 +++ b/far/vbuild.m4 @@ -1 +1 @@ -5814 +5815