LibWeb: Absolutize StyleValues before computing font properties

We also avoid prematurely constructing CSSPixels when computing
font-size which gains us a couple of test passes
This commit is contained in:
Callum Law 2025-10-03 23:16:30 +13:00 committed by Tim Ledbetter
parent ca9d107a1a
commit 28451b16c9
5 changed files with 53 additions and 45 deletions

View File

@ -18,7 +18,7 @@ struct CalculationResolutionContext {
using PercentageBasis = Variant<Empty, Angle, Frequency, Length, Time>;
PercentageBasis percentage_basis {};
Optional<Length::ResolutionContext> length_resolution_context;
Optional<Length::ResolutionContext> length_resolution_context {};
static CalculationResolutionContext from_computation_context(ComputationContext const& computation_context, PercentageBasis percentage_basis = {})
{

View File

@ -3381,29 +3381,31 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_size(NonnullRefPtr<S
// https://drafts.csswg.org/css-fonts/#font-size-prop
// an absolute length
auto const& absolutized_value = specified_value->absolutized(computation_context);
// <absolute-size>
if (auto absolute_size = keyword_to_absolute_size(specified_value->to_keyword()); absolute_size.has_value())
if (auto absolute_size = keyword_to_absolute_size(absolutized_value->to_keyword()); absolute_size.has_value())
return LengthStyleValue::create(Length::make_px(absolute_size_mapping(absolute_size.value(), default_user_font_size())));
// <relative-size>
if (auto relative_size = keyword_to_relative_size(specified_value->to_keyword()); relative_size.has_value())
if (auto relative_size = keyword_to_relative_size(absolutized_value->to_keyword()); relative_size.has_value())
return LengthStyleValue::create(Length::make_px(relative_size_mapping(relative_size.value(), inherited_font_size)));
// <length-percentage [0,∞]>
// A length value specifies an absolute font size (independent of the user agents font table). Negative lengths are invalid.
if (specified_value->is_length())
return LengthStyleValue::create(Length::make_px(max(CSSPixels { 0 }, specified_value->as_length().length().to_px(computation_context.length_resolution_context))));
if (absolutized_value->is_length())
return absolutized_value;
// A percentage value specifies an absolute font size relative to the parent elements computed font-size. Negative percentages are invalid.
if (specified_value->is_percentage())
return LengthStyleValue::create(Length::make_px(inherited_font_size * specified_value->as_percentage().percentage().as_fraction()));
if (absolutized_value->is_percentage())
return LengthStyleValue::create(Length::make_px(inherited_font_size * absolutized_value->as_percentage().percentage().as_fraction()));
if (specified_value->is_calculated())
return LengthStyleValue::create(specified_value->as_calculated().resolve_length(CalculationResolutionContext::from_computation_context(computation_context, Length(1, LengthUnit::Em))).value());
if (absolutized_value->is_calculated())
return LengthStyleValue::create(absolutized_value->as_calculated().resolve_length({ .percentage_basis = Length::make_px(inherited_font_size) }).value());
// math
// Special mathematical scaling rules must be applied when determining the computed value of the font-size property.
if (specified_value->to_keyword() == Keyword::Math) {
if (absolutized_value->to_keyword() == Keyword::Math) {
auto math_scaling_factor = [&]() {
// https://w3c.github.io/mathml-core/#the-math-script-level-property
// If the specified value font-size is math then the computed value of font-size is obtained by multiplying
@ -3439,7 +3441,7 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_size(NonnullRefPtr<S
return 1.0 / scale;
}();
return LengthStyleValue::create(Length::make_px(inherited_font_size.scale_by(math_scaling_factor)));
return LengthStyleValue::create(Length::make_px(inherited_font_size.scaled(math_scaling_factor)));
}
VERIFY_NOT_REACHED();
@ -3473,22 +3475,24 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_weight(NonnullRefPtr
// https://drafts.csswg.org/css-fonts-4/#font-weight-prop
// a number, see below
auto const& absolutized_value = specified_value->absolutized(computation_context);
// <number [1,1000]>
if (specified_value->is_number())
return specified_value;
if (absolutized_value->is_number())
return absolutized_value;
// AD-HOC: Anywhere we support a numbers we should also support calcs
if (specified_value->is_calculated())
return NumberStyleValue::create(specified_value->as_calculated().resolve_number(CalculationResolutionContext::from_computation_context(computation_context)).value());
if (absolutized_value->is_calculated())
return NumberStyleValue::create(absolutized_value->as_calculated().resolve_number({}).value());
// normal
// Same as 400.
if (specified_value->to_keyword() == Keyword::Normal)
if (absolutized_value->to_keyword() == Keyword::Normal)
return NumberStyleValue::create(400);
// bold
// Same as 700.
if (specified_value->to_keyword() == Keyword::Bold)
if (absolutized_value->to_keyword() == Keyword::Bold)
return NumberStyleValue::create(700);
// Specified values of bolder and lighter indicate weights relative to the weight of the parent element. The
@ -3504,7 +3508,7 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_weight(NonnullRefPtr
// bolder
// Specifies a bolder weight than the inherited value. See §2.2.1 Relative Weights.
if (specified_value->to_keyword() == Keyword::Bolder) {
if (absolutized_value->to_keyword() == Keyword::Bolder) {
if (inherited_font_weight < 350)
return NumberStyleValue::create(400);
@ -3519,7 +3523,7 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_weight(NonnullRefPtr
// lighter
// Specifies a lighter weight than the inherited value. See §2.2.1 Relative Weights.
if (specified_value->to_keyword() == Keyword::Lighter) {
if (absolutized_value->to_keyword() == Keyword::Lighter) {
if (inherited_font_weight < 100)
return NumberStyleValue::create(inherited_font_weight);
@ -3540,15 +3544,17 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_width(NonnullRefPtr<
// https://drafts.csswg.org/css-fonts-4/#font-width-prop
// a percentage, see below
auto absolutized_value = specified_value->absolutized(computation_context);
// <percentage [0,∞]>
if (specified_value->is_percentage())
return specified_value;
if (absolutized_value->is_percentage())
return absolutized_value;
// AD-HOC: We support calculated percentages as well
if (specified_value->is_calculated())
return PercentageStyleValue::create(specified_value->as_calculated().resolve_percentage(CalculationResolutionContext::from_computation_context(computation_context)).value());
if (absolutized_value->is_calculated())
return PercentageStyleValue::create(absolutized_value->as_calculated().resolve_percentage({}).value());
switch (specified_value->to_keyword()) {
switch (absolutized_value->to_keyword()) {
// ultra-condensed 50%
case Keyword::UltraCondensed:
return PercentageStyleValue::create(Percentage(50));
@ -3584,29 +3590,26 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_width(NonnullRefPtr<
NonnullRefPtr<StyleValue const> StyleComputer::compute_line_height(NonnullRefPtr<StyleValue const> const& specified_value, ComputationContext const& computation_context)
{
// https://drafts.csswg.org/css-inline-3/#line-height-property
// normal
if (specified_value->to_keyword() == Keyword::Normal)
return specified_value;
auto absolutized_value = specified_value->absolutized(computation_context);
// normal
// <length [0,∞]>
if (specified_value->is_length())
return specified_value->absolutized(computation_context);
// <number [0,∞]>
if (absolutized_value->to_keyword() == Keyword::Normal || absolutized_value->is_length() || absolutized_value->is_number())
return absolutized_value;
// NOTE: We also support calc()'d lengths (percentages resolve to lengths so we don't have to handle them separately)
if (specified_value->is_calculated() && specified_value->as_calculated().resolves_to_length_percentage())
return LengthStyleValue::create(specified_value->as_calculated().resolve_length(CalculationResolutionContext::from_computation_context(computation_context, Length(1, LengthUnit::Em))).value());
// <number [0,∞]>
if (specified_value->is_number())
return specified_value;
if (absolutized_value->is_calculated() && absolutized_value->as_calculated().resolves_to_length_percentage())
return LengthStyleValue::create(absolutized_value->as_calculated().resolve_length({ .percentage_basis = Length::make_px(computation_context.length_resolution_context.font_metrics.font_size) }).value());
// NOTE: We also support calc()'d numbers
if (specified_value->is_calculated() && specified_value->as_calculated().resolves_to_number())
return NumberStyleValue::create(specified_value->as_calculated().resolve_number(CalculationResolutionContext::from_computation_context(computation_context, Length(1, LengthUnit::Em))).value());
if (absolutized_value->is_calculated() && absolutized_value->as_calculated().resolves_to_number())
return NumberStyleValue::create(absolutized_value->as_calculated().resolve_number({ .percentage_basis = Length::make_px(computation_context.length_resolution_context.font_metrics.font_size) }).value());
// <percentage [0,∞]>
if (specified_value->is_percentage())
return LengthStyleValue::create(Length::make_px(computation_context.length_resolution_context.font_metrics.font_size * specified_value->as_percentage().percentage().as_fraction()));
if (absolutized_value->is_percentage())
return LengthStyleValue::create(Length::make_px(computation_context.length_resolution_context.font_metrics.font_size * absolutized_value->as_percentage().percentage().as_fraction()));
VERIFY_NOT_REACHED();
}

View File

@ -127,6 +127,10 @@ private:
double value;
Optional<NumericType> type;
};
// FIXME: Calculations should be simplified apart from percentages by the absolutized method prior to this method
// being called so we can take just the percentage_basis rather than a full CalculationResolutionContext.
// There are still some CalculatedStyleValues which we don't call absolutized for (i.e. sub-values of other
// StyleValue classes which lack their own absolutized method) which will need to be fixed beforehand.
Optional<ResolvedValue> resolve_value(CalculationResolutionContext const&) const;
Optional<ValueType> percentage_resolved_type() const;

View File

@ -112,6 +112,8 @@ public:
// NOTE: The initial value here is non-standard as the default font is "10px sans-serif"
auto inherited_font_size = CSSPixels { 10 };
auto inherited_font_weight = CSS::InitialValues::font_weight();
// FIXME: Investigate whether this is the correct resolution context (i.e. whether we should instead use
// a font-size of 10px) for OffscreenCanvas
auto length_resolution_context = CSS::Length::ResolutionContext::for_window(*document->window());
if constexpr (SameAs<CanvasType, HTML::HTMLCanvasElement>) {

View File

@ -2,8 +2,7 @@ Harness status: OK
Found 140 tests
136 Pass
4 Fail
140 Pass
Pass CSS Transitions: property <font-size> from neutral to [20px] at (-2) should be [0px]
Pass CSS Transitions: property <font-size> from neutral to [20px] at (-0.3) should be [7px]
Pass CSS Transitions: property <font-size> from neutral to [20px] at (0) should be [10px]
@ -36,28 +35,28 @@ Pass CSS Transitions: property <font-size> from [initial] to [20px] at (-2) shou
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px]
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (0) should be [16px]
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px]
Fail CSS Transitions: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (1) should be [20px]
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (1.5) should be [22px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (-2) should be [8px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0) should be [16px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px]
Fail CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (1) should be [20px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (1.5) should be [22px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (-2) should be [8px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (0) should be [16px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px]
Fail CSS Animations: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (1) should be [20px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (1.5) should be [22px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (-2) should be [8px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (0) should be [16px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px]
Fail Web Animations: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (1) should be [20px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (1.5) should be [22px]
Pass CSS Transitions: property <font-size> from [inherit] to [20px] at (-2) should be [50px]