diff options
-rw-r--r-- | Base/res/html/misc/calc.html | 5 | ||||
-rw-r--r-- | Tests/LibWeb/Layout/expected/grid/borders.txt | 6 | ||||
-rw-r--r-- | Tests/LibWeb/Layout/expected/grid/gap.txt | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 326 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 8 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp | 719 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h | 221 |
7 files changed, 812 insertions, 479 deletions
diff --git a/Base/res/html/misc/calc.html b/Base/res/html/misc/calc.html index 1141afdb3c..030af4519b 100644 --- a/Base/res/html/misc/calc.html +++ b/Base/res/html/misc/calc.html @@ -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> diff --git a/Tests/LibWeb/Layout/expected/grid/borders.txt b/Tests/LibWeb/Layout/expected/grid/borders.txt index 61d8110ce7..cec9df1629 100644 --- a/Tests/LibWeb/Layout/expected/grid/borders.txt +++ b/Tests/LibWeb/Layout/expected/grid/borders.txt @@ -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" diff --git a/Tests/LibWeb/Layout/expected/grid/gap.txt b/Tests/LibWeb/Layout/expected/grid/gap.txt index 3bf3f017a4..e199efc197 100644 --- a/Tests/LibWeb/Layout/expected/grid/gap.txt +++ b/Tests/LibWeb/Layout/expected/grid/gap.txt @@ -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" 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; }; } |