/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Values for CSS Box Alignment properties //! //! https://drafts.csswg.org/css-align/ use crate::parser::{Parse, ParserContext}; use cssparser::Parser; use std::fmt::{self, Write}; use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, ToCss}; bitflags! { /// Constants shared by multiple CSS Box Alignment properties #[derive(MallocSizeOf, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(C)] pub struct AlignFlags: u8 { // Enumeration stored in the lower 5 bits: /// {align,justify}-{content,items,self}: 'auto' const AUTO = 0; /// 'normal' const NORMAL = 1; /// 'start' const START = 2; /// 'end' const END = 3; /// 'flex-start' const FLEX_START = 4; /// 'flex-end' const FLEX_END = 5; /// 'center' const CENTER = 6; /// 'left' const LEFT = 7; /// 'right' const RIGHT = 8; /// 'baseline' const BASELINE = 9; /// 'last-baseline' const LAST_BASELINE = 10; /// 'stretch' const STRETCH = 11; /// 'self-start' const SELF_START = 12; /// 'self-end' const SELF_END = 13; /// 'space-between' const SPACE_BETWEEN = 14; /// 'space-around' const SPACE_AROUND = 15; /// 'space-evenly' const SPACE_EVENLY = 16; // Additional flags stored in the upper bits: /// 'legacy' (mutually exclusive w. SAFE & UNSAFE) const LEGACY = 1 << 5; /// 'safe' const SAFE = 1 << 6; /// 'unsafe' (mutually exclusive w. SAFE) const UNSAFE = 1 << 7; /// Mask for the additional flags above. const FLAG_BITS = 0b11100000; } } impl AlignFlags { /// Returns the enumeration value stored in the lower 5 bits. #[inline] fn value(&self) -> Self { *self & !AlignFlags::FLAG_BITS } } impl ToCss for AlignFlags { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { let extra_flags = *self & AlignFlags::FLAG_BITS; let value = self.value(); match extra_flags { AlignFlags::LEGACY => { dest.write_str("legacy")?; if value.is_empty() { return Ok(()); } dest.write_char(' ')?; }, AlignFlags::SAFE => dest.write_str("safe ")?, AlignFlags::UNSAFE => dest.write_str("unsafe ")?, _ => { debug_assert_eq!(extra_flags, AlignFlags::empty()); }, } dest.write_str(match value { AlignFlags::AUTO => "auto", AlignFlags::NORMAL => "normal", AlignFlags::START => "start", AlignFlags::END => "end", AlignFlags::FLEX_START => "flex-start", AlignFlags::FLEX_END => "flex-end", AlignFlags::CENTER => "center", AlignFlags::LEFT => "left", AlignFlags::RIGHT => "right", AlignFlags::BASELINE => "baseline", AlignFlags::LAST_BASELINE => "last baseline", AlignFlags::STRETCH => "stretch", AlignFlags::SELF_START => "self-start", AlignFlags::SELF_END => "self-end", AlignFlags::SPACE_BETWEEN => "space-between", AlignFlags::SPACE_AROUND => "space-around", AlignFlags::SPACE_EVENLY => "space-evenly", _ => unreachable!(), }) } } /// An axis direction, either inline (for the `justify` properties) or block, /// (for the `align` properties). #[derive(Clone, Copy, PartialEq)] pub enum AxisDirection { /// Block direction. Block, /// Inline direction. Inline, } /// Shared value for the `align-content` and `justify-content` properties. /// /// #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] pub struct ContentDistribution { primary: AlignFlags, // FIXME(https://github.com/w3c/csswg-drafts/issues/1002): This will need to // accept fallback alignment, eventually. } impl ContentDistribution { /// The initial value 'normal' #[inline] pub fn normal() -> Self { Self::new(AlignFlags::NORMAL) } /// `start` #[inline] pub fn start() -> Self { Self::new(AlignFlags::START) } /// The initial value 'normal' #[inline] pub fn new(primary: AlignFlags) -> Self { Self { primary } } /// Returns whether this value is a . pub fn is_baseline_position(&self) -> bool { matches!( self.primary.value(), AlignFlags::BASELINE | AlignFlags::LAST_BASELINE ) } /// The primary alignment #[inline] pub fn primary(self) -> AlignFlags { self.primary } /// Parse a value for align-content / justify-content. pub fn parse<'i, 't>( input: &mut Parser<'i, 't>, axis: AxisDirection, ) -> Result> { // NOTE Please also update the `list_keywords` function below // when this function is updated. // Try to parse normal first if input .try_parse(|i| i.expect_ident_matching("normal")) .is_ok() { return Ok(ContentDistribution::normal()); } // Parse , but only on the block axis. if axis == AxisDirection::Block { if let Ok(value) = input.try_parse(parse_baseline) { return Ok(ContentDistribution::new(value)); } } // if let Ok(value) = input.try_parse(parse_content_distribution) { return Ok(ContentDistribution::new(value)); } // ? let overflow_position = input .try_parse(parse_overflow_position) .unwrap_or(AlignFlags::empty()); let content_position = try_match_ident_ignore_ascii_case! { input, "start" => AlignFlags::START, "end" => AlignFlags::END, "flex-start" => AlignFlags::FLEX_START, "flex-end" => AlignFlags::FLEX_END, "center" => AlignFlags::CENTER, "left" if axis == AxisDirection::Inline => AlignFlags::LEFT, "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT, }; Ok(ContentDistribution::new( content_position | overflow_position, )) } fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) { f(&["normal"]); if axis == AxisDirection::Block { list_baseline_keywords(f); } list_content_distribution_keywords(f); list_overflow_position_keywords(f); f(&["start", "end", "flex-start", "flex-end", "center"]); if axis == AxisDirection::Inline { f(&["left", "right"]); } } } /// Value for the `align-content` property. /// /// #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(transparent)] pub struct AlignContent(pub ContentDistribution); impl Parse for AlignContent { fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update `impl SpecifiedValueInfo` below when // this function is updated. Ok(AlignContent(ContentDistribution::parse( input, AxisDirection::Block, )?)) } } impl SpecifiedValueInfo for AlignContent { fn collect_completion_keywords(f: KeywordsCollectFn) { ContentDistribution::list_keywords(f, AxisDirection::Block); } } /// Value for the `align-tracks` property. /// /// #[derive( Clone, Debug, Default, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(transparent)] #[css(comma)] pub struct AlignTracks(#[css(iterable, if_empty = "normal")] pub crate::OwnedSlice); impl Parse for AlignTracks { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let values = input.parse_comma_separated(|input| AlignContent::parse(context, input))?; Ok(AlignTracks(values.into())) } } /// Value for the `justify-content` property. /// /// #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(transparent)] pub struct JustifyContent(pub ContentDistribution); impl Parse for JustifyContent { fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update `impl SpecifiedValueInfo` below when // this function is updated. Ok(JustifyContent(ContentDistribution::parse( input, AxisDirection::Inline, )?)) } } impl SpecifiedValueInfo for JustifyContent { fn collect_completion_keywords(f: KeywordsCollectFn) { ContentDistribution::list_keywords(f, AxisDirection::Inline); } } /// Value for the `justify-tracks` property. /// /// #[derive( Clone, Debug, Default, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(transparent)] #[css(comma)] pub struct JustifyTracks( #[css(iterable, if_empty = "normal")] pub crate::OwnedSlice, ); impl Parse for JustifyTracks { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let values = input.parse_comma_separated(|input| JustifyContent::parse(context, input))?; Ok(JustifyTracks(values.into())) } } /// #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(transparent)] pub struct SelfAlignment(pub AlignFlags); impl SelfAlignment { /// The initial value 'auto' #[inline] pub fn auto() -> Self { SelfAlignment(AlignFlags::AUTO) } /// Returns whether this value is valid for both axis directions. pub fn is_valid_on_both_axes(&self) -> bool { match self.0.value() { // left | right are only allowed on the inline axis. AlignFlags::LEFT | AlignFlags::RIGHT => false, _ => true, } } /// Parse a self-alignment value on one of the axis. pub fn parse<'i, 't>( input: &mut Parser<'i, 't>, axis: AxisDirection, ) -> Result> { // NOTE Please also update the `list_keywords` function below // when this function is updated. // // // It's weird that this accepts , but not // justify-content... if let Ok(value) = input.try_parse(parse_baseline) { return Ok(SelfAlignment(value)); } // auto | normal | stretch if let Ok(value) = input.try_parse(parse_auto_normal_stretch) { return Ok(SelfAlignment(value)); } // ? let overflow_position = input .try_parse(parse_overflow_position) .unwrap_or(AlignFlags::empty()); let self_position = parse_self_position(input, axis)?; Ok(SelfAlignment(overflow_position | self_position)) } fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) { list_baseline_keywords(f); list_auto_normal_stretch(f); list_overflow_position_keywords(f); list_self_position_keywords(f, axis); } } /// The specified value of the align-self property. /// /// #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct AlignSelf(pub SelfAlignment); impl Parse for AlignSelf { fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update `impl SpecifiedValueInfo` below when // this function is updated. Ok(AlignSelf(SelfAlignment::parse( input, AxisDirection::Block, )?)) } } impl SpecifiedValueInfo for AlignSelf { fn collect_completion_keywords(f: KeywordsCollectFn) { SelfAlignment::list_keywords(f, AxisDirection::Block); } } /// The specified value of the justify-self property. /// /// #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct JustifySelf(pub SelfAlignment); impl Parse for JustifySelf { fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update `impl SpecifiedValueInfo` below when // this function is updated. Ok(JustifySelf(SelfAlignment::parse( input, AxisDirection::Inline, )?)) } } impl SpecifiedValueInfo for JustifySelf { fn collect_completion_keywords(f: KeywordsCollectFn) { SelfAlignment::list_keywords(f, AxisDirection::Inline); } } /// Value of the `align-items` property /// /// #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct AlignItems(pub AlignFlags); impl AlignItems { /// The initial value 'normal' #[inline] pub fn normal() -> Self { AlignItems(AlignFlags::NORMAL) } } impl Parse for AlignItems { // normal | stretch | | // ? fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update `impl SpecifiedValueInfo` below when // this function is updated. // if let Ok(baseline) = input.try_parse(parse_baseline) { return Ok(AlignItems(baseline)); } // normal | stretch if let Ok(value) = input.try_parse(parse_normal_stretch) { return Ok(AlignItems(value)); } // ? let overflow = input .try_parse(parse_overflow_position) .unwrap_or(AlignFlags::empty()); let self_position = parse_self_position(input, AxisDirection::Block)?; Ok(AlignItems(self_position | overflow)) } } impl SpecifiedValueInfo for AlignItems { fn collect_completion_keywords(f: KeywordsCollectFn) { list_baseline_keywords(f); list_normal_stretch(f); list_overflow_position_keywords(f); list_self_position_keywords(f, AxisDirection::Block); } } /// Value of the `justify-items` property /// /// #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)] #[repr(C)] pub struct JustifyItems(pub AlignFlags); impl JustifyItems { /// The initial value 'legacy' #[inline] pub fn legacy() -> Self { JustifyItems(AlignFlags::LEGACY) } /// The value 'normal' #[inline] pub fn normal() -> Self { JustifyItems(AlignFlags::NORMAL) } } impl Parse for JustifyItems { fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update `impl SpecifiedValueInfo` below when // this function is updated. // // // It's weird that this accepts , but not // justify-content... if let Ok(baseline) = input.try_parse(parse_baseline) { return Ok(JustifyItems(baseline)); } // normal | stretch if let Ok(value) = input.try_parse(parse_normal_stretch) { return Ok(JustifyItems(value)); } // legacy | [ legacy && [ left | right | center ] ] if let Ok(value) = input.try_parse(parse_legacy) { return Ok(JustifyItems(value)); } // ? let overflow = input .try_parse(parse_overflow_position) .unwrap_or(AlignFlags::empty()); let self_position = parse_self_position(input, AxisDirection::Inline)?; Ok(JustifyItems(overflow | self_position)) } } impl SpecifiedValueInfo for JustifyItems { fn collect_completion_keywords(f: KeywordsCollectFn) { list_baseline_keywords(f); list_normal_stretch(f); list_legacy_keywords(f); list_overflow_position_keywords(f); list_self_position_keywords(f, AxisDirection::Inline); } } // auto | normal | stretch fn parse_auto_normal_stretch<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update the `list_auto_normal_stretch` function // below when this function is updated. try_match_ident_ignore_ascii_case! { input, "auto" => Ok(AlignFlags::AUTO), "normal" => Ok(AlignFlags::NORMAL), "stretch" => Ok(AlignFlags::STRETCH), } } fn list_auto_normal_stretch(f: KeywordsCollectFn) { f(&["auto", "normal", "stretch"]); } // normal | stretch fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { // NOTE Please also update the `list_normal_stretch` function below // when this function is updated. try_match_ident_ignore_ascii_case! { input, "normal" => Ok(AlignFlags::NORMAL), "stretch" => Ok(AlignFlags::STRETCH), } } fn list_normal_stretch(f: KeywordsCollectFn) { f(&["normal", "stretch"]); } // fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { // NOTE Please also update the `list_baseline_keywords` function // below when this function is updated. try_match_ident_ignore_ascii_case! { input, "baseline" => Ok(AlignFlags::BASELINE), "first" => { input.expect_ident_matching("baseline")?; Ok(AlignFlags::BASELINE) }, "last" => { input.expect_ident_matching("baseline")?; Ok(AlignFlags::LAST_BASELINE) }, } } fn list_baseline_keywords(f: KeywordsCollectFn) { f(&["baseline", "first baseline", "last baseline"]); } // fn parse_content_distribution<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update the `list_content_distribution_keywords` // function below when this function is updated. try_match_ident_ignore_ascii_case! { input, "stretch" => Ok(AlignFlags::STRETCH), "space-between" => Ok(AlignFlags::SPACE_BETWEEN), "space-around" => Ok(AlignFlags::SPACE_AROUND), "space-evenly" => Ok(AlignFlags::SPACE_EVENLY), } } fn list_content_distribution_keywords(f: KeywordsCollectFn) { f(&["stretch", "space-between", "space-around", "space-evenly"]); } // fn parse_overflow_position<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update the `list_overflow_position_keywords` // function below when this function is updated. try_match_ident_ignore_ascii_case! { input, "safe" => Ok(AlignFlags::SAFE), "unsafe" => Ok(AlignFlags::UNSAFE), } } fn list_overflow_position_keywords(f: KeywordsCollectFn) { f(&["safe", "unsafe"]); } // | left | right in the inline axis. fn parse_self_position<'i, 't>( input: &mut Parser<'i, 't>, axis: AxisDirection, ) -> Result> { // NOTE Please also update the `list_self_position_keywords` // function below when this function is updated. Ok(try_match_ident_ignore_ascii_case! { input, "start" => AlignFlags::START, "end" => AlignFlags::END, "flex-start" => AlignFlags::FLEX_START, "flex-end" => AlignFlags::FLEX_END, "center" => AlignFlags::CENTER, "self-start" => AlignFlags::SELF_START, "self-end" => AlignFlags::SELF_END, "left" if axis == AxisDirection::Inline => AlignFlags::LEFT, "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT, }) } fn list_self_position_keywords(f: KeywordsCollectFn, axis: AxisDirection) { f(&[ "start", "end", "flex-start", "flex-end", "center", "self-start", "self-end", ]); if axis == AxisDirection::Inline { f(&["left", "right"]); } } fn parse_left_right_center<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result> { // NOTE Please also update the `list_legacy_keywords` function below // when this function is updated. Ok(try_match_ident_ignore_ascii_case! { input, "left" => AlignFlags::LEFT, "right" => AlignFlags::RIGHT, "center" => AlignFlags::CENTER, }) } // legacy | [ legacy && [ left | right | center ] ] fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { // NOTE Please also update the `list_legacy_keywords` function below // when this function is updated. let flags = try_match_ident_ignore_ascii_case! { input, "legacy" => { let flags = input.try_parse(parse_left_right_center) .unwrap_or(AlignFlags::empty()); return Ok(AlignFlags::LEGACY | flags) }, "left" => AlignFlags::LEFT, "right" => AlignFlags::RIGHT, "center" => AlignFlags::CENTER, }; input.expect_ident_matching("legacy")?; Ok(AlignFlags::LEGACY | flags) } fn list_legacy_keywords(f: KeywordsCollectFn) { f(&["legacy", "left", "right", "center"]); }