mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 14:38:11 +00:00

Previously, the code assumed that in dividing up the space in the affected tracks there would never be an overshoot. Instead, we can check for each track how much extra space is left and never consume any extra. In the same way, we can ensure that all extra space is consumed by distributing all remaining extra space starting from the first track. Thus, if there is no growth limit, the space distribution should always consume all the extra space.
469 lines
15 KiB
C++
469 lines
15 KiB
C++
/*
|
|
* 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
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/Concepts.h>
|
|
#include <AK/Debug.h>
|
|
#include <AK/DistinctNumeric.h>
|
|
#include <AK/Math.h>
|
|
#include <AK/Traits.h>
|
|
#include <LibGfx/Forward.h>
|
|
#include <LibGfx/Rect.h>
|
|
#include <math.h>
|
|
|
|
namespace Web {
|
|
|
|
/// DevicePixels: A position or length on the physical display.
|
|
AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, DevicePixels, Arithmetic, CastToUnderlying, Comparison, Increment);
|
|
|
|
template<Integral T>
|
|
constexpr bool operator==(DevicePixels left, T right) { return left.value() == right; }
|
|
|
|
template<Integral T>
|
|
constexpr bool operator!=(DevicePixels left, T right) { return left.value() != right; }
|
|
|
|
template<Integral T>
|
|
constexpr bool operator>(DevicePixels left, T right) { return left.value() > right; }
|
|
|
|
template<Integral T>
|
|
constexpr bool operator<(DevicePixels left, T right) { return left.value() < right; }
|
|
|
|
template<Integral T>
|
|
constexpr bool operator>=(DevicePixels left, T right) { return left.value() >= right; }
|
|
|
|
template<Integral T>
|
|
constexpr bool operator<=(DevicePixels left, T right) { return left.value() <= right; }
|
|
|
|
template<Integral T>
|
|
constexpr DevicePixels operator*(DevicePixels left, T right) { return left.value() * right; }
|
|
template<Integral T>
|
|
constexpr DevicePixels operator*(T left, DevicePixels right) { return right * left; }
|
|
|
|
template<Integral T>
|
|
constexpr DevicePixels operator/(DevicePixels left, T right) { return left.value() / right; }
|
|
|
|
template<Integral T>
|
|
constexpr DevicePixels operator%(DevicePixels left, T right) { return left.value() % right; }
|
|
|
|
class CSSPixelFraction;
|
|
|
|
/// CSSPixels: A position or length in CSS "reference pixels", independent of zoom or screen DPI.
|
|
/// See https://www.w3.org/TR/css-values-3/#reference-pixel
|
|
class CSSPixels {
|
|
public:
|
|
static constexpr i32 fractional_bits = 6;
|
|
static constexpr i32 fixed_point_denominator = 1 << fractional_bits;
|
|
|
|
static constexpr i32 radix_mask = fixed_point_denominator - 1;
|
|
|
|
static constexpr i32 max_integer_value = NumericLimits<int>::max() >> fractional_bits;
|
|
static constexpr i32 min_integer_value = NumericLimits<int>::min() >> fractional_bits;
|
|
|
|
constexpr CSSPixels() = default;
|
|
template<Signed I>
|
|
constexpr CSSPixels(I value)
|
|
{
|
|
if (value > max_integer_value) [[unlikely]]
|
|
m_value = NumericLimits<int>::max();
|
|
else if (value < min_integer_value) [[unlikely]]
|
|
m_value = NumericLimits<int>::min();
|
|
else
|
|
m_value = static_cast<int>(value) << fractional_bits;
|
|
}
|
|
|
|
template<FloatingPoint F>
|
|
explicit CSSPixels(F value)
|
|
{
|
|
*this = nearest_value_for(value);
|
|
}
|
|
|
|
template<FloatingPoint F>
|
|
static CSSPixels nearest_value_for(F value)
|
|
{
|
|
i32 raw_value = 0;
|
|
if (!isnan(value))
|
|
raw_value = AK::clamp_to_int(value * fixed_point_denominator);
|
|
// Note: The resolution of CSSPixels is 0.015625, so care must be taken when converting
|
|
// floats/doubles to CSSPixels as small values (such as scale factors) can underflow to zero,
|
|
// or otherwise produce inaccurate results (when scaled back up).
|
|
if (raw_value == 0 && value != 0)
|
|
dbgln_if(LIBWEB_CSS_DEBUG, "CSSPixels: Conversion from float or double underflowed to zero");
|
|
return from_raw(raw_value);
|
|
}
|
|
|
|
template<Unsigned U>
|
|
constexpr CSSPixels(U value)
|
|
{
|
|
if (value > max_integer_value) [[unlikely]]
|
|
m_value = NumericLimits<int>::max();
|
|
else
|
|
m_value = static_cast<int>(value) << fractional_bits;
|
|
}
|
|
|
|
static constexpr CSSPixels from_raw(int value)
|
|
{
|
|
CSSPixels res;
|
|
res.set_raw_value(value);
|
|
return res;
|
|
}
|
|
|
|
static constexpr CSSPixels min()
|
|
{
|
|
return from_raw(NumericLimits<int>::min());
|
|
}
|
|
|
|
static constexpr CSSPixels max()
|
|
{
|
|
return from_raw(NumericLimits<int>::max());
|
|
}
|
|
|
|
static constexpr CSSPixels smallest_positive_value()
|
|
{
|
|
return from_raw(1);
|
|
}
|
|
|
|
float to_float() const;
|
|
double to_double() const;
|
|
int to_int() const;
|
|
|
|
constexpr int raw_value() const { return m_value; }
|
|
constexpr void set_raw_value(int value) { m_value = value; }
|
|
|
|
constexpr bool might_be_saturated() const { return raw_value() == NumericLimits<i32>::max() || raw_value() == NumericLimits<i32>::min(); }
|
|
|
|
constexpr bool operator==(CSSPixels const& other) const = default;
|
|
|
|
explicit operator double() const { return to_double(); }
|
|
explicit operator float() const { return to_float(); }
|
|
explicit operator int() const { return to_int(); }
|
|
|
|
constexpr CSSPixels& operator++()
|
|
{
|
|
m_value = Checked<int>::saturating_add(m_value, fixed_point_denominator);
|
|
return *this;
|
|
}
|
|
constexpr CSSPixels& operator--()
|
|
{
|
|
m_value = Checked<int>::saturating_sub(m_value, fixed_point_denominator);
|
|
return *this;
|
|
}
|
|
|
|
constexpr int operator<=>(CSSPixels const& other) const
|
|
{
|
|
return raw_value() > other.raw_value()
|
|
? 1
|
|
: raw_value() < other.raw_value()
|
|
? -1
|
|
: 0;
|
|
}
|
|
|
|
constexpr CSSPixels operator+() const { return from_raw(+raw_value()); }
|
|
constexpr CSSPixels operator-() const { return from_raw(-raw_value()); }
|
|
|
|
constexpr CSSPixels operator+(CSSPixels const& other) const
|
|
{
|
|
return from_raw(Checked<int>::saturating_add(raw_value(), other.raw_value()));
|
|
}
|
|
|
|
constexpr CSSPixels operator-(CSSPixels const& other) const
|
|
{
|
|
return from_raw(Checked<int>::saturating_sub(raw_value(), other.raw_value()));
|
|
}
|
|
|
|
constexpr CSSPixels operator*(CSSPixels const& other) const
|
|
{
|
|
i64 value = raw_value();
|
|
value *= other.raw_value();
|
|
|
|
int int_value = AK::clamp_to_int(value >> fractional_bits);
|
|
|
|
// Rounding:
|
|
// If last bit cut off was 1:
|
|
if (value & (1u << (fractional_bits - 1))) {
|
|
// If any bit after was 1 as well
|
|
if (value & (radix_mask >> 1u)) {
|
|
// We need to round away from 0
|
|
int_value = Checked<int>::saturating_add(int_value, 1);
|
|
} else {
|
|
// Otherwise we round to the next even value
|
|
// Which means we add the least significant bit of the raw integer value
|
|
int_value = Checked<int>::saturating_add(int_value, int_value & 1);
|
|
}
|
|
}
|
|
|
|
return from_raw(int_value);
|
|
}
|
|
constexpr CSSPixels operator*(CSSPixelFraction const& other) const;
|
|
|
|
constexpr CSSPixelFraction operator/(CSSPixels const& other) const;
|
|
constexpr CSSPixels operator/(CSSPixelFraction const& other) const;
|
|
|
|
constexpr CSSPixels& operator+=(CSSPixels const& other)
|
|
{
|
|
*this = *this + other;
|
|
return *this;
|
|
}
|
|
constexpr CSSPixels& operator-=(CSSPixels const& other)
|
|
{
|
|
*this = *this - other;
|
|
return *this;
|
|
}
|
|
constexpr CSSPixels& operator*=(CSSPixels const& other)
|
|
{
|
|
*this = *this * other;
|
|
return *this;
|
|
}
|
|
constexpr CSSPixels& operator*=(CSSPixelFraction const& other)
|
|
{
|
|
*this = *this * other;
|
|
return *this;
|
|
}
|
|
constexpr CSSPixels& operator/=(CSSPixels const& other)
|
|
{
|
|
*this = *this * other;
|
|
return *this;
|
|
}
|
|
|
|
constexpr CSSPixels abs() const { return from_raw(::abs(m_value)); }
|
|
|
|
CSSPixels& scale_by(float value)
|
|
{
|
|
*this = CSSPixels(to_float() * value);
|
|
return *this;
|
|
}
|
|
|
|
CSSPixels& scale_by(double value)
|
|
{
|
|
*this = CSSPixels(to_double() * value);
|
|
return *this;
|
|
}
|
|
|
|
CSSPixels scaled(float value) const
|
|
{
|
|
auto result = *this;
|
|
result.scale_by(value);
|
|
return result;
|
|
}
|
|
|
|
CSSPixels scaled(double value) const
|
|
{
|
|
auto result = *this;
|
|
result.scale_by(value);
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
i32 m_value { 0 };
|
|
};
|
|
|
|
constexpr 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; }
|
|
|
|
constexpr 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; }
|
|
|
|
constexpr 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; }
|
|
|
|
constexpr CSSPixels operator*(CSSPixels left, int right) { return left * CSSPixels(right); }
|
|
constexpr 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; }
|
|
|
|
constexpr CSSPixels operator*(int left, CSSPixels right) { return right * CSSPixels(left); }
|
|
constexpr 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; }
|
|
|
|
class CSSPixelFraction {
|
|
public:
|
|
constexpr CSSPixelFraction(CSSPixels numerator, CSSPixels denominator)
|
|
: m_numerator(numerator)
|
|
, m_denominator(denominator)
|
|
{
|
|
}
|
|
|
|
explicit constexpr CSSPixelFraction(CSSPixels value)
|
|
: m_numerator(value)
|
|
, m_denominator(1)
|
|
{
|
|
}
|
|
|
|
template<Signed I>
|
|
constexpr CSSPixelFraction(I numerator, I denominator = 1)
|
|
: m_numerator(numerator)
|
|
, m_denominator(denominator)
|
|
{
|
|
}
|
|
|
|
constexpr operator CSSPixels() const
|
|
{
|
|
i64 wide_value = m_numerator.raw_value();
|
|
wide_value <<= CSSPixels::fractional_bits;
|
|
wide_value /= m_denominator.raw_value();
|
|
return CSSPixels::from_raw(AK::clamp_to_int(wide_value));
|
|
}
|
|
|
|
constexpr int operator<=>(CSSPixelFraction const& other) const
|
|
{
|
|
auto left = static_cast<i64>(m_numerator.raw_value()) * other.m_denominator.raw_value();
|
|
auto right = static_cast<i64>(other.m_numerator.raw_value()) * m_denominator.raw_value();
|
|
if (left > right)
|
|
return 1;
|
|
if (left < right)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
template<Signed I>
|
|
constexpr int operator<=>(I const& other) const
|
|
{
|
|
return *this <=> CSSPixelFraction(other);
|
|
}
|
|
|
|
constexpr CSSPixels numerator() const { return m_numerator; }
|
|
constexpr CSSPixels denominator() const { return m_denominator; }
|
|
|
|
float to_float() const { return CSSPixels(*this).to_float(); }
|
|
double to_double() const { return CSSPixels(*this).to_double(); }
|
|
int to_int() const { return CSSPixels(*this).to_int(); }
|
|
bool might_be_saturated() const { return CSSPixels(*this).might_be_saturated(); }
|
|
|
|
private:
|
|
CSSPixels m_numerator;
|
|
CSSPixels m_denominator;
|
|
};
|
|
|
|
constexpr CSSPixels CSSPixels::operator*(CSSPixelFraction const& other) const
|
|
{
|
|
i64 wide_value = raw_value();
|
|
wide_value *= other.numerator().raw_value();
|
|
wide_value /= other.denominator().raw_value();
|
|
return CSSPixels::from_raw(AK::clamp_to_int(wide_value));
|
|
}
|
|
|
|
constexpr CSSPixelFraction CSSPixels::operator/(CSSPixels const& other) const
|
|
{
|
|
return CSSPixelFraction(*this, other);
|
|
}
|
|
constexpr CSSPixels CSSPixels::operator/(CSSPixelFraction const& other) const
|
|
{
|
|
i64 wide_value = raw_value();
|
|
wide_value *= other.denominator().raw_value();
|
|
wide_value /= other.numerator().raw_value();
|
|
return CSSPixels::from_raw(AK::clamp_to_int(wide_value));
|
|
}
|
|
|
|
constexpr CSSPixels operator/(CSSPixels left, int right) { return left / CSSPixels(right); }
|
|
constexpr 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>;
|
|
using CSSPixelRect = Gfx::Rect<CSSPixels>;
|
|
using CSSPixelSize = Gfx::Size<CSSPixels>;
|
|
|
|
using DevicePixelLine = Gfx::Line<DevicePixels>;
|
|
using DevicePixelPoint = Gfx::Point<DevicePixels>;
|
|
using DevicePixelRect = Gfx::Rect<DevicePixels>;
|
|
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 Web::CSSPixels::from_raw(value.raw_value() & ~Web::CSSPixels::radix_mask);
|
|
}
|
|
|
|
constexpr Web::CSSPixels ceil(Web::CSSPixels const& value)
|
|
{
|
|
auto floor_value = value.raw_value() & ~Web::CSSPixels::radix_mask;
|
|
auto ceil_value = floor_value + (value.raw_value() & Web::CSSPixels::radix_mask ? Web::CSSPixels::fixed_point_denominator : 0);
|
|
return Web::CSSPixels::from_raw(ceil_value);
|
|
}
|
|
|
|
constexpr Web::CSSPixels round(Web::CSSPixels const& value)
|
|
{
|
|
// FIXME: Maybe do this with bit-fiddling instead
|
|
if (value > 0)
|
|
return floor(value + Web::CSSPixels::from_raw(Web::CSSPixels::fixed_point_denominator >> 1 /* 0.5 */));
|
|
return ceil(value - Web::CSSPixels::from_raw(Web::CSSPixels::fixed_point_denominator >> 1 /* 0.5 */));
|
|
}
|
|
|
|
constexpr Web::DevicePixels abs(Web::DevicePixels const& value)
|
|
{
|
|
return AK::abs(value.value());
|
|
}
|
|
|
|
template<>
|
|
template<>
|
|
[[nodiscard]] ALWAYS_INLINE Web::CSSPixelRect Web::CSSPixelRect::to_rounded<Web::CSSPixels>() const
|
|
{
|
|
return {
|
|
round(x()),
|
|
round(y()),
|
|
round(width()),
|
|
round(height()),
|
|
};
|
|
}
|
|
|
|
namespace AK {
|
|
|
|
template<>
|
|
struct Traits<Web::CSSPixels> : public GenericTraits<Web::CSSPixels> {
|
|
static unsigned hash(Web::CSSPixels const& key)
|
|
{
|
|
return Traits<int>::hash(key.raw_value());
|
|
}
|
|
|
|
static bool equals(Web::CSSPixels const& a, Web::CSSPixels const& b)
|
|
{
|
|
return a == b;
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct Traits<Web::DevicePixels> : public GenericTraits<Web::DevicePixels> {
|
|
static unsigned hash(Web::DevicePixels const& key)
|
|
{
|
|
return Traits<Web::DevicePixels::Type>::hash(key.value());
|
|
}
|
|
|
|
static bool equals(Web::DevicePixels const& a, Web::DevicePixels const& b)
|
|
{
|
|
return a == b;
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct Formatter<Web::CSSPixels> : Formatter<double> {
|
|
ErrorOr<void> format(FormatBuilder& builder, Web::CSSPixels const& value)
|
|
{
|
|
return Formatter<double>::format(builder, value.to_double());
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct Formatter<Web::DevicePixels> : Formatter<Web::DevicePixels::Type> {
|
|
ErrorOr<void> format(FormatBuilder& builder, Web::DevicePixels const& value)
|
|
{
|
|
return Formatter<Web::DevicePixels::Type>::format(builder, value.value());
|
|
}
|
|
};
|
|
|
|
}
|