From 95c17dfab51fefdd5ca364652e8571827d9693b1 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 1 Oct 2024 09:37:43 +0100 Subject: [PATCH] LibWeb/CSS: Parse and propagate font-feature-settings property --- .../css/getComputedStyle-print-all.txt | 3 +- .../Libraries/LibWeb/CSS/ComputedValues.h | 3 + Userland/Libraries/LibWeb/CSS/Keywords.json | 2 + .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 77 +++++++++++++++++++ Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 1 + Userland/Libraries/LibWeb/CSS/Properties.json | 14 ++++ .../Libraries/LibWeb/CSS/StyleProperties.cpp | 27 +++++++ .../Libraries/LibWeb/CSS/StyleProperties.h | 1 + Userland/Libraries/LibWeb/Layout/Node.cpp | 2 + 9 files changed, 129 insertions(+), 1 deletion(-) diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt index 99b69713a55..0c18222b6cb 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt @@ -11,6 +11,7 @@ fill: rgb(0, 0, 0) fill-opacity: 1 fill-rule: nonzero font-family: serif +font-feature-settings: normal font-language-override: normal font-size: 16px font-style: normal @@ -119,7 +120,7 @@ grid-row-start: auto grid-template-areas: grid-template-columns: grid-template-rows: -height: 2057px +height: 2074px inline-size: auto inset-block-end: auto inset-block-start: auto diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index 66c97db5b66..163843cb828 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Userland/Libraries/LibWeb/CSS/ComputedValues.h @@ -489,6 +489,7 @@ public: int font_weight() const { return m_inherited.font_weight; } CSS::FontVariant font_variant() const { return m_inherited.font_variant; } Optional font_language_override() const { return m_inherited.font_language_override; } + Optional> font_feature_settings() const { return m_inherited.font_feature_settings; } Optional> font_variation_settings() const { return m_inherited.font_variation_settings; } CSSPixels line_height() const { return m_inherited.line_height; } CSS::Time transition_delay() const { return m_noninherited.transition_delay; } @@ -522,6 +523,7 @@ protected: int font_weight { InitialValues::font_weight() }; CSS::FontVariant font_variant { InitialValues::font_variant() }; Optional font_language_override; + Optional> font_feature_settings; Optional> font_variation_settings; CSSPixels line_height { InitialValues::line_height() }; CSS::BorderCollapse border_collapse { InitialValues::border_collapse() }; @@ -681,6 +683,7 @@ public: void set_font_weight(int font_weight) { m_inherited.font_weight = font_weight; } void set_font_variant(CSS::FontVariant font_variant) { m_inherited.font_variant = font_variant; } void set_font_language_override(Optional font_language_override) { m_inherited.font_language_override = font_language_override; } + void set_font_feature_settings(Optional> value) { m_inherited.font_feature_settings = move(value); } void set_font_variation_settings(Optional> value) { m_inherited.font_variation_settings = move(value); } void set_line_height(CSSPixels line_height) { m_inherited.line_height = line_height; } void set_border_spacing_horizontal(CSS::Length border_spacing_horizontal) { m_inherited.border_spacing_horizontal = border_spacing_horizontal; } diff --git a/Userland/Libraries/LibWeb/CSS/Keywords.json b/Userland/Libraries/LibWeb/CSS/Keywords.json index 0ba873b362d..2ad59f61112 100644 --- a/Userland/Libraries/LibWeb/CSS/Keywords.json +++ b/Userland/Libraries/LibWeb/CSS/Keywords.json @@ -265,6 +265,8 @@ "nw-resize", "nwse-resize", "oblique", + "off", + "on", "opaque", "open-quote", "optimizequality", diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 73e3d3e00b7..72cc0f1ab06 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -5480,6 +5480,79 @@ RefPtr Parser::parse_font_language_override_value(TokenStream Parser::parse_font_feature_settings_value(TokenStream& tokens) +{ + // https://drafts.csswg.org/css-fonts/#propdef-font-feature-settings + // normal | # + + // normal + if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) + return normal; + + // # + auto transaction = tokens.begin_transaction(); + auto tag_values = parse_a_comma_separated_list_of_component_values(tokens); + + // "The computed value of font-feature-settings is a map, so any duplicates in the specified value must not be preserved. + // If the same feature tag appears more than once, the value associated with the last appearance supersedes any previous + // value for that axis." + // So, we deduplicate them here using a HashSet. + + OrderedHashMap> feature_tags_map; + for (auto const& values : tag_values) { + // = [ | on | off ]? + TokenStream tag_tokens { values }; + tag_tokens.skip_whitespace(); + auto opentype_tag = parse_opentype_tag_value(tag_tokens); + tag_tokens.skip_whitespace(); + RefPtr value; + if (tag_tokens.has_next_token()) { + if (auto integer = parse_integer_value(tag_tokens)) { + if (integer->is_integer() && integer->as_integer().value() < 0) + return nullptr; + value = integer; + } else { + // A value of on is synonymous with 1 and off is synonymous with 0. + auto keyword = parse_keyword_value(tag_tokens); + if (!keyword) + return nullptr; + switch (keyword->to_keyword()) { + case Keyword::On: + value = IntegerStyleValue::create(1); + break; + case Keyword::Off: + value = IntegerStyleValue::create(0); + break; + default: + return nullptr; + } + } + tag_tokens.skip_whitespace(); + } else { + // "If the value is omitted, a value of 1 is assumed." + value = IntegerStyleValue::create(1); + } + + if (!opentype_tag || !value || tag_tokens.has_next_token()) + return nullptr; + + feature_tags_map.set(opentype_tag->string_value(), OpenTypeTaggedStyleValue::create(opentype_tag->string_value(), value.release_nonnull())); + } + + // "The computed value contains the de-duplicated feature tags, sorted in ascending order by code unit." + StyleValueVector feature_tags; + feature_tags.ensure_capacity(feature_tags_map.size()); + for (auto const& [key, feature_tag] : feature_tags_map) + feature_tags.append(feature_tag); + + quick_sort(feature_tags, [](auto& a, auto& b) { + return a->as_open_type_tagged().tag() < b->as_open_type_tagged().tag(); + }); + + transaction.commit(); + return StyleValueList::create(move(feature_tags), StyleValueList::Separator::Comma); +} + RefPtr Parser::parse_font_variation_settings_value(TokenStream& tokens) { // https://drafts.csswg.org/css-fonts/#propdef-font-variation-settings @@ -7620,6 +7693,10 @@ Parser::ParseErrorOr> Parser::parse_css_value(Prope if (auto parsed_value = parse_font_family_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); return ParseError::SyntaxError; + case PropertyID::FontFeatureSettings: + if (auto parsed_value = parse_font_feature_settings_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; case PropertyID::FontLanguageOverride: if (auto parsed_value = parse_font_language_override_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index e8b0837ee78..80179d17699 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -307,6 +307,7 @@ private: RefPtr parse_font_value(TokenStream&); RefPtr parse_font_family_value(TokenStream&); RefPtr parse_font_language_override_value(TokenStream&); + RefPtr parse_font_feature_settings_value(TokenStream&); RefPtr parse_font_variation_settings_value(TokenStream&); RefPtr parse_list_style_value(TokenStream&); RefPtr parse_math_depth_value(TokenStream&); diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json index 614c5a16fcc..627b33519c3 100644 --- a/Userland/Libraries/LibWeb/CSS/Properties.json +++ b/Userland/Libraries/LibWeb/CSS/Properties.json @@ -1165,6 +1165,20 @@ "string" ] }, + "font-feature-settings": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "integer [0,∞]", + "opentype-tag" + ], + "valid-identifiers": [ + "normal", + "on", + "off" + ] + }, "font-language-override": { "animation-type": "discrete", "inherited": true, diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index 94d41d117f9..a7a861dfebe 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -1028,6 +1028,33 @@ Optional StyleProperties::font_language_override() const return {}; } +Optional> StyleProperties::font_feature_settings() const +{ + auto value = property(PropertyID::FontFeatureSettings); + + if (value->is_keyword()) + return {}; // normal + + if (value->is_value_list()) { + auto const& feature_tags = value->as_value_list().values(); + HashMap result; + result.ensure_capacity(feature_tags.size()); + for (auto const& tag_value : feature_tags) { + auto const& feature_tag = tag_value->as_open_type_tagged(); + + if (feature_tag.value()->is_integer()) { + result.set(feature_tag.tag(), feature_tag.value()->as_integer().value()); + } else { + VERIFY(feature_tag.value()->is_math()); + result.set(feature_tag.tag(), IntegerOrCalculated { feature_tag.value()->as_math() }); + } + } + return result; + } + + return {}; +} + Optional> StyleProperties::font_variation_settings() const { auto value = property(CSS::PropertyID::FontVariationSettings); diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index 71e4ad8859e..3e8c8e6e017 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -149,6 +149,7 @@ public: Variant vertical_align() const; Optional font_variant() const; Optional font_language_override() const; + Optional> font_feature_settings() const; Optional> font_variation_settings() const; CSS::GridTrackSizeList grid_auto_columns() const; CSS::GridTrackSizeList grid_auto_rows() const; diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index b590d443a56..a62b6b3c059 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -471,6 +471,8 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) computed_values.set_font_variant(maybe_font_variant.release_value()); if (auto maybe_font_language_override = computed_style.font_language_override(); maybe_font_language_override.has_value()) computed_values.set_font_language_override(maybe_font_language_override.release_value()); + if (auto maybe_font_feature_settings = computed_style.font_feature_settings(); maybe_font_feature_settings.has_value()) + computed_values.set_font_feature_settings(maybe_font_feature_settings.release_value()); if (auto maybe_font_variation_settings = computed_style.font_variation_settings(); maybe_font_variation_settings.has_value()) computed_values.set_font_variation_settings(maybe_font_variation_settings.release_value());