diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index 513dd45543..5e4fbdbed4 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -1416,48 +1416,14 @@ void Painter::draw_glyph_or_emoji(FloatPoint point, u32 code_point, Font const& void Painter::draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIterator& it, Font const& font, Color color) { - u32 code_point = *it; - auto next_code_point = it.peek(1); - - ScopeGuard consume_variation_selector = [&, initial_it = it] { - static auto const variation_selector = Unicode::property_from_string("Variation_Selector"sv); - if (!variation_selector.has_value()) - return; - - // If we advanced the iterator to consume an emoji sequence, don't look for another variation selector. - if (initial_it != it) - return; - - // Otherwise, discard one code point if it's a variation selector. - if (next_code_point.has_value() && Unicode::code_point_has_property(*next_code_point, *variation_selector)) - ++it; - }; - - // NOTE: We don't check for emoji - auto font_contains_glyph = font.contains_glyph(code_point); - auto check_for_emoji = !font.has_color_bitmaps() && Unicode::could_be_start_of_emoji_sequence(it, font_contains_glyph ? Unicode::SequenceType::EmojiPresentation : Unicode::SequenceType::Any); - - // If the font contains the glyph, and we know it's not the start of an emoji, draw a text glyph. - if (font_contains_glyph && !check_for_emoji) { - draw_glyph(point, code_point, font, color); - return; + auto draw_glyph_or_emoji = prepare_draw_glyph_or_emoji(point, it, font); + if (draw_glyph_or_emoji.has()) { + auto& glyph = draw_glyph_or_emoji.get(); + draw_glyph(glyph.position, glyph.code_point, *glyph.font, color); + } else { + auto& emoji = draw_glyph_or_emoji.get(); + draw_emoji(emoji.position, *emoji.emoji, *emoji.font); } - - // If we didn't find a text glyph, or have an emoji variation selector or regional indicator, try to draw an emoji glyph. - if (auto const* emoji = Emoji::emoji_for_code_point_iterator(it)) { - draw_emoji(point.to_type(), *emoji, font); - return; - } - - // If that failed, but we have a text glyph fallback, draw that. - if (font_contains_glyph) { - draw_glyph(point, code_point, font, color); - return; - } - - // No suitable glyph found, draw a replacement character. - dbgln_if(EMOJI_DEBUG, "Failed to find a glyph or emoji for code_point {}", code_point); - draw_glyph(point, 0xFFFD, font, color); } void Painter::draw_glyph(IntPoint point, u32 code_point, Color color) @@ -2473,8 +2439,14 @@ void Painter::draw_text_run(IntPoint baseline_start, Utf8View const& string, Fon void Painter::draw_text_run(FloatPoint baseline_start, Utf8View const& string, Font const& font, Color color) { - for_each_glyph_position(baseline_start, string, font, [&](GlyphPosition glyph_position) { - draw_glyph_or_emoji(glyph_position.position, glyph_position.it, font, color); + for_each_glyph_position(baseline_start, string, font, [&](DrawGlyphOrEmoji glyph_or_emoji) { + if (glyph_or_emoji.has()) { + auto& glyph = glyph_or_emoji.get(); + draw_glyph(glyph.position, glyph.code_point, *glyph.font, color); + } else { + auto& emoji = glyph_or_emoji.get(); + draw_emoji(emoji.position, *emoji.emoji, *emoji.font); + } }); } diff --git a/Userland/Libraries/LibGfx/Path.cpp b/Userland/Libraries/LibGfx/Path.cpp index d5c20ea25f..f4ad8e1587 100644 --- a/Userland/Libraries/LibGfx/Path.cpp +++ b/Userland/Libraries/LibGfx/Path.cpp @@ -167,13 +167,16 @@ void Path::text(Utf8View text, Font const& font) dbgln("Cannot path-ify bitmap fonts!"); return; } + auto& scaled_font = static_cast(font); for_each_glyph_position( - last_point(), text, font, [&](GlyphPosition glyph_position) { - move_to(glyph_position.position); - // FIXME: This does not correctly handle multi-codepoint glyphs (i.e. emojis). - auto glyph_id = scaled_font.glyph_id_for_code_point(*glyph_position.it); - scaled_font.append_glyph_path_to(*this, glyph_id); + last_point(), text, font, [&](DrawGlyphOrEmoji glyph_or_emoji) { + if (glyph_or_emoji.has()) { + auto& glyph = glyph_or_emoji.get(); + move_to(glyph.position); + auto glyph_id = scaled_font.glyph_id_for_code_point(glyph.code_point); + scaled_font.append_glyph_path_to(*this, glyph_id); + } }, IncludeLeftBearing::Yes); } diff --git a/Userland/Libraries/LibGfx/TextLayout.cpp b/Userland/Libraries/LibGfx/TextLayout.cpp index e9d352b49b..4cb50a544b 100644 --- a/Userland/Libraries/LibGfx/TextLayout.cpp +++ b/Userland/Libraries/LibGfx/TextLayout.cpp @@ -6,6 +6,10 @@ */ #include "TextLayout.h" +#include "Font/Emoji.h" +#include +#include +#include namespace Gfx { @@ -192,4 +196,74 @@ DeprecatedString TextLayout::elide_text_from_right(Utf8View text) const return text.as_string(); } +DrawGlyphOrEmoji prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIterator& it, Font const& font) +{ + u32 code_point = *it; + auto next_code_point = it.peek(1); + + ScopeGuard consume_variation_selector = [&, initial_it = it] { + static auto const variation_selector = Unicode::property_from_string("Variation_Selector"sv); + if (!variation_selector.has_value()) + return; + + // If we advanced the iterator to consume an emoji sequence, don't look for another variation selector. + if (initial_it != it) + return; + + // Otherwise, discard one code point if it's a variation selector. + if (next_code_point.has_value() && Unicode::code_point_has_property(*next_code_point, *variation_selector)) + ++it; + }; + + // NOTE: We don't check for emoji + auto font_contains_glyph = font.contains_glyph(code_point); + auto check_for_emoji = !font.has_color_bitmaps() && Unicode::could_be_start_of_emoji_sequence(it, font_contains_glyph ? Unicode::SequenceType::EmojiPresentation : Unicode::SequenceType::Any); + + // If the font contains the glyph, and we know it's not the start of an emoji, draw a text glyph. + if (font_contains_glyph && !check_for_emoji) { + return DrawGlyph { + .position = point, + .code_point = code_point, + .font = &font, + }; + } + + // If we didn't find a text glyph, or have an emoji variation selector or regional indicator, try to draw an emoji glyph. + if (auto const* emoji = Emoji::emoji_for_code_point_iterator(it)) { + return DrawEmoji { + .position = point.to_type(), + .emoji = emoji, + .font = &font, + }; + } + + // If that failed, but we have a text glyph fallback, draw that. + if (font_contains_glyph) { + return DrawGlyph { + .position = point, + .code_point = code_point, + .font = &font, + }; + } + + // No suitable glyph found, draw a replacement character. + dbgln_if(EMOJI_DEBUG, "Failed to find a glyph or emoji for code_point {}", code_point); + return DrawGlyph { + .position = point, + .code_point = 0xFFFD, + .font = &font, + }; +} + +Vector get_glyph_run(FloatPoint baseline_start, Utf8View const& string, Font const& font, IncludeLeftBearing include_left_bearing) +{ + Vector glyphs_or_emojis; + for_each_glyph_position( + baseline_start, string, font, [&](auto glyph_or_emoji) { + glyphs_or_emojis.append(glyph_or_emoji); + }, + include_left_bearing); + return glyphs_or_emojis; +} + } diff --git a/Userland/Libraries/LibGfx/TextLayout.h b/Userland/Libraries/LibGfx/TextLayout.h index db2cad499a..68dbc88d64 100644 --- a/Userland/Libraries/LibGfx/TextLayout.h +++ b/Userland/Libraries/LibGfx/TextLayout.h @@ -75,23 +75,33 @@ enum class IncludeLeftBearing { No }; -struct GlyphPosition { +struct DrawGlyph { FloatPoint position; - float glyph_width; - AK::Utf8CodePointIterator& it; + u32 code_point; + Font const* font; }; +struct DrawEmoji { + IntPoint position; + Gfx::Bitmap const* emoji; + Font const* font; +}; + +using DrawGlyphOrEmoji = Variant; + +Variant prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIterator& it, Font const& font); + template -void for_each_glyph_position(FloatPoint start, Utf8View text, Font const& font, Callback callback, IncludeLeftBearing include_left_bearing = IncludeLeftBearing::No) +void for_each_glyph_position(FloatPoint baseline_start, Utf8View string, Font const& font, Callback callback, IncludeLeftBearing include_left_bearing = IncludeLeftBearing::No) { float space_width = font.glyph_width(' ') + font.glyph_spacing(); u32 last_code_point = 0; - auto point = start; + auto point = baseline_start; point.translate_by(0, -font.pixel_metrics().ascent); - for (auto code_point_iterator = text.begin(); code_point_iterator != text.end(); ++code_point_iterator) { + for (auto code_point_iterator = string.begin(); code_point_iterator != string.end(); ++code_point_iterator) { auto code_point = *code_point_iterator; if (should_paint_as_space(code_point)) { point.translate_by(space_width, 0); @@ -106,18 +116,19 @@ void for_each_glyph_position(FloatPoint start, Utf8View text, Font const& font, auto it = code_point_iterator; // The callback function will advance the iterator, so create a copy for this lookup. auto glyph_width = font.glyph_or_emoji_width(it) + font.glyph_spacing(); - auto glyph_point = point; - if (include_left_bearing == IncludeLeftBearing::Yes) - glyph_point += FloatPoint(font.glyph_left_bearing(code_point), 0); + auto glyph_or_emoji = prepare_draw_glyph_or_emoji(point, code_point_iterator, font); + if (include_left_bearing == IncludeLeftBearing::Yes) { + if (glyph_or_emoji.has()) + glyph_or_emoji.get().position += FloatPoint(font.glyph_left_bearing(code_point), 0); + } - callback(GlyphPosition { - .position = glyph_point, - .glyph_width = glyph_width, - .it = code_point_iterator }); + callback(glyph_or_emoji); point.translate_by(glyph_width, 0); last_code_point = code_point; } } +Vector get_glyph_run(FloatPoint baseline_start, Utf8View const& string, Font const& font, IncludeLeftBearing include_left_bearing = IncludeLeftBearing::No); + }