use std::sync::Arc;
use super::length::{FontContext, Length, LengthContext, Viewport};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlexDirection {
Row,
RowReverse,
Column,
ColumnReverse,
}
impl FlexDirection {
pub fn is_row(&self) -> bool {
matches!(self, FlexDirection::Row | FlexDirection::RowReverse)
}
pub fn is_column(&self) -> bool {
matches!(self, FlexDirection::Column | FlexDirection::ColumnReverse)
}
pub fn is_reverse(&self) -> bool {
matches!(
self,
FlexDirection::RowReverse | FlexDirection::ColumnReverse
)
}
}
#[doc(hidden)]
pub fn parse_flex_direction(s: &str) -> Option<FlexDirection> {
match s.trim().to_ascii_lowercase().as_str() {
"row" => Some(FlexDirection::Row),
"row-reverse" => Some(FlexDirection::RowReverse),
"column" => Some(FlexDirection::Column),
"column-reverse" => Some(FlexDirection::ColumnReverse),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlexWrap {
NoWrap,
Wrap,
WrapReverse,
}
#[doc(hidden)]
pub fn parse_flex_wrap(s: &str) -> Option<FlexWrap> {
match s.trim().to_ascii_lowercase().as_str() {
"nowrap" => Some(FlexWrap::NoWrap),
"wrap" => Some(FlexWrap::Wrap),
"wrap-reverse" => Some(FlexWrap::WrapReverse),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JustifyContent {
FlexStart,
FlexEnd,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly,
}
#[doc(hidden)]
pub fn parse_justify_content(s: &str) -> Option<JustifyContent> {
match s.trim().to_ascii_lowercase().as_str() {
"flex-start" | "start" | "left" => Some(JustifyContent::FlexStart),
"flex-end" | "end" | "right" => Some(JustifyContent::FlexEnd),
"center" => Some(JustifyContent::Center),
"space-between" => Some(JustifyContent::SpaceBetween),
"space-around" => Some(JustifyContent::SpaceAround),
"space-evenly" => Some(JustifyContent::SpaceEvenly),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlignItems {
Stretch,
FlexStart,
FlexEnd,
Center,
Baseline,
}
#[doc(hidden)]
pub fn parse_align_items(s: &str) -> Option<AlignItems> {
match s.trim().to_ascii_lowercase().as_str() {
"stretch" => Some(AlignItems::Stretch),
"flex-start" | "start" | "self-start" => Some(AlignItems::FlexStart),
"flex-end" | "end" | "self-end" => Some(AlignItems::FlexEnd),
"center" => Some(AlignItems::Center),
"baseline" | "first baseline" => Some(AlignItems::Baseline),
_ => None,
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum AlignContent {
#[default]
Stretch,
FlexStart,
FlexEnd,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly,
}
#[doc(hidden)]
pub fn parse_align_content(s: &str) -> Option<AlignContent> {
match s.trim().to_ascii_lowercase().as_str() {
"stretch" => Some(AlignContent::Stretch),
"flex-start" | "start" | "left" => Some(AlignContent::FlexStart),
"flex-end" | "end" | "right" => Some(AlignContent::FlexEnd),
"center" => Some(AlignContent::Center),
"space-between" => Some(AlignContent::SpaceBetween),
"space-around" => Some(AlignContent::SpaceAround),
"space-evenly" => Some(AlignContent::SpaceEvenly),
_ => None,
}
}
pub(crate) fn is_place_shorthand_name(name: &str) -> bool {
matches!(name, "place-content" | "place-items" | "place-self")
}
pub(crate) fn expand_place_shorthand(
name: &str,
value: &str,
) -> Option<Vec<(&'static str, Arc<str>)>> {
let mut tokens = value.split_whitespace();
let first = tokens.next()?;
let second = tokens.next();
if tokens.next().is_some() {
return None;
}
match name {
"place-content" => {
parse_align_content(first)?;
let justify_token = match second {
Some(s) => {
parse_justify_content(s)?;
Some(s)
}
None => parse_justify_content(first).map(|_| first),
};
let mut out = vec![("align-content", Arc::from(first))];
if let Some(token) = justify_token {
out.push(("justify-content", Arc::from(token)));
}
Some(out)
}
"place-items" => {
parse_align_items(first)?;
let justify_token = match second {
Some(s) => {
parse_align_items(s)?;
Some(s)
}
None => parse_align_items(first).map(|_| first),
};
let mut out = vec![("align-items", Arc::from(first))];
if let Some(token) = justify_token {
out.push(("justify-items", Arc::from(token)));
}
Some(out)
}
"place-self" => {
if parse_align_items(first).is_none() && !first.eq_ignore_ascii_case("auto") {
return None;
}
let justify_token = match second {
Some(s) => {
parse_align_items(s)?;
Some(s)
}
None if first.eq_ignore_ascii_case("auto") => Some(first),
None => parse_align_items(first).map(|_| first),
};
let mut out = vec![("align-self", Arc::from(first))];
if let Some(token) = justify_token {
out.push(("justify-self", Arc::from(token)));
}
Some(out)
}
_ => None,
}
}
pub(crate) fn is_alignment_longhand(name: &str) -> bool {
matches!(
name,
"align-items" | "align-self" | "align-content" | "justify-content"
)
}
pub(crate) fn alignment_longhand_valid(name: &str, value: &str) -> bool {
let value = value.trim();
match name {
"align-items" => parse_align_items(value).is_some(),
"align-self" => value.eq_ignore_ascii_case("auto") || parse_align_items(value).is_some(),
"align-content" => parse_align_content(value).is_some(),
"justify-content" => parse_justify_content(value).is_some(),
_ => true,
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FlexBasis {
Auto,
Content,
Length(Length),
}
#[doc(hidden)]
pub fn parse_flex_basis(s: &str) -> Option<FlexBasis> {
let trimmed = s.trim();
if trimmed.eq_ignore_ascii_case("auto") {
return Some(FlexBasis::Auto);
}
if trimmed.eq_ignore_ascii_case("content") {
return Some(FlexBasis::Content);
}
Length::parse(trimmed).map(FlexBasis::Length)
}
pub fn resolve_flex_basis(
basis: FlexBasis,
containing_main: Option<usize>,
font_ctx: FontContext,
viewport: Viewport,
) -> Option<usize> {
match basis {
FlexBasis::Auto | FlexBasis::Content => None,
FlexBasis::Length(Length::Pct(_)) if containing_main.is_none() => None,
FlexBasis::Length(len) => Some(len.resolve(LengthContext {
containing: containing_main.unwrap_or(0),
font: font_ctx,
viewport,
})),
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FlexShorthand {
pub grow: f32,
pub shrink: f32,
pub basis: FlexBasis,
}
#[doc(hidden)]
pub fn parse_flex_shorthand(s: &str) -> Option<FlexShorthand> {
let trimmed = s.trim();
if trimmed.eq_ignore_ascii_case("none") {
return Some(FlexShorthand {
grow: 0.0,
shrink: 0.0,
basis: FlexBasis::Auto,
});
}
let tokens: Vec<&str> = trimmed.split_whitespace().collect();
if tokens.is_empty() || tokens.len() > 3 {
return None;
}
let zero_basis = FlexBasis::Length(Length::Px(0.0));
match tokens.as_slice() {
[t] => {
if let Some(g) = parse_non_negative_number(t) {
Some(FlexShorthand {
grow: g,
shrink: 1.0,
basis: zero_basis,
})
} else {
Some(FlexShorthand {
grow: 1.0,
shrink: 1.0,
basis: parse_flex_basis(t)?,
})
}
}
[a, b] => {
let na = parse_non_negative_number(a);
let nb = parse_non_negative_number(b);
match (na, nb) {
(Some(g), Some(sh)) => Some(FlexShorthand {
grow: g,
shrink: sh,
basis: zero_basis,
}),
(Some(g), None) => Some(FlexShorthand {
grow: g,
shrink: 1.0,
basis: parse_flex_basis(b)?,
}),
(None, Some(sh)) => Some(FlexShorthand {
grow: 1.0,
shrink: sh,
basis: parse_flex_basis(a)?,
}),
(None, None) => None,
}
}
[g, s, b] => Some(FlexShorthand {
grow: parse_non_negative_number(g)?,
shrink: parse_non_negative_number(s)?,
basis: parse_flex_basis(b)?,
}),
_ => None,
}
}
#[doc(hidden)]
pub fn parse_flex_flow(s: &str) -> Option<(FlexDirection, Option<FlexWrap>)> {
let mut dir: Option<FlexDirection> = None;
let mut wrap: Option<FlexWrap> = None;
for tok in s.split_whitespace() {
if let Some(d) = parse_flex_direction(tok) {
if dir.replace(d).is_some() {
return None;
}
continue;
}
let w = parse_flex_wrap(tok)?;
if wrap.replace(w).is_some() {
return None;
}
}
Some((dir.unwrap_or(FlexDirection::Row), wrap))
}
#[doc(hidden)]
pub fn parse_non_negative_number(s: &str) -> Option<f32> {
let trimmed = s.trim();
if trimmed.is_empty() {
return None;
}
let v: f32 = trimmed.parse().ok()?;
if v < 0.0 || !v.is_finite() {
return None;
}
Some(v)
}
#[doc(hidden)]
pub fn parse_order(s: &str) -> Option<i32> {
let trimmed = s.trim();
if trimmed.is_empty() {
return None;
}
let v: i32 = trimmed.parse().ok()?;
Some(v)
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct GapSizes {
pub row: usize,
pub column: usize,
}
#[doc(hidden)]
pub fn parse_gap_shorthand(s: &str, font_ctx: FontContext, viewport: Viewport) -> Option<GapSizes> {
fn resolve_gap(len: Length, font_ctx: FontContext, viewport: Viewport) -> usize {
match len {
Length::Pct(_) => 0,
len => len.resolve(LengthContext {
containing: 0,
font: font_ctx,
viewport,
}),
}
}
let mut parts = s.split_whitespace();
let first = parts.next()?;
let first_len = Length::parse(first);
let Some(second) = parts.next() else {
let v = first_len?;
return Some(GapSizes {
row: resolve_gap(v, font_ctx, viewport),
column: resolve_gap(v, font_ctx, viewport),
});
};
if parts.next().is_some() {
return None;
}
let second_len = Length::parse(second);
let row = first_len?;
let column = second_len?;
Some(GapSizes {
row: resolve_gap(row, font_ctx, viewport),
column: resolve_gap(column, font_ctx, viewport),
})
}