LibWeb: Reimplement CalculatedStyleValue as a calculation node tree

VALUES-4 defines the internal representation of `calc()` as a tree of
calculation nodes. ( https://www.w3.org/TR/css-values-4/#calc-internal )

VALUES-3 lacked any definition here, so we had our own ad-hoc
implementation based around the spec grammar. This commit replaces that
with CalculationNodes representing each possible node in the tree.

There are no intended functional changes, though we do now support
nested calc() which previously did not work. For example:
    `width: calc( 42 * calc(3 + 7) );`

I have added an example of this to our test page.

A couple of the layout tests that used `calc()` now return values that
are 0.5px different from before. There's no visual difference, so I
have updated the tests to use the new results.
This commit is contained in:
Sam Atkins 2023-04-11 15:48:06 +01:00 committed by Andreas Kling
parent 5f2f780662
commit d0f80b40b2
7 changed files with 828 additions and 495 deletions

View file

@ -34,6 +34,11 @@
<div class="box" style="width: calc(100px + 30% - (120px / (2*4 + 3 )));"></div>
</div>
<p>calc(100px + 30% - calc(120px / calc(2*4 + 3 )))</p>
<div class="container">
<div class="box" style="width: calc(100px + 30% - calc(120px / calc(2*4 + 3 )));"></div>
</div>
<p>calc(50% + 60px)</p>
<div class="container">
<div class="box" style="width: calc(50% + 60px);"></div>

View file

@ -103,14 +103,14 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
Box <div.grid-container> at (8,275.34375) content-size 784x90.9375 children: not-inline
BlockContainer <(anonymous)> at (8,275.34375) content-size 0x0 children: inline
TextNode <#text>
BlockContainer <div.grid-item> at (445.434356,285.34375) content-size 337.800018x17.46875 children: inline
BlockContainer <div.grid-item> at (445.934356,285.34375) content-size 337.300018x17.46875 children: inline
line 0 width: 6.34375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [445.434356,285.34375 6.34375x17.46875]
frag 0 from TextNode start: 0, length: 1, rect: [445.934356,285.34375 6.34375x17.46875]
"1"
TextNode <#text>
BlockContainer <(anonymous)> at (8,275.34375) content-size 0x0 children: inline
TextNode <#text>
BlockContainer <div.grid-item> at (18,338.8125) content-size 339.034362x17.46875 children: inline
BlockContainer <div.grid-item> at (18,338.8125) content-size 338.534362x17.46875 children: inline
line 0 width: 8.8125, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [18,338.8125 8.8125x17.46875]
"2"

View file

@ -38,14 +38,14 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
Box <div.grid-container> at (8,52.9375) content-size 784x50.9375 children: not-inline
BlockContainer <(anonymous)> at (8,52.9375) content-size 0x0 children: inline
TextNode <#text>
BlockContainer <div.grid-item> at (435.434356,52.9375) content-size 357.800018x17.46875 children: inline
BlockContainer <div.grid-item> at (435.934356,52.9375) content-size 357.300018x17.46875 children: inline
line 0 width: 6.34375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [435.434356,52.9375 6.34375x17.46875]
frag 0 from TextNode start: 0, length: 1, rect: [435.934356,52.9375 6.34375x17.46875]
"1"
TextNode <#text>
BlockContainer <(anonymous)> at (8,52.9375) content-size 0x0 children: inline
TextNode <#text>
BlockContainer <div.grid-item> at (8,86.40625) content-size 359.034362x17.46875 children: inline
BlockContainer <div.grid-item> at (8,86.40625) content-size 358.534362x17.46875 children: inline
line 0 width: 8.8125, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [8,86.40625 8.8125x17.46875]
"2"

View file

@ -3271,11 +3271,21 @@ RefPtr<StyleValue> Parser::parse_builtin_value(ComponentValue const& component_v
RefPtr<CalculatedStyleValue> Parser::parse_calculated_value(Vector<ComponentValue> const& component_values)
{
auto calc_expression = parse_calc_expression(component_values);
if (calc_expression == nullptr)
return nullptr;
auto calculation_tree = parse_a_calculation(component_values).release_value_but_fixme_should_propagate_errors();
auto calc_type = calc_expression->resolved_type();
if (calculation_tree == nullptr) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse calculation tree");
return nullptr;
} else {
if constexpr (CSS_PARSER_DEBUG) {
dbgln("Parsed calculation tree:");
StringBuilder builder;
calculation_tree->dump(builder, 0).release_value_but_fixme_should_propagate_errors();
dbgln(builder.string_view());
}
}
auto calc_type = calculation_tree->resolved_type();
if (!calc_type.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "calc() resolved as invalid!!!");
return nullptr;
@ -3302,7 +3312,7 @@ RefPtr<CalculatedStyleValue> Parser::parse_calculated_value(Vector<ComponentValu
};
dbgln_if(CSS_PARSER_DEBUG, "Deduced calc() resolved type as: {}", to_string(calc_type.value()));
return CalculatedStyleValue::create(calc_expression.release_nonnull(), calc_type.release_value());
return CalculatedStyleValue::create(calculation_tree.release_nonnull(), calc_type.release_value());
}
RefPtr<StyleValue> Parser::parse_dynamic_value(ComponentValue const& component_value)
@ -7085,147 +7095,231 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
return syntax_error();
}
OwnPtr<CalculatedStyleValue::CalcSum> Parser::parse_calc_expression(Vector<ComponentValue> const& values)
{
auto tokens = TokenStream(values);
return parse_calc_sum(tokens);
}
class UnparsedCalculationNode final : public CalculationNode {
public:
static ErrorOr<NonnullOwnPtr<UnparsedCalculationNode>> create(ComponentValue component_value)
{
return adopt_nonnull_own_or_enomem(new (nothrow) UnparsedCalculationNode(move(component_value)));
}
virtual ~UnparsedCalculationNode() = default;
Optional<CalculatedStyleValue::CalcValue> Parser::parse_calc_value(TokenStream<ComponentValue>& tokens)
{
auto current_token = tokens.next_token();
ComponentValue& component_value() { return m_component_value; }
if (current_token.is_block() && current_token.block().is_paren()) {
auto block_values = TokenStream(current_token.block().values());
auto parsed_calc_sum = parse_calc_sum(block_values);
if (!parsed_calc_sum)
return {};
return CalculatedStyleValue::CalcValue { parsed_calc_sum.release_nonnull() };
virtual ErrorOr<String> to_string() const override { VERIFY_NOT_REACHED(); }
virtual Optional<CalculatedStyleValue::ResolvedType> resolved_type() const override { VERIFY_NOT_REACHED(); }
virtual bool contains_percentage() const override { VERIFY_NOT_REACHED(); }
virtual CalculatedStyleValue::CalculationResult resolve(Layout::Node const*, CalculatedStyleValue::PercentageBasis const&) const override { VERIFY_NOT_REACHED(); }
virtual ErrorOr<void> dump(StringBuilder& builder, int indent) const override
{
return builder.try_appendff("{: >{}}UNPARSED({})\n", "", indent, TRY(m_component_value.to_debug_string()));
}
if (current_token.is(Token::Type::Number))
return CalculatedStyleValue::CalcValue { current_token.token().number() };
if (current_token.is(Token::Type::Dimension) || current_token.is(Token::Type::Percentage)) {
auto maybe_dimension = parse_dimension(current_token);
if (!maybe_dimension.has_value())
return {};
auto& dimension = maybe_dimension.value();
if (dimension.is_angle())
return CalculatedStyleValue::CalcValue { dimension.angle() };
if (dimension.is_frequency())
return CalculatedStyleValue::CalcValue { dimension.frequency() };
if (dimension.is_length())
return CalculatedStyleValue::CalcValue { dimension.length() };
if (dimension.is_percentage())
return CalculatedStyleValue::CalcValue { dimension.percentage() };
if (dimension.is_resolution()) {
// Resolution is not allowed in calc()
return {};
}
if (dimension.is_time())
return CalculatedStyleValue::CalcValue { dimension.time() };
VERIFY_NOT_REACHED();
private:
UnparsedCalculationNode(ComponentValue component_value)
: CalculationNode(Type::Unparsed)
, m_component_value(move(component_value))
{
}
return {};
}
OwnPtr<CalculatedStyleValue::CalcProductPartWithOperator> Parser::parse_calc_product_part_with_operator(TokenStream<ComponentValue>& tokens)
{
tokens.skip_whitespace();
auto const& op_token = tokens.peek_token();
if (!op_token.is(Token::Type::Delim))
return nullptr;
auto op = op_token.token().delim();
if (op != '*' && op != '/')
return nullptr;
tokens.next_token();
tokens.skip_whitespace();
auto parsed_calc_value = parse_calc_value(tokens);
if (!parsed_calc_value.has_value())
return nullptr;
auto operation = op == '*'
? CalculatedStyleValue::ProductOperation::Multiply
: CalculatedStyleValue::ProductOperation::Divide;
return make<CalculatedStyleValue::CalcProductPartWithOperator>(operation, parsed_calc_value.release_value());
}
// https://www.w3.org/TR/css-values-4/#typedef-calc-product
OwnPtr<CalculatedStyleValue::CalcProduct> Parser::parse_calc_product(TokenStream<ComponentValue>& tokens)
{
// `<calc-product> = <calc-value> [ [ '*' | '/' ] <calc-value> ]*`
auto first_calc_value_or_error = parse_calc_value(tokens);
if (!first_calc_value_or_error.has_value())
return nullptr;
auto calc_product = make<CalculatedStyleValue::CalcProduct>(
first_calc_value_or_error.release_value(),
Vector<NonnullOwnPtr<CalculatedStyleValue::CalcProductPartWithOperator>> {});
while (tokens.has_next_token()) {
auto product_with_operator = parse_calc_product_part_with_operator(tokens);
if (!product_with_operator)
break;
calc_product->zero_or_more_additional_calc_values.append(product_with_operator.release_nonnull());
}
return calc_product;
}
OwnPtr<CalculatedStyleValue::CalcSumPartWithOperator> Parser::parse_calc_sum_part_with_operator(TokenStream<ComponentValue>& tokens)
{
// The following has to have the shape of <Whitespace><+ or -><Whitespace>
// But the first whitespace gets eaten in parse_calc_product_part_with_operator().
if (!(tokens.peek_token().is(Token::Type::Delim)
&& (tokens.peek_token().token().delim() == '+' || tokens.peek_token().token().delim() == '-')
&& tokens.peek_token(1).is(Token::Type::Whitespace)))
return nullptr;
auto const& token = tokens.next_token();
tokens.skip_whitespace();
CalculatedStyleValue::SumOperation op;
auto delim = token.token().delim();
if (delim == '+')
op = CalculatedStyleValue::SumOperation::Add;
else if (delim == '-')
op = CalculatedStyleValue::SumOperation::Subtract;
else
return nullptr;
auto calc_product = parse_calc_product(tokens);
if (!calc_product)
return nullptr;
return make<CalculatedStyleValue::CalcSumPartWithOperator>(op, calc_product.release_nonnull());
ComponentValue m_component_value;
};
// https://www.w3.org/TR/css-values-4/#typedef-calc-sum
OwnPtr<CalculatedStyleValue::CalcSum> Parser::parse_calc_sum(TokenStream<ComponentValue>& tokens)
// https://www.w3.org/TR/css-values-4/#parse-a-calculation
ErrorOr<OwnPtr<CalculationNode>> Parser::parse_a_calculation(Vector<ComponentValue> const& original_values)
{
// `<calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*`
// 1. Discard any <whitespace-token>s from values.
// 2. An item in values is an “operator” if its a <delim-token> with the value "+", "-", "*", or "/". Otherwise, its a “value”.
struct Operator {
char delim;
};
using Value = Variant<NonnullOwnPtr<CalculationNode>, Operator>;
Vector<Value> values;
for (auto& value : original_values) {
if (value.is(Token::Type::Whitespace))
continue;
if (value.is(Token::Type::Delim)) {
if (first_is_one_of(value.token().delim(), static_cast<u32>('+'), static_cast<u32>('-'), static_cast<u32>('*'), static_cast<u32>('/'))) {
// NOTE: Sequential operators are invalid syntax.
if (!values.is_empty() && values.last().has<Operator>())
return nullptr;
auto parsed_calc_product = parse_calc_product(tokens);
if (!parsed_calc_product)
return nullptr;
TRY(values.try_append(Operator { static_cast<char>(value.token().delim()) }));
continue;
}
}
Vector<NonnullOwnPtr<CalculatedStyleValue::CalcSumPartWithOperator>> additional {};
while (tokens.has_next_token()) {
auto calc_sum_part = parse_calc_sum_part_with_operator(tokens);
if (!calc_sum_part)
return nullptr;
additional.append(calc_sum_part.release_nonnull());
if (value.is(Token::Type::Number)) {
TRY(values.try_append({ TRY(NumericCalculationNode::create(value.token().number())) }));
continue;
}
if (auto dimension = parse_dimension(value); dimension.has_value()) {
if (dimension->is_angle())
TRY(values.try_append({ TRY(NumericCalculationNode::create(dimension->angle())) }));
else if (dimension->is_frequency())
TRY(values.try_append({ TRY(NumericCalculationNode::create(dimension->frequency())) }));
else if (dimension->is_length())
TRY(values.try_append({ TRY(NumericCalculationNode::create(dimension->length())) }));
else if (dimension->is_percentage())
TRY(values.try_append({ TRY(NumericCalculationNode::create(dimension->percentage())) }));
// FIXME: Resolutions, once calc() supports them.
else if (dimension->is_time())
TRY(values.try_append({ TRY(NumericCalculationNode::create(dimension->time())) }));
else
VERIFY_NOT_REACHED();
continue;
}
TRY(values.try_append({ TRY(UnparsedCalculationNode::create(value)) }));
}
tokens.skip_whitespace();
// If we have no values, the syntax is invalid.
if (values.is_empty())
return nullptr;
return make<CalculatedStyleValue::CalcSum>(parsed_calc_product.release_nonnull(), move(additional));
// NOTE: If the first or last value is an operator, the syntax is invalid.
if (values.first().has<Operator>() || values.last().has<Operator>())
return nullptr;
// 3. Collect children into Product and Invert nodes.
// For every consecutive run of value items in values separated by "*" or "/" operators:
while (true) {
Optional<size_t> first_product_operator = values.find_first_index_if([](auto const& item) {
return item.template has<Operator>()
&& first_is_one_of(item.template get<Operator>().delim, '*', '/');
});
if (!first_product_operator.has_value())
break;
auto start_of_run = first_product_operator.value() - 1;
auto end_of_run = first_product_operator.value() + 1;
for (auto i = start_of_run + 1; i < values.size(); i += 2) {
auto& item = values[i];
if (!item.has<Operator>()) {
end_of_run = i - 1;
break;
}
auto delim = item.get<Operator>().delim;
if (!first_is_one_of(delim, '*', '/')) {
end_of_run = i - 1;
break;
}
}
// 1. For each "/" operator in the run, replace its right-hand value item rhs with an Invert node containing rhs as its child.
Vector<NonnullOwnPtr<CalculationNode>> run_values;
TRY(run_values.try_append(move(values[start_of_run].get<NonnullOwnPtr<CalculationNode>>())));
for (auto i = start_of_run + 1; i <= end_of_run; i += 2) {
auto& operator_ = values[i].get<Operator>().delim;
auto& rhs = values[i + 1];
if (operator_ == '/') {
TRY(run_values.try_append(TRY(InvertCalculationNode::create(move(rhs.get<NonnullOwnPtr<CalculationNode>>())))));
continue;
}
VERIFY(operator_ == '*');
TRY(run_values.try_append(move(rhs.get<NonnullOwnPtr<CalculationNode>>())));
}
// 2. Replace the entire run with a Product node containing the value items of the run as its children.
auto product_node = TRY(ProductCalculationNode::create(move(run_values)));
values.remove(start_of_run, end_of_run - start_of_run + 1);
TRY(values.try_insert(start_of_run, { move(product_node) }));
}
// 4. Collect children into Sum and Negate nodes.
Optional<NonnullOwnPtr<CalculationNode>> single_value;
{
// 1. For each "-" operator item in values, replace its right-hand value item rhs with a Negate node containing rhs as its child.
for (auto i = 0u; i < values.size(); ++i) {
auto& maybe_minus_operator = values[i];
if (!maybe_minus_operator.has<Operator>() || maybe_minus_operator.get<Operator>().delim != '-')
continue;
auto rhs_index = ++i;
auto& rhs = values[rhs_index];
NonnullOwnPtr<CalculationNode> negate_node = TRY(NegateCalculationNode::create(move(rhs.get<NonnullOwnPtr<CalculationNode>>())));
values.remove(rhs_index);
values.insert(rhs_index, move(negate_node));
}
// 2. If values has only one item, and it is a Product node or a parenthesized simple block, replace values with that item.
if (values.size() == 1) {
TRY(values.first().visit(
[&](ComponentValue& component_value) -> ErrorOr<void> {
if (component_value.is_block() && component_value.block().is_paren())
single_value = TRY(UnparsedCalculationNode::create(component_value));
return {};
},
[&](NonnullOwnPtr<CalculationNode>& node) -> ErrorOr<void> {
if (node->type() == CalculationNode::Type::Product)
single_value = move(node);
return {};
},
[](auto&) -> ErrorOr<void> { return {}; }));
}
// Otherwise, replace values with a Sum node containing the value items of values as its children.
if (!single_value.has_value()) {
values.remove_all_matching([](Value& value) { return value.has<Operator>(); });
Vector<NonnullOwnPtr<CalculationNode>> value_items;
TRY(value_items.try_ensure_capacity(values.size()));
for (auto& value : values) {
if (value.has<Operator>())
continue;
value_items.unchecked_append(move(value.get<NonnullOwnPtr<CalculationNode>>()));
}
single_value = TRY(SumCalculationNode::create(move(value_items)));
}
}
// 5. At this point values is a tree of Sum, Product, Negate, and Invert nodes, with other types of values at the leaf nodes. Process the leaf nodes.
// For every leaf node leaf in values:
bool parsing_failed_for_child_node = false;
TRY(single_value.value()->for_each_child_node([&](NonnullOwnPtr<CalculationNode>& node) -> ErrorOr<void> {
if (node->type() != CalculationNode::Type::Unparsed)
return {};
auto& unparsed_node = static_cast<UnparsedCalculationNode&>(*node);
auto& component_value = unparsed_node.component_value();
// 1. If leaf is a parenthesized simple block, replace leaf with the result of parsing a calculation from leafs contents.
if (component_value.is_block() && component_value.block().is_paren()) {
auto leaf_calculation = TRY(parse_a_calculation(component_value.block().values()));
if (!leaf_calculation) {
parsing_failed_for_child_node = true;
return {};
}
node = leaf_calculation.release_nonnull();
}
// 2. If leaf is a math function, replace leaf with the internal representation of that math function.
// NOTE: All function tokens at this point should be math functions.
else if (component_value.is_function()) {
auto& function = component_value.function();
if (function.name().equals_ignoring_ascii_case("calc"sv)) {
auto leaf_calculation = TRY(parse_a_calculation(function.values()));
if (!leaf_calculation) {
parsing_failed_for_child_node = true;
return {};
}
node = leaf_calculation.release_nonnull();
} else {
// FIXME: Parse more math functions once we have them.
parsing_failed_for_child_node = true;
return {};
}
}
return {};
}));
if (parsing_failed_for_child_node)
return nullptr;
// FIXME: 6. Return the result of simplifying a calculation tree from values.
return single_value.release_value();
}
bool Parser::has_ignored_vendor_prefix(StringView string)

View file

@ -323,13 +323,7 @@ private:
RefPtr<StyleValue> parse_grid_template_areas_value(Vector<ComponentValue> const&);
RefPtr<StyleValue> parse_grid_area_shorthand_value(Vector<ComponentValue> const&);
// calc() parsing, according to https://www.w3.org/TR/css-values-3/#calc-syntax
OwnPtr<CalculatedStyleValue::CalcSum> parse_calc_sum(TokenStream<ComponentValue>&);
OwnPtr<CalculatedStyleValue::CalcProduct> parse_calc_product(TokenStream<ComponentValue>&);
Optional<CalculatedStyleValue::CalcValue> parse_calc_value(TokenStream<ComponentValue>&);
OwnPtr<CalculatedStyleValue::CalcProductPartWithOperator> parse_calc_product_part_with_operator(TokenStream<ComponentValue>&);
OwnPtr<CalculatedStyleValue::CalcSumPartWithOperator> parse_calc_sum_part_with_operator(TokenStream<ComponentValue>&);
OwnPtr<CalculatedStyleValue::CalcSum> parse_calc_expression(Vector<ComponentValue> const&);
ErrorOr<OwnPtr<CalculationNode>> parse_a_calculation(Vector<ComponentValue> const&);
ParseErrorOr<NonnullRefPtr<Selector>> parse_complex_selector(TokenStream<ComponentValue>&, SelectorType);
ParseErrorOr<Optional<Selector::CompoundSelector>> parse_compound_selector(TokenStream<ComponentValue>&);

View file

@ -12,6 +12,384 @@
namespace Web::CSS {
static bool is_number(CalculatedStyleValue::ResolvedType type)
{
return type == CalculatedStyleValue::ResolvedType::Number || type == CalculatedStyleValue::ResolvedType::Integer;
}
static bool is_dimension(CalculatedStyleValue::ResolvedType type)
{
return type != CalculatedStyleValue::ResolvedType::Number
&& type != CalculatedStyleValue::ResolvedType::Integer
&& type != CalculatedStyleValue::ResolvedType::Percentage;
}
CalculationNode::CalculationNode(Type type)
: m_type(type)
{
}
CalculationNode::~CalculationNode() = default;
ErrorOr<NonnullOwnPtr<NumericCalculationNode>> NumericCalculationNode::create(NumericValue value)
{
return adopt_nonnull_own_or_enomem(new (nothrow) NumericCalculationNode(move(value)));
}
NumericCalculationNode::NumericCalculationNode(NumericValue value)
: CalculationNode(Type::Numeric)
, m_value(move(value))
{
}
NumericCalculationNode::~NumericCalculationNode() = default;
ErrorOr<String> NumericCalculationNode::to_string() const
{
return m_value.visit([](auto& value) { return value.to_string(); });
}
Optional<CalculatedStyleValue::ResolvedType> NumericCalculationNode::resolved_type() const
{
return m_value.visit(
[](Number const&) { return CalculatedStyleValue::ResolvedType::Number; },
[](Angle const&) { return CalculatedStyleValue::ResolvedType::Angle; },
[](Frequency const&) { return CalculatedStyleValue::ResolvedType::Frequency; },
[](Length const&) { return CalculatedStyleValue::ResolvedType::Length; },
[](Percentage const&) { return CalculatedStyleValue::ResolvedType::Percentage; },
[](Time const&) { return CalculatedStyleValue::ResolvedType::Time; });
}
bool NumericCalculationNode::contains_percentage() const
{
return m_value.has<Percentage>();
}
CalculatedStyleValue::CalculationResult NumericCalculationNode::resolve(Layout::Node const*, CalculatedStyleValue::PercentageBasis const&) const
{
return m_value;
}
ErrorOr<void> NumericCalculationNode::dump(StringBuilder& builder, int indent) const
{
return builder.try_appendff("{: >{}}NUMERIC({})\n", "", indent, TRY(m_value.visit([](auto& it) { return it.to_string(); })));
}
ErrorOr<NonnullOwnPtr<SumCalculationNode>> SumCalculationNode::create(Vector<NonnullOwnPtr<CalculationNode>> values)
{
return adopt_nonnull_own_or_enomem(new (nothrow) SumCalculationNode(move(values)));
}
SumCalculationNode::SumCalculationNode(Vector<NonnullOwnPtr<CalculationNode>> values)
: CalculationNode(Type::Sum)
, m_values(move(values))
{
VERIFY(!m_values.is_empty());
}
SumCalculationNode::~SumCalculationNode() = default;
ErrorOr<String> SumCalculationNode::to_string() const
{
bool first = true;
StringBuilder builder;
for (auto& value : m_values) {
if (!first)
TRY(builder.try_append(" + "sv));
TRY(builder.try_append(TRY(value->to_string())));
first = false;
}
return builder.to_string();
}
Optional<CalculatedStyleValue::ResolvedType> SumCalculationNode::resolved_type() const
{
// FIXME: Implement https://www.w3.org/TR/css-values-4/#determine-the-type-of-a-calculation
// For now, this is just ad-hoc, based on the old implementation.
Optional<CalculatedStyleValue::ResolvedType> type;
for (auto const& value : m_values) {
auto maybe_value_type = value->resolved_type();
if (!maybe_value_type.has_value())
return {};
auto value_type = maybe_value_type.value();
if (!type.has_value()) {
type = value_type;
continue;
}
// At + or -, check that both sides have the same type, or that one side is a <number> and the other is an <integer>.
// If both sides are the same type, resolve to that type.
if (value_type == type)
continue;
// If one side is a <number> and the other is an <integer>, resolve to <number>.
if (is_number(*type) && is_number(value_type)) {
type = CalculatedStyleValue::ResolvedType::Number;
continue;
}
// FIXME: calc() handles <percentage> by allowing them to pretend to be whatever <dimension> type is allowed at this location.
// Since we can't easily check what that type is, we just allow <percentage> to combine with any other <dimension> type.
if (type == CalculatedStyleValue::ResolvedType::Percentage && is_dimension(value_type)) {
type = value_type;
continue;
}
if (is_dimension(*type) && value_type == CalculatedStyleValue::ResolvedType::Percentage)
continue;
return {};
}
return type;
}
bool SumCalculationNode::contains_percentage() const
{
for (auto const& value : m_values) {
if (value->contains_percentage())
return true;
}
return false;
}
CalculatedStyleValue::CalculationResult SumCalculationNode::resolve(Layout::Node const* layout_node, CalculatedStyleValue::PercentageBasis const& percentage_basis) const
{
Optional<CalculatedStyleValue::CalculationResult> total;
for (auto& additional_product : m_values) {
auto additional_value = additional_product->resolve(layout_node, percentage_basis);
if (!total.has_value()) {
total = additional_value;
continue;
}
total->add(additional_value, layout_node, percentage_basis);
}
return total.value();
}
ErrorOr<void> SumCalculationNode::for_each_child_node(Function<ErrorOr<void>(NonnullOwnPtr<CalculationNode>&)> const& callback)
{
for (auto& item : m_values) {
TRY(item->for_each_child_node(callback));
TRY(callback(item));
}
return {};
}
ErrorOr<void> SumCalculationNode::dump(StringBuilder& builder, int indent) const
{
TRY(builder.try_appendff("{: >{}}SUM:\n", "", indent));
for (auto const& item : m_values)
TRY(item->dump(builder, indent + 2));
return {};
}
ErrorOr<NonnullOwnPtr<ProductCalculationNode>> ProductCalculationNode::create(Vector<NonnullOwnPtr<CalculationNode>> values)
{
return adopt_nonnull_own_or_enomem(new (nothrow) ProductCalculationNode(move(values)));
}
ProductCalculationNode::ProductCalculationNode(Vector<NonnullOwnPtr<CalculationNode>> values)
: CalculationNode(Type::Product)
, m_values(move(values))
{
VERIFY(!m_values.is_empty());
}
ProductCalculationNode::~ProductCalculationNode() = default;
ErrorOr<String> ProductCalculationNode::to_string() const
{
bool first = true;
StringBuilder builder;
for (auto& value : m_values) {
if (!first)
TRY(builder.try_append(" * "sv));
TRY(builder.try_append(TRY(value->to_string())));
first = false;
}
return builder.to_string();
}
Optional<CalculatedStyleValue::ResolvedType> ProductCalculationNode::resolved_type() const
{
// FIXME: Implement https://www.w3.org/TR/css-values-4/#determine-the-type-of-a-calculation
// For now, this is just ad-hoc, based on the old implementation.
Optional<CalculatedStyleValue::ResolvedType> type;
for (auto const& value : m_values) {
auto maybe_value_type = value->resolved_type();
if (!maybe_value_type.has_value())
return {};
auto value_type = maybe_value_type.value();
if (!type.has_value()) {
type = value_type;
continue;
}
// At *, check that at least one side is <number>.
if (!(is_number(*type) || is_number(value_type)))
return {};
// If both sides are <integer>, resolve to <integer>.
if (type == CalculatedStyleValue::ResolvedType::Integer && value_type == CalculatedStyleValue::ResolvedType::Integer) {
type = CalculatedStyleValue::ResolvedType::Integer;
} else {
// Otherwise, resolve to the type of the other side.
if (is_number(*type))
type = value_type;
}
}
return type;
}
bool ProductCalculationNode::contains_percentage() const
{
for (auto const& value : m_values) {
if (value->contains_percentage())
return true;
}
return false;
}
CalculatedStyleValue::CalculationResult ProductCalculationNode::resolve(Layout::Node const* layout_node, CalculatedStyleValue::PercentageBasis const& percentage_basis) const
{
Optional<CalculatedStyleValue::CalculationResult> total;
for (auto& additional_product : m_values) {
auto additional_value = additional_product->resolve(layout_node, percentage_basis);
if (!total.has_value()) {
total = additional_value;
continue;
}
total->multiply_by(additional_value, layout_node);
}
return total.value();
}
ErrorOr<void> ProductCalculationNode::for_each_child_node(Function<ErrorOr<void>(NonnullOwnPtr<CalculationNode>&)> const& callback)
{
for (auto& item : m_values) {
TRY(item->for_each_child_node(callback));
TRY(callback(item));
}
return {};
}
ErrorOr<void> ProductCalculationNode::dump(StringBuilder& builder, int indent) const
{
TRY(builder.try_appendff("{: >{}}PRODUCT:\n", "", indent));
for (auto const& item : m_values)
TRY(item->dump(builder, indent + 2));
return {};
}
ErrorOr<NonnullOwnPtr<NegateCalculationNode>> NegateCalculationNode::create(NonnullOwnPtr<Web::CSS::CalculationNode> value)
{
return adopt_nonnull_own_or_enomem(new (nothrow) NegateCalculationNode(move(value)));
}
NegateCalculationNode::NegateCalculationNode(NonnullOwnPtr<CalculationNode> value)
: CalculationNode(Type::Negate)
, m_value(move(value))
{
}
NegateCalculationNode::~NegateCalculationNode() = default;
ErrorOr<String> NegateCalculationNode::to_string() const
{
return String::formatted("(0 - {})", TRY(m_value->to_string()));
}
Optional<CalculatedStyleValue::ResolvedType> NegateCalculationNode::resolved_type() const
{
return m_value->resolved_type();
}
bool NegateCalculationNode::contains_percentage() const
{
return m_value->contains_percentage();
}
CalculatedStyleValue::CalculationResult NegateCalculationNode::resolve(Layout::Node const* layout_node, CalculatedStyleValue::PercentageBasis const& percentage_basis) const
{
auto child_value = m_value->resolve(layout_node, percentage_basis);
child_value.negate();
return child_value;
}
ErrorOr<void> NegateCalculationNode::for_each_child_node(Function<ErrorOr<void>(NonnullOwnPtr<CalculationNode>&)> const& callback)
{
TRY(m_value->for_each_child_node(callback));
TRY(callback(m_value));
return {};
}
ErrorOr<void> NegateCalculationNode::dump(StringBuilder& builder, int indent) const
{
TRY(builder.try_appendff("{: >{}}NEGATE:\n", "", indent));
TRY(m_value->dump(builder, indent + 2));
return {};
}
ErrorOr<NonnullOwnPtr<InvertCalculationNode>> InvertCalculationNode::create(NonnullOwnPtr<Web::CSS::CalculationNode> value)
{
return adopt_nonnull_own_or_enomem(new (nothrow) InvertCalculationNode(move(value)));
}
InvertCalculationNode::InvertCalculationNode(NonnullOwnPtr<CalculationNode> value)
: CalculationNode(Type::Invert)
, m_value(move(value))
{
}
InvertCalculationNode::~InvertCalculationNode() = default;
ErrorOr<String> InvertCalculationNode::to_string() const
{
return String::formatted("(1 / {})", TRY(m_value->to_string()));
}
Optional<CalculatedStyleValue::ResolvedType> InvertCalculationNode::resolved_type() const
{
auto type = m_value->resolved_type();
if (type == CalculatedStyleValue::ResolvedType::Integer)
return CalculatedStyleValue::ResolvedType::Number;
return type;
}
bool InvertCalculationNode::contains_percentage() const
{
return m_value->contains_percentage();
}
CalculatedStyleValue::CalculationResult InvertCalculationNode::resolve(Layout::Node const* layout_node, CalculatedStyleValue::PercentageBasis const& percentage_basis) const
{
auto child_value = m_value->resolve(layout_node, percentage_basis);
child_value.invert();
return child_value;
}
ErrorOr<void> InvertCalculationNode::for_each_child_node(Function<ErrorOr<void>(NonnullOwnPtr<CalculationNode>&)> const& callback)
{
TRY(m_value->for_each_child_node(callback));
TRY(callback(m_value));
return {};
}
ErrorOr<void> InvertCalculationNode::dump(StringBuilder& builder, int indent) const
{
TRY(builder.try_appendff("{: >{}}INVERT:\n", "", indent));
TRY(m_value->dump(builder, indent + 2));
return {};
}
void CalculatedStyleValue::CalculationResult::add(CalculationResult const& other, Layout::Node const* layout_node, PercentageBasis const& percentage_basis)
{
add_or_subtract_internal(SumOperation::Add, other, layout_node, percentage_basis);
@ -201,9 +579,57 @@ void CalculatedStyleValue::CalculationResult::divide_by(CalculationResult const&
});
}
void CalculatedStyleValue::CalculationResult::negate()
{
m_value.visit(
[&](Number const& number) {
m_value = Number { number.type(), 1 - number.value() };
},
[&](Angle const& angle) {
m_value = Angle { 1 - angle.raw_value(), angle.type() };
},
[&](Frequency const& frequency) {
m_value = Frequency { 1 - frequency.raw_value(), frequency.type() };
},
[&](Length const& length) {
m_value = Length { 1 - length.raw_value(), length.type() };
},
[&](Time const& time) {
m_value = Time { 1 - time.raw_value(), time.type() };
},
[&](Percentage const& percentage) {
m_value = Percentage { 1 - percentage.value() };
});
}
void CalculatedStyleValue::CalculationResult::invert()
{
// FIXME: Correctly handle division by zero.
m_value.visit(
[&](Number const& number) {
m_value = Number { Number::Type::Number, 1 / number.value() };
},
[&](Angle const& angle) {
m_value = Angle { 1 / angle.raw_value(), angle.type() };
},
[&](Frequency const& frequency) {
m_value = Frequency { 1 / frequency.raw_value(), frequency.type() };
},
[&](Length const& length) {
m_value = Length { 1 / length.raw_value(), length.type() };
},
[&](Time const& time) {
m_value = Time { 1 / time.raw_value(), time.type() };
},
[&](Percentage const& percentage) {
m_value = Percentage { 1 / percentage.value() };
});
}
ErrorOr<String> CalculatedStyleValue::to_string() const
{
return String::formatted("calc({})", TRY(m_expression->to_string()));
// FIXME: Implement this according to https://www.w3.org/TR/css-values-4/#calc-serialize once that stabilizes.
return String::formatted("calc({})", TRY(m_calculation->to_string()));
}
bool CalculatedStyleValue::equals(StyleValue const& other) const
@ -214,45 +640,9 @@ bool CalculatedStyleValue::equals(StyleValue const& other) const
return to_string().release_value_but_fixme_should_propagate_errors() == other.to_string().release_value_but_fixme_should_propagate_errors();
}
ErrorOr<String> CalculatedStyleValue::CalcValue::to_string() const
{
return value.visit(
[](Number const& number) -> ErrorOr<String> { return String::number(number.value()); },
[](NonnullOwnPtr<CalcSum> const& sum) -> ErrorOr<String> { return String::formatted("({})", TRY(sum->to_string())); },
[](auto const& v) -> ErrorOr<String> { return v.to_string(); });
}
ErrorOr<String> CalculatedStyleValue::CalcSum::to_string() const
{
StringBuilder builder;
TRY(builder.try_append(TRY(first_calc_product->to_string())));
for (auto const& item : zero_or_more_additional_calc_products)
TRY(builder.try_append(TRY(item->to_string())));
return builder.to_string();
}
ErrorOr<String> CalculatedStyleValue::CalcProduct::to_string() const
{
StringBuilder builder;
TRY(builder.try_append(TRY(first_calc_value.to_string())));
for (auto const& item : zero_or_more_additional_calc_values)
TRY(builder.try_append(TRY(item->to_string())));
return builder.to_string();
}
ErrorOr<String> CalculatedStyleValue::CalcSumPartWithOperator::to_string() const
{
return String::formatted(" {} {}", op == SumOperation::Add ? "+"sv : "-"sv, TRY(value->to_string()));
}
ErrorOr<String> CalculatedStyleValue::CalcProductPartWithOperator::to_string() const
{
return String::formatted(" {} {}", op == ProductOperation::Multiply ? "*"sv : "/"sv, TRY(value.to_string()));
}
Optional<Angle> CalculatedStyleValue::resolve_angle() const
{
auto result = m_expression->resolve(nullptr, {});
auto result = m_calculation->resolve(nullptr, {});
if (result.value().has<Angle>())
return result.value().get<Angle>();
@ -261,7 +651,7 @@ Optional<Angle> CalculatedStyleValue::resolve_angle() const
Optional<Angle> CalculatedStyleValue::resolve_angle_percentage(Angle const& percentage_basis) const
{
auto result = m_expression->resolve(nullptr, percentage_basis);
auto result = m_calculation->resolve(nullptr, percentage_basis);
return result.value().visit(
[&](Angle const& angle) -> Optional<Angle> {
@ -277,7 +667,7 @@ Optional<Angle> CalculatedStyleValue::resolve_angle_percentage(Angle const& perc
Optional<Frequency> CalculatedStyleValue::resolve_frequency() const
{
auto result = m_expression->resolve(nullptr, {});
auto result = m_calculation->resolve(nullptr, {});
if (result.value().has<Frequency>())
return result.value().get<Frequency>();
@ -286,7 +676,7 @@ Optional<Frequency> CalculatedStyleValue::resolve_frequency() const
Optional<Frequency> CalculatedStyleValue::resolve_frequency_percentage(Frequency const& percentage_basis) const
{
auto result = m_expression->resolve(nullptr, percentage_basis);
auto result = m_calculation->resolve(nullptr, percentage_basis);
return result.value().visit(
[&](Frequency const& frequency) -> Optional<Frequency> {
@ -302,7 +692,7 @@ Optional<Frequency> CalculatedStyleValue::resolve_frequency_percentage(Frequency
Optional<Length> CalculatedStyleValue::resolve_length(Layout::Node const& layout_node) const
{
auto result = m_expression->resolve(&layout_node, {});
auto result = m_calculation->resolve(&layout_node, {});
if (result.value().has<Length>())
return result.value().get<Length>();
@ -311,7 +701,7 @@ Optional<Length> CalculatedStyleValue::resolve_length(Layout::Node const& layout
Optional<Length> CalculatedStyleValue::resolve_length_percentage(Layout::Node const& layout_node, Length const& percentage_basis) const
{
auto result = m_expression->resolve(&layout_node, percentage_basis);
auto result = m_calculation->resolve(&layout_node, percentage_basis);
return result.value().visit(
[&](Length const& length) -> Optional<Length> {
@ -327,7 +717,7 @@ Optional<Length> CalculatedStyleValue::resolve_length_percentage(Layout::Node co
Optional<Percentage> CalculatedStyleValue::resolve_percentage() const
{
auto result = m_expression->resolve(nullptr, {});
auto result = m_calculation->resolve(nullptr, {});
if (result.value().has<Percentage>())
return result.value().get<Percentage>();
return {};
@ -335,7 +725,7 @@ Optional<Percentage> CalculatedStyleValue::resolve_percentage() const
Optional<Time> CalculatedStyleValue::resolve_time() const
{
auto result = m_expression->resolve(nullptr, {});
auto result = m_calculation->resolve(nullptr, {});
if (result.value().has<Time>())
return result.value().get<Time>();
@ -344,7 +734,7 @@ Optional<Time> CalculatedStyleValue::resolve_time() const
Optional<Time> CalculatedStyleValue::resolve_time_percentage(Time const& percentage_basis) const
{
auto result = m_expression->resolve(nullptr, percentage_basis);
auto result = m_calculation->resolve(nullptr, percentage_basis);
return result.value().visit(
[&](Time const& time) -> Optional<Time> {
@ -357,7 +747,7 @@ Optional<Time> CalculatedStyleValue::resolve_time_percentage(Time const& percent
Optional<float> CalculatedStyleValue::resolve_number()
{
auto result = m_expression->resolve(nullptr, {});
auto result = m_calculation->resolve(nullptr, {});
if (result.value().has<Number>())
return result.value().get<Number>().value();
return {};
@ -365,246 +755,15 @@ Optional<float> CalculatedStyleValue::resolve_number()
Optional<i64> CalculatedStyleValue::resolve_integer()
{
auto result = m_expression->resolve(nullptr, {});
auto result = m_calculation->resolve(nullptr, {});
if (result.value().has<Number>())
return result.value().get<Number>().integer_value();
return {};
}
static bool is_number(CalculatedStyleValue::ResolvedType type)
{
return type == CalculatedStyleValue::ResolvedType::Number || type == CalculatedStyleValue::ResolvedType::Integer;
}
static bool is_dimension(CalculatedStyleValue::ResolvedType type)
{
return type != CalculatedStyleValue::ResolvedType::Number
&& type != CalculatedStyleValue::ResolvedType::Integer
&& type != CalculatedStyleValue::ResolvedType::Percentage;
}
template<typename SumWithOperator>
static Optional<CalculatedStyleValue::ResolvedType> resolve_sum_type(CalculatedStyleValue::ResolvedType first_type, Vector<NonnullOwnPtr<SumWithOperator>> const& zero_or_more_additional_products)
{
auto type = first_type;
for (auto const& product : zero_or_more_additional_products) {
auto maybe_product_type = product->resolved_type();
if (!maybe_product_type.has_value())
return {};
auto product_type = maybe_product_type.value();
// At + or -, check that both sides have the same type, or that one side is a <number> and the other is an <integer>.
// If both sides are the same type, resolve to that type.
if (product_type == type)
continue;
// If one side is a <number> and the other is an <integer>, resolve to <number>.
if (is_number(type) && is_number(product_type)) {
type = CalculatedStyleValue::ResolvedType::Number;
continue;
}
// FIXME: calc() handles <percentage> by allowing them to pretend to be whatever <dimension> type is allowed at this location.
// Since we can't easily check what that type is, we just allow <percentage> to combine with any other <dimension> type.
if (type == CalculatedStyleValue::ResolvedType::Percentage && is_dimension(product_type)) {
type = product_type;
continue;
}
if (is_dimension(type) && product_type == CalculatedStyleValue::ResolvedType::Percentage)
continue;
return {};
}
return type;
}
Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcSum::resolved_type() const
{
auto maybe_type = first_calc_product->resolved_type();
if (!maybe_type.has_value())
return {};
auto type = maybe_type.value();
return resolve_sum_type(type, zero_or_more_additional_calc_products);
}
template<typename ProductWithOperator>
static Optional<CalculatedStyleValue::ResolvedType> resolve_product_type(CalculatedStyleValue::ResolvedType first_type, Vector<NonnullOwnPtr<ProductWithOperator>> const& zero_or_more_additional_values)
{
auto type = first_type;
for (auto const& value : zero_or_more_additional_values) {
auto maybe_value_type = value->resolved_type();
if (!maybe_value_type.has_value())
return {};
auto value_type = maybe_value_type.value();
if (value->op == CalculatedStyleValue::ProductOperation::Multiply) {
// At *, check that at least one side is <number>.
if (!(is_number(type) || is_number(value_type)))
return {};
// If both sides are <integer>, resolve to <integer>.
if (type == CalculatedStyleValue::ResolvedType::Integer && value_type == CalculatedStyleValue::ResolvedType::Integer) {
type = CalculatedStyleValue::ResolvedType::Integer;
} else {
// Otherwise, resolve to the type of the other side.
if (is_number(type))
type = value_type;
}
continue;
} else {
VERIFY(value->op == CalculatedStyleValue::ProductOperation::Divide);
// At /, check that the right side is <number>.
if (!is_number(value_type))
return {};
// If the left side is <integer>, resolve to <number>.
if (type == CalculatedStyleValue::ResolvedType::Integer) {
type = CalculatedStyleValue::ResolvedType::Number;
} else {
// Otherwise, resolve to the type of the left side.
}
// FIXME: Division by zero makes the whole calc() expression invalid.
}
}
return type;
}
Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcProduct::resolved_type() const
{
auto maybe_type = first_calc_value.resolved_type();
if (!maybe_type.has_value())
return {};
auto type = maybe_type.value();
return resolve_product_type(type, zero_or_more_additional_calc_values);
}
Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcSumPartWithOperator::resolved_type() const
{
return value->resolved_type();
}
Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcProductPartWithOperator::resolved_type() const
{
return value.resolved_type();
}
Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcValue::resolved_type() const
{
return value.visit(
[](Number const& number) -> Optional<CalculatedStyleValue::ResolvedType> {
return { number.is_integer() ? ResolvedType::Integer : ResolvedType::Number };
},
[](Angle const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Angle }; },
[](Frequency const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Frequency }; },
[](Length const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Length }; },
[](Percentage const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Percentage }; },
[](Time const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Time }; },
[](NonnullOwnPtr<CalcSum> const& sum) { return sum->resolved_type(); });
}
CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcValue::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
{
return value.visit(
[&](NonnullOwnPtr<CalcSum> const& sum) -> CalculatedStyleValue::CalculationResult {
return sum->resolve(layout_node, percentage_basis);
},
[&](auto const& v) -> CalculatedStyleValue::CalculationResult {
return CalculatedStyleValue::CalculationResult { v };
});
}
CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcSum::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
{
auto value = first_calc_product->resolve(layout_node, percentage_basis);
for (auto& additional_product : zero_or_more_additional_calc_products) {
auto additional_value = additional_product->resolve(layout_node, percentage_basis);
if (additional_product->op == CalculatedStyleValue::SumOperation::Add)
value.add(additional_value, layout_node, percentage_basis);
else if (additional_product->op == CalculatedStyleValue::SumOperation::Subtract)
value.subtract(additional_value, layout_node, percentage_basis);
else
VERIFY_NOT_REACHED();
}
return value;
}
CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcProduct::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
{
auto value = first_calc_value.resolve(layout_node, percentage_basis);
for (auto& additional_value : zero_or_more_additional_calc_values) {
if (additional_value->op == ProductOperation::Multiply) {
auto resolved_value = additional_value->value.resolve(layout_node, percentage_basis);
value.multiply_by(resolved_value, layout_node);
} else {
auto resolved_calc_number_value = additional_value->value.resolve(layout_node, percentage_basis);
// FIXME: Return the relevant constant here. (infinity?)
VERIFY(resolved_calc_number_value.value().get<Number>().value() != 0.0f);
value.divide_by(resolved_calc_number_value, layout_node);
}
}
return value;
}
CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcProductPartWithOperator::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
{
return value.resolve(layout_node, percentage_basis);
}
CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcSumPartWithOperator::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
{
return value->resolve(layout_node, percentage_basis);
}
bool CalculatedStyleValue::contains_percentage() const
{
return m_expression->contains_percentage();
}
bool CalculatedStyleValue::CalcSum::contains_percentage() const
{
if (first_calc_product->contains_percentage())
return true;
for (auto& part : zero_or_more_additional_calc_products) {
if (part->contains_percentage())
return true;
}
return false;
}
bool CalculatedStyleValue::CalcSumPartWithOperator::contains_percentage() const
{
return value->contains_percentage();
}
bool CalculatedStyleValue::CalcProduct::contains_percentage() const
{
if (first_calc_value.contains_percentage())
return true;
for (auto& part : zero_or_more_additional_calc_values) {
if (part->contains_percentage())
return true;
}
return false;
}
bool CalculatedStyleValue::CalcProductPartWithOperator::contains_percentage() const
{
return value.contains_percentage();
}
bool CalculatedStyleValue::CalcValue::contains_percentage() const
{
return value.visit(
[](Percentage const&) { return true; },
[](NonnullOwnPtr<CalcSum> const& sum) { return sum->contains_percentage(); },
[](auto const&) { return false; });
return m_calculation->contains_percentage();
}
}

View file

@ -18,6 +18,8 @@
namespace Web::CSS {
class CalculationNode;
class CalculatedStyleValue : public StyleValue {
public:
enum class ResolvedType {
@ -52,6 +54,8 @@ public:
void subtract(CalculationResult const& other, Layout::Node const*, PercentageBasis const& percentage_basis);
void multiply_by(CalculationResult const& other, Layout::Node const*);
void divide_by(CalculationResult const& other, Layout::Node const*);
void negate();
void invert();
Value const& value() const { return m_value; }
@ -60,79 +64,14 @@ public:
Value m_value;
};
struct CalcSum;
struct CalcSumPartWithOperator;
struct CalcProduct;
struct CalcProductPartWithOperator;
struct CalcValue {
Variant<Number, Angle, Frequency, Length, Percentage, Time, NonnullOwnPtr<CalcSum>> value;
ErrorOr<String> to_string() const;
Optional<ResolvedType> resolved_type() const;
CalculationResult resolve(Layout::Node const*, PercentageBasis const& percentage_basis) const;
bool contains_percentage() const;
};
// This represents that: https://www.w3.org/TR/css-values-3/#calc-syntax
struct CalcSum {
CalcSum(NonnullOwnPtr<CalcProduct> first_calc_product, Vector<NonnullOwnPtr<CalcSumPartWithOperator>> additional)
: first_calc_product(move(first_calc_product))
, zero_or_more_additional_calc_products(move(additional)) {};
NonnullOwnPtr<CalcProduct> first_calc_product;
Vector<NonnullOwnPtr<CalcSumPartWithOperator>> zero_or_more_additional_calc_products;
ErrorOr<String> to_string() const;
Optional<ResolvedType> resolved_type() const;
CalculationResult resolve(Layout::Node const*, PercentageBasis const& percentage_basis) const;
bool contains_percentage() const;
};
struct CalcProduct {
CalcValue first_calc_value;
Vector<NonnullOwnPtr<CalcProductPartWithOperator>> zero_or_more_additional_calc_values;
ErrorOr<String> to_string() const;
Optional<ResolvedType> resolved_type() const;
CalculationResult resolve(Layout::Node const*, PercentageBasis const& percentage_basis) const;
bool contains_percentage() const;
};
struct CalcSumPartWithOperator {
CalcSumPartWithOperator(SumOperation op, NonnullOwnPtr<CalcProduct> calc_product)
: op(op)
, value(move(calc_product)) {};
SumOperation op;
NonnullOwnPtr<CalcProduct> value;
ErrorOr<String> to_string() const;
Optional<ResolvedType> resolved_type() const;
CalculationResult resolve(Layout::Node const*, PercentageBasis const& percentage_basis) const;
bool contains_percentage() const;
};
struct CalcProductPartWithOperator {
ProductOperation op;
CalcValue value;
ErrorOr<String> to_string() const;
Optional<ResolvedType> resolved_type() const;
CalculationResult resolve(Layout::Node const*, PercentageBasis const& percentage_basis) const;
bool contains_percentage() const;
};
static ValueComparingNonnullRefPtr<CalculatedStyleValue> create(NonnullOwnPtr<CalcSum> calc_sum, ResolvedType resolved_type)
static ValueComparingNonnullRefPtr<CalculatedStyleValue> create(NonnullOwnPtr<CalculationNode> calculation, ResolvedType resolved_type)
{
return adopt_ref(*new CalculatedStyleValue(move(calc_sum), resolved_type));
return adopt_ref(*new CalculatedStyleValue(move(calculation), resolved_type));
}
ErrorOr<String> to_string() const override;
virtual bool equals(StyleValue const& other) const override;
ResolvedType resolved_type() const { return m_resolved_type; }
NonnullOwnPtr<CalcSum> const& expression() const { return m_expression; }
bool resolves_to_angle() const { return m_resolved_type == ResolvedType::Angle; }
Optional<Angle> resolve_angle() const;
@ -161,15 +100,157 @@ public:
bool contains_percentage() const;
private:
explicit CalculatedStyleValue(NonnullOwnPtr<CalcSum> calc_sum, ResolvedType resolved_type)
explicit CalculatedStyleValue(NonnullOwnPtr<CalculationNode> calculation, ResolvedType resolved_type)
: StyleValue(Type::Calculated)
, m_resolved_type(resolved_type)
, m_expression(move(calc_sum))
, m_calculation(move(calculation))
{
}
ResolvedType m_resolved_type;
NonnullOwnPtr<CalcSum> m_expression;
NonnullOwnPtr<CalculationNode> m_calculation;
};
// https://www.w3.org/TR/css-values-4/#calculation-tree
class CalculationNode {
public:
enum class Type {
Numeric,
// NOTE: Currently, any value with a `var()` or `attr()` function in it is always an
// UnresolvedStyleValue so we do not have to implement a NonMathFunction type here.
// Operator nodes
// https://www.w3.org/TR/css-values-4/#calculation-tree-operator-nodes
// Calc-operator nodes, a sub-type of operator node
// https://www.w3.org/TR/css-values-4/#calculation-tree-calc-operator-nodes
Sum,
Product,
Negate,
Invert,
// This only exists during parsing.
Unparsed,
};
using NumericValue = CalculatedStyleValue::CalculationResult::Value;
virtual ~CalculationNode();
Type type() const { return m_type; }
bool is_operator_node() const
{
// FIXME: Check for operator node types once they exist
return is_calc_operator_node();
}
bool is_calc_operator_node() const
{
return first_is_one_of(m_type, Type::Sum, Type::Product, Type::Negate, Type::Invert);
}
virtual ErrorOr<String> to_string() const = 0;
virtual Optional<CalculatedStyleValue::ResolvedType> resolved_type() const = 0;
virtual bool contains_percentage() const = 0;
virtual CalculatedStyleValue::CalculationResult resolve(Layout::Node const*, CalculatedStyleValue::PercentageBasis const&) const = 0;
virtual ErrorOr<void> for_each_child_node(Function<ErrorOr<void>(NonnullOwnPtr<CalculationNode>&)> const&) { return {}; }
virtual ErrorOr<void> dump(StringBuilder&, int indent) const = 0;
protected:
explicit CalculationNode(Type);
private:
Type m_type;
};
class NumericCalculationNode final : public CalculationNode {
public:
static ErrorOr<NonnullOwnPtr<NumericCalculationNode>> create(NumericValue);
~NumericCalculationNode();
virtual ErrorOr<String> to_string() const override;
virtual Optional<CalculatedStyleValue::ResolvedType> resolved_type() const override;
virtual bool contains_percentage() const override;
virtual CalculatedStyleValue::CalculationResult resolve(Layout::Node const*, CalculatedStyleValue::PercentageBasis const&) const override;
virtual ErrorOr<void> dump(StringBuilder&, int indent) const override;
private:
explicit NumericCalculationNode(NumericValue);
NumericValue m_value;
};
class SumCalculationNode final : public CalculationNode {
public:
static ErrorOr<NonnullOwnPtr<SumCalculationNode>> create(Vector<NonnullOwnPtr<CalculationNode>>);
~SumCalculationNode();
virtual ErrorOr<String> to_string() const override;
virtual Optional<CalculatedStyleValue::ResolvedType> resolved_type() const override;
virtual bool contains_percentage() const override;
virtual CalculatedStyleValue::CalculationResult resolve(Layout::Node const*, CalculatedStyleValue::PercentageBasis const&) const override;
virtual ErrorOr<void> for_each_child_node(Function<ErrorOr<void>(NonnullOwnPtr<CalculationNode>&)> const&) override;
virtual ErrorOr<void> dump(StringBuilder&, int indent) const override;
private:
explicit SumCalculationNode(Vector<NonnullOwnPtr<CalculationNode>>);
Vector<NonnullOwnPtr<CalculationNode>> m_values;
};
class ProductCalculationNode final : public CalculationNode {
public:
static ErrorOr<NonnullOwnPtr<ProductCalculationNode>> create(Vector<NonnullOwnPtr<CalculationNode>>);
~ProductCalculationNode();
virtual ErrorOr<String> to_string() const override;
virtual Optional<CalculatedStyleValue::ResolvedType> resolved_type() const override;
virtual bool contains_percentage() const override;
virtual CalculatedStyleValue::CalculationResult resolve(Layout::Node const*, CalculatedStyleValue::PercentageBasis const&) const override;
virtual ErrorOr<void> for_each_child_node(Function<ErrorOr<void>(NonnullOwnPtr<CalculationNode>&)> const&) override;
virtual ErrorOr<void> dump(StringBuilder&, int indent) const override;
private:
explicit ProductCalculationNode(Vector<NonnullOwnPtr<CalculationNode>>);
Vector<NonnullOwnPtr<CalculationNode>> m_values;
};
class NegateCalculationNode final : public CalculationNode {
public:
static ErrorOr<NonnullOwnPtr<NegateCalculationNode>> create(NonnullOwnPtr<CalculationNode>);
~NegateCalculationNode();
virtual ErrorOr<String> to_string() const override;
virtual Optional<CalculatedStyleValue::ResolvedType> resolved_type() const override;
virtual bool contains_percentage() const override;
virtual CalculatedStyleValue::CalculationResult resolve(Layout::Node const*, CalculatedStyleValue::PercentageBasis const&) const override;
virtual ErrorOr<void> for_each_child_node(Function<ErrorOr<void>(NonnullOwnPtr<CalculationNode>&)> const&) override;
virtual ErrorOr<void> dump(StringBuilder&, int indent) const override;
private:
explicit NegateCalculationNode(NonnullOwnPtr<CalculationNode>);
NonnullOwnPtr<CalculationNode> m_value;
};
class InvertCalculationNode final : public CalculationNode {
public:
static ErrorOr<NonnullOwnPtr<InvertCalculationNode>> create(NonnullOwnPtr<CalculationNode>);
~InvertCalculationNode();
virtual ErrorOr<String> to_string() const override;
virtual Optional<CalculatedStyleValue::ResolvedType> resolved_type() const override;
virtual bool contains_percentage() const override;
virtual CalculatedStyleValue::CalculationResult resolve(Layout::Node const*, CalculatedStyleValue::PercentageBasis const&) const override;
virtual ErrorOr<void> for_each_child_node(Function<ErrorOr<void>(NonnullOwnPtr<CalculationNode>&)> const&) override;
virtual ErrorOr<void> dump(StringBuilder&, int indent) const override;
private:
explicit InvertCalculationNode(NonnullOwnPtr<CalculationNode>);
NonnullOwnPtr<CalculationNode> m_value;
};
}