LibGfx+LibWeb: Only trigger @font-face loads from text shaping

This commit is contained in:
Johan Dahlin
2026-04-22 09:17:48 +02:00
committed by Andreas Kling
parent acabf765c1
commit 8c49029bb7
Notes: github-actions[bot] 2026-04-25 15:07:53 +00:00
5 changed files with 35 additions and 20 deletions

View File

@@ -58,20 +58,27 @@ void FontCascadeList::extend(FontCascadeList const& other)
m_pending_faces.extend(other.m_pending_faces);
}
Gfx::Font const& FontCascadeList::font_for_code_point(u32 code_point) const
Gfx::Font const& FontCascadeList::font_for_code_point(u32 code_point, TriggerPendingLoads trigger_pending_loads) const
{
// Walk pending entries first: if this codepoint falls in an unloaded face's
// unicode-range we kick off the fetch and drop the entry — a fallback font that
// happens to cover the codepoint shouldn't prevent the real face from loading.
// FontComputer::clear_computed_font_cache() rebuilds the cascade once the fetch
// completes, so later shapes pick up the loaded face. Run before the ASCII cache
// lookup so a previously-cached codepoint still triggers a newly-added face.
m_pending_faces.remove_all_matching([code_point](auto const& pending) {
if (!pending->covers(code_point))
return false;
pending->start_load();
return true;
});
// Only the text-shaping paths pass TriggerPendingLoads::Yes. Probes that don't
// lead to a glyph being drawn (the U+0020 check used to compute first-available-
// font metrics, for instance) skip this block so they can't initiate a download
// for a subset face that happens to cover the probe codepoint.
if (trigger_pending_loads == TriggerPendingLoads::Yes) {
// Walk pending entries first: if this codepoint falls in an unloaded face's
// unicode-range we kick off the fetch and drop the entry — a fallback font
// that happens to cover the codepoint shouldn't prevent the real face from
// loading. FontComputer::clear_computed_font_cache() rebuilds the cascade
// once the fetch completes, so later shapes pick up the loaded face. Run
// before the ASCII cache lookup so a previously-cached codepoint still
// triggers a newly-added face.
m_pending_faces.remove_all_matching([code_point](auto const& pending) {
if (!pending->covers(code_point))
return false;
pending->start_load();
return true;
});
}
if (code_point < m_ascii_cache.size()) {
if (auto const* cached = m_ascii_cache[code_point])

View File

@@ -43,7 +43,15 @@ public:
void extend(FontCascadeList const& other);
Gfx::Font const& font_for_code_point(u32 code_point) const;
// A pending-face fetch should only be initiated for codepoints that are actually
// being shaped into glyph runs. Callers that merely probe the cascade (e.g. the
// U+0020 check in "first available font" metrics) pass No so that probing does
// not kick off downloads for subset faces that happen to cover the probe point.
enum class TriggerPendingLoads : u8 {
No,
Yes,
};
Gfx::Font const& font_for_code_point(u32 code_point, TriggerPendingLoads = TriggerPendingLoads::No) const;
bool equals(FontCascadeList const& other) const;

View File

@@ -121,7 +121,7 @@ Vector<NonnullRefPtr<GlyphRun>> shape_text(FloatPoint baseline_start, Utf16View
auto it = string.begin();
auto substring_begin_offset = string.iterator_offset(it);
Font const* last_font = &font_cascade_list.font_for_code_point(*it);
Font const* last_font = &font_cascade_list.font_for_code_point(*it, FontCascadeList::TriggerPendingLoads::Yes);
FloatPoint last_position = baseline_start;
auto add_run = [&runs, &last_position, letter_spacing](Utf16View const& string, Font const& font) {
@@ -132,7 +132,7 @@ Vector<NonnullRefPtr<GlyphRun>> shape_text(FloatPoint baseline_start, Utf16View
while (it != string.end()) {
auto code_point = *it;
auto const* font = &font_cascade_list.font_for_code_point(code_point);
auto const* font = &font_cascade_list.font_for_code_point(code_point, FontCascadeList::TriggerPendingLoads::Yes);
if (font != last_font) {
auto substring = string.substring_view(substring_begin_offset, string.iterator_offset(it) - substring_begin_offset);
add_run(substring, *last_font);

View File

@@ -643,7 +643,7 @@ Gfx::Font const& TextNode::ChunkIterator::font_for_space(size_t at_index, u32 sp
for (size_t i = at_index; i < m_view.length_in_code_units();) {
auto cp = m_view.code_point_at(i);
if (!is_interword_space(cp) && cp != '\t' && cp != '\n') {
auto const& font = m_font_cascade_list.font_for_code_point(cp);
auto const& font = m_font_cascade_list.font_for_code_point(cp, Gfx::FontCascadeList::TriggerPendingLoads::Yes);
if (!font.is_emoji_font() && has_glyph(font))
return font;
// Text is coming from an emoji face; we'll fall back to (3).
@@ -653,7 +653,7 @@ Gfx::Font const& TextNode::ChunkIterator::font_for_space(size_t at_index, u32 sp
}
// 3. No text around (leading/trailing/all spaces) — pick a font with the glyph from the cascade.
return m_font_cascade_list.font_for_code_point(space_code_point);
return m_font_cascade_list.font_for_code_point(space_code_point, Gfx::FontCascadeList::TriggerPendingLoads::Yes);
}
Optional<TextNode::Chunk> TextNode::ChunkIterator::next_without_peek()
@@ -680,7 +680,7 @@ Optional<TextNode::Chunk> TextNode::ChunkIterator::next_without_peek()
auto const& expected_font_for = [&](u32 cp) -> Gfx::Font const& {
return is_interword_space(cp)
? font_for_space(m_current_index, cp)
: m_font_cascade_list.font_for_code_point(cp);
: m_font_cascade_list.font_for_code_point(cp, Gfx::FontCascadeList::TriggerPendingLoads::Yes);
};
auto const& font = expected_font_for(current_code_point());

View File

@@ -1 +1 @@
ProbeFont status: error
ProbeFont status: unloaded