2026-01-18 16:16:17 +13:00
/*
* Copyright ( c ) 2026 , Callum Law < callumlaw1709 @ outlook . com >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include "FontFeatureData.h"
# include <AK/HashMap.h>
2026-02-19 18:30:55 +13:00
# include <LibWeb/CSS/Enums.h>
2026-01-18 16:16:17 +13:00
namespace Web : : CSS {
2026-02-20 20:40:49 +13:00
Gfx : : ShapeFeatures FontFeatureData : : to_shape_features ( HashMap < FontFeatureValueKey , Vector < u32 > > const & font_feature_values ) const
2026-01-18 16:16:17 +13:00
{
2026-02-20 17:35:52 +13:00
HashMap < FlyString , u8 > merged_features ;
2026-01-18 16:16:17 +13:00
auto font_variant_features = [ & ] ( ) {
2026-02-20 17:35:52 +13:00
HashMap < FlyString , u8 > features ;
2026-01-18 16:16:17 +13:00
// 6.4 https://drafts.csswg.org/css-fonts/#font-variant-ligatures-prop
auto disable_all_ligatures = [ & ] ( ) {
2026-02-20 17:35:52 +13:00
features . set ( " liga " _fly_string , 0 ) ;
features . set ( " clig " _fly_string , 0 ) ;
features . set ( " dlig " _fly_string , 0 ) ;
features . set ( " hlig " _fly_string , 0 ) ;
features . set ( " calt " _fly_string , 0 ) ;
2026-01-18 16:16:17 +13:00
} ;
if ( font_variant_ligatures . has_value ( ) ) {
auto ligature = font_variant_ligatures . value ( ) ;
if ( ligature . none ) {
// Specifies that all types of ligatures and contextual forms covered by this property are explicitly disabled.
disable_all_ligatures ( ) ;
} else {
2026-02-19 10:51:45 +13:00
if ( ligature . common . has_value ( ) ) {
switch ( ligature . common . value ( ) ) {
case CommonLigValue : : CommonLigatures :
// Enables display of common ligatures (OpenType features: liga, clig).
2026-02-20 17:35:52 +13:00
features . set ( " liga " _fly_string , 1 ) ;
features . set ( " clig " _fly_string , 1 ) ;
2026-02-19 10:51:45 +13:00
break ;
case CommonLigValue : : NoCommonLigatures :
// Disables display of common ligatures (OpenType features: liga, clig).
2026-02-20 17:35:52 +13:00
features . set ( " liga " _fly_string , 0 ) ;
features . set ( " clig " _fly_string , 0 ) ;
2026-02-19 10:51:45 +13:00
break ;
}
2026-01-18 16:16:17 +13:00
}
2026-02-19 10:51:45 +13:00
if ( ligature . discretionary . has_value ( ) ) {
switch ( ligature . discretionary . value ( ) ) {
case DiscretionaryLigValue : : DiscretionaryLigatures :
// Enables display of discretionary ligatures (OpenType feature: dlig).
2026-02-20 17:35:52 +13:00
features . set ( " dlig " _fly_string , 1 ) ;
2026-02-19 10:51:45 +13:00
break ;
case DiscretionaryLigValue : : NoDiscretionaryLigatures :
// Disables display of discretionary ligatures (OpenType feature: dlig).
2026-02-20 17:35:52 +13:00
features . set ( " dlig " _fly_string , 0 ) ;
2026-02-19 10:51:45 +13:00
break ;
}
2026-01-18 16:16:17 +13:00
}
2026-02-19 10:51:45 +13:00
if ( ligature . historical . has_value ( ) ) {
switch ( ligature . historical . value ( ) ) {
case HistoricalLigValue : : HistoricalLigatures :
// Enables display of historical ligatures (OpenType feature: hlig).
2026-02-20 17:35:52 +13:00
features . set ( " hlig " _fly_string , 1 ) ;
2026-02-19 10:51:45 +13:00
break ;
case HistoricalLigValue : : NoHistoricalLigatures :
// Disables display of historical ligatures (OpenType feature: hlig).
2026-02-20 17:35:52 +13:00
features . set ( " hlig " _fly_string , 0 ) ;
2026-02-19 10:51:45 +13:00
break ;
}
2026-01-18 16:16:17 +13:00
}
2026-02-19 10:51:45 +13:00
if ( ligature . contextual . has_value ( ) ) {
switch ( ligature . contextual . value ( ) ) {
case ContextualAltValue : : Contextual :
// Enables display of contextual ligatures (OpenType feature: calt).
2026-02-20 17:35:52 +13:00
features . set ( " calt " _fly_string , 1 ) ;
2026-02-19 10:51:45 +13:00
break ;
case ContextualAltValue : : NoContextual :
// Disables display of contextual ligatures (OpenType feature: calt).
2026-02-20 17:35:52 +13:00
features . set ( " calt " _fly_string , 0 ) ;
2026-02-19 10:51:45 +13:00
break ;
}
2026-01-18 16:16:17 +13:00
}
}
} else if ( text_rendering = = TextRendering : : Optimizespeed ) {
// AD-HOC: Disable ligatures if font-variant-ligatures is set to normal and text rendering is set to optimize speed.
disable_all_ligatures ( ) ;
} else {
// A value of normal specifies that common default features are enabled, as described in detail in the next section.
2026-02-20 17:35:52 +13:00
features . set ( " liga " _fly_string , 1 ) ;
features . set ( " clig " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
}
// 6.5 https://drafts.csswg.org/css-fonts/#font-variant-position-prop
switch ( font_variant_position ) {
case FontVariantPosition : : Normal :
// None of the features listed below are enabled.
break ;
case FontVariantPosition : : Sub :
// Enables display of subscripts (OpenType feature: subs).
2026-02-20 17:35:52 +13:00
features . set ( " subs " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
break ;
case FontVariantPosition : : Super :
// Enables display of superscripts (OpenType feature: sups).
2026-02-20 17:35:52 +13:00
features . set ( " sups " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
break ;
default :
break ;
}
// 6.6 https://drafts.csswg.org/css-fonts/#font-variant-caps-prop
switch ( font_variant_caps ) {
case FontVariantCaps : : Normal :
// None of the features listed below are enabled.
break ;
case FontVariantCaps : : SmallCaps :
// Enables display of small capitals (OpenType feature: smcp). Small-caps glyphs typically use the form of uppercase letters but are reduced to the size of lowercase letters.
2026-02-20 17:35:52 +13:00
features . set ( " smcp " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
break ;
case FontVariantCaps : : AllSmallCaps :
// Enables display of small capitals for both upper and lowercase letters (OpenType features: c2sc, smcp).
2026-02-20 17:35:52 +13:00
features . set ( " c2sc " _fly_string , 1 ) ;
features . set ( " smcp " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
break ;
case FontVariantCaps : : PetiteCaps :
// Enables display of petite capitals (OpenType feature: pcap).
2026-02-20 17:35:52 +13:00
features . set ( " pcap " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
break ;
case FontVariantCaps : : AllPetiteCaps :
// Enables display of petite capitals for both upper and lowercase letters (OpenType features: c2pc, pcap).
2026-02-20 17:35:52 +13:00
features . set ( " c2pc " _fly_string , 1 ) ;
features . set ( " pcap " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
break ;
case FontVariantCaps : : Unicase :
// Enables display of mixture of small capitals for uppercase letters with normal lowercase letters (OpenType feature: unic).
2026-02-20 17:35:52 +13:00
features . set ( " unic " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
break ;
case FontVariantCaps : : TitlingCaps :
// Enables display of titling capitals (OpenType feature: titl).
2026-02-20 17:35:52 +13:00
features . set ( " titl " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
break ;
default :
break ;
}
// 6.7 https://drafts.csswg.org/css-fonts/#font-variant-numeric-prop
if ( font_variant_numeric . has_value ( ) ) {
auto numeric = font_variant_numeric . value ( ) ;
2026-02-19 22:57:35 +13:00
if ( numeric . figure = = NumericFigureValue : : OldstyleNums ) {
2026-01-18 16:16:17 +13:00
// Enables display of old-style numerals (OpenType feature: onum).
2026-02-20 17:35:52 +13:00
features . set ( " onum " _fly_string , 1 ) ;
2026-02-19 22:57:35 +13:00
} else if ( numeric . figure = = NumericFigureValue : : LiningNums ) {
2026-01-18 16:16:17 +13:00
// Enables display of lining numerals (OpenType feature: lnum).
2026-02-20 17:35:52 +13:00
features . set ( " lnum " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
}
2026-02-19 22:57:35 +13:00
if ( numeric . spacing = = NumericSpacingValue : : ProportionalNums ) {
2026-01-18 16:16:17 +13:00
// Enables display of proportional numerals (OpenType feature: pnum).
2026-02-20 17:35:52 +13:00
features . set ( " pnum " _fly_string , 1 ) ;
2026-02-19 22:57:35 +13:00
} else if ( numeric . spacing = = NumericSpacingValue : : TabularNums ) {
2026-01-18 16:16:17 +13:00
// Enables display of tabular numerals (OpenType feature: tnum).
2026-02-20 17:35:52 +13:00
features . set ( " tnum " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
}
2026-02-19 22:57:35 +13:00
if ( numeric . fraction = = NumericFractionValue : : DiagonalFractions ) {
2026-01-18 16:16:17 +13:00
// Enables display of diagonal fractions (OpenType feature: frac).
2026-02-20 17:35:52 +13:00
features . set ( " frac " _fly_string , 1 ) ;
2026-02-19 22:57:35 +13:00
} else if ( numeric . fraction = = NumericFractionValue : : StackedFractions ) {
2026-01-18 16:16:17 +13:00
// Enables display of stacked fractions (OpenType feature: afrc).
2026-02-20 17:35:52 +13:00
features . set ( " afrc " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
}
if ( numeric . ordinal ) {
// Enables display of letter forms used with ordinal numbers (OpenType feature: ordn).
2026-02-20 17:35:52 +13:00
features . set ( " ordn " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
}
if ( numeric . slashed_zero ) {
// Enables display of slashed zeros (OpenType feature: zero).
2026-02-20 17:35:52 +13:00
features . set ( " zero " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
}
}
2026-02-20 16:34:01 +13:00
// 6.8 https://drafts.csswg.org/css-fonts/#font-variant-alternates-prop
2026-02-20 20:40:49 +13:00
// FIXME: These values never apply to generic font families
2026-02-20 16:34:01 +13:00
if ( font_variant_alternates . has_value ( ) ) {
auto alternates = font_variant_alternates . value ( ) ;
if ( alternates . historical_forms ) {
// Enables display of historical forms (OpenType feature: hist).
2026-02-20 17:35:52 +13:00
features . set ( " hist " _fly_string , 1 ) ;
2026-02-20 16:34:01 +13:00
}
2026-02-20 20:40:49 +13:00
for ( auto const & key : alternates . font_feature_value_entries ) {
auto const & maybe_values = font_feature_values . get ( key ) ;
if ( ! maybe_values . has_value ( ) )
continue ;
auto const & values = maybe_values . value ( ) ;
switch ( key . type ) {
case FontFeatureValueType : : Stylistic : {
// Enables display of stylistic alternates (font specific, OpenType feature: salt <feature-index>).
// stylistic(<feature-value-name>)
features . set ( " salt " _fly_string , values [ 0 ] ) ;
break ;
}
case FontFeatureValueType : : Styleset : {
// Enables display with stylistic sets (font specific, OpenType feature: ss<feature-index> OpenType
// currently defines ss01 through ss20).
// https://drafts.csswg.org/css-fonts/#multi-value-features
// For the styleset() property value and @styleset rule, multiple values indicate the style sets to
// be enabled. Values between 1 and 99 enable OpenType features ss01 through ss99. However, the
// OpenType standard only officially defines ss01 through ss20. For OpenType fonts, values greater
// than 99 or equal to 0 do not generate a syntax error when parsed but enable no OpenType features.
for ( auto const & value : values ) {
if ( value > 99 | | value = = 0 )
continue ;
features . set ( MUST ( String : : formatted ( " ss{:02} " , value ) ) , 1 ) ;
}
break ;
}
case FontFeatureValueType : : CharacterVariant : {
// Enables display of specific character variants (font specific, OpenType feature:
// cv<feature-index> OpenType currently defines cv01 through cv99).
// https://drafts.csswg.org/css-fonts/#multi-value-features
// For <@character-variant>, a single value between 1 and 99 indicates the enabling of OpenType
// feature cv01 through cv99. For OpenType fonts, values greater than 99 or equal to 0 are ignored
// but do not generate a syntax error when parsed but enable no OpenType features. When two values
// are listed, the first value indicates the feature used and the second the value passed for that
// feature.
auto const feature = values [ 0 ] ;
if ( feature > 99 | | feature = = 0 )
break ;
auto const value = values . size ( ) > 1 ? values [ 1 ] : 1 ;
features . set ( MUST ( String : : formatted ( " cv{:02} " , feature ) ) , value ) ;
break ;
}
case FontFeatureValueType : : Swash : {
// Enables display of swash glyphs (font specific, OpenType feature: swsh <feature-index>,
// cswh <feature-index>).
features . set ( " swsh " _fly_string , values [ 0 ] ) ;
features . set ( " cswh " _fly_string , values [ 0 ] ) ;
break ;
}
case FontFeatureValueType : : Ornaments : {
// Enables replacement of default glyphs with ornaments, if provided in the font (font specific,
// OpenType feature: ornm <feature-index>).
features . set ( " ornm " _fly_string , values [ 0 ] ) ;
break ;
}
case FontFeatureValueType : : Annotation : {
// annotation(<feature-value-name>)
// Enables display of alternate annotation forms (font specific, OpenType feature: nalt <feature-index>).
features . set ( " nalt " _fly_string , values [ 0 ] ) ;
break ;
}
}
}
2026-02-20 16:34:01 +13:00
}
2026-01-20 23:50:16 +13:00
2026-01-18 16:16:17 +13:00
// 6.10 https://drafts.csswg.org/css-fonts/#font-variant-east-asian-prop
if ( font_variant_east_asian . has_value ( ) ) {
auto east_asian = font_variant_east_asian . value ( ) ;
2026-02-18 23:13:36 +13:00
if ( east_asian . variant . has_value ( ) ) {
switch ( east_asian . variant . value ( ) ) {
case EastAsianVariant : : Jis78 :
// Enables display of JIS78 forms (OpenType feature: jp78).
2026-02-20 17:35:52 +13:00
features . set ( " jp78 " _fly_string , 1 ) ;
2026-02-18 23:13:36 +13:00
break ;
case EastAsianVariant : : Jis83 :
// Enables display of JIS83 forms (OpenType feature: jp83).
2026-02-20 17:35:52 +13:00
features . set ( " jp83 " _fly_string , 1 ) ;
2026-02-18 23:13:36 +13:00
break ;
case EastAsianVariant : : Jis90 :
// Enables display of JIS90 forms (OpenType feature: jp90).
2026-02-20 17:35:52 +13:00
features . set ( " jp90 " _fly_string , 1 ) ;
2026-02-18 23:13:36 +13:00
break ;
case EastAsianVariant : : Jis04 :
// Enables display of JIS04 forms (OpenType feature: jp04).
2026-02-20 17:35:52 +13:00
features . set ( " jp04 " _fly_string , 1 ) ;
2026-02-18 23:13:36 +13:00
break ;
case EastAsianVariant : : Simplified :
// Enables display of simplified forms (OpenType feature: smpl).
2026-02-20 17:35:52 +13:00
features . set ( " smpl " _fly_string , 1 ) ;
2026-02-18 23:13:36 +13:00
break ;
case EastAsianVariant : : Traditional :
// Enables display of traditional forms (OpenType feature: trad).
2026-02-20 17:35:52 +13:00
features . set ( " trad " _fly_string , 1 ) ;
2026-02-18 23:13:36 +13:00
break ;
}
2026-01-18 16:16:17 +13:00
}
2026-02-18 23:13:36 +13:00
if ( east_asian . width . has_value ( ) ) {
switch ( east_asian . width . value ( ) ) {
case EastAsianWidth : : FullWidth :
// Enables display of full-width forms (OpenType feature: fwid).
2026-02-20 17:35:52 +13:00
features . set ( " fwid " _fly_string , 1 ) ;
2026-02-18 23:13:36 +13:00
break ;
case EastAsianWidth : : ProportionalWidth :
// Enables display of proportional-width forms (OpenType feature: pwid).
2026-02-20 17:35:52 +13:00
features . set ( " pwid " _fly_string , 1 ) ;
2026-02-18 23:13:36 +13:00
break ;
}
2026-01-18 16:16:17 +13:00
}
if ( east_asian . ruby ) {
// Enables display of ruby forms (OpenType feature: ruby).
2026-02-20 17:35:52 +13:00
features . set ( " ruby " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
}
}
// FIXME: vkrn should be enabled for vertical text.
switch ( font_kerning ) {
case FontKerning : : Auto :
// AD-HOC: Disable kerning if font-kerning is set to normal and text rendering is set to optimize speed.
2026-02-20 17:35:52 +13:00
features . set ( " kern " _fly_string , text_rendering ! = TextRendering : : Optimizespeed ? 1 : 0 ) ;
2026-01-18 16:16:17 +13:00
break ;
case FontKerning : : Normal :
2026-02-20 17:35:52 +13:00
features . set ( " kern " _fly_string , 1 ) ;
2026-01-18 16:16:17 +13:00
break ;
case FontKerning : : None :
2026-02-20 17:35:52 +13:00
features . set ( " kern " _fly_string , 0 ) ;
2026-01-18 16:16:17 +13:00
break ;
default :
break ;
}
return features ;
} ;
2026-01-20 23:50:16 +13:00
// https://drafts.csswg.org/css-fonts-4/#feature-variation-precedence
// FIXME: 1. Font features enabled by default are applied, including features required for a given script. See § 7.1
// Default features for a description of these.
// FIXME: 2. Font variations as enabled by the font-weight, font-width, and font-style properties are applied.
//
// The application of the value enabled by font-style is affected by font selection, because this property
// might select an italic or an oblique font. The value applied is the closest matching value as
// determined by the font matching algorithm. User agents must apply at most one value due to the
// font-style property; both "ital" and "slnt" values must not be set together.
//
// If the selected font is defined in an @font-face rule, then the values applied at this step should be
// clamped to the value of the font-weight, font-width, and font-style descriptors in that @font-face
// rule.
//
// Then, the values applied in this step should be clamped (possibly again) to the values that are
// supported by the font.
// FIXME: 3. The language specified by the inherited value of lang/xml:lang is applied.
// FIXME: 4. If the font is defined via an @font-face rule, the font language override implied by the
// font-language-override descriptor in the @font-face rule is applied.
2026-01-18 16:16:17 +13:00
2026-01-20 23:50:16 +13:00
// FIXME: 5. If the font is defined via an @font-face rule, that @font-face rule includes at least one valid
// font-named-instance descriptor with a value other than 'font-named-instance/none', and the loaded font
// resource includes a named instance with that name according to the § 5.1 Localized name matching rules,
// then all the variation values represented by that named instance are applied. These values are clamped
// to the values that are supported by the font.
2026-01-18 16:16:17 +13:00
2026-01-20 23:50:16 +13:00
// FIXME: 6. If the font is defined via an @font-face rule, the font variations implied by the
// font-variation-settings descriptor in the @font-face rule are applied.
2026-01-18 16:16:17 +13:00
2026-01-20 23:50:16 +13:00
// FIXME: 7. If the font is defined via an @font-face rule, the font features implied by the font-feature-settings
// descriptor in the @font-face rule are applied.
// FIXME: 8. The font language override implied by the value of the font-language-override property is applied.
// FIXME: 9. Font variations implied by the value of the font-optical-sizing property are applied.
// 10. Font features implied by the value of the font-variant property, the related font-variant subproperties and
// any other CSS property that uses OpenType features (e.g. the font-kerning property) are applied.
2026-01-18 16:16:17 +13:00
merged_features . update ( font_variant_features ( ) ) ;
2026-01-20 23:50:16 +13:00
// FIXME: 11. Feature settings determined by properties other than font-variant or font-feature-settings are
// applied. For example, setting a non-default value for the letter-spacing property disables optional
// ligatures.
// FIXME: 12. Font variations implied by the value of the font-variation-settings property are applied. These values
// should be clamped to the values that are supported by the font.
2026-01-18 16:16:17 +13:00
2026-01-20 23:50:16 +13:00
// 13. Font features implied by the value of font-feature-settings property are applied.
2026-02-20 17:35:52 +13:00
merged_features . update ( font_feature_settings ) ;
2026-01-18 16:16:17 +13:00
Gfx : : ShapeFeatures shape_features ;
shape_features . ensure_capacity ( merged_features . size ( ) ) ;
2026-02-20 17:35:52 +13:00
for ( auto & it : merged_features ) {
auto key_string_view = it . key . bytes_as_string_view ( ) ;
shape_features . unchecked_append ( { { key_string_view [ 0 ] , key_string_view [ 1 ] , key_string_view [ 2 ] , key_string_view [ 3 ] } , static_cast < u32 > ( it . value ) } ) ;
}
2026-01-18 16:16:17 +13:00
return shape_features ;
}
}