Skip to content

Commit

Permalink
LibWeb/CSS: Use CalcSV's context to determine what percentages are
Browse files Browse the repository at this point in the history
This lets us implement the `matches_number()` and `matches_dimension()`
methods of `CSSNumericType` to spec, instead of being an ad-hoc hack.
  • Loading branch information
AtkinsSJ committed Jan 9, 2025
1 parent f15d157 commit 8daf4f3
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 78 deletions.
80 changes: 48 additions & 32 deletions Libraries/LibWeb/CSS/CSSNumericType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@
*/

#include "CSSNumericType.h"
#include <AK/HashMap.h>
#include <LibWeb/CSS/Angle.h>
#include <LibWeb/CSS/Frequency.h>
#include <LibWeb/CSS/Length.h>
#include <LibWeb/CSS/Percentage.h>
#include <LibWeb/CSS/Resolution.h>
#include <LibWeb/CSS/Time.h>

Expand Down Expand Up @@ -374,26 +372,47 @@ Optional<CSSNumericType::BaseType> CSSNumericType::entry_with_value_1_while_all_
return result;
}

static bool matches(CSSNumericType::BaseType base_type, ValueType value_type)
{
switch (base_type) {
case CSSNumericType::BaseType::Length:
return value_type == ValueType::Length;
case CSSNumericType::BaseType::Angle:
return value_type == ValueType::Angle;
case CSSNumericType::BaseType::Time:
return value_type == ValueType::Time;
case CSSNumericType::BaseType::Frequency:
return value_type == ValueType::Frequency;
case CSSNumericType::BaseType::Resolution:
return value_type == ValueType::Resolution;
case CSSNumericType::BaseType::Flex:
return value_type == ValueType::Flex;
case CSSNumericType::BaseType::Percent:
return value_type == ValueType::Percentage;
case CSSNumericType::BaseType::__Count:
default:
return false;
}
}

// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
bool CSSNumericType::matches_dimension(BaseType type) const
bool CSSNumericType::matches_dimension(BaseType type, Optional<ValueType> percentages_resolve_as) const
{
// A type matches <length> if its only non-zero entry is «[ "length" → 1 ]».
// Similarly for <angle>, <time>, <frequency>, <resolution>, and <flex>.
//
if (entry_with_value_1_while_all_others_are_0() != type)
return false;

// If the context in which the value is used allows <percentage> values, and those percentages are resolved
// against another type, then for the type to be considered matching it must either have a null percent hint,
// or the percent hint must match the other type.
//
if (percentages_resolve_as.has_value())
return !percent_hint().has_value() || matches(*percent_hint(), *percentages_resolve_as);

// If the context does not allow <percentage> values to be mixed with <length>/etc values (or doesn’t allow
// <percentage> values at all, such as border-width), then for the type to be considered matching the percent
// hint must be null.

// FIXME: Somehow we need to know what type percentages would be resolved against.
// I'm not at all sure if this check is correct.
if (percent_hint().has_value() && percent_hint() != type)
return false;

return entry_with_value_1_while_all_others_are_0() == type;
return !percent_hint().has_value();
}

// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
Expand All @@ -408,42 +427,39 @@ bool CSSNumericType::matches_percentage() const
}

// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
bool CSSNumericType::matches_dimension_percentage(BaseType type) const
bool CSSNumericType::matches_dimension_percentage(BaseType type, Optional<ValueType> percentages_resolve_as) const
{
// A type matches <length-percentage> if it matches <length> or matches <percentage>.
// Same for <angle-percentage>, <time-percentage>, etc.
return matches_percentage() || matches_dimension(type);
return matches_percentage() || matches_dimension(type, percentages_resolve_as);
}

// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
bool CSSNumericType::matches_number() const
bool CSSNumericType::matches_number(Optional<ValueType> percentages_resolve_as) const
{
// A type matches <number> if it has no non-zero entries.
//
for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
auto base_type = static_cast<BaseType>(i);
auto type_exponent = exponent(base_type);
if (type_exponent.has_value() && type_exponent != 0)
return false;
}

// If the context in which the value is used allows <percentage> values, and those percentages are resolved
// against a type other than <number>, then for the type to be considered matching the percent hint must
// either be null or match the other type.
//
if (percentages_resolve_as.has_value() && percentages_resolve_as != ValueType::Number)
return !percent_hint().has_value() || matches(*percent_hint(), *percentages_resolve_as);

// If the context allows <percentage> values, but either doesn’t resolve them against another type or resolves
// them against a <number>, then for the type to be considered matching the percent hint must either be null
// or "percent".
//
if (percentages_resolve_as == ValueType::Number)
return !percent_hint().has_value() || percent_hint() == BaseType::Percent;

// If the context does not allow <percentage> values, then for the type to be considered matching the percent
// hint must be null.

// FIXME: Somehow we need to know what type percentages would be resolved against.
// For now, just require no percent hint.
if (percent_hint().has_value())
return false;

for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
auto base_type = static_cast<BaseType>(i);
auto type_exponent = exponent(base_type);
if (type_exponent.has_value() && type_exponent != 0)
return false;
}

return true;
return !percent_hint().has_value();
}

bool CSSNumericType::matches_dimension() const
Expand Down
26 changes: 13 additions & 13 deletions Libraries/LibWeb/CSS/CSSNumericType.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,18 @@ class CSSNumericType {
Optional<CSSNumericType> consistent_type(CSSNumericType const& other) const;
Optional<CSSNumericType> made_consistent_with(CSSNumericType const& other) const;

bool matches_angle() const { return matches_dimension(BaseType::Angle); }
bool matches_angle_percentage() const { return matches_dimension_percentage(BaseType::Angle); }
bool matches_flex() const { return matches_dimension(BaseType::Flex); }
bool matches_frequency() const { return matches_dimension(BaseType::Frequency); }
bool matches_frequency_percentage() const { return matches_dimension_percentage(BaseType::Frequency); }
bool matches_length() const { return matches_dimension(BaseType::Length); }
bool matches_length_percentage() const { return matches_dimension_percentage(BaseType::Length); }
bool matches_number() const;
bool matches_angle(Optional<ValueType> percentages_resolve_as) const { return matches_dimension(BaseType::Angle, percentages_resolve_as); }
bool matches_angle_percentage(Optional<ValueType> percentages_resolve_as) const { return matches_dimension_percentage(BaseType::Angle, percentages_resolve_as); }
bool matches_flex(Optional<ValueType> percentages_resolve_as) const { return matches_dimension(BaseType::Flex, percentages_resolve_as); }
bool matches_frequency(Optional<ValueType> percentages_resolve_as) const { return matches_dimension(BaseType::Frequency, percentages_resolve_as); }
bool matches_frequency_percentage(Optional<ValueType> percentages_resolve_as) const { return matches_dimension_percentage(BaseType::Frequency, percentages_resolve_as); }
bool matches_length(Optional<ValueType> percentages_resolve_as) const { return matches_dimension(BaseType::Length, percentages_resolve_as); }
bool matches_length_percentage(Optional<ValueType> percentages_resolve_as) const { return matches_dimension_percentage(BaseType::Length, percentages_resolve_as); }
bool matches_number(Optional<ValueType> percentages_resolve_as) const;
bool matches_percentage() const;
bool matches_resolution() const { return matches_dimension(BaseType::Resolution); }
bool matches_time() const { return matches_dimension(BaseType::Time); }
bool matches_time_percentage() const { return matches_dimension_percentage(BaseType::Time); }
bool matches_resolution(Optional<ValueType> percentages_resolve_as) const { return matches_dimension(BaseType::Resolution, percentages_resolve_as); }
bool matches_time(Optional<ValueType> percentages_resolve_as) const { return matches_dimension(BaseType::Time, percentages_resolve_as); }
bool matches_time_percentage(Optional<ValueType> percentages_resolve_as) const { return matches_dimension_percentage(BaseType::Time, percentages_resolve_as); }

bool matches_dimension() const;

Expand All @@ -104,8 +104,8 @@ class CSSNumericType {
void copy_all_entries_from(CSSNumericType const& other, SkipIfAlreadyPresent);

Optional<BaseType> entry_with_value_1_while_all_others_are_0() const;
bool matches_dimension(BaseType) const;
bool matches_dimension_percentage(BaseType) const;
bool matches_dimension(BaseType, Optional<ValueType> percentages_resolve_as) const;
bool matches_dimension_percentage(BaseType, Optional<ValueType> percentages_resolve_as) const;

Array<Optional<i32>, to_underlying(BaseType::__Count)> m_type_exponents;
Optional<BaseType> m_percent_hint;
Expand Down
30 changes: 15 additions & 15 deletions Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1825,7 +1825,7 @@ bool CalculatedStyleValue::equals(CSSStyleValue const& other) const
Optional<Angle> CalculatedStyleValue::resolve_angle() const
{
auto result = m_calculation->resolve({}, {});
if (result.type().has_value() && result.type()->matches_angle())
if (result.type().has_value() && result.type()->matches_angle(m_context.percentages_resolve_as))
return Angle::make_degrees(result.value());
return {};
}
Expand All @@ -1838,47 +1838,47 @@ Optional<Angle> CalculatedStyleValue::resolve_angle(Layout::Node const& layout_n
Optional<Angle> CalculatedStyleValue::resolve_angle(Length::ResolutionContext const& context) const
{
auto result = m_calculation->resolve(context, {});
if (result.type().has_value() && result.type()->matches_angle())
if (result.type().has_value() && result.type()->matches_angle(m_context.percentages_resolve_as))
return Angle::make_degrees(result.value());
return {};
}

Optional<Angle> CalculatedStyleValue::resolve_angle_percentage(Angle const& percentage_basis) const
{
auto result = m_calculation->resolve({}, percentage_basis);
if (result.type().has_value() && result.type()->matches_angle())
if (result.type().has_value() && result.type()->matches_angle(m_context.percentages_resolve_as))
return Angle::make_degrees(result.value());
return {};
}

Optional<Flex> CalculatedStyleValue::resolve_flex() const
{
auto result = m_calculation->resolve({}, {});
if (result.type().has_value() && result.type()->matches_flex())
if (result.type().has_value() && result.type()->matches_flex(m_context.percentages_resolve_as))
return Flex::make_fr(result.value());
return {};
}

Optional<Frequency> CalculatedStyleValue::resolve_frequency() const
{
auto result = m_calculation->resolve({}, {});
if (result.type().has_value() && result.type()->matches_frequency())
if (result.type().has_value() && result.type()->matches_frequency(m_context.percentages_resolve_as))
return Frequency::make_hertz(result.value());
return {};
}

Optional<Frequency> CalculatedStyleValue::resolve_frequency_percentage(Frequency const& percentage_basis) const
{
auto result = m_calculation->resolve({}, percentage_basis);
if (result.type().has_value() && result.type()->matches_frequency())
if (result.type().has_value() && result.type()->matches_frequency(m_context.percentages_resolve_as))
return Frequency::make_hertz(result.value());
return {};
}

Optional<Length> CalculatedStyleValue::resolve_length(Length::ResolutionContext const& context) const
{
auto result = m_calculation->resolve(context, {});
if (result.type().has_value() && result.type()->matches_length())
if (result.type().has_value() && result.type()->matches_length(m_context.percentages_resolve_as))
return Length::make_px(CSSPixels { result.value() });
return {};
}
Expand All @@ -1901,7 +1901,7 @@ Optional<Length> CalculatedStyleValue::resolve_length_percentage(Layout::Node co
Optional<Length> CalculatedStyleValue::resolve_length_percentage(Length::ResolutionContext const& resolution_context, Length const& percentage_basis) const
{
auto result = m_calculation->resolve(resolution_context, percentage_basis);
if (result.type().has_value() && result.type()->matches_length())
if (result.type().has_value() && result.type()->matches_length(m_context.percentages_resolve_as))
return Length::make_px(CSSPixels { result.value() });
return {};
}
Expand All @@ -1917,39 +1917,39 @@ Optional<Percentage> CalculatedStyleValue::resolve_percentage() const
Optional<Resolution> CalculatedStyleValue::resolve_resolution() const
{
auto result = m_calculation->resolve({}, {});
if (result.type().has_value() && result.type()->matches_resolution())
if (result.type().has_value() && result.type()->matches_resolution(m_context.percentages_resolve_as))
return Resolution::make_dots_per_pixel(result.value());
return {};
}

Optional<Time> CalculatedStyleValue::resolve_time() const
{
auto result = m_calculation->resolve({}, {});
if (result.type().has_value() && result.type()->matches_time())
if (result.type().has_value() && result.type()->matches_time(m_context.percentages_resolve_as))
return Time::make_seconds(result.value());
return {};
}

Optional<Time> CalculatedStyleValue::resolve_time_percentage(Time const& percentage_basis) const
{
auto result = m_calculation->resolve({}, percentage_basis);
if (result.type().has_value() && result.type()->matches_time())
if (result.type().has_value() && result.type()->matches_time(m_context.percentages_resolve_as))
return Time::make_seconds(result.value());
return {};
}

Optional<double> CalculatedStyleValue::resolve_number() const
{
auto result = m_calculation->resolve({}, {});
if (result.type().has_value() && result.type()->matches_number())
if (result.type().has_value() && result.type()->matches_number(m_context.percentages_resolve_as))
return result.value();
return {};
}

Optional<double> CalculatedStyleValue::resolve_number(Length::ResolutionContext const& context) const
{
auto result = m_calculation->resolve(context, {});
if (result.type().has_value() && result.type()->matches_number())
if (result.type().has_value() && result.type()->matches_number(m_context.percentages_resolve_as))
return result.value();
return {};
}
Expand All @@ -1962,15 +1962,15 @@ Optional<double> CalculatedStyleValue::resolve_number(Layout::Node const& layout
Optional<i64> CalculatedStyleValue::resolve_integer() const
{
auto result = m_calculation->resolve({}, {});
if (result.type().has_value() && result.type()->matches_number())
if (result.type().has_value() && result.type()->matches_number(m_context.percentages_resolve_as))
return llround(result.value());
return {};
}

Optional<i64> CalculatedStyleValue::resolve_integer(Length::ResolutionContext const& context) const
{
auto result = m_calculation->resolve(context, {});
if (result.type().has_value() && result.type()->matches_number())
if (result.type().has_value() && result.type()->matches_number(m_context.percentages_resolve_as))
return llround(result.value());
return {};
}
Expand Down
24 changes: 13 additions & 11 deletions Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,23 @@ class CalculatedStyleValue : public CSSStyleValue {
virtual String to_string(SerializationMode) const override;
virtual bool equals(CSSStyleValue const& other) const override;

bool resolves_to_angle() const { return m_resolved_type.matches_angle(); }
bool resolves_to_angle_percentage() const { return m_resolved_type.matches_angle_percentage(); }
bool resolves_to_angle() const { return m_resolved_type.matches_angle(m_context.percentages_resolve_as); }
bool resolves_to_angle_percentage() const { return m_resolved_type.matches_angle_percentage(m_context.percentages_resolve_as); }
Optional<Angle> resolve_angle() const;
Optional<Angle> resolve_angle(Layout::Node const& layout_node) const;
Optional<Angle> resolve_angle(Length::ResolutionContext const& context) const;
Optional<Angle> resolve_angle_percentage(Angle const& percentage_basis) const;

bool resolves_to_flex() const { return m_resolved_type.matches_flex(); }
bool resolves_to_flex() const { return m_resolved_type.matches_flex(m_context.percentages_resolve_as); }
Optional<Flex> resolve_flex() const;

bool resolves_to_frequency() const { return m_resolved_type.matches_frequency(); }
bool resolves_to_frequency_percentage() const { return m_resolved_type.matches_frequency_percentage(); }
bool resolves_to_frequency() const { return m_resolved_type.matches_frequency(m_context.percentages_resolve_as); }
bool resolves_to_frequency_percentage() const { return m_resolved_type.matches_frequency_percentage(m_context.percentages_resolve_as); }
Optional<Frequency> resolve_frequency() const;
Optional<Frequency> resolve_frequency_percentage(Frequency const& percentage_basis) const;

bool resolves_to_length() const { return m_resolved_type.matches_length(); }
bool resolves_to_length_percentage() const { return m_resolved_type.matches_length_percentage(); }
bool resolves_to_length() const { return m_resolved_type.matches_length(m_context.percentages_resolve_as); }
bool resolves_to_length_percentage() const { return m_resolved_type.matches_length_percentage(m_context.percentages_resolve_as); }
Optional<Length> resolve_length(Length::ResolutionContext const&) const;
Optional<Length> resolve_length(Layout::Node const& layout_node) const;
Optional<Length> resolve_length_percentage(Layout::Node const&, Length const& percentage_basis) const;
Expand All @@ -95,15 +95,15 @@ class CalculatedStyleValue : public CSSStyleValue {
bool resolves_to_percentage() const { return m_resolved_type.matches_percentage(); }
Optional<Percentage> resolve_percentage() const;

bool resolves_to_resolution() const { return m_resolved_type.matches_resolution(); }
bool resolves_to_resolution() const { return m_resolved_type.matches_resolution(m_context.percentages_resolve_as); }
Optional<Resolution> resolve_resolution() const;

bool resolves_to_time() const { return m_resolved_type.matches_time(); }
bool resolves_to_time_percentage() const { return m_resolved_type.matches_time_percentage(); }
bool resolves_to_time() const { return m_resolved_type.matches_time(m_context.percentages_resolve_as); }
bool resolves_to_time_percentage() const { return m_resolved_type.matches_time_percentage(m_context.percentages_resolve_as); }
Optional<Time> resolve_time() const;
Optional<Time> resolve_time_percentage(Time const& percentage_basis) const;

bool resolves_to_number() const { return m_resolved_type.matches_number(); }
bool resolves_to_number() const { return m_resolved_type.matches_number(m_context.percentages_resolve_as); }
Optional<double> resolve_number() const;
Optional<double> resolve_number(Length::ResolutionContext const&) const;
Optional<double> resolve_number(Layout::Node const& layout_node) const;
Expand All @@ -126,6 +126,8 @@ class CalculatedStyleValue : public CSSStyleValue {
{
}

Optional<ValueType> percentage_resolved_type() const;

CSSNumericType m_resolved_type;
NonnullOwnPtr<CalculationNode> m_calculation;
CalculationContext m_context;
Expand Down
Loading

0 comments on commit 8daf4f3

Please sign in to comment.