1use 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
20pub type LengthPercentage = DimensionPercentage<LengthValue>;
23
24impl LengthPercentage {
25 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#[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 Auto,
64 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 #[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 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 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 unit: LengthUnit,
322 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 Px,
340 In,
342 Cm,
344 Mm,
346 Q / QUnit,
348 Pt,
350 Pc,
352
353 Em,
357 Rem / RemUnit,
360 Ex / ExUnit,
362 Rex,
364 Ch / ChUnit,
366 Rch,
368 Cap / CapUnit,
370 Rcap,
372 Ic / IcUnit,
374 Ric,
376 Lh / LhUnit,
378 Rlh / RlhUnit,
380
381 Vw / VwUnit,
384 Lvw / ViewportPercentageUnitsLarge,
386 Svw / ViewportPercentageUnitsSmall,
388 Dvw / ViewportPercentageUnitsDynamic,
390 Cqw / ContainerQueryLengthUnits,
392
393 Vh / VhUnit,
395 Lvh / ViewportPercentageUnitsLarge,
397 Svh / ViewportPercentageUnitsSmall,
399 Dvh / ViewportPercentageUnitsDynamic,
401 Cqh / ContainerQueryLengthUnits,
403
404 Vi / ViUnit,
407 Svi / ViewportPercentageUnitsSmall,
410 Lvi / ViewportPercentageUnitsLarge,
413 Dvi / ViewportPercentageUnitsDynamic,
416 Cqi / ContainerQueryLengthUnits,
418
419 Vb / VbUnit,
422 Svb / ViewportPercentageUnitsSmall,
425 Lvb / ViewportPercentageUnitsLarge,
428 Dvb / ViewportPercentageUnitsDynamic,
431 Cqb / ContainerQueryLengthUnits,
433
434 Vmin / VminUnit,
436 Svmin / ViewportPercentageUnitsSmall,
438 Lvmin / ViewportPercentageUnitsLarge,
440 Dvmin / ViewportPercentageUnitsDynamic,
442 Cqmin / ContainerQueryLengthUnits,
444
445 Vmax / VmaxUnit,
447 Svmax / ViewportPercentageUnitsSmall,
449 Lvmax / ViewportPercentageUnitsLarge,
451 Dvmax / ViewportPercentageUnitsDynamic,
453 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 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 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#[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 Value(LengthValue),
544 #[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 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 pub fn px(px: CSSNumber) -> Length {
630 Length::Value(LengthValue::Px(px))
631 }
632
633 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#[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 Number(CSSNumber),
818 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}