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