瀏覽代碼

LibWeb: Parse font-variant-* css properties

Johan Dahlin 7 月之前
父節點
當前提交
aabbe87628
共有 2 個文件被更改,包括 484 次插入0 次删除
  1. 477 0
      Libraries/LibWeb/CSS/Parser/Parser.cpp
  2. 7 0
      Libraries/LibWeb/CSS/Parser/Parser.h

+ 477 - 0
Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -6056,6 +6056,459 @@ RefPtr<CSSStyleValue> Parser::parse_font_variation_settings_value(TokenStream<Co
     return StyleValueList::create(move(axis_tags), StyleValueList::Separator::Comma);
     return StyleValueList::create(move(axis_tags), StyleValueList::Separator::Comma);
 }
 }
 
 
+RefPtr<CSSStyleValue> Parser::parse_font_variant(TokenStream<ComponentValue>& tokens)
+{
+    // 6.11 https://drafts.csswg.org/css-fonts/#propdef-font-variant
+    // normal | none |
+    // [ [ <common-lig-values> || <discretionary-lig-values> || <historical-lig-values> || <contextual-alt-values> ]
+    // || [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ] ||
+    // [ FIXME: stylistic(<feature-value-name>) ||
+    // historical-forms ||
+    // FIXME: styleset(<feature-value-name>#) ||
+    // FIXME: character-variant(<feature-value-name>#) ||
+    // FIXME: swash(<feature-value-name>) ||
+    // FIXME: ornaments(<feature-value-name>) ||
+    // FIXME: annotation(<feature-value-name>) ] ||
+    // [ <numeric-figure-values> || <numeric-spacing-values> || <numeric-fraction-values> ||
+    // ordinal || slashed-zero ] || [ <east-asian-variant-values> || <east-asian-width-values> || ruby ] ||
+    // [ sub | super ] || [ text | emoji | unicode ] ]
+
+    bool has_common_ligatures = false;
+    bool has_discretionary_ligatures = false;
+    bool has_historical_ligatures = false;
+    bool has_contextual = false;
+    bool has_numeric_figures = false;
+    bool has_numeric_spacing = false;
+    bool has_numeric_fractions = false;
+    bool has_numeric_ordinals = false;
+    bool has_numeric_slashed_zero = false;
+    bool has_east_asian_variant = false;
+    bool has_east_asian_width = false;
+    bool has_east_asian_ruby = false;
+    RefPtr<CSSStyleValue> alternates_value {};
+    RefPtr<CSSStyleValue> caps_value {};
+    RefPtr<CSSStyleValue> emoji_value {};
+    RefPtr<CSSStyleValue> position_value {};
+    StyleValueVector east_asian_values;
+    StyleValueVector ligatures_values;
+    StyleValueVector numeric_values;
+
+    if (auto parsed_value = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) {
+        // normal, do nothing
+    } else if (auto parsed_value = parse_all_as_single_keyword_value(tokens, Keyword::None)) {
+        // none
+        ligatures_values.append(parsed_value.release_nonnull());
+    } else {
+
+        while (tokens.has_next_token()) {
+            auto maybe_value = parse_keyword_value(tokens);
+            if (!maybe_value)
+                break;
+            auto value = maybe_value.release_nonnull();
+            if (!value->is_keyword()) {
+                // FIXME: alternate functions such as stylistic()
+                return nullptr;
+            }
+            auto keyword = value->to_keyword();
+
+            switch (keyword) {
+            // <common-lig-values>       = [ common-ligatures | no-common-ligatures ]
+            case Keyword::CommonLigatures:
+            case Keyword::NoCommonLigatures:
+                if (has_common_ligatures)
+                    return nullptr;
+                ligatures_values.append(move(value));
+                has_common_ligatures = true;
+                break;
+            // <discretionary-lig-values> = [ discretionary-ligatures | no-discretionary-ligatures ]
+            case Keyword::DiscretionaryLigatures:
+            case Keyword::NoDiscretionaryLigatures:
+                if (has_discretionary_ligatures)
+                    return nullptr;
+                ligatures_values.append(move(value));
+                has_discretionary_ligatures = true;
+                break;
+            // <historical-lig-values>   = [ historical-ligatures | no-historical-ligatures ]
+            case Keyword::HistoricalLigatures:
+            case Keyword::NoHistoricalLigatures:
+                if (has_historical_ligatures)
+                    return nullptr;
+                ligatures_values.append(move(value));
+                has_historical_ligatures = true;
+                break;
+            // <contextual-alt-values>   = [ contextual | no-contextual ]
+            case Keyword::Contextual:
+            case Keyword::NoContextual:
+                if (has_contextual)
+                    return nullptr;
+                ligatures_values.append(move(value));
+                has_contextual = true;
+                break;
+            // historical-forms
+            case Keyword::HistoricalForms:
+                if (alternates_value != nullptr)
+                    return nullptr;
+                alternates_value = value.ptr();
+                break;
+            // [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ]
+            case Keyword::SmallCaps:
+            case Keyword::AllSmallCaps:
+            case Keyword::PetiteCaps:
+            case Keyword::AllPetiteCaps:
+            case Keyword::Unicase:
+            case Keyword::TitlingCaps:
+                if (caps_value != nullptr)
+                    return nullptr;
+                caps_value = value.ptr();
+                break;
+            // <numeric-figure-values>       = [ lining-nums | oldstyle-nums ]
+            case Keyword::LiningNums:
+            case Keyword::OldstyleNums:
+                if (has_numeric_figures)
+                    return nullptr;
+                numeric_values.append(move(value));
+                has_numeric_figures = true;
+                break;
+            // <numeric-spacing-values>      = [ proportional-nums | tabular-nums ]
+            case Keyword::ProportionalNums:
+            case Keyword::TabularNums:
+                if (has_numeric_spacing)
+                    return nullptr;
+                numeric_values.append(move(value));
+                has_numeric_spacing = true;
+                break;
+            // <numeric-fraction-values>     = [ diagonal-fractions | stacked-fractions]
+            case Keyword::DiagonalFractions:
+            case Keyword::StackedFractions:
+                if (has_numeric_fractions)
+                    return nullptr;
+                numeric_values.append(move(value));
+                has_numeric_fractions = true;
+                break;
+            // ordinal
+            case Keyword::Ordinal:
+                if (has_numeric_ordinals)
+                    return nullptr;
+                numeric_values.append(move(value));
+                has_numeric_ordinals = true;
+                break;
+            case Keyword::SlashedZero:
+                if (has_numeric_slashed_zero)
+                    return nullptr;
+                numeric_values.append(move(value));
+                has_numeric_slashed_zero = true;
+                break;
+            // <east-asian-variant-values> = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ]
+            case Keyword::Jis78:
+            case Keyword::Jis83:
+            case Keyword::Jis90:
+            case Keyword::Jis04:
+            case Keyword::Simplified:
+            case Keyword::Traditional:
+                if (has_east_asian_variant)
+                    return nullptr;
+                east_asian_values.append(move(value));
+                has_east_asian_variant = true;
+                break;
+            // <east-asian-width-values>   = [ full-width | proportional-width ]
+            case Keyword::FullWidth:
+            case Keyword::ProportionalWidth:
+                if (has_east_asian_width)
+                    return nullptr;
+                east_asian_values.append(move(value));
+                has_east_asian_width = true;
+                break;
+            // ruby
+            case Keyword::Ruby:
+                if (has_east_asian_ruby)
+                    return nullptr;
+                east_asian_values.append(move(value));
+                has_east_asian_ruby = true;
+                break;
+            // text | emoji | unicode
+            case Keyword::Text:
+            case Keyword::Emoji:
+            case Keyword::Unicode:
+                if (emoji_value != nullptr)
+                    return nullptr;
+                emoji_value = value.ptr();
+                break;
+            // sub | super
+            case Keyword::Sub:
+            case Keyword::Super:
+                if (position_value != nullptr)
+                    return nullptr;
+                position_value = value.ptr();
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    if (ligatures_values.is_empty())
+        ligatures_values.append(CSSKeywordValue::create(Keyword::Normal));
+    if (numeric_values.is_empty())
+        numeric_values.append(CSSKeywordValue::create(Keyword::Normal));
+    if (east_asian_values.is_empty())
+        east_asian_values.append(CSSKeywordValue::create(Keyword::Normal));
+
+    return ShorthandStyleValue::create(PropertyID::FontVariant,
+        { PropertyID::FontVariantAlternates,
+            PropertyID::FontVariantCaps,
+            PropertyID::FontVariantEastAsian,
+            PropertyID::FontVariantEmoji,
+            PropertyID::FontVariantLigatures,
+            PropertyID::FontVariantNumeric,
+            PropertyID::FontVariantPosition },
+        {
+            alternates_value == nullptr ? CSSKeywordValue::create(Keyword::Normal) : alternates_value.release_nonnull(),
+            caps_value == nullptr ? CSSKeywordValue::create(Keyword::Normal) : caps_value.release_nonnull(),
+            StyleValueList::create(move(east_asian_values), StyleValueList::Separator::Space),
+            emoji_value == nullptr ? CSSKeywordValue::create(Keyword::Normal) : emoji_value.release_nonnull(),
+            StyleValueList::create(move(ligatures_values), StyleValueList::Separator::Space),
+            StyleValueList::create(move(numeric_values), StyleValueList::Separator::Space),
+            position_value == nullptr ? CSSKeywordValue::create(Keyword::Normal) : position_value.release_nonnull(),
+        });
+}
+
+RefPtr<CSSStyleValue> Parser::parse_font_variant_alternates_value(TokenStream<ComponentValue>& tokens)
+{
+    // 6.8 https://drafts.csswg.org/css-fonts/#font-variant-alternates-prop
+    // normal |
+    // [ FIXME: stylistic(<feature-value-name>) ||
+    //   historical-forms ||
+    //   FIXME: styleset(<feature-value-name>#) ||
+    //   FIXME: character-variant(<feature-value-name>#) ||
+    //   FIXME: swash(<feature-value-name>) ||
+    //   FIXME: ornaments(<feature-value-name>) ||
+    //   FIXME: annotation(<feature-value-name>) ]
+
+    // normal
+    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal))
+        return normal;
+
+    // historical-forms
+    // FIXME: Support this together with other values when we parse them.
+    if (auto historical_forms = parse_all_as_single_keyword_value(tokens, Keyword::HistoricalForms))
+        return historical_forms;
+
+    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-variant-alternate: parsing {} not implemented.", tokens.next_token().to_debug_string());
+    return nullptr;
+}
+
+// FIXME: This should not be needed, however http://wpt.live/css/css-fonts/font-variant-caps.html fails without it
+RefPtr<CSSStyleValue> Parser::parse_font_variant_caps_value(TokenStream<ComponentValue>& tokens)
+{
+    // https://drafts.csswg.org/css-fonts/#propdef-font-variant-caps
+    // normal | small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps
+
+    bool has_token = false;
+    while (tokens.has_next_token()) {
+        if (has_token)
+            break;
+        auto maybe_value = parse_keyword_value(tokens);
+        if (!maybe_value)
+            break;
+        auto value = maybe_value.release_nonnull();
+        auto font_variant = keyword_to_font_variant_caps(value->to_keyword());
+        if (font_variant.has_value()) {
+            return value;
+        }
+        break;
+    }
+
+    return nullptr;
+}
+
+RefPtr<CSSStyleValue> Parser::parse_font_variant_east_asian_value(TokenStream<ComponentValue>& tokens)
+{
+    // 6.10 https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian
+    // normal | [ <east-asian-variant-values> || <east-asian-width-values> || ruby ]
+    // <east-asian-variant-values> = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ]
+    // <east-asian-width-values>   = [ full-width | proportional-width ]
+
+    StyleValueVector value_list;
+    bool has_ruby = false;
+    bool has_variant = false;
+    bool has_width = false;
+
+    // normal | ...
+    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) {
+        value_list.append(normal.release_nonnull());
+    } else {
+        while (tokens.has_next_token()) {
+            auto maybe_value = parse_keyword_value(tokens);
+            if (!maybe_value)
+                break;
+            auto value = maybe_value.release_nonnull();
+            auto font_variant = keyword_to_font_variant_east_asian(value->to_keyword());
+            if (!font_variant.has_value()) {
+                return nullptr;
+            }
+            auto keyword = value->to_keyword();
+            if (keyword == Keyword::Ruby) {
+                if (has_ruby)
+                    return nullptr;
+                has_ruby = true;
+            } else if (keyword == Keyword::FullWidth || keyword == Keyword::ProportionalWidth) {
+                if (has_width)
+                    return nullptr;
+                has_width = true;
+            } else {
+                if (has_variant)
+                    return nullptr;
+                has_variant = true;
+            }
+            value_list.append(move(value));
+        }
+    }
+    if (value_list.is_empty())
+        return nullptr;
+
+    return StyleValueList::create(move(value_list), StyleValueList::Separator::Space);
+}
+
+RefPtr<CSSStyleValue> Parser::parse_font_variant_ligatures_value(TokenStream<ComponentValue>& tokens)
+{
+    // 6.4 https://drafts.csswg.org/css-fonts/#propdef-font-variant-ligatures
+    // normal | none | [ <common-lig-values> || <discretionary-lig-values> || <historical-lig-values> || <contextual-alt-values> ]
+    // <common-lig-values>       = [ common-ligatures | no-common-ligatures ]
+    // <discretionary-lig-values> = [ discretionary-ligatures | no-discretionary-ligatures ]
+    // <historical-lig-values>   = [ historical-ligatures | no-historical-ligatures ]
+    // <contextual-alt-values>   = [ contextual | no-contextual ]
+
+    StyleValueVector value_list;
+    bool has_common_ligatures = false;
+    bool has_discretionary_ligatures = false;
+    bool has_historical_ligatures = false;
+    bool has_contextual = false;
+
+    // normal | ...
+    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) {
+        value_list.append(normal.release_nonnull());
+        // none | ...
+    } else if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None)) {
+        value_list.append(none.release_nonnull());
+    } else {
+        while (tokens.has_next_token()) {
+            auto maybe_value = parse_keyword_value(tokens);
+            if (!maybe_value)
+                break;
+            auto value = maybe_value.release_nonnull();
+            switch (value->to_keyword()) {
+            // <common-lig-values>       = [ common-ligatures | no-common-ligatures ]
+            case Keyword::CommonLigatures:
+            case Keyword::NoCommonLigatures:
+                if (has_common_ligatures)
+                    return nullptr;
+                has_common_ligatures = true;
+                break;
+            // <discretionary-lig-values> = [ discretionary-ligatures | no-discretionary-ligatures ]
+            case Keyword::DiscretionaryLigatures:
+            case Keyword::NoDiscretionaryLigatures:
+                if (has_discretionary_ligatures)
+                    return nullptr;
+                has_discretionary_ligatures = true;
+                break;
+            // <historical-lig-values> = [ historical-ligatures | no-historical-ligatures ]
+            case Keyword::HistoricalLigatures:
+            case Keyword::NoHistoricalLigatures:
+                if (has_historical_ligatures)
+                    return nullptr;
+                has_historical_ligatures = true;
+                break;
+            // <contextual-alt-values> = [ contextual | no-contextual ]
+            case Keyword::Contextual:
+            case Keyword::NoContextual:
+                if (has_contextual)
+                    return nullptr;
+                has_contextual = true;
+                break;
+            default:
+                return nullptr;
+            }
+            value_list.append(move(value));
+        }
+    }
+
+    if (value_list.is_empty())
+        return nullptr;
+
+    return StyleValueList::create(move(value_list), StyleValueList::Separator::Space);
+}
+
+RefPtr<CSSStyleValue> Parser::parse_font_variant_numeric_value(TokenStream<ComponentValue>& tokens)
+{
+    // 6.7 https://drafts.csswg.org/css-fonts/#propdef-font-variant-numeric
+    // normal | [ <numeric-figure-values> || <numeric-spacing-values> || <numeric-fraction-values> || ordinal || slashed-zero]
+    // <numeric-figure-values>       = [ lining-nums | oldstyle-nums ]
+    // <numeric-spacing-values>      = [ proportional-nums | tabular-nums ]
+    // <numeric-fraction-values>     = [ diagonal-fractions | stacked-fractions ]
+
+    StyleValueVector value_list;
+    bool has_figures = false;
+    bool has_spacing = false;
+    bool has_fractions = false;
+    bool has_ordinals = false;
+    bool has_slashed_zero = false;
+
+    // normal | ...
+    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) {
+        value_list.append(normal.release_nonnull());
+    } else {
+        while (tokens.has_next_token()) {
+            auto maybe_value = parse_keyword_value(tokens);
+            if (!maybe_value)
+                break;
+            auto value = maybe_value.release_nonnull();
+            switch (value->to_keyword()) {
+            // ... || ordinal
+            case Keyword::Ordinal:
+                if (has_ordinals)
+                    return nullptr;
+                has_ordinals = true;
+                break;
+            // ... || slashed-zero
+            case Keyword::SlashedZero:
+                if (has_slashed_zero)
+                    return nullptr;
+                has_slashed_zero = true;
+                break;
+            // <numeric-figure-values> = [ lining-nums | oldstyle-nums ]
+            case Keyword::LiningNums:
+            case Keyword::OldstyleNums:
+                if (has_figures)
+                    return nullptr;
+                has_figures = true;
+                break;
+            // <numeric-spacing-values> = [ proportional-nums | tabular-nums ]
+            case Keyword::ProportionalNums:
+            case Keyword::TabularNums:
+                if (has_spacing)
+                    return nullptr;
+                has_spacing = true;
+                break;
+            // <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions ]
+            case Keyword::DiagonalFractions:
+            case Keyword::StackedFractions:
+                if (has_fractions)
+                    return nullptr;
+                has_fractions = true;
+                break;
+            default:
+                return nullptr;
+            }
+            value_list.append(value);
+        }
+    }
+
+    if (value_list.is_empty())
+        return nullptr;
+
+    return StyleValueList::create(move(value_list), StyleValueList::Separator::Space);
+}
+
 Vector<ParsedFontFace::Source> Parser::parse_as_font_face_src()
 Vector<ParsedFontFace::Source> Parser::parse_as_font_face_src()
 {
 {
     return parse_font_face_src(m_token_stream);
     return parse_font_face_src(m_token_stream);
@@ -8046,6 +8499,30 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_css_value(Prope
         if (auto parsed_value = parse_font_variation_settings_value(tokens); parsed_value && !tokens.has_next_token())
         if (auto parsed_value = parse_font_variation_settings_value(tokens); parsed_value && !tokens.has_next_token())
             return parsed_value.release_nonnull();
             return parsed_value.release_nonnull();
         return ParseError::SyntaxError;
         return ParseError::SyntaxError;
+    case PropertyID::FontVariant:
+        if (auto parsed_value = parse_font_variant(tokens); parsed_value && !tokens.has_next_token())
+            return parsed_value.release_nonnull();
+        return ParseError::SyntaxError;
+    case PropertyID::FontVariantAlternates:
+        if (auto parsed_value = parse_font_variant_alternates_value(tokens); parsed_value && !tokens.has_next_token())
+            return parsed_value.release_nonnull();
+        return ParseError::SyntaxError;
+    case PropertyID::FontVariantCaps:
+        if (auto parsed_value = parse_font_variant_caps_value(tokens); parsed_value && !tokens.has_next_token())
+            return parsed_value.release_nonnull();
+        return ParseError::SyntaxError;
+    case PropertyID::FontVariantEastAsian:
+        if (auto parsed_value = parse_font_variant_east_asian_value(tokens); parsed_value && !tokens.has_next_token())
+            return parsed_value.release_nonnull();
+        return ParseError::SyntaxError;
+    case PropertyID::FontVariantLigatures:
+        if (auto parsed_value = parse_font_variant_ligatures_value(tokens); parsed_value && !tokens.has_next_token())
+            return parsed_value.release_nonnull();
+        return ParseError::SyntaxError;
+    case PropertyID::FontVariantNumeric:
+        if (auto parsed_value = parse_font_variant_numeric_value(tokens); parsed_value && !tokens.has_next_token())
+            return parsed_value.release_nonnull();
+        return ParseError::SyntaxError;
     case PropertyID::GridArea:
     case PropertyID::GridArea:
         if (auto parsed_value = parse_grid_area_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
         if (auto parsed_value = parse_grid_area_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
             return parsed_value.release_nonnull();
             return parsed_value.release_nonnull();

+ 7 - 0
Libraries/LibWeb/CSS/Parser/Parser.h

@@ -330,6 +330,13 @@ private:
     RefPtr<CSSStyleValue> parse_font_language_override_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_font_language_override_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_font_feature_settings_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_font_feature_settings_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_font_variation_settings_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_font_variation_settings_value(TokenStream<ComponentValue>&);
+    RefPtr<CSSStyleValue> parse_font_variant(TokenStream<ComponentValue>&);
+    RefPtr<CSSStyleValue> parse_font_variant_alternates_value(TokenStream<ComponentValue>&);
+    RefPtr<CSSStyleValue> parse_font_variant_caps_value(TokenStream<ComponentValue>&);
+    RefPtr<CSSStyleValue> parse_font_variant_east_asian_value(TokenStream<ComponentValue>&);
+    RefPtr<CSSStyleValue> parse_font_variant_emoji(TokenStream<ComponentValue>&);
+    RefPtr<CSSStyleValue> parse_font_variant_ligatures_value(TokenStream<ComponentValue>&);
+    RefPtr<CSSStyleValue> parse_font_variant_numeric_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_list_style_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_list_style_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_math_depth_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_math_depth_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_overflow_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_overflow_value(TokenStream<ComponentValue>&);