CounterStyleValue.cpp 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. * Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
  4. * Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include "CounterStyleValue.h"
  9. #include <LibWeb/CSS/Enums.h>
  10. #include <LibWeb/CSS/Keyword.h>
  11. #include <LibWeb/CSS/Serialize.h>
  12. #include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
  13. #include <LibWeb/CSS/StyleValues/StringStyleValue.h>
  14. #include <LibWeb/DOM/Element.h>
  15. namespace Web::CSS {
  16. CounterStyleValue::CounterStyleValue(CounterFunction function, FlyString counter_name, ValueComparingNonnullRefPtr<CSSStyleValue const> counter_style, FlyString join_string)
  17. : StyleValueWithDefaultOperators(Type::Counter)
  18. , m_properties {
  19. .function = function,
  20. .counter_name = move(counter_name),
  21. .counter_style = move(counter_style),
  22. .join_string = move(join_string)
  23. }
  24. {
  25. }
  26. CounterStyleValue::~CounterStyleValue() = default;
  27. // https://drafts.csswg.org/css-counter-styles-3/#generate-a-counter
  28. static String generate_a_counter_representation(CSSStyleValue const& counter_style, i32 value)
  29. {
  30. // When asked to generate a counter representation using a particular counter style for a particular
  31. // counter value, follow these steps:
  32. // TODO: 1. If the counter style is unknown, exit this algorithm and instead generate a counter representation
  33. // using the decimal style and the same counter value.
  34. // TODO: 2. If the counter value is outside the range of the counter style, exit this algorithm and instead
  35. // generate a counter representation using the counter style’s fallback style and the same counter value.
  36. // TODO: 3. Using the counter value and the counter algorithm for the counter style, generate an initial
  37. // representation for the counter value.
  38. // If the counter value is negative and the counter style uses a negative sign, instead generate an
  39. // initial representation using the absolute value of the counter value.
  40. // TODO: 4. Prepend symbols to the representation as specified in the pad descriptor.
  41. // TODO: 5. If the counter value is negative and the counter style uses a negative sign, wrap the representation
  42. // in the counter style’s negative sign as specified in the negative descriptor.
  43. // TODO: 6. Return the representation.
  44. // FIXME: Below is an ad-hoc implementation until we support @counter-style.
  45. // It's based largely on the ListItemMarkerBox code, with minimal adjustments.
  46. if (counter_style.is_custom_ident()) {
  47. auto counter_style_name = counter_style.as_custom_ident().custom_ident();
  48. auto keyword = keyword_from_string(counter_style_name);
  49. if (keyword.has_value()) {
  50. auto list_style_type = keyword_to_list_style_type(*keyword);
  51. if (list_style_type.has_value()) {
  52. switch (*list_style_type) {
  53. case ListStyleType::Square:
  54. return "▪"_string;
  55. case ListStyleType::Circle:
  56. return "◦"_string;
  57. case ListStyleType::Disc:
  58. return "•"_string;
  59. case ListStyleType::DisclosureClosed:
  60. return "▸"_string;
  61. case ListStyleType::DisclosureOpen:
  62. return "▾"_string;
  63. case ListStyleType::Decimal:
  64. return MUST(String::formatted("{}", value));
  65. case ListStyleType::DecimalLeadingZero:
  66. // This is weird, but in accordance to spec.
  67. if (value < 10)
  68. return MUST(String::formatted("0{}", value));
  69. return MUST(String::formatted("{}", value));
  70. case ListStyleType::LowerAlpha:
  71. case ListStyleType::LowerLatin:
  72. return MUST(String::from_byte_string(ByteString::bijective_base_from(value - 1).to_lowercase()));
  73. case ListStyleType::UpperAlpha:
  74. case ListStyleType::UpperLatin:
  75. return MUST(String::from_byte_string(ByteString::bijective_base_from(value - 1)));
  76. case ListStyleType::LowerRoman:
  77. return MUST(String::from_byte_string(ByteString::roman_number_from(value).to_lowercase()));
  78. case ListStyleType::UpperRoman:
  79. return MUST(String::from_byte_string(ByteString::roman_number_from(value)));
  80. default:
  81. break;
  82. }
  83. }
  84. }
  85. }
  86. // FIXME: Handle `symbols()` function for counter_style.
  87. dbgln("FIXME: Unsupported counter style '{}'", counter_style.to_string());
  88. return MUST(String::formatted("{}", value));
  89. }
  90. String CounterStyleValue::resolve(DOM::Element& element) const
  91. {
  92. // "If no counter named <counter-name> exists on an element where counter() or counters() is used,
  93. // one is first instantiated with a starting value of 0."
  94. auto& counters_set = element.ensure_counters_set();
  95. if (!counters_set.last_counter_with_name(m_properties.counter_name).has_value())
  96. counters_set.instantiate_a_counter(m_properties.counter_name, element.unique_id(), false, 0);
  97. // counter( <counter-name>, <counter-style>? )
  98. // "Represents the value of the innermost counter in the element’s CSS counters set named <counter-name>
  99. // using the counter style named <counter-style>."
  100. if (m_properties.function == CounterFunction::Counter) {
  101. // NOTE: This should always be present because of the handling of a missing counter above.
  102. auto& counter = counters_set.last_counter_with_name(m_properties.counter_name).value();
  103. return generate_a_counter_representation(m_properties.counter_style, counter.value.value_or(0).value());
  104. }
  105. // counters( <counter-name>, <string>, <counter-style>? )
  106. // "Represents the values of all the counters in the element’s CSS counters set named <counter-name>
  107. // using the counter style named <counter-style>, sorted in outermost-first to innermost-last order
  108. // and joined by the specified <string>."
  109. // NOTE: The way counters sets are inherited, this should be the order they appear in the counters set.
  110. StringBuilder stb;
  111. for (auto const& counter : counters_set.counters()) {
  112. if (counter.name != m_properties.counter_name)
  113. continue;
  114. auto counter_string = generate_a_counter_representation(m_properties.counter_style, counter.value.value_or(0).value());
  115. if (!stb.is_empty())
  116. stb.append(m_properties.join_string);
  117. stb.append(counter_string);
  118. }
  119. return stb.to_string_without_validation();
  120. }
  121. // https://drafts.csswg.org/cssom-1/#ref-for-typedef-counter
  122. String CounterStyleValue::to_string() const
  123. {
  124. // The return value of the following algorithm:
  125. // 1. Let s be the empty string.
  126. StringBuilder s;
  127. // 2. If <counter> has three CSS component values append the string "counters(" to s.
  128. if (m_properties.function == CounterFunction::Counters)
  129. s.append("counters("sv);
  130. // 3. If <counter> has two CSS component values append the string "counter(" to s.
  131. else if (m_properties.function == CounterFunction::Counter)
  132. s.append("counter("sv);
  133. // 4. Let list be a list of CSS component values belonging to <counter>,
  134. // omitting the last CSS component value if it is "decimal".
  135. Vector<RefPtr<CSSStyleValue const>> list;
  136. list.append(CustomIdentStyleValue::create(m_properties.counter_name));
  137. if (m_properties.function == CounterFunction::Counters)
  138. list.append(StringStyleValue::create(m_properties.join_string.to_string()));
  139. if (m_properties.counter_style->to_keyword() != Keyword::Decimal)
  140. list.append(m_properties.counter_style);
  141. // 5. Let each item in list be the result of invoking serialize a CSS component value on that item.
  142. // 6. Append the result of invoking serialize a comma-separated list on list to s.
  143. serialize_a_comma_separated_list(s, list, [](auto& builder, auto& item) {
  144. builder.append(item->to_string());
  145. });
  146. // 7. Append ")" (U+0029) to s.
  147. s.append(")"sv);
  148. // 8. Return s.
  149. return MUST(s.to_string());
  150. }
  151. bool CounterStyleValue::properties_equal(CounterStyleValue const& other) const
  152. {
  153. return m_properties == other.m_properties;
  154. }
  155. }