use crate::parser::{Parse, ParserContext};
use cssparser::Parser;
use std::fmt::{self, Write};
use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, ToCss};
#[derive(
Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(C)]
pub struct AlignFlags(u8);
bitflags! {
impl AlignFlags: u8 {
const AUTO = 0;
const NORMAL = 1;
const START = 2;
const END = 3;
const FLEX_START = 4;
const FLEX_END = 5;
const CENTER = 6;
const LEFT = 7;
const RIGHT = 8;
const BASELINE = 9;
const LAST_BASELINE = 10;
const STRETCH = 11;
const SELF_START = 12;
const SELF_END = 13;
const SPACE_BETWEEN = 14;
const SPACE_AROUND = 15;
const SPACE_EVENLY = 16;
const ANCHOR_CENTER = 17;
const LEGACY = 1 << 5;
const SAFE = 1 << 6;
const UNSAFE = 1 << 7;
const FLAG_BITS = 0b11100000;
}
}
impl AlignFlags {
#[inline]
pub fn value(&self) -> Self {
*self & !AlignFlags::FLAG_BITS
}
#[inline]
pub fn flags(&self) -> Self {
*self & AlignFlags::FLAG_BITS
}
}
impl ToCss for AlignFlags {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> 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",
AlignFlags::ANCHOR_CENTER => "anchor-center",
_ => unreachable!(),
})
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum AxisDirection {
Block,
Inline,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(C)]
pub struct ContentDistribution {
primary: AlignFlags,
}
impl ContentDistribution {
#[inline]
pub fn normal() -> Self {
Self::new(AlignFlags::NORMAL)
}
#[inline]
pub fn start() -> Self {
Self::new(AlignFlags::START)
}
#[inline]
pub fn new(primary: AlignFlags) -> Self {
Self { primary }
}
pub fn is_baseline_position(&self) -> bool {
matches!(
self.primary.value(),
AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
)
}
#[inline]
pub fn primary(self) -> AlignFlags {
self.primary
}
pub fn parse<'i, 't>(
input: &mut Parser<'i, 't>,
axis: AxisDirection,
) -> Result<Self, ParseError<'i>> {
if input
.try_parse(|i| i.expect_ident_matching("normal"))
.is_ok()
{
return Ok(ContentDistribution::normal());
}
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"]);
}
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(transparent)]
pub struct AlignContent(pub ContentDistribution);
impl Parse for AlignContent {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Ok(AlignContent(ContentDistribution::parse(
input,
AxisDirection::Block,
)?))
}
}
impl SpecifiedValueInfo for AlignContent {
fn collect_completion_keywords(f: KeywordsCollectFn) {
ContentDistribution::list_keywords(f, AxisDirection::Block);
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(transparent)]
pub struct JustifyContent(pub ContentDistribution);
impl Parse for JustifyContent {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Ok(JustifyContent(ContentDistribution::parse(
input,
AxisDirection::Inline,
)?))
}
}
impl SpecifiedValueInfo for JustifyContent {
fn collect_completion_keywords(f: KeywordsCollectFn) {
ContentDistribution::list_keywords(f, AxisDirection::Inline);
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(transparent)]
pub struct SelfAlignment(pub AlignFlags);
impl SelfAlignment {
#[inline]
pub fn auto() -> Self {
SelfAlignment(AlignFlags::AUTO)
}
pub fn is_valid_on_both_axes(&self) -> bool {
match self.0.value() {
AlignFlags::LEFT | AlignFlags::RIGHT => false,
_ => true,
}
}
pub fn parse<'i, 't>(
input: &mut Parser<'i, 't>,
axis: AxisDirection,
) -> Result<Self, ParseError<'i>> {
if let Ok(value) = input.try_parse(parse_baseline) {
return Ok(SelfAlignment(value));
}
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);
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(C)]
pub struct AlignSelf(pub SelfAlignment);
impl Parse for AlignSelf {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Ok(AlignSelf(SelfAlignment::parse(
input,
AxisDirection::Block,
)?))
}
}
impl SpecifiedValueInfo for AlignSelf {
fn collect_completion_keywords(f: KeywordsCollectFn) {
SelfAlignment::list_keywords(f, AxisDirection::Block);
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(C)]
pub struct JustifySelf(pub SelfAlignment);
impl Parse for JustifySelf {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Ok(JustifySelf(SelfAlignment::parse(
input,
AxisDirection::Inline,
)?))
}
}
impl SpecifiedValueInfo for JustifySelf {
fn collect_completion_keywords(f: KeywordsCollectFn) {
SelfAlignment::list_keywords(f, AxisDirection::Inline);
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(C)]
pub struct AlignItems(pub AlignFlags);
impl AlignItems {
#[inline]
pub fn normal() -> Self {
AlignItems(AlignFlags::NORMAL)
}
}
impl Parse for AlignItems {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if let Ok(baseline) = input.try_parse(parse_baseline) {
return Ok(AlignItems(baseline));
}
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);
}
}
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(C)]
pub struct JustifyItems(pub AlignFlags);
impl JustifyItems {
#[inline]
pub fn legacy() -> Self {
JustifyItems(AlignFlags::LEGACY)
}
#[inline]
pub fn normal() -> Self {
JustifyItems(AlignFlags::NORMAL)
}
}
impl Parse for JustifyItems {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if let Ok(baseline) = input.try_parse(parse_baseline) {
return Ok(JustifyItems(baseline));
}
if let Ok(value) = input.try_parse(parse_normal_stretch) {
return Ok(JustifyItems(value));
}
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);
}
}
fn parse_auto_normal_stretch<'i, 't>(
input: &mut Parser<'i, 't>,
) -> Result<AlignFlags, ParseError<'i>> {
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"]);
}
fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
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<AlignFlags, ParseError<'i>> {
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<AlignFlags, ParseError<'i>> {
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<AlignFlags, ParseError<'i>> {
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"]);
}
fn parse_self_position<'i, 't>(
input: &mut Parser<'i, 't>,
axis: AxisDirection,
) -> Result<AlignFlags, ParseError<'i>> {
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,
"anchor-center" if static_prefs::pref!("layout.css.anchor-positioning.enabled") => AlignFlags::ANCHOR_CENTER,
})
}
fn list_self_position_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
f(&[
"start",
"end",
"flex-start",
"flex-end",
"center",
"self-start",
"self-end",
"anchor-center",
]);
if axis == AxisDirection::Inline {
f(&["left", "right"]);
}
}
fn parse_left_right_center<'i, 't>(
input: &mut Parser<'i, 't>,
) -> Result<AlignFlags, ParseError<'i>> {
Ok(try_match_ident_ignore_ascii_case! { input,
"left" => AlignFlags::LEFT,
"right" => AlignFlags::RIGHT,
"center" => AlignFlags::CENTER,
})
}
fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
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"]);
}