| 
									
										
										
										
											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 { | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-08 10:11:51 +01:00
										 |  |  |  | CounterStyleValue::CounterStyleValue(CounterFunction function, FlyString counter_name, ValueComparingNonnullRefPtr<StyleValue 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
 | 
					
						
							| 
									
										
										
										
											2025-08-08 10:11:51 +01:00
										 |  |  |  | static String generate_a_counter_representation(StyleValue 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-07-18 17:08:27 -04:00
										 |  |  |  |                 case CounterStyleNameKeyword::LowerGreek: | 
					
						
							|  |  |  |  |                     return String::greek_letter_from(value); | 
					
						
							| 
									
										
										
										
											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".
 | 
					
						
							| 
									
										
										
										
											2025-08-08 10:11:51 +01:00
										 |  |  |  |     Vector<RefPtr<StyleValue 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; | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | } |