From 6d3f52c4a4be4590a9c05a0a363969b95479d74e Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 4 Sep 2019 23:51:47 +0300 Subject: [PATCH] Terminal: Add some basic emoji support This is not as perfect as it is elsewhere in the system, as we cannot really change how terminal "thinks about" characters and bytes. What we can do though, and what this commit does, is to *render* emojis, but make it seem as if they take up all the space, and all the columns their bytes would take if they were all regular characters. --- Applications/Terminal/TerminalWidget.cpp | 60 +++++++++++++++++++----- Libraries/LibVT/Terminal.h | 2 + 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/Applications/Terminal/TerminalWidget.cpp b/Applications/Terminal/TerminalWidget.cpp index d08094263de..5d4338fca91 100644 --- a/Applications/Terminal/TerminalWidget.cpp +++ b/Applications/Terminal/TerminalWidget.cpp @@ -225,21 +225,57 @@ void TerminalWidget::paint_event(GPaintEvent& event) painter.fill_rect(row_rect, Color::Red); else if (has_only_one_background_color) painter.fill_rect(row_rect, lookup_color(line.attributes[0].background_color).with_alpha(m_opacity)); - for (u16 column = 0; column < m_terminal.columns(); ++column) { - char ch = line.characters[column]; - bool should_reverse_fill_for_cursor_or_selection = (m_cursor_blink_state && m_in_active_window && row == row_with_cursor && column == m_terminal.cursor_column()) - || selection_contains({ row, column }); - auto& attribute = line.attributes[column]; - auto character_rect = glyph_rect(row, column); - if (!has_only_one_background_color || should_reverse_fill_for_cursor_or_selection) { - auto cell_rect = character_rect.inflated(0, m_line_spacing); - painter.fill_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor_or_selection ? attribute.foreground_color : attribute.background_color).with_alpha(m_opacity)); + + + // The terminal insists on thinking characters and + // bytes are the same thing. We want to still draw + // emojis in *some* way, but it won't be completely + // perfect. So what we do is we make multi-byte + // characters take up multiple columns, and render + // the character itself in the center of the columns + // its bytes take up as far as the terminal is concerned. + + ASSERT(line.text().length() == m_terminal.columns()); + Utf8View utf8_view { line.text() }; + + for (auto it = utf8_view.begin(); it != utf8_view.end(); ++it) { + u32 codepoint = *it; + int this_char_column = utf8_view.byte_offset_of(it); + AK::Utf8CodepointIterator it_copy = it; + int next_char_column = utf8_view.byte_offset_of(++it_copy); + + // Columns from this_char_column up until next_char_column + // are logically taken up by this (possibly multi-byte) + // character. Iterate over these columns and draw background + // for each one of them separately. + + bool should_reverse_fill_for_cursor_or_selection = false; + VT::Attribute attribute; + + for (u16 column = this_char_column; column < next_char_column; ++column) { + should_reverse_fill_for_cursor_or_selection |= + m_cursor_blink_state + && m_in_active_window + && row == row_with_cursor + && column == m_terminal.cursor_column(); + should_reverse_fill_for_cursor_or_selection |= selection_contains({ row, column }); + attribute = line.attributes[column]; + auto character_rect = glyph_rect(row, column); + if (!has_only_one_background_color || should_reverse_fill_for_cursor_or_selection) { + auto cell_rect = character_rect.inflated(0, m_line_spacing); + painter.fill_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor_or_selection ? attribute.foreground_color : attribute.background_color).with_alpha(m_opacity)); + } } - if (ch == ' ') + + if (codepoint == ' ') continue; - painter.draw_glyph( + + auto character_rect = glyph_rect(row, this_char_column); + auto num_columns = next_char_column - this_char_column; + character_rect.move_by((num_columns - 1) * font().glyph_width('x') / 2, 0); + painter.draw_glyph_or_emoji( character_rect.location(), - ch, + codepoint, attribute.flags & VT::Attribute::Bold ? Font::default_bold_fixed_width_font() : font(), lookup_color(should_reverse_fill_for_cursor_or_selection ? attribute.background_color : attribute.foreground_color)); } diff --git a/Libraries/LibVT/Terminal.h b/Libraries/LibVT/Terminal.h index d6bf5e736b9..3169c92860f 100644 --- a/Libraries/LibVT/Terminal.h +++ b/Libraries/LibVT/Terminal.h @@ -82,6 +82,8 @@ public: void clear(Attribute); bool has_only_one_background_color() const; void set_length(u16); + StringView text() const { return { characters, m_length }; } + u8* characters { nullptr }; Attribute* attributes { nullptr }; bool dirty { false };