Skip to content

Commit f6b0851

Browse files
kalenikaliaksandrawesomekling
authored andcommitted
LibGfx+LibWeb: Support per-glyph font fallbacks in canvas text painting
Instead of using the first font from the FontCascadeList for all glyphs in a text, we perform a text shaping process that finds a suitable font for each glyph and returns a list of glyph runs, where each glyph run represents consecutive glyphs using the same font.
1 parent 1e7922f commit f6b0851

File tree

3 files changed

+49
-4
lines changed

3 files changed

+49
-4
lines changed

Libraries/LibGfx/TextLayout.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/*
22
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
33
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
4+
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
45
*
56
* SPDX-License-Identifier: BSD-2-Clause
67
*/
@@ -12,6 +13,45 @@
1213

1314
namespace Gfx {
1415

16+
Vector<NonnullRefPtr<GlyphRun>> shape_text(FloatPoint baseline_start, Utf8View string, FontCascadeList const& font_cascade_list)
17+
{
18+
if (string.length() == 0)
19+
return {};
20+
21+
Vector<NonnullRefPtr<GlyphRun>> runs;
22+
23+
auto it = string.begin();
24+
auto substring_begin_offset = string.iterator_offset(it);
25+
Font const* last_font = &font_cascade_list.font_for_code_point(*it);
26+
FloatPoint last_position = baseline_start;
27+
28+
auto add_run = [&runs, &last_position](Utf8View string, Font const& font) {
29+
auto run = shape_text(last_position, 0, string, font, GlyphRun::TextType::Common, {});
30+
last_position.translate_by(run->width(), 0);
31+
runs.append(*run);
32+
};
33+
34+
while (it != string.end()) {
35+
auto code_point = *it;
36+
auto const* font = &font_cascade_list.font_for_code_point(code_point);
37+
if (font != last_font) {
38+
auto substring = string.substring_view(substring_begin_offset, string.iterator_offset(it) - substring_begin_offset);
39+
add_run(substring, *last_font);
40+
last_font = font;
41+
substring_begin_offset = string.iterator_offset(it);
42+
}
43+
++it;
44+
}
45+
46+
auto end_offset = string.iterator_offset(it);
47+
if (substring_begin_offset < end_offset) {
48+
auto substring = string.substring_view(substring_begin_offset, end_offset - substring_begin_offset);
49+
add_run(substring, *last_font);
50+
}
51+
52+
return runs;
53+
}
54+
1555
RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type, ShapeFeatures const& features)
1656
{
1757
static hb_buffer_t* buffer = hb_buffer_create();

Libraries/LibGfx/TextLayout.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/*
22
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
33
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
4+
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
45
*
56
* SPDX-License-Identifier: BSD-2-Clause
67
*/
@@ -12,6 +13,7 @@
1213
#include <AK/Utf8View.h>
1314
#include <AK/Vector.h>
1415
#include <LibGfx/Font/Font.h>
16+
#include <LibGfx/FontCascadeList.h>
1517
#include <LibGfx/Forward.h>
1618
#include <LibGfx/Point.h>
1719

@@ -69,6 +71,7 @@ class GlyphRun : public AtomicRefCounted<GlyphRun> {
6971
};
7072

7173
RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType, ShapeFeatures const& features);
74+
Vector<NonnullRefPtr<GlyphRun>> shape_text(FloatPoint baseline_start, Utf8View string, FontCascadeList const&);
7275
float measure_text_width(Utf8View const& string, Gfx::Font const& font, ShapeFeatures const& features);
7376

7477
}

Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,13 @@ Gfx::Path CanvasRenderingContext2D::text_path(StringView text, float x, float y,
232232

233233
auto& drawing_state = this->drawing_state();
234234

235-
auto const& font = font_cascade_list()->first();
236-
235+
auto const& font_cascade_list = this->font_cascade_list();
236+
auto const& font = font_cascade_list->first();
237+
auto glyph_runs = Gfx::shape_text({ x, y }, Utf8View(text), *font_cascade_list);
237238
Gfx::Path path;
238-
auto glyph_run = Gfx::shape_text({ x, y }, 0, Utf8View(text), font, Gfx::GlyphRun::TextType::Ltr, {});
239-
path.glyph_run(*glyph_run);
239+
for (auto const& glyph_run : glyph_runs) {
240+
path.glyph_run(glyph_run);
241+
}
240242

241243
auto text_width = path.bounding_box().width();
242244
Gfx::AffineTransform transform = {};

0 commit comments

Comments
 (0)