use crate::compat::Feature;
use crate::css_parser as css;
use crate::css_parser::{CssResult, Maybe, Parser, PrintErr, Printer, Token};
use crate::targets::Browsers;
use crate::values::angle::Angle;
use crate::values::calc::{Calc, MathFunction};
use crate::values::number::CSSNumber;
use crate::values::percentage::DimensionPercentage;
use crate::values::protocol;
use core::cmp::Ordering;
#[derive(Clone, PartialEq, css::Parse, css::ToCss)]
pub enum LengthOrNumber {
Number(CSSNumber),
Length(Length),
}
impl LengthOrNumber {
pub(crate) fn is_compatible(&self, browsers: &Browsers) -> bool {
match self {
Self::Length(l) => l.is_compatible(browsers),
Self::Number(_) => true,
}
}
}
impl Default for LengthOrNumber {
fn default() -> Self {
Self::Number(0.0)
}
}
pub(crate) type LengthPercentage = DimensionPercentage<LengthValue>;
#[derive(Clone, PartialEq, css::Parse, css::ToCss)]
pub enum LengthPercentageOrAuto {
Auto,
Length(LengthPercentage),
}
impl LengthPercentageOrAuto {
pub fn is_compatible(&self, browsers: &Browsers) -> bool {
match self {
Self::Length(l) => l.is_compatible(browsers),
_ => true,
}
}
}
const PX_PER_IN: f32 = 96.0;
const PX_PER_CM: f32 = PX_PER_IN / 2.54;
const PX_PER_MM: f32 = PX_PER_CM / 10.0;
const PX_PER_Q: f32 = PX_PER_CM / 40.0;
const PX_PER_PT: f32 = PX_PER_IN / 72.0;
const PX_PER_PC: f32 = PX_PER_IN / 6.0;
macro_rules! define_length_units {
(
$(
$(#[$doc:meta])*
$variant:ident : $unit:literal => $feature:expr
),* $(,)?
) => {
#[derive(Clone, Copy, Debug, crate::generics::CssEql, crate::generics::CssHash, crate::generics::DeepClone)]
pub enum LengthValue {
$(
$(#[$doc])*
$variant(CSSNumber),
)*
}
impl LengthValue {
#[inline]
fn value(&self) -> CSSNumber {
match self { $( Self::$variant(v) => *v, )* }
}
#[inline]
fn unit(&self) -> &'static [u8] {
match self { $( Self::$variant(_) => $unit, )* }
}
#[inline]
fn from_unit_ci(unit: &[u8], value: CSSNumber) -> Option<Self> {
crate::match_ignore_ascii_case! { unit, {
$( $unit => Some(Self::$variant(value)), )*
_ => None,
}}
}
#[inline]
fn map_value(&self, f: impl FnOnce(f32) -> f32) -> Self {
match self { $( Self::$variant(v) => Self::$variant(f(*v)), )* }
}
#[inline]
fn try_same_unit_op(&self, other: &Self, f: impl Fn(f32, f32) -> f32) -> Option<Self> {
match (self, other) {
$( (Self::$variant(a), Self::$variant(b)) => Some(Self::$variant(f(*a, *b))), )*
_ => None,
}
}
#[inline]
fn feature(&self) -> Option<Feature> {
match self { $( Self::$variant(_) => $feature, )* }
}
}
};
}
define_length_units! {
Px: b"px" => None,
In: b"in" => None,
Cm: b"cm" => None,
Mm: b"mm" => None,
Q: b"q" => Some(Feature::QUnit),
Pt: b"pt" => None,
Pc: b"pc" => None,
Em: b"em" => None,
Rem: b"rem" => Some(Feature::RemUnit),
Ex: b"ex" => Some(Feature::ExUnit),
Rex: b"rex" => None,
Ch: b"ch" => Some(Feature::ChUnit),
Rch: b"rch" => None,
Cap: b"cap" => Some(Feature::CapUnit),
Rcap: b"rcap" => None,
Ic: b"ic" => Some(Feature::IcUnit),
Ric: b"ric" => None,
Lh: b"lh" => Some(Feature::LhUnit),
Rlh: b"rlh" => Some(Feature::RlhUnit),
Vw: b"vw" => Some(Feature::VwUnit),
Lvw: b"lvw" => Some(Feature::ViewportPercentageUnitsLarge),
Svw: b"svw" => Some(Feature::ViewportPercentageUnitsSmall),
Dvw: b"dvw" => Some(Feature::ViewportPercentageUnitsDynamic),
Cqw: b"cqw" => Some(Feature::ContainerQueryLengthUnits),
Vh: b"vh" => Some(Feature::VhUnit),
Lvh: b"lvh" => Some(Feature::ViewportPercentageUnitsLarge),
Svh: b"svh" => Some(Feature::ViewportPercentageUnitsSmall),
Dvh: b"dvh" => Some(Feature::ViewportPercentageUnitsDynamic),
Cqh: b"cqh" => Some(Feature::ContainerQueryLengthUnits),
Vi: b"vi" => Some(Feature::ViUnit),
Svi: b"svi" => Some(Feature::ViewportPercentageUnitsSmall),
Lvi: b"lvi" => Some(Feature::ViewportPercentageUnitsLarge),
Dvi: b"dvi" => Some(Feature::ViewportPercentageUnitsDynamic),
Cqi: b"cqi" => Some(Feature::ContainerQueryLengthUnits),
Vb: b"vb" => Some(Feature::VbUnit),
Svb: b"svb" => Some(Feature::ViewportPercentageUnitsSmall),
Lvb: b"lvb" => Some(Feature::ViewportPercentageUnitsLarge),
Dvb: b"dvb" => Some(Feature::ViewportPercentageUnitsDynamic),
Cqb: b"cqb" => Some(Feature::ContainerQueryLengthUnits),
Vmin: b"vmin" => Some(Feature::VminUnit),
Svmin: b"svmin" => Some(Feature::ViewportPercentageUnitsSmall),
Lvmin: b"lvmin" => Some(Feature::ViewportPercentageUnitsLarge),
Dvmin: b"dvmin" => Some(Feature::ViewportPercentageUnitsDynamic),
Cqmin: b"cqmin" => Some(Feature::ContainerQueryLengthUnits),
Vmax: b"vmax" => Some(Feature::VmaxUnit),
Svmax: b"svmax" => Some(Feature::ViewportPercentageUnitsSmall),
Lvmax: b"lvmax" => Some(Feature::ViewportPercentageUnitsLarge),
Dvmax: b"dvmax" => Some(Feature::ViewportPercentageUnitsDynamic),
Cqmax: b"cqmax" => Some(Feature::ContainerQueryLengthUnits),
}
impl LengthValue {
pub(crate) fn parse(input: &mut Parser) -> CssResult<Self> {
let location = input.current_source_location();
let token = input.next()?.clone();
match &token {
Token::Dimension(dim) => {
if let Some(v) = Self::from_unit_ci(dim.unit, dim.num.value) {
return Ok(v);
}
}
Token::Number(num) => return Ok(Self::Px(num.value)),
_ => {}
}
Err(location.new_unexpected_token_error(token))
}
pub(crate) fn to_css(self, dest: &mut Printer) -> Result<(), PrintErr> {
let (value, unit) = self.to_unit_value();
if !dest.in_calc && value == 0.0 {
return dest.write_char(b'0');
}
css::serializer::serialize_dimension(value, unit, dest)
}
pub(crate) fn is_zero(self) -> bool {
self.value() == 0.0
}
pub(crate) fn zero() -> LengthValue {
Self::Px(0.0)
}
pub(crate) fn to_px(self) -> Option<CSSNumber> {
match self {
Self::Px(v) => Some(v),
Self::In(v) => Some(v * PX_PER_IN),
Self::Cm(v) => Some(v * PX_PER_CM),
Self::Mm(v) => Some(v * PX_PER_MM),
Self::Q(v) => Some(v * PX_PER_Q),
Self::Pt(v) => Some(v * PX_PER_PT),
Self::Pc(v) => Some(v * PX_PER_PC),
_ => None,
}
}
pub(crate) fn try_sign(self) -> Option<f32> {
Some(self.sign())
}
pub(crate) fn sign(self) -> f32 {
css::signfns::sign_f32(self.value())
}
pub(crate) fn try_from_token(token: &Token) -> Maybe<Self, ()> {
match token {
Token::Dimension(dim) => {
if let Some(v) = Self::from_unit_ci(dim.unit, dim.num.value) {
return Ok(v);
}
}
_ => {}
}
Err(())
}
pub(crate) fn to_unit_value(self) -> (CSSNumber, &'static [u8]) {
(self.value(), self.unit())
}
pub(crate) fn map(self, map_fn: impl FnOnce(f32) -> f32) -> LengthValue {
self.map_value(map_fn)
}
pub(crate) fn mul_f32(self, other: f32) -> LengthValue {
self.map_value(|v| v * other)
}
pub(crate) fn partial_cmp(self, other: LengthValue) -> Option<Ordering> {
if core::mem::discriminant(&self) == core::mem::discriminant(&other) {
let a = self.value();
let b = other.value();
return crate::generic::partial_cmp_f32(a, b);
}
let a = self.to_px();
let b = other.to_px();
if let (Some(a), Some(b)) = (a, b) {
return crate::generic::partial_cmp_f32(a, b);
}
None
}
pub(crate) fn try_add(self, rhs: LengthValue) -> Option<LengthValue> {
if let Some(v) = self.try_same_unit_op(&rhs, |a, b| a + b) {
return Some(v);
}
if let Some(a) = self.to_px() {
if let Some(b) = rhs.to_px() {
return Some(Self::Px(a + b));
}
}
None
}
pub(crate) fn is_compatible(self, browsers: &Browsers) -> bool {
match self.feature() {
Some(feature) => feature.is_compatible(browsers),
None => true,
}
}
}
impl PartialEq for LengthValue {
#[inline]
fn eq(&self, other: &Self) -> bool {
core::mem::discriminant(self) == core::mem::discriminant(other)
&& self.value() == other.value()
}
}
#[derive(Clone, PartialEq, css::ToCss)]
pub enum Length {
Value(LengthValue),
Calc(Box<Calc<Length>>),
}
impl Length {
pub(crate) fn zero() -> Length {
Self::Value(LengthValue::zero())
}
pub(crate) fn deep_clone(&self) -> Length {
self.clone()
}
pub(crate) fn parse(input: &mut Parser) -> CssResult<Length> {
if let Ok(calc_value) = input.try_parse(Calc::<Length>::parse) {
if let Calc::Value(v) = calc_value {
return Ok(*v);
}
return Ok(Self::Calc(Box::new(calc_value)));
}
let len = LengthValue::parse(input)?;
Ok(Self::Value(len))
}
pub(crate) fn px(p: CSSNumber) -> Length {
Self::Value(LengthValue::Px(p))
}
pub(crate) fn to_px(&self) -> Option<CSSNumber> {
match self {
Self::Value(a) => a.to_px(),
_ => None,
}
}
pub(crate) fn mul_f32(self, other: f32) -> Length {
match self {
Self::Value(v) => Self::Value(v.mul_f32(other)),
Self::Calc(c) => Self::Calc(Box::new(c.mul_f32(other))),
}
}
pub(crate) fn add(self, other: Length) -> Length {
let a = Self::unwrap_calc(self);
let b = Self::unwrap_calc(other);
let res = Length::add_internal(a, b);
if let Self::Calc(c) = res {
match *c {
Calc::Value(v) => return *v,
Calc::Function(f) if !matches!(*f, MathFunction::Calc(_)) => {
return Self::Calc(Box::new(Calc::Function(f)));
}
other => {
return Self::Calc(Box::new(Calc::Function(Box::new(MathFunction::Calc(
other,
)))));
}
}
}
res
}
pub(crate) fn add_internal(self, other: Length) -> Length {
if let Some(r) = self.try_add(&other) {
return r;
}
self.add__(other)
}
pub(crate) fn into_calc(self) -> Calc<Length> {
match self {
Self::Calc(c) => *c,
v => Calc::Value(Box::new(v)),
}
}
fn add__(self, other: Length) -> Length {
let mut a = self;
let mut b = other;
if a.is_zero() {
return b;
}
if b.is_zero() {
return a;
}
if a.is_sign_negative() && b.is_sign_positive() {
core::mem::swap(&mut a, &mut b);
}
match (a, b) {
(Self::Calc(ca), b) if matches!(*ca, Calc::Value(_)) && !matches!(b, Self::Calc(_)) => {
let Calc::Value(v) = *ca else { unreachable!() };
v.add__(b)
}
(a, Self::Calc(cb)) if matches!(*cb, Calc::Value(_)) && !matches!(a, Self::Calc(_)) => {
let Calc::Value(v) = *cb else { unreachable!() };
a.add__(*v)
}
(a, b) => Self::Calc(Box::new(Calc::Sum {
left: Box::new(a.into_calc()),
right: Box::new(b.into_calc()),
})),
}
}
fn try_add(&self, other: &Length) -> Option<Length> {
if let (Self::Value(a), Self::Value(b)) = (self, other) {
if let Some(res) = a.try_add(*b) {
return Some(Self::Value(res));
}
return None;
}
if let Self::Calc(c) = self {
match &**c {
Calc::Value(v) => return v.try_add(other),
Calc::Sum { left, right } => {
let a = Self::Calc(left.clone());
if let Some(res) = a.try_add(other) {
return Some(res.add__(Self::Calc(right.clone())));
}
let b = Self::Calc(right.clone());
if let Some(res) = b.try_add(other) {
return Some(Self::Calc(left.clone()).add__(res));
}
return None;
}
_ => return None,
}
}
if let Self::Calc(c) = other {
match &**c {
Calc::Value(v) => return v.try_add(self),
Calc::Sum { left, right } => {
let a = Self::Calc(left.clone());
if let Some(res) = self.try_add(&a) {
return Some(res.add__(Self::Calc(right.clone())));
}
let b = Self::Calc(right.clone());
if let Some(res) = self.try_add(&b) {
return Some(Self::Calc(left.clone()).add__(res));
}
return None;
}
_ => return None,
}
}
None
}
fn unwrap_calc(length: Length) -> Length {
match length {
Self::Calc(c) => match *c {
Calc::Function(f) => match *f {
MathFunction::Calc(c2) => Self::Calc(Box::new(c2)),
c2 => Self::Calc(Box::new(Calc::Function(Box::new(c2)))),
},
_ => Self::Calc(c),
},
_ => length,
}
}
pub(crate) fn try_sign(&self) -> Option<f32> {
match self {
Self::Value(v) => Some(v.sign()),
Self::Calc(v) => v.try_sign(),
}
}
pub(crate) fn is_sign_negative(&self) -> bool {
let Some(s) = self.try_sign() else {
return false;
};
s.is_sign_negative()
}
pub(crate) fn is_sign_positive(&self) -> bool {
let Some(s) = self.try_sign() else {
return false;
};
s.is_sign_positive()
}
pub(crate) fn partial_cmp(&self, other: &Length) -> Option<Ordering> {
if let (Self::Value(a), Self::Value(b)) = (self, other) {
return LengthValue::partial_cmp(*a, *b);
}
None
}
pub(crate) fn try_map(&self, map_fn: impl FnOnce(f32) -> f32) -> Option<Length> {
match self {
Self::Value(v) => Some(Self::Value(v.map(map_fn))),
_ => None,
}
}
pub(crate) fn is_zero(&self) -> bool {
match self {
Self::Value(v) => v.is_zero(),
_ => false,
}
}
pub(crate) fn is_compatible(&self, browsers: &Browsers) -> bool {
match self {
Self::Value(v) => v.is_compatible(browsers),
Self::Calc(c) => c.is_compatible(browsers),
}
}
}
impl protocol::Zero for LengthValue {
#[inline]
fn zero() -> Self {
LengthValue::zero()
}
#[inline]
fn is_zero(&self) -> bool {
LengthValue::is_zero(*self)
}
}
impl protocol::MulF32 for LengthValue {
#[inline]
fn mul_f32(self, rhs: f32) -> Self {
LengthValue::mul_f32(self, rhs)
}
}
impl protocol::TryAdd for LengthValue {
#[inline]
fn try_add(&self, rhs: &Self) -> Option<Self> {
LengthValue::try_add(*self, *rhs)
}
}
impl protocol::TryFromAngle for LengthValue {
#[inline]
fn try_from_angle(_: Angle) -> Option<Self> {
None
}
}
impl protocol::TrySign for LengthValue {
#[inline]
fn try_sign(&self) -> Option<f32> {
LengthValue::try_sign(*self)
}
}
impl protocol::TryMap for LengthValue {
#[inline]
fn try_map(&self, f: impl Fn(f32) -> f32) -> Option<Self> {
Some(self.map_value(f))
}
}
impl protocol::TryOp for LengthValue {
#[inline]
fn try_op<C>(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> f32) -> Option<Self> {
if core::mem::discriminant(self) == core::mem::discriminant(rhs) {
let v = f(ctx, self.value(), rhs.value());
return Some(self.map_value(|_| v));
}
if let (Some(a), Some(b)) = (self.to_px(), self.to_px()) {
return Some(LengthValue::Px(f(ctx, a, b)));
}
None
}
}
impl protocol::TryOpTo for LengthValue {
#[inline]
fn try_op_to<R, C>(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> R) -> Option<R> {
if core::mem::discriminant(self) == core::mem::discriminant(rhs) {
return Some(f(ctx, self.value(), rhs.value()));
}
if let (Some(a), Some(b)) = (self.to_px(), self.to_px()) {
return Some(f(ctx, a, b));
}
None
}
}
impl protocol::PartialCmp for LengthValue {
#[inline]
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
LengthValue::partial_cmp(*self, *rhs)
}
}
impl protocol::Parse for LengthValue {
#[inline]
fn parse(input: &mut Parser) -> CssResult<Self> {
LengthValue::parse(input)
}
}
impl protocol::ToCss for LengthValue {
#[inline]
fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
LengthValue::to_css(*self, dest)
}
}
impl protocol::IsCompatible for LengthValue {
#[inline]
fn is_compatible(&self, browsers: &Browsers) -> bool {
LengthValue::is_compatible(*self, browsers)
}
}
impl protocol::Zero for Angle {
#[inline]
fn zero() -> Self {
Angle::zero()
}
#[inline]
fn is_zero(&self) -> bool {
Angle::is_zero(*self)
}
}
impl protocol::MulF32 for Angle {
#[inline]
fn mul_f32(self, rhs: f32) -> Self {
Angle::mul_f32(self, rhs)
}
}
impl protocol::TryAdd for Angle {
#[inline]
fn try_add(&self, rhs: &Self) -> Option<Self> {
Angle::try_add(*self, *rhs)
}
}
impl protocol::TryFromAngle for Angle {
#[inline]
fn try_from_angle(a: Angle) -> Option<Self> {
Some(a)
}
}
impl protocol::TrySign for Angle {
#[inline]
fn try_sign(&self) -> Option<f32> {
Some(self.sign())
}
}
impl protocol::TryMap for Angle {
#[inline]
fn try_map(&self, f: impl Fn(f32) -> f32) -> Option<Self> {
Some(self.map(f))
}
}
impl protocol::TryOp for Angle {
#[inline]
fn try_op<C>(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> f32) -> Option<Self> {
Some(match (self, rhs) {
(Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(f(ctx, *a, *b)),
(Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(f(ctx, *a, *b)),
(Angle::Grad(a), Angle::Grad(b)) => Angle::Grad(f(ctx, *a, *b)),
(Angle::Turn(a), Angle::Turn(b)) => Angle::Turn(f(ctx, *a, *b)),
_ => Angle::Deg(f(ctx, self.to_degrees(), rhs.to_degrees())),
})
}
}
impl protocol::TryOpTo for Angle {
#[inline]
fn try_op_to<R, C>(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> R) -> Option<R> {
Some(match (self, rhs) {
(Angle::Deg(a), Angle::Deg(b)) => f(ctx, *a, *b),
(Angle::Rad(a), Angle::Rad(b)) => f(ctx, *a, *b),
(Angle::Grad(a), Angle::Grad(b)) => f(ctx, *a, *b),
(Angle::Turn(a), Angle::Turn(b)) => f(ctx, *a, *b),
_ => f(ctx, self.to_degrees(), rhs.to_degrees()),
})
}
}
impl protocol::PartialCmp for Angle {
#[inline]
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
Angle::partial_cmp(*self, *rhs)
}
}
impl protocol::Parse for Angle {
#[inline]
fn parse(input: &mut Parser) -> CssResult<Self> {
Angle::parse(input)
}
}
impl protocol::ToCss for Angle {
#[inline]
fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
Angle::to_css(*self, dest)
}
}
impl protocol::IsCompatible for Angle {
#[inline]
fn is_compatible(&self, _: &Browsers) -> bool {
true
}
}