2025-11-06 18:27:22 +13:00
/*
* Copyright ( c ) 2018 - 2025 , Andreas Kling < andreas @ ladybird . org >
* Copyright ( c ) 2021 , the SerenityOS developers .
* Copyright ( c ) 2021 - 2025 , Sam Atkins < sam @ ladybird . org >
* Copyright ( c ) 2024 , Matthew Olsson < mattco @ serenityos . org >
* Copyright ( c ) 2025 , Callum Law < callumlaw1709 @ outlook . com >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include "FontComputer.h"
# include <AK/NonnullRawPtr.h>
# include <LibGfx/Font/FontDatabase.h>
# include <LibGfx/Font/WOFF/Loader.h>
# include <LibGfx/Font/WOFF2/Loader.h>
# include <LibWeb/CSS/CSSFontFaceRule.h>
# include <LibWeb/CSS/ComputedProperties.h>
# include <LibWeb/CSS/Fetch.h>
# include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
# include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
# include <LibWeb/CSS/StyleValues/StringStyleValue.h>
# include <LibWeb/CSS/StyleValues/StyleValueList.h>
# include <LibWeb/DOM/Document.h>
# include <LibWeb/Fetch/Infrastructure/HTTP/MIME.h>
# include <LibWeb/Fetch/Response.h>
# include <LibWeb/MimeSniff/Resource.h>
# include <LibWeb/Platform/FontPlugin.h>
namespace Web : : CSS {
GC_DEFINE_ALLOCATOR ( FontComputer ) ;
GC_DEFINE_ALLOCATOR ( FontLoader ) ;
struct FontFaceKey {
NonnullRawPtr < FlyString const > family_name ;
int weight { 0 } ;
int slope { 0 } ;
} ;
}
namespace AK {
namespace Detail {
template < >
inline constexpr bool IsHashCompatible < Web : : CSS : : FontFaceKey , Web : : CSS : : OwnFontFaceKey > = true ;
template < >
inline constexpr bool IsHashCompatible < Web : : CSS : : OwnFontFaceKey , Web : : CSS : : FontFaceKey > = true ;
}
template < >
struct Traits < Web : : CSS : : FontFaceKey > : public DefaultTraits < Web : : CSS : : FontFaceKey > {
static unsigned hash ( Web : : CSS : : FontFaceKey const & key ) { return pair_int_hash ( key . family_name - > hash ( ) , pair_int_hash ( key . weight , key . slope ) ) ; }
} ;
template < >
struct Traits < Web : : CSS : : OwnFontFaceKey > : public DefaultTraits < Web : : CSS : : OwnFontFaceKey > {
static unsigned hash ( Web : : CSS : : OwnFontFaceKey const & key ) { return pair_int_hash ( key . family_name . hash ( ) , pair_int_hash ( key . weight , key . slope ) ) ; }
} ;
template < >
struct Traits < Web : : CSS : : FontMatchingAlgorithmCacheKey > : public DefaultTraits < Web : : CSS : : FontMatchingAlgorithmCacheKey > {
static unsigned hash ( Web : : CSS : : FontMatchingAlgorithmCacheKey const & key )
{
auto hash = key . family_name . hash ( ) ;
hash = pair_int_hash ( hash , key . weight ) ;
hash = pair_int_hash ( hash , key . slope ) ;
hash = pair_int_hash ( hash , Traits < float > : : hash ( key . font_size_in_pt ) ) ;
return hash ;
}
} ;
}
namespace Web : : CSS {
OwnFontFaceKey : : OwnFontFaceKey ( FontFaceKey const & other )
: family_name ( other . family_name )
, weight ( other . weight )
, slope ( other . slope )
{
}
OwnFontFaceKey : : operator FontFaceKey ( ) const
{
return FontFaceKey {
family_name ,
weight ,
slope
} ;
}
[[nodiscard]] bool OwnFontFaceKey : : operator = = ( FontFaceKey const & other ) const
{
return family_name = = other . family_name
& & weight = = other . weight
& & slope = = other . slope ;
}
FontLoader : : FontLoader ( FontComputer & font_computer , GC : : Ptr < CSSStyleSheet > parent_style_sheet , FlyString family_name , Vector < Gfx : : UnicodeRange > unicode_ranges , Vector < URL > urls , Function < void ( RefPtr < Gfx : : Typeface const > ) > on_load )
: m_font_computer ( font_computer )
, m_parent_style_sheet ( parent_style_sheet )
, m_family_name ( move ( family_name ) )
, m_unicode_ranges ( move ( unicode_ranges ) )
, m_urls ( move ( urls ) )
, m_on_load ( move ( on_load ) )
{
}
FontLoader : : ~ FontLoader ( ) = default ;
void FontLoader : : visit_edges ( Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_font_computer ) ;
visitor . visit ( m_parent_style_sheet ) ;
visitor . visit ( m_fetch_controller ) ;
}
bool FontLoader : : is_loading ( ) const
{
return m_fetch_controller & & ! m_vector_font ;
}
RefPtr < Gfx : : Font const > FontLoader : : font_with_point_size ( float point_size , Gfx : : FontVariationSettings const & variations )
{
if ( ! m_vector_font ) {
if ( ! m_fetch_controller )
start_loading_next_url ( ) ;
return nullptr ;
}
return m_vector_font - > font ( point_size , variations ) ;
}
void FontLoader : : start_loading_next_url ( )
{
// FIXME: Load local() fonts somehow.
if ( m_fetch_controller & & m_fetch_controller - > state ( ) = = Fetch : : Infrastructure : : FetchController : : State : : Ongoing )
return ;
if ( m_urls . is_empty ( ) )
return ;
// https://drafts.csswg.org/css-fonts-4/#fetch-a-font
// To fetch a font given a selected <url> url for @font-face rule, fetch url, with stylesheet being rule’ s parent
// CSS style sheet, destination "font", CORS mode "cors", and processResponse being the following steps given
// response res and null, failure or a byte stream stream:
auto style_sheet_or_document = m_parent_style_sheet ? StyleSheetOrDocument { * m_parent_style_sheet } : StyleSheetOrDocument { m_font_computer - > document ( ) } ;
m_fetch_controller = fetch_a_style_resource ( m_urls . take_first ( ) , style_sheet_or_document , Fetch : : Infrastructure : : Request : : Destination : : Font , CorsMode : : Cors ,
[ loader = this ] ( auto response , auto stream ) {
// 1. If stream is null, return.
// 2. Load a font from stream according to its type.
// NB: We need to fetch the next source if this one fails to fetch OR decode. So, first try to decode it.
RefPtr < Gfx : : Typeface const > typeface ;
if ( auto * bytes = stream . template get_pointer < ByteBuffer > ( ) ) {
if ( auto maybe_typeface = loader - > try_load_font ( response , * bytes ) ; ! maybe_typeface . is_error ( ) )
typeface = maybe_typeface . release_value ( ) ;
}
if ( ! typeface ) {
// NB: If we have other sources available, try the next one.
if ( loader - > m_urls . is_empty ( ) ) {
loader - > font_did_load_or_fail ( nullptr ) ;
} else {
loader - > m_fetch_controller = nullptr ;
loader - > start_loading_next_url ( ) ;
}
} else {
loader - > font_did_load_or_fail ( move ( typeface ) ) ;
}
} ) ;
if ( ! m_fetch_controller )
font_did_load_or_fail ( nullptr ) ;
}
void FontLoader : : font_did_load_or_fail ( RefPtr < Gfx : : Typeface const > typeface )
{
if ( typeface ) {
m_vector_font = typeface . release_nonnull ( ) ;
m_font_computer - > did_load_font ( m_family_name ) ;
if ( m_on_load )
m_on_load ( m_vector_font ) ;
} else {
if ( m_on_load )
m_on_load ( nullptr ) ;
}
m_fetch_controller = nullptr ;
}
ErrorOr < NonnullRefPtr < Gfx : : Typeface const > > FontLoader : : try_load_font ( Fetch : : Infrastructure : : Response const & response , ByteBuffer const & bytes )
{
// FIXME: This could maybe use the format() provided in @font-face as well, since often the mime type is just application/octet-stream and we have to try every format
auto mime_type = Fetch : : Infrastructure : : extract_mime_type ( response . header_list ( ) ) ;
if ( ! mime_type . has_value ( ) | | ! mime_type - > is_font ( ) ) {
mime_type = MimeSniff : : Resource : : sniff ( bytes , MimeSniff : : SniffingConfiguration { . sniffing_context = MimeSniff : : SniffingContext : : Font } ) ;
}
if ( mime_type . has_value ( ) ) {
if ( mime_type - > essence ( ) = = " font/ttf " sv | | mime_type - > essence ( ) = = " application/x-font-ttf " sv | | mime_type - > essence ( ) = = " font/otf " sv ) {
if ( auto result = Gfx : : Typeface : : try_load_from_temporary_memory ( bytes ) ; ! result . is_error ( ) ) {
return result ;
}
}
if ( mime_type - > essence ( ) = = " font/woff " sv | | mime_type - > essence ( ) = = " application/font-woff " sv ) {
if ( auto result = WOFF : : try_load_from_bytes ( bytes ) ; ! result . is_error ( ) ) {
return result ;
}
}
if ( mime_type - > essence ( ) = = " font/woff2 " sv | | mime_type - > essence ( ) = = " application/font-woff2 " sv ) {
if ( auto result = WOFF2 : : try_load_from_bytes ( bytes ) ; ! result . is_error ( ) ) {
return result ;
}
}
}
return Error : : from_string_literal ( " Automatic format detection failed " ) ;
}
struct FontComputer : : MatchingFontCandidate {
FontFaceKey key ;
Variant < FontLoaderList * , Gfx : : Typeface const * > loader_or_typeface ;
[[nodiscard]] RefPtr < Gfx : : FontCascadeList const > font_with_point_size ( float point_size , Gfx : : FontVariationSettings const & variations ) const
{
auto font_list = Gfx : : FontCascadeList : : create ( ) ;
if ( auto const * loader_list = loader_or_typeface . get_pointer < FontLoaderList * > ( ) ; loader_list ) {
for ( auto const & loader : * * loader_list ) {
if ( auto font = loader - > font_with_point_size ( point_size , variations ) ; font )
font_list - > add ( * font , loader - > unicode_ranges ( ) ) ;
}
return font_list ;
}
font_list - > add ( loader_or_typeface . get < Gfx : : Typeface const * > ( ) - > font ( point_size , variations ) ) ;
return font_list ;
}
} ;
void FontComputer : : visit_edges ( Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_document ) ;
visitor . visit ( m_loaded_fonts ) ;
}
RefPtr < Gfx : : FontCascadeList const > FontComputer : : find_matching_font_weight_ascending ( Vector < MatchingFontCandidate > const & candidates , int target_weight , float font_size_in_pt , Gfx : : FontVariationSettings const & variations , bool inclusive )
{
using Fn = AK : : Function < bool ( MatchingFontCandidate const & ) > ;
auto pred = inclusive ? Fn ( [ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight > = target_weight ; } )
: Fn ( [ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight > target_weight ; } ) ;
auto it = find_if ( candidates . begin ( ) , candidates . end ( ) , pred ) ;
for ( ; it ! = candidates . end ( ) ; + + it ) {
if ( auto found_font = it - > font_with_point_size ( font_size_in_pt , variations ) )
return found_font ;
}
return { } ;
}
RefPtr < Gfx : : FontCascadeList const > FontComputer : : find_matching_font_weight_descending ( Vector < MatchingFontCandidate > const & candidates , int target_weight , float font_size_in_pt , Gfx : : FontVariationSettings const & variations , bool inclusive )
{
using Fn = AK : : Function < bool ( MatchingFontCandidate const & ) > ;
auto pred = inclusive ? Fn ( [ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight < = target_weight ; } )
: Fn ( [ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight < target_weight ; } ) ;
auto it = find_if ( candidates . rbegin ( ) , candidates . rend ( ) , pred ) ;
for ( ; it ! = candidates . rend ( ) ; + + it ) {
if ( auto found_font = it - > font_with_point_size ( font_size_in_pt , variations ) )
return found_font ;
}
return { } ;
}
RefPtr < Gfx : : FontCascadeList const > FontComputer : : font_matching_algorithm ( FlyString const & family_name , int weight , int slope , float font_size_in_pt ) const
{
FontMatchingAlgorithmCacheKey key { family_name , weight , slope , font_size_in_pt } ;
return m_font_matching_algorithm_cache . ensure ( key , [ & ] {
return font_matching_algorithm_impl ( family_name , weight , slope , font_size_in_pt ) ;
} ) ;
}
// Partial implementation of the font-matching algorithm: https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm
// FIXME: This should be replaced by the full CSS font selection algorithm.
RefPtr < Gfx : : FontCascadeList const > FontComputer : : font_matching_algorithm_impl ( FlyString const & family_name , int weight , int slope , float font_size_in_pt ) const
{
// If a font family match occurs, the user agent assembles the set of font faces in that family and then
// narrows the set to a single face using other font properties in the order given below.
Vector < MatchingFontCandidate > matching_family_fonts ;
for ( auto const & font_key_and_loader : m_loaded_fonts ) {
if ( font_key_and_loader . key . family_name . equals_ignoring_ascii_case ( family_name ) )
matching_family_fonts . empend ( font_key_and_loader . key , const_cast < FontLoaderList * > ( & font_key_and_loader . value ) ) ;
}
Gfx : : FontDatabase : : the ( ) . for_each_typeface_with_family_name ( family_name , [ & ] ( Gfx : : Typeface const & typeface ) {
matching_family_fonts . empend (
FontFaceKey {
. family_name = typeface . family ( ) ,
. weight = static_cast < int > ( typeface . weight ( ) ) ,
. slope = typeface . slope ( ) ,
} ,
& typeface ) ;
} ) ;
quick_sort ( matching_family_fonts , [ ] ( auto const & a , auto const & b ) {
return a . key . weight < b . key . weight ;
} ) ;
// FIXME: 1. font-stretch is tried first.
// FIXME: 2. font-style is tried next.
// We don't have complete support of italic and oblique fonts, so matching on font-style can be simplified to:
// If a matching slope is found, all faces which don't have that matching slope are excluded from the matching set.
auto style_it = find_if ( matching_family_fonts . begin ( ) , matching_family_fonts . end ( ) ,
[ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . slope = = slope ; } ) ;
if ( style_it ! = matching_family_fonts . end ( ) ) {
matching_family_fonts . remove_all_matching ( [ & ] ( auto const & matching_font_candidate ) {
return matching_font_candidate . key . slope ! = slope ;
} ) ;
}
// 3. font-weight is matched next.
// If the desired weight is inclusively between 400 and 500, weights greater than or equal to the target weight
// are checked in ascending order until 500 is hit and checked, followed by weights less than the target weight
// in descending order, followed by weights greater than 500, until a match is found.
Gfx : : FontVariationSettings variations ;
variations . set_weight ( weight ) ;
if ( weight > = 400 & & weight < = 500 ) {
auto it = find_if ( matching_family_fonts . begin ( ) , matching_family_fonts . end ( ) ,
[ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight > = weight ; } ) ;
for ( ; it ! = matching_family_fonts . end ( ) & & it - > key . weight < = 500 ; + + it ) {
if ( auto found_font = it - > font_with_point_size ( font_size_in_pt , variations ) )
return found_font ;
}
if ( auto found_font = find_matching_font_weight_descending ( matching_family_fonts , weight , font_size_in_pt , variations , false ) )
return found_font ;
for ( ; it ! = matching_family_fonts . end ( ) ; + + it ) {
if ( auto found_font = it - > font_with_point_size ( font_size_in_pt , variations ) )
return found_font ;
}
}
// If the desired weight is less than 400, weights less than or equal to the desired weight are checked in descending order
// followed by weights above the desired weight in ascending order until a match is found.
if ( weight < 400 ) {
if ( auto found_font = find_matching_font_weight_descending ( matching_family_fonts , weight , font_size_in_pt , variations , true ) )
return found_font ;
if ( auto found_font = find_matching_font_weight_ascending ( matching_family_fonts , weight , font_size_in_pt , variations , false ) )
return found_font ;
}
// If the desired weight is greater than 500, weights greater than or equal to the desired weight are checked in ascending order
// followed by weights below the desired weight in descending order until a match is found.
if ( weight > 500 ) {
if ( auto found_font = find_matching_font_weight_ascending ( matching_family_fonts , weight , font_size_in_pt , variations , true ) )
return found_font ;
if ( auto found_font = find_matching_font_weight_descending ( matching_family_fonts , weight , font_size_in_pt , variations , false ) )
return found_font ;
}
return { } ;
}
2025-11-09 00:39:08 +13:00
NonnullRefPtr < Gfx : : FontCascadeList const > FontComputer : : compute_font_for_style_values ( StyleValue const & font_family , CSSPixels const & font_size , int slope , double font_weight , Percentage const & font_width , HashMap < FlyString , double > const & font_variation_settings ) const
2025-11-06 18:27:22 +13:00
{
// FIXME: We round to int here as that is what is expected by our font infrastructure below
auto width = round_to < int > ( font_width . value ( ) ) ;
// FIXME: We round to int here as that is what is expected by our font infrastructure below
auto weight = round_to < int > ( font_weight ) ;
// FIXME: Implement the full font-matching algorithm: https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm
float const font_size_in_pt = font_size * 0.75f ;
auto find_font = [ & ] ( FlyString const & family ) - > RefPtr < Gfx : : FontCascadeList const > {
FontFaceKey key {
. family_name = family ,
. weight = weight ,
. slope = slope ,
} ;
auto result = Gfx : : FontCascadeList : : create ( ) ;
if ( auto it = m_loaded_fonts . find ( key ) ; it ! = m_loaded_fonts . end ( ) ) {
auto const & loaders = it - > value ;
Gfx : : FontVariationSettings variation ;
variation . set_weight ( font_weight ) ;
for ( auto const & [ tag_string , value ] : font_variation_settings ) {
auto string_view = tag_string . bytes_as_string_view ( ) ;
if ( string_view . length ( ) ! = 4 )
continue ;
auto tag = Gfx : : FourCC ( string_view . characters_without_null_termination ( ) ) ;
variation . axes . set ( tag , value ) ;
}
for ( auto const & loader : loaders ) {
if ( auto found_font = loader - > font_with_point_size ( font_size_in_pt , variation ) )
result - > add ( * found_font , loader - > unicode_ranges ( ) ) ;
}
return result ;
}
if ( auto found_font = font_matching_algorithm ( family , weight , slope , font_size_in_pt ) ; found_font & & ! found_font - > is_empty ( ) ) {
return found_font ;
}
if ( auto found_font = Gfx : : FontDatabase : : the ( ) . get ( family , font_size_in_pt , weight , width , slope ) ) {
result - > add ( * found_font ) ;
return result ;
}
return { } ;
} ;
auto find_generic_font = [ & ] ( Keyword font_id ) - > RefPtr < Gfx : : FontCascadeList const > {
Platform : : GenericFont generic_font { } ;
switch ( font_id ) {
case Keyword : : Monospace :
case Keyword : : UiMonospace :
generic_font = Platform : : GenericFont : : Monospace ;
break ;
case Keyword : : Serif :
generic_font = Platform : : GenericFont : : Serif ;
break ;
case Keyword : : Fantasy :
generic_font = Platform : : GenericFont : : Fantasy ;
break ;
case Keyword : : SansSerif :
generic_font = Platform : : GenericFont : : SansSerif ;
break ;
case Keyword : : Cursive :
generic_font = Platform : : GenericFont : : Cursive ;
break ;
case Keyword : : UiSerif :
generic_font = Platform : : GenericFont : : UiSerif ;
break ;
case Keyword : : UiSansSerif :
generic_font = Platform : : GenericFont : : UiSansSerif ;
break ;
case Keyword : : UiRounded :
generic_font = Platform : : GenericFont : : UiRounded ;
break ;
default :
return { } ;
}
return find_font ( Platform : : FontPlugin : : the ( ) . generic_font_name ( generic_font ) ) ;
} ;
auto font_list = Gfx : : FontCascadeList : : create ( ) ;
for ( auto const & family : font_family . as_value_list ( ) . values ( ) ) {
RefPtr < Gfx : : FontCascadeList const > other_font_list ;
if ( family - > is_keyword ( ) ) {
other_font_list = find_generic_font ( family - > to_keyword ( ) ) ;
} else if ( family - > is_string ( ) ) {
other_font_list = find_font ( family - > as_string ( ) . string_value ( ) ) ;
} else if ( family - > is_custom_ident ( ) ) {
other_font_list = find_font ( family - > as_custom_ident ( ) . custom_ident ( ) ) ;
}
if ( other_font_list )
font_list - > extend ( * other_font_list ) ;
}
auto default_font = Platform : : FontPlugin : : the ( ) . default_font ( font_size_in_pt ) ;
if ( font_list - > is_empty ( ) ) {
// This is needed to make sure we check default font before reaching to emojis.
font_list - > add ( * default_font ) ;
}
// Add emoji and symbol fonts
for ( auto font_name : Platform : : FontPlugin : : the ( ) . symbol_font_names ( ) ) {
if ( auto other_font_list = find_font ( font_name ) ) {
font_list - > extend ( * other_font_list ) ;
}
}
// The default font is already included in the font list, but we explicitly set it
// as the last-resort font. This ensures that if none of the specified fonts contain
// the requested code point, there is still a font available to provide a fallback glyph.
font_list - > set_last_resort_font ( * default_font ) ;
return font_list ;
}
Gfx : : Font const & FontComputer : : initial_font ( ) const
{
// FIXME: This is not correct.
static auto font = ComputedProperties : : font_fallback ( false , false , 12 ) ;
return font ;
}
void FontComputer : : did_load_font ( FlyString const & )
{
m_font_matching_algorithm_cache = { } ;
document ( ) . invalidate_style ( DOM : : StyleInvalidationReason : : CSSFontLoaded ) ;
}
GC : : Ptr < FontLoader > FontComputer : : load_font_face ( ParsedFontFace const & font_face , Function < void ( RefPtr < Gfx : : Typeface const > ) > on_load )
{
if ( font_face . sources ( ) . is_empty ( ) ) {
if ( on_load )
on_load ( { } ) ;
return { } ;
}
FontFaceKey key {
. family_name = font_face . font_family ( ) ,
. weight = font_face . weight ( ) . value_or ( 0 ) ,
. slope = font_face . slope ( ) . value_or ( 0 ) ,
} ;
// FIXME: Pass the sources directly, so the font loader can make use of the format information, or load local fonts.
Vector < URL > urls ;
for ( auto const & source : font_face . sources ( ) ) {
if ( source . local_or_url . has < URL > ( ) )
urls . append ( source . local_or_url . get < URL > ( ) ) ;
// FIXME: Handle local()
}
if ( urls . is_empty ( ) ) {
if ( on_load )
on_load ( { } ) ;
return { } ;
}
auto loader = heap ( ) . allocate < FontLoader > ( * this , font_face . parent_style_sheet ( ) , font_face . font_family ( ) , font_face . unicode_ranges ( ) , move ( urls ) , move ( on_load ) ) ;
auto & loader_ref = * loader ;
auto maybe_font_loaders_list = m_loaded_fonts . get ( key ) ;
if ( maybe_font_loaders_list . has_value ( ) ) {
maybe_font_loaders_list - > append ( move ( loader ) ) ;
} else {
FontLoaderList loaders ;
loaders . append ( loader ) ;
m_loaded_fonts . set ( OwnFontFaceKey ( key ) , move ( loaders ) ) ;
}
// Actual object owned by font loader list inside m_loaded_fonts, this isn't use-after-move/free
return loader_ref ;
}
void FontComputer : : load_fonts_from_sheet ( CSSStyleSheet & sheet )
{
for ( auto const & rule : sheet . rules ( ) ) {
if ( ! is < CSSFontFaceRule > ( * rule ) )
continue ;
auto const & font_face_rule = static_cast < CSSFontFaceRule const & > ( * rule ) ;
if ( ! font_face_rule . is_valid ( ) )
continue ;
if ( auto font_loader = load_font_face ( font_face_rule . font_face ( ) ) ) {
sheet . add_associated_font_loader ( * font_loader ) ;
}
}
}
void FontComputer : : unload_fonts_from_sheet ( CSSStyleSheet & sheet )
{
for ( auto & [ _ , font_loader_list ] : m_loaded_fonts ) {
font_loader_list . remove_all_matching ( [ & ] ( auto & font_loader ) {
return sheet . has_associated_font_loader ( * font_loader ) ;
} ) ;
}
}
size_t FontComputer : : number_of_css_font_faces_with_loading_in_progress ( ) const
{
size_t count = 0 ;
for ( auto const & [ _ , loaders ] : m_loaded_fonts ) {
for ( auto const & loader : loaders ) {
if ( loader - > is_loading ( ) )
+ + count ;
}
}
return count ;
}
}