lightningcss/properties/
align.rs

1//! CSS properties related to box alignment.
2
3use super::flex::{BoxAlign, BoxPack, FlexAlign, FlexItemAlign, FlexLinePack, FlexPack};
4use super::{Property, PropertyId};
5use crate::compat;
6use crate::context::PropertyHandlerContext;
7use crate::declaration::{DeclarationBlock, DeclarationList};
8use crate::error::{ParserError, PrinterError};
9use crate::macros::*;
10use crate::prefixes::{is_flex_2009, Feature};
11use crate::printer::Printer;
12use crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss};
13use crate::values::length::LengthPercentage;
14use crate::vendor_prefix::VendorPrefix;
15#[cfg(feature = "visitor")]
16use crate::visitor::Visit;
17use cssparser::*;
18
19#[cfg(feature = "serde")]
20use crate::serialization::ValueWrapper;
21
22/// A [`<baseline-position>`](https://www.w3.org/TR/css-align-3/#typedef-baseline-position) value,
23/// as used in the alignment properties.
24#[derive(Debug, Clone, PartialEq)]
25#[cfg_attr(feature = "visitor", derive(Visit))]
26#[cfg_attr(
27  feature = "serde",
28  derive(serde::Serialize, serde::Deserialize),
29  serde(rename_all = "kebab-case")
30)]
31#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
32pub enum BaselinePosition {
33  /// The first baseline.
34  First,
35  /// The last baseline.
36  Last,
37}
38
39impl<'i> Parse<'i> for BaselinePosition {
40  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
41    let location = input.current_source_location();
42    let ident = input.expect_ident()?;
43    match_ignore_ascii_case! { &*ident,
44      "baseline" => Ok(BaselinePosition::First),
45      "first" => {
46        input.expect_ident_matching("baseline")?;
47        Ok(BaselinePosition::First)
48      },
49      "last" => {
50        input.expect_ident_matching("baseline")?;
51        Ok(BaselinePosition::Last)
52      },
53      _ => Err(location.new_unexpected_token_error(
54        cssparser::Token::Ident(ident.clone())
55      ))
56    }
57  }
58}
59
60impl ToCss for BaselinePosition {
61  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
62  where
63    W: std::fmt::Write,
64  {
65    match self {
66      BaselinePosition::First => dest.write_str("baseline"),
67      BaselinePosition::Last => dest.write_str("last baseline"),
68    }
69  }
70}
71
72enum_property! {
73  /// A [`<content-distribution>`](https://www.w3.org/TR/css-align-3/#typedef-content-distribution) value.
74  pub enum ContentDistribution {
75    /// Items are spaced evenly, with the first and last items against the edge of the container.
76    SpaceBetween,
77    /// Items are spaced evenly, with half-size spaces at the start and end.
78    SpaceAround,
79    /// Items are spaced evenly, with full-size spaces at the start and end.
80    SpaceEvenly,
81    /// Items are stretched evenly to fill free space.
82    Stretch,
83  }
84}
85
86enum_property! {
87  /// An [`<overflow-position>`](https://www.w3.org/TR/css-align-3/#typedef-overflow-position) value.
88  pub enum OverflowPosition {
89    /// If the size of the alignment subject overflows the alignment container,
90    /// the alignment subject is instead aligned as if the alignment mode were start.
91    Safe,
92    /// Regardless of the relative sizes of the alignment subject and alignment
93    /// container, the given alignment value is honored.
94    Unsafe,
95  }
96}
97
98enum_property! {
99  /// A [`<content-position>`](https://www.w3.org/TR/css-align-3/#typedef-content-position) value.
100  pub enum ContentPosition {
101    /// Content is centered within the container.
102    Center,
103    /// Content is aligned to the start of the container.
104    Start,
105    /// Content is aligned to the end of the container.
106    End,
107    /// Same as `start` when within a flexbox container.
108    FlexStart,
109    /// Same as `end` when within a flexbox container.
110    FlexEnd,
111  }
112}
113
114/// A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property.
115#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
116#[cfg_attr(feature = "visitor", derive(Visit))]
117#[cfg_attr(
118  feature = "serde",
119  derive(serde::Serialize, serde::Deserialize),
120  serde(tag = "type", rename_all = "kebab-case")
121)]
122#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
123#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
124pub enum AlignContent {
125  /// Default alignment.
126  Normal,
127  /// A baseline position.
128  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
129  BaselinePosition(BaselinePosition),
130  /// A content distribution keyword.
131  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<ContentDistribution>"))]
132  ContentDistribution(ContentDistribution),
133  /// A content position keyword.
134  ContentPosition {
135    /// An overflow alignment mode.
136    overflow: Option<OverflowPosition>,
137    /// A content position keyword.
138    value: ContentPosition,
139  },
140}
141
142/// A value for the [justify-content](https://www.w3.org/TR/css-align-3/#propdef-justify-content) property.
143#[derive(Debug, Clone, PartialEq)]
144#[cfg_attr(feature = "visitor", derive(Visit))]
145#[cfg_attr(
146  feature = "serde",
147  derive(serde::Serialize, serde::Deserialize),
148  serde(tag = "type", rename_all = "kebab-case")
149)]
150#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
151#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
152pub enum JustifyContent {
153  /// Default justification.
154  Normal,
155  /// A content distribution keyword.
156  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<ContentDistribution>"))]
157  ContentDistribution(ContentDistribution),
158  /// A content position keyword.
159  ContentPosition {
160    /// A content position keyword.
161    value: ContentPosition,
162    /// An overflow alignment mode.
163    overflow: Option<OverflowPosition>,
164  },
165  /// Justify to the left.
166  Left {
167    /// An overflow alignment mode.
168    overflow: Option<OverflowPosition>,
169  },
170  /// Justify to the right.
171  Right {
172    /// An overflow alignment mode.
173    overflow: Option<OverflowPosition>,
174  },
175}
176
177impl<'i> Parse<'i> for JustifyContent {
178  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
179    if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
180      return Ok(JustifyContent::Normal);
181    }
182
183    if let Ok(val) = input.try_parse(ContentDistribution::parse) {
184      return Ok(JustifyContent::ContentDistribution(val));
185    }
186
187    let overflow = input.try_parse(OverflowPosition::parse).ok();
188    if let Ok(content_position) = input.try_parse(ContentPosition::parse) {
189      return Ok(JustifyContent::ContentPosition {
190        overflow,
191        value: content_position,
192      });
193    }
194
195    let location = input.current_source_location();
196    let ident = input.expect_ident()?;
197    match_ignore_ascii_case! { &*ident,
198      "left" => Ok(JustifyContent::Left { overflow }),
199      "right" => Ok(JustifyContent::Right { overflow }),
200      _ => Err(location.new_unexpected_token_error(
201        cssparser::Token::Ident(ident.clone())
202      ))
203    }
204  }
205}
206
207impl ToCss for JustifyContent {
208  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
209  where
210    W: std::fmt::Write,
211  {
212    match self {
213      JustifyContent::Normal => dest.write_str("normal"),
214      JustifyContent::ContentDistribution(value) => value.to_css(dest),
215      JustifyContent::ContentPosition { overflow, value } => {
216        if let Some(overflow) = overflow {
217          overflow.to_css(dest)?;
218          dest.write_str(" ")?;
219        }
220
221        value.to_css(dest)
222      }
223      JustifyContent::Left { overflow } => {
224        if let Some(overflow) = overflow {
225          overflow.to_css(dest)?;
226          dest.write_str(" ")?;
227        }
228
229        dest.write_str("left")
230      }
231      JustifyContent::Right { overflow } => {
232        if let Some(overflow) = overflow {
233          overflow.to_css(dest)?;
234          dest.write_str(" ")?;
235        }
236
237        dest.write_str("right")
238      }
239    }
240  }
241}
242
243define_shorthand! {
244  /// A value for the [place-content](https://www.w3.org/TR/css-align-3/#place-content) shorthand property.
245  pub struct PlaceContent {
246    /// The content alignment.
247    align: AlignContent(AlignContent, VendorPrefix),
248    /// The content justification.
249    justify: JustifyContent(JustifyContent, VendorPrefix),
250  }
251}
252
253impl<'i> Parse<'i> for PlaceContent {
254  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
255    let align = AlignContent::parse(input)?;
256    let justify = match input.try_parse(JustifyContent::parse) {
257      Ok(j) => j,
258      Err(_) => {
259        // The second value is assigned to justify-content; if omitted, it is copied
260        // from the first value, unless that value is a <baseline-position> in which
261        // case it is defaulted to start.
262        match align {
263          AlignContent::BaselinePosition(_) => JustifyContent::ContentPosition {
264            overflow: None,
265            value: ContentPosition::Start,
266          },
267          AlignContent::Normal => JustifyContent::Normal,
268          AlignContent::ContentDistribution(value) => JustifyContent::ContentDistribution(value.clone()),
269          AlignContent::ContentPosition { overflow, value } => JustifyContent::ContentPosition {
270            overflow: overflow.clone(),
271            value: value.clone(),
272          },
273        }
274      }
275    };
276
277    Ok(PlaceContent { align, justify })
278  }
279}
280
281impl ToCss for PlaceContent {
282  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
283  where
284    W: std::fmt::Write,
285  {
286    self.align.to_css(dest)?;
287    let is_equal = match self.justify {
288      JustifyContent::Normal if self.align == AlignContent::Normal => true,
289      JustifyContent::ContentDistribution(d) if matches!(self.align, AlignContent::ContentDistribution(d2) if d == d2) => {
290        true
291      }
292      JustifyContent::ContentPosition { overflow: o, value: c } if matches!(self.align, AlignContent::ContentPosition { overflow: o2, value: c2 } if o == o2 && c == c2) => {
293        true
294      }
295      _ => false,
296    };
297
298    if !is_equal {
299      dest.write_str(" ")?;
300      self.justify.to_css(dest)?;
301    }
302
303    Ok(())
304  }
305}
306
307enum_property! {
308  /// A [`<self-position>`](https://www.w3.org/TR/css-align-3/#typedef-self-position) value.
309  pub enum SelfPosition {
310    /// Item is centered within the container.
311    Center,
312    /// Item is aligned to the start of the container.
313    Start,
314    /// Item is aligned to the end of the container.
315    End,
316    /// Item is aligned to the edge of the container corresponding to the start side of the item.
317    SelfStart,
318    /// Item is aligned to the edge of the container corresponding to the end side of the item.
319    SelfEnd,
320    /// Item  is aligned to the start of the container, within flexbox layouts.
321    FlexStart,
322    /// Item  is aligned to the end of the container, within flexbox layouts.
323    FlexEnd,
324  }
325}
326
327/// A value for the [align-self](https://www.w3.org/TR/css-align-3/#align-self-property) property.
328#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
329#[cfg_attr(feature = "visitor", derive(Visit))]
330#[cfg_attr(
331  feature = "serde",
332  derive(serde::Serialize, serde::Deserialize),
333  serde(tag = "type", rename_all = "kebab-case")
334)]
335#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
336#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
337pub enum AlignSelf {
338  /// Automatic alignment.
339  Auto,
340  /// Default alignment.
341  Normal,
342  /// Item is stretched.
343  Stretch,
344  /// A baseline position keyword.
345  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
346  BaselinePosition(BaselinePosition),
347  /// A self position keyword.
348  SelfPosition {
349    /// An overflow alignment mode.
350    overflow: Option<OverflowPosition>,
351    /// A self position keyword.
352    value: SelfPosition,
353  },
354}
355
356/// A value for the [justify-self](https://www.w3.org/TR/css-align-3/#justify-self-property) property.
357#[derive(Debug, Clone, PartialEq)]
358#[cfg_attr(feature = "visitor", derive(Visit))]
359#[cfg_attr(
360  feature = "serde",
361  derive(serde::Serialize, serde::Deserialize),
362  serde(tag = "type", rename_all = "kebab-case")
363)]
364#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
365#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
366pub enum JustifySelf {
367  /// Automatic justification.
368  Auto,
369  /// Default justification.
370  Normal,
371  /// Item is stretched.
372  Stretch,
373  /// A baseline position keyword.
374  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
375  BaselinePosition(BaselinePosition),
376  /// A self position keyword.
377  SelfPosition {
378    /// A self position keyword.
379    value: SelfPosition,
380    /// An overflow alignment mode.
381    overflow: Option<OverflowPosition>,
382  },
383  /// Item is justified to the left.
384  Left {
385    /// An overflow alignment mode.
386    overflow: Option<OverflowPosition>,
387  },
388  /// Item is justified to the right.
389  Right {
390    /// An overflow alignment mode.
391    overflow: Option<OverflowPosition>,
392  },
393}
394
395impl<'i> Parse<'i> for JustifySelf {
396  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
397    if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
398      return Ok(JustifySelf::Auto);
399    }
400
401    if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
402      return Ok(JustifySelf::Normal);
403    }
404
405    if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() {
406      return Ok(JustifySelf::Stretch);
407    }
408
409    if let Ok(val) = input.try_parse(BaselinePosition::parse) {
410      return Ok(JustifySelf::BaselinePosition(val));
411    }
412
413    let overflow = input.try_parse(OverflowPosition::parse).ok();
414    if let Ok(value) = input.try_parse(SelfPosition::parse) {
415      return Ok(JustifySelf::SelfPosition { overflow, value });
416    }
417
418    let location = input.current_source_location();
419    let ident = input.expect_ident()?;
420    match_ignore_ascii_case! { &*ident,
421      "left" => Ok(JustifySelf::Left { overflow }),
422      "right" => Ok(JustifySelf::Right { overflow }),
423      _ => Err(location.new_unexpected_token_error(
424        cssparser::Token::Ident(ident.clone())
425      ))
426    }
427  }
428}
429
430impl ToCss for JustifySelf {
431  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
432  where
433    W: std::fmt::Write,
434  {
435    match self {
436      JustifySelf::Auto => dest.write_str("auto"),
437      JustifySelf::Normal => dest.write_str("normal"),
438      JustifySelf::Stretch => dest.write_str("stretch"),
439      JustifySelf::BaselinePosition(val) => val.to_css(dest),
440      JustifySelf::SelfPosition { overflow, value } => {
441        if let Some(overflow) = overflow {
442          overflow.to_css(dest)?;
443          dest.write_str(" ")?;
444        }
445
446        value.to_css(dest)
447      }
448      JustifySelf::Left { overflow } => {
449        if let Some(overflow) = overflow {
450          overflow.to_css(dest)?;
451          dest.write_str(" ")?;
452        }
453
454        dest.write_str("left")
455      }
456      JustifySelf::Right { overflow } => {
457        if let Some(overflow) = overflow {
458          overflow.to_css(dest)?;
459          dest.write_str(" ")?;
460        }
461
462        dest.write_str("right")
463      }
464    }
465  }
466}
467
468define_shorthand! {
469  /// A value for the [place-self](https://www.w3.org/TR/css-align-3/#place-self-property) shorthand property.
470  pub struct PlaceSelf {
471    /// The item alignment.
472    align: AlignSelf(AlignSelf, VendorPrefix),
473    /// The item justification.
474    justify: JustifySelf(JustifySelf),
475  }
476}
477
478impl<'i> Parse<'i> for PlaceSelf {
479  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
480    let align = AlignSelf::parse(input)?;
481    let justify = match input.try_parse(JustifySelf::parse) {
482      Ok(j) => j,
483      Err(_) => {
484        // The second value is assigned to justify-self; if omitted, it is copied from the first value.
485        match &align {
486          AlignSelf::Auto => JustifySelf::Auto,
487          AlignSelf::Normal => JustifySelf::Normal,
488          AlignSelf::Stretch => JustifySelf::Stretch,
489          AlignSelf::BaselinePosition(p) => JustifySelf::BaselinePosition(p.clone()),
490          AlignSelf::SelfPosition { overflow, value } => JustifySelf::SelfPosition {
491            overflow: overflow.clone(),
492            value: value.clone(),
493          },
494        }
495      }
496    };
497
498    Ok(PlaceSelf { align, justify })
499  }
500}
501
502impl ToCss for PlaceSelf {
503  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
504  where
505    W: std::fmt::Write,
506  {
507    self.align.to_css(dest)?;
508    let is_equal = match &self.justify {
509      JustifySelf::Auto => true,
510      JustifySelf::Normal => self.align == AlignSelf::Normal,
511      JustifySelf::Stretch => self.align == AlignSelf::Normal,
512      JustifySelf::BaselinePosition(p) if matches!(&self.align, AlignSelf::BaselinePosition(p2) if p == p2) => {
513        true
514      }
515      JustifySelf::SelfPosition { overflow: o, value: c } if matches!(&self.align, AlignSelf::SelfPosition  { overflow: o2, value: c2 } if o == o2 && c == c2) => {
516        true
517      }
518      _ => false,
519    };
520
521    if !is_equal {
522      dest.write_str(" ")?;
523      self.justify.to_css(dest)?;
524    }
525
526    Ok(())
527  }
528}
529
530/// A value for the [align-items](https://www.w3.org/TR/css-align-3/#align-items-property) property.
531#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
532#[cfg_attr(feature = "visitor", derive(Visit))]
533#[cfg_attr(
534  feature = "serde",
535  derive(serde::Serialize, serde::Deserialize),
536  serde(tag = "type", rename_all = "kebab-case")
537)]
538#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
539#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
540pub enum AlignItems {
541  /// Default alignment.
542  Normal,
543  /// Items are stretched.
544  Stretch,
545  /// A baseline position keyword.
546  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
547  BaselinePosition(BaselinePosition),
548  /// A self position keyword.
549  SelfPosition {
550    /// An overflow alignment mode.
551    overflow: Option<OverflowPosition>,
552    /// A self position keyword.
553    value: SelfPosition,
554  },
555}
556
557/// A legacy justification keyword, as used in the `justify-items` property.
558#[derive(Debug, Clone, PartialEq)]
559#[cfg_attr(feature = "visitor", derive(Visit))]
560#[cfg_attr(
561  feature = "serde",
562  derive(serde::Serialize, serde::Deserialize),
563  serde(rename_all = "kebab-case")
564)]
565#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
566pub enum LegacyJustify {
567  /// Left justify.
568  Left,
569  /// Right justify.
570  Right,
571  /// Centered.
572  Center,
573}
574
575impl<'i> Parse<'i> for LegacyJustify {
576  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
577    let location = input.current_source_location();
578    let ident = input.expect_ident()?;
579    match_ignore_ascii_case! { &*ident,
580      "legacy" => {
581        let location = input.current_source_location();
582        let ident = input.expect_ident()?;
583        match_ignore_ascii_case! { &*ident,
584          "left" => Ok(LegacyJustify::Left),
585          "right" => Ok(LegacyJustify::Right),
586          "center" => Ok(LegacyJustify::Center),
587          _ => Err(location.new_unexpected_token_error(
588            cssparser::Token::Ident(ident.clone())
589          ))
590        }
591      },
592      "left" => {
593        input.expect_ident_matching("legacy")?;
594        Ok(LegacyJustify::Left)
595      },
596      "right" => {
597        input.expect_ident_matching("legacy")?;
598        Ok(LegacyJustify::Right)
599      },
600      "center" => {
601        input.expect_ident_matching("legacy")?;
602        Ok(LegacyJustify::Center)
603      },
604      _ => Err(location.new_unexpected_token_error(
605        cssparser::Token::Ident(ident.clone())
606      ))
607    }
608  }
609}
610
611impl ToCss for LegacyJustify {
612  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
613  where
614    W: std::fmt::Write,
615  {
616    dest.write_str("legacy ")?;
617    match self {
618      LegacyJustify::Left => dest.write_str("left"),
619      LegacyJustify::Right => dest.write_str("right"),
620      LegacyJustify::Center => dest.write_str("center"),
621    }
622  }
623}
624
625/// A value for the [justify-items](https://www.w3.org/TR/css-align-3/#justify-items-property) property.
626#[derive(Debug, Clone, PartialEq)]
627#[cfg_attr(feature = "visitor", derive(Visit))]
628#[cfg_attr(
629  feature = "serde",
630  derive(serde::Serialize, serde::Deserialize),
631  serde(tag = "type", rename_all = "kebab-case")
632)]
633#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
634#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
635pub enum JustifyItems {
636  /// Default justification.
637  Normal,
638  /// Items are stretched.
639  Stretch,
640  /// A baseline position keyword.
641  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
642  BaselinePosition(BaselinePosition),
643  /// A self position keyword, with optional overflow position.
644  SelfPosition {
645    /// A self position keyword.
646    value: SelfPosition,
647    /// An overflow alignment mode.
648    overflow: Option<OverflowPosition>,
649  },
650  /// Items are justified to the left, with an optional overflow position.
651  Left {
652    /// An overflow alignment mode.
653    overflow: Option<OverflowPosition>,
654  },
655  /// Items are justified to the right, with an optional overflow position.
656  Right {
657    /// An overflow alignment mode.
658    overflow: Option<OverflowPosition>,
659  },
660  /// A legacy justification keyword.
661  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LegacyJustify>"))]
662  Legacy(LegacyJustify),
663}
664
665impl<'i> Parse<'i> for JustifyItems {
666  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
667    if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
668      return Ok(JustifyItems::Normal);
669    }
670
671    if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() {
672      return Ok(JustifyItems::Stretch);
673    }
674
675    if let Ok(val) = input.try_parse(BaselinePosition::parse) {
676      return Ok(JustifyItems::BaselinePosition(val));
677    }
678
679    if let Ok(val) = input.try_parse(LegacyJustify::parse) {
680      return Ok(JustifyItems::Legacy(val));
681    }
682
683    let overflow = input.try_parse(OverflowPosition::parse).ok();
684    if let Ok(value) = input.try_parse(SelfPosition::parse) {
685      return Ok(JustifyItems::SelfPosition { overflow, value });
686    }
687
688    let location = input.current_source_location();
689    let ident = input.expect_ident()?;
690    match_ignore_ascii_case! { &*ident,
691      "left" => Ok(JustifyItems::Left { overflow }),
692      "right" => Ok(JustifyItems::Right { overflow }),
693      _ => Err(location.new_unexpected_token_error(
694        cssparser::Token::Ident(ident.clone())
695      ))
696    }
697  }
698}
699
700impl ToCss for JustifyItems {
701  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
702  where
703    W: std::fmt::Write,
704  {
705    match self {
706      JustifyItems::Normal => dest.write_str("normal"),
707      JustifyItems::Stretch => dest.write_str("stretch"),
708      JustifyItems::BaselinePosition(val) => val.to_css(dest),
709      JustifyItems::Legacy(val) => val.to_css(dest),
710      JustifyItems::SelfPosition { overflow, value } => {
711        if let Some(overflow) = overflow {
712          overflow.to_css(dest)?;
713          dest.write_str(" ")?;
714        }
715
716        value.to_css(dest)
717      }
718      JustifyItems::Left { overflow } => {
719        if let Some(overflow) = overflow {
720          overflow.to_css(dest)?;
721          dest.write_str(" ")?;
722        }
723
724        dest.write_str("left")
725      }
726      JustifyItems::Right { overflow } => {
727        if let Some(overflow) = overflow {
728          overflow.to_css(dest)?;
729          dest.write_str(" ")?;
730        }
731
732        dest.write_str("right")
733      }
734    }
735  }
736}
737
738define_shorthand! {
739  /// A value for the [place-items](https://www.w3.org/TR/css-align-3/#place-items-property) shorthand property.
740  pub struct PlaceItems {
741    /// The item alignment.
742    align: AlignItems(AlignItems, VendorPrefix),
743    /// The item justification.
744    justify: JustifyItems(JustifyItems),
745  }
746}
747
748impl<'i> Parse<'i> for PlaceItems {
749  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
750    let align = AlignItems::parse(input)?;
751    let justify = match input.try_parse(JustifyItems::parse) {
752      Ok(j) => j,
753      Err(_) => {
754        // The second value is assigned to justify-items; if omitted, it is copied from the first value.
755        match &align {
756          AlignItems::Normal => JustifyItems::Normal,
757          AlignItems::Stretch => JustifyItems::Stretch,
758          AlignItems::BaselinePosition(p) => JustifyItems::BaselinePosition(p.clone()),
759          AlignItems::SelfPosition { overflow, value } => JustifyItems::SelfPosition {
760            overflow: overflow.clone(),
761            value: value.clone(),
762          },
763        }
764      }
765    };
766
767    Ok(PlaceItems { align, justify })
768  }
769}
770
771impl ToCss for PlaceItems {
772  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
773  where
774    W: std::fmt::Write,
775  {
776    self.align.to_css(dest)?;
777    let is_equal = match &self.justify {
778      JustifyItems::Normal => self.align == AlignItems::Normal,
779      JustifyItems::Stretch => self.align == AlignItems::Normal,
780      JustifyItems::BaselinePosition(p) if matches!(&self.align, AlignItems::BaselinePosition(p2) if p == p2) => {
781        true
782      }
783      JustifyItems::SelfPosition { overflow: o, value: c } if matches!(&self.align, AlignItems::SelfPosition { overflow: o2, value: c2 } if o == o2 && c == c2) => {
784        true
785      }
786      _ => false,
787    };
788
789    if !is_equal {
790      dest.write_str(" ")?;
791      self.justify.to_css(dest)?;
792    }
793
794    Ok(())
795  }
796}
797
798/// A [gap](https://www.w3.org/TR/css-align-3/#column-row-gap) value, as used in the
799/// `column-gap` and `row-gap` properties.
800#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
801#[cfg_attr(feature = "visitor", derive(Visit))]
802#[cfg_attr(
803  feature = "serde",
804  derive(serde::Serialize, serde::Deserialize),
805  serde(tag = "type", content = "value", rename_all = "kebab-case")
806)]
807#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
808#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
809pub enum GapValue {
810  /// Equal to `1em` for multi-column containers, and zero otherwise.
811  Normal,
812  /// An explicit length.
813  LengthPercentage(LengthPercentage),
814}
815
816define_shorthand! {
817  /// A value for the [gap](https://www.w3.org/TR/css-align-3/#gap-shorthand) shorthand property.
818  pub struct Gap {
819    /// The row gap.
820    row: RowGap(GapValue),
821    /// The column gap.
822    column: ColumnGap(GapValue),
823  }
824}
825
826impl<'i> Parse<'i> for Gap {
827  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
828    let row = GapValue::parse(input)?;
829    let column = input.try_parse(GapValue::parse).unwrap_or(row.clone());
830    Ok(Gap { row, column })
831  }
832}
833
834impl ToCss for Gap {
835  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
836  where
837    W: std::fmt::Write,
838  {
839    self.row.to_css(dest)?;
840    if self.column != self.row {
841      dest.write_str(" ")?;
842      self.column.to_css(dest)?;
843    }
844    Ok(())
845  }
846}
847
848#[derive(Default, Debug)]
849pub(crate) struct AlignHandler {
850  align_content: Option<(AlignContent, VendorPrefix)>,
851  flex_line_pack: Option<(FlexLinePack, VendorPrefix)>,
852  justify_content: Option<(JustifyContent, VendorPrefix)>,
853  box_pack: Option<(BoxPack, VendorPrefix)>,
854  flex_pack: Option<(FlexPack, VendorPrefix)>,
855  align_self: Option<(AlignSelf, VendorPrefix)>,
856  flex_item_align: Option<(FlexItemAlign, VendorPrefix)>,
857  justify_self: Option<JustifySelf>,
858  align_items: Option<(AlignItems, VendorPrefix)>,
859  box_align: Option<(BoxAlign, VendorPrefix)>,
860  flex_align: Option<(FlexAlign, VendorPrefix)>,
861  justify_items: Option<JustifyItems>,
862  row_gap: Option<GapValue>,
863  column_gap: Option<GapValue>,
864  has_any: bool,
865}
866
867impl<'i> PropertyHandler<'i> for AlignHandler {
868  fn handle_property(
869    &mut self,
870    property: &Property<'i>,
871    dest: &mut DeclarationList<'i>,
872    context: &mut PropertyHandlerContext<'i, '_>,
873  ) -> bool {
874    use Property::*;
875
876    macro_rules! maybe_flush {
877      ($prop: ident, $val: expr, $vp: expr) => {{
878        // If two vendor prefixes for the same property have different
879        // values, we need to flush what we have immediately to preserve order.
880        if let Some((val, prefixes)) = &self.$prop {
881          if val != $val && !prefixes.contains(*$vp) {
882            self.flush(dest, context);
883          }
884        }
885      }};
886    }
887
888    macro_rules! property {
889      ($prop: ident, $val: expr, $vp: expr) => {{
890        maybe_flush!($prop, $val, $vp);
891
892        // Otherwise, update the value and add the prefix.
893        if let Some((val, prefixes)) = &mut self.$prop {
894          *val = $val.clone();
895          *prefixes |= *$vp;
896        } else {
897          self.$prop = Some(($val.clone(), *$vp));
898          self.has_any = true;
899        }
900      }};
901    }
902
903    match property {
904      AlignContent(val, vp) => {
905        self.flex_line_pack = None;
906        property!(align_content, val, vp);
907      }
908      FlexLinePack(val, vp) => property!(flex_line_pack, val, vp),
909      JustifyContent(val, vp) => {
910        self.box_pack = None;
911        self.flex_pack = None;
912        property!(justify_content, val, vp);
913      }
914      BoxPack(val, vp) => property!(box_pack, val, vp),
915      FlexPack(val, vp) => property!(flex_pack, val, vp),
916      PlaceContent(val) => {
917        self.flex_line_pack = None;
918        self.box_pack = None;
919        self.flex_pack = None;
920        maybe_flush!(align_content, &val.align, &VendorPrefix::None);
921        maybe_flush!(justify_content, &val.justify, &VendorPrefix::None);
922        property!(align_content, &val.align, &VendorPrefix::None);
923        property!(justify_content, &val.justify, &VendorPrefix::None);
924      }
925      AlignSelf(val, vp) => {
926        self.flex_item_align = None;
927        property!(align_self, val, vp);
928      }
929      FlexItemAlign(val, vp) => property!(flex_item_align, val, vp),
930      JustifySelf(val) => {
931        self.justify_self = Some(val.clone());
932        self.has_any = true;
933      }
934      PlaceSelf(val) => {
935        self.flex_item_align = None;
936        property!(align_self, &val.align, &VendorPrefix::None);
937        self.justify_self = Some(val.justify.clone());
938      }
939      AlignItems(val, vp) => {
940        self.box_align = None;
941        self.flex_align = None;
942        property!(align_items, val, vp);
943      }
944      BoxAlign(val, vp) => property!(box_align, val, vp),
945      FlexAlign(val, vp) => property!(flex_align, val, vp),
946      JustifyItems(val) => {
947        self.justify_items = Some(val.clone());
948        self.has_any = true;
949      }
950      PlaceItems(val) => {
951        self.box_align = None;
952        self.flex_align = None;
953        property!(align_items, &val.align, &VendorPrefix::None);
954        self.justify_items = Some(val.justify.clone());
955      }
956      RowGap(val) => {
957        self.row_gap = Some(val.clone());
958        self.has_any = true;
959      }
960      ColumnGap(val) => {
961        self.column_gap = Some(val.clone());
962        self.has_any = true;
963      }
964      Gap(val) => {
965        self.row_gap = Some(val.row.clone());
966        self.column_gap = Some(val.column.clone());
967        self.has_any = true;
968      }
969      Unparsed(val) if is_align_property(&val.property_id) => {
970        self.flush(dest, context);
971        dest.push(property.clone()) // TODO: prefix?
972      }
973      _ => return false,
974    }
975
976    true
977  }
978
979  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
980    self.flush(dest, context);
981  }
982}
983
984impl AlignHandler {
985  fn flush<'i>(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
986    if !self.has_any {
987      return;
988    }
989
990    self.has_any = false;
991
992    let mut align_content = std::mem::take(&mut self.align_content);
993    let mut justify_content = std::mem::take(&mut self.justify_content);
994    let mut align_self = std::mem::take(&mut self.align_self);
995    let mut justify_self = std::mem::take(&mut self.justify_self);
996    let mut align_items = std::mem::take(&mut self.align_items);
997    let mut justify_items = std::mem::take(&mut self.justify_items);
998    let row_gap = std::mem::take(&mut self.row_gap);
999    let column_gap = std::mem::take(&mut self.column_gap);
1000    let box_align = std::mem::take(&mut self.box_align);
1001    let box_pack = std::mem::take(&mut self.box_pack);
1002    let flex_line_pack = std::mem::take(&mut self.flex_line_pack);
1003    let flex_pack = std::mem::take(&mut self.flex_pack);
1004    let flex_align = std::mem::take(&mut self.flex_align);
1005    let flex_item_align = std::mem::take(&mut self.flex_item_align);
1006
1007    // Gets prefixes for standard properties.
1008    macro_rules! prefixes {
1009      ($prop: ident) => {{
1010        let mut prefix = context.targets.prefixes(VendorPrefix::None, Feature::$prop);
1011        // Firefox only implemented the 2009 spec prefixed.
1012        // Microsoft only implemented the 2012 spec prefixed.
1013        prefix.remove(VendorPrefix::Moz | VendorPrefix::Ms);
1014        prefix
1015      }};
1016    }
1017
1018    macro_rules! standard_property {
1019      ($prop: ident, $key: ident) => {
1020        if let Some((val, prefix)) = $key {
1021          // If we have an unprefixed property, override necessary prefixes.
1022          let prefix = if prefix.contains(VendorPrefix::None) {
1023            prefixes!($prop)
1024          } else {
1025            prefix
1026          };
1027          dest.push(Property::$prop(val, prefix))
1028        }
1029      };
1030    }
1031
1032    macro_rules! legacy_property {
1033      ($prop: ident, $key: ident, $( $prop_2009: ident )?, $prop_2012: ident) => {
1034        if let Some((val, prefix)) = &$key {
1035          // If we have an unprefixed standard property, generate legacy prefixed versions.
1036          let mut prefix = context.targets.prefixes(*prefix, Feature::$prop);
1037
1038          if prefix.contains(VendorPrefix::None) {
1039            $(
1040              // 2009 spec, implemented by webkit and firefox.
1041              if let Some(targets) = context.targets.browsers {
1042                let mut prefixes_2009 = VendorPrefix::empty();
1043                if is_flex_2009(targets) {
1044                  prefixes_2009 |= VendorPrefix::WebKit;
1045                }
1046                if prefix.contains(VendorPrefix::Moz) {
1047                  prefixes_2009 |= VendorPrefix::Moz;
1048                }
1049                if !prefixes_2009.is_empty() {
1050                  if let Some(v) = $prop_2009::from_standard(&val) {
1051                    dest.push(Property::$prop_2009(v, prefixes_2009));
1052                  }
1053                }
1054              }
1055            )?
1056          }
1057
1058          // 2012 spec, implemented by microsoft.
1059          if prefix.contains(VendorPrefix::Ms) {
1060            if let Some(v) = $prop_2012::from_standard(&val) {
1061              dest.push(Property::$prop_2012(v, VendorPrefix::Ms));
1062            }
1063          }
1064
1065          // Remove Firefox and IE from standard prefixes.
1066          prefix.remove(VendorPrefix::Moz | VendorPrefix::Ms);
1067        }
1068      };
1069    }
1070
1071    macro_rules! prefixed_property {
1072      ($prop: ident, $key: expr) => {
1073        if let Some((val, prefix)) = $key {
1074          dest.push(Property::$prop(val, prefix))
1075        }
1076      };
1077    }
1078
1079    macro_rules! unprefixed_property {
1080      ($prop: ident, $key: expr) => {
1081        if let Some(val) = $key {
1082          dest.push(Property::$prop(val))
1083        }
1084      };
1085    }
1086
1087    macro_rules! shorthand {
1088      ($prop: ident, $align_prop: ident, $align: ident, $justify: ident $(, $justify_prop: ident )?) => {
1089        if let (Some((align, align_prefix)), Some(justify)) = (&mut $align, &mut $justify) {
1090          let intersection = *align_prefix $( & {
1091            // Hack for conditional compilation. Have to use a variable.
1092            #[allow(non_snake_case)]
1093            let $justify_prop = justify.1;
1094            $justify_prop
1095          })?;
1096
1097          // Only use shorthand if unprefixed.
1098          if intersection.contains(VendorPrefix::None) {
1099            // Add prefixed longhands if needed.
1100            *align_prefix = prefixes!($align_prop);
1101            align_prefix.remove(VendorPrefix::None);
1102            if !align_prefix.is_empty() {
1103              dest.push(Property::$align_prop(align.clone(), *align_prefix))
1104            }
1105
1106            $(
1107              let (justify, justify_prefix) = justify;
1108              *justify_prefix = prefixes!($justify_prop);
1109              justify_prefix.remove(VendorPrefix::None);
1110
1111              if !justify_prefix.is_empty() {
1112                dest.push(Property::$justify_prop(justify.clone(), *justify_prefix))
1113              }
1114            )?
1115
1116            // Add shorthand.
1117            dest.push(Property::$prop($prop {
1118              align: align.clone(),
1119              justify: justify.clone()
1120            }));
1121
1122            $align = None;
1123            $justify = None;
1124          }
1125        }
1126      };
1127    }
1128
1129    // 2009 properties
1130    prefixed_property!(BoxAlign, box_align);
1131    prefixed_property!(BoxPack, box_pack);
1132
1133    // 2012 properties
1134    prefixed_property!(FlexPack, flex_pack);
1135    prefixed_property!(FlexAlign, flex_align);
1136    prefixed_property!(FlexItemAlign, flex_item_align);
1137    prefixed_property!(FlexLinePack, flex_line_pack);
1138
1139    legacy_property!(AlignContent, align_content, , FlexLinePack);
1140    legacy_property!(JustifyContent, justify_content, BoxPack, FlexPack);
1141    if context.targets.is_compatible(compat::Feature::PlaceContent) {
1142      shorthand!(
1143        PlaceContent,
1144        AlignContent,
1145        align_content,
1146        justify_content,
1147        JustifyContent
1148      );
1149    }
1150    standard_property!(AlignContent, align_content);
1151    standard_property!(JustifyContent, justify_content);
1152
1153    legacy_property!(AlignSelf, align_self, , FlexItemAlign);
1154    if context.targets.is_compatible(compat::Feature::PlaceSelf) {
1155      shorthand!(PlaceSelf, AlignSelf, align_self, justify_self);
1156    }
1157    standard_property!(AlignSelf, align_self);
1158    unprefixed_property!(JustifySelf, justify_self);
1159
1160    legacy_property!(AlignItems, align_items, BoxAlign, FlexAlign);
1161    if context.targets.is_compatible(compat::Feature::PlaceItems) {
1162      shorthand!(PlaceItems, AlignItems, align_items, justify_items);
1163    }
1164    standard_property!(AlignItems, align_items);
1165    unprefixed_property!(JustifyItems, justify_items);
1166
1167    if row_gap.is_some() && column_gap.is_some() {
1168      dest.push(Property::Gap(Gap {
1169        row: row_gap.unwrap(),
1170        column: column_gap.unwrap(),
1171      }))
1172    } else {
1173      if let Some(gap) = row_gap {
1174        dest.push(Property::RowGap(gap))
1175      }
1176
1177      if let Some(gap) = column_gap {
1178        dest.push(Property::ColumnGap(gap))
1179      }
1180    }
1181  }
1182}
1183
1184#[inline]
1185fn is_align_property(property_id: &PropertyId) -> bool {
1186  match property_id {
1187    PropertyId::AlignContent(_)
1188    | PropertyId::FlexLinePack(_)
1189    | PropertyId::JustifyContent(_)
1190    | PropertyId::BoxPack(_)
1191    | PropertyId::FlexPack(_)
1192    | PropertyId::PlaceContent
1193    | PropertyId::AlignSelf(_)
1194    | PropertyId::FlexItemAlign(_)
1195    | PropertyId::JustifySelf
1196    | PropertyId::PlaceSelf
1197    | PropertyId::AlignItems(_)
1198    | PropertyId::BoxAlign(_)
1199    | PropertyId::FlexAlign(_)
1200    | PropertyId::JustifyItems
1201    | PropertyId::PlaceItems
1202    | PropertyId::RowGap
1203    | PropertyId::ColumnGap
1204    | PropertyId::Gap => true,
1205    _ => false,
1206  }
1207}