mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibWeb+LibGfx: Defer @font-face fetches until a codepoint renders
This commit is contained in:
committed by
Andreas Kling
parent
0de26af387
commit
acabf765c1
Notes:
github-actions[bot]
2026-04-25 15:07:59 +00:00
Author: https://github.com/jdahlin Commit: https://github.com/LadybirdBrowser/ladybird/commit/acabf765c1f Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/9032 Reviewed-by: https://github.com/Psychpsyo
@@ -34,13 +34,45 @@ void FontCascadeList::add(NonnullRefPtr<Font const> font, Vector<UnicodeRange> u
|
|||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FontCascadeList::add_pending_face(Vector<UnicodeRange> unicode_ranges, Function<void()> start_load)
|
||||||
|
{
|
||||||
|
if (unicode_ranges.is_empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
u32 lowest_code_point = 0xFFFFFFFF;
|
||||||
|
u32 highest_code_point = 0;
|
||||||
|
for (auto const& range : unicode_ranges) {
|
||||||
|
lowest_code_point = min(lowest_code_point, range.min_code_point());
|
||||||
|
highest_code_point = max(highest_code_point, range.max_code_point());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pending_faces.append(adopt_ref(*new PendingFace(
|
||||||
|
UnicodeRange { lowest_code_point, highest_code_point },
|
||||||
|
move(unicode_ranges),
|
||||||
|
move(start_load))));
|
||||||
|
}
|
||||||
|
|
||||||
void FontCascadeList::extend(FontCascadeList const& other)
|
void FontCascadeList::extend(FontCascadeList const& other)
|
||||||
{
|
{
|
||||||
m_fonts.extend(other.m_fonts);
|
m_fonts.extend(other.m_fonts);
|
||||||
|
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) 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;
|
||||||
|
});
|
||||||
|
|
||||||
if (code_point < m_ascii_cache.size()) {
|
if (code_point < m_ascii_cache.size()) {
|
||||||
if (auto const* cached = m_ascii_cache[code_point])
|
if (auto const* cached = m_ascii_cache[code_point])
|
||||||
return *cached;
|
return *cached;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <AK/Array.h>
|
#include <AK/Array.h>
|
||||||
#include <AK/Function.h>
|
#include <AK/Function.h>
|
||||||
|
#include <AK/RefCounted.h>
|
||||||
#include <LibGfx/Font/Font.h>
|
#include <LibGfx/Font/Font.h>
|
||||||
#include <LibGfx/Font/UnicodeRange.h>
|
#include <LibGfx/Font/UnicodeRange.h>
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t size() const { return m_fonts.size(); }
|
size_t size() const { return m_fonts.size(); }
|
||||||
bool is_empty() const { return m_fonts.is_empty() && !m_last_resort_font; }
|
bool is_empty() const { return m_fonts.is_empty() && m_pending_faces.is_empty() && !m_last_resort_font; }
|
||||||
Font const& first() const { return !m_fonts.is_empty() ? *m_fonts.first().font : *m_last_resort_font; }
|
Font const& first() const { return !m_fonts.is_empty() ? *m_fonts.first().font : *m_last_resort_font; }
|
||||||
|
|
||||||
template<typename Callback>
|
template<typename Callback>
|
||||||
@@ -36,6 +37,10 @@ public:
|
|||||||
void add(NonnullRefPtr<Font const> font);
|
void add(NonnullRefPtr<Font const> font);
|
||||||
void add(NonnullRefPtr<Font const> font, Vector<UnicodeRange> unicode_ranges);
|
void add(NonnullRefPtr<Font const> font, Vector<UnicodeRange> unicode_ranges);
|
||||||
|
|
||||||
|
// Register an unloaded face covering `unicode_ranges`. The cascade invokes
|
||||||
|
// `start_load` the first time a rendered codepoint falls within one of the ranges.
|
||||||
|
void add_pending_face(Vector<UnicodeRange> unicode_ranges, Function<void()> start_load);
|
||||||
|
|
||||||
void extend(FontCascadeList const& other);
|
void extend(FontCascadeList const& other);
|
||||||
|
|
||||||
Gfx::Font const& font_for_code_point(u32 code_point) const;
|
Gfx::Font const& font_for_code_point(u32 code_point) const;
|
||||||
@@ -53,6 +58,34 @@ public:
|
|||||||
Optional<RangeData> range_data;
|
Optional<RangeData> range_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PendingFace : public RefCounted<PendingFace> {
|
||||||
|
public:
|
||||||
|
PendingFace(UnicodeRange enclosing, Vector<UnicodeRange> ranges, Function<void()> start_load)
|
||||||
|
: m_enclosing_range(enclosing)
|
||||||
|
, m_unicode_ranges(move(ranges))
|
||||||
|
, m_start_load(move(start_load))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool covers(u32 code_point) const
|
||||||
|
{
|
||||||
|
if (!m_enclosing_range.contains(code_point))
|
||||||
|
return false;
|
||||||
|
for (auto const& range : m_unicode_ranges) {
|
||||||
|
if (range.contains(code_point))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_load() { m_start_load(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
UnicodeRange m_enclosing_range;
|
||||||
|
Vector<UnicodeRange> m_unicode_ranges;
|
||||||
|
Function<void()> m_start_load;
|
||||||
|
};
|
||||||
|
|
||||||
void set_last_resort_font(NonnullRefPtr<Font> font) { m_last_resort_font = move(font); }
|
void set_last_resort_font(NonnullRefPtr<Font> font) { m_last_resort_font = move(font); }
|
||||||
void set_system_font_fallback_callback(SystemFontFallbackCallback callback) { m_system_font_fallback_callback = move(callback); }
|
void set_system_font_fallback_callback(SystemFontFallbackCallback callback) { m_system_font_fallback_callback = move(callback); }
|
||||||
|
|
||||||
@@ -67,6 +100,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
RefPtr<Font const> m_last_resort_font;
|
RefPtr<Font const> m_last_resort_font;
|
||||||
mutable Vector<Entry> m_fonts;
|
mutable Vector<Entry> m_fonts;
|
||||||
|
mutable Vector<NonnullRefPtr<PendingFace>> m_pending_faces;
|
||||||
SystemFontFallbackCallback m_system_font_fallback_callback;
|
SystemFontFallbackCallback m_system_font_fallback_callback;
|
||||||
|
|
||||||
// OPTIMIZATION: Cache of resolved fonts for ASCII code points. Since m_fonts only grows and the cascade returns
|
// OPTIMIZATION: Cache of resolved fonts for ASCII code points. Since m_fonts only grows and the cascade returns
|
||||||
|
|||||||
@@ -268,8 +268,18 @@ struct FontComputer::MatchingFontCandidate {
|
|||||||
|
|
||||||
auto font_list = Gfx::FontCascadeList::create();
|
auto font_list = Gfx::FontCascadeList::create();
|
||||||
for (auto const& face : it->value) {
|
for (auto const& face : it->value) {
|
||||||
if (auto face_fonts = face->font_with_point_size(point_size, variations, shape_features))
|
if (auto face_fonts = face->font_with_point_size(point_size, variations, shape_features)) {
|
||||||
font_list->extend(*face_fonts);
|
font_list->extend(*face_fonts);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Unloaded subset face: surface it as a pending entry so the fetch only
|
||||||
|
// fires once font_for_code_point() sees a codepoint in its unicode-range.
|
||||||
|
if (face->has_urls() && face->has_non_default_unicode_range()) {
|
||||||
|
GC::Root<FontFace> rooted_face(*face);
|
||||||
|
font_list->add_pending_face(face->unicode_ranges(), [rooted_face = move(rooted_face)] {
|
||||||
|
const_cast<FontFace&>(*rooted_face).load();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (font_list->is_empty())
|
if (font_list->is_empty())
|
||||||
return {};
|
return {};
|
||||||
@@ -771,12 +781,18 @@ void FontComputer::load_fonts_from_sheet(CSSStyleSheet& sheet)
|
|||||||
auto font_face = FontFace::create_css_connected(document().realm(), *font_face_rule);
|
auto font_face = FontFace::create_css_connected(document().realm(), *font_face_rule);
|
||||||
document().fonts()->add_css_connected_font(font_face);
|
document().fonts()->add_css_connected_font(font_face);
|
||||||
|
|
||||||
// NB: Load via FontFace::load(), to satisfy this requirement:
|
if (font_face->has_non_default_unicode_range()) {
|
||||||
// https://drafts.csswg.org/css-font-loading/#font-face-load
|
// Register for matching, but defer loading until a rendered codepoint
|
||||||
// User agents can initiate font loads on their own, whenever they determine that a given font face is
|
// actually falls in this face's unicode-range.
|
||||||
// necessary to render something on the page. When this happens, they must act as if they had called the
|
register_font_face(font_face);
|
||||||
// corresponding FontFace’s load() method described here.
|
} else {
|
||||||
font_face->load();
|
// NB: Load via FontFace::load(), to satisfy this requirement:
|
||||||
|
// https://drafts.csswg.org/css-font-loading/#font-face-load
|
||||||
|
// User agents can initiate font loads on their own, whenever they determine that a given font face is
|
||||||
|
// necessary to render something on the page. When this happens, they must act as if they had called the
|
||||||
|
// corresponding FontFace’s load() method described here.
|
||||||
|
font_face->load();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto* font_feature_values_rule = as_if<CSSFontFeatureValuesRule>(*rule))
|
if (auto* font_feature_values_rule = as_if<CSSFontFeatureValuesRule>(*rule))
|
||||||
|
|||||||
@@ -100,6 +100,17 @@ public:
|
|||||||
|
|
||||||
RefPtr<Gfx::FontCascadeList const> font_with_point_size(float point_size, Gfx::FontVariationSettings const&, Gfx::ShapeFeatures const&) const;
|
RefPtr<Gfx::FontCascadeList const> font_with_point_size(float point_size, Gfx::FontVariationSettings const&, Gfx::ShapeFeatures const&) const;
|
||||||
|
|
||||||
|
Vector<Gfx::UnicodeRange> const& unicode_ranges() const { return m_unicode_ranges; }
|
||||||
|
bool has_urls() const { return !m_urls.is_empty(); }
|
||||||
|
|
||||||
|
bool has_non_default_unicode_range() const
|
||||||
|
{
|
||||||
|
if (m_unicode_ranges.size() != 1)
|
||||||
|
return true;
|
||||||
|
auto const& range = m_unicode_ranges.first();
|
||||||
|
return range.min_code_point() != 0 || range.max_code_point() != 0x10FFFF;
|
||||||
|
}
|
||||||
|
|
||||||
Bindings::FontFaceLoadStatus status() const { return m_status; }
|
Bindings::FontFaceLoadStatus status() const { return m_status; }
|
||||||
|
|
||||||
GC::Ref<WebIDL::Promise> load();
|
GC::Ref<WebIDL::Promise> load();
|
||||||
|
|||||||
@@ -379,6 +379,11 @@ void FontFaceSet::set_is_pending_on_the_environment(bool is_pending_on_the_envir
|
|||||||
// Spec issue: https://github.com/w3c/csswg-drafts/issues/13538#issuecomment-3933951987
|
// Spec issue: https://github.com/w3c/csswg-drafts/issues/13538#issuecomment-3933951987
|
||||||
if (m_set_entries->set_size() == 0 || (m_is_stuck_on_the_environment && m_loading_fonts.is_empty()))
|
if (m_set_entries->set_size() == 0 || (m_is_stuck_on_the_environment && m_loading_fonts.is_empty()))
|
||||||
switch_to_loaded();
|
switch_to_loaded();
|
||||||
|
// AD-HOC: Also switch when nothing has ever entered the LoadingFonts list — an empty set, or a set whose
|
||||||
|
// entries all have deferred unicode-ranges that no rendered codepoint matched. Without this the
|
||||||
|
// ready promise stays pending forever.
|
||||||
|
else if (m_loading_fonts.is_empty())
|
||||||
|
switch_to_loaded();
|
||||||
|
|
||||||
// 2. If the FontFaceSet is stuck on the environment, unmark it as such.
|
// 2. If the FontFaceSet is stuck on the environment, unmark it as such.
|
||||||
m_is_stuck_on_the_environment = false;
|
m_is_stuck_on_the_environment = false;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
MultiFont face count: 3
|
MultiFont face count: 3
|
||||||
range=U+41 status=loaded
|
range=U+41 status=loaded
|
||||||
range=U+42 status=error
|
range=U+42 status=unloaded
|
||||||
range=U+43 status=error
|
range=U+43 status=unloaded
|
||||||
|
|||||||
Reference in New Issue
Block a user