| //! 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 [`<baseline-position>`](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<Self, ParseError<'i, ParserError<'i>>> { |
| 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<W>(&self, dest: &mut Printer<W>) -> 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 [`<content-distribution>`](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 [`<overflow-position>`](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 [`<content-position>`](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(BaselinePosition), |
| /// A content distribution keyword. |
| #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<ContentDistribution>"))] |
| ContentDistribution(ContentDistribution), |
| /// A content position keyword. |
| ContentPosition { |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| /// 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(ContentDistribution), |
| /// A content position keyword. |
| ContentPosition { |
| /// A content position keyword. |
| value: ContentPosition, |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| }, |
| /// Justify to the left. |
| Left { |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| }, |
| /// Justify to the right. |
| Right { |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| }, |
| } |
| |
| impl<'i> Parse<'i> for JustifyContent { |
| fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> { |
| 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<W>(&self, dest: &mut Printer<W>) -> 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<Self, ParseError<'i, ParserError<'i>>> { |
| 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 <baseline-position> 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<W>(&self, dest: &mut Printer<W>) -> 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 [`<self-position>`](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(BaselinePosition), |
| /// A self position keyword. |
| SelfPosition { |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| /// 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(BaselinePosition), |
| /// A self position keyword. |
| SelfPosition { |
| /// A self position keyword. |
| value: SelfPosition, |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| }, |
| /// Item is justified to the left. |
| Left { |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| }, |
| /// Item is justified to the right. |
| Right { |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| }, |
| } |
| |
| impl<'i> Parse<'i> for JustifySelf { |
| fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> { |
| 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<W>(&self, dest: &mut Printer<W>) -> 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<Self, ParseError<'i, ParserError<'i>>> { |
| 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<W>(&self, dest: &mut Printer<W>) -> 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(BaselinePosition), |
| /// A self position keyword. |
| SelfPosition { |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| /// 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<Self, ParseError<'i, ParserError<'i>>> { |
| 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<W>(&self, dest: &mut Printer<W>) -> 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(BaselinePosition), |
| /// A self position keyword, with optional overflow position. |
| SelfPosition { |
| /// A self position keyword. |
| value: SelfPosition, |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| }, |
| /// Items are justified to the left, with an optional overflow position. |
| Left { |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| }, |
| /// Items are justified to the right, with an optional overflow position. |
| Right { |
| /// An overflow alignment mode. |
| overflow: Option<OverflowPosition>, |
| }, |
| /// A legacy justification keyword. |
| #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LegacyJustify>"))] |
| Legacy(LegacyJustify), |
| } |
| |
| impl<'i> Parse<'i> for JustifyItems { |
| fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> { |
| 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<W>(&self, dest: &mut Printer<W>) -> 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<Self, ParseError<'i, ParserError<'i>>> { |
| 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<W>(&self, dest: &mut Printer<W>) -> 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<Self, ParseError<'i, ParserError<'i>>> { |
| 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<W>(&self, dest: &mut Printer<W>) -> 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<JustifySelf>, |
| align_items: Option<(AlignItems, VendorPrefix)>, |
| box_align: Option<(BoxAlign, VendorPrefix)>, |
| flex_align: Option<(FlexAlign, VendorPrefix)>, |
| justify_items: Option<JustifyItems>, |
| row_gap: Option<GapValue>, |
| column_gap: Option<GapValue>, |
| 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, |
| } |
| } |