From f239fbaa58aa80ff615a3e66bcf35bbf878271b2 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 20 Oct 2020 09:51:04 -0600 Subject: LibGfx: Reduce code duplication in Painter::draw_text Use the same logic for all variants for Painter::draw_text. Also, add an overload that allows taking a callback function for custom gylph drawing. This allows drawing some glyphs differently in the correct location when drawing more complex strings (e.g. multi-line, elisions, etc). --- Libraries/LibGfx/Painter.cpp | 242 ++++++++++++++++++------------------------- Libraries/LibGfx/Painter.h | 6 +- 2 files changed, 104 insertions(+), 144 deletions(-) (limited to 'Libraries/LibGfx') diff --git a/Libraries/LibGfx/Painter.cpp b/Libraries/LibGfx/Painter.cpp index b6a044b7be..fb4294a5b5 100644 --- a/Libraries/LibGfx/Painter.cpp +++ b/Libraries/LibGfx/Painter.cpp @@ -854,20 +854,52 @@ void Painter::draw_glyph_or_emoji(const IntPoint& point, u32 code_point, const F draw_emoji(point, *emoji, font); } -void Painter::draw_text_line(const IntRect& a_rect, const Utf8View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision) +static void apply_elision(Utf8View& final_text, String& elided_text, size_t offset) +{ + StringBuilder builder; + builder.append(final_text.substring_view(0, offset).as_string()); + builder.append("..."); + elided_text = builder.to_string(); + final_text = Utf8View { elided_text }; +} + +static void apply_elision(Utf32View& final_text, Vector& elided_text, size_t offset) +{ + elided_text.append(final_text.code_points(), offset); + elided_text.append('.'); + elided_text.append('.'); + elided_text.append('.'); + final_text = Utf32View { elided_text.data(), elided_text.size() }; +} + +template +struct ElidedText { +}; + +template<> +struct ElidedText { + typedef String Type; +}; + +template<> +struct ElidedText { + typedef Vector Type; +}; + +template +void draw_text_line(const IntRect& a_rect, const TextType& text, const Font& font, TextAlignment alignment, TextElision elision, DrawGlyphFunction draw_glyph) { auto rect = a_rect; - Utf8View final_text(text); - String elided_text; + TextType final_text(text); + typename ElidedText::Type elided_text; if (elision == TextElision::Right) { int text_width = font.width(final_text); if (font.width(final_text) > rect.width()) { int glyph_spacing = font.glyph_spacing(); - int byte_offset = 0; int new_width = font.width("..."); if (new_width < text_width) { - for (auto it = final_text.begin(); it != final_text.end(); ++it) { - u32 code_point = *it; + size_t offset = 0; + for (auto code_point : text) { int glyph_width = font.glyph_or_emoji_width(code_point); // NOTE: Glyph spacing should not be added after the last glyph on the line, // but since we are here because the last glyph does not actually fit on the line, @@ -875,14 +907,10 @@ void Painter::draw_text_line(const IntRect& a_rect, const Utf8View& text, const int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing; if (width_with_this_glyph_included > rect.width()) break; - byte_offset = final_text.byte_offset_of(it); new_width += glyph_width + glyph_spacing; + offset++; } - StringBuilder builder; - builder.append(final_text.substring_view(0, byte_offset).as_string()); - builder.append("..."); - elided_text = builder.to_string(); - final_text = Utf8View { elided_text }; + apply_elision(final_text, elided_text, offset); } } } @@ -920,105 +948,50 @@ void Painter::draw_text_line(const IntRect& a_rect, const Utf8View& text, const point.move_by(space_width, 0); continue; } - draw_glyph_or_emoji(point, code_point, font, color); - point.move_by(font.glyph_or_emoji_width(code_point) + font.glyph_spacing(), 0); + IntSize glyph_size(font.glyph_or_emoji_width(code_point) + font.glyph_spacing(), font.glyph_height()); + draw_glyph({ point, glyph_size }, code_point); + point.move_by(glyph_size.width(), 0); } } -void Painter::draw_text_line(const IntRect& a_rect, const Utf32View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision) +static inline size_t draw_text_iterator_offset(const Utf8View& text, const Utf8View::Iterator& it) { - auto rect = a_rect; - Utf32View final_text(text); - Vector elided_text; - if (elision == TextElision::Right) { - int text_width = font.width(final_text); - if (font.width(final_text) > rect.width()) { - int glyph_spacing = font.glyph_spacing(); - int new_width = font.width("..."); - if (new_width < text_width) { - size_t i = 0; - for (; i < text.length(); ++i) { - u32 code_point = text.code_points()[i]; - int glyph_width = font.glyph_or_emoji_width(code_point); - // NOTE: Glyph spacing should not be added after the last glyph on the line, - // but since we are here because the last glyph does not actually fit on the line, - // we don't have to worry about spacing. - int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing; - if (width_with_this_glyph_included > rect.width()) - break; - new_width += glyph_width + glyph_spacing; - } - elided_text.clear(); - elided_text.append(final_text.code_points(), i); - elided_text.append('.'); - elided_text.append('.'); - elided_text.append('.'); - final_text = Utf32View { elided_text.data(), elided_text.size() }; - } - } - } - - switch (alignment) { - case TextAlignment::TopLeft: - case TextAlignment::CenterLeft: - break; - case TextAlignment::TopRight: - case TextAlignment::CenterRight: - rect.set_x(rect.right() - font.width(final_text)); - break; - case TextAlignment::Center: { - auto shrunken_rect = rect; - shrunken_rect.set_width(font.width(final_text)); - shrunken_rect.center_within(rect); - rect = shrunken_rect; - break; - } - default: - ASSERT_NOT_REACHED(); - } - - auto point = rect.location(); - int space_width = font.glyph_width(' ') + font.glyph_spacing(); + return text.byte_offset_of(it); +} - for (size_t i = 0; i < final_text.length(); ++i) { - auto code_point = final_text.code_points()[i]; - if (code_point == ' ') { - point.move_by(space_width, 0); - continue; - } - draw_glyph_or_emoji(point, code_point, font, color); - point.move_by(font.glyph_or_emoji_width(code_point) + font.glyph_spacing(), 0); - } +static inline size_t draw_text_iterator_offset(const Utf32View& text, const Utf32View::Iterator& it) +{ + return it - text.begin(); } -void Painter::draw_text(const IntRect& rect, const StringView& text, TextAlignment alignment, Color color, TextElision elision) +static inline size_t draw_text_get_length(const Utf8View& text) { - draw_text(rect, text, font(), alignment, color, elision); + return text.byte_length(); } -void Painter::draw_text(const IntRect& rect, const Utf32View& text, TextAlignment alignment, Color color, TextElision elision) +static inline size_t draw_text_get_length(const Utf32View& text) { - draw_text(rect, text, font(), alignment, color, elision); + return text.length(); } -void Painter::draw_text(const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision) +template +void do_draw_text(const IntRect& rect, const TextType& text, const Font& font, TextAlignment alignment, TextElision elision, DrawGlyphFunction draw_glyph) { - Utf8View text { raw_text }; - Vector lines; + Vector lines; - int start_of_current_line = 0; + size_t start_of_current_line = 0; for (auto it = text.begin(); it != text.end(); ++it) { u32 code_point = *it; if (code_point == '\n') { - int byte_offset = text.byte_offset_of(it); - Utf8View line = text.substring_view(start_of_current_line, byte_offset - start_of_current_line); + auto offset = draw_text_iterator_offset(text, it); + TextType line = text.substring_view(start_of_current_line, offset - start_of_current_line); lines.append(line); - start_of_current_line = byte_offset + 1; + start_of_current_line = offset + 1; } } - if (start_of_current_line != text.byte_length()) { - Utf8View line = text.substring_view(start_of_current_line, text.byte_length() - start_of_current_line); + if (start_of_current_line != draw_text_get_length(text)) { + TextType line = text.substring_view(start_of_current_line, draw_text_get_length(text) - start_of_current_line); lines.append(line); } @@ -1048,9 +1021,6 @@ void Painter::draw_text(const IntRect& rect, const StringView& raw_text, const F case TextAlignment::Center: bounding_rect.center_within(rect); break; - case TextAlignment::BottomRight: - bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), (rect.bottom() + 1) - bounding_rect.height() }); - break; default: ASSERT_NOT_REACHED(); } @@ -1059,65 +1029,55 @@ void Painter::draw_text(const IntRect& rect, const StringView& raw_text, const F auto& line = lines[i]; IntRect line_rect { bounding_rect.x(), bounding_rect.y() + static_cast(i) * line_height, bounding_rect.width(), line_height }; line_rect.intersect(rect); - draw_text_line(line_rect, line, font, alignment, color, elision); + draw_text_line(line_rect, line, font, alignment, elision, draw_glyph); } } -void Painter::draw_text(const IntRect& rect, const Utf32View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision) +void Painter::draw_text(const IntRect& rect, const StringView& text, TextAlignment alignment, Color color, TextElision elision) { - Vector lines; + draw_text(rect, text, font(), alignment, color, elision); +} - size_t start_of_current_line = 0; - for (size_t i = 0; i < text.length(); ++i) { - u32 code_point = text.code_points()[i]; - if (code_point == '\n') { - Utf32View line = text.substring_view(start_of_current_line, i - start_of_current_line); - lines.append(line); - start_of_current_line = i + 1; - } - } +void Painter::draw_text(const IntRect& rect, const Utf32View& text, TextAlignment alignment, Color color, TextElision elision) +{ + draw_text(rect, text, font(), alignment, color, elision); +} - if (start_of_current_line != text.length()) { - Utf32View line = text.substring_view(start_of_current_line, text.length() - start_of_current_line); - lines.append(line); - } +void Painter::draw_text(const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision) +{ + Utf8View text { raw_text }; + do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) { + draw_glyph_or_emoji(r.location(), code_point, font, color); + }); +} - static const int line_spacing = 4; - int line_height = font.glyph_height() + line_spacing; - IntRect bounding_rect { 0, 0, 0, (static_cast(lines.size()) * line_height) - line_spacing }; +void Painter::draw_text(const IntRect& rect, const Utf32View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision) +{ + do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) { + draw_glyph_or_emoji(r.location(), code_point, font, color); + }); +} - for (auto& line : lines) { - auto line_width = font.width(line); - if (line_width > bounding_rect.width()) - bounding_rect.set_width(line_width); - } +void Painter::draw_text(Function draw_one_glyph, const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, TextElision elision) +{ + Utf8View text { raw_text }; + do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) { + draw_one_glyph(r, code_point); + }); +} - switch (alignment) { - case TextAlignment::TopLeft: - bounding_rect.set_location(rect.location()); - break; - case TextAlignment::TopRight: - bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.y() }); - break; - case TextAlignment::CenterLeft: - bounding_rect.set_location({ rect.x(), rect.center().y() - (bounding_rect.height() / 2) }); - break; - case TextAlignment::CenterRight: - bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.center().y() - (bounding_rect.height() / 2) }); - break; - case TextAlignment::Center: - bounding_rect.center_within(rect); - break; - default: - ASSERT_NOT_REACHED(); - } +void Painter::draw_text(Function draw_one_glyph, const IntRect& rect, const Utf8View& text, const Font& font, TextAlignment alignment, TextElision elision) +{ + do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) { + draw_one_glyph(r, code_point); + }); +} - for (size_t i = 0; i < lines.size(); ++i) { - auto& line = lines[i]; - IntRect line_rect { bounding_rect.x(), bounding_rect.y() + static_cast(i) * line_height, bounding_rect.width(), line_height }; - line_rect.intersect(rect); - draw_text_line(line_rect, line, font, alignment, color, elision); - } +void Painter::draw_text(Function draw_one_glyph, const IntRect& rect, const Utf32View& text, const Font& font, TextAlignment alignment, TextElision elision) +{ + do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) { + draw_one_glyph(r, code_point); + }); } void Painter::set_pixel(const IntPoint& p, Color color) diff --git a/Libraries/LibGfx/Painter.h b/Libraries/LibGfx/Painter.h index cb3271214b..4983f0ef8b 100644 --- a/Libraries/LibGfx/Painter.h +++ b/Libraries/LibGfx/Painter.h @@ -79,6 +79,9 @@ public: void draw_text(const IntRect&, const StringView&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None); void draw_text(const IntRect&, const Utf32View&, const Font&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None); void draw_text(const IntRect&, const Utf32View&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None); + void draw_text(Function, const IntRect&, const StringView&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None); + void draw_text(Function, const IntRect&, const Utf8View&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None); + void draw_text(Function, const IntRect&, const Utf32View&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None); void draw_glyph(const IntPoint&, u32, Color); void draw_glyph(const IntPoint&, u32, const Font&, Color); void draw_emoji(const IntPoint&, const Gfx::Bitmap&, const Font&); @@ -135,9 +138,6 @@ protected: void blit_with_opacity(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity); void draw_pixel(const IntPoint&, Color, int thickness = 1); - void draw_text_line(const IntRect&, const Utf8View&, const Font&, TextAlignment, Color, TextElision); - void draw_text_line(const IntRect&, const Utf32View&, const Font&, TextAlignment, Color, TextElision); - struct State { const Font* font; IntPoint translation; -- cgit v1.2.3