mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-11-04 07:10:57 +00:00 
			
		
		
		
	This reverts 0e3487b9ab.
Back when I made that change, I thought we could make our StyleValue
classes match the typed-om definitions directly. However, they have
different requirements. Typed-om types need to be mutable and GCed,
whereas StyleValues are immutable and ideally wouldn't require a JS VM.
While I was already making such a cataclysmic change, I've moved it into
the StyleValues directory, because it *not* being there has bothered me
for a long time. 😅
		
	
			
		
			
				
	
	
		
			177 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			177 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						||
 * Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
 | 
						||
 * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
 | 
						||
 * Copyright (c) 2024-2025, Sam Atkins <sam@ladybird.org>
 | 
						||
 *
 | 
						||
 * SPDX-License-Identifier: BSD-2-Clause
 | 
						||
 */
 | 
						||
 | 
						||
#include <LibWeb/CSS/CountersSet.h>
 | 
						||
#include <LibWeb/CSS/Enums.h>
 | 
						||
#include <LibWeb/CSS/Keyword.h>
 | 
						||
#include <LibWeb/CSS/Serialize.h>
 | 
						||
#include <LibWeb/CSS/StyleValues/CounterStyleValue.h>
 | 
						||
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
 | 
						||
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
 | 
						||
#include <LibWeb/DOM/Element.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/css-counter-styles-3/#generate-a-counter
 | 
						||
static String generate_a_counter_representation(StyleValue const& counter_style, i32 value)
 | 
						||
{
 | 
						||
    // 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();
 | 
						||
        auto keyword = keyword_from_string(counter_style_name);
 | 
						||
        if (keyword.has_value()) {
 | 
						||
            if (auto list_style_type = keyword_to_counter_style_name_keyword(*keyword); list_style_type.has_value()) {
 | 
						||
                switch (*list_style_type) {
 | 
						||
                case CounterStyleNameKeyword::Square:
 | 
						||
                    return "â–ª"_string;
 | 
						||
                case CounterStyleNameKeyword::Circle:
 | 
						||
                    return "â—¦"_string;
 | 
						||
                case CounterStyleNameKeyword::Disc:
 | 
						||
                    return "•"_string;
 | 
						||
                case CounterStyleNameKeyword::DisclosureClosed:
 | 
						||
                    return "â–¸"_string;
 | 
						||
                case CounterStyleNameKeyword::DisclosureOpen:
 | 
						||
                    return "â–¾"_string;
 | 
						||
                case CounterStyleNameKeyword::Decimal:
 | 
						||
                    return MUST(String::formatted("{}", value));
 | 
						||
                case CounterStyleNameKeyword::DecimalLeadingZero:
 | 
						||
                    // This is weird, but in accordance to spec.
 | 
						||
                    if (value < 10)
 | 
						||
                        return MUST(String::formatted("0{}", value));
 | 
						||
                    return MUST(String::formatted("{}", value));
 | 
						||
                case CounterStyleNameKeyword::LowerAlpha:
 | 
						||
                case CounterStyleNameKeyword::LowerLatin:
 | 
						||
                    return String::bijective_base_from(value - 1, String::Case::Lower);
 | 
						||
                case CounterStyleNameKeyword::UpperAlpha:
 | 
						||
                case CounterStyleNameKeyword::UpperLatin:
 | 
						||
                    return String::bijective_base_from(value - 1, String::Case::Upper);
 | 
						||
                case CounterStyleNameKeyword::LowerGreek:
 | 
						||
                    return String::greek_letter_from(value);
 | 
						||
                case CounterStyleNameKeyword::LowerRoman:
 | 
						||
                    return String::roman_number_from(value, String::Case::Lower);
 | 
						||
                case CounterStyleNameKeyword::UpperRoman:
 | 
						||
                    return String::roman_number_from(value, String::Case::Upper);
 | 
						||
                default:
 | 
						||
                    break;
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
    }
 | 
						||
    // FIXME: Handle `symbols()` function for counter_style.
 | 
						||
 | 
						||
    dbgln("FIXME: Unsupported counter style '{}'", counter_style.to_string(SerializationMode::Normal));
 | 
						||
    return MUST(String::formatted("{}", value));
 | 
						||
}
 | 
						||
 | 
						||
String CounterStyleValue::resolve(DOM::AbstractElement& element_reference) const
 | 
						||
{
 | 
						||
    // "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."
 | 
						||
    auto& counters_set = element_reference.ensure_counters_set();
 | 
						||
    if (!counters_set.last_counter_with_name(m_properties.counter_name).has_value())
 | 
						||
        counters_set.instantiate_a_counter(m_properties.counter_name, element_reference, false, 0);
 | 
						||
 | 
						||
    // 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();
 | 
						||
}
 | 
						||
 | 
						||
// https://drafts.csswg.org/cssom-1/#ref-for-typedef-counter
 | 
						||
String CounterStyleValue::to_string(SerializationMode mode) 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_string(mode) != "decimal"sv)
 | 
						||
        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, [mode](auto& builder, auto& item) {
 | 
						||
        builder.append(item->to_string(mode));
 | 
						||
    });
 | 
						||
 | 
						||
    // 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;
 | 
						||
}
 | 
						||
 | 
						||
}
 |