/* 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 http://mozilla.org/MPL/2.0/. */ //! Values for CSS Box Alignment properties //! //! https://drafts.csswg.org/css-align/ use cssparser::Parser; use gecko_bindings::structs; use parser::{Parse, ParserContext}; use selectors::parser::SelectorParseErrorKind; #[allow(unused_imports)] use std::ascii::AsciiExt; use std::fmt; use style_traits::{ToCss, ParseError, StyleParseErrorKind}; bitflags! { /// Constants shared by multiple CSS Box Alignment properties /// /// These constants match Gecko's `NS_STYLE_ALIGN_*` constants. #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(ToComputedValue)] pub struct AlignFlags: u8 { // Enumeration stored in the lower 5 bits: /// 'auto' const AUTO = structs::NS_STYLE_ALIGN_AUTO as u8; /// 'normal' const NORMAL = structs::NS_STYLE_ALIGN_NORMAL as u8; /// 'start' const START = structs::NS_STYLE_ALIGN_START as u8; /// 'end' const END = structs::NS_STYLE_ALIGN_END as u8; /// 'flex-start' const FLEX_START = structs::NS_STYLE_ALIGN_FLEX_START as u8; /// 'flex-end' const FLEX_END = structs::NS_STYLE_ALIGN_FLEX_END as u8; /// 'center' const CENTER = structs::NS_STYLE_ALIGN_CENTER as u8; /// 'left' const LEFT = structs::NS_STYLE_ALIGN_LEFT as u8; /// 'right' const RIGHT = structs::NS_STYLE_ALIGN_RIGHT as u8; /// 'baseline' const BASELINE = structs::NS_STYLE_ALIGN_BASELINE as u8; /// 'last-baseline' const LAST_BASELINE = structs::NS_STYLE_ALIGN_LAST_BASELINE as u8; /// 'stretch' const STRETCH = structs::NS_STYLE_ALIGN_STRETCH as u8; /// 'self-start' const SELF_START = structs::NS_STYLE_ALIGN_SELF_START as u8; /// 'self-end' const SELF_END = structs::NS_STYLE_ALIGN_SELF_END as u8; /// 'space-between' const SPACE_BETWEEN = structs::NS_STYLE_ALIGN_SPACE_BETWEEN as u8; /// 'space-around' const SPACE_AROUND = structs::NS_STYLE_ALIGN_SPACE_AROUND as u8; /// 'space-evenly' const SPACE_EVENLY = structs::NS_STYLE_ALIGN_SPACE_EVENLY as u8; // Additional flags stored in the upper bits: /// 'legacy' (mutually exclusive w. SAFE & UNSAFE) const LEGACY = structs::NS_STYLE_ALIGN_LEGACY as u8; /// 'safe' const SAFE = structs::NS_STYLE_ALIGN_SAFE as u8; /// 'unsafe' (mutually exclusive w. SAFE) const UNSAFE = structs::NS_STYLE_ALIGN_UNSAFE as u8; /// Mask for the additional flags above. const FLAG_BITS = structs::NS_STYLE_ALIGN_FLAG_BITS as u8; } } impl ToCss for AlignFlags { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { let s = match *self & !AlignFlags::FLAG_BITS { 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!() }; dest.write_str(s)?; match *self & AlignFlags::FLAG_BITS { AlignFlags::LEGACY => { dest.write_str(" legacy")?; } AlignFlags::SAFE => { dest.write_str(" safe")?; } AlignFlags::UNSAFE => { dest.write_str(" unsafe")?; } _ => {} } Ok(()) } } /// Mask for a single AlignFlags value. const ALIGN_ALL_BITS: u16 = structs::NS_STYLE_ALIGN_ALL_BITS as u16; /// Number of bits to shift a fallback alignment. const ALIGN_ALL_SHIFT: u32 = structs::NS_STYLE_ALIGN_ALL_SHIFT; /// Value of the `align-content` or `justify-content` property. /// /// /// /// The 16-bit field stores the primary value in its lower 8 bits, and the optional fallback value /// in its upper 8 bits. This matches the representation of these properties in Gecko. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] pub struct AlignJustifyContent(u16); /// Whether fallback is allowed in align-content / justify-content parsing. /// /// This is used for the place-content shorthand, until the resolutions from [1] /// are specified. /// /// [1]: https://github.com/w3c/csswg-drafts/issues/1002 #[derive(Clone, Copy, PartialEq)] pub enum FallbackAllowed { /// Allow fallback alignment. Yes, /// Don't allow fallback alignment. No, } impl AlignJustifyContent { /// The initial value 'normal' #[inline] pub fn normal() -> Self { Self::new(AlignFlags::NORMAL) } /// Construct a value with no fallback. #[inline] pub fn new(flags: AlignFlags) -> Self { AlignJustifyContent(flags.bits() as u16) } /// Construct a value including a fallback alignment. /// /// #[inline] pub fn with_fallback(flags: AlignFlags, fallback: AlignFlags) -> Self { AlignJustifyContent(flags.bits() as u16 | ((fallback.bits() as u16) << ALIGN_ALL_SHIFT)) } /// The primary alignment #[inline] pub fn primary(self) -> AlignFlags { AlignFlags::from_bits((self.0 & ALIGN_ALL_BITS) as u8) .expect("AlignJustifyContent must contain valid flags") } /// The fallback alignment #[inline] pub fn fallback(self) -> AlignFlags { AlignFlags::from_bits((self.0 >> ALIGN_ALL_SHIFT) as u8) .expect("AlignJustifyContent must contain valid flags") } /// Whether this value has extra flags. #[inline] pub fn has_extra_flags(self) -> bool { self.primary().intersects(AlignFlags::FLAG_BITS) || self.fallback().intersects(AlignFlags::FLAG_BITS) } /// Parse a value for align-content / justify-content, optionally allowing /// fallback. pub fn parse_with_fallback<'i, 't>( input: &mut Parser<'i, 't>, fallback_allowed: FallbackAllowed, ) -> Result> { // normal | if let Ok(value) = input.try(|input| parse_normal_or_baseline(input)) { return Ok(AlignJustifyContent::new(value)) } // followed by optional <*-position> if let Ok(value) = input.try(|input| parse_content_distribution(input)) { if fallback_allowed == FallbackAllowed::Yes { if let Ok(fallback) = input.try(|input| parse_overflow_content_position(input)) { return Ok(AlignJustifyContent::with_fallback(value, fallback)) } } return Ok(AlignJustifyContent::new(value)) } // <*-position> followed by optional let fallback = parse_overflow_content_position(input)?; if fallback_allowed == FallbackAllowed::Yes { if let Ok(value) = input.try(|input| parse_content_distribution(input)) { return Ok(AlignJustifyContent::with_fallback(value, fallback)) } } Ok(AlignJustifyContent::new(fallback)) } } impl ToCss for AlignJustifyContent { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { self.primary().to_css(dest)?; match self.fallback() { AlignFlags::AUTO => {} fallback => { dest.write_str(" ")?; fallback.to_css(dest)?; } } Ok(()) } } impl Parse for AlignJustifyContent { // normal | | // [ || [ ? && ] ] fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { Self::parse_with_fallback(input, FallbackAllowed::Yes) } } /// Value of the `align-self` or `justify-self` property. /// /// #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Copy, Debug, Eq, PartialEq, ToComputedValue, ToCss)] pub struct AlignJustifySelf(pub AlignFlags); impl AlignJustifySelf { /// The initial value 'auto' #[inline] pub fn auto() -> Self { AlignJustifySelf(AlignFlags::AUTO) } /// Whether this value has extra flags. #[inline] pub fn has_extra_flags(self) -> bool { self.0.intersects(AlignFlags::FLAG_BITS) } } impl Parse for AlignJustifySelf { // auto | normal | stretch | | // [ ? && ] fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { // auto | normal | stretch | if let Ok(value) = input.try(parse_auto_normal_stretch_baseline) { return Ok(AlignJustifySelf(value)) } // [ ? && ] if let Ok(value) = input.try(parse_overflow_self_position) { return Ok(AlignJustifySelf(value)) } Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } /// Value of the `align-items` property /// /// #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Copy, Debug, Eq, PartialEq, ToComputedValue, ToCss)] pub struct AlignItems(pub AlignFlags); impl AlignItems { /// The initial value 'normal' #[inline] pub fn normal() -> Self { AlignItems(AlignFlags::NORMAL) } /// Whether this value has extra flags. #[inline] pub fn has_extra_flags(self) -> bool { self.0.intersects(AlignFlags::FLAG_BITS) } } impl Parse for AlignItems { // normal | stretch | | // [ ? && ] fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { // normal | stretch | if let Ok(value) = input.try(parse_normal_stretch_baseline) { return Ok(AlignItems(value)) } // [ ? && ] if let Ok(value) = input.try(parse_overflow_self_position) { return Ok(AlignItems(value)) } Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } /// Value of the `justify-items` property /// /// #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Copy, Debug, Eq, PartialEq, ToCss)] pub struct JustifyItems(pub AlignFlags); impl JustifyItems { /// The initial value 'auto' #[inline] pub fn auto() -> Self { JustifyItems(AlignFlags::AUTO) } /// The value 'normal' #[inline] pub fn normal() -> Self { JustifyItems(AlignFlags::NORMAL) } /// Whether this value has extra flags. #[inline] pub fn has_extra_flags(self) -> bool { self.0.intersects(AlignFlags::FLAG_BITS) } } impl Parse for JustifyItems { // auto | normal | stretch | | // [ ? && ] // [ legacy && [ left | right | center ] ] fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { // auto | normal | stretch | if let Ok(value) = input.try(parse_auto_normal_stretch_baseline) { return Ok(JustifyItems(value)) } // [ legacy && [ left | right | center ] ] if let Ok(value) = input.try(parse_legacy) { return Ok(JustifyItems(value)) } // [ ? && ] if let Ok(value) = parse_overflow_self_position(input) { return Ok(JustifyItems(value)) } Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } #[cfg(feature = "gecko")] impl From for AlignJustifyContent { fn from(bits: u16) -> AlignJustifyContent { AlignJustifyContent(bits) } } #[cfg(feature = "gecko")] impl From for u16 { fn from(v: AlignJustifyContent) -> u16 { v.0 } } // auto | normal | stretch | fn parse_auto_normal_stretch_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { if let Ok(baseline) = input.try(parse_baseline) { return Ok(baseline); } try_match_ident_ignore_ascii_case! { input, "auto" => Ok(AlignFlags::AUTO), "normal" => Ok(AlignFlags::NORMAL), "stretch" => Ok(AlignFlags::STRETCH), } } // normal | stretch | fn parse_normal_stretch_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { if let Ok(baseline) = input.try(parse_baseline) { return Ok(baseline); } try_match_ident_ignore_ascii_case! { input, "normal" => Ok(AlignFlags::NORMAL), "stretch" => Ok(AlignFlags::STRETCH), } } // normal | fn parse_normal_or_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { if let Ok(baseline) = input.try(parse_baseline) { return Ok(baseline); } input.expect_ident_matching("normal")?; Ok(AlignFlags::NORMAL) } // fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { // FIXME: remove clone() when lifetimes are non-lexical 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 parse_content_distribution<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { 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 parse_overflow_content_position<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { // followed by optional if let Ok(mut content) = input.try(parse_content_position) { if let Ok(overflow) = input.try(parse_overflow_position) { content |= overflow; } return Ok(content) } // followed by required if let Ok(overflow) = parse_overflow_position(input) { if let Ok(content) = parse_content_position(input) { return Ok(overflow | content) } } return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } // fn parse_content_position<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { try_match_ident_ignore_ascii_case! { input, "start" => Ok(AlignFlags::START), "end" => Ok(AlignFlags::END), "flex-start" => Ok(AlignFlags::FLEX_START), "flex-end" => Ok(AlignFlags::FLEX_END), "center" => Ok(AlignFlags::CENTER), "left" => Ok(AlignFlags::LEFT), "right" => Ok(AlignFlags::RIGHT), } } // fn parse_overflow_position<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { try_match_ident_ignore_ascii_case! { input, "safe" => Ok(AlignFlags::SAFE), "unsafe" => Ok(AlignFlags::UNSAFE), } } // [ ? && ] fn parse_overflow_self_position<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { // followed by optional if let Ok(mut self_position) = input.try(parse_self_position) { if let Ok(overflow) = input.try(parse_overflow_position) { self_position |= overflow; } return Ok(self_position) } // followed by required if let Ok(overflow) = parse_overflow_position(input) { if let Ok(self_position) = parse_self_position(input) { return Ok(overflow | self_position) } } return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } // fn parse_self_position<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { try_match_ident_ignore_ascii_case! { input, "start" => Ok(AlignFlags::START), "end" => Ok(AlignFlags::END), "flex-start" => Ok(AlignFlags::FLEX_START), "flex-end" => Ok(AlignFlags::FLEX_END), "center" => Ok(AlignFlags::CENTER), "left" => Ok(AlignFlags::LEFT), "right" => Ok(AlignFlags::RIGHT), "self-start" => Ok(AlignFlags::SELF_START), "self-end" => Ok(AlignFlags::SELF_END), } } // [ legacy && [ left | right | center ] ] fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { let a_location = input.current_source_location(); let a = input.expect_ident()?.clone(); let b_location = input.current_source_location(); let b = input.expect_ident()?; if a.eq_ignore_ascii_case("legacy") { (match_ignore_ascii_case! { &b, "left" => Ok(AlignFlags::LEGACY | AlignFlags::LEFT), "right" => Ok(AlignFlags::LEGACY | AlignFlags::RIGHT), "center" => Ok(AlignFlags::LEGACY | AlignFlags::CENTER), _ => Err(()) }).map_err(|()| b_location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(b.clone()))) } else if b.eq_ignore_ascii_case("legacy") { (match_ignore_ascii_case! { &a, "left" => Ok(AlignFlags::LEGACY | AlignFlags::LEFT), "right" => Ok(AlignFlags::LEGACY | AlignFlags::RIGHT), "center" => Ok(AlignFlags::LEGACY | AlignFlags::CENTER), _ => Err(()) }).map_err(|()| a_location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(a))) } else { Err(a_location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } }