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 };