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
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

@ -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 };