//! CSS properties related to box alignment. use super::flex::{BoxAlign, BoxPack, FlexAlign, FlexItemAlign, FlexLinePack, FlexPack}; use super::{Property, PropertyId}; use crate::compat; use crate::context::PropertyHandlerContext; use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::macros::*; use crate::prefixes::{is_flex_2009, Feature}; use crate::printer::Printer; use crate::traits::{FromStandard, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::length::LengthPercentage; use crate::vendor_prefix::VendorPrefix; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; #[cfg(feature = "serde")] use crate::serialization::ValueWrapper; /// A [``](https://www.w3.org/TR/css-align-3/#typedef-baseline-position) value, /// as used in the alignment properties. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum BaselinePosition { /// The first baseline. First, /// The last baseline. Last, } impl<'i> Parse<'i> for BaselinePosition { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let location = input.current_source_location(); let ident = input.expect_ident()?; match_ignore_ascii_case! { &*ident, "baseline" => Ok(BaselinePosition::First), "first" => { input.expect_ident_matching("baseline")?; Ok(BaselinePosition::First) }, "last" => { input.expect_ident_matching("baseline")?; Ok(BaselinePosition::Last) }, _ => Err(location.new_unexpected_token_error( cssparser::Token::Ident(ident.clone()) )) } } } impl ToCss for BaselinePosition { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { BaselinePosition::First => dest.write_str("baseline"), BaselinePosition::Last => dest.write_str("last baseline"), } } } enum_property! { /// A [``](https://www.w3.org/TR/css-align-3/#typedef-content-distribution) value. pub enum ContentDistribution { /// Items are spaced evenly, with the first and last items against the edge of the container. SpaceBetween, /// Items are spaced evenly, with half-size spaces at the start and end. SpaceAround, /// Items are spaced evenly, with full-size spaces at the start and end. SpaceEvenly, /// Items are stretched evenly to fill free space. Stretch, } } enum_property! { /// An [``](https://www.w3.org/TR/css-align-3/#typedef-overflow-position) value. pub enum OverflowPosition { /// If the size of the alignment subject overflows the alignment container, /// the alignment subject is instead aligned as if the alignment mode were start. Safe, /// Regardless of the relative sizes of the alignment subject and alignment /// container, the given alignment value is honored. Unsafe, } } enum_property! { /// A [``](https://www.w3.org/TR/css-align-3/#typedef-content-position) value. pub enum ContentPosition { /// Content is centered within the container. Center, /// Content is aligned to the start of the container. Start, /// Content is aligned to the end of the container. End, /// Same as `start` when within a flexbox container. FlexStart, /// Same as `end` when within a flexbox container. FlexEnd, } } /// A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property. #[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum AlignContent { /// Default alignment. Normal, /// A baseline position. #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::"))] BaselinePosition(BaselinePosition), /// A content distribution keyword. #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::"))] ContentDistribution(ContentDistribution), /// A content position keyword. ContentPosition { /// An overflow alignment mode. overflow: Option, /// A content position keyword. value: ContentPosition, }, } /// A value for the [justify-content](https://www.w3.org/TR/css-align-3/#propdef-justify-content) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum JustifyContent { /// Default justification. Normal, /// A content distribution keyword. #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::"))] ContentDistribution(ContentDistribution), /// A content position keyword. ContentPosition { /// A content position keyword. value: ContentPosition, /// An overflow alignment mode. overflow: Option, }, /// Justify to the left. Left { /// An overflow alignment mode. overflow: Option, }, /// Justify to the right. Right { /// An overflow alignment mode. overflow: Option, }, } impl<'i> Parse<'i> for JustifyContent { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { return Ok(JustifyContent::Normal); } if let Ok(val) = input.try_parse(ContentDistribution::parse) { return Ok(JustifyContent::ContentDistribution(val)); } let overflow = input.try_parse(OverflowPosition::parse).ok(); if let Ok(content_position) = input.try_parse(ContentPosition::parse) { return Ok(JustifyContent::ContentPosition { overflow, value: content_position, }); } let location = input.current_source_location(); let ident = input.expect_ident()?; match_ignore_ascii_case! { &*ident, "left" => Ok(JustifyContent::Left { overflow }), "right" => Ok(JustifyContent::Right { overflow }), _ => Err(location.new_unexpected_token_error( cssparser::Token::Ident(ident.clone()) )) } } } impl ToCss for JustifyContent { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { JustifyContent::Normal => dest.write_str("normal"), JustifyContent::ContentDistribution(value) => value.to_css(dest), JustifyContent::ContentPosition { overflow, value } => { if let Some(overflow) = overflow { overflow.to_css(dest)?; dest.write_str(" ")?; } value.to_css(dest) } JustifyContent::Left { overflow } => { if let Some(overflow) = overflow { overflow.to_css(dest)?; dest.write_str(" ")?; } dest.write_str("left") } JustifyContent::Right { overflow } => { if let Some(overflow) = overflow { overflow.to_css(dest)?; dest.write_str(" ")?; } dest.write_str("right") } } } } define_shorthand! { /// A value for the [place-content](https://www.w3.org/TR/css-align-3/#place-content) shorthand property. pub struct PlaceContent { /// The content alignment. align: AlignContent(AlignContent, VendorPrefix), /// The content justification. justify: JustifyContent(JustifyContent, VendorPrefix), } } impl<'i> Parse<'i> for PlaceContent { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let align = AlignContent::parse(input)?; let justify = match input.try_parse(JustifyContent::parse) { Ok(j) => j, Err(_) => { // The second value is assigned to justify-content; if omitted, it is copied // from the first value, unless that value is a in which // case it is defaulted to start. match align { AlignContent::BaselinePosition(_) => JustifyContent::ContentPosition { overflow: None, value: ContentPosition::Start, }, AlignContent::Normal => JustifyContent::Normal, AlignContent::ContentDistribution(value) => JustifyContent::ContentDistribution(value.clone()), AlignContent::ContentPosition { overflow, value } => JustifyContent::ContentPosition { overflow: overflow.clone(), value: value.clone(), }, } } }; Ok(PlaceContent { align, justify }) } } impl ToCss for PlaceContent { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { self.align.to_css(dest)?; let is_equal = match self.justify { JustifyContent::Normal if self.align == AlignContent::Normal => true, JustifyContent::ContentDistribution(d) if matches!(self.align, AlignContent::ContentDistribution(d2) if d == d2) => { true } JustifyContent::ContentPosition { overflow: o, value: c } if matches!(self.align, AlignContent::ContentPosition { overflow: o2, value: c2 } if o == o2 && c == c2) => { true } _ => false, }; if !is_equal { dest.write_str(" ")?; self.justify.to_css(dest)?; } Ok(()) } } enum_property! { /// A [``](https://www.w3.org/TR/css-align-3/#typedef-self-position) value. pub enum SelfPosition { /// Item is centered within the container. Center, /// Item is aligned to the start of the container. Start, /// Item is aligned to the end of the container. End, /// Item is aligned to the edge of the container corresponding to the start side of the item. SelfStart, /// Item is aligned to the edge of the container corresponding to the end side of the item. SelfEnd, /// Item is aligned to the start of the container, within flexbox layouts. FlexStart, /// Item is aligned to the end of the container, within flexbox layouts. FlexEnd, } } /// A value for the [align-self](https://www.w3.org/TR/css-align-3/#align-self-property) property. #[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum AlignSelf { /// Automatic alignment. Auto, /// Default alignment. Normal, /// Item is stretched. Stretch, /// A baseline position keyword. #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::"))] BaselinePosition(BaselinePosition), /// A self position keyword. SelfPosition { /// An overflow alignment mode. overflow: Option, /// A self position keyword. value: SelfPosition, }, } /// A value for the [justify-self](https://www.w3.org/TR/css-align-3/#justify-self-property) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum JustifySelf { /// Automatic justification. Auto, /// Default justification. Normal, /// Item is stretched. Stretch, /// A baseline position keyword. #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::"))] BaselinePosition(BaselinePosition), /// A self position keyword. SelfPosition { /// A self position keyword. value: SelfPosition, /// An overflow alignment mode. overflow: Option, }, /// Item is justified to the left. Left { /// An overflow alignment mode. overflow: Option, }, /// Item is justified to the right. Right { /// An overflow alignment mode. overflow: Option, }, } impl<'i> Parse<'i> for JustifySelf { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { return Ok(JustifySelf::Auto); } if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { return Ok(JustifySelf::Normal); } if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() { return Ok(JustifySelf::Stretch); } if let Ok(val) = input.try_parse(BaselinePosition::parse) { return Ok(JustifySelf::BaselinePosition(val)); } let overflow = input.try_parse(OverflowPosition::parse).ok(); if let Ok(value) = input.try_parse(SelfPosition::parse) { return Ok(JustifySelf::SelfPosition { overflow, value }); } let location = input.current_source_location(); let ident = input.expect_ident()?; match_ignore_ascii_case! { &*ident, "left" => Ok(JustifySelf::Left { overflow }), "right" => Ok(JustifySelf::Right { overflow }), _ => Err(location.new_unexpected_token_error( cssparser::Token::Ident(ident.clone()) )) } } } impl ToCss for JustifySelf { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { JustifySelf::Auto => dest.write_str("auto"), JustifySelf::Normal => dest.write_str("normal"), JustifySelf::Stretch => dest.write_str("stretch"), JustifySelf::BaselinePosition(val) => val.to_css(dest), JustifySelf::SelfPosition { overflow, value } => { if let Some(overflow) = overflow { overflow.to_css(dest)?; dest.write_str(" ")?; } value.to_css(dest) } JustifySelf::Left { overflow } => { if let Some(overflow) = overflow { overflow.to_css(dest)?; dest.write_str(" ")?; } dest.write_str("left") } JustifySelf::Right { overflow } => { if let Some(overflow) = overflow { overflow.to_css(dest)?; dest.write_str(" ")?; } dest.write_str("right") } } } } define_shorthand! { /// A value for the [place-self](https://www.w3.org/TR/css-align-3/#place-self-property) shorthand property. pub struct PlaceSelf { /// The item alignment. align: AlignSelf(AlignSelf, VendorPrefix), /// The item justification. justify: JustifySelf(JustifySelf), } } impl<'i> Parse<'i> for PlaceSelf { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let align = AlignSelf::parse(input)?; let justify = match input.try_parse(JustifySelf::parse) { Ok(j) => j, Err(_) => { // The second value is assigned to justify-self; if omitted, it is copied from the first value. match &align { AlignSelf::Auto => JustifySelf::Auto, AlignSelf::Normal => JustifySelf::Normal, AlignSelf::Stretch => JustifySelf::Stretch, AlignSelf::BaselinePosition(p) => JustifySelf::BaselinePosition(p.clone()), AlignSelf::SelfPosition { overflow, value } => JustifySelf::SelfPosition { overflow: overflow.clone(), value: value.clone(), }, } } }; Ok(PlaceSelf { align, justify }) } } impl ToCss for PlaceSelf { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { self.align.to_css(dest)?; let is_equal = match &self.justify { JustifySelf::Auto => true, JustifySelf::Normal => self.align == AlignSelf::Normal, JustifySelf::Stretch => self.align == AlignSelf::Normal, JustifySelf::BaselinePosition(p) if matches!(&self.align, AlignSelf::BaselinePosition(p2) if p == p2) => { true } JustifySelf::SelfPosition { overflow: o, value: c } if matches!(&self.align, AlignSelf::SelfPosition { overflow: o2, value: c2 } if o == o2 && c == c2) => { true } _ => false, }; if !is_equal { dest.write_str(" ")?; self.justify.to_css(dest)?; } Ok(()) } } /// A value for the [align-items](https://www.w3.org/TR/css-align-3/#align-items-property) property. #[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum AlignItems { /// Default alignment. Normal, /// Items are stretched. Stretch, /// A baseline position keyword. #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::"))] BaselinePosition(BaselinePosition), /// A self position keyword. SelfPosition { /// An overflow alignment mode. overflow: Option, /// A self position keyword. value: SelfPosition, }, } /// A legacy justification keyword, as used in the `justify-items` property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum LegacyJustify { /// Left justify. Left, /// Right justify. Right, /// Centered. Center, } impl<'i> Parse<'i> for LegacyJustify { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let location = input.current_source_location(); let ident = input.expect_ident()?; match_ignore_ascii_case! { &*ident, "legacy" => { let location = input.current_source_location(); let ident = input.expect_ident()?; match_ignore_ascii_case! { &*ident, "left" => Ok(LegacyJustify::Left), "right" => Ok(LegacyJustify::Right), "center" => Ok(LegacyJustify::Center), _ => Err(location.new_unexpected_token_error( cssparser::Token::Ident(ident.clone()) )) } }, "left" => { input.expect_ident_matching("legacy")?; Ok(LegacyJustify::Left) }, "right" => { input.expect_ident_matching("legacy")?; Ok(LegacyJustify::Right) }, "center" => { input.expect_ident_matching("legacy")?; Ok(LegacyJustify::Center) }, _ => Err(location.new_unexpected_token_error( cssparser::Token::Ident(ident.clone()) )) } } } impl ToCss for LegacyJustify { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { dest.write_str("legacy ")?; match self { LegacyJustify::Left => dest.write_str("left"), LegacyJustify::Right => dest.write_str("right"), LegacyJustify::Center => dest.write_str("center"), } } } /// A value for the [justify-items](https://www.w3.org/TR/css-align-3/#justify-items-property) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum JustifyItems { /// Default justification. Normal, /// Items are stretched. Stretch, /// A baseline position keyword. #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::"))] BaselinePosition(BaselinePosition), /// A self position keyword, with optional overflow position. SelfPosition { /// A self position keyword. value: SelfPosition, /// An overflow alignment mode. overflow: Option, }, /// Items are justified to the left, with an optional overflow position. Left { /// An overflow alignment mode. overflow: Option, }, /// Items are justified to the right, with an optional overflow position. Right { /// An overflow alignment mode. overflow: Option, }, /// A legacy justification keyword. #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::"))] Legacy(LegacyJustify), } impl<'i> Parse<'i> for JustifyItems { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { return Ok(JustifyItems::Normal); } if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() { return Ok(JustifyItems::Stretch); } if let Ok(val) = input.try_parse(BaselinePosition::parse) { return Ok(JustifyItems::BaselinePosition(val)); } if let Ok(val) = input.try_parse(LegacyJustify::parse) { return Ok(JustifyItems::Legacy(val)); } let overflow = input.try_parse(OverflowPosition::parse).ok(); if let Ok(value) = input.try_parse(SelfPosition::parse) { return Ok(JustifyItems::SelfPosition { overflow, value }); } let location = input.current_source_location(); let ident = input.expect_ident()?; match_ignore_ascii_case! { &*ident, "left" => Ok(JustifyItems::Left { overflow }), "right" => Ok(JustifyItems::Right { overflow }), _ => Err(location.new_unexpected_token_error( cssparser::Token::Ident(ident.clone()) )) } } } impl ToCss for JustifyItems { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { JustifyItems::Normal => dest.write_str("normal"), JustifyItems::Stretch => dest.write_str("stretch"), JustifyItems::BaselinePosition(val) => val.to_css(dest), JustifyItems::Legacy(val) => val.to_css(dest), JustifyItems::SelfPosition { overflow, value } => { if let Some(overflow) = overflow { overflow.to_css(dest)?; dest.write_str(" ")?; } value.to_css(dest) } JustifyItems::Left { overflow } => { if let Some(overflow) = overflow { overflow.to_css(dest)?; dest.write_str(" ")?; } dest.write_str("left") } JustifyItems::Right { overflow } => { if let Some(overflow) = overflow { overflow.to_css(dest)?; dest.write_str(" ")?; } dest.write_str("right") } } } } define_shorthand! { /// A value for the [place-items](https://www.w3.org/TR/css-align-3/#place-items-property) shorthand property. pub struct PlaceItems { /// The item alignment. align: AlignItems(AlignItems, VendorPrefix), /// The item justification. justify: JustifyItems(JustifyItems), } } impl<'i> Parse<'i> for PlaceItems { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let align = AlignItems::parse(input)?; let justify = match input.try_parse(JustifyItems::parse) { Ok(j) => j, Err(_) => { // The second value is assigned to justify-items; if omitted, it is copied from the first value. match &align { AlignItems::Normal => JustifyItems::Normal, AlignItems::Stretch => JustifyItems::Stretch, AlignItems::BaselinePosition(p) => JustifyItems::BaselinePosition(p.clone()), AlignItems::SelfPosition { overflow, value } => JustifyItems::SelfPosition { overflow: overflow.clone(), value: value.clone(), }, } } }; Ok(PlaceItems { align, justify }) } } impl ToCss for PlaceItems { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { self.align.to_css(dest)?; let is_equal = match &self.justify { JustifyItems::Normal => self.align == AlignItems::Normal, JustifyItems::Stretch => self.align == AlignItems::Normal, JustifyItems::BaselinePosition(p) if matches!(&self.align, AlignItems::BaselinePosition(p2) if p == p2) => { true } JustifyItems::SelfPosition { overflow: o, value: c } if matches!(&self.align, AlignItems::SelfPosition { overflow: o2, value: c2 } if o == o2 && c == c2) => { true } _ => false, }; if !is_equal { dest.write_str(" ")?; self.justify.to_css(dest)?; } Ok(()) } } /// A [gap](https://www.w3.org/TR/css-align-3/#column-row-gap) value, as used in the /// `column-gap` and `row-gap` properties. #[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", content = "value", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum GapValue { /// Equal to `1em` for multi-column containers, and zero otherwise. Normal, /// An explicit length. LengthPercentage(LengthPercentage), } define_shorthand! { /// A value for the [gap](https://www.w3.org/TR/css-align-3/#gap-shorthand) shorthand property. pub struct Gap { /// The row gap. row: RowGap(GapValue), /// The column gap. column: ColumnGap(GapValue), } } impl<'i> Parse<'i> for Gap { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let row = GapValue::parse(input)?; let column = input.try_parse(GapValue::parse).unwrap_or(row.clone()); Ok(Gap { row, column }) } } impl ToCss for Gap { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { self.row.to_css(dest)?; if self.column != self.row { dest.write_str(" ")?; self.column.to_css(dest)?; } Ok(()) } } #[derive(Default, Debug)] pub(crate) struct AlignHandler { align_content: Option<(AlignContent, VendorPrefix)>, flex_line_pack: Option<(FlexLinePack, VendorPrefix)>, justify_content: Option<(JustifyContent, VendorPrefix)>, box_pack: Option<(BoxPack, VendorPrefix)>, flex_pack: Option<(FlexPack, VendorPrefix)>, align_self: Option<(AlignSelf, VendorPrefix)>, flex_item_align: Option<(FlexItemAlign, VendorPrefix)>, justify_self: Option, align_items: Option<(AlignItems, VendorPrefix)>, box_align: Option<(BoxAlign, VendorPrefix)>, flex_align: Option<(FlexAlign, VendorPrefix)>, justify_items: Option, row_gap: Option, column_gap: Option, has_any: bool, } impl<'i> PropertyHandler<'i> for AlignHandler { fn handle_property( &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; macro_rules! maybe_flush { ($prop: ident, $val: expr, $vp: expr) => {{ // If two vendor prefixes for the same property have different // values, we need to flush what we have immediately to preserve order. if let Some((val, prefixes)) = &self.$prop { if val != $val && !prefixes.contains(*$vp) { self.flush(dest, context); } } }}; } macro_rules! property { ($prop: ident, $val: expr, $vp: expr) => {{ maybe_flush!($prop, $val, $vp); // Otherwise, update the value and add the prefix. if let Some((val, prefixes)) = &mut self.$prop { *val = $val.clone(); *prefixes |= *$vp; } else { self.$prop = Some(($val.clone(), *$vp)); self.has_any = true; } }}; } match property { AlignContent(val, vp) => { self.flex_line_pack = None; property!(align_content, val, vp); } FlexLinePack(val, vp) => property!(flex_line_pack, val, vp), JustifyContent(val, vp) => { self.box_pack = None; self.flex_pack = None; property!(justify_content, val, vp); } BoxPack(val, vp) => property!(box_pack, val, vp), FlexPack(val, vp) => property!(flex_pack, val, vp), PlaceContent(val) => { self.flex_line_pack = None; self.box_pack = None; self.flex_pack = None; maybe_flush!(align_content, &val.align, &VendorPrefix::None); maybe_flush!(justify_content, &val.justify, &VendorPrefix::None); property!(align_content, &val.align, &VendorPrefix::None); property!(justify_content, &val.justify, &VendorPrefix::None); } AlignSelf(val, vp) => { self.flex_item_align = None; property!(align_self, val, vp); } FlexItemAlign(val, vp) => property!(flex_item_align, val, vp), JustifySelf(val) => { self.justify_self = Some(val.clone()); self.has_any = true; } PlaceSelf(val) => { self.flex_item_align = None; property!(align_self, &val.align, &VendorPrefix::None); self.justify_self = Some(val.justify.clone()); } AlignItems(val, vp) => { self.box_align = None; self.flex_align = None; property!(align_items, val, vp); } BoxAlign(val, vp) => property!(box_align, val, vp), FlexAlign(val, vp) => property!(flex_align, val, vp), JustifyItems(val) => { self.justify_items = Some(val.clone()); self.has_any = true; } PlaceItems(val) => { self.box_align = None; self.flex_align = None; property!(align_items, &val.align, &VendorPrefix::None); self.justify_items = Some(val.justify.clone()); } RowGap(val) => { self.row_gap = Some(val.clone()); self.has_any = true; } ColumnGap(val) => { self.column_gap = Some(val.clone()); self.has_any = true; } Gap(val) => { self.row_gap = Some(val.row.clone()); self.column_gap = Some(val.column.clone()); self.has_any = true; } Unparsed(val) if is_align_property(&val.property_id) => { self.flush(dest, context); dest.push(property.clone()) // TODO: prefix? } _ => return false, } true } fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest, context); } } impl AlignHandler { fn flush<'i>(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return; } self.has_any = false; let mut align_content = std::mem::take(&mut self.align_content); let mut justify_content = std::mem::take(&mut self.justify_content); let mut align_self = std::mem::take(&mut self.align_self); let mut justify_self = std::mem::take(&mut self.justify_self); let mut align_items = std::mem::take(&mut self.align_items); let mut justify_items = std::mem::take(&mut self.justify_items); let row_gap = std::mem::take(&mut self.row_gap); let column_gap = std::mem::take(&mut self.column_gap); let box_align = std::mem::take(&mut self.box_align); let box_pack = std::mem::take(&mut self.box_pack); let flex_line_pack = std::mem::take(&mut self.flex_line_pack); let flex_pack = std::mem::take(&mut self.flex_pack); let flex_align = std::mem::take(&mut self.flex_align); let flex_item_align = std::mem::take(&mut self.flex_item_align); // Gets prefixes for standard properties. macro_rules! prefixes { ($prop: ident) => {{ let mut prefix = context.targets.prefixes(VendorPrefix::None, Feature::$prop); // Firefox only implemented the 2009 spec prefixed. // Microsoft only implemented the 2012 spec prefixed. prefix.remove(VendorPrefix::Moz | VendorPrefix::Ms); prefix }}; } macro_rules! standard_property { ($prop: ident, $key: ident) => { if let Some((val, prefix)) = $key { // If we have an unprefixed property, override necessary prefixes. let prefix = if prefix.contains(VendorPrefix::None) { prefixes!($prop) } else { prefix }; dest.push(Property::$prop(val, prefix)) } }; } macro_rules! legacy_property { ($prop: ident, $key: ident, $( $prop_2009: ident )?, $prop_2012: ident) => { if let Some((val, prefix)) = &$key { // If we have an unprefixed standard property, generate legacy prefixed versions. let mut prefix = context.targets.prefixes(*prefix, Feature::$prop); if prefix.contains(VendorPrefix::None) { $( // 2009 spec, implemented by webkit and firefox. if let Some(targets) = context.targets.browsers { let mut prefixes_2009 = VendorPrefix::empty(); if is_flex_2009(targets) { prefixes_2009 |= VendorPrefix::WebKit; } if prefix.contains(VendorPrefix::Moz) { prefixes_2009 |= VendorPrefix::Moz; } if !prefixes_2009.is_empty() { if let Some(v) = $prop_2009::from_standard(&val) { dest.push(Property::$prop_2009(v, prefixes_2009)); } } } )? } // 2012 spec, implemented by microsoft. if prefix.contains(VendorPrefix::Ms) { if let Some(v) = $prop_2012::from_standard(&val) { dest.push(Property::$prop_2012(v, VendorPrefix::Ms)); } } // Remove Firefox and IE from standard prefixes. prefix.remove(VendorPrefix::Moz | VendorPrefix::Ms); } }; } macro_rules! prefixed_property { ($prop: ident, $key: expr) => { if let Some((val, prefix)) = $key { dest.push(Property::$prop(val, prefix)) } }; } macro_rules! unprefixed_property { ($prop: ident, $key: expr) => { if let Some(val) = $key { dest.push(Property::$prop(val)) } }; } macro_rules! shorthand { ($prop: ident, $align_prop: ident, $align: ident, $justify: ident $(, $justify_prop: ident )?) => { if let (Some((align, align_prefix)), Some(justify)) = (&mut $align, &mut $justify) { let intersection = *align_prefix $( & { // Hack for conditional compilation. Have to use a variable. #[allow(non_snake_case)] let $justify_prop = justify.1; $justify_prop })?; // Only use shorthand if unprefixed. if intersection.contains(VendorPrefix::None) { // Add prefixed longhands if needed. *align_prefix = prefixes!($align_prop); align_prefix.remove(VendorPrefix::None); if !align_prefix.is_empty() { dest.push(Property::$align_prop(align.clone(), *align_prefix)) } $( let (justify, justify_prefix) = justify; *justify_prefix = prefixes!($justify_prop); justify_prefix.remove(VendorPrefix::None); if !justify_prefix.is_empty() { dest.push(Property::$justify_prop(justify.clone(), *justify_prefix)) } )? // Add shorthand. dest.push(Property::$prop($prop { align: align.clone(), justify: justify.clone() })); $align = None; $justify = None; } } }; } // 2009 properties prefixed_property!(BoxAlign, box_align); prefixed_property!(BoxPack, box_pack); // 2012 properties prefixed_property!(FlexPack, flex_pack); prefixed_property!(FlexAlign, flex_align); prefixed_property!(FlexItemAlign, flex_item_align); prefixed_property!(FlexLinePack, flex_line_pack); legacy_property!(AlignContent, align_content, , FlexLinePack); legacy_property!(JustifyContent, justify_content, BoxPack, FlexPack); if context.targets.is_compatible(compat::Feature::PlaceContent) { shorthand!( PlaceContent, AlignContent, align_content, justify_content, JustifyContent ); } standard_property!(AlignContent, align_content); standard_property!(JustifyContent, justify_content); legacy_property!(AlignSelf, align_self, , FlexItemAlign); if context.targets.is_compatible(compat::Feature::PlaceSelf) { shorthand!(PlaceSelf, AlignSelf, align_self, justify_self); } standard_property!(AlignSelf, align_self); unprefixed_property!(JustifySelf, justify_self); legacy_property!(AlignItems, align_items, BoxAlign, FlexAlign); if context.targets.is_compatible(compat::Feature::PlaceItems) { shorthand!(PlaceItems, AlignItems, align_items, justify_items); } standard_property!(AlignItems, align_items); unprefixed_property!(JustifyItems, justify_items); if row_gap.is_some() && column_gap.is_some() { dest.push(Property::Gap(Gap { row: row_gap.unwrap(), column: column_gap.unwrap(), })) } else { if let Some(gap) = row_gap { dest.push(Property::RowGap(gap)) } if let Some(gap) = column_gap { dest.push(Property::ColumnGap(gap)) } } } } #[inline] fn is_align_property(property_id: &PropertyId) -> bool { match property_id { PropertyId::AlignContent(_) | PropertyId::FlexLinePack(_) | PropertyId::JustifyContent(_) | PropertyId::BoxPack(_) | PropertyId::FlexPack(_) | PropertyId::PlaceContent | PropertyId::AlignSelf(_) | PropertyId::FlexItemAlign(_) | PropertyId::JustifySelf | PropertyId::PlaceSelf | PropertyId::AlignItems(_) | PropertyId::BoxAlign(_) | PropertyId::FlexAlign(_) | PropertyId::JustifyItems | PropertyId::PlaceItems | PropertyId::RowGap | PropertyId::ColumnGap | PropertyId::Gap => true, _ => false, } }