Skip to content

Commit

Permalink
Continue fullwidth-aware rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
alabuzhev committed Jun 7, 2021
1 parent 45efcc1 commit a5ebad9
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 89 deletions.
5 changes: 5 additions & 0 deletions far/changelog
Original file line number Diff line number Diff line change
@@ -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

Expand Down
8 changes: 4 additions & 4 deletions far/common/2d/algorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
namespace detail
{
template<typename P, typename T, typename I>
using try_3_args = decltype(std::declval<P&>()(std::declval<T&>(), std::declval<I&>(), std::declval<I&>()));
using try_2_args = decltype(std::declval<P&>()(std::declval<T&>(), std::declval<I&>()));

template<typename P, typename T, typename I>
inline constexpr bool has_3_args = is_detected_v<try_3_args, P, T, I>;
inline constexpr bool has_2_args = is_detected_v<try_2_args, P, T, I>;
}

template<class T, class P>
Expand All @@ -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<P, decltype(Matrix[i][j]), decltype(i)>)
Predicate(Matrix[i][j], i - Rect.top, j - Rect.left);
if constexpr (detail::has_2_args<P, decltype(Matrix[i][j]), point>)
Predicate(Matrix[i][j], point{ j - Rect.left, i - Rect.top });
else
Predicate(Matrix[i][j]);
}
Expand Down
8 changes: 4 additions & 4 deletions far/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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
{
Expand Down
1 change: 1 addition & 0 deletions far/console.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
125 changes: 72 additions & 53 deletions far/edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1823,20 +1823,21 @@ 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
{
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();

// Корректировка табов
Expand Down Expand Up @@ -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<int>(string_length_to_visual(StringPart)) - static_cast<int>(StringPart.size());
const auto WidthCorrection = static_cast<int>(string_length_to_visual(StringPart, space_glyph(), tab_glyph())) - static_cast<int>(StringPart.size());

if (CorrectPos)
*CorrectPos = 0;

const auto Result = GetTabExpandMode() == EXPAND_ALLTABS || PrevPos >= m_Str.size()?
PrevLength + Pos - PrevPos :
static_cast<int>(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<int>(real_pos_to_visual_tab(
m_Str,
GetTabSize(),
Pos,
WidthCorrection? 0 : PrevVisualPos, // BUGBUG
WidthCorrection? 0 : PrevRealPos, // BUGBUG
CorrectPos
));

return Result + WidthCorrection;
}
Expand Down Expand Up @@ -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<int>(Pos) - static_cast<int>(RealPos);

const auto Result = GetTabExpandMode() == EXPAND_ALLTABS?
Expand Down Expand Up @@ -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)
Expand All @@ -2104,78 +2116,75 @@ 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)
int CorrectPos = CurItem.Flags & ECF_TABMARKFIRST ? 0 : 1;

// Получаем конечную позицию
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<End;
AbsoluteVisualEnd = RealPosToVisual(VisualPos, RealPos, ++EndPos);
VisualEnd = AbsoluteVisualEnd - FocusedLeftPos;
TabMarkCurrent = (CurItem.Flags & ECF_TABMARKCURRENT) && 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;
}
// Для оптимизации делаем вычисление относительно предыдущей позиции (с учётом
// корректировки относительно табов)
Expand All @@ -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());
}
}
}
Expand Down Expand Up @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion far/edit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions far/encoding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1585,6 +1585,24 @@ TEST_CASE("encoding.utf8")
ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ
ᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ
ᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬
)"sv },

{ true, false, R"(
぀ ぁ あ ぃ い ぅ う ぇ え ぉ お か が き ぎ く
ぐ け げ こ ご さ ざ し じ す ず せ ぜ そ ぞ た
だ ち ぢ っ つ づ て で と ど な に ぬ ね の は
ば ぱ ひ び ぴ ふ ぶ ぷ へ べ ぺ ほ ぼ ぽ ま み
む め も ゃ や ゅ ゆ ょ よ ら り る れ ろ ゎ わ
ゐ ゑ を ん ゔ ゕ ゖ ゗ ゘ ゙ ゚ ゛ ゜ ゝ ゞ ゟ
)"sv },

{ true, false, R"(
゠ ァ ア ィ イ ゥ ウ ェ エ ォ オ カ ガ キ ギ ク
グ ケ ゲ コ ゴ サ ザ シ ジ ス ズ セ ゼ ソ ゾ タ
ダ チ ヂ ッ ツ ヅ テ デ ト ド ナ ニ ヌ ネ ノ ハ
バ パ ヒ ビ ピ フ ブ プ ヘ ベ ペ ホ ボ ポ マ ミ
ム メ モ ャ ヤ ュ ユ ョ ヨ ラ リ ル レ ロ ヮ ワ
ヰ ヱ ヲ ン ヴ ヵ ヶ ヷ ヸ ヹ ヺ ・ ー ヽ ヾ ヿ
)"sv },

{ true, false, R"(
Expand Down
8 changes: 4 additions & 4 deletions far/interf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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<wchar_t>::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;
Expand All @@ -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<wchar_t>::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);
Expand Down
Loading

0 comments on commit a5ebad9

Please sign in to comment.