diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2023-04-11 15:48:06 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2023-04-13 09:53:47 +0200 |
commit | d0f80b40b2a662c9cdd697c1f4880cef83cb205d (patch) | |
tree | 4773a1586273275c58ff2c6578f73108cd60ce9e /Userland/Libraries | |
parent | 5f2f780662a493bf33d2fd799483a2ff2c62f5ea (diff) | |
download | serenity-d0f80b40b2a662c9cdd697c1f4880cef83cb205d.zip |
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.
Diffstat (limited to 'Userland/Libraries')
4 files changed, 801 insertions, 473 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 49ff0fc7be..5aa739a1eb 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -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) + auto calculation_tree = parse_a_calculation(component_values).release_value_but_fixme_should_propagate_errors(); + + 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 = calc_expression->resolved_type(); + 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(); } - if (current_token.is(Token::Type::Number)) - return CalculatedStyleValue::CalcValue { current_token.token().number() }; + 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::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 {}; -} + ComponentValue m_component_value; +}; -OwnPtr<CalculatedStyleValue::CalcProductPartWithOperator> Parser::parse_calc_product_part_with_operator(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) { - tokens.skip_whitespace(); + // 1. Discard any <whitespace-token>s from values. + // 2. An item in values is an “operator” if it’s a <delim-token> with the value "+", "-", "*", or "/". Otherwise, it’s 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 const& op_token = tokens.peek_token(); - if (!op_token.is(Token::Type::Delim)) - return nullptr; + TRY(values.try_append(Operator { static_cast<char>(value.token().delim()) })); + continue; + } + } - auto op = op_token.token().delim(); - if (op != '*' && op != '/') - return nullptr; + if (value.is(Token::Type::Number)) { + TRY(values.try_append({ TRY(NumericCalculationNode::create(value.token().number())) })); + continue; + } - tokens.next_token(); - tokens.skip_whitespace(); - auto parsed_calc_value = parse_calc_value(tokens); - if (!parsed_calc_value.has_value()) - return nullptr; + 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; + } - auto operation = op == '*' - ? CalculatedStyleValue::ProductOperation::Multiply - : CalculatedStyleValue::ProductOperation::Divide; - return make<CalculatedStyleValue::CalcProductPartWithOperator>(operation, parsed_calc_value.release_value()); -} + TRY(values.try_append({ TRY(UnparsedCalculationNode::create(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> ]*` + // If we have no values, the syntax is invalid. + if (values.is_empty()) + return nullptr; - auto first_calc_value_or_error = parse_calc_value(tokens); - if (!first_calc_value_or_error.has_value()) + // 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; - auto calc_product = make<CalculatedStyleValue::CalcProduct>( - first_calc_value_or_error.release_value(), - Vector<NonnullOwnPtr<CalculatedStyleValue::CalcProductPartWithOperator>> {}); + // 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, '*', '/'); + }); - while (tokens.has_next_token()) { - auto product_with_operator = parse_calc_product_part_with_operator(tokens); - if (!product_with_operator) + if (!first_product_operator.has_value()) break; - calc_product->zero_or_more_additional_calc_values.append(product_with_operator.release_nonnull()); + + 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) })); } - return calc_product; -} + // 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; -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 rhs_index = ++i; + auto& rhs = values[rhs_index]; - auto const& token = tokens.next_token(); - tokens.skip_whitespace(); + NonnullOwnPtr<CalculationNode> negate_node = TRY(NegateCalculationNode::create(move(rhs.get<NonnullOwnPtr<CalculationNode>>()))); + values.remove(rhs_index); + values.insert(rhs_index, move(negate_node)); + } - CalculatedStyleValue::SumOperation op; - auto delim = token.token().delim(); - if (delim == '+') - op = CalculatedStyleValue::SumOperation::Add; - else if (delim == '-') - op = CalculatedStyleValue::SumOperation::Subtract; - else - return nullptr; + // 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))); + } + } - auto calc_product = parse_calc_product(tokens); - if (!calc_product) - return nullptr; - return make<CalculatedStyleValue::CalcSumPartWithOperator>(op, calc_product.release_nonnull()); -}; + // 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 {}; -// https://www.w3.org/TR/css-values-4/#typedef-calc-sum -OwnPtr<CalculatedStyleValue::CalcSum> Parser::parse_calc_sum(TokenStream<ComponentValue>& tokens) -{ - // `<calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*` + auto& unparsed_node = static_cast<UnparsedCalculationNode&>(*node); + auto& component_value = unparsed_node.component_value(); - auto parsed_calc_product = parse_calc_product(tokens); - if (!parsed_calc_product) - return nullptr; + // 1. If leaf is a parenthesized simple block, replace leaf with the result of parsing a calculation from leaf’s 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(); + } - 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()); - } + // 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 {}; + } + } - tokens.skip_whitespace(); + return {}; + })); + + if (parsing_failed_for_child_node) + return nullptr; - return make<CalculatedStyleValue::CalcSum>(parsed_calc_product.release_nonnull(), move(additional)); + // 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) diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 76894a6f40..4b8afdde31 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -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>&); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp index 5697510909..a4886502da 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp @@ -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,58 +579,70 @@ void CalculatedStyleValue::CalculationResult::divide_by(CalculationResult const& }); } -ErrorOr<String> CalculatedStyleValue::to_string() const +void CalculatedStyleValue::CalculationResult::negate() { - return String::formatted("calc({})", TRY(m_expression->to_string())); -} - -bool CalculatedStyleValue::equals(StyleValue const& other) const -{ - if (type() != other.type()) - return false; - // This is a case where comparing the strings actually makes sense. - 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(); + 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() }; + }); } -ErrorOr<String> CalculatedStyleValue::CalcProduct::to_string() const +void CalculatedStyleValue::CalculationResult::invert() { - 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(); + // 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::CalcSumPartWithOperator::to_string() const +ErrorOr<String> CalculatedStyleValue::to_string() const { - return String::formatted(" {} {}", op == SumOperation::Add ? "+"sv : "-"sv, TRY(value->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())); } -ErrorOr<String> CalculatedStyleValue::CalcProductPartWithOperator::to_string() const +bool CalculatedStyleValue::equals(StyleValue const& other) const { - return String::formatted(" {} {}", op == ProductOperation::Multiply ? "*"sv : "/"sv, TRY(value.to_string())); + if (type() != other.type()) + return false; + // This is a case where comparing the strings actually makes sense. + return to_string().release_value_but_fixme_should_propagate_errors() == other.to_string().release_value_but_fixme_should_propagate_errors(); } 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(); } } diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h index 9e2873ed34..11b601ee5a 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h @@ -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; }; } |