Coverage Report

Created: 2026-02-14 08:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibGfx/TextLayout.h
Line
Count
Source
1
/*
2
 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#pragma once
9
10
#include <AK/ByteString.h>
11
#include <AK/CharacterTypes.h>
12
#include <AK/Forward.h>
13
#include <AK/Utf32View.h>
14
#include <AK/Utf8View.h>
15
#include <AK/Variant.h>
16
#include <AK/Vector.h>
17
#include <LibGfx/Font/Font.h>
18
#include <LibGfx/FontCascadeList.h>
19
#include <LibGfx/Forward.h>
20
#include <LibGfx/Rect.h>
21
#include <LibGfx/TextElision.h>
22
#include <LibGfx/TextWrapping.h>
23
24
namespace Gfx {
25
26
// FIXME: This currently isn't an ideal way of doing things; ideally, TextLayout
27
// would be doing the rendering by painting individual glyphs. However, this
28
// would regress our Unicode bidirectional text support. Therefore, fixing this
29
// requires:
30
// - Moving the bidirectional algorithm either here, or some place TextLayout
31
//   can access;
32
// - Making TextLayout render the given text into something like a Vector<Line>
33
//   where:
34
//   using Line = Vector<DirectionalRun>;
35
//   struct DirectionalRun {
36
//       Utf32View glyphs;
37
//       Vector<int> advance;
38
//       TextDirection direction;
39
//   };
40
// - Either;
41
//   a) Making TextLayout output these Lines directly using a given Painter, or
42
//   b) Taking the Lines from TextLayout and painting each glyph.
43
class TextLayout {
44
public:
45
    TextLayout(Gfx::Font const& font, Utf8View const& text, FloatRect const& rect)
46
0
        : m_font(font)
47
0
        , m_font_metrics(font.pixel_metrics())
48
0
        , m_text(text)
49
0
        , m_rect(rect)
50
0
    {
51
0
    }
52
53
    Vector<ByteString, 32> lines(TextElision elision, TextWrapping wrapping) const
54
0
    {
55
0
        return wrap_lines(elision, wrapping);
56
0
    }
57
58
    FloatRect bounding_rect(TextWrapping) const;
59
60
private:
61
    Vector<ByteString, 32> wrap_lines(TextElision, TextWrapping) const;
62
    ByteString elide_text_from_right(Utf8View) const;
63
64
    Font const& m_font;
65
    FontPixelMetrics m_font_metrics;
66
    Utf8View m_text;
67
    FloatRect m_rect;
68
};
69
70
inline bool should_paint_as_space(u32 code_point)
71
0
{
72
0
    return is_ascii_space(code_point) || code_point == 0xa0;
73
0
}
74
75
enum class IncludeLeftBearing {
76
    Yes,
77
    No
78
};
79
80
struct DrawGlyph {
81
    FloatPoint position;
82
    u32 code_point;
83
84
    void translate_by(FloatPoint const& delta)
85
0
    {
86
0
        position.translate_by(delta);
87
0
    }
88
};
89
90
struct DrawEmoji {
91
    FloatPoint position;
92
    Gfx::Bitmap const* emoji;
93
94
    void translate_by(FloatPoint const& delta)
95
0
    {
96
0
        position.translate_by(delta);
97
0
    }
98
};
99
100
using DrawGlyphOrEmoji = Variant<DrawGlyph, DrawEmoji>;
101
102
class GlyphRun : public RefCounted<GlyphRun> {
103
public:
104
    enum class TextType {
105
        Common,
106
        ContextDependent,
107
        EndPadding,
108
        Ltr,
109
        Rtl,
110
    };
111
112
    GlyphRun(Vector<Gfx::DrawGlyphOrEmoji>&& glyphs, NonnullRefPtr<Font> font, TextType text_type)
113
        : m_glyphs(move(glyphs))
114
        , m_font(move(font))
115
        , m_text_type(text_type)
116
0
    {
117
0
    }
118
119
0
    [[nodiscard]] Font const& font() const { return m_font; }
120
0
    [[nodiscard]] TextType text_type() const { return m_text_type; }
121
0
    [[nodiscard]] Vector<Gfx::DrawGlyphOrEmoji> const& glyphs() const { return m_glyphs; }
122
0
    [[nodiscard]] Vector<Gfx::DrawGlyphOrEmoji>& glyphs() { return m_glyphs; }
123
0
    [[nodiscard]] bool is_empty() const { return m_glyphs.is_empty(); }
124
125
0
    void append(Gfx::DrawGlyphOrEmoji glyph) { m_glyphs.append(glyph); }
126
127
private:
128
    Vector<Gfx::DrawGlyphOrEmoji> m_glyphs;
129
    NonnullRefPtr<Font> m_font;
130
    TextType m_text_type;
131
};
132
133
Variant<DrawGlyph, DrawEmoji> prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIterator& it, Font const& font);
134
135
template<typename Callback>
136
void for_each_glyph_position(FloatPoint baseline_start, Utf8View string, Gfx::Font const& font, Callback callback, IncludeLeftBearing include_left_bearing = IncludeLeftBearing::No, Optional<float&> width = {})
137
0
{
138
0
    float space_width = font.glyph_width(' ') + font.glyph_spacing();
139
140
0
    u32 last_code_point = 0;
141
142
0
    auto point = baseline_start;
143
0
    for (auto code_point_iterator = string.begin(); code_point_iterator != string.end(); ++code_point_iterator) {
144
0
        auto it = code_point_iterator; // The callback function will advance the iterator, so create a copy for this lookup.
145
0
        auto code_point = *code_point_iterator;
146
147
0
        point.set_y(baseline_start.y() - font.pixel_metrics().ascent);
148
149
0
        if (should_paint_as_space(code_point)) {
150
0
            point.translate_by(space_width, 0);
151
0
            last_code_point = code_point;
152
0
            continue;
153
0
        }
154
155
0
        auto kerning = font.glyphs_horizontal_kerning(last_code_point, code_point);
156
0
        if (kerning != 0.0f)
157
0
            point.translate_by(kerning, 0);
158
159
0
        auto glyph_width = font.glyph_or_emoji_width(it) + font.glyph_spacing();
160
0
        auto glyph_or_emoji = prepare_draw_glyph_or_emoji(point, code_point_iterator, font);
161
0
        if (include_left_bearing == IncludeLeftBearing::Yes) {
162
0
            if (glyph_or_emoji.has<DrawGlyph>())
163
0
                glyph_or_emoji.get<DrawGlyph>().position += FloatPoint(font.glyph_left_bearing(code_point), 0);
164
0
        }
165
166
0
        callback(glyph_or_emoji);
167
168
0
        point.translate_by(glyph_width, 0);
169
0
        last_code_point = code_point;
170
0
    }
171
172
0
    if (width.has_value())
173
0
        *width = point.x() - font.glyph_spacing();
174
0
}
Unexecuted instantiation: Painter.cpp:void Gfx::for_each_glyph_position<Gfx::Painter::draw_text_run(Gfx::Point<float>, AK::Utf8View const&, Gfx::Font const&, Gfx::Color)::$_0>(Gfx::Point<float>, AK::Utf8View, Gfx::Font const&, Gfx::Painter::draw_text_run(Gfx::Point<float>, AK::Utf8View const&, Gfx::Font const&, Gfx::Color)::$_0, Gfx::IncludeLeftBearing, AK::Optional<float&>)
Unexecuted instantiation: Path.cpp:void Gfx::for_each_glyph_position<Gfx::Path::text(AK::Utf8View, Gfx::Font const&)::$_0>(Gfx::Point<float>, AK::Utf8View, Gfx::Font const&, Gfx::Path::text(AK::Utf8View, Gfx::Font const&)::$_0, Gfx::IncludeLeftBearing, AK::Optional<float&>)
Unexecuted instantiation: Path.cpp:void Gfx::for_each_glyph_position<Gfx::Path::place_text_along(AK::Utf8View, Gfx::Font const&) const::$_1>(Gfx::Point<float>, AK::Utf8View, Gfx::Font const&, Gfx::Path::place_text_along(AK::Utf8View, Gfx::Font const&) const::$_1, Gfx::IncludeLeftBearing, AK::Optional<float&>)
Unexecuted instantiation: InlineLevelIterator.cpp:void Gfx::for_each_glyph_position<Web::Layout::InlineLevelIterator::next_without_lookahead()::$_0>(Gfx::Point<float>, AK::Utf8View, Gfx::Font const&, Web::Layout::InlineLevelIterator::next_without_lookahead()::$_0, Gfx::IncludeLeftBearing, AK::Optional<float&>)
Unexecuted instantiation: DisplayListRecorder.cpp:void Gfx::for_each_glyph_position<Web::Painting::DisplayListRecorder::draw_text(Gfx::Rect<int> const&, AK::String, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color)::$_0>(Gfx::Point<float>, AK::Utf8View, Gfx::Font const&, Web::Painting::DisplayListRecorder::draw_text(Gfx::Rect<int> const&, AK::String, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color)::$_0, Gfx::IncludeLeftBearing, AK::Optional<float&>)
175
176
}