1use 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#[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 First,
35 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 pub enum ContentDistribution {
75 SpaceBetween,
77 SpaceAround,
79 SpaceEvenly,
81 Stretch,
83 }
84}
85
86enum_property! {
87 pub enum OverflowPosition {
89 Safe,
92 Unsafe,
95 }
96}
97
98enum_property! {
99 pub enum ContentPosition {
101 Center,
103 Start,
105 End,
107 FlexStart,
109 FlexEnd,
111 }
112}
113
114#[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 Normal,
127 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
129 BaselinePosition(BaselinePosition),
130 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<ContentDistribution>"))]
132 ContentDistribution(ContentDistribution),
133 ContentPosition {
135 overflow: Option<OverflowPosition>,
137 value: ContentPosition,
139 },
140}
141
142#[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 Normal,
155 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<ContentDistribution>"))]
157 ContentDistribution(ContentDistribution),
158 ContentPosition {
160 value: ContentPosition,
162 overflow: Option<OverflowPosition>,
164 },
165 Left {
167 overflow: Option<OverflowPosition>,
169 },
170 Right {
172 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 pub struct PlaceContent {
246 align: AlignContent(AlignContent, VendorPrefix),
248 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 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 pub enum SelfPosition {
310 Center,
312 Start,
314 End,
316 SelfStart,
318 SelfEnd,
320 FlexStart,
322 FlexEnd,
324 }
325}
326
327#[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 Auto,
340 Normal,
342 Stretch,
344 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
346 BaselinePosition(BaselinePosition),
347 SelfPosition {
349 overflow: Option<OverflowPosition>,
351 value: SelfPosition,
353 },
354}
355
356#[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 Auto,
369 Normal,
371 Stretch,
373 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
375 BaselinePosition(BaselinePosition),
376 SelfPosition {
378 value: SelfPosition,
380 overflow: Option<OverflowPosition>,
382 },
383 Left {
385 overflow: Option<OverflowPosition>,
387 },
388 Right {
390 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 pub struct PlaceSelf {
471 align: AlignSelf(AlignSelf, VendorPrefix),
473 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 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#[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 Normal,
543 Stretch,
545 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
547 BaselinePosition(BaselinePosition),
548 SelfPosition {
550 overflow: Option<OverflowPosition>,
552 value: SelfPosition,
554 },
555}
556
557#[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,
569 Right,
571 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#[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 Normal,
638 Stretch,
640 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<BaselinePosition>"))]
642 BaselinePosition(BaselinePosition),
643 SelfPosition {
645 value: SelfPosition,
647 overflow: Option<OverflowPosition>,
649 },
650 Left {
652 overflow: Option<OverflowPosition>,
654 },
655 Right {
657 overflow: Option<OverflowPosition>,
659 },
660 #[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 pub struct PlaceItems {
741 align: AlignItems(AlignItems, VendorPrefix),
743 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 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#[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 Normal,
812 LengthPercentage(LengthPercentage),
814}
815
816define_shorthand! {
817 pub struct Gap {
819 row: RowGap(GapValue),
821 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 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 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()) }
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 macro_rules! prefixes {
1009 ($prop: ident) => {{
1010 let mut prefix = context.targets.prefixes(VendorPrefix::None, Feature::$prop);
1011 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 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 let mut prefix = context.targets.prefixes(*prefix, Feature::$prop);
1037
1038 if prefix.contains(VendorPrefix::None) {
1039 $(
1040 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 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 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 #[allow(non_snake_case)]
1093 let $justify_prop = justify.1;
1094 $justify_prop
1095 })?;
1096
1097 if intersection.contains(VendorPrefix::None) {
1099 *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 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 prefixed_property!(BoxAlign, box_align);
1131 prefixed_property!(BoxPack, box_pack);
1132
1133 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}