From c0f9b11070b9dd8d67d196175d6eadf2763e2b1e Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Fri, 2 May 2025 13:55:58 +0100 Subject: [PATCH] LibWeb: Parse oblique `font-style` with an angle value --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/CSS/CSSStyleValue.cpp | 7 +++ Libraries/LibWeb/CSS/CSSStyleValue.h | 5 +++ Libraries/LibWeb/CSS/Enums.json | 7 +++ Libraries/LibWeb/CSS/Parser/Parser.h | 1 + .../LibWeb/CSS/Parser/PropertyParsing.cpp | 35 ++++++++++++++- Libraries/LibWeb/CSS/Properties.json | 6 +-- .../CSS/StyleValues/FontStyleStyleValue.cpp | 42 +++++++++++++++++ .../CSS/StyleValues/FontStyleStyleValue.h | 45 +++++++++++++++++++ .../CSS/StyleValues/ShorthandStyleValue.cpp | 5 ++- Libraries/LibWeb/Forward.h | 1 + .../css-fonts/parsing/font-style-computed.txt | 22 ++++----- .../css-fonts/parsing/font-style-valid.txt | 26 +++++------ 13 files changed, 172 insertions(+), 31 deletions(-) create mode 100644 Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.cpp create mode 100644 Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 9a67775d2fc..17174724da6 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -173,6 +173,7 @@ set(SOURCES CSS/StyleValues/EdgeStyleValue.cpp CSS/StyleValues/FilterValueListStyleValue.cpp CSS/StyleValues/FontSourceStyleValue.cpp + CSS/StyleValues/FontStyleStyleValue.cpp CSS/StyleValues/GridAutoFlowStyleValue.cpp CSS/StyleValues/GridTemplateAreaStyleValue.cpp CSS/StyleValues/GridTrackPlacementStyleValue.cpp diff --git a/Libraries/LibWeb/CSS/CSSStyleValue.cpp b/Libraries/LibWeb/CSS/CSSStyleValue.cpp index 03b2886070b..319018c38b5 100644 --- a/Libraries/LibWeb/CSS/CSSStyleValue.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleValue.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -203,6 +204,12 @@ FontSourceStyleValue const& CSSStyleValue::as_font_source() const return static_cast(*this); } +FontStyleStyleValue const& CSSStyleValue::as_font_style() const +{ + VERIFY(is_font_style()); + return static_cast(*this); +} + FrequencyStyleValue const& CSSStyleValue::as_frequency() const { VERIFY(is_frequency()); diff --git a/Libraries/LibWeb/CSS/CSSStyleValue.h b/Libraries/LibWeb/CSS/CSSStyleValue.h index 77b37b4f90c..b6090434e2c 100644 --- a/Libraries/LibWeb/CSS/CSSStyleValue.h +++ b/Libraries/LibWeb/CSS/CSSStyleValue.h @@ -107,6 +107,7 @@ public: FitContent, Flex, FontSource, + FontStyle, FontVariant, Frequency, GridAutoFlow, @@ -234,6 +235,10 @@ public: FontSourceStyleValue const& as_font_source() const; FontSourceStyleValue& as_font_source() { return const_cast(const_cast(*this).as_font_source()); } + bool is_font_style() const { return type() == Type::FontStyle; } + FontStyleStyleValue const& as_font_style() const; + FontStyleStyleValue& as_font_style() { return const_cast(const_cast(*this).as_font_style()); } + bool is_frequency() const { return type() == Type::Frequency; } FrequencyStyleValue const& as_frequency() const; FrequencyStyleValue& as_frequency() { return const_cast(const_cast(*this).as_frequency()); } diff --git a/Libraries/LibWeb/CSS/Enums.json b/Libraries/LibWeb/CSS/Enums.json index 18834c1d857..90114f90485 100644 --- a/Libraries/LibWeb/CSS/Enums.json +++ b/Libraries/LibWeb/CSS/Enums.json @@ -247,6 +247,13 @@ "fallback", "optional" ], + "font-style": [ + "normal", + "italic", + "left", + "right", + "oblique" + ], "font-variant-alternates": [ "normal", "historical-forms" diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 567c7ec9c97..c23905cfb59 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -394,6 +394,7 @@ private: RefPtr parse_font_family_value(TokenStream&); RefPtr parse_font_language_override_value(TokenStream&); RefPtr parse_font_feature_settings_value(TokenStream&); + RefPtr parse_font_style_value(TokenStream&); RefPtr parse_font_variation_settings_value(TokenStream&); RefPtr parse_font_variant(TokenStream&); RefPtr parse_font_variant_alternates_value(TokenStream&); diff --git a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp index ee26b884b35..fca8e79bd29 100644 --- a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -523,6 +524,10 @@ Parser::ParseErrorOr> Parser::parse_css_value if (auto parsed_value = parse_font_language_override_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); return ParseError::SyntaxError; + case PropertyID::FontStyle: + if (auto parsed_value = parse_font_style_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; case PropertyID::FontVariationSettings: if (auto parsed_value = parse_font_variation_settings_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); @@ -2361,7 +2366,7 @@ RefPtr Parser::parse_font_value(TokenStream } case PropertyID::FontStyle: { VERIFY(!font_style); - font_style = value.release_nonnull(); + font_style = FontStyleStyleValue::create(*keyword_to_font_style(value.release_nonnull()->to_keyword())); continue; } case PropertyID::FontVariant: { @@ -2597,6 +2602,34 @@ RefPtr Parser::parse_font_feature_settings_value(TokenStrea return StyleValueList::create(move(feature_tags), StyleValueList::Separator::Comma); } +RefPtr Parser::parse_font_style_value(TokenStream& tokens) +{ + // https://drafts.csswg.org/css-fonts/#font-style-prop + // normal | italic | left | right | oblique ? + auto transaction = tokens.begin_transaction(); + auto keyword_value = parse_css_value_for_property(PropertyID::FontStyle, tokens); + if (!keyword_value) + return nullptr; + auto font_style = keyword_to_font_style(keyword_value->to_keyword()); + VERIFY(font_style.has_value()); + if (tokens.has_next_token() && keyword_value->to_keyword() == Keyword::Oblique) { + if (auto angle_value = parse_angle_value(tokens)) { + if (angle_value->is_angle()) { + auto angle = angle_value->as_angle().angle(); + auto angle_degrees = angle.to_degrees(); + if (angle_degrees < -90 || angle_degrees > 90) + return nullptr; + } + + transaction.commit(); + return FontStyleStyleValue::create(*font_style, angle_value); + } + } + + transaction.commit(); + return FontStyleStyleValue::create(*font_style); +} + RefPtr Parser::parse_font_variation_settings_value(TokenStream& tokens) { // https://drafts.csswg.org/css-fonts/#propdef-font-variation-settings diff --git a/Libraries/LibWeb/CSS/Properties.json b/Libraries/LibWeb/CSS/Properties.json index d462dbc201a..ee187e40a8d 100644 --- a/Libraries/LibWeb/CSS/Properties.json +++ b/Libraries/LibWeb/CSS/Properties.json @@ -1440,10 +1440,8 @@ "animation-type": "custom", "inherited": true, "initial": "normal", - "valid-identifiers": [ - "italic", - "normal", - "oblique" + "valid-types": [ + "font-style" ] }, "font-variant": { diff --git a/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.cpp new file mode 100644 index 00000000000..f962615fe13 --- /dev/null +++ b/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "FontStyleStyleValue.h" +#include +#include +#include + +namespace Web::CSS { + +FontStyleStyleValue::FontStyleStyleValue(FontStyle font_style, ValueComparingRefPtr angle_value) + : StyleValueWithDefaultOperators(Type::FontStyle) + , m_font_style(font_style) + , m_angle_value(angle_value) +{ +} + +FontStyleStyleValue::~FontStyleStyleValue() = default; + +String FontStyleStyleValue::to_string(SerializationMode mode) const +{ + Optional angle_string; + if (m_angle_value) { + angle_string = m_angle_value->to_string(mode); + if (m_font_style == FontStyle::Oblique && angle_string == "0deg"sv) + return "normal"_string; + } + StringBuilder builder; + builder.append(CSS::to_string(m_font_style)); + // https://drafts.csswg.org/css-fonts/#valdef-font-style-oblique-angle--90deg-90deg + // The lack of an represents 14deg. (Note that a font might internally provide its own mapping for "oblique", but the mapping within the font is disregarded.) + static auto default_angle = Angle::make_degrees(14); + if (angle_string.has_value() && !(m_angle_value->is_angle() && m_angle_value->as_angle().angle() == default_angle)) + builder.appendff(" {}", angle_string); + + return MUST(builder.to_string()); +} + +} diff --git a/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.h new file mode 100644 index 00000000000..17da84852ab --- /dev/null +++ b/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::CSS { + +class FontStyleStyleValue final : public StyleValueWithDefaultOperators { +public: + static ValueComparingNonnullRefPtr create(FontStyle font_style, ValueComparingRefPtr angle_value = {}) + { + return adopt_ref(*new (nothrow) FontStyleStyleValue(font_style, angle_value)); + } + + virtual ~FontStyleStyleValue() override; + + FontStyle font_style() const { return m_font_style; } + ValueComparingRefPtr angle() const { return m_angle_value; } + + virtual String to_string(SerializationMode) const override; + + bool equals(CSSStyleValue const& other) const override + { + if (type() != other.type()) + return false; + auto const& other_font_style = other.as_font_style(); + return m_font_style == other_font_style.m_font_style && m_angle_value == other_font_style.m_angle_value; + } + + bool properties_equal(FontStyleStyleValue const& other) const { return m_font_style == other.m_font_style && m_angle_value == other.m_angle_value; } + +private: + FontStyleStyleValue(FontStyle, ValueComparingRefPtr angle_value); + + FontStyle m_font_style; + ValueComparingRefPtr m_angle_value; +}; + +} diff --git a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp index ffa1e55d55a..c30066d9947 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp @@ -233,8 +233,9 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const builder.append(' '); builder.append(string); }; - if (font_style->to_keyword() != Keyword::Normal && font_style->to_keyword() != Keyword::Initial) - append(font_style->to_string(mode)); + auto font_style_string = font_style->to_string(mode); + if (font_style_string != "normal"sv) + append(font_style_string); if (font_variant_string != "normal"sv && font_variant_string != "initial"sv) append(font_variant_string); if (font_weight->to_font_weight() != Gfx::FontWeight::Regular && font_weight->to_keyword() != Keyword::Initial) diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 52619913ad3..fe8ff3d4638 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -204,6 +204,7 @@ class FlexStyleValue; class FontFace; class FontFaceSet; class FontSourceStyleValue; +class FontStyleStyleValue; class Frequency; class FrequencyOrCalculated; class FrequencyPercentage; diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-style-computed.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-style-computed.txt index 663e8f53b92..42a9a4c6a4b 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-style-computed.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-style-computed.txt @@ -2,20 +2,20 @@ Harness status: OK Found 15 tests -3 Pass -12 Fail +12 Pass +3 Fail Pass Property font-style value 'normal' Pass Property font-style value 'italic' Pass Property font-style value 'oblique' -Fail Property font-style value 'oblique 0deg' -Fail Property font-style value 'oblique calc(10deg - 10deg)' -Fail Property font-style value 'oblique 10deg' -Fail Property font-style value 'oblique -10deg' -Fail Property font-style value 'oblique -90deg' -Fail Property font-style value 'oblique 90deg' -Fail Property font-style value 'oblique 10grad' -Fail Property font-style value 'oblique calc(90deg)' +Pass Property font-style value 'oblique 0deg' +Pass Property font-style value 'oblique calc(10deg - 10deg)' +Pass Property font-style value 'oblique 10deg' +Pass Property font-style value 'oblique -10deg' +Pass Property font-style value 'oblique -90deg' +Pass Property font-style value 'oblique 90deg' +Pass Property font-style value 'oblique 10grad' +Pass Property font-style value 'oblique calc(90deg)' Fail Property font-style value 'oblique calc(100deg)' Fail Property font-style value 'oblique calc(-100deg)' -Fail Property font-style value 'oblique calc(30deg * 2)' +Pass Property font-style value 'oblique calc(30deg * 2)' Fail Property font-style value 'oblique calc(30deg + (sign(20cqw - 10px) * 5deg))' \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-style-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-style-valid.txt index d5915bb7354..5a50c3b940f 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-style-valid.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-style-valid.txt @@ -2,20 +2,20 @@ Harness status: OK Found 15 tests -3 Pass -12 Fail +14 Pass +1 Fail Pass e.style['font-style'] = "normal" should set the property value Pass e.style['font-style'] = "italic" should set the property value Pass e.style['font-style'] = "oblique" should set the property value -Fail e.style['font-style'] = "oblique 0deg" should set the property value -Fail e.style['font-style'] = "oblique calc(10deg - 10deg)" should set the property value -Fail e.style['font-style'] = "oblique 10deg" should set the property value -Fail e.style['font-style'] = "oblique -10deg" should set the property value -Fail e.style['font-style'] = "oblique -90deg" should set the property value -Fail e.style['font-style'] = "oblique 90deg" should set the property value -Fail e.style['font-style'] = "oblique 10grad" should set the property value -Fail e.style['font-style'] = "oblique calc(90deg)" should set the property value -Fail e.style['font-style'] = "oblique calc(100deg)" should set the property value -Fail e.style['font-style'] = "oblique calc(-100deg)" should set the property value -Fail e.style['font-style'] = "oblique calc(30deg * 2)" should set the property value +Pass e.style['font-style'] = "oblique 0deg" should set the property value +Pass e.style['font-style'] = "oblique calc(10deg - 10deg)" should set the property value +Pass e.style['font-style'] = "oblique 10deg" should set the property value +Pass e.style['font-style'] = "oblique -10deg" should set the property value +Pass e.style['font-style'] = "oblique -90deg" should set the property value +Pass e.style['font-style'] = "oblique 90deg" should set the property value +Pass e.style['font-style'] = "oblique 10grad" should set the property value +Pass e.style['font-style'] = "oblique calc(90deg)" should set the property value +Pass e.style['font-style'] = "oblique calc(100deg)" should set the property value +Pass e.style['font-style'] = "oblique calc(-100deg)" should set the property value +Pass e.style['font-style'] = "oblique calc(30deg * 2)" should set the property value Fail e.style['font-style'] = "oblique calc(30deg + (sign(2cqw - 10px) * 5deg))" should set the property value \ No newline at end of file