stylo 0.19.0

The Stylo CSS engine
Documentation
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! Computed-value calc() leaf types.

use super::{Angle, Length, Number, Percentage, Resolution, Time};
use crate::derives::*;
use crate::values::generics::calc::{self, CalcUnits, PositivePercentageBasis};
use crate::Zero;
use debug_unreachable::debug_unreachable;
use serde::{Deserialize, Serialize};

/// The computed leaf of a calc() expression.
#[derive(
    Clone,
    Debug,
    Deserialize,
    MallocSizeOf,
    PartialEq,
    Serialize,
    ToAnimatedZero,
    ToCss,
    ToResolvedValue,
    ToTyped,
)]
#[allow(missing_docs)]
#[repr(u8)]
pub enum ComputedLeaf {
    Length(Length),
    Percentage(Percentage),
    Number(Number),
    Angle(Angle),
    Time(Time),
    Resolution(Resolution),
}

impl ComputedLeaf {
    pub(super) fn is_zero_length(&self) -> bool {
        match *self {
            Self::Length(ref l) => l.is_zero(),
            Self::Percentage(..)
            | Self::Number(..)
            | Self::Angle(..)
            | Self::Time(..)
            | Self::Resolution(..) => false,
        }
    }
}

impl calc::CalcNodeLeaf for ComputedLeaf {
    fn unit(&self) -> CalcUnits {
        match self {
            Self::Length(_) => CalcUnits::LENGTH,
            Self::Percentage(_) => CalcUnits::PERCENTAGE,
            Self::Number(_) => CalcUnits::empty(),
            Self::Angle(_) => CalcUnits::ANGLE,
            Self::Time(_) => CalcUnits::TIME,
            Self::Resolution(_) => CalcUnits::RESOLUTION,
        }
    }

    fn unitless_value(&self) -> Option<f32> {
        Some(match *self {
            Self::Length(ref l) => l.px(),
            Self::Percentage(ref p) => p.0,
            Self::Number(n) => n,
            Self::Angle(ref a) => a.degrees(),
            Self::Time(ref t) => t.seconds(),
            Self::Resolution(ref r) => r.dppx(),
        })
    }

    fn new_number(value: f32) -> Self {
        Self::Number(value)
    }

    fn as_number(&self) -> Option<f32> {
        match *self {
            Self::Length(_)
            | Self::Percentage(_)
            | Self::Angle(_)
            | Self::Time(_)
            | Self::Resolution(_) => None,
            Self::Number(value) => Some(value),
        }
    }

    fn as_angle_radians(&self) -> Option<f32> {
        match *self {
            Self::Angle(a) => Some(a.radians()),
            _ => None,
        }
    }

    fn new_angle_from_radians(radians: f32) -> Self {
        Self::Angle(Angle::from_radians(radians))
    }

    fn compare(&self, other: &Self, basis: PositivePercentageBasis) -> Option<std::cmp::Ordering> {
        use self::ComputedLeaf::*;
        if std::mem::discriminant(self) != std::mem::discriminant(other) {
            return None;
        }

        if matches!(self, Percentage(..)) && matches!(basis, PositivePercentageBasis::Unknown) {
            return None;
        }

        let Ok(self_negative) = self.is_negative() else {
            return None;
        };
        let Ok(other_negative) = other.is_negative() else {
            return None;
        };
        if self_negative != other_negative {
            return Some(if self_negative {
                std::cmp::Ordering::Less
            } else {
                std::cmp::Ordering::Greater
            });
        }

        match (self, other) {
            (&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
            (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
            (&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
            (&Angle(ref one), &Angle(ref other)) => one.partial_cmp(other),
            (&Time(ref one), &Time(ref other)) => one.partial_cmp(other),
            (&Resolution(ref one), &Resolution(ref other)) => one.partial_cmp(other),
            _ => unsafe {
                match *self {
                    Length(..) | Percentage(..) | Number(..) | Angle(..) | Time(..)
                    | Resolution(..) => {},
                }
                debug_unreachable!("Forgot to handle unit in compare()")
            },
        }
    }

    fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
        use self::ComputedLeaf::*;

        // 0px plus anything else is equal to the right hand side.
        if self.is_zero_length() {
            *self = other.clone();
            return Ok(());
        }

        if other.is_zero_length() {
            return Ok(());
        }

        if std::mem::discriminant(self) != std::mem::discriminant(other) {
            return Err(());
        }

        match (self, other) {
            (&mut Length(ref mut one), &Length(ref other)) => {
                *one += *other;
            },
            (&mut Percentage(ref mut one), &Percentage(ref other)) => {
                one.0 += other.0;
            },
            (&mut Number(ref mut one), &Number(ref other)) => {
                *one += *other;
            },
            (&mut Angle(ref mut one), &Angle(ref other)) => {
                *one += *other;
            },
            (&mut Time(ref mut one), &Time(ref other)) => {
                *one += *other;
            },
            (&mut Resolution(ref mut one), &Resolution(ref other)) => {
                *one += *other;
            },
            _ => unsafe {
                match *other {
                    Length(..) | Percentage(..) | Number(..) | Angle(..) | Time(..)
                    | Resolution(..) => {},
                }
                debug_unreachable!("Forgot to handle unit in try_sum_in_place()")
            },
        }

        Ok(())
    }

    fn try_product_in_place(&mut self, other: &mut Self) -> bool {
        if let Self::Number(ref mut left) = *self {
            if let Self::Number(ref right) = *other {
                // Both sides are numbers, so we can just modify the left side.
                *left *= *right;
                true
            } else {
                // The right side is not a number, so the result should be in the units of the right
                // side.
                if other.map(|v| v * *left).is_ok() {
                    std::mem::swap(self, other);
                    true
                } else {
                    false
                }
            }
        } else if let Self::Number(ref right) = *other {
            // The left side is not a number, but the right side is, so the result is the left
            // side unit.
            self.map(|v| v * *right).is_ok()
        } else {
            // Neither side is a number, so a product is not possible.
            false
        }
    }

    fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
    where
        O: Fn(f32, f32) -> f32,
    {
        use self::ComputedLeaf::*;
        if std::mem::discriminant(self) != std::mem::discriminant(other) {
            return Err(());
        }
        Ok(match (self, other) {
            (&Length(ref one), &Length(ref other)) => {
                Length(super::Length::new(op(one.px(), other.px())))
            },
            (&Percentage(one), &Percentage(other)) => {
                Self::Percentage(super::Percentage(op(one.0, other.0)))
            },
            (&Number(one), &Number(other)) => Self::Number(op(one, other)),
            (&Angle(ref one), &Angle(ref other)) => Self::Angle(super::Angle::from_degrees(op(
                one.degrees(),
                other.degrees(),
            ))),
            (&Time(ref one), &Time(ref other)) => Self::Time(super::Time::from_seconds(op(
                one.seconds(),
                other.seconds(),
            ))),
            (&Resolution(ref one), &Resolution(ref other)) => {
                Self::Resolution(super::Resolution::from_dppx(op(one.dppx(), other.dppx())))
            },
            _ => unsafe {
                match *self {
                    Length(..) | Percentage(..) | Number(..) | Angle(..) | Time(..)
                    | Resolution(..) => {},
                }
                debug_unreachable!("Forgot to handle unit in try_op()")
            },
        })
    }

    fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
        Ok(match self {
            Self::Length(value) => {
                *value = Length::new(op(value.px()));
            },
            Self::Percentage(value) => {
                *value = Percentage(op(value.0));
            },
            Self::Number(value) => {
                *value = op(*value);
            },
            Self::Angle(value) => {
                *value = Angle::from_degrees(op(value.degrees()));
            },
            Self::Time(value) => {
                *value = Time::from_seconds(op(value.seconds()));
            },
            Self::Resolution(value) => {
                *value = Resolution::from_dppx(op(value.dppx()));
            },
        })
    }

    fn simplify(&mut self) {}

    fn sort_key(&self) -> calc::SortKey {
        match *self {
            Self::Length(..) => calc::SortKey::Px,
            Self::Percentage(..) => calc::SortKey::Percentage,
            Self::Number(..) => calc::SortKey::Number,
            Self::Angle(..) => calc::SortKey::Deg,
            Self::Time(..) => calc::SortKey::S,
            Self::Resolution(..) => calc::SortKey::Dppx,
        }
    }
}