2024-07-24 14:56:16 +01:00
|
|
|
|
/*
|
2024-10-04 13:19:50 +02:00
|
|
|
|
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
2024-07-18 20:29:02 +01:00
|
|
|
|
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
2025-06-17 16:33:23 +01:00
|
|
|
|
* Copyright (c) 2024-2025, Sam Atkins <sam@ladybird.org>
|
2024-07-24 14:56:16 +01:00
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2025-06-17 16:33:23 +01:00
|
|
|
|
#include <LibWeb/CSS/CountersSet.h>
|
2024-07-18 20:29:02 +01:00
|
|
|
|
#include <LibWeb/CSS/Enums.h>
|
2024-08-14 14:06:03 +01:00
|
|
|
|
#include <LibWeb/CSS/Keyword.h>
|
2024-07-24 14:56:16 +01:00
|
|
|
|
#include <LibWeb/CSS/Serialize.h>
|
2025-06-17 16:33:23 +01:00
|
|
|
|
#include <LibWeb/CSS/StyleValues/CounterStyleValue.h>
|
2024-07-24 14:56:16 +01:00
|
|
|
|
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
|
|
|
|
|
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
|
2024-07-18 20:29:02 +01:00
|
|
|
|
#include <LibWeb/DOM/Element.h>
|
2024-07-24 14:56:16 +01:00
|
|
|
|
|
|
|
|
|
namespace Web::CSS {
|
|
|
|
|
|
2024-08-14 11:10:54 +01:00
|
|
|
|
CounterStyleValue::CounterStyleValue(CounterFunction function, FlyString counter_name, ValueComparingNonnullRefPtr<CSSStyleValue const> counter_style, FlyString join_string)
|
2024-07-24 14:56:16 +01:00
|
|
|
|
: 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;
|
|
|
|
|
|
2024-07-18 20:29:02 +01:00
|
|
|
|
// https://drafts.csswg.org/css-counter-styles-3/#generate-a-counter
|
2024-08-14 11:10:54 +01:00
|
|
|
|
static String generate_a_counter_representation(CSSStyleValue const& counter_style, i32 value)
|
2024-07-18 20:29:02 +01:00
|
|
|
|
{
|
|
|
|
|
// When asked to generate a counter representation using a particular counter style for a particular
|
|
|
|
|
// counter value, follow these steps:
|
|
|
|
|
// TODO: 1. If the counter style is unknown, exit this algorithm and instead generate a counter representation
|
|
|
|
|
// using the decimal style and the same counter value.
|
|
|
|
|
// TODO: 2. If the counter value is outside the range of the counter style, exit this algorithm and instead
|
|
|
|
|
// generate a counter representation using the counter style’s fallback style and the same counter value.
|
|
|
|
|
// TODO: 3. Using the counter value and the counter algorithm for the counter style, generate an initial
|
|
|
|
|
// representation for the counter value.
|
|
|
|
|
// If the counter value is negative and the counter style uses a negative sign, instead generate an
|
|
|
|
|
// initial representation using the absolute value of the counter value.
|
|
|
|
|
// TODO: 4. Prepend symbols to the representation as specified in the pad descriptor.
|
|
|
|
|
// TODO: 5. If the counter value is negative and the counter style uses a negative sign, wrap the representation
|
|
|
|
|
// in the counter style’s negative sign as specified in the negative descriptor.
|
|
|
|
|
// TODO: 6. Return the representation.
|
|
|
|
|
|
|
|
|
|
// FIXME: Below is an ad-hoc implementation until we support @counter-style.
|
|
|
|
|
// It's based largely on the ListItemMarkerBox code, with minimal adjustments.
|
|
|
|
|
if (counter_style.is_custom_ident()) {
|
|
|
|
|
auto counter_style_name = counter_style.as_custom_ident().custom_ident();
|
2024-08-14 14:06:03 +01:00
|
|
|
|
auto keyword = keyword_from_string(counter_style_name);
|
|
|
|
|
if (keyword.has_value()) {
|
2025-02-10 12:48:40 +00:00
|
|
|
|
if (auto list_style_type = keyword_to_counter_style_name_keyword(*keyword); list_style_type.has_value()) {
|
2024-07-18 20:29:02 +01:00
|
|
|
|
switch (*list_style_type) {
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::Square:
|
2024-07-18 20:29:02 +01:00
|
|
|
|
return "â–ª"_string;
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::Circle:
|
2024-07-18 20:29:02 +01:00
|
|
|
|
return "â—¦"_string;
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::Disc:
|
2024-07-18 20:29:02 +01:00
|
|
|
|
return "•"_string;
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::DisclosureClosed:
|
2024-07-18 20:29:02 +01:00
|
|
|
|
return "â–¸"_string;
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::DisclosureOpen:
|
2024-07-18 20:29:02 +01:00
|
|
|
|
return "â–¾"_string;
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::Decimal:
|
2024-07-18 20:29:02 +01:00
|
|
|
|
return MUST(String::formatted("{}", value));
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::DecimalLeadingZero:
|
2024-07-18 20:29:02 +01:00
|
|
|
|
// This is weird, but in accordance to spec.
|
|
|
|
|
if (value < 10)
|
|
|
|
|
return MUST(String::formatted("0{}", value));
|
|
|
|
|
return MUST(String::formatted("{}", value));
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::LowerAlpha:
|
|
|
|
|
case CounterStyleNameKeyword::LowerLatin:
|
2025-02-10 11:48:12 +00:00
|
|
|
|
return String::bijective_base_from(value - 1, String::Case::Lower);
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::UpperAlpha:
|
|
|
|
|
case CounterStyleNameKeyword::UpperLatin:
|
2025-02-10 11:48:12 +00:00
|
|
|
|
return String::bijective_base_from(value - 1, String::Case::Upper);
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::LowerRoman:
|
2025-02-10 11:48:12 +00:00
|
|
|
|
return String::roman_number_from(value, String::Case::Lower);
|
2025-02-10 12:48:40 +00:00
|
|
|
|
case CounterStyleNameKeyword::UpperRoman:
|
2025-02-10 11:48:12 +00:00
|
|
|
|
return String::roman_number_from(value, String::Case::Upper);
|
2024-07-18 20:29:02 +01:00
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// FIXME: Handle `symbols()` function for counter_style.
|
|
|
|
|
|
2025-05-16 19:20:24 +01:00
|
|
|
|
dbgln("FIXME: Unsupported counter style '{}'", counter_style.to_string(SerializationMode::Normal));
|
2024-07-18 20:29:02 +01:00
|
|
|
|
return MUST(String::formatted("{}", value));
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-17 16:33:23 +01:00
|
|
|
|
String CounterStyleValue::resolve(DOM::AbstractElement& element_reference) const
|
2024-07-18 20:29:02 +01:00
|
|
|
|
{
|
|
|
|
|
// "If no counter named <counter-name> exists on an element where counter() or counters() is used,
|
|
|
|
|
// one is first instantiated with a starting value of 0."
|
2025-06-17 16:33:23 +01:00
|
|
|
|
auto& counters_set = element_reference.ensure_counters_set();
|
2024-07-18 20:29:02 +01:00
|
|
|
|
if (!counters_set.last_counter_with_name(m_properties.counter_name).has_value())
|
2025-06-17 16:33:23 +01:00
|
|
|
|
counters_set.instantiate_a_counter(m_properties.counter_name, element_reference, false, 0);
|
2024-07-18 20:29:02 +01:00
|
|
|
|
|
|
|
|
|
// counter( <counter-name>, <counter-style>? )
|
|
|
|
|
// "Represents the value of the innermost counter in the element’s CSS counters set named <counter-name>
|
|
|
|
|
// using the counter style named <counter-style>."
|
|
|
|
|
if (m_properties.function == CounterFunction::Counter) {
|
|
|
|
|
// NOTE: This should always be present because of the handling of a missing counter above.
|
|
|
|
|
auto& counter = counters_set.last_counter_with_name(m_properties.counter_name).value();
|
|
|
|
|
return generate_a_counter_representation(m_properties.counter_style, counter.value.value_or(0).value());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// counters( <counter-name>, <string>, <counter-style>? )
|
|
|
|
|
// "Represents the values of all the counters in the element’s CSS counters set named <counter-name>
|
|
|
|
|
// using the counter style named <counter-style>, sorted in outermost-first to innermost-last order
|
|
|
|
|
// and joined by the specified <string>."
|
|
|
|
|
// NOTE: The way counters sets are inherited, this should be the order they appear in the counters set.
|
|
|
|
|
StringBuilder stb;
|
|
|
|
|
for (auto const& counter : counters_set.counters()) {
|
|
|
|
|
if (counter.name != m_properties.counter_name)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
auto counter_string = generate_a_counter_representation(m_properties.counter_style, counter.value.value_or(0).value());
|
|
|
|
|
if (!stb.is_empty())
|
|
|
|
|
stb.append(m_properties.join_string);
|
|
|
|
|
stb.append(counter_string);
|
|
|
|
|
}
|
|
|
|
|
return stb.to_string_without_validation();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-24 14:56:16 +01:00
|
|
|
|
// https://drafts.csswg.org/cssom-1/#ref-for-typedef-counter
|
2024-12-07 00:59:49 +01:00
|
|
|
|
String CounterStyleValue::to_string(SerializationMode mode) const
|
2024-07-24 14:56:16 +01:00
|
|
|
|
{
|
|
|
|
|
// 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".
|
2024-08-14 11:10:54 +01:00
|
|
|
|
Vector<RefPtr<CSSStyleValue const>> list;
|
2024-07-24 14:56:16 +01:00
|
|
|
|
list.append(CustomIdentStyleValue::create(m_properties.counter_name));
|
|
|
|
|
if (m_properties.function == CounterFunction::Counters)
|
|
|
|
|
list.append(StringStyleValue::create(m_properties.join_string.to_string()));
|
2024-12-07 00:59:49 +01:00
|
|
|
|
if (m_properties.counter_style->to_string(mode) != "decimal"sv)
|
2024-07-24 14:56:16 +01:00
|
|
|
|
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.
|
2024-12-07 00:59:49 +01:00
|
|
|
|
serialize_a_comma_separated_list(s, list, [mode](auto& builder, auto& item) {
|
|
|
|
|
builder.append(item->to_string(mode));
|
2024-07-24 14:56:16 +01:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|