From 0e15561df08a5fe0d080cbd3a2dcb1b0c750a24b Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 3 Aug 2021 15:56:51 +0100 Subject: [PATCH] LibWeb: Implement and use ListStyleStyleValue Yes, the name is silly, but it's a StyleValue for list-style, so... yeah. :^) Since `list-style-type` and `list-style-image` can both have `none` as a value, and can appear in any order, we have to handle it separately, and then assign either or both of those to `none` depending on how many `none`s there are, and whether those sub-properties already have values. Added some extra test cases to lists.html to cover list-style-image and list-style-position parts of the list-style shorthand, and the `none` values. --- Base/res/html/misc/custom-list-item.png | Bin 0 -> 633 bytes Base/res/html/misc/custome-list-item-2.png | Bin 0 -> 231 bytes Base/res/html/misc/lists.html | 84 +++++++++++++ .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 114 ++++++++++++++++++ Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 1 + .../Libraries/LibWeb/CSS/StyleResolver.cpp | 106 ++-------------- Userland/Libraries/LibWeb/CSS/StyleValue.h | 39 ++++++ 7 files changed, 246 insertions(+), 98 deletions(-) create mode 100644 Base/res/html/misc/custom-list-item.png create mode 100644 Base/res/html/misc/custome-list-item-2.png diff --git a/Base/res/html/misc/custom-list-item.png b/Base/res/html/misc/custom-list-item.png new file mode 100644 index 0000000000000000000000000000000000000000..392d32007af49aff973d5821249a0fe3a256963c GIT binary patch literal 633 zcmV-<0*3vGP)EX>4Tx04R}tkv&MmKpe$iQ;Q-k4i*)0$WWauh>ALD6^c-y)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfa&%I3krMxx6k5c1aNLh~_a1le0HI!Hs@X9HsG4P@ z;xRFkT@^cD5kNme=)<(cOnokyOu%z|-NVP%yC~1{KKJJcDLIn?K9P8q>4rtTK|Hl- z>74h8!>lMN#OK7L23?T&k?XR{Z=4Gb3p_Jyq*L?6VPdh+#&R38qM;H`5=RtOqkJLb zvch?bvs$jQ<~{ifgE?(wnd>x%5XT~xkc0>sRg_SMg($5WDJD|1AM@}JI{qZNWO9|j z$gzM5R7j2={11M2Yvw1$-K1a)2)x+#$1u>j3p8rB{e5iPjT6BC3|wg~f29u0e3D*k zX^|tKXB)VeSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{002=*L_t&-(_>&D1iqM<{QqKN@*kbe2xBmai$hd0GBPmy zhX^sDNH8$KB@mK00DeiHGiMkXUcN*XWo2dH*}9dH5!uBsyLq;5WyIM!st7>U(FZR`1|gbS2B{$U&PIA+x?eP50>hrxz33Vp!nmaQ}CT zk3w$feHP)F{FfaVX3W$Vwq7k{X%is9%vcd)Tgq_VQqW}^t7_KlX&v9Z?re3Ib>Lda zH(}PdwX&WZS0z8}RZp{gTe~DWM4f7Wh|O literal 0 HcmV?d00001 diff --git a/Base/res/html/misc/lists.html b/Base/res/html/misc/lists.html index 1c72a0b3925..643ca41ed70 100644 --- a/Base/res/html/misc/lists.html +++ b/Base/res/html/misc/lists.html @@ -32,6 +32,18 @@
  • Another entry
  • +

    list-style: inside url(list-item.png)

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    + +

    list-style: outside url(list-item.png)

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    +

    ol

    default

      @@ -175,6 +187,78 @@
    1. Another Entry
    +

    list-style: outside lower-roman url(list-file.png)

    +
      +
    1. First
    2. +
    3. Second
    4. +
    5. Third
    6. +
    7. Another Entry
    8. +
    9. Another Entry
    10. +
    11. Another Entry
    12. +
    13. Another Entry
    14. +
    15. Another Entry
    16. +
    17. Another Entry
    18. +
    19. Another Entry
    20. +
    21. Another Entry
    22. +
    23. Another Entry
    24. +
    25. Another Entry
    26. +
    27. Another Entry
    28. +
    29. Another Entry
    30. +
    31. Another Entry
    32. +
    33. Another Entry
    34. +
    35. Another Entry
    36. +
    37. Another Entry
    38. +
    + +

    'none' values

    +

    list-style: none

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    + +

    list-style: none square

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    + +

    list-style: square none

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    + +

    list-style: url(list-item.png) none

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    + +

    list-style: none none

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    + +

    list-style: inside none none

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    + +

    list-style: square none none (error)

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    + +

    list-style: none none none (is an error)

    +
      +
    • Entry one
    • +
    • Another entry
    • +
    + diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index b222f024539..d933b8bcc51 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -2063,6 +2063,116 @@ RefPtr Parser::parse_font_value(ParsingContext const& context, Vecto return FontStyleValue::create(font_style.release_nonnull(), font_weight.release_nonnull(), font_size.release_nonnull(), line_height.release_nonnull(), move(font_families)); } +RefPtr Parser::parse_list_style_value(ParsingContext const& context, Vector const& component_values) +{ + auto is_list_style_image = [](StyleValue const& value) -> bool { + if (value.is_image()) + return true; + if (value.is_identifier() && value.to_identifier() == ValueID::None) + return true; + + return false; + }; + + auto is_list_style_position = [](StyleValue const& value) -> bool { + switch (value.to_identifier()) { + case ValueID::Inside: + case ValueID::Outside: + return true; + default: + return false; + } + }; + + auto is_list_style_type = [](StyleValue const& value) -> bool { + // FIXME: Handle strings and symbols("...") syntax + switch (value.to_identifier()) { + case CSS::ValueID::None: + case CSS::ValueID::Disc: + case CSS::ValueID::Circle: + case CSS::ValueID::Square: + case CSS::ValueID::Decimal: + case CSS::ValueID::DecimalLeadingZero: + case CSS::ValueID::LowerAlpha: + case CSS::ValueID::LowerLatin: + case CSS::ValueID::UpperAlpha: + case CSS::ValueID::UpperLatin: + case CSS::ValueID::UpperRoman: + case CSS::ValueID::LowerRoman: + return true; + default: + return false; + } + }; + + if (component_values.size() > 3) + return nullptr; + + RefPtr list_position; + RefPtr list_image; + RefPtr list_type; + int found_nones = 0; + + for (auto& part : component_values) { + auto value = parse_css_value(context, PropertyID::ListStyle, part); + if (!value) + return nullptr; + + if (value->to_identifier() == ValueID::None) { + found_nones++; + continue; + } + + if (is_list_style_position(*value)) { + if (list_position) + return nullptr; + list_position = value.release_nonnull(); + continue; + } + if (is_list_style_image(*value)) { + if (list_image) + return nullptr; + list_image = value.release_nonnull(); + continue; + } + if (is_list_style_type(*value)) { + if (list_type) + return nullptr; + list_type = value.release_nonnull(); + continue; + } + } + + if (found_nones > 2) + return nullptr; + + if (found_nones == 2) { + if (list_image || list_type) + return nullptr; + auto none = IdentifierStyleValue::create(ValueID::None); + list_image = none; + list_type = none; + + } else if (found_nones == 1) { + if (list_image && list_type) + return nullptr; + auto none = IdentifierStyleValue::create(ValueID::None); + if (!list_image) + list_image = none; + if (!list_type) + list_type = none; + } + + if (!list_position) + list_position = IdentifierStyleValue::create(ValueID::Outside); + if (!list_image) + list_image = IdentifierStyleValue::create(ValueID::None); + if (!list_type) + list_type = IdentifierStyleValue::create(ValueID::Disc); + + return ListStyleStyleValue::create(list_position.release_nonnull(), list_image.release_nonnull(), list_type.release_nonnull()); +} + RefPtr Parser::parse_as_css_value(PropertyID property_id) { auto component_values = parse_as_list_of_component_values(); @@ -2105,6 +2215,10 @@ RefPtr Parser::parse_css_value(PropertyID property_id, TokenStream parse_background_value(ParsingContext const&, Vector const&); static RefPtr parse_box_shadow_value(ParsingContext const&, Vector const&); static RefPtr parse_font_value(ParsingContext const&, Vector const&); + static RefPtr parse_list_style_value(ParsingContext const&, Vector const&); // calc() parsing, according to https://www.w3.org/TR/css-values-3/#calc-syntax static OwnPtr parse_calc_sum(ParsingContext const&, TokenStream&); diff --git a/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp index 89ae825ddb3..0e759ade69c 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp @@ -336,56 +336,6 @@ static inline bool is_line_width(StyleValue const& value) } } -static inline bool is_list_style_image(StyleValue const& value) -{ - if (value.is_builtin_or_dynamic()) - return true; - if (value.is_image()) - return true; - if (value.is_identifier() && value.to_identifier() == ValueID::None) - return true; - - return false; -} - -static inline bool is_list_style_position(StyleValue const& value) -{ - if (value.is_builtin_or_dynamic()) - return true; - - switch (value.to_identifier()) { - case ValueID::Inside: - case ValueID::Outside: - return true; - default: - return false; - } -} - -static inline bool is_list_style_type(StyleValue const& value) -{ - if (value.is_builtin_or_dynamic()) - return true; - // FIXME: Handle strings and symbols("...") syntax - switch (value.to_identifier()) { - case CSS::ValueID::None: - case CSS::ValueID::Disc: - case CSS::ValueID::Circle: - case CSS::ValueID::Square: - case CSS::ValueID::Decimal: - case CSS::ValueID::DecimalLeadingZero: - case CSS::ValueID::LowerAlpha: - case CSS::ValueID::LowerLatin: - case CSS::ValueID::UpperAlpha: - case CSS::ValueID::UpperLatin: - case CSS::ValueID::UpperRoman: - case CSS::ValueID::LowerRoman: - return true; - default: - return true; - } -} - static inline bool is_text_decoration_line(StyleValue const& value) { if (value.is_builtin_or_dynamic()) @@ -868,59 +818,19 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope } if (property_id == CSS::PropertyID::ListStyle) { - if (value.is_component_value_list()) { - auto parts = static_cast(value).values(); - - if (!parts.is_empty() && parts.size() <= 3) { - RefPtr position_value; - RefPtr image_value; - RefPtr type_value; - - // FIXME: `none` is ambiguous as it is a valid value for ListStyleImage and ListStyleType, - // so requires special handling. https://www.w3.org/TR/css-lists-3/#propdef-list-style - - for (auto& part : parts) { - auto value = Parser::parse_css_value(context, property_id, part); - if (!value) - return; - - if (is_list_style_position(*value)) { - if (position_value) - return; - position_value = move(value); - continue; - } - if (is_list_style_image(*value)) { - if (image_value) - return; - image_value = move(value); - continue; - } - if (is_list_style_type(*value)) { - if (type_value) - return; - type_value = move(value); - continue; - } - } - - if (position_value) - style.set_property(CSS::PropertyID::ListStylePosition, *position_value); - if (image_value) - style.set_property(CSS::PropertyID::ListStyleImage, *image_value); - if (type_value) - style.set_property(CSS::PropertyID::ListStyleType, *type_value); - } + if (value.is_list_style()) { + auto& list_style = static_cast(value); + style.set_property(CSS::PropertyID::ListStylePosition, list_style.position()); + style.set_property(CSS::PropertyID::ListStyleImage, list_style.image()); + style.set_property(CSS::PropertyID::ListStyleType, list_style.style_type()); return; } - - if (is_list_style_position(value)) + if (value.is_builtin()) { style.set_property(CSS::PropertyID::ListStylePosition, value); - else if (is_list_style_image(value)) style.set_property(CSS::PropertyID::ListStyleImage, value); - else if (is_list_style_type(value)) style.set_property(CSS::PropertyID::ListStyleType, value); - + return; + } return; } diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index fa0d859b867..4a19bf8a6b8 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -232,6 +232,7 @@ public: Background, BoxShadow, Font, + ListStyle, }; Type type() const { return m_type; } @@ -251,6 +252,7 @@ public: bool is_background() const { return type() == Type::Background; } bool is_box_shadow() const { return type() == Type::BoxShadow; } bool is_font() const { return type() == Type::Font; } + bool is_list_style() const { return type() == Type::ListStyle; } bool is_builtin() const { return is_inherit() || is_initial(); } @@ -717,6 +719,43 @@ private: // FIXME: Implement font-stretch and font-variant. }; +class ListStyleStyleValue final : public StyleValue { +public: + static NonnullRefPtr create( + NonnullRefPtr position, + NonnullRefPtr image, + NonnullRefPtr style_type) + { + return adopt_ref(*new ListStyleStyleValue(position, image, style_type)); + } + virtual ~ListStyleStyleValue() override { } + + NonnullRefPtr position() const { return m_position; } + NonnullRefPtr image() const { return m_image; } + NonnullRefPtr style_type() const { return m_style_type; } + + virtual String to_string() const override + { + return String::formatted("ListStyle position: {}, image: {}, style_type: {}", m_position->to_string(), m_image->to_string(), m_style_type->to_string()); + } + +private: + ListStyleStyleValue( + NonnullRefPtr position, + NonnullRefPtr image, + NonnullRefPtr style_type) + : StyleValue(Type::ListStyle) + , m_position(position) + , m_image(image) + , m_style_type(style_type) + { + } + + NonnullRefPtr m_position; + NonnullRefPtr m_image; + NonnullRefPtr m_style_type; +}; + class StyleValueList final : public StyleValue { public: static NonnullRefPtr create(NonnullRefPtrVector&& values) { return adopt_ref(*new StyleValueList(move(values))); }