LibWeb/CSS: Give calc() a CalculationContext for resolving percentages

This is passed in at construction, meaning we will be able to refer to
it later, when we're no longer inside the Parser.
This commit is contained in:
Sam Atkins 2025-01-08 16:14:17 +00:00
parent bc00ef8314
commit 4efdb76857
Notes: github-actions[bot] 2025-01-13 11:00:31 +00:00
8 changed files with 111 additions and 84 deletions

View file

@ -12,6 +12,7 @@
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
#include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
@ -60,10 +61,14 @@ ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element& ele
auto from = with_keyword_values_resolved(element, property_id, a_from);
auto to = with_keyword_values_resolved(element, property_id, a_to);
CalculationContext calculation_context {
.percentages_resolve_as = property_resolves_percentages_relative_to(property_id),
};
auto animation_type = animation_type_from_longhand_property(property_id);
switch (animation_type) {
case AnimationType::ByComputedValue:
return interpolate_value(element, from, to, delta);
return interpolate_value(element, calculation_context, from, to, delta);
case AnimationType::None:
return to;
case AnimationType::Custom: {
@ -78,7 +83,7 @@ ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element& ele
return {};
}
if (property_id == PropertyID::BoxShadow)
return interpolate_box_shadow(element, from, to, delta);
return interpolate_box_shadow(element, calculation_context, from, to, delta);
// FIXME: Handle all custom animatable properties
[[fallthrough]];
@ -422,7 +427,7 @@ Color interpolate_color(Color from, Color to, float delta)
return color;
}
NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element, CalculationContext const& calculation_context, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
{
// https://drafts.csswg.org/css-backgrounds/#box-shadow
// Animation type: by computed value, treating none as a zero-item list and appending blank shadows
@ -472,10 +477,10 @@ NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element,
auto const& to_shadow = to_shadows[i]->as_shadow();
auto result_shadow = ShadowStyleValue::create(
CSSColorValue::create_from_color(interpolate_color(from_shadow.color()->to_color({}), to_shadow.color()->to_color({}), delta)),
interpolate_value(element, from_shadow.offset_x(), to_shadow.offset_x(), delta),
interpolate_value(element, from_shadow.offset_y(), to_shadow.offset_y(), delta),
interpolate_value(element, from_shadow.blur_radius(), to_shadow.blur_radius(), delta),
interpolate_value(element, from_shadow.spread_distance(), to_shadow.spread_distance(), delta),
interpolate_value(element, calculation_context, from_shadow.offset_x(), to_shadow.offset_x(), delta),
interpolate_value(element, calculation_context, from_shadow.offset_y(), to_shadow.offset_y(), delta),
interpolate_value(element, calculation_context, from_shadow.blur_radius(), to_shadow.blur_radius(), delta),
interpolate_value(element, calculation_context, from_shadow.spread_distance(), to_shadow.spread_distance(), delta),
delta >= 0.5f ? to_shadow.placement() : from_shadow.placement());
result_shadows.unchecked_append(result_shadow);
}
@ -483,7 +488,7 @@ NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element,
return StyleValueList::create(move(result_shadows), StyleValueList::Separator::Comma);
}
NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, CalculationContext const& calculation_context, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
{
if (from.type() != to.type()) {
// Handle mixed percentage and dimension types
@ -520,18 +525,18 @@ NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, CSSS
}
};
static constexpr auto to_calculation_node = [](CSSStyleValue const& value) -> NonnullOwnPtr<CalculationNode> {
static auto to_calculation_node = [calculation_context](CSSStyleValue const& value) -> NonnullOwnPtr<CalculationNode> {
switch (value.type()) {
case CSSStyleValue::Type::Angle:
return NumericCalculationNode::create(value.as_angle().angle());
return NumericCalculationNode::create(value.as_angle().angle(), calculation_context);
case CSSStyleValue::Type::Frequency:
return NumericCalculationNode::create(value.as_frequency().frequency());
return NumericCalculationNode::create(value.as_frequency().frequency(), calculation_context);
case CSSStyleValue::Type::Length:
return NumericCalculationNode::create(value.as_length().length());
return NumericCalculationNode::create(value.as_length().length(), calculation_context);
case CSSStyleValue::Type::Percentage:
return NumericCalculationNode::create(value.as_percentage().percentage());
return NumericCalculationNode::create(value.as_percentage().percentage(), calculation_context);
case CSSStyleValue::Type::Time:
return NumericCalculationNode::create(value.as_time().time());
return NumericCalculationNode::create(value.as_time().time(), calculation_context);
default:
VERIFY_NOT_REACHED();
}
@ -546,15 +551,15 @@ NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, CSSS
// hard to understand how this interpolation works, but if instead we rewrite the values as "30px + 0%" and
// "0px + 80%", then it is very simple to understand; we just interpolate each component separately.
auto interpolated_from = interpolate_value(element, from, from_base_type_and_default->default_value, delta);
auto interpolated_to = interpolate_value(element, to_base_type_and_default->default_value, to, delta);
auto interpolated_from = interpolate_value(element, calculation_context, from, from_base_type_and_default->default_value, delta);
auto interpolated_to = interpolate_value(element, calculation_context, to_base_type_and_default->default_value, to, delta);
Vector<NonnullOwnPtr<CalculationNode>> values;
values.ensure_capacity(2);
values.unchecked_append(to_calculation_node(interpolated_from));
values.unchecked_append(to_calculation_node(interpolated_to));
auto calc_node = SumCalculationNode::create(move(values));
return CalculatedStyleValue::create(move(calc_node), CSSNumericType { to_base_type_and_default->base_type, 1 });
return CalculatedStyleValue::create(move(calc_node), CSSNumericType { to_base_type_and_default->base_type, 1 }, calculation_context);
}
return delta >= 0.5f ? to : from;
@ -587,8 +592,8 @@ NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, CSSS
auto const& from_position = from.as_position();
auto const& to_position = to.as_position();
return PositionStyleValue::create(
interpolate_value(element, from_position.edge_x(), to_position.edge_x(), delta)->as_edge(),
interpolate_value(element, from_position.edge_y(), to_position.edge_y(), delta)->as_edge());
interpolate_value(element, calculation_context, from_position.edge_x(), to_position.edge_x(), delta)->as_edge(),
interpolate_value(element, calculation_context, from_position.edge_y(), to_position.edge_y(), delta)->as_edge());
}
case CSSStyleValue::Type::Ratio: {
auto from_ratio = from.as_ratio().ratio();
@ -634,7 +639,7 @@ NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, CSSS
StyleValueVector interpolated_values;
interpolated_values.ensure_capacity(from_list.size());
for (size_t i = 0; i < from_list.size(); ++i)
interpolated_values.append(interpolate_value(element, from_list.values()[i], to_list.values()[i], delta));
interpolated_values.append(interpolate_value(element, calculation_context, from_list.values()[i], to_list.values()[i], delta));
return StyleValueList::create(move(interpolated_values), from_list.separator());
}

View file

@ -11,13 +11,15 @@
namespace Web::CSS {
struct CalculationContext;
ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element&, PropertyID, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
// https://drafts.csswg.org/css-transitions/#transitionable
bool property_values_are_transitionable(PropertyID, CSSStyleValue const& old_value, CSSStyleValue const& new_value);
NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element&, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element&, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element&, CalculationContext const&, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element&, CalculationContext const&, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
RefPtr<CSSStyleValue const> interpolate_transform(DOM::Element&, CSSStyleValue const& from, CSSStyleValue const& to, float delta);
Color interpolate_color(Color from, Color to, float delta);

View file

@ -1922,7 +1922,30 @@ RefPtr<CalculatedStyleValue> Parser::parse_calculated_value(ComponentValue const
auto const& function = component_value.function();
auto function_node = parse_a_calc_function_node(function);
CalculationContext context {};
for (auto const& value_context : m_value_context.in_reverse()) {
auto percentages_resolve_as = value_context.visit(
[](PropertyID property_id) -> Optional<ValueType> {
return property_resolves_percentages_relative_to(property_id);
},
[](FunctionContext const& function) -> Optional<ValueType> {
// Gradients resolve percentages as lengths relative to the gradient-box.
if (function.name.is_one_of_ignoring_ascii_case(
"linear-gradient"sv, "repeating-linear-gradient"sv,
"radial-gradient"sv, "repeating-radial-gradient"sv,
"conic-gradient"sv, "repeating-conic-gradient"sv)) {
return ValueType::Length;
}
// FIXME: Add other functions that provide a context for resolving percentages
return {};
});
if (percentages_resolve_as.has_value()) {
context.percentages_resolve_as = move(percentages_resolve_as);
break;
}
}
auto function_node = parse_a_calc_function_node(function, context);
if (!function_node)
return nullptr;
@ -1930,17 +1953,17 @@ RefPtr<CalculatedStyleValue> Parser::parse_calculated_value(ComponentValue const
if (!function_type.has_value())
return nullptr;
return CalculatedStyleValue::create(function_node.release_nonnull(), function_type.release_value());
return CalculatedStyleValue::create(function_node.release_nonnull(), function_type.release_value(), context);
}
OwnPtr<CalculationNode> Parser::parse_a_calc_function_node(Function const& function)
OwnPtr<CalculationNode> Parser::parse_a_calc_function_node(Function const& function, CalculationContext const& context)
{
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function.name });
if (function.name.equals_ignoring_ascii_case("calc"sv))
return parse_a_calculation(function.value);
return parse_a_calculation(function.value, context);
if (auto maybe_function = parse_math_function(function))
if (auto maybe_function = parse_math_function(function, context))
return maybe_function;
return nullptr;
@ -9231,15 +9254,15 @@ LengthOrCalculated Parser::Parser::parse_as_sizes_attribute(DOM::Element const&
return Length(100, Length::Type::Vw);
}
OwnPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node const& node)
OwnPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node const& node, CalculationContext const& context)
{
return node.visit(
[this](NonnullOwnPtr<CalcParsing::ProductNode> const& product_node) -> OwnPtr<CalculationNode> {
[this, &context](NonnullOwnPtr<CalcParsing::ProductNode> const& product_node) -> OwnPtr<CalculationNode> {
Vector<NonnullOwnPtr<CalculationNode>> children;
children.ensure_capacity(product_node->children.size());
for (auto const& child : product_node->children) {
if (auto child_as_node = convert_to_calculation_node(child)) {
if (auto child_as_node = convert_to_calculation_node(child, context)) {
children.append(child_as_node.release_nonnull());
} else {
return nullptr;
@ -9248,12 +9271,12 @@ OwnPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node co
return ProductCalculationNode::create(move(children));
},
[this](NonnullOwnPtr<CalcParsing::SumNode> const& sum_node) -> OwnPtr<CalculationNode> {
[this, &context](NonnullOwnPtr<CalcParsing::SumNode> const& sum_node) -> OwnPtr<CalculationNode> {
Vector<NonnullOwnPtr<CalculationNode>> children;
children.ensure_capacity(sum_node->children.size());
for (auto const& child : sum_node->children) {
if (auto child_as_node = convert_to_calculation_node(child)) {
if (auto child_as_node = convert_to_calculation_node(child, context)) {
children.append(child_as_node.release_nonnull());
} else {
return nullptr;
@ -9262,24 +9285,24 @@ OwnPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node co
return SumCalculationNode::create(move(children));
},
[this](NonnullOwnPtr<CalcParsing::InvertNode> const& invert_node) -> OwnPtr<CalculationNode> {
if (auto child_as_node = convert_to_calculation_node(invert_node->child))
[this, &context](NonnullOwnPtr<CalcParsing::InvertNode> const& invert_node) -> OwnPtr<CalculationNode> {
if (auto child_as_node = convert_to_calculation_node(invert_node->child, context))
return InvertCalculationNode::create(child_as_node.release_nonnull());
return nullptr;
},
[this](NonnullOwnPtr<CalcParsing::NegateNode> const& negate_node) -> OwnPtr<CalculationNode> {
if (auto child_as_node = convert_to_calculation_node(negate_node->child))
[this, &context](NonnullOwnPtr<CalcParsing::NegateNode> const& negate_node) -> OwnPtr<CalculationNode> {
if (auto child_as_node = convert_to_calculation_node(negate_node->child, context))
return NegateCalculationNode::create(child_as_node.release_nonnull());
return nullptr;
},
[this](NonnullRawPtr<ComponentValue const> const& component_value) -> OwnPtr<CalculationNode> {
[this, &context](NonnullRawPtr<ComponentValue const> const& component_value) -> OwnPtr<CalculationNode> {
// NOTE: This is the "process the leaf nodes" part of step 5 of https://drafts.csswg.org/css-values-4/#parse-a-calculation
// We divert a little from the spec: Rather than modify an existing tree of values, we construct a new one from that source tree.
// This lets us make CalculationNodes immutable.
// 1. If leaf is a parenthesized simple block, replace leaf with the result of parsing a calculation from leafs contents.
if (component_value->is_block() && component_value->block().is_paren()) {
auto leaf_calculation = parse_a_calculation(component_value->block().value);
auto leaf_calculation = parse_a_calculation(component_value->block().value, context);
if (!leaf_calculation)
return nullptr;
@ -9290,7 +9313,7 @@ OwnPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node co
// NOTE: All function tokens at this point should be math functions.
if (component_value->is_function()) {
auto const& function = component_value->function();
auto leaf_calculation = parse_a_calc_function_node(function);
auto leaf_calculation = parse_a_calc_function_node(function, context);
if (!leaf_calculation)
return nullptr;
@ -9307,17 +9330,17 @@ OwnPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node co
}
if (component_value->is(Token::Type::Number))
return NumericCalculationNode::create(component_value->token().number());
return NumericCalculationNode::create(component_value->token().number(), context);
if (component_value->is(Token::Type::Dimension)) {
auto numeric_value = component_value->token().dimension_value();
auto unit_string = component_value->token().dimension_unit();
if (auto length_type = Length::unit_from_name(unit_string); length_type.has_value())
return NumericCalculationNode::create(Length { numeric_value, length_type.release_value() });
return NumericCalculationNode::create(Length { numeric_value, length_type.release_value() }, context);
if (auto angle_type = Angle::unit_from_name(unit_string); angle_type.has_value())
return NumericCalculationNode::create(Angle { numeric_value, angle_type.release_value() });
return NumericCalculationNode::create(Angle { numeric_value, angle_type.release_value() }, context);
if (auto flex_type = Flex::unit_from_name(unit_string); flex_type.has_value()) {
// https://www.w3.org/TR/css3-grid-layout/#fr-unit
@ -9329,34 +9352,20 @@ OwnPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node co
}
if (auto frequency_type = Frequency::unit_from_name(unit_string); frequency_type.has_value())
return NumericCalculationNode::create(Frequency { numeric_value, frequency_type.release_value() });
return NumericCalculationNode::create(Frequency { numeric_value, frequency_type.release_value() }, context);
if (auto resolution_type = Resolution::unit_from_name(unit_string); resolution_type.has_value())
return NumericCalculationNode::create(Resolution { numeric_value, resolution_type.release_value() });
return NumericCalculationNode::create(Resolution { numeric_value, resolution_type.release_value() }, context);
if (auto time_type = Time::unit_from_name(unit_string); time_type.has_value())
return NumericCalculationNode::create(Time { numeric_value, time_type.release_value() });
return NumericCalculationNode::create(Time { numeric_value, time_type.release_value() }, context);
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized dimension type in calc() expression: {}", component_value->to_string());
return nullptr;
}
if (component_value->is(Token::Type::Percentage)) {
Optional<ValueType> percentage_resolved_type;
for (auto const& value_context : m_value_context.in_reverse()) {
percentage_resolved_type = value_context.visit(
[](PropertyID property_id) -> Optional<ValueType> {
return property_resolves_percentages_relative_to(property_id);
},
[](FunctionContext const&) -> Optional<ValueType> {
// FIXME: Some functions provide this. The spec mentions `media-progress()` as an example.
return {};
});
if (percentage_resolved_type.has_value())
break;
}
return NumericCalculationNode::create(Percentage { component_value->token().percentage() }, percentage_resolved_type);
}
if (component_value->is(Token::Type::Percentage))
return NumericCalculationNode::create(Percentage { component_value->token().percentage() }, context);
// NOTE: If we get here, then we have a ComponentValue that didn't get replaced with something else,
// so the calc() is invalid.
@ -9370,7 +9379,7 @@ OwnPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node co
}
// https://drafts.csswg.org/css-values-4/#parse-a-calculation
OwnPtr<CalculationNode> Parser::parse_a_calculation(Vector<ComponentValue> const& original_values)
OwnPtr<CalculationNode> Parser::parse_a_calculation(Vector<ComponentValue> const& original_values, CalculationContext const& context)
{
// 1. Discard any <whitespace-token>s from values.
// 2. An item in values is an “operator” if its a <delim-token> with the value "+", "-", "*", or "/". Otherwise, its a “value”.
@ -9483,7 +9492,7 @@ OwnPtr<CalculationNode> Parser::parse_a_calculation(Vector<ComponentValue> const
// 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.
// NOTE: We process leaf nodes as part of this conversion.
auto calculation_tree = convert_to_calculation_node(*single_value);
auto calculation_tree = convert_to_calculation_node(*single_value, context);
if (!calculation_tree)
return nullptr;

View file

@ -276,8 +276,8 @@ private:
RefPtr<CalculatedStyleValue> parse_calculated_value(ComponentValue const&);
RefPtr<CustomIdentStyleValue> parse_custom_ident_value(TokenStream<ComponentValue>&, std::initializer_list<StringView> blacklist);
// NOTE: Implemented in generated code. (GenerateCSSMathFunctions.cpp)
OwnPtr<CalculationNode> parse_math_function(Function const&);
OwnPtr<CalculationNode> parse_a_calc_function_node(Function const&);
OwnPtr<CalculationNode> parse_math_function(Function const&, CalculationContext const&);
OwnPtr<CalculationNode> parse_a_calc_function_node(Function const&, CalculationContext const&);
RefPtr<CSSStyleValue> parse_keyword_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_hue_none_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_solidus_and_alpha_value(TokenStream<ComponentValue>&);
@ -396,8 +396,8 @@ private:
RefPtr<CSSStyleValue> parse_grid_area_shorthand_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_grid_shorthand_value(TokenStream<ComponentValue>&);
OwnPtr<CalculationNode> convert_to_calculation_node(CalcParsing::Node const&);
OwnPtr<CalculationNode> parse_a_calculation(Vector<ComponentValue> const&);
OwnPtr<CalculationNode> convert_to_calculation_node(CalcParsing::Node const&, CalculationContext const&);
OwnPtr<CalculationNode> parse_a_calculation(Vector<ComponentValue> const&, CalculationContext const&);
ParseErrorOr<NonnullRefPtr<Selector>> parse_complex_selector(TokenStream<ComponentValue>&, SelectorType);
ParseErrorOr<Optional<Selector::CompoundSelector>> parse_compound_selector(TokenStream<ComponentValue>&);

View file

@ -109,7 +109,7 @@ CalculationNode::CalculationNode(Type type, Optional<CSSNumericType> numeric_typ
CalculationNode::~CalculationNode() = default;
static CSSNumericType numeric_type_from_calculated_style_value(CalculatedStyleValue::CalculationResult::Value const& value, Optional<ValueType> percentage_resolved_type)
static CSSNumericType numeric_type_from_calculated_style_value(CalculatedStyleValue::CalculationResult::Value const& value, CalculationContext const& context)
{
// https://drafts.csswg.org/css-values-4/#determine-the-type-of-a-calculation
// Anything else is a terminal value, whose type is determined based on its CSS type.
@ -152,14 +152,14 @@ static CSSNumericType numeric_type_from_calculated_style_value(CalculatedStyleVa
return CSSNumericType { CSSNumericType::BaseType::Flex, 1 };
},
// NOTE: <calc-constant> is a separate node type. (FIXME: Should it be?)
[&percentage_resolved_type](Percentage const&) {
[&context](Percentage const&) {
// -> <percentage>
// If, in the context in which the math function containing this calculation is placed,
// <percentage>s are resolved relative to another type of value (such as in width,
// where <percentage> is resolved against a <length>), and that other type is not <number>,
// the type is determined as the other type, but with a percent hint set to that other type.
if (percentage_resolved_type.has_value() && percentage_resolved_type != ValueType::Number && percentage_resolved_type != ValueType::Percentage) {
auto base_type = CSSNumericType::base_type_from_value_type(*percentage_resolved_type);
if (context.percentages_resolve_as.has_value() && context.percentages_resolve_as != ValueType::Number && context.percentages_resolve_as != ValueType::Percentage) {
auto base_type = CSSNumericType::base_type_from_value_type(*context.percentages_resolve_as);
VERIFY(base_type.has_value());
auto result = CSSNumericType { base_type.value(), 1 };
result.set_percent_hint(base_type);
@ -174,9 +174,9 @@ static CSSNumericType numeric_type_from_calculated_style_value(CalculatedStyleVa
});
}
NonnullOwnPtr<NumericCalculationNode> NumericCalculationNode::create(NumericValue value, Optional<ValueType> percentage_resolved_type)
NonnullOwnPtr<NumericCalculationNode> NumericCalculationNode::create(NumericValue value, CalculationContext const& context)
{
auto numeric_type = numeric_type_from_calculated_style_value(value, percentage_resolved_type);
auto numeric_type = numeric_type_from_calculated_style_value(value, context);
return adopt_own(*new (nothrow) NumericCalculationNode(move(value), numeric_type));
}

View file

@ -24,6 +24,11 @@ namespace Web::CSS {
class CalculationNode;
// https://drafts.csswg.org/css-values-4/#ref-for-calc-calculation%E2%91%A2%E2%91%A7
struct CalculationContext {
Optional<ValueType> percentages_resolve_as {};
};
class CalculatedStyleValue : public CSSStyleValue {
public:
using PercentageBasis = Variant<Empty, Angle, Flex, Frequency, Length, Time>;
@ -56,9 +61,9 @@ public:
Optional<CSSNumericType> m_type;
};
static ValueComparingNonnullRefPtr<CalculatedStyleValue> create(NonnullOwnPtr<CalculationNode> calculation, CSSNumericType resolved_type)
static ValueComparingNonnullRefPtr<CalculatedStyleValue> create(NonnullOwnPtr<CalculationNode> calculation, CSSNumericType resolved_type, CalculationContext context)
{
return adopt_ref(*new (nothrow) CalculatedStyleValue(move(calculation), resolved_type));
return adopt_ref(*new (nothrow) CalculatedStyleValue(move(calculation), move(resolved_type), move(context)));
}
virtual String to_string(SerializationMode) const override;
@ -113,15 +118,17 @@ public:
String dump() const;
private:
explicit CalculatedStyleValue(NonnullOwnPtr<CalculationNode> calculation, CSSNumericType resolved_type)
explicit CalculatedStyleValue(NonnullOwnPtr<CalculationNode> calculation, CSSNumericType resolved_type, CalculationContext context)
: CSSStyleValue(Type::Calculated)
, m_resolved_type(resolved_type)
, m_resolved_type(move(resolved_type))
, m_calculation(move(calculation))
, m_context(move(context))
{
}
CSSNumericType m_resolved_type;
NonnullOwnPtr<CalculationNode> m_calculation;
CalculationContext m_context;
};
// https://www.w3.org/TR/css-values-4/#calculation-tree
@ -258,7 +265,7 @@ private:
class NumericCalculationNode final : public CalculationNode {
public:
static NonnullOwnPtr<NumericCalculationNode> create(NumericValue, Optional<ValueType> percentage_resolved_type = {});
static NonnullOwnPtr<NumericCalculationNode> create(NumericValue, CalculationContext const&);
~NumericCalculationNode();
virtual String to_string() const override;

View file

@ -16,15 +16,19 @@ String EdgeStyleValue::to_string(SerializationMode mode) const
auto flipped_percentage = 100 - offset().percentage().value();
return Percentage(flipped_percentage).to_string();
}
// FIXME: Figure out how to get the proper calculation context here
CalculationContext context = {};
Vector<NonnullOwnPtr<CalculationNode>> sum_parts;
sum_parts.append(NumericCalculationNode::create(Percentage(100)));
sum_parts.append(NumericCalculationNode::create(Percentage(100), context));
if (offset().is_length()) {
sum_parts.append(NegateCalculationNode::create(NumericCalculationNode::create(offset().length())));
sum_parts.append(NegateCalculationNode::create(NumericCalculationNode::create(offset().length(), context)));
} else {
// FIXME: Flip calculated offsets (convert CalculatedStyleValue to CalculationNode, then negate and append)
return to_string(CSSStyleValue::SerializationMode::Normal);
}
auto flipped_absolute = CalculatedStyleValue::create(SumCalculationNode::create(move(sum_parts)), CSSNumericType(CSSNumericType::BaseType::Length, 1));
auto flipped_absolute = CalculatedStyleValue::create(SumCalculationNode::create(move(sum_parts)), CSSNumericType(CSSNumericType::BaseType::Length, 1), context);
return flipped_absolute->to_string(mode);
}
return offset().to_string();

View file

@ -116,7 +116,7 @@ static Optional<RoundingStrategy> parse_rounding_strategy(Vector<ComponentValue>
return keyword_to_rounding_strategy(maybe_keyword.value());
}
OwnPtr<CalculationNode> Parser::parse_math_function(Function const& function)
OwnPtr<CalculationNode> Parser::parse_math_function(Function const& function, CalculationContext const& context)
{
TokenStream stream { function.value };
auto arguments = parse_a_comma_separated_list_of_component_values(stream);
@ -140,7 +140,7 @@ OwnPtr<CalculationNode> Parser::parse_math_function(Function const& function)
parsed_arguments.ensure_capacity(arguments.size());
for (auto& argument : arguments) {
auto calculation_node = parse_a_calculation(argument);
auto calculation_node = parse_a_calculation(argument, context);
if (!calculation_node) {
dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() argument #{} is not a valid calculation", parsed_arguments.size());
return nullptr;
@ -243,7 +243,7 @@ OwnPtr<CalculationNode> Parser::parse_math_function(Function const& function)
// NOTE: This assumes everything not handled above is a calculation node of some kind.
parameter_is_calculation = true;
parameter_generator.set("parameter_type", "OwnPtr<CalculationNode>"_string);
parameter_generator.set("parse_function", "parse_a_calculation(arguments[argument_index])"_string);
parameter_generator.set("parse_function", "parse_a_calculation(arguments[argument_index], context)"_string);
parameter_generator.set("check_function", " != nullptr"_string);
parameter_generator.set("release_function", ".release_nonnull()"_string);