/* * Copyright (c) 2023, Aliaksandr Kalenik * Copyright (c) 2012-2023, Apple Inc. All rights reserved. * Copyright (c) 2022, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include namespace Web { /// DevicePixels: A position or length on the physical display. AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, DevicePixels, Arithmetic, CastToUnderlying, Comparison, Increment); template constexpr bool operator==(DevicePixels left, T right) { return left.value() == right; } template constexpr bool operator!=(DevicePixels left, T right) { return left.value() != right; } template constexpr bool operator>(DevicePixels left, T right) { return left.value() > right; } template constexpr bool operator<(DevicePixels left, T right) { return left.value() < right; } template constexpr bool operator>=(DevicePixels left, T right) { return left.value() >= right; } template constexpr bool operator<=(DevicePixels left, T right) { return left.value() <= right; } template constexpr DevicePixels operator*(DevicePixels left, T right) { return left.value() * right; } template constexpr DevicePixels operator*(T left, DevicePixels right) { return right * left; } template constexpr DevicePixels operator/(DevicePixels left, T right) { return left.value() / right; } template constexpr DevicePixels operator%(DevicePixels left, T right) { return left.value() % right; } /// 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::max() >> fractional_bits; static constexpr i32 min_integer_value = NumericLimits::min() >> fractional_bits; constexpr CSSPixels() = default; template constexpr CSSPixels(I value) { if (value > max_integer_value) [[unlikely]] m_value = NumericLimits::max(); else if (value < min_integer_value) [[unlikely]] m_value = NumericLimits::min(); else m_value = static_cast(value) << fractional_bits; } CSSPixels(float value) { if (!isnan(value)) m_value = AK::clamp_to_int(value * fixed_point_denominator); } CSSPixels(double value) { if (!isnan(value)) m_value = AK::clamp_to_int(value * fixed_point_denominator); } template constexpr CSSPixels(U value) { if (value > max_integer_value) [[unlikely]] m_value = NumericLimits::max(); else m_value = static_cast(value) << fractional_bits; } static constexpr CSSPixels from_raw(int value) { CSSPixels res; res.set_raw_value(value); return res; } 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::max() || raw_value() == NumericLimits::min(); } constexpr bool operator==(CSSPixels const& other) const = default; explicit operator double() const { return to_double(); } constexpr CSSPixels& operator++() { m_value = Checked::saturating_add(m_value, fixed_point_denominator); return *this; } constexpr CSSPixels& operator--() { m_value = Checked::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::saturating_add(raw_value(), other.raw_value())); } constexpr CSSPixels operator-(CSSPixels const& other) const { return from_raw(Checked::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 the bit after was 1 as well if (value & (radix_mask >> 2u)) { // We need to round away from 0 int_value = Checked::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::saturating_add(int_value, int_value & 1); } } return from_raw(int_value); } constexpr CSSPixels operator/(CSSPixels const& other) const { i64 mult = raw_value(); mult <<= fractional_bits; mult /= other.raw_value(); int int_value = AK::clamp_to_int(mult); return from_raw(int_value); } 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/=(CSSPixels const& other) { *this = *this / other; return *this; } constexpr CSSPixels abs() const { return from_raw(::abs(m_value)); } 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; } 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; using CSSPixelPoint = Gfx::Point; using CSSPixelRect = Gfx::Rect; using CSSPixelSize = Gfx::Size; using DevicePixelLine = Gfx::Line; using DevicePixelPoint = Gfx::Point; using DevicePixelRect = Gfx::Rect; using DevicePixelSize = Gfx::Size; } 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()); } namespace AK { template<> struct Traits : public GenericTraits { static unsigned hash(Web::CSSPixels const& key) { return Traits::hash(key.raw_value()); } static bool equals(Web::CSSPixels const& a, Web::CSSPixels const& b) { return a == b; } }; template<> struct Traits : public GenericTraits { static unsigned hash(Web::DevicePixels const& key) { return Traits::hash(key.value()); } static bool equals(Web::DevicePixels const& a, Web::DevicePixels const& b) { return a == b; } }; template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::CSSPixels const& value) { return Formatter::format(builder, value.to_double()); } }; template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::DevicePixels const& value) { return Formatter::format(builder, value.value()); } }; }