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