//! CSS length values. use super::angle::impl_try_from_angle; use super::calc::{Calc, MathFunction}; use super::number::CSSNumber; use super::percentage::DimensionPercentage; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; use crate::targets::Browsers; use crate::traits::{ private::{AddInternal, TryAdd}, Map, Parse, Sign, ToCss, TryMap, TryOp, Zero, }; use crate::traits::{IsCompatible, TrySign}; #[cfg(feature = "visitor")] use crate::visitor::Visit; use const_str; use cssparser::*; /// A CSS [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage) value. /// May be specified as either a length or a percentage that resolves to an length. pub type LengthPercentage = DimensionPercentage; impl LengthPercentage { /// Constructs a `LengthPercentage` with the given pixel value. pub fn px(val: CSSNumber) -> LengthPercentage { LengthPercentage::Dimension(LengthValue::Px(val)) } pub(crate) fn to_css_unitless(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { DimensionPercentage::Dimension(d) => d.to_css_unitless(dest), _ => self.to_css(dest), } } } impl IsCompatible for LengthPercentage { fn is_compatible(&self, browsers: Browsers) -> bool { match self { LengthPercentage::Dimension(d) => d.is_compatible(browsers), LengthPercentage::Calc(c) => c.is_compatible(browsers), LengthPercentage::Percentage(..) => true, } } } /// Either a [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword. #[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", content = "value", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum LengthPercentageOrAuto { /// The `auto` keyword. Auto, /// A [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). LengthPercentage(LengthPercentage), } impl IsCompatible for LengthPercentageOrAuto { fn is_compatible(&self, browsers: Browsers) -> bool { match self { LengthPercentageOrAuto::LengthPercentage(p) => p.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 { ( $( $(#[$meta: meta])* $name: ident $(/ $feature: ident)?, )+ ) => { /// A CSS [``](https://www.w3.org/TR/css-values-4/#lengths) value, /// without support for `calc()`. See also: [Length](Length). #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "visitor", visit(visit_length, LENGTHS))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "unit", content = "value", rename_all = "kebab-case"))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum LengthValue { $( $(#[$meta])* $name(CSSNumber), )+ } impl<'i> Parse<'i> for LengthValue { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let location = input.current_source_location(); let token = input.next()?; match *token { Token::Dimension { value, ref unit, .. } => { Ok(match unit { $( s if s.eq_ignore_ascii_case(stringify!($name)) => LengthValue::$name(value), )+ _ => return Err(location.new_unexpected_token_error(token.clone())), }) }, Token::Number { value, .. } => { // TODO: quirks mode only? Ok(LengthValue::Px(value)) } ref token => return Err(location.new_unexpected_token_error(token.clone())), } } } impl<'i> TryFrom<&Token<'i>> for LengthValue { type Error = (); fn try_from(token: &Token) -> Result { match token { Token::Dimension { value, ref unit, .. } => { Ok(match unit { $( s if s.eq_ignore_ascii_case(stringify!($name)) => LengthValue::$name(*value), )+ _ => return Err(()), }) }, _ => Err(()) } } } impl LengthValue { /// Returns the numeric value and unit string for the length value. pub fn to_unit_value(&self) -> (CSSNumber, &str) { match self { $( LengthValue::$name(value) => (*value, const_str::convert_ascii_case!(lower, stringify!($name))), )+ } } } impl IsCompatible for LengthValue { fn is_compatible(&self, browsers: Browsers) -> bool { macro_rules! is_compatible { ($f: ident) => { crate::compat::Feature::$f.is_compatible(browsers) }; () => { true }; } match self { $( LengthValue::$name(_) => { is_compatible!($($feature)?) } )+ } } } impl TryAdd for LengthValue { fn try_add(&self, other: &LengthValue) -> Option { use LengthValue::*; match (self, other) { $( ($name(a), $name(b)) => Some($name(a + b)), )+ (a, b) => { if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) { Some(Px(a + b)) } else { None } } } } } impl std::ops::Mul for LengthValue { type Output = Self; fn mul(self, other: CSSNumber) -> LengthValue { use LengthValue::*; match self { $( $name(value) => $name(value * other), )+ } } } impl std::cmp::PartialOrd for LengthValue { fn partial_cmp(&self, other: &LengthValue) -> Option { use LengthValue::*; match (self, other) { $( ($name(a), $name(b)) => a.partial_cmp(b), )+ (a, b) => { if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) { a.partial_cmp(&b) } else { None } } } } } impl TryOp for LengthValue { fn try_op f32>(&self, rhs: &Self, op: F) -> Option { use LengthValue::*; match (self, rhs) { $( ($name(a), $name(b)) => Some($name(op(*a, *b))), )+ (a, b) => { if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) { Some(Px(op(a, b))) } else { None } } } } fn try_op_to T>(&self, rhs: &Self, op: F) -> Option { use LengthValue::*; match (self, rhs) { $( ($name(a), $name(b)) => Some(op(*a, *b)), )+ (a, b) => { if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) { Some(op(a, b)) } else { None } } } } } impl Map for LengthValue { fn map f32>(&self, op: F) -> Self { use LengthValue::*; match self { $( $name(value) => $name(op(*value)), )+ } } } impl Sign for LengthValue { fn sign(&self) -> f32 { use LengthValue::*; match self { $( $name(value) => value.sign(), )+ } } } impl Zero for LengthValue { fn zero() -> Self { LengthValue::Px(0.0) } fn is_zero(&self) -> bool { use LengthValue::*; match self { $( $name(value) => value.is_zero(), )+ } } } impl_try_from_angle!(LengthValue); #[cfg(feature = "jsonschema")] #[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))] impl schemars::JsonSchema for LengthValue { fn is_referenceable() -> bool { true } fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { #[derive(schemars::JsonSchema)] #[schemars(rename_all = "lowercase")] #[allow(dead_code)] enum LengthUnit { $( $(#[$meta])* $name, )+ } #[derive(schemars::JsonSchema)] #[allow(dead_code)] struct LengthValue { /// The length unit. unit: LengthUnit, /// The length value. value: CSSNumber } LengthValue::json_schema(gen) } fn schema_name() -> String { "LengthValue".into() } } }; } define_length_units! { // https://www.w3.org/TR/css-values-4/#absolute-lengths /// A length in pixels. Px, /// A length in inches. 1in = 96px. In, /// A length in centimeters. 1cm = 96px / 2.54. Cm, /// A length in millimeters. 1mm = 1/10th of 1cm. Mm, /// A length in quarter-millimeters. 1Q = 1/40th of 1cm. Q / QUnit, /// A length in points. 1pt = 1/72nd of 1in. Pt, /// A length in picas. 1pc = 1/6th of 1in. Pc, // https://www.w3.org/TR/css-values-4/#font-relative-lengths /// A length in the `em` unit. An `em` is equal to the computed value of the /// font-size property of the element on which it is used. Em, /// A length in the `rem` unit. A `rem` is equal to the computed value of the /// `em` unit on the root element. Rem / RemUnit, /// A length in `ex` unit. An `ex` is equal to the x-height of the font. Ex / ExUnit, /// A length in the `rex` unit. A `rex` is equal to the value of the `ex` unit on the root element. Rex, /// A length in the `ch` unit. A `ch` is equal to the width of the zero ("0") character in the current font. Ch / ChUnit, /// A length in the `rch` unit. An `rch` is equal to the value of the `ch` unit on the root element. Rch, /// A length in the `cap` unit. A `cap` is equal to the cap-height of the font. Cap / CapUnit, /// A length in the `rcap` unit. An `rcap` is equal to the value of the `cap` unit on the root element. Rcap, /// A length in the `ic` unit. An `ic` is equal to the width of the “水” (CJK water ideograph) character in the current font. Ic / IcUnit, /// A length in the `ric` unit. An `ric` is equal to the value of the `ic` unit on the root element. Ric, /// A length in the `lh` unit. An `lh` is equal to the computed value of the `line-height` property. Lh / LhUnit, /// A length in the `rlh` unit. An `rlh` is equal to the value of the `lh` unit on the root element. Rlh / RlhUnit, // https://www.w3.org/TR/css-values-4/#viewport-relative-units /// A length in the `vw` unit. A `vw` is equal to 1% of the [viewport width](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size). Vw / VwUnit, /// A length in the `lvw` unit. An `lvw` is equal to 1% of the [large viewport width](https://www.w3.org/TR/css-values-4/#large-viewport-size). Lvw / ViewportPercentageUnitsLarge, /// A length in the `svw` unit. An `svw` is equal to 1% of the [small viewport width](https://www.w3.org/TR/css-values-4/#small-viewport-size). Svw / ViewportPercentageUnitsSmall, /// A length in the `dvw` unit. An `dvw` is equal to 1% of the [dynamic viewport width](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size). Dvw / ViewportPercentageUnitsDynamic, /// A length in the `cqw` unit. An `cqw` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) width. Cqw / ContainerQueryLengthUnits, /// A length in the `vh` unit. A `vh` is equal to 1% of the [viewport height](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size). Vh / VhUnit, /// A length in the `lvh` unit. An `lvh` is equal to 1% of the [large viewport height](https://www.w3.org/TR/css-values-4/#large-viewport-size). Lvh / ViewportPercentageUnitsLarge, /// A length in the `svh` unit. An `svh` is equal to 1% of the [small viewport height](https://www.w3.org/TR/css-values-4/#small-viewport-size). Svh / ViewportPercentageUnitsSmall, /// A length in the `dvh` unit. An `dvh` is equal to 1% of the [dynamic viewport height](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size). Dvh / ViewportPercentageUnitsDynamic, /// A length in the `cqh` unit. An `cqh` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) height. Cqh / ContainerQueryLengthUnits, /// A length in the `vi` unit. A `vi` is equal to 1% of the [viewport size](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size) /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis). Vi / ViUnit, /// A length in the `svi` unit. A `svi` is equal to 1% of the [small viewport size](https://www.w3.org/TR/css-values-4/#small-viewport-size) /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis). Svi / ViewportPercentageUnitsSmall, /// A length in the `lvi` unit. A `lvi` is equal to 1% of the [large viewport size](https://www.w3.org/TR/css-values-4/#large-viewport-size) /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis). Lvi / ViewportPercentageUnitsLarge, /// A length in the `dvi` unit. A `dvi` is equal to 1% of the [dynamic viewport size](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size) /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis). Dvi / ViewportPercentageUnitsDynamic, /// A length in the `cqi` unit. An `cqi` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) inline size. Cqi / ContainerQueryLengthUnits, /// A length in the `vb` unit. A `vb` is equal to 1% of the [viewport size](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size) /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis). Vb / VbUnit, /// A length in the `svb` unit. A `svb` is equal to 1% of the [small viewport size](https://www.w3.org/TR/css-values-4/#small-viewport-size) /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis). Svb / ViewportPercentageUnitsSmall, /// A length in the `lvb` unit. A `lvb` is equal to 1% of the [large viewport size](https://www.w3.org/TR/css-values-4/#large-viewport-size) /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis). Lvb / ViewportPercentageUnitsLarge, /// A length in the `dvb` unit. A `dvb` is equal to 1% of the [dynamic viewport size](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size) /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis). Dvb / ViewportPercentageUnitsDynamic, /// A length in the `cqb` unit. An `cqb` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) block size. Cqb / ContainerQueryLengthUnits, /// A length in the `vmin` unit. A `vmin` is equal to the smaller of `vw` and `vh`. Vmin / VminUnit, /// A length in the `svmin` unit. An `svmin` is equal to the smaller of `svw` and `svh`. Svmin / ViewportPercentageUnitsSmall, /// A length in the `lvmin` unit. An `lvmin` is equal to the smaller of `lvw` and `lvh`. Lvmin / ViewportPercentageUnitsLarge, /// A length in the `dvmin` unit. A `dvmin` is equal to the smaller of `dvw` and `dvh`. Dvmin / ViewportPercentageUnitsDynamic, /// A length in the `cqmin` unit. An `cqmin` is equal to the smaller of `cqi` and `cqb`. Cqmin / ContainerQueryLengthUnits, /// A length in the `vmax` unit. A `vmax` is equal to the larger of `vw` and `vh`. Vmax / VmaxUnit, /// A length in the `svmax` unit. An `svmax` is equal to the larger of `svw` and `svh`. Svmax / ViewportPercentageUnitsSmall, /// A length in the `lvmax` unit. An `lvmax` is equal to the larger of `lvw` and `lvh`. Lvmax / ViewportPercentageUnitsLarge, /// A length in the `dvmax` unit. An `dvmax` is equal to the larger of `dvw` and `dvh`. Dvmax / ViewportPercentageUnitsDynamic, /// A length in the `cqmax` unit. An `cqmin` is equal to the larger of `cqi` and `cqb`. Cqmax / ContainerQueryLengthUnits, } impl ToCss for LengthValue { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { let (value, unit) = self.to_unit_value(); // The unit can be omitted if the value is zero, except inside calc() // expressions, where unitless numbers won't be parsed as dimensions. if !dest.in_calc && value == 0.0 { return dest.write_char('0'); } serialize_dimension(value, unit, dest) } } impl LengthValue { pub(crate) fn to_css_unitless(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { LengthValue::Px(value) => value.to_css(dest), _ => self.to_css(dest), } } } pub(crate) fn serialize_dimension(value: f32, unit: &str, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { use cssparser::ToCss; let int_value = if value.fract() == 0.0 { Some(value as i32) } else { None }; let token = Token::Dimension { has_sign: value < 0.0, value, int_value, unit: CowRcStr::from(unit), }; if value != 0.0 && value.abs() < 1.0 { let mut s = String::new(); token.to_css(&mut s)?; if value < 0.0 { dest.write_char('-')?; dest.write_str(s.trim_start_matches("-0")) } else { dest.write_str(s.trim_start_matches('0')) } } else { token.to_css(dest)?; Ok(()) } } impl LengthValue { /// Attempts to convert the value to pixels. /// Returns `None` if the conversion is not possible. pub fn to_px(&self) -> Option { use LengthValue::*; match self { Px(value) => Some(*value), In(value) => Some(value * PX_PER_IN), Cm(value) => Some(value * PX_PER_CM), Mm(value) => Some(value * PX_PER_MM), Q(value) => Some(value * PX_PER_Q), Pt(value) => Some(value * PX_PER_PT), Pc(value) => Some(value * PX_PER_PC), _ => None, } } } /// A CSS [``](https://www.w3.org/TR/css-values-4/#lengths) value, with support for `calc()`. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", content = "value", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum Length { /// An explicitly specified length value. Value(LengthValue), /// A computed length value using `calc()`. #[cfg_attr(feature = "visitor", skip_type)] Calc(Box>), } impl<'i> Parse<'i> for Length { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { match input.try_parse(Calc::parse) { Ok(Calc::Value(v)) => return Ok(*v), Ok(calc) => return Ok(Length::Calc(Box::new(calc))), _ => {} } let len = LengthValue::parse(input)?; Ok(Length::Value(len)) } } impl ToCss for Length { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { Length::Value(a) => a.to_css(dest), Length::Calc(c) => c.to_css(dest), } } } impl std::ops::Mul for Length { type Output = Self; fn mul(self, other: CSSNumber) -> Length { match self { Length::Value(a) => Length::Value(a * other), Length::Calc(a) => Length::Calc(Box::new(*a * other)), } } } impl std::ops::Add for Length { type Output = Self; fn add(self, other: Length) -> Length { // Unwrap calc(...) functions so we can add inside. // Then wrap the result in a calc(...) again if necessary. let a = unwrap_calc(self); let b = unwrap_calc(other); let res = AddInternal::add(a, b); match res { Length::Calc(c) => match *c { Calc::Value(l) => *l, Calc::Function(f) if !matches!(*f, MathFunction::Calc(_)) => Length::Calc(Box::new(Calc::Function(f))), c => Length::Calc(Box::new(Calc::Function(Box::new(MathFunction::Calc(c))))), }, _ => res, } } } fn unwrap_calc(length: Length) -> Length { match length { Length::Calc(c) => match *c { Calc::Function(f) => match *f { MathFunction::Calc(c) => Length::Calc(Box::new(c)), c => Length::Calc(Box::new(Calc::Function(Box::new(c)))), }, _ => Length::Calc(c), }, _ => length, } } impl AddInternal for Length { fn add(self, other: Self) -> Self { match self.try_add(&other) { Some(r) => r, None => self.add(other), } } } impl Length { /// Constructs a length with the given pixel value. pub fn px(px: CSSNumber) -> Length { Length::Value(LengthValue::Px(px)) } /// Attempts to convert the length to pixels. /// Returns `None` if the conversion is not possible. pub fn to_px(&self) -> Option { match self { Length::Value(a) => a.to_px(), _ => None, } } 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() { std::mem::swap(&mut a, &mut b); } match (a, b) { (Length::Calc(a), Length::Calc(b)) => return Length::Calc(Box::new(a.add(*b))), (Length::Calc(calc), b) => { if let Calc::Value(a) = *calc { a.add(b) } else { Length::Calc(Box::new(Calc::Sum(Box::new((*calc).into()), Box::new(b.into())))) } } (a, Length::Calc(calc)) => { if let Calc::Value(b) = *calc { a.add(*b) } else { Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new((*calc).into())))) } } (a, b) => Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new(b.into())))), } } } impl IsCompatible for Length { fn is_compatible(&self, browsers: Browsers) -> bool { match self { Length::Value(v) => v.is_compatible(browsers), Length::Calc(calc) => calc.is_compatible(browsers), } } } impl Zero for Length { fn zero() -> Length { Length::Value(LengthValue::Px(0.0)) } fn is_zero(&self) -> bool { match self { Length::Value(v) => v.is_zero(), _ => false, } } } impl TryAdd for Length { fn try_add(&self, other: &Length) -> Option { match (self, other) { (Length::Value(a), Length::Value(b)) => { if let Some(res) = a.try_add(b) { Some(Length::Value(res)) } else { None } } (Length::Calc(a), other) => match &**a { Calc::Value(v) => v.try_add(other), Calc::Sum(a, b) => { if let Some(res) = Length::Calc(Box::new(*a.clone())).try_add(other) { return Some(res.add(Length::from(*b.clone()))); } if let Some(res) = Length::Calc(Box::new(*b.clone())).try_add(other) { return Some(Length::from(*a.clone()).add(res)); } None } _ => None, }, (other, Length::Calc(b)) => match &**b { Calc::Value(v) => other.try_add(&*v), Calc::Sum(a, b) => { if let Some(res) = other.try_add(&Length::Calc(Box::new(*a.clone()))) { return Some(res.add(Length::from(*b.clone()))); } if let Some(res) = other.try_add(&Length::Calc(Box::new(*b.clone()))) { return Some(Length::from(*a.clone()).add(res)); } None } _ => None, }, } } } impl std::convert::Into> for Length { fn into(self) -> Calc { match self { Length::Calc(c) => *c, b => Calc::Value(Box::new(b)), } } } impl std::convert::From> for Length { fn from(calc: Calc) -> Length { Length::Calc(Box::new(calc)) } } impl std::cmp::PartialOrd for Length { fn partial_cmp(&self, other: &Length) -> Option { match (self, other) { (Length::Value(a), Length::Value(b)) => a.partial_cmp(b), _ => None, } } } impl TryOp for Length { fn try_op f32>(&self, rhs: &Self, op: F) -> Option { match (self, rhs) { (Length::Value(a), Length::Value(b)) => a.try_op(b, op).map(Length::Value), _ => None, } } fn try_op_to T>(&self, rhs: &Self, op: F) -> Option { match (self, rhs) { (Length::Value(a), Length::Value(b)) => a.try_op_to(b, op), _ => None, } } } impl TryMap for Length { fn try_map f32>(&self, op: F) -> Option { match self { Length::Value(v) => v.try_map(op).map(Length::Value), _ => None, } } } impl TrySign for Length { fn try_sign(&self) -> Option { match self { Length::Value(v) => Some(v.sign()), Length::Calc(c) => c.try_sign(), } } } impl_try_from_angle!(Length); /// Either a [``](https://www.w3.org/TR/css-values-4/#lengths) or a [``](https://www.w3.org/TR/css-values-4/#numbers). #[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", content = "value", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum LengthOrNumber { /// A number. Number(CSSNumber), /// A length. Length(Length), } impl Default for LengthOrNumber { fn default() -> LengthOrNumber { LengthOrNumber::Number(0.0) } } impl Zero for LengthOrNumber { fn zero() -> Self { LengthOrNumber::Number(0.0) } fn is_zero(&self) -> bool { match self { LengthOrNumber::Length(l) => l.is_zero(), LengthOrNumber::Number(v) => v.is_zero(), } } } impl IsCompatible for LengthOrNumber { fn is_compatible(&self, browsers: Browsers) -> bool { match self { LengthOrNumber::Length(l) => l.is_compatible(browsers), LengthOrNumber::Number(..) => true, } } }