lightningcss/values/
length.rs

1//! CSS length values.
2
3use super::angle::impl_try_from_angle;
4use super::calc::{Calc, MathFunction};
5use super::number::CSSNumber;
6use super::percentage::DimensionPercentage;
7use crate::error::{ParserError, PrinterError};
8use crate::printer::Printer;
9use crate::targets::Browsers;
10use crate::traits::{
11  private::{AddInternal, TryAdd},
12  Map, Parse, Sign, ToCss, TryMap, TryOp, Zero,
13};
14use crate::traits::{IsCompatible, TrySign};
15#[cfg(feature = "visitor")]
16use crate::visitor::Visit;
17use const_str;
18use cssparser::*;
19
20/// A CSS [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage) value.
21/// May be specified as either a length or a percentage that resolves to an length.
22pub type LengthPercentage = DimensionPercentage<LengthValue>;
23
24impl LengthPercentage {
25  /// Constructs a `LengthPercentage` with the given pixel value.
26  pub fn px(val: CSSNumber) -> LengthPercentage {
27    LengthPercentage::Dimension(LengthValue::Px(val))
28  }
29
30  pub(crate) fn to_css_unitless<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
31  where
32    W: std::fmt::Write,
33  {
34    match self {
35      DimensionPercentage::Dimension(d) => d.to_css_unitless(dest),
36      _ => self.to_css(dest),
37    }
38  }
39}
40
41impl IsCompatible for LengthPercentage {
42  fn is_compatible(&self, browsers: Browsers) -> bool {
43    match self {
44      LengthPercentage::Dimension(d) => d.is_compatible(browsers),
45      LengthPercentage::Calc(c) => c.is_compatible(browsers),
46      LengthPercentage::Percentage(..) => true,
47    }
48  }
49}
50
51/// Either a [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword.
52#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
53#[cfg_attr(feature = "visitor", derive(Visit))]
54#[cfg_attr(
55  feature = "serde",
56  derive(serde::Serialize, serde::Deserialize),
57  serde(tag = "type", content = "value", rename_all = "kebab-case")
58)]
59#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
60#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
61pub enum LengthPercentageOrAuto {
62  /// The `auto` keyword.
63  Auto,
64  /// A [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage).
65  LengthPercentage(LengthPercentage),
66}
67
68impl IsCompatible for LengthPercentageOrAuto {
69  fn is_compatible(&self, browsers: Browsers) -> bool {
70    match self {
71      LengthPercentageOrAuto::LengthPercentage(p) => p.is_compatible(browsers),
72      _ => true,
73    }
74  }
75}
76
77const PX_PER_IN: f32 = 96.0;
78const PX_PER_CM: f32 = PX_PER_IN / 2.54;
79const PX_PER_MM: f32 = PX_PER_CM / 10.0;
80const PX_PER_Q: f32 = PX_PER_CM / 40.0;
81const PX_PER_PT: f32 = PX_PER_IN / 72.0;
82const PX_PER_PC: f32 = PX_PER_IN / 6.0;
83
84macro_rules! define_length_units {
85  (
86    $(
87      $(#[$meta: meta])*
88      $name: ident $(/ $feature: ident)?,
89    )+
90  ) => {
91    /// A CSS [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) value,
92    /// without support for `calc()`. See also: [Length](Length).
93    #[derive(Debug, Clone, PartialEq)]
94    #[cfg_attr(feature = "visitor", derive(Visit))]
95    #[cfg_attr(feature = "visitor", visit(visit_length, LENGTHS))]
96    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "unit", content = "value", rename_all = "kebab-case"))]
97    #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
98    pub enum LengthValue {
99      $(
100        $(#[$meta])*
101        $name(CSSNumber),
102      )+
103    }
104
105    impl<'i> Parse<'i> for LengthValue {
106      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
107        let location = input.current_source_location();
108        let token = input.next()?;
109        match *token {
110          Token::Dimension { value, ref unit, .. } => {
111            Ok(match unit {
112              $(
113                s if s.eq_ignore_ascii_case(stringify!($name)) => LengthValue::$name(value),
114              )+
115              _ => return Err(location.new_unexpected_token_error(token.clone())),
116            })
117          },
118          Token::Number { value, .. } => {
119            // TODO: quirks mode only?
120            Ok(LengthValue::Px(value))
121          }
122          ref token => return Err(location.new_unexpected_token_error(token.clone())),
123        }
124      }
125    }
126
127    impl<'i> TryFrom<&Token<'i>> for LengthValue {
128      type Error = ();
129
130      fn try_from(token: &Token) -> Result<Self, Self::Error> {
131        match token {
132          Token::Dimension { value, ref unit, .. } => {
133            Ok(match unit {
134              $(
135                s if s.eq_ignore_ascii_case(stringify!($name)) => LengthValue::$name(*value),
136              )+
137              _ => return Err(()),
138            })
139          },
140          _ => Err(())
141        }
142      }
143    }
144
145    impl LengthValue {
146      /// Returns the numeric value and unit string for the length value.
147      pub fn to_unit_value(&self) -> (CSSNumber, &str) {
148        match self {
149          $(
150            LengthValue::$name(value) => (*value, const_str::convert_ascii_case!(lower, stringify!($name))),
151          )+
152        }
153      }
154    }
155
156    impl IsCompatible for LengthValue {
157      fn is_compatible(&self, browsers: Browsers) -> bool {
158        macro_rules! is_compatible {
159          ($f: ident) => {
160            crate::compat::Feature::$f.is_compatible(browsers)
161          };
162          () => {
163            true
164          };
165        }
166
167        match self {
168          $(
169            LengthValue::$name(_) => {
170              is_compatible!($($feature)?)
171            }
172          )+
173        }
174      }
175    }
176
177    impl TryAdd<LengthValue> for LengthValue {
178      fn try_add(&self, other: &LengthValue) -> Option<LengthValue> {
179        use LengthValue::*;
180        match (self, other) {
181          $(
182            ($name(a), $name(b)) => Some($name(a + b)),
183          )+
184          (a, b) => {
185            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {
186              Some(Px(a + b))
187            } else {
188              None
189            }
190          }
191        }
192      }
193    }
194
195    impl std::ops::Mul<CSSNumber> for LengthValue {
196      type Output = Self;
197
198      fn mul(self, other: CSSNumber) -> LengthValue {
199        use LengthValue::*;
200        match self {
201          $(
202            $name(value) => $name(value * other),
203          )+
204        }
205      }
206    }
207
208    impl std::cmp::PartialOrd<LengthValue> for LengthValue {
209      fn partial_cmp(&self, other: &LengthValue) -> Option<std::cmp::Ordering> {
210        use LengthValue::*;
211        match (self, other) {
212          $(
213            ($name(a), $name(b)) => a.partial_cmp(b),
214          )+
215          (a, b) => {
216            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {
217              a.partial_cmp(&b)
218            } else {
219              None
220            }
221          }
222        }
223      }
224    }
225
226    impl TryOp for LengthValue {
227      fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self> {
228        use LengthValue::*;
229        match (self, rhs) {
230          $(
231            ($name(a), $name(b)) => Some($name(op(*a, *b))),
232          )+
233          (a, b) => {
234            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {
235              Some(Px(op(a, b)))
236            } else {
237              None
238            }
239          }
240        }
241      }
242
243      fn try_op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> Option<T> {
244        use LengthValue::*;
245        match (self, rhs) {
246          $(
247            ($name(a), $name(b)) => Some(op(*a, *b)),
248          )+
249          (a, b) => {
250            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {
251              Some(op(a, b))
252            } else {
253              None
254            }
255          }
256        }
257      }
258    }
259
260    impl Map for LengthValue {
261      fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self {
262        use LengthValue::*;
263        match self {
264          $(
265            $name(value) => $name(op(*value)),
266          )+
267        }
268      }
269    }
270
271    impl Sign for LengthValue {
272      fn sign(&self) -> f32 {
273        use LengthValue::*;
274        match self {
275          $(
276            $name(value) => value.sign(),
277          )+
278        }
279      }
280    }
281
282    impl Zero for LengthValue {
283      fn zero() -> Self {
284        LengthValue::Px(0.0)
285      }
286
287      fn is_zero(&self) -> bool {
288        use LengthValue::*;
289        match self {
290          $(
291            $name(value) => value.is_zero(),
292          )+
293        }
294      }
295    }
296
297    impl_try_from_angle!(LengthValue);
298
299    #[cfg(feature = "jsonschema")]
300    #[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
301    impl schemars::JsonSchema for LengthValue {
302      fn is_referenceable() -> bool {
303        true
304      }
305
306      fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
307        #[derive(schemars::JsonSchema)]
308        #[schemars(rename_all = "lowercase")]
309        #[allow(dead_code)]
310        enum LengthUnit {
311          $(
312            $(#[$meta])*
313            $name,
314          )+
315        }
316
317        #[derive(schemars::JsonSchema)]
318        #[allow(dead_code)]
319        struct LengthValue {
320          /// The length unit.
321          unit: LengthUnit,
322          /// The length value.
323          value: CSSNumber
324        }
325
326        LengthValue::json_schema(gen)
327      }
328
329      fn schema_name() -> String {
330        "LengthValue".into()
331      }
332    }
333  };
334}
335
336define_length_units! {
337  // https://www.w3.org/TR/css-values-4/#absolute-lengths
338  /// A length in pixels.
339  Px,
340  /// A length in inches. 1in = 96px.
341  In,
342  /// A length in centimeters. 1cm = 96px / 2.54.
343  Cm,
344  /// A length in millimeters. 1mm = 1/10th of 1cm.
345  Mm,
346  /// A length in quarter-millimeters. 1Q = 1/40th of 1cm.
347  Q / QUnit,
348  /// A length in points. 1pt = 1/72nd of 1in.
349  Pt,
350  /// A length in picas. 1pc = 1/6th of 1in.
351  Pc,
352
353  // https://www.w3.org/TR/css-values-4/#font-relative-lengths
354  /// A length in the `em` unit. An `em` is equal to the computed value of the
355  /// font-size property of the element on which it is used.
356  Em,
357  /// A length in the `rem` unit. A `rem` is equal to the computed value of the
358  /// `em` unit on the root element.
359  Rem / RemUnit,
360  /// A length in `ex` unit. An `ex` is equal to the x-height of the font.
361  Ex / ExUnit,
362  /// A length in the `rex` unit. A `rex` is equal to the value of the `ex` unit on the root element.
363  Rex,
364  /// A length in the `ch` unit. A `ch` is equal to the width of the zero ("0") character in the current font.
365  Ch / ChUnit,
366  /// A length in the `rch` unit. An `rch` is equal to the value of the `ch` unit on the root element.
367  Rch,
368  /// A length in the `cap` unit. A `cap` is equal to the cap-height of the font.
369  Cap / CapUnit,
370  /// A length in the `rcap` unit. An `rcap` is equal to the value of the `cap` unit on the root element.
371  Rcap,
372  /// A length in the `ic` unit. An `ic` is equal to the width of the “水” (CJK water ideograph) character in the current font.
373  Ic / IcUnit,
374  /// A length in the `ric` unit. An `ric` is equal to the value of the `ic` unit on the root element.
375  Ric,
376  /// A length in the `lh` unit. An `lh` is equal to the computed value of the `line-height` property.
377  Lh / LhUnit,
378  /// A length in the `rlh` unit. An `rlh` is equal to the value of the `lh` unit on the root element.
379  Rlh / RlhUnit,
380
381  // https://www.w3.org/TR/css-values-4/#viewport-relative-units
382  /// 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).
383  Vw / VwUnit,
384  /// 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).
385  Lvw / ViewportPercentageUnitsLarge,
386  /// 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).
387  Svw / ViewportPercentageUnitsSmall,
388  /// 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).
389  Dvw / ViewportPercentageUnitsDynamic,
390  /// 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.
391  Cqw / ContainerQueryLengthUnits,
392
393  /// 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).
394  Vh / VhUnit,
395  /// 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).
396  Lvh / ViewportPercentageUnitsLarge,
397  /// 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).
398  Svh / ViewportPercentageUnitsSmall,
399  /// 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).
400  Dvh / ViewportPercentageUnitsDynamic,
401  /// 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.
402  Cqh / ContainerQueryLengthUnits,
403
404  /// 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)
405  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).
406  Vi / ViUnit,
407  /// 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)
408  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).
409  Svi / ViewportPercentageUnitsSmall,
410  /// 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)
411  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).
412  Lvi / ViewportPercentageUnitsLarge,
413  /// 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)
414  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).
415  Dvi / ViewportPercentageUnitsDynamic,
416  /// 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.
417  Cqi / ContainerQueryLengthUnits,
418
419  /// 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)
420  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).
421  Vb / VbUnit,
422  /// 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)
423  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).
424  Svb / ViewportPercentageUnitsSmall,
425  /// 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)
426  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).
427  Lvb / ViewportPercentageUnitsLarge,
428  /// 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)
429  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).
430  Dvb / ViewportPercentageUnitsDynamic,
431  /// 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.
432  Cqb / ContainerQueryLengthUnits,
433
434  /// A length in the `vmin` unit. A `vmin` is equal to the smaller of `vw` and `vh`.
435  Vmin / VminUnit,
436  /// A length in the `svmin` unit. An `svmin` is equal to the smaller of `svw` and `svh`.
437  Svmin / ViewportPercentageUnitsSmall,
438  /// A length in the `lvmin` unit. An `lvmin` is equal to the smaller of `lvw` and `lvh`.
439  Lvmin / ViewportPercentageUnitsLarge,
440  /// A length in the `dvmin` unit. A `dvmin` is equal to the smaller of `dvw` and `dvh`.
441  Dvmin / ViewportPercentageUnitsDynamic,
442  /// A length in the `cqmin` unit. An `cqmin` is equal to the smaller of `cqi` and `cqb`.
443  Cqmin / ContainerQueryLengthUnits,
444
445  /// A length in the `vmax` unit. A `vmax` is equal to the larger of `vw` and `vh`.
446  Vmax / VmaxUnit,
447  /// A length in the `svmax` unit. An `svmax` is equal to the larger of `svw` and `svh`.
448  Svmax / ViewportPercentageUnitsSmall,
449  /// A length in the `lvmax` unit. An `lvmax` is equal to the larger of `lvw` and `lvh`.
450  Lvmax / ViewportPercentageUnitsLarge,
451  /// A length in the `dvmax` unit. An `dvmax` is equal to the larger of `dvw` and `dvh`.
452  Dvmax / ViewportPercentageUnitsDynamic,
453  /// A length in the `cqmax` unit. An `cqmin` is equal to the larger of `cqi` and `cqb`.
454  Cqmax / ContainerQueryLengthUnits,
455}
456
457impl ToCss for LengthValue {
458  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
459  where
460    W: std::fmt::Write,
461  {
462    let (value, unit) = self.to_unit_value();
463
464    // The unit can be omitted if the value is zero, except inside calc()
465    // expressions, where unitless numbers won't be parsed as dimensions.
466    if !dest.in_calc && value == 0.0 {
467      return dest.write_char('0');
468    }
469
470    serialize_dimension(value, unit, dest)
471  }
472}
473
474impl LengthValue {
475  pub(crate) fn to_css_unitless<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
476  where
477    W: std::fmt::Write,
478  {
479    match self {
480      LengthValue::Px(value) => value.to_css(dest),
481      _ => self.to_css(dest),
482    }
483  }
484}
485
486pub(crate) fn serialize_dimension<W>(value: f32, unit: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>
487where
488  W: std::fmt::Write,
489{
490  use cssparser::ToCss;
491  let int_value = if value.fract() == 0.0 { Some(value as i32) } else { None };
492  let token = Token::Dimension {
493    has_sign: value < 0.0,
494    value,
495    int_value,
496    unit: CowRcStr::from(unit),
497  };
498  if value != 0.0 && value.abs() < 1.0 {
499    let mut s = String::new();
500    token.to_css(&mut s)?;
501    if value < 0.0 {
502      dest.write_char('-')?;
503      dest.write_str(s.trim_start_matches("-0"))
504    } else {
505      dest.write_str(s.trim_start_matches('0'))
506    }
507  } else {
508    token.to_css(dest)?;
509    Ok(())
510  }
511}
512
513impl LengthValue {
514  /// Attempts to convert the value to pixels.
515  /// Returns `None` if the conversion is not possible.
516  pub fn to_px(&self) -> Option<CSSNumber> {
517    use LengthValue::*;
518    match self {
519      Px(value) => Some(*value),
520      In(value) => Some(value * PX_PER_IN),
521      Cm(value) => Some(value * PX_PER_CM),
522      Mm(value) => Some(value * PX_PER_MM),
523      Q(value) => Some(value * PX_PER_Q),
524      Pt(value) => Some(value * PX_PER_PT),
525      Pc(value) => Some(value * PX_PER_PC),
526      _ => None,
527    }
528  }
529}
530
531/// A CSS [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) value, with support for `calc()`.
532#[derive(Debug, Clone, PartialEq)]
533#[cfg_attr(feature = "visitor", derive(Visit))]
534#[cfg_attr(
535  feature = "serde",
536  derive(serde::Serialize, serde::Deserialize),
537  serde(tag = "type", content = "value", rename_all = "kebab-case")
538)]
539#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
540#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
541pub enum Length {
542  /// An explicitly specified length value.
543  Value(LengthValue),
544  /// A computed length value using `calc()`.
545  #[cfg_attr(feature = "visitor", skip_type)]
546  Calc(Box<Calc<Length>>),
547}
548
549impl<'i> Parse<'i> for Length {
550  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
551    match input.try_parse(Calc::parse) {
552      Ok(Calc::Value(v)) => return Ok(*v),
553      Ok(calc) => return Ok(Length::Calc(Box::new(calc))),
554      _ => {}
555    }
556
557    let len = LengthValue::parse(input)?;
558    Ok(Length::Value(len))
559  }
560}
561
562impl ToCss for Length {
563  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
564  where
565    W: std::fmt::Write,
566  {
567    match self {
568      Length::Value(a) => a.to_css(dest),
569      Length::Calc(c) => c.to_css(dest),
570    }
571  }
572}
573
574impl std::ops::Mul<CSSNumber> for Length {
575  type Output = Self;
576
577  fn mul(self, other: CSSNumber) -> Length {
578    match self {
579      Length::Value(a) => Length::Value(a * other),
580      Length::Calc(a) => Length::Calc(Box::new(*a * other)),
581    }
582  }
583}
584
585impl std::ops::Add<Length> for Length {
586  type Output = Self;
587
588  fn add(self, other: Length) -> Length {
589    // Unwrap calc(...) functions so we can add inside.
590    // Then wrap the result in a calc(...) again if necessary.
591    let a = unwrap_calc(self);
592    let b = unwrap_calc(other);
593    let res = AddInternal::add(a, b);
594    match res {
595      Length::Calc(c) => match *c {
596        Calc::Value(l) => *l,
597        Calc::Function(f) if !matches!(*f, MathFunction::Calc(_)) => Length::Calc(Box::new(Calc::Function(f))),
598        c => Length::Calc(Box::new(Calc::Function(Box::new(MathFunction::Calc(c))))),
599      },
600      _ => res,
601    }
602  }
603}
604
605fn unwrap_calc(length: Length) -> Length {
606  match length {
607    Length::Calc(c) => match *c {
608      Calc::Function(f) => match *f {
609        MathFunction::Calc(c) => Length::Calc(Box::new(c)),
610        c => Length::Calc(Box::new(Calc::Function(Box::new(c)))),
611      },
612      _ => Length::Calc(c),
613    },
614    _ => length,
615  }
616}
617
618impl AddInternal for Length {
619  fn add(self, other: Self) -> Self {
620    match self.try_add(&other) {
621      Some(r) => r,
622      None => self.add(other),
623    }
624  }
625}
626
627impl Length {
628  /// Constructs a length with the given pixel value.
629  pub fn px(px: CSSNumber) -> Length {
630    Length::Value(LengthValue::Px(px))
631  }
632
633  /// Attempts to convert the length to pixels.
634  /// Returns `None` if the conversion is not possible.
635  pub fn to_px(&self) -> Option<CSSNumber> {
636    match self {
637      Length::Value(a) => a.to_px(),
638      _ => None,
639    }
640  }
641
642  fn add(self, other: Length) -> Length {
643    let mut a = self;
644    let mut b = other;
645
646    if a.is_zero() {
647      return b;
648    }
649
650    if b.is_zero() {
651      return a;
652    }
653
654    if a.is_sign_negative() && b.is_sign_positive() {
655      std::mem::swap(&mut a, &mut b);
656    }
657
658    match (a, b) {
659      (Length::Calc(a), Length::Calc(b)) => return Length::Calc(Box::new(a.add(*b))),
660      (Length::Calc(calc), b) => {
661        if let Calc::Value(a) = *calc {
662          a.add(b)
663        } else {
664          Length::Calc(Box::new(Calc::Sum(Box::new((*calc).into()), Box::new(b.into()))))
665        }
666      }
667      (a, Length::Calc(calc)) => {
668        if let Calc::Value(b) = *calc {
669          a.add(*b)
670        } else {
671          Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new((*calc).into()))))
672        }
673      }
674      (a, b) => Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new(b.into())))),
675    }
676  }
677}
678
679impl IsCompatible for Length {
680  fn is_compatible(&self, browsers: Browsers) -> bool {
681    match self {
682      Length::Value(v) => v.is_compatible(browsers),
683      Length::Calc(calc) => calc.is_compatible(browsers),
684    }
685  }
686}
687
688impl Zero for Length {
689  fn zero() -> Length {
690    Length::Value(LengthValue::Px(0.0))
691  }
692
693  fn is_zero(&self) -> bool {
694    match self {
695      Length::Value(v) => v.is_zero(),
696      _ => false,
697    }
698  }
699}
700
701impl TryAdd<Length> for Length {
702  fn try_add(&self, other: &Length) -> Option<Length> {
703    match (self, other) {
704      (Length::Value(a), Length::Value(b)) => {
705        if let Some(res) = a.try_add(b) {
706          Some(Length::Value(res))
707        } else {
708          None
709        }
710      }
711      (Length::Calc(a), other) => match &**a {
712        Calc::Value(v) => v.try_add(other),
713        Calc::Sum(a, b) => {
714          if let Some(res) = Length::Calc(Box::new(*a.clone())).try_add(other) {
715            return Some(res.add(Length::from(*b.clone())));
716          }
717
718          if let Some(res) = Length::Calc(Box::new(*b.clone())).try_add(other) {
719            return Some(Length::from(*a.clone()).add(res));
720          }
721
722          None
723        }
724        _ => None,
725      },
726      (other, Length::Calc(b)) => match &**b {
727        Calc::Value(v) => other.try_add(&*v),
728        Calc::Sum(a, b) => {
729          if let Some(res) = other.try_add(&Length::Calc(Box::new(*a.clone()))) {
730            return Some(res.add(Length::from(*b.clone())));
731          }
732
733          if let Some(res) = other.try_add(&Length::Calc(Box::new(*b.clone()))) {
734            return Some(Length::from(*a.clone()).add(res));
735          }
736
737          None
738        }
739        _ => None,
740      },
741    }
742  }
743}
744
745impl std::convert::Into<Calc<Length>> for Length {
746  fn into(self) -> Calc<Length> {
747    match self {
748      Length::Calc(c) => *c,
749      b => Calc::Value(Box::new(b)),
750    }
751  }
752}
753
754impl std::convert::From<Calc<Length>> for Length {
755  fn from(calc: Calc<Length>) -> Length {
756    Length::Calc(Box::new(calc))
757  }
758}
759
760impl std::cmp::PartialOrd<Length> for Length {
761  fn partial_cmp(&self, other: &Length) -> Option<std::cmp::Ordering> {
762    match (self, other) {
763      (Length::Value(a), Length::Value(b)) => a.partial_cmp(b),
764      _ => None,
765    }
766  }
767}
768
769impl TryOp for Length {
770  fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self> {
771    match (self, rhs) {
772      (Length::Value(a), Length::Value(b)) => a.try_op(b, op).map(Length::Value),
773      _ => None,
774    }
775  }
776
777  fn try_op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> Option<T> {
778    match (self, rhs) {
779      (Length::Value(a), Length::Value(b)) => a.try_op_to(b, op),
780      _ => None,
781    }
782  }
783}
784
785impl TryMap for Length {
786  fn try_map<F: FnOnce(f32) -> f32>(&self, op: F) -> Option<Self> {
787    match self {
788      Length::Value(v) => v.try_map(op).map(Length::Value),
789      _ => None,
790    }
791  }
792}
793
794impl TrySign for Length {
795  fn try_sign(&self) -> Option<f32> {
796    match self {
797      Length::Value(v) => Some(v.sign()),
798      Length::Calc(c) => c.try_sign(),
799    }
800  }
801}
802
803impl_try_from_angle!(Length);
804
805/// Either a [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) or a [`<number>`](https://www.w3.org/TR/css-values-4/#numbers).
806#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
807#[cfg_attr(feature = "visitor", derive(Visit))]
808#[cfg_attr(
809  feature = "serde",
810  derive(serde::Serialize, serde::Deserialize),
811  serde(tag = "type", content = "value", rename_all = "kebab-case")
812)]
813#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
814#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
815pub enum LengthOrNumber {
816  /// A number.
817  Number(CSSNumber),
818  /// A length.
819  Length(Length),
820}
821
822impl Default for LengthOrNumber {
823  fn default() -> LengthOrNumber {
824    LengthOrNumber::Number(0.0)
825  }
826}
827
828impl Zero for LengthOrNumber {
829  fn zero() -> Self {
830    LengthOrNumber::Number(0.0)
831  }
832
833  fn is_zero(&self) -> bool {
834    match self {
835      LengthOrNumber::Length(l) => l.is_zero(),
836      LengthOrNumber::Number(v) => v.is_zero(),
837    }
838  }
839}
840
841impl IsCompatible for LengthOrNumber {
842  fn is_compatible(&self, browsers: Browsers) -> bool {
843    match self {
844      LengthOrNumber::Length(l) => l.is_compatible(browsers),
845      LengthOrNumber::Number(..) => true,
846    }
847  }
848}