mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 19:17:44 +00:00
LibWeb: Use fixed-point saturated arithmetics for CSSPixels
Using fixed-point saturated arithmetics for CSSPixels allows to avoid accumulating floating-point errors. This implementation is not complete yet: currently saturated arithmetics implemented only for addition. But it is enough to not regress any of layout tests we have :) See https://github.com/SerenityOS/serenity/issues/18566
This commit is contained in:
parent
5cdd03fc53
commit
bec07d4af7
136 changed files with 1938 additions and 1844 deletions
|
@ -490,6 +490,7 @@ set(SOURCES
|
|||
PerformanceTimeline/EntryTypes.cpp
|
||||
PerformanceTimeline/PerformanceEntry.cpp
|
||||
PermissionsPolicy/AutoplayAllowlist.cpp
|
||||
PixelUnits.cpp
|
||||
Platform/AudioCodecPlugin.cpp
|
||||
Platform/EventLoopPlugin.cpp
|
||||
Platform/EventLoopPluginSerenity.cpp
|
||||
|
|
|
@ -987,7 +987,7 @@ void FlexFormattingContext::resolve_flexible_lengths_for_line(FlexLine& line)
|
|||
|
||||
// AD-HOC: We allow the remaining free space to be infinite, but we can't let infinity
|
||||
// leak into the layout geometry, so we treat infinity as zero when used in arithmetic.
|
||||
auto remaining_free_space_or_zero_if_infinite = isfinite(line.remaining_free_space.to_double()) ? line.remaining_free_space : 0;
|
||||
auto remaining_free_space_or_zero_if_infinite = !line.remaining_free_space.might_be_saturated() ? line.remaining_free_space : 0;
|
||||
|
||||
// c. If the remaining free space is non-zero, distribute it proportional to the flex factors:
|
||||
if (line.remaining_free_space != 0) {
|
||||
|
@ -1096,7 +1096,7 @@ void FlexFormattingContext::resolve_flexible_lengths_for_line(FlexLine& line)
|
|||
|
||||
// AD-HOC: Due to the way we calculate the remaining free space, it can be infinite when sizing
|
||||
// under a max-content constraint. In that case, we can simply set it to zero here.
|
||||
if (!isfinite(line.remaining_free_space.to_double()))
|
||||
if (line.remaining_free_space.might_be_saturated())
|
||||
line.remaining_free_space = 0;
|
||||
|
||||
// 6. Set each item’s used main size to its target main size.
|
||||
|
|
|
@ -803,7 +803,7 @@ void GridFormattingContext::distribute_extra_space_across_spanned_tracks_base_si
|
|||
// 2. Distribute space up to limits:
|
||||
// FIXME: If a fixed-point type were used to represent CSS pixels, it would be possible to compare with 0
|
||||
// instead of epsilon.
|
||||
while (extra_space > NumericLimits<double>().epsilon()) {
|
||||
while (extra_space > CSSPixels::epsilon()) {
|
||||
auto all_frozen = all_of(affected_tracks, [](auto const& track) { return track.base_size_frozen; });
|
||||
if (all_frozen)
|
||||
break;
|
||||
|
@ -893,7 +893,7 @@ void GridFormattingContext::distribute_extra_space_across_spanned_tracks_growth_
|
|||
// 2. Distribute space up to limits:
|
||||
// FIXME: If a fixed-point type were used to represent CSS pixels, it would be possible to compare with 0
|
||||
// instead of epsilon.
|
||||
while (extra_space > NumericLimits<double>().epsilon()) {
|
||||
while (extra_space > CSSPixels::epsilon()) {
|
||||
auto all_frozen = all_of(affected_tracks, [](auto const& track) { return track.growth_limit_frozen; });
|
||||
if (all_frozen)
|
||||
break;
|
||||
|
|
|
@ -173,7 +173,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available
|
|||
|
||||
// The initial value for preserveAspectRatio is xMidYMid meet.
|
||||
auto preserve_aspect_ratio = svg_svg_element.preserve_aspect_ratio().value_or(SVG::PreserveAspectRatio {});
|
||||
auto viewbox_transform = scale_and_align_viewbox_content(preserve_aspect_ratio, view_box, { scale_width.to_double(), scale_height.to_double() }, svg_box_state);
|
||||
auto viewbox_transform = scale_and_align_viewbox_content(preserve_aspect_ratio, view_box, { scale_width, scale_height }, svg_box_state);
|
||||
path_transform = Gfx::AffineTransform {}.translate(viewbox_transform.offset.to_type<double>().to_type<float>()).scale(viewbox_transform.scale_factor, viewbox_transform.scale_factor).translate({ -view_box.min_x, -view_box.min_y }).multiply(path_transform);
|
||||
viewbox_scale = viewbox_transform.scale_factor;
|
||||
}
|
||||
|
|
|
@ -94,8 +94,8 @@ CSSPixelPoint Page::device_to_css_point(DevicePixelPoint point) const
|
|||
DevicePixelPoint Page::css_to_device_point(CSSPixelPoint point) const
|
||||
{
|
||||
return {
|
||||
(point.x() * client().device_pixels_per_css_pixel()).to_int(),
|
||||
(point.y() * client().device_pixels_per_css_pixel()).to_int(),
|
||||
point.x() * client().device_pixels_per_css_pixel(),
|
||||
point.y() * client().device_pixels_per_css_pixel(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -447,7 +447,7 @@ static void paint_text_decoration(PaintContext& context, Gfx::Painter& painter,
|
|||
if (computed_thickness.is_auto())
|
||||
return max(glyph_height * 0.1, 1.);
|
||||
|
||||
return computed_thickness.to_px(text_node);
|
||||
return computed_thickness.to_px(text_node).to_double();
|
||||
}();
|
||||
auto device_line_thickness = context.rounded_device_pixels(css_line_thickness);
|
||||
|
||||
|
|
169
Userland/Libraries/LibWeb/PixelUnits.cpp
Normal file
169
Userland/Libraries/LibWeb/PixelUnits.cpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Math.h>
|
||||
#include <LibWeb/PixelUnits.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
static i32 const fractional_bits = 6;
|
||||
static constexpr i32 fixed_point_denominator = 1 << fractional_bits;
|
||||
|
||||
CSSPixels::CSSPixels(int value)
|
||||
{
|
||||
m_value = value * fixed_point_denominator;
|
||||
}
|
||||
|
||||
CSSPixels::CSSPixels(unsigned int value)
|
||||
{
|
||||
m_value = value * fixed_point_denominator;
|
||||
}
|
||||
|
||||
CSSPixels::CSSPixels(unsigned long value)
|
||||
{
|
||||
m_value = value * fixed_point_denominator;
|
||||
}
|
||||
|
||||
CSSPixels::CSSPixels(float value)
|
||||
{
|
||||
if (!isnan(value))
|
||||
m_value = AK::clamp_to_int(value * fixed_point_denominator);
|
||||
}
|
||||
|
||||
CSSPixels::CSSPixels(double value)
|
||||
{
|
||||
if (!isnan(value))
|
||||
m_value = AK::clamp_to_int(value * fixed_point_denominator);
|
||||
}
|
||||
|
||||
float CSSPixels::to_float() const
|
||||
{
|
||||
return static_cast<float>(m_value) / fixed_point_denominator;
|
||||
}
|
||||
|
||||
double CSSPixels::to_double() const
|
||||
{
|
||||
return static_cast<double>(m_value) / fixed_point_denominator;
|
||||
}
|
||||
|
||||
int CSSPixels::to_int() const
|
||||
{
|
||||
return m_value / fixed_point_denominator;
|
||||
}
|
||||
|
||||
bool CSSPixels::might_be_saturated() const
|
||||
{
|
||||
return raw_value() == NumericLimits<i32>::max() || raw_value() == NumericLimits<i32>::min();
|
||||
}
|
||||
|
||||
bool CSSPixels::operator==(CSSPixels const& other) const
|
||||
{
|
||||
return raw_value() == other.raw_value();
|
||||
}
|
||||
|
||||
CSSPixels& CSSPixels::operator++()
|
||||
{
|
||||
m_value += fixed_point_denominator;
|
||||
return *this;
|
||||
}
|
||||
CSSPixels& CSSPixels::operator--()
|
||||
{
|
||||
m_value -= fixed_point_denominator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int CSSPixels::operator<=>(CSSPixels const& other) const
|
||||
{
|
||||
return raw_value() > other.raw_value() ? 1 : raw_value() < other.raw_value() ? -1
|
||||
: 0;
|
||||
}
|
||||
|
||||
CSSPixels CSSPixels::operator+() const
|
||||
{
|
||||
CSSPixels result;
|
||||
result.set_raw_value(+raw_value());
|
||||
return result;
|
||||
}
|
||||
|
||||
CSSPixels CSSPixels::operator-() const
|
||||
{
|
||||
CSSPixels result;
|
||||
result.set_raw_value(-raw_value());
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline int saturated_addition(int a, int b)
|
||||
{
|
||||
i32 overflow = (b > 0 && a > NumericLimits<i32>::max() - b);
|
||||
i32 underflow = (b < 0 && a < NumericLimits<i32>::min() - b);
|
||||
return overflow ? NumericLimits<i32>::max() : (underflow ? NumericLimits<i32>::min() : a + b);
|
||||
}
|
||||
|
||||
CSSPixels CSSPixels::operator+(CSSPixels const& other) const
|
||||
{
|
||||
CSSPixels result;
|
||||
result.set_raw_value(saturated_addition(raw_value(), other.raw_value()));
|
||||
return result;
|
||||
}
|
||||
|
||||
CSSPixels CSSPixels::operator-(CSSPixels const& other) const
|
||||
{
|
||||
CSSPixels result;
|
||||
result.set_raw_value(raw_value() - other.raw_value());
|
||||
return result;
|
||||
}
|
||||
|
||||
CSSPixels CSSPixels::operator*(CSSPixels const& other) const
|
||||
{
|
||||
CSSPixels result;
|
||||
result.set_raw_value((raw_value() * other.raw_value()) >> fractional_bits);
|
||||
return result;
|
||||
}
|
||||
|
||||
CSSPixels CSSPixels::operator/(CSSPixels const& other) const
|
||||
{
|
||||
CSSPixels result;
|
||||
result.set_raw_value(static_cast<long long>(fixed_point_denominator) * raw_value() / other.raw_value());
|
||||
return result;
|
||||
}
|
||||
|
||||
CSSPixels& CSSPixels::operator+=(CSSPixels const& other)
|
||||
{
|
||||
*this = *this + other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CSSPixels& CSSPixels::operator-=(CSSPixels const& other)
|
||||
{
|
||||
*this = *this - other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CSSPixels& CSSPixels::operator*=(CSSPixels const& other)
|
||||
{
|
||||
*this = *this * other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CSSPixels& CSSPixels::operator/=(CSSPixels const& other)
|
||||
{
|
||||
*this = *this / other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CSSPixels CSSPixels::abs() const
|
||||
{
|
||||
CSSPixels result;
|
||||
result.set_raw_value(::abs(m_value));
|
||||
return result;
|
||||
}
|
||||
|
||||
float CSSPixels::epsilon()
|
||||
{
|
||||
return 1.0f / fixed_point_denominator;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
* Copyright (c) 2012-2023, Apple Inc. All rights reserved.
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -50,154 +52,78 @@ constexpr DevicePixels operator%(DevicePixels left, T right) { return left.value
|
|||
/// See https://www.w3.org/TR/css-values-3/#reference-pixel
|
||||
class CSSPixels {
|
||||
public:
|
||||
constexpr CSSPixels() = default;
|
||||
CSSPixels() = default;
|
||||
CSSPixels(int value);
|
||||
CSSPixels(unsigned int value);
|
||||
CSSPixels(unsigned long value);
|
||||
CSSPixels(float value);
|
||||
CSSPixels(double value);
|
||||
|
||||
constexpr CSSPixels(double value)
|
||||
: m_value { value }
|
||||
{
|
||||
}
|
||||
float to_float() const;
|
||||
double to_double() const;
|
||||
int to_int() const;
|
||||
|
||||
constexpr float to_float() const
|
||||
{
|
||||
return static_cast<float>(m_value);
|
||||
}
|
||||
inline int raw_value() const { return m_value; }
|
||||
inline void set_raw_value(int value) { m_value = value; }
|
||||
|
||||
constexpr double to_double() const
|
||||
{
|
||||
return static_cast<double>(m_value);
|
||||
}
|
||||
bool might_be_saturated() const;
|
||||
|
||||
constexpr int to_int() const
|
||||
{
|
||||
return static_cast<int>(m_value);
|
||||
}
|
||||
bool operator==(CSSPixels const& other) const;
|
||||
|
||||
constexpr bool operator==(CSSPixels const& other) const
|
||||
{
|
||||
return this->m_value == other.m_value;
|
||||
}
|
||||
explicit operator double() const { return to_double(); }
|
||||
|
||||
constexpr explicit operator double() const
|
||||
{
|
||||
return to_double();
|
||||
}
|
||||
CSSPixels& operator++();
|
||||
CSSPixels& operator--();
|
||||
|
||||
constexpr CSSPixels& operator++()
|
||||
{
|
||||
this->m_value += 1;
|
||||
return *this;
|
||||
}
|
||||
constexpr CSSPixels operator++(int)
|
||||
{
|
||||
CSSPixels ret = this->m_value;
|
||||
this->m_value += 1;
|
||||
return ret;
|
||||
}
|
||||
constexpr CSSPixels& operator--()
|
||||
{
|
||||
this->m_value -= 1;
|
||||
return *this;
|
||||
}
|
||||
constexpr CSSPixels operator--(int)
|
||||
{
|
||||
CSSPixels ret = this->m_value;
|
||||
this->m_value -= 1;
|
||||
return ret;
|
||||
}
|
||||
int operator<=>(CSSPixels const& other) const;
|
||||
|
||||
constexpr int operator<=>(CSSPixels const& other) const
|
||||
{
|
||||
return this->m_value > other.m_value ? 1 : this->m_value < other.m_value ? -1
|
||||
: 0;
|
||||
}
|
||||
CSSPixels operator+() const;
|
||||
CSSPixels operator-() const;
|
||||
|
||||
constexpr CSSPixels operator+(CSSPixels const& other) const
|
||||
{
|
||||
return this->m_value + other.m_value;
|
||||
}
|
||||
CSSPixels operator+(CSSPixels const& other) const;
|
||||
CSSPixels operator-(CSSPixels const& other) const;
|
||||
CSSPixels operator*(CSSPixels const& other) const;
|
||||
CSSPixels operator/(CSSPixels const& other) const;
|
||||
|
||||
constexpr CSSPixels operator-(CSSPixels const& other) const
|
||||
{
|
||||
return this->m_value - other.m_value;
|
||||
}
|
||||
CSSPixels& operator+=(CSSPixels const& other);
|
||||
CSSPixels& operator-=(CSSPixels const& other);
|
||||
CSSPixels& operator*=(CSSPixels const& other);
|
||||
CSSPixels& operator/=(CSSPixels const& other);
|
||||
|
||||
constexpr CSSPixels operator+() const
|
||||
{
|
||||
return +this->m_value;
|
||||
}
|
||||
CSSPixels abs() const;
|
||||
|
||||
constexpr CSSPixels operator-() const
|
||||
{
|
||||
return -this->m_value;
|
||||
}
|
||||
|
||||
constexpr CSSPixels operator*(CSSPixels const& other) const
|
||||
{
|
||||
return this->m_value * other.m_value;
|
||||
}
|
||||
|
||||
constexpr CSSPixels operator/(CSSPixels const& other) const
|
||||
{
|
||||
return this->m_value / other.m_value;
|
||||
}
|
||||
|
||||
constexpr CSSPixels& operator+=(CSSPixels const& other)
|
||||
{
|
||||
this->m_value += other.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr CSSPixels& operator-=(CSSPixels const& other)
|
||||
{
|
||||
this->m_value -= other.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr CSSPixels& operator*=(CSSPixels const& other)
|
||||
{
|
||||
this->m_value *= other.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr CSSPixels& operator/=(CSSPixels const& other)
|
||||
{
|
||||
this->m_value /= other.m_value;
|
||||
return *this;
|
||||
}
|
||||
static float epsilon();
|
||||
|
||||
private:
|
||||
double m_value {};
|
||||
i32 m_value { 0 };
|
||||
};
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr bool operator==(CSSPixels left, T right) { return left.to_double() == right; }
|
||||
inline bool operator==(CSSPixels left, int right) { return left == CSSPixels(right); }
|
||||
inline bool operator==(CSSPixels left, float right) { return left.to_float() == right; }
|
||||
inline bool operator==(CSSPixels left, double right) { return left.to_double() == right; }
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr bool operator!=(CSSPixels left, T right) { return left.to_double() != right; }
|
||||
inline bool operator>(CSSPixels left, int right) { return left > CSSPixels(right); }
|
||||
inline bool operator>(CSSPixels left, float right) { return left.to_float() > right; }
|
||||
inline bool operator>(CSSPixels left, double right) { return left.to_double() > right; }
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr bool operator>(CSSPixels left, T right) { return left.to_double() > right; }
|
||||
inline bool operator<(CSSPixels left, int right) { return left < CSSPixels(right); }
|
||||
inline bool operator<(CSSPixels left, float right) { return left.to_float() < right; }
|
||||
inline bool operator<(CSSPixels left, double right) { return left.to_double() < right; }
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr bool operator<(CSSPixels left, T right) { return left.to_double() < right; }
|
||||
inline CSSPixels operator*(CSSPixels left, int right) { return left * CSSPixels(right); }
|
||||
inline CSSPixels operator*(CSSPixels left, unsigned long right) { return left * CSSPixels(right); }
|
||||
inline float operator*(CSSPixels left, float right) { return left.to_float() * right; }
|
||||
inline double operator*(CSSPixels left, double right) { return left.to_double() * right; }
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr bool operator>=(CSSPixels left, T right) { return left.to_double() >= right; }
|
||||
inline CSSPixels operator*(int left, CSSPixels right) { return right * CSSPixels(left); }
|
||||
inline CSSPixels operator*(unsigned long left, CSSPixels right) { return right * CSSPixels(left); }
|
||||
inline float operator*(float left, CSSPixels right) { return right.to_float() * left; }
|
||||
inline double operator*(double left, CSSPixels right) { return right.to_double() * left; }
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr bool operator<=(CSSPixels left, T right) { return left.to_double() <= right; }
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr CSSPixels operator*(CSSPixels left, T right) { return left.to_double() * right; }
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr CSSPixels operator*(T left, CSSPixels right) { return right * left; }
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr CSSPixels operator/(CSSPixels left, T right) { return left.to_double() / right; }
|
||||
|
||||
template<Arithmetic T>
|
||||
constexpr CSSPixels operator%(CSSPixels left, T right) { return left.to_double() % right; }
|
||||
inline CSSPixels operator/(CSSPixels left, int right) { return left / CSSPixels(right); }
|
||||
inline CSSPixels operator/(CSSPixels left, unsigned long right) { return left / CSSPixels(right); }
|
||||
inline float operator/(CSSPixels left, float right) { return left.to_float() / right; }
|
||||
inline double operator/(CSSPixels left, double right) { return left.to_double() / right; }
|
||||
|
||||
using CSSPixelLine = Gfx::Line<CSSPixels>;
|
||||
using CSSPixelPoint = Gfx::Point<CSSPixels>;
|
||||
|
@ -211,29 +137,27 @@ using DevicePixelSize = Gfx::Size<DevicePixels>;
|
|||
|
||||
}
|
||||
|
||||
inline Web::CSSPixels abs(Web::CSSPixels const& value)
|
||||
{
|
||||
return value.abs();
|
||||
}
|
||||
|
||||
constexpr Web::CSSPixels floor(Web::CSSPixels const& value)
|
||||
{
|
||||
return ::floorf(value.to_float());
|
||||
// FIXME: Actually floor value
|
||||
return value;
|
||||
}
|
||||
|
||||
constexpr Web::CSSPixels ceil(Web::CSSPixels const& value)
|
||||
{
|
||||
return ::ceilf(value.to_float());
|
||||
// FIXME: Actually ceil value
|
||||
return value;
|
||||
}
|
||||
|
||||
constexpr Web::CSSPixels round(Web::CSSPixels const& value)
|
||||
{
|
||||
return ::roundf(value.to_float());
|
||||
}
|
||||
|
||||
constexpr Web::CSSPixels fmod(Web::CSSPixels const& x, Web::CSSPixels const& y)
|
||||
{
|
||||
return ::fmodf(x.to_float(), y.to_float());
|
||||
}
|
||||
|
||||
constexpr Web::CSSPixels abs(Web::CSSPixels const& value)
|
||||
{
|
||||
return AK::abs(value.to_float());
|
||||
// FIXME: Actually round value
|
||||
return value;
|
||||
}
|
||||
|
||||
constexpr Web::DevicePixels abs(Web::DevicePixels const& value)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue