LibWeb: Set up initial infrastructure for non-math functions in calc

There are some non-math functions (such as tree counting functions)
which we should allow within `calc()`s . This commit implements the
initial infrastructure for this.

We don't yet parse any of these non-math functions in
`parse_a_calculation` so there is no functional change.
This commit is contained in:
Callum Law 2025-09-30 16:39:23 +13:00 committed by Tim Ledbetter
commit 55bcdcf824
Notes: github-actions[bot] 2025-10-20 15:13:37 +00:00
5 changed files with 89 additions and 9 deletions

View file

@ -14,8 +14,6 @@
namespace Web::CSS {
struct CalculationContext;
enum class AllowDiscrete {
Yes,
No,

View file

@ -19,6 +19,7 @@
#include <AK/StringConversions.h>
#include <AK/TemporaryChange.h>
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/MathFunctions.h>
#include <LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h>
#include <LibWeb/CSS/Parser/ErrorReporter.h>
#include <LibWeb/CSS/Parser/Parser.h>
@ -4249,8 +4250,7 @@ RefPtr<CalculationNode const> Parser::convert_to_calculation_node(CalcParsing::N
}
// 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.
if (component_value->is_function()) {
if (component_value->is_function() && math_function_from_string(component_value->function().name).has_value()) {
auto const& function = component_value->function();
auto leaf_calculation = parse_a_calc_function_node(function, context);
if (!leaf_calculation)

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2025, Callum Law <callumlaw1709@outlook.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/StyleValues/StyleValue.h>
namespace Web::CSS {
class AbstractNonMathCalcFunctionStyleValue : public StyleValue {
using StyleValue::StyleValue;
public:
virtual RefPtr<CalculationNode const> resolve_to_calculation_node(CalculationContext const&, CalculationResolutionContext const&) const = 0;
};
}

View file

@ -23,6 +23,7 @@
#include <LibWeb/CSS/Percentage.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/PropertyNameAndID.h>
#include <LibWeb/CSS/StyleValues/AbstractNonMathCalcFunctionStyleValue.h>
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
#include <LibWeb/CSS/StyleValues/FlexStyleValue.h>
#include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
@ -411,10 +412,12 @@ static String serialize_a_calculation_tree(CalculationNode const& root, Calculat
// NOTE: Already the case.
// 2. If root is a numeric value, or a non-math function, serialize root per the normal rules for it and return the result.
// FIXME: Support non-math functions in calculation trees.
if (root.type() == CalculationNode::Type::Numeric)
return static_cast<NumericCalculationNode const&>(root).value_to_string();
if (root.type() == CalculationNode::Type::NonMathFunction)
return as<NonMathFunctionCalculationNode>(root).function()->to_string(serialization_mode);
// 3. If root is anything but a Sum, Negate, Product, or Invert node, serialize a math function for the function
// corresponding to the node type, treating the nodes children as the functions comma-separated calculation
// arguments, and return the result.
@ -580,6 +583,7 @@ StringView CalculationNode::name() const
case Type::Product:
case Type::Negate:
case Type::Invert:
case Type::NonMathFunction:
return "calc"sv;
}
VERIFY_NOT_REACHED();
@ -2473,6 +2477,35 @@ bool RemCalculationNode::equals(CalculationNode const& other) const
&& m_y->equals(*static_cast<RemCalculationNode const&>(other).m_y);
}
NonnullRefPtr<NonMathFunctionCalculationNode const> NonMathFunctionCalculationNode::create(AbstractNonMathCalcFunctionStyleValue const& function, NumericType numeric_type)
{
return adopt_ref(*new (nothrow) NonMathFunctionCalculationNode(move(function), move(numeric_type)));
}
NonMathFunctionCalculationNode::NonMathFunctionCalculationNode(AbstractNonMathCalcFunctionStyleValue const& function, NumericType numeric_type)
: CalculationNode(Type::NonMathFunction, numeric_type)
, m_function(function)
{
}
NonMathFunctionCalculationNode::~NonMathFunctionCalculationNode() = default;
void NonMathFunctionCalculationNode::dump(StringBuilder& builder, int indent) const
{
builder.appendff("{: >{}}NON-MATH FUNCTION: {}", "", indent, m_function->to_string(SerializationMode::Normal));
}
bool NonMathFunctionCalculationNode::equals(CalculationNode const& other) const
{
if (this == &other)
return true;
if (type() != other.type())
return false;
return static_cast<NonMathFunctionCalculationNode const&>(other).function() == m_function;
}
CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalculationResult::from_value(Value const& value, CalculationResolutionContext const& context, Optional<NumericType> numeric_type)
{
auto number = value.visit(
@ -2931,7 +2964,15 @@ NonnullRefPtr<CalculationNode const> simplify_a_calculation_tree(CalculationNode
}
// 2. If root is any other leaf node (not an operator node):
// FIXME: We don't yet allow any of these inside a calculation tree. Revisit once we do.
if (root->type() == CalculationNode::Type::NonMathFunction) {
// 1. If there is enough information available to determine its numeric value, return its value, expressed in
// the values canonical unit.
if (auto resolved_calculation_node = as<NonMathFunctionCalculationNode>(*root).function()->resolve_to_calculation_node(context, resolution_context))
return resolved_calculation_node.release_nonnull();
// 2. Otherwise, return root.
return root;
}
// 3. At this point, root is an operator node. Simplify all the calculation children of root.
root = root->with_simplified_children(context, resolution_context);

View file

@ -19,13 +19,12 @@
#include <LibWeb/CSS/NumericType.h>
#include <LibWeb/CSS/Percentage.h>
#include <LibWeb/CSS/Resolution.h>
#include <LibWeb/CSS/StyleValues/AbstractNonMathCalcFunctionStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValue.h>
#include <LibWeb/CSS/Time.h>
namespace Web::CSS {
class CalculationNode;
// https://drafts.csswg.org/css-values-4/#calc-context
// Contains the context available at parse-time.
struct CalculationContext {
@ -146,7 +145,7 @@ 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.
// UnresolvedStyleValue so we do not have to implement them as CalculationNodes.
// Comparison function nodes, a sub-type of operator node
// https://drafts.csswg.org/css-values-4/#comp-func
@ -189,6 +188,9 @@ public:
Round,
Mod,
Rem,
// Non-math functions
NonMathFunction
};
using NumericValue = CalculatedStyleValue::CalculationResult::Value;
@ -764,6 +766,25 @@ private:
NonnullRefPtr<CalculationNode const> m_y;
};
class NonMathFunctionCalculationNode final : public CalculationNode {
public:
static NonnullRefPtr<NonMathFunctionCalculationNode const> create(AbstractNonMathCalcFunctionStyleValue const&, NumericType);
~NonMathFunctionCalculationNode();
virtual bool contains_percentage() const override { return false; }
virtual NonnullRefPtr<CalculationNode const> with_simplified_children(CalculationContext const&, CalculationResolutionContext const&) const override { return *this; }
virtual Vector<NonnullRefPtr<CalculationNode const>> children() const override { return {}; }
virtual void dump(StringBuilder&, int indent) const override;
virtual bool equals(CalculationNode const&) const override;
ValueComparingNonnullRefPtr<AbstractNonMathCalcFunctionStyleValue const> function() const { return m_function; }
private:
NonMathFunctionCalculationNode(AbstractNonMathCalcFunctionStyleValue const& function, NumericType);
ValueComparingNonnullRefPtr<AbstractNonMathCalcFunctionStyleValue const> m_function;
};
// https://drafts.csswg.org/css-values-4/#calc-simplification
NonnullRefPtr<CalculationNode const> simplify_a_calculation_tree(CalculationNode const& root, CalculationContext const& context, CalculationResolutionContext const& resolution_context);