ladybird/Libraries/LibWeb/CSS/CounterStyleDefinition.cpp

244 lines
12 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2026, Callum Law <callumlaw1709@outlook.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CounterStyleDefinition.h"
#include <LibWeb/CSS/CSSCounterStyleRule.h>
#include <LibWeb/CSS/Enums.h>
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
namespace Web::CSS {
// https://drafts.csswg.org/css-counter-styles-3/#valdef-counter-style-range-auto
Vector<CounterStyleRangeEntry> AutoRange::resolve(CounterStyleAlgorithm const& algorithm)
{
// The range depends on the counter system:
return algorithm.visit(
[](AdditiveCounterStyleAlgorithm const&) -> Vector<CounterStyleRangeEntry> {
// For additive systems, the range is 0 to positive infinity.
return { { 0, NumericLimits<i32>::max() } };
},
[](FixedCounterStyleAlgorithm const&) -> Vector<CounterStyleRangeEntry> {
// For cyclic, numeric, and fixed systems, the range is negative infinity to positive infinity.
// NB: cyclic and numeric are handled below.
return { { NumericLimits<i32>::min(), NumericLimits<i32>::max() } };
},
[](GenericCounterStyleAlgorithm const& generic_algorithm) -> Vector<CounterStyleRangeEntry> {
switch (generic_algorithm.type) {
case CounterStyleSystem::Cyclic:
case CounterStyleSystem::Numeric:
// For cyclic, numeric, and fixed systems, the range is negative infinity to positive infinity.
// NB: Fixed is handled above.
return { { NumericLimits<i32>::min(), NumericLimits<i32>::max() } };
case CounterStyleSystem::Alphabetic:
case CounterStyleSystem::Symbolic:
// For alphabetic and symbolic systems, the range is 1 to positive infinity.
return { { 1, NumericLimits<i32>::max() } };
case CounterStyleSystem::Additive:
// NB: Additive is handled above.
VERIFY_NOT_REACHED();
}
VERIFY_NOT_REACHED();
},
[](EthiopicNumericCounterStyleAlgorithm const&) -> Vector<CounterStyleRangeEntry> {
// NB: All complex predefined counter styles define their range explicitly (i.e. not via auto)
VERIFY_NOT_REACHED();
},
[](ExtendedCJKCounterStyleAlgorithm const&) -> Vector<CounterStyleRangeEntry> {
// NB: All complex predefined counter styles define their range explicitly (i.e. not via auto)
VERIFY_NOT_REACHED();
});
}
Optional<CounterStyleDefinition> CounterStyleDefinition::from_counter_style_rule(CSSCounterStyleRule const& rule, ComputationContext const& computation_context)
{
auto system_style_value = rule.system_style_value() ? NonnullRefPtr<StyleValue const> { *rule.system_style_value() } : CounterStyleSystemStyleValue::create(CounterStyleSystem::Symbolic);
auto maybe_algorithm = resolve_algorithm(system_style_value, rule.symbols_style_value(), rule.additive_symbols_style_value(), computation_context);
if (maybe_algorithm.has<Empty>())
return {};
return CounterStyleDefinition::create(
rule.name(),
maybe_algorithm.downcast<CounterStyleAlgorithm, CounterStyleSystemStyleValue::Extends>(),
rule.negative_style_value() ? Optional<CounterStyleNegativeSign> { resolve_negative_sign(*rule.negative_style_value()) } : Optional<CounterStyleNegativeSign> {},
rule.prefix_style_value() ? Optional<CounterStyleSymbol> { string_from_style_value(*rule.prefix_style_value()) } : Optional<CounterStyleSymbol> {},
rule.suffix_style_value() ? Optional<CounterStyleSymbol> { string_from_style_value(*rule.suffix_style_value()) } : Optional<CounterStyleSymbol> {},
rule.range_style_value() ? Variant<Empty, AutoRange, Vector<CounterStyleRangeEntry>> { resolve_range(*rule.range_style_value(), computation_context) } : Variant<Empty, AutoRange, Vector<CounterStyleRangeEntry>> {},
rule.fallback_style_value() ? Optional<FlyString> { string_from_style_value(*rule.fallback_style_value()) } : Optional<FlyString> {},
rule.pad_style_value() ? Optional<CounterStylePad> { resolve_pad(*rule.pad_style_value(), computation_context) } : Optional<CounterStylePad> {});
}
// https://drafts.csswg.org/css-counter-styles-3/#counter-style-system
Variant<Empty, CounterStyleAlgorithm, CounterStyleSystemStyleValue::Extends> CounterStyleDefinition::resolve_algorithm(NonnullRefPtr<StyleValue const> const& system_style_value, RefPtr<StyleValue const> const& symbols_style_value, RefPtr<StyleValue const> const& additive_symbols_style_value, ComputationContext const& computation_context)
{
// https://drafts.csswg.org/css-counter-styles-3/#counter-style-symbols
// The @counter-style rule must have a valid symbols descriptor if the counter system is cyclic,
// numeric, alphabetic, symbolic, or fixed, or a valid additive-symbols descriptor if the counter system
// is additive; otherwise, the @counter-style does not define a counter style (but is still a valid
// at-rule).
return system_style_value->as_counter_style_system().value().visit(
[&](CounterStyleSystem const& system) -> Variant<Empty, CounterStyleAlgorithm, CounterStyleSystemStyleValue::Extends> {
switch (system) {
case CounterStyleSystem::Cyclic:
case CounterStyleSystem::Alphabetic:
case CounterStyleSystem::Symbolic:
case CounterStyleSystem::Numeric: {
if (!symbols_style_value)
return {};
auto parsed_symbols = resolve_symbols(*symbols_style_value);
if (!system_style_value->as_counter_style_system().is_valid_symbol_count(parsed_symbols.size()))
return {};
return {
GenericCounterStyleAlgorithm {
.type = system,
.symbol_list = move(parsed_symbols),
}
};
}
case CounterStyleSystem::Additive: {
if (!additive_symbols_style_value)
return {};
auto additive_tuples = resolve_additive_symbols(*additive_symbols_style_value, computation_context);
if (!system_style_value->as_counter_style_system().is_valid_additive_symbol_count(additive_tuples.size()))
return {};
return { AdditiveCounterStyleAlgorithm { .symbol_list = move(additive_tuples) } };
}
}
VERIFY_NOT_REACHED();
},
[&](CounterStyleSystemStyleValue::Fixed const& fixed) -> Variant<Empty, CounterStyleAlgorithm, CounterStyleSystemStyleValue::Extends> {
if (!symbols_style_value)
return {};
auto parsed_symbols = resolve_symbols(*symbols_style_value);
if (!system_style_value->as_counter_style_system().is_valid_symbol_count(parsed_symbols.size()))
return {};
// https://drafts.csswg.org/css-counter-styles-3/#fixed-system
// If it is omitted, the first symbol value is 1.
i32 first_symbol = 1;
if (fixed.first_symbol)
first_symbol = int_from_style_value(fixed.first_symbol->absolutized(computation_context));
return {
FixedCounterStyleAlgorithm {
.first_symbol = first_symbol,
.symbol_list = move(parsed_symbols),
}
};
},
[&](CounterStyleSystemStyleValue::Extends const& extends) -> Variant<Empty, CounterStyleAlgorithm, CounterStyleSystemStyleValue::Extends> {
return extends;
});
}
// https://drafts.csswg.org/css-counter-styles-3/#descdef-counter-style-symbols
Vector<CounterStyleSymbol> CounterStyleDefinition::resolve_symbols(NonnullRefPtr<StyleValue const> const& symbols_style_value)
{
auto const& entries = symbols_style_value->as_value_list().values();
Vector<CounterStyleSymbol> symbols;
symbols.ensure_capacity(entries.size());
for (auto const& entry : entries)
symbols.unchecked_append(string_from_style_value(entry));
return symbols;
}
// https://drafts.csswg.org/css-counter-styles-3/#descdef-counter-style-additive-symbols
Vector<AdditiveCounterStyleAlgorithm::AdditiveTuple> CounterStyleDefinition::resolve_additive_symbols(NonnullRefPtr<StyleValue const> const& additive_symbols_style_value, ComputationContext const& computation_context)
{
auto const& entries = additive_symbols_style_value->as_value_list().values();
Vector<AdditiveCounterStyleAlgorithm::AdditiveTuple> additive_tuples;
additive_tuples.ensure_capacity(entries.size());
for (auto const& entry : entries) {
auto const& tuple = entry->as_value_list().values();
VERIFY(tuple.size() == 2);
auto weight = AK::clamp_to<i32>(int_from_style_value(tuple[0]->absolutized(computation_context)));
auto symbol = string_from_style_value(tuple[1]);
additive_tuples.unchecked_append({ weight, symbol });
}
return additive_tuples;
}
// https://drafts.csswg.org/css-counter-styles-3/#counter-style-negative
CounterStyleNegativeSign CounterStyleDefinition::resolve_negative_sign(NonnullRefPtr<StyleValue const> const& style_value)
{
auto const& negative_entries = style_value->as_value_list().values();
return {
.prefix = string_from_style_value(negative_entries[0]),
.suffix = negative_entries.size() > 1 ? string_from_style_value(negative_entries[1]) : ""_fly_string,
};
}
// https://drafts.csswg.org/css-counter-styles-3/#counter-style-range
Variant<AutoRange, Vector<CounterStyleRangeEntry>> CounterStyleDefinition::resolve_range(NonnullRefPtr<StyleValue const> const& style_value, ComputationContext const& computation_context)
{
// auto
// NB: Resolving auto depends on the algorithm, which we may not know at parse time i.e. if the system is 'extends'
// To handle this we return an intermediate value which we resolve when creating the CounterStyle.
if (style_value->has_auto())
return AutoRange {};
// [ [ <integer> | infinite ]{2} ]#
// This defines a comma-separated list of ranges. For each individual range, the first value is the lower bound and
// the second value is the upper bound. This range is inclusive - it contains both the lower and upper bound
// numbers. If infinite is used as the first value in a range, it represents negative infinity; if used as the
// second value, it represents positive infinity. The range of the counter style is the union of all the ranges
// defined in the list.
auto const& range_entries = style_value->as_value_list().values();
Vector<CounterStyleRangeEntry> ranges;
ranges.ensure_capacity(range_entries.size());
for (auto const& entry : range_entries) {
auto const& range_values = entry->as_value_list().values();
VERIFY(range_values.size() == 2);
auto const resolve_value = [&](NonnullRefPtr<StyleValue const> const& value, i32 infinite_value) {
if (value->is_keyword() && value->to_keyword() == Keyword::Infinite)
return infinite_value;
return int_from_style_value(value->absolutized(computation_context));
};
ranges.unchecked_append({ resolve_value(range_values[0], NumericLimits<i32>::min()), resolve_value(range_values[1], NumericLimits<i32>::max()) });
}
return ranges;
}
// https://drafts.csswg.org/css-counter-styles-3/#counter-style-pad
CounterStylePad CounterStyleDefinition::resolve_pad(NonnullRefPtr<StyleValue const> const& style_value, ComputationContext const& computation_context)
{
auto const& pad_entries = style_value->as_value_list().values();
return CounterStylePad {
.minimum_length = AK::clamp_to<i32>(int_from_style_value(pad_entries[0]->absolutized(computation_context))),
.symbol = string_from_style_value(pad_entries[1]),
};
}
}