2022-03-28 20:30:26 +01:00
/*
2025-04-03 12:05:49 +01:00
* Copyright ( c ) 2022 - 2025 , Sam Atkins < sam @ ladybird . org >
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2022 - 2023 , Andreas Kling < andreas @ ladybird . org >
2022-03-28 20:30:26 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2024-10-02 14:18:08 +01:00
# include <LibGfx/Font/Font.h>
2023-05-24 19:58:52 +02:00
# include <LibGfx/Font/FontStyleMapping.h>
2022-09-24 16:34:04 -06:00
# include <LibWeb/Bindings/CSSFontFaceRulePrototype.h>
# include <LibWeb/Bindings/Intrinsics.h>
2022-03-28 20:30:26 +01:00
# include <LibWeb/CSS/CSSFontFaceRule.h>
2026-01-05 09:50:55 +00:00
# include <LibWeb/CSS/CSSStyleSheet.h>
# include <LibWeb/CSS/FontFace.h>
# include <LibWeb/CSS/FontFaceSet.h>
2022-09-11 12:20:16 -04:00
# include <LibWeb/CSS/Serialize.h>
2026-01-05 09:50:55 +00:00
# include <LibWeb/DOM/Document.h>
2025-12-04 12:03:01 +00:00
# include <LibWeb/Dump.h>
2023-02-12 22:44:04 +01:00
# include <LibWeb/WebIDL/ExceptionOr.h>
2022-03-28 20:30:26 +01:00
namespace Web : : CSS {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( CSSFontFaceRule ) ;
2023-12-23 15:15:27 +01:00
2025-04-03 12:05:49 +01:00
GC : : Ref < CSSFontFaceRule > CSSFontFaceRule : : create ( JS : : Realm & realm , GC : : Ref < CSSFontFaceDescriptors > style )
2022-03-28 20:30:26 +01:00
{
2025-04-03 12:05:49 +01:00
return realm . create < CSSFontFaceRule > ( realm , style ) ;
2022-08-07 15:46:44 +02:00
}
2025-04-03 12:05:49 +01:00
CSSFontFaceRule : : CSSFontFaceRule ( JS : : Realm & realm , GC : : Ref < CSSFontFaceDescriptors > style )
2024-10-28 20:16:28 +01:00
: CSSRule ( realm , Type : : FontFace )
2025-04-03 12:05:49 +01:00
, m_style ( style )
2022-08-07 15:46:44 +02:00
{
2025-05-02 11:13:02 +01:00
m_style - > set_parent_rule ( * this ) ;
2023-01-10 06:28:20 -05:00
}
2023-08-07 08:41:28 +02:00
void CSSFontFaceRule : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( CSSFontFaceRule ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2022-03-28 20:30:26 +01:00
}
2025-04-03 12:15:11 +01:00
bool CSSFontFaceRule : : is_valid ( ) const
{
// @font-face rules require a font-family and src descriptor; if either of these are missing, the @font-face rule
// must not be considered when performing the font matching algorithm.
// https://drafts.csswg.org/css-fonts-4/#font-face-rule
2026-03-05 00:03:57 +13:00
return ! m_style - > descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : FontFamily ) ) . is_null ( )
& & ! m_style - > descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : Src ) ) . is_null ( ) ;
2025-04-03 12:15:11 +01:00
}
2025-04-03 12:05:49 +01:00
ParsedFontFace CSSFontFaceRule : : font_face ( ) const
2022-03-28 20:30:26 +01:00
{
2025-04-03 12:05:49 +01:00
return ParsedFontFace : : from_descriptors ( m_style ) ;
2022-03-28 20:30:26 +01:00
}
// https://www.w3.org/TR/cssom/#ref-for-cssfontfacerule
2023-11-20 23:16:39 +13:00
String CSSFontFaceRule : : serialized ( ) const
2022-03-28 20:30:26 +01:00
{
2025-04-04 12:18:53 +01:00
auto & descriptors = * m_style ;
2022-09-11 12:20:16 -04:00
StringBuilder builder ;
// The result of concatenating the following:
// 1. The string "@font-face {", followed by a single SPACE (U+0020).
2026-01-10 17:36:48 +13:00
// AD-HOC: We add the in the below AD-HOC block to avoid an extra space if there is no font-family descriptor.
builder . append ( " @font-face { " sv ) ;
2022-09-11 12:20:16 -04:00
2026-01-10 17:36:48 +13:00
// AD-HOC: We don't necessary always have a font-family descriptor as the spec assumes,
// see https://github.com/w3c/csswg-drafts/issues/13323
2022-09-11 12:20:16 -04:00
2026-03-05 00:03:57 +13:00
if ( auto font_family = descriptors . descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : FontFamily ) ) ; ! font_family . is_null ( ) ) {
2026-01-10 17:36:48 +13:00
builder . append ( ' ' ) ;
2022-09-11 12:20:16 -04:00
2026-01-10 17:36:48 +13:00
// 2. The string "font-family:", followed by a single SPACE (U+0020).
builder . append ( " font-family: " sv ) ;
// 3. The result of performing serialize a string on the rule’ s font family name.
2026-03-05 00:03:57 +13:00
descriptors . descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : FontFamily ) ) - > serialize ( builder , SerializationMode : : Normal ) ;
2026-01-10 17:36:48 +13:00
// 4. The string ";", i.e., SEMICOLON (U+003B).
builder . append ( ' ; ' ) ;
}
2022-09-11 12:20:16 -04:00
// 5. If the rule’ s associated source list is not empty, follow these substeps:
2026-03-05 00:03:57 +13:00
if ( auto sources = descriptors . descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : Src ) ) ) {
2022-09-11 12:20:16 -04:00
// 1. A single SPACE (U+0020), followed by the string "src:", followed by a single SPACE (U+0020).
builder . append ( " src: " sv ) ;
// 2. The result of invoking serialize a comma-separated list on performing serialize a URL or serialize a LOCAL for each source on the source list.
2026-01-08 21:11:43 +00:00
sources - > serialize ( builder , SerializationMode : : Normal ) ;
2022-09-11 12:20:16 -04:00
// 3. The string ";", i.e., SEMICOLON (U+003B).
builder . append ( ' ; ' ) ;
}
// 6. If rule’ s associated unicode-range descriptor is present, a single SPACE (U+0020), followed by the string "unicode-range:", followed by a single SPACE (U+0020), followed by the result of performing serialize a <'unicode-range'>, followed by the string ";", i.e., SEMICOLON (U+003B).
2026-03-05 00:03:57 +13:00
if ( auto unicode_range = descriptors . descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : UnicodeRange ) ) ) {
2025-04-04 12:18:53 +01:00
builder . append ( " unicode-range: " sv ) ;
2026-01-08 21:11:43 +00:00
unicode_range - > serialize ( builder , SerializationMode : : Normal ) ;
2025-04-04 12:18:53 +01:00
builder . append ( ' ; ' ) ;
}
2022-09-11 12:20:16 -04:00
// FIXME: 7. If rule’ s associated font-variant descriptor is present, a single SPACE (U+0020),
// followed by the string "font-variant:", followed by a single SPACE (U+0020),
// followed by the result of performing serialize a <'font-variant'>,
// followed by the string ";", i.e., SEMICOLON (U+003B).
2024-10-02 14:18:08 +01:00
// 8. If rule’ s associated font-feature-settings descriptor is present, a single SPACE (U+0020),
// followed by the string "font-feature-settings:", followed by a single SPACE (U+0020),
// followed by the result of performing serialize a <'font-feature-settings'>,
// followed by the string ";", i.e., SEMICOLON (U+003B).
2026-03-05 00:03:57 +13:00
if ( auto font_feature_settings = descriptors . descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : FontFeatureSettings ) ) ) {
2024-10-02 14:18:08 +01:00
builder . append ( " font-feature-settings: " sv ) ;
2026-01-08 21:11:43 +00:00
font_feature_settings - > serialize ( builder , SerializationMode : : Normal ) ;
2024-10-02 14:18:08 +01:00
builder . append ( " ; " sv ) ;
}
// 9. If rule’ s associated font-stretch descriptor is present, a single SPACE (U+0020),
// followed by the string "font-stretch:", followed by a single SPACE (U+0020),
// followed by the result of performing serialize a <'font-stretch'>,
// followed by the string ";", i.e., SEMICOLON (U+003B).
// NOTE: font-stretch is now an alias for font-width, so we use that instead.
2026-03-05 00:03:57 +13:00
if ( auto font_width = descriptors . descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : FontWidth ) ) ) {
2025-04-04 12:18:53 +01:00
builder . append ( " font-stretch: " sv ) ;
2026-01-08 21:11:43 +00:00
font_width - > serialize ( builder , SerializationMode : : Normal ) ;
2024-10-02 14:18:08 +01:00
builder . append ( " ; " sv ) ;
}
2022-09-11 12:20:16 -04:00
2023-05-24 19:58:52 +02:00
// 10. If rule’ s associated font-weight descriptor is present, a single SPACE (U+0020),
// followed by the string "font-weight:", followed by a single SPACE (U+0020),
// followed by the result of performing serialize a <'font-weight'>,
// followed by the string ";", i.e., SEMICOLON (U+003B).
2026-03-05 00:03:57 +13:00
if ( auto font_weight = descriptors . descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : FontWeight ) ) ) {
2023-05-24 19:58:52 +02:00
builder . append ( " font-weight: " sv ) ;
2026-01-08 21:11:43 +00:00
font_weight - > serialize ( builder , SerializationMode : : Normal ) ;
2023-05-24 19:58:52 +02:00
builder . append ( " ; " sv ) ;
}
2022-09-11 12:20:16 -04:00
2023-05-24 19:58:52 +02:00
// 11. If rule’ s associated font-style descriptor is present, a single SPACE (U+0020),
// followed by the string "font-style:", followed by a single SPACE (U+0020),
// followed by the result of performing serialize a <'font-style'>,
// followed by the string ";", i.e., SEMICOLON (U+003B).
2026-03-05 00:03:57 +13:00
if ( auto font_style = descriptors . descriptor ( DescriptorNameAndID : : from_id ( DescriptorID : : FontStyle ) ) ) {
2023-05-24 19:58:52 +02:00
builder . append ( " font-style: " sv ) ;
2026-01-08 21:11:43 +00:00
font_style - > serialize ( builder , SerializationMode : : Normal ) ;
2023-05-24 19:58:52 +02:00
builder . append ( " ; " sv ) ;
}
2022-09-11 12:20:16 -04:00
// 12. A single SPACE (U+0020), followed by the string "}", i.e., RIGHT CURLY BRACKET (U+007D).
builder . append ( " } " sv ) ;
2023-11-20 23:16:39 +13:00
return MUST ( builder . to_string ( ) ) ;
2022-03-28 20:30:26 +01:00
}
2025-04-03 12:05:49 +01:00
void CSSFontFaceRule : : visit_edges ( Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_style ) ;
2026-01-05 09:50:55 +00:00
visitor . visit ( m_css_connected_font_face ) ;
}
2026-01-10 16:23:23 +13:00
void CSSFontFaceRule : : handle_descriptor_change ( FlyString const & property )
{
if ( ! m_css_connected_font_face )
return ;
2026-01-11 00:33:34 +13:00
if ( ! is_valid ( ) ) {
disconnect_font_face ( ) ;
return ;
}
if ( property . equals_ignoring_ascii_case ( " src " sv ) )
handle_src_descriptor_change ( ) ;
2026-01-10 16:23:23 +13:00
// https://drafts.csswg.org/css-font-loading/#font-face-css-connection
// any change made to a @font-face descriptor is immediately reflected in the corresponding FontFace attribute
m_css_connected_font_face - > reparse_connected_css_font_face_rule_descriptors ( ) ;
}
2026-01-05 09:50:55 +00:00
// https://drafts.csswg.org/css-font-loading/#font-face-css-connection
void CSSFontFaceRule : : handle_src_descriptor_change ( )
{
// If a @font-face rule has its src descriptor changed to a new value, the original connected FontFace object must
// stop being CSS-connected. A new FontFace reflecting its new src must be created and CSS-connected to the
// @font-face.
if ( ! m_css_connected_font_face )
return ;
2026-01-10 15:16:08 +13:00
disconnect_font_face ( ) ;
2026-01-05 09:50:55 +00:00
auto * style_sheet = parent_style_sheet ( ) ;
if ( ! style_sheet )
return ;
auto document = style_sheet - > owning_document ( ) ;
if ( ! document )
return ;
auto new_font_face = FontFace : : create_css_connected ( realm ( ) , * this ) ;
document - > fonts ( ) - > add_css_connected_font ( new_font_face ) ;
2025-04-03 12:05:49 +01:00
}
2026-01-05 10:08:52 +00:00
void CSSFontFaceRule : : disconnect_font_face ( )
{
if ( ! m_css_connected_font_face )
return ;
m_css_connected_font_face - > disconnect_from_css_rule ( ) ;
2026-01-10 15:16:08 +13:00
if ( auto * style_sheet = parent_style_sheet ( ) ) {
if ( auto document = style_sheet - > owning_document ( ) )
document - > fonts ( ) - > delete_ ( m_css_connected_font_face ) ;
}
2026-01-05 10:08:52 +00:00
m_css_connected_font_face = nullptr ;
}
2025-12-04 12:03:01 +00:00
void CSSFontFaceRule : : dump ( StringBuilder & builder , int indent_levels ) const
{
Base : : dump ( builder , indent_levels ) ;
2026-02-23 14:31:54 +00:00
dump_indent ( builder , indent_levels + 1 ) ;
builder . appendff ( " Loading state: {} \n " , CSSStyleSheet : : loading_state_name ( loading_state ( ) ) ) ;
2025-12-04 12:03:01 +00:00
dump_indent ( builder , indent_levels + 1 ) ;
builder . appendff ( " Valid: {} \n " , is_valid ( ) ) ;
dump_descriptors ( builder , descriptors ( ) , indent_levels + 1 ) ;
}
2026-02-23 14:31:54 +00:00
void CSSFontFaceRule : : set_parent_style_sheet ( CSSStyleSheet * parent_style_sheet )
{
if ( m_parent_style_sheet )
m_parent_style_sheet - > remove_critical_subresource ( * this ) ;
Base : : set_parent_style_sheet ( parent_style_sheet ) ;
if ( m_parent_style_sheet )
m_parent_style_sheet - > add_critical_subresource ( * this ) ;
}
2022-03-28 20:30:26 +01:00
}