summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Base/res/html/misc/calc.html5
-rw-r--r--Tests/LibWeb/Layout/expected/grid/borders.txt6
-rw-r--r--Tests/LibWeb/Layout/expected/grid/gap.txt6
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp326
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.h8
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp719
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h221
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;
};
}