/* * Copyright (c) 2026, Callum Law * * SPDX-License-Identifier: BSD-2-Clause */ #include "FontFeatureData.h" #include #include namespace Web::CSS { Gfx::ShapeFeatures FontFeatureData::to_shape_features(HashMap> const&) const { HashMap merged_features; auto font_variant_features = [&]() { HashMap features; // 6.4 https://drafts.csswg.org/css-fonts/#font-variant-ligatures-prop auto disable_all_ligatures = [&]() { 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); }; 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 { if (ligature.common.has_value()) { switch (ligature.common.value()) { case CommonLigValue::CommonLigatures: // Enables display of common ligatures (OpenType features: liga, clig). features.set("liga"_fly_string, 1); features.set("clig"_fly_string, 1); break; case CommonLigValue::NoCommonLigatures: // Disables display of common ligatures (OpenType features: liga, clig). features.set("liga"_fly_string, 0); features.set("clig"_fly_string, 0); break; } } if (ligature.discretionary.has_value()) { switch (ligature.discretionary.value()) { case DiscretionaryLigValue::DiscretionaryLigatures: // Enables display of discretionary ligatures (OpenType feature: dlig). features.set("dlig"_fly_string, 1); break; case DiscretionaryLigValue::NoDiscretionaryLigatures: // Disables display of discretionary ligatures (OpenType feature: dlig). features.set("dlig"_fly_string, 0); break; } } if (ligature.historical.has_value()) { switch (ligature.historical.value()) { case HistoricalLigValue::HistoricalLigatures: // Enables display of historical ligatures (OpenType feature: hlig). features.set("hlig"_fly_string, 1); break; case HistoricalLigValue::NoHistoricalLigatures: // Disables display of historical ligatures (OpenType feature: hlig). features.set("hlig"_fly_string, 0); break; } } if (ligature.contextual.has_value()) { switch (ligature.contextual.value()) { case ContextualAltValue::Contextual: // Enables display of contextual ligatures (OpenType feature: calt). features.set("calt"_fly_string, 1); break; case ContextualAltValue::NoContextual: // Disables display of contextual ligatures (OpenType feature: calt). features.set("calt"_fly_string, 0); break; } } } } 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. features.set("liga"_fly_string, 1); features.set("clig"_fly_string, 1); } // 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). features.set("subs"_fly_string, 1); break; case FontVariantPosition::Super: // Enables display of superscripts (OpenType feature: sups). features.set("sups"_fly_string, 1); 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. features.set("smcp"_fly_string, 1); break; case FontVariantCaps::AllSmallCaps: // Enables display of small capitals for both upper and lowercase letters (OpenType features: c2sc, smcp). features.set("c2sc"_fly_string, 1); features.set("smcp"_fly_string, 1); break; case FontVariantCaps::PetiteCaps: // Enables display of petite capitals (OpenType feature: pcap). features.set("pcap"_fly_string, 1); break; case FontVariantCaps::AllPetiteCaps: // Enables display of petite capitals for both upper and lowercase letters (OpenType features: c2pc, pcap). features.set("c2pc"_fly_string, 1); features.set("pcap"_fly_string, 1); break; case FontVariantCaps::Unicase: // Enables display of mixture of small capitals for uppercase letters with normal lowercase letters (OpenType feature: unic). features.set("unic"_fly_string, 1); break; case FontVariantCaps::TitlingCaps: // Enables display of titling capitals (OpenType feature: titl). features.set("titl"_fly_string, 1); 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(); if (numeric.figure == NumericFigureValue::OldstyleNums) { // Enables display of old-style numerals (OpenType feature: onum). features.set("onum"_fly_string, 1); } else if (numeric.figure == NumericFigureValue::LiningNums) { // Enables display of lining numerals (OpenType feature: lnum). features.set("lnum"_fly_string, 1); } if (numeric.spacing == NumericSpacingValue::ProportionalNums) { // Enables display of proportional numerals (OpenType feature: pnum). features.set("pnum"_fly_string, 1); } else if (numeric.spacing == NumericSpacingValue::TabularNums) { // Enables display of tabular numerals (OpenType feature: tnum). features.set("tnum"_fly_string, 1); } if (numeric.fraction == NumericFractionValue::DiagonalFractions) { // Enables display of diagonal fractions (OpenType feature: frac). features.set("frac"_fly_string, 1); } else if (numeric.fraction == NumericFractionValue::StackedFractions) { // Enables display of stacked fractions (OpenType feature: afrc). features.set("afrc"_fly_string, 1); } if (numeric.ordinal) { // Enables display of letter forms used with ordinal numbers (OpenType feature: ordn). features.set("ordn"_fly_string, 1); } if (numeric.slashed_zero) { // Enables display of slashed zeros (OpenType feature: zero). features.set("zero"_fly_string, 1); } } // 6.8 https://drafts.csswg.org/css-fonts/#font-variant-alternates-prop if (font_variant_alternates.has_value()) { auto alternates = font_variant_alternates.value(); if (alternates.historical_forms) { // Enables display of historical forms (OpenType feature: hist). features.set("hist"_fly_string, 1); } // FIXME: Support function entries } // 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(); if (east_asian.variant.has_value()) { switch (east_asian.variant.value()) { case EastAsianVariant::Jis78: // Enables display of JIS78 forms (OpenType feature: jp78). features.set("jp78"_fly_string, 1); break; case EastAsianVariant::Jis83: // Enables display of JIS83 forms (OpenType feature: jp83). features.set("jp83"_fly_string, 1); break; case EastAsianVariant::Jis90: // Enables display of JIS90 forms (OpenType feature: jp90). features.set("jp90"_fly_string, 1); break; case EastAsianVariant::Jis04: // Enables display of JIS04 forms (OpenType feature: jp04). features.set("jp04"_fly_string, 1); break; case EastAsianVariant::Simplified: // Enables display of simplified forms (OpenType feature: smpl). features.set("smpl"_fly_string, 1); break; case EastAsianVariant::Traditional: // Enables display of traditional forms (OpenType feature: trad). features.set("trad"_fly_string, 1); break; } } if (east_asian.width.has_value()) { switch (east_asian.width.value()) { case EastAsianWidth::FullWidth: // Enables display of full-width forms (OpenType feature: fwid). features.set("fwid"_fly_string, 1); break; case EastAsianWidth::ProportionalWidth: // Enables display of proportional-width forms (OpenType feature: pwid). features.set("pwid"_fly_string, 1); break; } } if (east_asian.ruby) { // Enables display of ruby forms (OpenType feature: ruby). features.set("ruby"_fly_string, 1); } } // 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. features.set("kern"_fly_string, text_rendering != TextRendering::Optimizespeed ? 1 : 0); break; case FontKerning::Normal: features.set("kern"_fly_string, 1); break; case FontKerning::None: features.set("kern"_fly_string, 0); break; default: break; } return features; }; // 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. // 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. // 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. // 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. merged_features.update(font_variant_features()); // 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. // 13. Font features implied by the value of font-feature-settings property are applied. merged_features.update(font_feature_settings); Gfx::ShapeFeatures shape_features; shape_features.ensure_capacity(merged_features.size()); 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(it.value) }); } return shape_features; } }