LibWeb: Implement CounterStyleValue

This is `counter(name, style?)` or `counters(name, link, style?)`. The
difference being, `counter()` matches only the nearest level (eg, "1"),
and `counters()` combines all the levels in the tree (eg, "3.4.1").
This commit is contained in:
Sam Atkins 2024-07-24 14:56:16 +01:00
parent 017d6c3314
commit 576a431408
Notes: github-actions[bot] 2024-07-26 10:05:34 +00:00
11 changed files with 274 additions and 3 deletions

View file

@ -22,9 +22,28 @@ bool is_animatable_property(JsonObject& properties, StringView property_name);
static bool type_name_is_enum(StringView type_name)
{
return !AK::first_is_one_of(type_name,
"angle"sv, "background-position"sv, "basic-shape"sv, "color"sv, "custom-ident"sv, "easing-function"sv, "flex"sv, "frequency"sv, "image"sv,
"integer"sv, "length"sv, "number"sv, "paint"sv, "percentage"sv, "position"sv, "ratio"sv, "rect"sv,
"resolution"sv, "string"sv, "time"sv, "url"sv);
"angle"sv,
"background-position"sv,
"basic-shape"sv,
"color"sv,
"counter"sv,
"custom-ident"sv,
"easing-function"sv,
"flex"sv,
"frequency"sv,
"image"sv,
"integer"sv,
"length"sv,
"number"sv,
"paint"sv,
"percentage"sv,
"position"sv,
"ratio"sv,
"rect"sv,
"resolution"sv,
"string"sv,
"time"sv,
"url"sv);
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
@ -183,6 +202,7 @@ enum class ValueType {
BackgroundPosition,
BasicShape,
Color,
Counter,
CustomIdent,
EasingFunction,
FilterValueList,
@ -732,6 +752,8 @@ bool property_accepts_type(PropertyID property_id, ValueType value_type)
property_generator.appendln(" case ValueType::BasicShape:");
} else if (type_name == "color") {
property_generator.appendln(" case ValueType::Color:");
} else if (type_name == "counter") {
property_generator.appendln(" case ValueType::Counter:");
} else if (type_name == "custom-ident") {
property_generator.appendln(" case ValueType::CustomIdent:");
} else if (type_name == "easing-function") {

View file

@ -111,6 +111,7 @@ set(SOURCES
CSS/StyleValues/ConicGradientStyleValue.cpp
CSS/StyleValues/ContentStyleValue.cpp
CSS/StyleValues/CounterDefinitionsStyleValue.cpp
CSS/StyleValues/CounterStyleValue.cpp
CSS/StyleValues/DisplayStyleValue.cpp
CSS/StyleValues/EasingStyleValue.cpp
CSS/StyleValues/EdgeStyleValue.cpp

View file

@ -36,6 +36,7 @@ Optional<CSSNumericType::BaseType> CSSNumericType::base_type_from_value_type(Val
case ValueType::BackgroundPosition:
case ValueType::BasicShape:
case ValueType::Color:
case ValueType::Counter:
case ValueType::CustomIdent:
case ValueType::EasingFunction:
case ValueType::FilterValueList:

View file

@ -45,6 +45,7 @@
#include <LibWeb/CSS/StyleValues/ColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
#include <LibWeb/CSS/StyleValues/CounterStyleValue.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
@ -2892,6 +2893,118 @@ RefPtr<StyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tokens
return nullptr;
}
// https://drafts.csswg.org/css-lists-3/#counter-functions
RefPtr<StyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& tokens)
{
auto parse_counter_name = [](TokenStream<ComponentValue>& tokens) -> Optional<FlyString> {
// https://drafts.csswg.org/css-lists-3/#typedef-counter-name
// Counters are referred to in CSS syntax using the <counter-name> type, which represents
// their name as a <custom-ident>. A <counter-name> name cannot match the keyword none;
// such an identifier is invalid as a <counter-name>.
auto transaction = tokens.begin_transaction();
tokens.skip_whitespace();
auto& token = tokens.next_token();
if (!token.is(Token::Type::Ident) || token.token().ident() == "none"sv)
return {};
tokens.skip_whitespace();
if (tokens.has_next_token())
return {};
transaction.commit();
return token.token().ident();
};
auto parse_counter_style = [](TokenStream<ComponentValue>& tokens) -> RefPtr<StyleValue> {
// https://drafts.csswg.org/css-counter-styles-3/#typedef-counter-style
// <counter-style> = <counter-style-name> | <symbols()>
// For now we just support <counter-style-name>, found here:
// https://drafts.csswg.org/css-counter-styles-3/#typedef-counter-style-name
// <counter-style-name> is a <custom-ident> that is not an ASCII case-insensitive match for none.
auto transaction = tokens.begin_transaction();
tokens.skip_whitespace();
auto& token = tokens.next_token();
if (!token.is(Token::Type::Ident) || token.token().ident() == "none"sv)
return {};
tokens.skip_whitespace();
if (tokens.has_next_token())
return {};
transaction.commit();
return CustomIdentStyleValue::create(token.token().ident());
};
auto transaction = tokens.begin_transaction();
auto token = tokens.next_token();
if (token.is_function("counter"sv)) {
// counter() = counter( <counter-name>, <counter-style>? )
auto& function = token.function();
TokenStream function_tokens { function.values() };
auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens);
if (function_values.is_empty() || function_values.size() > 2)
return nullptr;
TokenStream name_tokens { function_values[0] };
auto counter_name = parse_counter_name(name_tokens);
if (!counter_name.has_value())
return nullptr;
RefPtr<StyleValue> counter_style;
if (function_values.size() > 1) {
TokenStream counter_style_tokens { function_values[1] };
counter_style = parse_counter_style(counter_style_tokens);
if (!counter_style)
return nullptr;
} else {
// In both cases, if the <counter-style> argument is omitted it defaults to `decimal`.
counter_style = CustomIdentStyleValue::create("decimal"_fly_string);
}
transaction.commit();
return CounterStyleValue::create_counter(counter_name.release_value(), counter_style.release_nonnull());
}
if (token.is_function("counters"sv)) {
// counters() = counters( <counter-name>, <string>, <counter-style>? )
auto& function = token.function();
TokenStream function_tokens { function.values() };
auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens);
if (function_values.is_empty() || function_values.size() > 3)
return nullptr;
TokenStream name_tokens { function_values[0] };
auto counter_name = parse_counter_name(name_tokens);
if (!counter_name.has_value())
return nullptr;
TokenStream string_tokens { function_values[1] };
string_tokens.skip_whitespace();
RefPtr<StyleValue> join_string = parse_string_value(string_tokens);
string_tokens.skip_whitespace();
if (!join_string || string_tokens.has_next_token())
return nullptr;
RefPtr<StyleValue> counter_style;
if (function_values.size() > 2) {
TokenStream counter_style_tokens { function_values[2] };
counter_style = parse_counter_style(counter_style_tokens);
if (!counter_style)
return nullptr;
} else {
// In both cases, if the <counter-style> argument is omitted it defaults to `decimal`.
counter_style = CustomIdentStyleValue::create("decimal"_fly_string);
}
transaction.commit();
return CounterStyleValue::create_counters(counter_name.release_value(), join_string->as_string().string_value(), counter_style.release_nonnull());
}
return nullptr;
}
RefPtr<StyleValue> Parser::parse_counter_definitions_value(TokenStream<ComponentValue>& tokens, AllowReversed allow_reversed, i32 default_value_if_not_reversed)
{
// If AllowReversed is Yes, parses:
@ -7141,6 +7254,11 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
return PropertyAndValue { *property, maybe_color };
}
if (auto property = any_property_accepts_type(property_ids, ValueType::Counter); property.has_value()) {
if (auto maybe_counter = parse_counter_value(tokens))
return PropertyAndValue { *property, maybe_counter };
}
if (auto property = any_property_accepts_type(property_ids, ValueType::Image); property.has_value()) {
if (auto maybe_image = parse_image_value(tokens))
return PropertyAndValue { *property, maybe_image };

View file

@ -288,6 +288,7 @@ private:
RefPtr<StyleValue> parse_number_or_percentage_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_identifier_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_color_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_counter_value(TokenStream<ComponentValue>&);
enum class AllowReversed {
No,
Yes,

View file

@ -966,6 +966,7 @@
"initial": "normal",
"__comment": "FIXME: This accepts a whole lot of other types and identifiers!",
"valid-types": [
"counter",
"string"
],
"valid-identifiers": [

View file

@ -21,6 +21,7 @@
#include <LibWeb/CSS/StyleValues/ConicGradientStyleValue.h>
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
#include <LibWeb/CSS/StyleValues/CounterStyleValue.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>

View file

@ -92,6 +92,7 @@ using StyleValueVector = Vector<ValueComparingNonnullRefPtr<StyleValue const>>;
__ENUMERATE_STYLE_VALUE_TYPE(Color, color) \
__ENUMERATE_STYLE_VALUE_TYPE(ConicGradient, conic_gradient) \
__ENUMERATE_STYLE_VALUE_TYPE(Content, content) \
__ENUMERATE_STYLE_VALUE_TYPE(Counter, counter) \
__ENUMERATE_STYLE_VALUE_TYPE(CounterDefinitions, counter_definitions) \
__ENUMERATE_STYLE_VALUE_TYPE(CustomIdent, custom_ident) \
__ENUMERATE_STYLE_VALUE_TYPE(Display, display) \

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CounterStyleValue.h"
#include <LibWeb/CSS/Serialize.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
namespace Web::CSS {
CounterStyleValue::CounterStyleValue(CounterFunction function, FlyString counter_name, ValueComparingNonnullRefPtr<StyleValue const> counter_style, FlyString join_string)
: StyleValueWithDefaultOperators(Type::Counter)
, m_properties {
.function = function,
.counter_name = move(counter_name),
.counter_style = move(counter_style),
.join_string = move(join_string)
}
{
}
CounterStyleValue::~CounterStyleValue() = default;
// https://drafts.csswg.org/cssom-1/#ref-for-typedef-counter
String CounterStyleValue::to_string() const
{
// The return value of the following algorithm:
// 1. Let s be the empty string.
StringBuilder s;
// 2. If <counter> has three CSS component values append the string "counters(" to s.
if (m_properties.function == CounterFunction::Counters)
s.append("counters("sv);
// 3. If <counter> has two CSS component values append the string "counter(" to s.
else if (m_properties.function == CounterFunction::Counter)
s.append("counter("sv);
// 4. Let list be a list of CSS component values belonging to <counter>,
// omitting the last CSS component value if it is "decimal".
Vector<RefPtr<StyleValue const>> list;
list.append(CustomIdentStyleValue::create(m_properties.counter_name));
if (m_properties.function == CounterFunction::Counters)
list.append(StringStyleValue::create(m_properties.join_string.to_string()));
if (m_properties.counter_style->to_identifier() != ValueID::Decimal)
list.append(m_properties.counter_style);
// 5. Let each item in list be the result of invoking serialize a CSS component value on that item.
// 6. Append the result of invoking serialize a comma-separated list on list to s.
serialize_a_comma_separated_list(s, list, [](auto& builder, auto& item) {
builder.append(item->to_string());
});
// 7. Append ")" (U+0029) to s.
s.append(")"sv);
// 8. Return s.
return MUST(s.to_string());
}
bool CounterStyleValue::properties_equal(CounterStyleValue const& other) const
{
return m_properties == other.m_properties;
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <LibWeb/CSS/StyleValue.h>
namespace Web::CSS {
// https://drafts.csswg.org/css-lists-3/#counter-functions
class CounterStyleValue : public StyleValueWithDefaultOperators<CounterStyleValue> {
public:
enum class CounterFunction {
Counter,
Counters,
};
static ValueComparingNonnullRefPtr<CounterStyleValue> create_counter(FlyString counter_name, ValueComparingNonnullRefPtr<StyleValue const> counter_style)
{
return adopt_ref(*new (nothrow) CounterStyleValue(CounterFunction::Counter, move(counter_name), move(counter_style), {}));
}
static ValueComparingNonnullRefPtr<CounterStyleValue> create_counters(FlyString counter_name, FlyString join_string, ValueComparingNonnullRefPtr<StyleValue const> counter_style)
{
return adopt_ref(*new (nothrow) CounterStyleValue(CounterFunction::Counters, move(counter_name), move(counter_style), move(join_string)));
}
virtual ~CounterStyleValue() override;
CounterFunction function_type() const { return m_properties.function; }
auto counter_name() const { return m_properties.counter_name; }
auto counter_style() const { return m_properties.counter_style; }
auto join_string() const { return m_properties.join_string; }
String resolve(DOM::Element&) const;
virtual String to_string() const override;
bool properties_equal(CounterStyleValue const& other) const;
private:
explicit CounterStyleValue(CounterFunction, FlyString counter_name, ValueComparingNonnullRefPtr<StyleValue const> counter_style, FlyString join_string);
struct Properties {
CounterFunction function;
FlyString counter_name;
ValueComparingNonnullRefPtr<StyleValue const> counter_style;
FlyString join_string;
bool operator==(Properties const&) const = default;
} m_properties;
};
}

View file

@ -120,6 +120,7 @@ class Clip;
class ColorStyleValue;
class ConicGradientStyleValue;
class ContentStyleValue;
class CounterStyleValue;
class CounterDefinitionsStyleValue;
class CustomIdentStyleValue;
class Display;