From 50d33f79fac2f560a9e4cc694bb065b623b54171 Mon Sep 17 00:00:00 2001 From: MacDue Date: Sat, 4 Nov 2023 21:41:24 +0000 Subject: [PATCH] LibGfx: Allow extracting paths from fonts and add Gfx::Path::text() This updates fonts so rather than rastering directly to a bitmap, you can extract paths for glyphs. This is then used to implement a Gfx::Path::text("some text", font) API, that if given a vector font appends the path of the text to your Gfx::Path. This then allows arbitrary manipulation of the text (rotation, skewing, etc), paving the way for Word Art in Serenity. --- Tests/LibGfx/TestFontHandling.cpp | 7 +- .../Libraries/LibGfx/Font/OpenType/Font.cpp | 70 ++++++++++++++----- .../Libraries/LibGfx/Font/OpenType/Font.h | 10 +++ .../Libraries/LibGfx/Font/OpenType/Glyf.cpp | 23 ++---- .../Libraries/LibGfx/Font/OpenType/Glyf.h | 36 +++++----- Userland/Libraries/LibGfx/Font/ScaledFont.cpp | 5 ++ Userland/Libraries/LibGfx/Font/ScaledFont.h | 1 + Userland/Libraries/LibGfx/Font/VectorFont.h | 3 + Userland/Libraries/LibGfx/Font/WOFF/Font.h | 2 + Userland/Libraries/LibGfx/Font/WOFF2/Font.h | 1 + Userland/Libraries/LibGfx/Painter.cpp | 35 +--------- Userland/Libraries/LibGfx/Path.cpp | 22 ++++++ Userland/Libraries/LibGfx/Path.h | 3 + Userland/Libraries/LibGfx/TextLayout.h | 56 +++++++++++++++ 14 files changed, 186 insertions(+), 88 deletions(-) diff --git a/Tests/LibGfx/TestFontHandling.cpp b/Tests/LibGfx/TestFontHandling.cpp index 690ecbed2c..5776c4f6e9 100644 --- a/Tests/LibGfx/TestFontHandling.cpp +++ b/Tests/LibGfx/TestFontHandling.cpp @@ -176,7 +176,7 @@ TEST_CASE(test_character_set_masking) EXPECT(masked_font->glyph_index(0xFFFD).value() == 0x1FD); } -TEST_CASE(rasterize_glyph_containing_single_off_curve_point) +TEST_CASE(resolve_glyph_path_containing_single_off_curve_point) { Vector glyph_data { 0, 5, 0, 205, 255, 51, 7, 51, 6, 225, 0, 3, 0, 6, 0, 9, 0, 12, 0, 15, 0, 31, 64, 13, 13, 2, 15, 5, 7, 2, 8, 5, 10, 3, 0, @@ -188,8 +188,9 @@ TEST_CASE(rasterize_glyph_containing_single_off_curve_point) OpenType::Glyf glyf(glyph_data.span()); auto glyph = glyf.glyph(118); EXPECT(glyph.has_value()); - EXPECT_NO_CRASH("rasterizing glyph containing single off-curve point should not crash", [&] { - (void)glyph->rasterize(0, 0, 1, 1, {}, [&](u16) -> Optional { VERIFY_NOT_REACHED(); }); + EXPECT_NO_CRASH("resolving the path of glyph containing single off-curve point should not crash", [&] { + Gfx::Path path; + (void)glyph->append_path(path, 0, 0, 1, 1, [&](u16) -> Optional { VERIFY_NOT_REACHED(); }); return Test::Crash::Failure::DidNotCrash; }); } diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp b/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp index a090b31b94..021f2da290 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp +++ b/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -708,14 +709,25 @@ float Font::glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, flo return 0.0f; } -RefPtr Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const +Font::AscenderAndDescender Font::resolve_ascender_and_descender() const { - if (auto bitmap = color_bitmap(glyph_id)) { - return bitmap; - } + i16 ascender = 0; + i16 descender = 0; + if (m_os2.has_value() && m_os2->use_typographic_metrics()) { + ascender = m_os2->typographic_ascender(); + descender = m_os2->typographic_descender(); + } else { + ascender = m_hhea.ascender(); + descender = m_hhea.descender(); + } + return { ascender, descender }; +} + +Optional Font::extract_and_append_glyph_path_to(Gfx::Path& path, u32 glyph_id, i16 ascender, i16 descender, float x_scale, float y_scale) const +{ if (!m_loca.has_value() || !m_glyf.has_value()) { - return nullptr; + return {}; } if (glyph_id >= glyph_count()) { @@ -727,30 +739,50 @@ RefPtr Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_s // If a glyph has no outline, then loca[n] = loca [n+1]. if (glyph_offset0 == glyph_offset1) - return nullptr; + return {}; auto glyph = m_glyf->glyph(glyph_offset0); if (!glyph.has_value()) - return nullptr; + return {}; - i16 ascender = 0; - i16 descender = 0; - - if (m_os2.has_value() && m_os2->use_typographic_metrics()) { - ascender = m_os2->typographic_ascender(); - descender = m_os2->typographic_descender(); - } else { - ascender = m_hhea.ascender(); - descender = m_hhea.descender(); - } - - return glyph->rasterize(ascender, descender, x_scale, y_scale, subpixel_offset, [&](u16 glyph_id) { + bool success = glyph->append_path(path, ascender, descender, x_scale, y_scale, [&](u16 glyph_id) { if (glyph_id >= glyph_count()) { glyph_id = 0; } auto glyph_offset = m_loca->get_glyph_offset(glyph_id); return m_glyf->glyph(glyph_offset); }); + + if (success) + return glyph; + return {}; +} + +bool Font::append_glyph_path_to(Gfx::Path& path, u32 glyph_id, float x_scale, float y_scale) const +{ + auto ascender_and_descender = resolve_ascender_and_descender(); + return extract_and_append_glyph_path_to(path, glyph_id, ascender_and_descender.ascender, ascender_and_descender.descender, x_scale, y_scale).has_value(); +} + +RefPtr Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const +{ + if (auto bitmap = color_bitmap(glyph_id)) + return bitmap; + + auto ascender_and_descender = resolve_ascender_and_descender(); + Gfx::Path path; + path.move_to(subpixel_offset.to_float_point()); + auto glyph = extract_and_append_glyph_path_to(path, glyph_id, ascender_and_descender.ascender, ascender_and_descender.descender, x_scale, y_scale); + if (!glyph.has_value()) + return {}; + + u32 width = (u32)(ceilf((glyph->xmax() - glyph->xmin()) * x_scale)) + 2; + u32 height = (u32)(ceilf((ascender_and_descender.ascender - ascender_and_descender.descender) * y_scale)) + 2; + auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }).release_value_but_fixme_should_propagate_errors(); + Gfx::Painter painter { bitmap }; + Gfx::AntiAliasingPainter aa_painter(painter); + aa_painter.fill_path(path, Gfx::Color::White); + return bitmap; } u32 Font::glyph_count() const diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Font.h b/Userland/Libraries/LibGfx/Font/OpenType/Font.h index c0e1f7e56b..3fa9f7d2c7 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Font.h +++ b/Userland/Libraries/LibGfx/Font/OpenType/Font.h @@ -31,6 +31,7 @@ public: virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const override; virtual float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const override; virtual RefPtr rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset) const override; + virtual bool append_glyph_path_to(Gfx::Path&, u32 glyph_id, float x_scale, float y_scale) const override; virtual u32 glyph_count() const override; virtual u16 units_per_em() const override; virtual u32 glyph_id_for_code_point(u32 code_point) const override; @@ -47,6 +48,15 @@ public: Optional glyph_program(u32 glyph_id) const; private: + struct AscenderAndDescender { + i16 ascender; + i16 descender; + }; + + AscenderAndDescender resolve_ascender_and_descender() const; + + Optional extract_and_append_glyph_path_to(Gfx::Path&, u32 glyph_id, i16 ascender, i16 descender, float x_scale, float y_scale) const; + RefPtr color_bitmap(u32 glyph_id) const; struct EmbeddedBitmapWithFormat17 { diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Glyf.cpp b/Userland/Libraries/LibGfx/Font/OpenType/Glyf.cpp index 76847d902f..92f7db75c6 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Glyf.cpp +++ b/Userland/Libraries/LibGfx/Font/OpenType/Glyf.cpp @@ -245,7 +245,7 @@ ReadonlyBytes Glyf::Glyph::program() const return m_slice.slice(instructions_start + 2, num_instructions); } -void Glyf::Glyph::rasterize_impl(Gfx::Painter& painter, Gfx::AffineTransform const& transform) const +void Glyf::Glyph::append_path_impl(Gfx::Path& path, Gfx::AffineTransform const& transform) const { // Get offset for flags, x, and y. u16 num_points = be_u16(m_slice.offset((m_num_contours - 1) * 2)) + 1; @@ -256,7 +256,6 @@ void Glyf::Glyph::rasterize_impl(Gfx::Painter& painter, Gfx::AffineTransform con get_ttglyph_offsets(m_slice, num_points, flags_offset, &x_offset, &y_offset); // Prepare to render glyph. - Gfx::Path path; PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, transform); u32 current_point_index = 0; @@ -297,32 +296,24 @@ void Glyf::Glyph::rasterize_impl(Gfx::Painter& painter, Gfx::AffineTransform con } } } - - constexpr auto base_color = Color::White; - Gfx::AntiAliasingPainter aa_painter { painter }; - aa_painter.fill_path(path, base_color); } -RefPtr Glyf::Glyph::rasterize_simple(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const +bool Glyf::Glyph::append_simple_path(Gfx::Path& path, i16 font_ascender, i16 font_descender, float x_scale, float y_scale) const { if (m_xmin > m_xmax) [[unlikely]] { dbgln("OpenType: Glyph has invalid xMin ({}) > xMax ({})", m_xmin, m_xmax); - return nullptr; + return false; } if (font_descender > font_ascender) [[unlikely]] { dbgln("OpenType: Glyph has invalid ascender ({}) > descender ({})", font_ascender, font_descender); - return nullptr; + return false; } - u32 width = (u32)(ceilf((m_xmax - m_xmin) * x_scale)) + 2; - u32 height = (u32)(ceilf((font_ascender - font_descender) * y_scale)) + 2; - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }).release_value_but_fixme_should_propagate_errors(); auto affine = Gfx::AffineTransform() - .translate(subpixel_offset.to_float_point()) + .translate(path.last_point()) .scale(x_scale, -y_scale) .translate(-m_xmin, -font_ascender); - Gfx::Painter painter { bitmap }; - rasterize_impl(painter, affine); - return bitmap; + append_path_impl(path, affine); + return true; } Optional Glyf::glyph(u32 offset) const diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Glyf.h b/Userland/Libraries/LibGfx/Font/OpenType/Glyf.h index 2b2fdefbe8..5901686c55 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Glyf.h +++ b/Userland/Libraries/LibGfx/Font/OpenType/Glyf.h @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace OpenType { @@ -71,17 +72,22 @@ public: m_type = Type::Simple; } } + template - RefPtr rasterize(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset, GlyphCb glyph_callback) const + bool append_path(Gfx::Path& path, i16 font_ascender, i16 font_descender, float x_scale, float y_scale, GlyphCb glyph_callback) const { switch (m_type) { case Type::Simple: - return rasterize_simple(font_ascender, font_descender, x_scale, y_scale, subpixel_offset); + return append_simple_path(path, font_ascender, font_descender, x_scale, y_scale); case Type::Composite: - return rasterize_composite(font_ascender, font_descender, x_scale, y_scale, subpixel_offset, glyph_callback); + return append_composite_path(path, font_ascender, x_scale, y_scale, glyph_callback); } VERIFY_NOT_REACHED(); } + + i16 xmax() const { return m_xmax; } + i16 xmin() const { return m_xmin; } + int ascender() const { return m_ymax; } int descender() const { return m_ymin; } @@ -112,11 +118,11 @@ public: u32 m_offset { 0 }; }; - void rasterize_impl(Gfx::Painter&, Gfx::AffineTransform const&) const; - RefPtr rasterize_simple(i16 ascender, i16 descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset) const; + void append_path_impl(Gfx::Path&, Gfx::AffineTransform const&) const; + bool append_simple_path(Gfx::Path&, i16 ascender, i16 descender, float x_scale, float y_scale) const; template - void rasterize_composite_loop(Gfx::Painter& painter, Gfx::AffineTransform const& transform, GlyphCb glyph_callback) const + void resolve_composite_path_loop(Gfx::Path& path, Gfx::AffineTransform const& transform, GlyphCb glyph_callback) const { ComponentIterator component_iterator(m_slice); @@ -133,28 +139,22 @@ public: continue; if (glyph->m_type == Type::Simple) { - glyph->rasterize_impl(painter, affine_here); + glyph->append_path_impl(path, affine_here); } else { - glyph->rasterize_composite_loop(painter, transform, glyph_callback); + glyph->resolve_composite_path_loop(path, transform, glyph_callback); } } } template - RefPtr rasterize_composite(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset, GlyphCb glyph_callback) const + bool append_composite_path(Gfx::Path& path, i16 font_ascender, float x_scale, float y_scale, GlyphCb glyph_callback) const { - u32 width = (u32)(ceilf((m_xmax - m_xmin) * x_scale)) + 1; - u32 height = (u32)(ceilf((font_ascender - font_descender) * y_scale)) + 1; - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }).release_value_but_fixme_should_propagate_errors(); auto affine = Gfx::AffineTransform() - .translate(subpixel_offset.to_float_point()) + .translate(path.last_point()) .scale(x_scale, -y_scale) .translate(-m_xmin, -font_ascender); - - Gfx::Painter painter { bitmap }; - rasterize_composite_loop(painter, affine, glyph_callback); - - return bitmap; + resolve_composite_path_loop(path, affine, glyph_callback); + return true; } Type m_type { Type::Composite }; diff --git a/Userland/Libraries/LibGfx/Font/ScaledFont.cpp b/Userland/Libraries/LibGfx/Font/ScaledFont.cpp index d64682a923..15524c0275 100644 --- a/Userland/Libraries/LibGfx/Font/ScaledFont.cpp +++ b/Userland/Libraries/LibGfx/Font/ScaledFont.cpp @@ -83,6 +83,11 @@ RefPtr ScaledFont::rasterize_glyph(u32 glyph_id, GlyphSubpixelOffse return glyph_bitmap; } +bool ScaledFont::append_glyph_path_to(Gfx::Path& path, u32 glyph_id) const +{ + return m_font->append_glyph_path_to(path, glyph_id, m_x_scale, m_y_scale); +} + Gfx::Glyph ScaledFont::glyph(u32 code_point) const { return glyph(code_point, GlyphSubpixelOffset { 0, 0 }); diff --git a/Userland/Libraries/LibGfx/Font/ScaledFont.h b/Userland/Libraries/LibGfx/Font/ScaledFont.h index 2e13982b13..1ae93d69bf 100644 --- a/Userland/Libraries/LibGfx/Font/ScaledFont.h +++ b/Userland/Libraries/LibGfx/Font/ScaledFont.h @@ -31,6 +31,7 @@ public: ScaledFontMetrics metrics() const { return m_font->metrics(m_x_scale, m_y_scale); } ScaledGlyphMetrics glyph_metrics(u32 glyph_id) const { return m_font->glyph_metrics(glyph_id, m_x_scale, m_y_scale, m_point_width, m_point_height); } RefPtr rasterize_glyph(u32 glyph_id, GlyphSubpixelOffset) const; + bool append_glyph_path_to(Gfx::Path&, u32 glyph_id) const; // ^Gfx::Font virtual NonnullRefPtr clone() const override { return MUST(try_clone()); } // FIXME: clone() should not need to be implemented diff --git a/Userland/Libraries/LibGfx/Font/VectorFont.h b/Userland/Libraries/LibGfx/Font/VectorFont.h index ebd8714ff3..07de33c0d9 100644 --- a/Userland/Libraries/LibGfx/Font/VectorFont.h +++ b/Userland/Libraries/LibGfx/Font/VectorFont.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace Gfx { @@ -39,6 +40,8 @@ public: virtual ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const = 0; virtual float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const = 0; virtual RefPtr rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, GlyphSubpixelOffset) const = 0; + virtual bool append_glyph_path_to(Gfx::Path&, u32 glyph_id, float x_scale, float y_scale) const = 0; + virtual u32 glyph_count() const = 0; virtual u16 units_per_em() const = 0; virtual u32 glyph_id_for_code_point(u32 code_point) const = 0; diff --git a/Userland/Libraries/LibGfx/Font/WOFF/Font.h b/Userland/Libraries/LibGfx/Font/WOFF/Font.h index d2be0553ac..beae3e1a0b 100644 --- a/Userland/Libraries/LibGfx/Font/WOFF/Font.h +++ b/Userland/Libraries/LibGfx/Font/WOFF/Font.h @@ -28,6 +28,8 @@ public: virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const override { return m_input_font->glyph_metrics(glyph_id, x_scale, y_scale, point_width, point_height); } virtual float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const override { return m_input_font->glyphs_horizontal_kerning(left_glyph_id, right_glyph_id, x_scale); } virtual RefPtr rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const override { return m_input_font->rasterize_glyph(glyph_id, x_scale, y_scale, subpixel_offset); } + virtual bool append_glyph_path_to(Gfx::Path& path, u32 glyph_id, float x_scale, float y_scale) const override { return m_input_font->append_glyph_path_to(path, glyph_id, x_scale, y_scale); } + virtual u32 glyph_count() const override { return m_input_font->glyph_count(); } virtual u16 units_per_em() const override { return m_input_font->units_per_em(); } virtual u32 glyph_id_for_code_point(u32 code_point) const override { return m_input_font->glyph_id_for_code_point(code_point); } diff --git a/Userland/Libraries/LibGfx/Font/WOFF2/Font.h b/Userland/Libraries/LibGfx/Font/WOFF2/Font.h index 1cfd6b1e67..fab0bf15f1 100644 --- a/Userland/Libraries/LibGfx/Font/WOFF2/Font.h +++ b/Userland/Libraries/LibGfx/Font/WOFF2/Font.h @@ -32,6 +32,7 @@ public: { return m_input_font->rasterize_glyph(glyph_id, x_scale, y_scale, subpixel_offset); } + virtual bool append_glyph_path_to(Gfx::Path& path, u32 glyph_id, float x_scale, float y_scale) const override { return m_input_font->append_glyph_path_to(path, glyph_id, x_scale, y_scale); } virtual u32 glyph_count() const override { return m_input_font->glyph_count(); } virtual u16 units_per_em() const override { return m_input_font->units_per_em(); } virtual u32 glyph_id_for_code_point(u32 code_point) const override { return m_input_font->glyph_id_for_code_point(code_point); } diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index ee75e909ec..513dd45543 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -42,11 +42,6 @@ namespace Gfx { -static bool should_paint_as_space(u32 code_point) -{ - return is_ascii_space(code_point) || code_point == 0xa0; -} - ALWAYS_INLINE static Color color_for_format(BitmapFormat format, ARGB32 value) { switch (format) { @@ -2478,33 +2473,9 @@ 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) { - float space_width = font.glyph_width(' ') + font.glyph_spacing(); - - u32 last_code_point = 0; - - auto point = baseline_start; - point.translate_by(0, -font.pixel_metrics().ascent); - - 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); - last_code_point = code_point; - continue; - } - - auto kerning = font.glyphs_horizontal_kerning(last_code_point, code_point); - if (kerning != 0.0f) - point.translate_by(kerning, 0); - - 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(); - - draw_glyph_or_emoji(point, code_point_iterator, font, color); - - point.translate_by(glyph_width, 0); - last_code_point = code_point; - } + for_each_glyph_position(baseline_start, string, font, [&](GlyphPosition glyph_position) { + draw_glyph_or_emoji(glyph_position.position, glyph_position.it, font, color); + }); } void Painter::draw_scaled_bitmap_with_transform(IntRect const& dst_rect, Bitmap const& bitmap, FloatRect const& src_rect, AffineTransform const& transform, float opacity, Painter::ScalingMode scaling_mode) diff --git a/Userland/Libraries/LibGfx/Path.cpp b/Userland/Libraries/LibGfx/Path.cpp index 9caf5d0b8e..d5c20ea25f 100644 --- a/Userland/Libraries/LibGfx/Path.cpp +++ b/Userland/Libraries/LibGfx/Path.cpp @@ -9,8 +9,11 @@ #include #include #include +#include +#include #include #include +#include namespace Gfx { @@ -156,6 +159,25 @@ void Path::elliptical_arc_to(FloatPoint point, FloatSize radii, float x_axis_rot theta_1, theta_delta); } + +void Path::text(Utf8View text, Font const& font) +{ + if (!is(font)) { + // FIXME: This API only accepts Gfx::Font for ease of use. + 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); + }, + IncludeLeftBearing::Yes); +} + FloatPoint Path::last_point() { FloatPoint last_point { 0, 0 }; diff --git a/Userland/Libraries/LibGfx/Path.h b/Userland/Libraries/LibGfx/Path.h index 8c519c31c4..13ccf2f933 100644 --- a/Userland/Libraries/LibGfx/Path.h +++ b/Userland/Libraries/LibGfx/Path.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -153,6 +154,8 @@ public: elliptical_arc_to(point, { radius, radius }, 0, large_arc, sweep); } + void text(Utf8View, Font const&); + FloatPoint last_point(); void close(); diff --git a/Userland/Libraries/LibGfx/TextLayout.h b/Userland/Libraries/LibGfx/TextLayout.h index 944876697c..db2cad499a 100644 --- a/Userland/Libraries/LibGfx/TextLayout.h +++ b/Userland/Libraries/LibGfx/TextLayout.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -64,4 +65,59 @@ private: FloatRect m_rect; }; +inline bool should_paint_as_space(u32 code_point) +{ + return is_ascii_space(code_point) || code_point == 0xa0; +} + +enum class IncludeLeftBearing { + Yes, + No +}; + +struct GlyphPosition { + FloatPoint position; + float glyph_width; + AK::Utf8CodePointIterator& it; +}; + +template +void for_each_glyph_position(FloatPoint start, Utf8View text, 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; + point.translate_by(0, -font.pixel_metrics().ascent); + + for (auto code_point_iterator = text.begin(); code_point_iterator != text.end(); ++code_point_iterator) { + auto code_point = *code_point_iterator; + if (should_paint_as_space(code_point)) { + point.translate_by(space_width, 0); + last_code_point = code_point; + continue; + } + + auto kerning = font.glyphs_horizontal_kerning(last_code_point, code_point); + if (kerning != 0.0f) + point.translate_by(kerning, 0); + + 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); + + callback(GlyphPosition { + .position = glyph_point, + .glyph_width = glyph_width, + .it = code_point_iterator }); + + point.translate_by(glyph_width, 0); + last_code_point = code_point; + } +} + }