parcel_css/properties/
flex.rs

1//! CSS properties related to flexbox layout.
2
3use super::align::{
4  AlignContent, AlignItems, AlignSelf, ContentDistribution, ContentPosition, JustifyContent, SelfPosition,
5};
6use super::{Property, PropertyId};
7use crate::context::PropertyHandlerContext;
8use crate::declaration::{DeclarationBlock, DeclarationList};
9use crate::error::{ParserError, PrinterError};
10use crate::macros::*;
11use crate::prefixes::{is_flex_2009, Feature};
12use crate::printer::Printer;
13use crate::targets::Browsers;
14use crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss, Zero};
15use crate::values::number::{CSSInteger, CSSNumber};
16use crate::values::{
17  length::{LengthPercentage, LengthPercentageOrAuto},
18  percentage::Percentage,
19};
20use crate::vendor_prefix::VendorPrefix;
21use cssparser::*;
22
23enum_property! {
24  /// A value for the [flex-direction](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#propdef-flex-direction) property.
25  pub enum FlexDirection {
26    /// Flex items are laid out in a row.
27    "row": Row,
28    /// Flex items are laid out in a row, and reversed.
29    "row-reverse": RowReverse,
30    /// Flex items are laid out in a column.
31    "column": Column,
32    /// Flex items are laid out in a column, and reversed.
33    "column-reverse": ColumnReverse,
34  }
35}
36
37impl Default for FlexDirection {
38  fn default() -> FlexDirection {
39    FlexDirection::Row
40  }
41}
42
43enum_property! {
44  /// A value for the [flex-wrap](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-wrap-property) property.
45  pub enum FlexWrap {
46    /// The flex items do not wrap.
47    "nowrap": NoWrap,
48    /// The flex items wrap.
49    "wrap": Wrap,
50    /// The flex items wrap, in reverse.
51    "wrap-reverse": WrapReverse,
52  }
53}
54
55impl Default for FlexWrap {
56  fn default() -> FlexWrap {
57    FlexWrap::NoWrap
58  }
59}
60
61impl FromStandard<FlexWrap> for FlexWrap {
62  fn from_standard(wrap: &FlexWrap) -> Option<FlexWrap> {
63    Some(wrap.clone())
64  }
65}
66
67define_shorthand! {
68  /// A value for the [flex-flow](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-flow-property) shorthand property.
69  pub struct FlexFlow(VendorPrefix) {
70    /// The direction that flex items flow.
71    direction: FlexDirection(FlexDirection, VendorPrefix),
72    /// How the flex items wrap.
73    wrap: FlexWrap(FlexWrap, VendorPrefix),
74  }
75}
76
77impl<'i> Parse<'i> for FlexFlow {
78  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
79    let mut direction = None;
80    let mut wrap = None;
81    loop {
82      if direction.is_none() {
83        if let Ok(value) = input.try_parse(FlexDirection::parse) {
84          direction = Some(value);
85          continue;
86        }
87      }
88      if wrap.is_none() {
89        if let Ok(value) = input.try_parse(FlexWrap::parse) {
90          wrap = Some(value);
91          continue;
92        }
93      }
94      break;
95    }
96
97    Ok(FlexFlow {
98      direction: direction.unwrap_or_default(),
99      wrap: wrap.unwrap_or_default(),
100    })
101  }
102}
103
104impl ToCss for FlexFlow {
105  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
106  where
107    W: std::fmt::Write,
108  {
109    let mut needs_space = false;
110    if self.direction != FlexDirection::default() || self.wrap == FlexWrap::default() {
111      self.direction.to_css(dest)?;
112      needs_space = true;
113    }
114
115    if self.wrap != FlexWrap::default() {
116      if needs_space {
117        dest.write_str(" ")?;
118      }
119      self.wrap.to_css(dest)?;
120    }
121
122    Ok(())
123  }
124}
125
126define_shorthand! {
127/// A value for the [flex](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#flex-property) shorthand property.
128  pub struct Flex(VendorPrefix) {
129    /// The flex grow factor.
130    grow: FlexGrow(CSSNumber, VendorPrefix),
131    /// The flex shrink factor.
132    shrink: FlexShrink(CSSNumber, VendorPrefix),
133    /// The flex basis.
134    basis: FlexBasis(LengthPercentageOrAuto, VendorPrefix),
135  }
136}
137
138impl<'i> Parse<'i> for Flex {
139  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
140    if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
141      return Ok(Flex {
142        grow: 0.0,
143        shrink: 0.0,
144        basis: LengthPercentageOrAuto::Auto,
145      });
146    }
147
148    let mut grow = None;
149    let mut shrink = None;
150    let mut basis = None;
151
152    loop {
153      if grow.is_none() {
154        if let Ok(val) = input.try_parse(CSSNumber::parse) {
155          grow = Some(val);
156          shrink = input.try_parse(CSSNumber::parse).ok();
157          continue;
158        }
159      }
160
161      if basis.is_none() {
162        if let Ok(val) = input.try_parse(LengthPercentageOrAuto::parse) {
163          basis = Some(val);
164          continue;
165        }
166      }
167
168      break;
169    }
170
171    Ok(Flex {
172      grow: grow.unwrap_or(1.0),
173      shrink: shrink.unwrap_or(1.0),
174      basis: basis.unwrap_or(LengthPercentageOrAuto::LengthPercentage(LengthPercentage::Percentage(
175        Percentage(0.0),
176      ))),
177    })
178  }
179}
180
181impl ToCss for Flex {
182  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
183  where
184    W: std::fmt::Write,
185  {
186    if self.grow == 0.0 && self.shrink == 0.0 && self.basis == LengthPercentageOrAuto::Auto {
187      dest.write_str("none")?;
188      return Ok(());
189    }
190
191    let is_basis_zero = match &self.basis {
192      LengthPercentageOrAuto::LengthPercentage(lp) => lp.is_zero(),
193      _ => false,
194    };
195
196    if self.grow != 1.0 || self.shrink != 1.0 || is_basis_zero {
197      self.grow.to_css(dest)?;
198      if self.shrink != 1.0 {
199        dest.write_str(" ")?;
200        self.shrink.to_css(dest)?;
201      }
202    }
203
204    if !is_basis_zero {
205      if self.grow != 1.0 || self.shrink != 1.0 {
206        dest.write_str(" ")?;
207      }
208      self.basis.to_css(dest)?;
209    }
210
211    Ok(())
212  }
213}
214
215// Old flex (2009): https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/
216
217enum_property! {
218  /// A value for the legacy (prefixed) [box-orient](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#orientation) property.
219  /// Partially equivalent to `flex-direction` in the standard syntax.
220  pub enum BoxOrient {
221    /// Items are laid out horizontally.
222    "horizontal": Horizontal,
223    /// Items are laid out vertically.
224    "vertical": Vertical,
225    /// Items are laid out along the inline axis, according to the writing direction.
226    "inline-axis": InlineAxis,
227    /// Items are laid out along the block axis, according to the writing direction.
228    "block-axis": BlockAxis,
229  }
230}
231
232impl FlexDirection {
233  fn to_2009(&self) -> (BoxOrient, BoxDirection) {
234    match self {
235      FlexDirection::Row => (BoxOrient::Horizontal, BoxDirection::Normal),
236      FlexDirection::Column => (BoxOrient::Vertical, BoxDirection::Normal),
237      FlexDirection::RowReverse => (BoxOrient::Horizontal, BoxDirection::Reverse),
238      FlexDirection::ColumnReverse => (BoxOrient::Vertical, BoxDirection::Reverse),
239    }
240  }
241}
242
243enum_property! {
244  /// A value for the legacy (prefixed) [box-direction](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#displayorder) property.
245  /// Partially equivalent to the `flex-direction` property in the standard syntax.
246  pub enum BoxDirection {
247    /// Items flow in the natural direction.
248    Normal,
249    /// Items flow in the reverse direction.
250    Reverse,
251  }
252}
253
254enum_property! {
255  /// A value for the legacy (prefixed) [box-align](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#alignment) property.
256  /// Equivalent to the `align-items` property in the standard syntax.
257  pub enum BoxAlign {
258    /// Items are aligned to the start.
259    Start,
260    /// Items are aligned to the end.
261    End,
262    /// Items are centered.
263    Center,
264    /// Items are aligned to the baseline.
265    Baseline,
266    /// Items are stretched.
267    Stretch,
268  }
269}
270
271impl FromStandard<AlignItems> for BoxAlign {
272  fn from_standard(align: &AlignItems) -> Option<BoxAlign> {
273    match align {
274      AlignItems::SelfPosition(None, sp) => match sp {
275        SelfPosition::Start | SelfPosition::FlexStart => Some(BoxAlign::Start),
276        SelfPosition::End | SelfPosition::FlexEnd => Some(BoxAlign::End),
277        SelfPosition::Center => Some(BoxAlign::Center),
278        _ => None,
279      },
280      AlignItems::Stretch => Some(BoxAlign::Stretch),
281      _ => None,
282    }
283  }
284}
285
286enum_property! {
287  /// A value for the legacy (prefixed) [box-pack](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#packing) property.
288  /// Equivalent to the `justify-content` property in the standard syntax.
289  pub enum BoxPack {
290    /// Items are justified to the start.
291    Start,
292    /// Items are justified to the end.
293    End,
294    /// Items are centered.
295    Center,
296    /// Items are justifed to the start and end.
297    Justify,
298  }
299}
300
301impl FromStandard<JustifyContent> for BoxPack {
302  fn from_standard(justify: &JustifyContent) -> Option<BoxPack> {
303    match justify {
304      JustifyContent::ContentDistribution(cd) => match cd {
305        ContentDistribution::SpaceBetween => Some(BoxPack::Justify),
306        _ => None,
307      },
308      JustifyContent::ContentPosition(None, pos) => match pos {
309        ContentPosition::Start | ContentPosition::FlexStart => Some(BoxPack::Start),
310        ContentPosition::End | ContentPosition::FlexEnd => Some(BoxPack::End),
311        ContentPosition::Center => Some(BoxPack::Center),
312      },
313      _ => None,
314    }
315  }
316}
317
318enum_property! {
319  /// A value for the legacy (prefixed) [box-lines](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#multiple) property.
320  /// Equivalent to the `flex-wrap` property in the standard syntax.
321  pub enum BoxLines {
322    /// Items are laid out in a single line.
323    Single,
324    /// Items may wrap into multiple lines.
325    Multiple,
326  }
327}
328
329impl FromStandard<FlexWrap> for BoxLines {
330  fn from_standard(wrap: &FlexWrap) -> Option<BoxLines> {
331    match wrap {
332      FlexWrap::NoWrap => Some(BoxLines::Single),
333      FlexWrap::Wrap => Some(BoxLines::Multiple),
334      _ => None,
335    }
336  }
337}
338
339type BoxOrdinalGroup = CSSInteger;
340impl FromStandard<CSSInteger> for BoxOrdinalGroup {
341  fn from_standard(order: &CSSInteger) -> Option<BoxOrdinalGroup> {
342    Some(*order)
343  }
344}
345
346// Old flex (2012): https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/
347
348enum_property! {
349  /// A value for the legacy (prefixed) [flex-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-pack) property.
350  /// Equivalent to the `justify-content` property in the standard syntax.
351  pub enum FlexPack {
352    /// Items are justified to the start.
353    Start,
354    /// Items are justified to the end.
355    End,
356    /// Items are centered.
357    Center,
358    /// Items are justified to the start and end.
359    Justify,
360    /// Items are distributed evenly, with half size spaces on either end.
361    Distribute,
362  }
363}
364
365impl FromStandard<JustifyContent> for FlexPack {
366  fn from_standard(justify: &JustifyContent) -> Option<FlexPack> {
367    match justify {
368      JustifyContent::ContentDistribution(cd) => match cd {
369        ContentDistribution::SpaceBetween => Some(FlexPack::Justify),
370        ContentDistribution::SpaceAround => Some(FlexPack::Distribute),
371        _ => None,
372      },
373      JustifyContent::ContentPosition(None, pos) => match pos {
374        ContentPosition::Start | ContentPosition::FlexStart => Some(FlexPack::Start),
375        ContentPosition::End | ContentPosition::FlexEnd => Some(FlexPack::End),
376        ContentPosition::Center => Some(FlexPack::Center),
377      },
378      _ => None,
379    }
380  }
381}
382
383/// A value for the legacy (prefixed) [flex-align](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-align) property.
384pub type FlexAlign = BoxAlign;
385
386enum_property! {
387  /// A value for the legacy (prefixed) [flex-item-align](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-align) property.
388  /// Equivalent to the `align-self` property in the standard syntax.
389  pub enum FlexItemAlign {
390    /// Equivalent to the value of `flex-align`.
391    Auto,
392    /// The item is aligned to the start.
393    Start,
394    /// The item is aligned to the end.
395    End,
396    /// The item is centered.
397    Center,
398    /// The item is aligned to the baseline.
399    Baseline,
400    /// The item is stretched.
401    Stretch,
402  }
403}
404
405impl FromStandard<AlignSelf> for FlexItemAlign {
406  fn from_standard(justify: &AlignSelf) -> Option<FlexItemAlign> {
407    match justify {
408      AlignSelf::Auto => Some(FlexItemAlign::Auto),
409      AlignSelf::Stretch => Some(FlexItemAlign::Stretch),
410      AlignSelf::SelfPosition(None, pos) => match pos {
411        SelfPosition::Start | SelfPosition::FlexStart => Some(FlexItemAlign::Start),
412        SelfPosition::End | SelfPosition::FlexEnd => Some(FlexItemAlign::End),
413        SelfPosition::Center => Some(FlexItemAlign::Center),
414        _ => None,
415      },
416      _ => None,
417    }
418  }
419}
420
421enum_property! {
422  /// A value for the legacy (prefixed) [flex-line-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-line-pack) property.
423  /// Equivalent to the `align-content` property in the standard syntax.
424  pub enum FlexLinePack {
425    /// Content is aligned to the start.
426    Start,
427    /// Content is aligned to the end.
428    End,
429    /// Content is centered.
430    Center,
431    /// Content is justified.
432    Justify,
433    /// Content is distributed evenly, with half size spaces on either end.
434    Distribute,
435    /// Content is stretched.
436    Stretch,
437  }
438}
439
440impl FromStandard<AlignContent> for FlexLinePack {
441  fn from_standard(justify: &AlignContent) -> Option<FlexLinePack> {
442    match justify {
443      AlignContent::ContentDistribution(cd) => match cd {
444        ContentDistribution::SpaceBetween => Some(FlexLinePack::Justify),
445        ContentDistribution::SpaceAround => Some(FlexLinePack::Distribute),
446        ContentDistribution::Stretch => Some(FlexLinePack::Stretch),
447        _ => None,
448      },
449      AlignContent::ContentPosition(None, pos) => match pos {
450        ContentPosition::Start | ContentPosition::FlexStart => Some(FlexLinePack::Start),
451        ContentPosition::End | ContentPosition::FlexEnd => Some(FlexLinePack::End),
452        ContentPosition::Center => Some(FlexLinePack::Center),
453      },
454      _ => None,
455    }
456  }
457}
458
459#[derive(Default, Debug)]
460pub(crate) struct FlexHandler {
461  targets: Option<Browsers>,
462  direction: Option<(FlexDirection, VendorPrefix)>,
463  box_orient: Option<(BoxOrient, VendorPrefix)>,
464  box_direction: Option<(BoxDirection, VendorPrefix)>,
465  wrap: Option<(FlexWrap, VendorPrefix)>,
466  box_lines: Option<(BoxLines, VendorPrefix)>,
467  grow: Option<(CSSNumber, VendorPrefix)>,
468  box_flex: Option<(CSSNumber, VendorPrefix)>,
469  flex_positive: Option<(CSSNumber, VendorPrefix)>,
470  shrink: Option<(CSSNumber, VendorPrefix)>,
471  flex_negative: Option<(CSSNumber, VendorPrefix)>,
472  basis: Option<(LengthPercentageOrAuto, VendorPrefix)>,
473  preferred_size: Option<(LengthPercentageOrAuto, VendorPrefix)>,
474  order: Option<(CSSInteger, VendorPrefix)>,
475  box_ordinal_group: Option<(BoxOrdinalGroup, VendorPrefix)>,
476  flex_order: Option<(CSSInteger, VendorPrefix)>,
477  has_any: bool,
478}
479
480impl FlexHandler {
481  pub fn new(targets: Option<Browsers>) -> FlexHandler {
482    FlexHandler {
483      targets,
484      ..FlexHandler::default()
485    }
486  }
487}
488
489impl<'i> PropertyHandler<'i> for FlexHandler {
490  fn handle_property(
491    &mut self,
492    property: &Property<'i>,
493    dest: &mut DeclarationList<'i>,
494    _: &mut PropertyHandlerContext<'i, '_>,
495  ) -> bool {
496    use Property::*;
497
498    macro_rules! maybe_flush {
499      ($prop: ident, $val: expr, $vp: ident) => {{
500        // If two vendor prefixes for the same property have different
501        // values, we need to flush what we have immediately to preserve order.
502        if let Some((val, prefixes)) = &self.$prop {
503          if val != $val && !prefixes.contains(*$vp) {
504            self.flush(dest);
505          }
506        }
507      }};
508    }
509
510    macro_rules! property {
511      ($prop: ident, $val: expr, $vp: ident) => {{
512        maybe_flush!($prop, $val, $vp);
513
514        // Otherwise, update the value and add the prefix.
515        if let Some((val, prefixes)) = &mut self.$prop {
516          *val = $val.clone();
517          *prefixes |= *$vp;
518        } else {
519          self.$prop = Some(($val.clone(), *$vp));
520          self.has_any = true;
521        }
522      }};
523    }
524
525    match property {
526      FlexDirection(val, vp) => {
527        if self.targets.is_some() {
528          self.box_direction = None;
529          self.box_orient = None;
530        }
531        property!(direction, val, vp);
532      }
533      BoxOrient(val, vp) => property!(box_orient, val, vp),
534      BoxDirection(val, vp) => property!(box_direction, val, vp),
535      FlexWrap(val, vp) => {
536        if self.targets.is_some() {
537          self.box_lines = None;
538        }
539        property!(wrap, val, vp);
540      }
541      BoxLines(val, vp) => property!(box_lines, val, vp),
542      FlexFlow(val, vp) => {
543        if self.targets.is_some() {
544          self.box_direction = None;
545          self.box_orient = None;
546        }
547        property!(direction, &val.direction, vp);
548        property!(wrap, &val.wrap, vp);
549      }
550      FlexGrow(val, vp) => {
551        if self.targets.is_some() {
552          self.box_flex = None;
553          self.flex_positive = None;
554        }
555        property!(grow, val, vp);
556      }
557      BoxFlex(val, vp) => property!(box_flex, val, vp),
558      FlexPositive(val, vp) => property!(flex_positive, val, vp),
559      FlexShrink(val, vp) => {
560        if self.targets.is_some() {
561          self.flex_negative = None;
562        }
563        property!(shrink, val, vp);
564      }
565      FlexNegative(val, vp) => property!(flex_negative, val, vp),
566      FlexBasis(val, vp) => {
567        if self.targets.is_some() {
568          self.preferred_size = None;
569        }
570        property!(basis, val, vp);
571      }
572      FlexPreferredSize(val, vp) => property!(preferred_size, val, vp),
573      Flex(val, vp) => {
574        if self.targets.is_some() {
575          self.box_flex = None;
576          self.flex_positive = None;
577          self.flex_negative = None;
578          self.preferred_size = None;
579        }
580        maybe_flush!(grow, &val.grow, vp);
581        maybe_flush!(shrink, &val.shrink, vp);
582        maybe_flush!(basis, &val.basis, vp);
583        property!(grow, &val.grow, vp);
584        property!(shrink, &val.shrink, vp);
585        property!(basis, &val.basis, vp);
586      }
587      Order(val, vp) => {
588        if self.targets.is_some() {
589          self.box_ordinal_group = None;
590          self.flex_order = None;
591        }
592        property!(order, val, vp);
593      }
594      BoxOrdinalGroup(val, vp) => property!(box_ordinal_group, val, vp),
595      FlexOrder(val, vp) => property!(flex_order, val, vp),
596      Unparsed(val) if is_flex_property(&val.property_id) => {
597        self.flush(dest);
598        dest.push(property.clone()) // TODO: prefix?
599      }
600      _ => return false,
601    }
602
603    true
604  }
605
606  fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) {
607    self.flush(dest);
608  }
609}
610
611impl FlexHandler {
612  fn flush(&mut self, dest: &mut DeclarationList) {
613    if !self.has_any {
614      return;
615    }
616
617    self.has_any = false;
618
619    let mut direction = std::mem::take(&mut self.direction);
620    let mut wrap = std::mem::take(&mut self.wrap);
621    let mut grow = std::mem::take(&mut self.grow);
622    let mut shrink = std::mem::take(&mut self.shrink);
623    let mut basis = std::mem::take(&mut self.basis);
624    let box_orient = std::mem::take(&mut self.box_orient);
625    let box_direction = std::mem::take(&mut self.box_direction);
626    let box_flex = std::mem::take(&mut self.box_flex);
627    let box_ordinal_group = std::mem::take(&mut self.box_ordinal_group);
628    let box_lines = std::mem::take(&mut self.box_lines);
629    let flex_positive = std::mem::take(&mut self.flex_positive);
630    let flex_negative = std::mem::take(&mut self.flex_negative);
631    let preferred_size = std::mem::take(&mut self.preferred_size);
632    let order = std::mem::take(&mut self.order);
633    let flex_order = std::mem::take(&mut self.flex_order);
634
635    macro_rules! single_property {
636      ($prop: ident, $key: ident $(, 2012: $prop_2012: ident )? $(, 2009: $prop_2009: ident )?) => {
637        if let Some((val, prefix)) = $key {
638          if !prefix.is_empty() {
639            let mut prefix = prefix;
640            if prefix.contains(VendorPrefix::None) {
641              if let Some(targets) = self.targets {
642                prefix = Feature::$prop.prefixes_for(targets);
643
644                // 2009 spec, implemented by webkit and firefox.
645                $(
646                  let mut prefixes_2009 = VendorPrefix::empty();
647                  if is_flex_2009(targets) {
648                    prefixes_2009 |= VendorPrefix::WebKit;
649                  }
650                  if prefix.contains(VendorPrefix::Moz) {
651                    prefixes_2009 |= VendorPrefix::Moz;
652                  }
653                  if !prefixes_2009.is_empty() {
654                    if let Some(v) = $prop_2009::from_standard(&val) {
655                      dest.push(Property::$prop_2009(v, prefixes_2009));
656                    }
657                  }
658                )?
659
660                $(
661                  let mut ms = true;
662                  if prefix.contains(VendorPrefix::Ms) {
663                    dest.push(Property::$prop_2012(val.clone(), VendorPrefix::Ms));
664                    ms = false;
665                  }
666                  if !ms {
667                    prefix.remove(VendorPrefix::Ms);
668                  }
669                )?
670
671                // Firefox only implemented the 2009 spec prefixed.
672                prefix.remove(VendorPrefix::Moz);
673              }
674            }
675            dest.push(Property::$prop(val, prefix))
676          }
677        }
678      };
679    }
680
681    macro_rules! legacy_property {
682      ($prop: ident, $key: expr) => {
683        if let Some((val, prefix)) = $key {
684          if !prefix.is_empty() {
685            dest.push(Property::$prop(val, prefix))
686          }
687        }
688      };
689    }
690
691    // Legacy properties. These are only set if the final standard properties were unset.
692    legacy_property!(BoxOrient, box_orient);
693    legacy_property!(BoxDirection, box_direction);
694    legacy_property!(BoxOrdinalGroup, box_ordinal_group);
695    legacy_property!(BoxFlex, box_flex);
696    legacy_property!(BoxLines, box_lines);
697    legacy_property!(FlexPositive, flex_positive);
698    legacy_property!(FlexNegative, flex_negative);
699    legacy_property!(FlexPreferredSize, preferred_size.clone());
700    legacy_property!(FlexOrder, flex_order.clone());
701
702    if let Some((direction, _)) = direction {
703      if let Some(targets) = self.targets {
704        let prefixes = Feature::FlexDirection.prefixes_for(targets);
705        let mut prefixes_2009 = VendorPrefix::empty();
706        if is_flex_2009(targets) {
707          prefixes_2009 |= VendorPrefix::WebKit;
708        }
709        if prefixes.contains(VendorPrefix::Moz) {
710          prefixes_2009 |= VendorPrefix::Moz;
711        }
712        if !prefixes_2009.is_empty() {
713          let (orient, dir) = direction.to_2009();
714          dest.push(Property::BoxOrient(orient, prefixes_2009));
715          dest.push(Property::BoxDirection(dir, prefixes_2009));
716        }
717      }
718    }
719
720    if let (Some((direction, dir_prefix)), Some((wrap, wrap_prefix))) = (&mut direction, &mut wrap) {
721      let intersection = *dir_prefix & *wrap_prefix;
722      if !intersection.is_empty() {
723        let mut prefix = intersection;
724        if prefix.contains(VendorPrefix::None) {
725          if let Some(targets) = self.targets {
726            prefix = Feature::FlexFlow.prefixes_for(targets);
727            // Firefox only implemented the 2009 spec prefixed.
728            prefix.remove(VendorPrefix::Moz);
729          }
730        }
731        dest.push(Property::FlexFlow(
732          FlexFlow {
733            direction: *direction,
734            wrap: *wrap,
735          },
736          prefix,
737        ));
738        dir_prefix.remove(intersection);
739        wrap_prefix.remove(intersection);
740      }
741    }
742
743    single_property!(FlexDirection, direction);
744    single_property!(FlexWrap, wrap, 2009: BoxLines);
745
746    if let Some(targets) = self.targets {
747      if let Some((grow, _)) = grow {
748        let prefixes = Feature::FlexGrow.prefixes_for(targets);
749        let mut prefixes_2009 = VendorPrefix::empty();
750        if is_flex_2009(targets) {
751          prefixes_2009 |= VendorPrefix::WebKit;
752        }
753        if prefixes.contains(VendorPrefix::Moz) {
754          prefixes_2009 |= VendorPrefix::Moz;
755        }
756        if !prefixes_2009.is_empty() {
757          dest.push(Property::BoxFlex(grow, prefixes_2009));
758        }
759      }
760    }
761
762    if let (Some((grow, grow_prefix)), Some((shrink, shrink_prefix)), Some((basis, basis_prefix))) =
763      (&mut grow, &mut shrink, &mut basis)
764    {
765      let intersection = *grow_prefix & *shrink_prefix & *basis_prefix;
766      if !intersection.is_empty() {
767        let mut prefix = intersection;
768        if prefix.contains(VendorPrefix::None) {
769          if let Some(targets) = self.targets {
770            prefix = Feature::Flex.prefixes_for(targets);
771            // Firefox only implemented the 2009 spec prefixed.
772            prefix.remove(VendorPrefix::Moz);
773          }
774        }
775        dest.push(Property::Flex(
776          Flex {
777            grow: *grow,
778            shrink: *shrink,
779            basis: basis.clone(),
780          },
781          prefix,
782        ));
783        grow_prefix.remove(intersection);
784        shrink_prefix.remove(intersection);
785        basis_prefix.remove(intersection);
786      }
787    }
788
789    single_property!(FlexGrow, grow, 2012: FlexPositive);
790    single_property!(FlexShrink, shrink, 2012: FlexNegative);
791    single_property!(FlexBasis, basis, 2012: FlexPreferredSize);
792    single_property!(Order, order, 2012: FlexOrder, 2009: BoxOrdinalGroup);
793  }
794}
795
796#[inline]
797fn is_flex_property(property_id: &PropertyId) -> bool {
798  match property_id {
799    PropertyId::FlexDirection(_)
800    | PropertyId::BoxOrient(_)
801    | PropertyId::BoxDirection(_)
802    | PropertyId::FlexWrap(_)
803    | PropertyId::BoxLines(_)
804    | PropertyId::FlexFlow(_)
805    | PropertyId::FlexGrow(_)
806    | PropertyId::BoxFlex(_)
807    | PropertyId::FlexPositive(_)
808    | PropertyId::FlexShrink(_)
809    | PropertyId::FlexNegative(_)
810    | PropertyId::FlexBasis(_)
811    | PropertyId::FlexPreferredSize(_)
812    | PropertyId::Flex(_)
813    | PropertyId::Order(_)
814    | PropertyId::BoxOrdinalGroup(_)
815    | PropertyId::FlexOrder(_) => true,
816    _ => false,
817  }
818}