mod animation;
mod aspect_ratio;
mod background;
mod background_image;
mod background_position;
mod background_repeat;
mod background_size;
mod blend_mode;
mod border;
mod box_alignment;
mod box_shadow;
mod clip_path;
mod color;
mod conic_gradient;
mod content;
mod filter;
mod flex;
mod flex_grow;
mod font_family;
mod font_feature_settings;
mod font_size;
mod font_stretch;
mod font_style;
mod font_synthesis;
mod font_variation_settings;
mod font_weight;
mod gradient_utils;
mod grid;
mod length;
mod line_clamp;
mod line_height;
mod linear_gradient;
mod order;
mod overflow;
mod overflow_wrap;
mod percentage_number;
mod radial_gradient;
mod sides;
mod space_pair;
mod text_decoration;
mod text_fit;
mod text_indent;
mod text_overflow;
mod text_shadow;
mod text_stroke;
mod text_wrap;
mod transform;
mod vertical_align;
mod white_space;
mod word_break;
mod z_index;
pub use animation::*;
pub use aspect_ratio::*;
pub use background::*;
pub use background_image::*;
pub use background_position::*;
pub use background_repeat::*;
pub use background_size::*;
pub use blend_mode::*;
pub use border::*;
pub use box_alignment::*;
pub use box_shadow::*;
pub use clip_path::*;
pub use color::*;
pub use conic_gradient::*;
pub use content::*;
pub use filter::*;
pub use flex::*;
pub use flex_grow::*;
pub use font_family::*;
pub use font_feature_settings::*;
pub use font_size::*;
pub use font_stretch::*;
pub use font_style::*;
pub use font_synthesis::*;
pub use font_variation_settings::*;
pub use font_weight::*;
pub(crate) use gradient_utils::{
GradientOverlayTile, overlay_gradient_tile_fast_normal_unconstrained,
};
pub use grid::*;
pub use length::*;
pub use line_clamp::*;
pub use line_height::*;
pub use linear_gradient::*;
pub use order::*;
pub use overflow::*;
pub use overflow_wrap::*;
pub use percentage_number::*;
pub use radial_gradient::*;
use serde::Deserialize;
pub use sides::*;
pub use space_pair::*;
pub use text_decoration::*;
pub use text_fit::*;
pub use text_indent::*;
pub use text_overflow::*;
pub use text_shadow::*;
pub use text_stroke::*;
pub use text_wrap::*;
pub use transform::*;
pub use vertical_align::*;
pub use white_space::*;
pub use word_break::*;
pub use z_index::*;
use cssparser::{ParseError, Parser, ParserInput, match_ignore_ascii_case};
use image::imageops::FilterType;
use parley::Alignment;
use std::borrow::Cow;
use std::fmt;
use crate::{
layout::style::tw::TailwindPropertyParser,
rendering::{Join, Sizing},
};
pub type ParseResult<'i, T> = Result<T, ParseError<'i, Cow<'i, str>>>;
#[derive(Clone, Copy)]
#[non_exhaustive]
pub enum CssSyntaxKind {
Angle,
BorderStyle,
Clip,
Color,
CustomIdent,
EasingFunction,
FamilyName,
GenericName,
Ident,
Image,
Integer,
Length,
LineNames,
Number,
Percentage,
Position,
Repeat,
String,
Time,
TrackSize,
TransformFunction,
}
impl CssSyntaxKind {
const fn as_str(self) -> &'static str {
match self {
Self::Angle => "angle",
Self::BorderStyle => "border-style",
Self::Clip => "clip",
Self::Color => "color",
Self::CustomIdent => "custom-ident",
Self::EasingFunction => "easing-function",
Self::FamilyName => "family-name",
Self::GenericName => "generic-name",
Self::Ident => "ident",
Self::Image => "image",
Self::Integer => "integer",
Self::Length => "length",
Self::LineNames => "line-names",
Self::Number => "number",
Self::Percentage => "percentage",
Self::Position => "position",
Self::Repeat => "repeat",
Self::String => "string",
Self::Time => "time",
Self::TrackSize => "track-size",
Self::TransformFunction => "transform-function",
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy)]
pub enum CssDescriptorKind {
BlurFn,
BlendMode,
BrightnessFn,
CircleFn,
ColorAndPercentage,
ColorMixFn,
ConicGradientFn,
RepeatingConicGradientFn,
ContrastFn,
CubicBezierFn,
DropShadowFn,
EllipseFn,
GrayscaleFn,
HueRotateFn,
InColorSpace,
InsetFn,
InvertFn,
LinearGradientFn,
RepeatingLinearGradientFn,
MinmaxFn,
OpacityFn,
PathFn,
PolygonFn,
RadialGradientFn,
RepeatingRadialGradientFn,
RepeatFn,
SaturateFn,
SepiaFn,
StepsFn,
TextWrapMode,
TextWrapStyle,
UrlFn,
WhiteSpaceCollapse,
}
impl CssDescriptorKind {
const fn as_str(self) -> &'static str {
match self {
Self::BlurFn => "blur()",
Self::BlendMode => "blend-mode",
Self::BrightnessFn => "brightness()",
Self::CircleFn => "circle()",
Self::ColorAndPercentage => "color and percentage",
Self::ColorMixFn => "color-mix()",
Self::ConicGradientFn => "conic-gradient()",
Self::RepeatingConicGradientFn => "repeating-conic-gradient()",
Self::ContrastFn => "contrast()",
Self::CubicBezierFn => "cubic-bezier()",
Self::DropShadowFn => "drop-shadow()",
Self::EllipseFn => "ellipse()",
Self::GrayscaleFn => "grayscale()",
Self::HueRotateFn => "hue-rotate()",
Self::InColorSpace => "in <color-space>",
Self::InsetFn => "inset()",
Self::InvertFn => "invert()",
Self::LinearGradientFn => "linear-gradient()",
Self::RepeatingLinearGradientFn => "repeating-linear-gradient()",
Self::MinmaxFn => "minmax()",
Self::OpacityFn => "opacity()",
Self::PathFn => "path()",
Self::PolygonFn => "polygon()",
Self::RadialGradientFn => "radial-gradient()",
Self::RepeatingRadialGradientFn => "repeating-radial-gradient()",
Self::RepeatFn => "repeat()",
Self::SaturateFn => "saturate()",
Self::SepiaFn => "sepia()",
Self::StepsFn => "steps()",
Self::TextWrapMode => "text-wrap-mode",
Self::TextWrapStyle => "text-wrap-style",
Self::UrlFn => "url()",
Self::WhiteSpaceCollapse => "white-space-collapse",
}
}
}
#[non_exhaustive]
pub enum CssToken {
Keyword(&'static str),
Syntax(CssSyntaxKind),
Descriptor(CssDescriptorKind),
}
impl std::fmt::Display for CssToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CssToken::Keyword(keyword) => write!(f, "'{}'", keyword),
CssToken::Syntax(token) => write!(f, "<{}>", token.as_str()),
CssToken::Descriptor(token) => write!(f, "<{}>", token.as_str()),
}
}
}
#[non_exhaustive]
pub enum CssExpectedMessage {
ValueOrNone,
OneValue,
OneOrTwoValues,
OneToFourValues,
BorderRadius,
}
impl CssExpectedMessage {
pub(crate) fn build_message(&self, token: &str, valid_tokens: String) -> String {
match self {
Self::ValueOrNone => {
format!("Unexpected token: {token}, expected a value of {valid_tokens} or 'none'")
}
Self::OneValue => format!("Unexpected token: {token}, expected a value of {valid_tokens}"),
Self::OneOrTwoValues => {
format!("Unexpected token: {token}, expected 1 ~ 2 values of {valid_tokens}")
}
Self::OneToFourValues => {
format!("Unexpected token: {token}, expected 1 ~ 4 values of {valid_tokens}")
}
Self::BorderRadius => format!(
"Unexpected token: {token}, expected 1 to 4 length values for width, optionally followed by '/' and 1 to 4 length values for height"
),
}
}
}
pub trait FromCss<'i> {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self>
where
Self: Sized;
fn from_str(source: &'i str) -> ParseResult<'i, Self>
where
Self: Sized,
{
let mut input = ParserInput::new(source);
let mut parser = Parser::new(&mut input);
Self::from_css(&mut parser)
}
const VALID_TOKENS: &'static [CssToken];
const EXPECT_MESSAGE: CssExpectedMessage = CssExpectedMessage::OneValue;
}
impl<'i, T: FromCss<'i>> FromCss<'i> for Option<T> {
const VALID_TOKENS: &'static [CssToken] = T::VALID_TOKENS;
const EXPECT_MESSAGE: CssExpectedMessage = CssExpectedMessage::ValueOrNone;
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(None);
}
T::from_css(input).map(Some)
}
}
impl<'i> FromCss<'i> for String {
const VALID_TOKENS: &'static [CssToken] = &[
CssToken::Syntax(CssSyntaxKind::String),
CssToken::Syntax(CssSyntaxKind::CustomIdent),
];
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
Ok(input.expect_ident_or_string()?.to_string())
}
}
pub(crate) trait MakeComputed {
fn make_computed(&mut self, _sizing: &Sizing) {}
}
pub(crate) trait Animatable: Sized + Clone {
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
_sizing: &Sizing,
_current_color: Color,
) {
*self = if progress >= 1.0 {
to.clone()
} else {
from.clone()
};
}
fn list_interpolation_strategy() -> ListInterpolationStrategy {
ListInterpolationStrategy::Discrete
}
fn neutral_value_like(_other: &Self) -> Option<Self> {
None
}
fn missing_value() -> Option<Self> {
None
}
}
pub(crate) fn lerp(lhs: f32, rhs: f32, progress: f32) -> f32 {
lhs + (rhs - lhs) * progress
}
pub(crate) enum ListInterpolationStrategy {
Discrete,
RepeatToLcm,
PadToLongestWithNeutral,
}
impl<T: MakeComputed> MakeComputed for Option<T> {
fn make_computed(&mut self, sizing: &Sizing) {
if let Some(value) = self.as_mut() {
value.make_computed(sizing);
}
}
}
impl<T: MakeComputed> MakeComputed for Box<[T]> {
fn make_computed(&mut self, sizing: &Sizing) {
for value in self.iter_mut() {
value.make_computed(sizing);
}
}
}
impl<T: MakeComputed> MakeComputed for Vec<T> {
fn make_computed(&mut self, sizing: &Sizing) {
for value in self.iter_mut() {
value.make_computed(sizing);
}
}
}
pub(crate) fn next_is_comma<'i>(input: &mut Parser<'i, '_>) -> bool {
let state = input.state();
let is_comma = input.expect_comma().is_ok();
input.reset(&state);
is_comma
}
impl<T: Animatable + Clone> Animatable for Option<T> {
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
*self = match (from, to) {
(Some(from), Some(to)) => {
let mut value = from.clone();
value.interpolate(from, to, progress, sizing, current_color);
Some(value)
}
(Some(from), None) => T::missing_value().map_or_else(
|| {
if progress >= 0.5 {
None
} else {
Some(from.clone())
}
},
|missing| {
let mut value = from.clone();
value.interpolate(from, &missing, progress, sizing, current_color);
Some(value)
},
),
(None, Some(to)) => T::missing_value().map_or_else(
|| {
if progress >= 0.5 {
Some(to.clone())
} else {
None
}
},
|missing| {
let mut value = missing.clone();
value.interpolate(&missing, to, progress, sizing, current_color);
Some(value)
},
),
(None, None) => None,
};
}
}
impl<T: Animatable + Clone> Animatable for Box<[T]> {
fn missing_value() -> Option<Self> {
match T::list_interpolation_strategy() {
ListInterpolationStrategy::Discrete => None,
ListInterpolationStrategy::RepeatToLcm
| ListInterpolationStrategy::PadToLongestWithNeutral => Some(Box::default()),
}
}
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
*self = interpolate_list(
from,
to,
progress,
sizing,
current_color,
Vec::into_boxed_slice,
)
.unwrap_or_else(|| {
if progress >= 1.0 {
to.clone()
} else {
from.clone()
}
});
}
}
impl<T: Animatable + Clone> Animatable for Vec<T> {
fn missing_value() -> Option<Self> {
match T::list_interpolation_strategy() {
ListInterpolationStrategy::Discrete => None,
ListInterpolationStrategy::RepeatToLcm
| ListInterpolationStrategy::PadToLongestWithNeutral => Some(Vec::new()),
}
}
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
*self = interpolate_list(from, to, progress, sizing, current_color, |values| values)
.unwrap_or_else(|| {
if progress >= 1.0 {
to.clone()
} else {
from.clone()
}
});
}
}
fn interpolate_list<T: Animatable + Clone, C: AsRef<[T]>, O>(
from: &C,
to: &C,
progress: f32,
sizing: &Sizing,
current_color: Color,
build: impl FnOnce(Vec<T>) -> O,
) -> Option<O> {
let from = from.as_ref();
let to = to.as_ref();
let values = match T::list_interpolation_strategy() {
ListInterpolationStrategy::Discrete => {
if from.len() != to.len() {
return None;
}
interpolate_pairwise_list(from, to, from.len(), progress, sizing, current_color)
}
ListInterpolationStrategy::RepeatToLcm => {
if from.is_empty() || to.is_empty() {
return None;
}
interpolate_pairwise_list(
from,
to,
lcm(from.len(), to.len()),
progress,
sizing,
current_color,
)
}
ListInterpolationStrategy::PadToLongestWithNeutral => {
interpolate_neutral_padded_list(from, to, progress, sizing, current_color)?
}
};
Some(build(values))
}
fn interpolate_pairwise_list<T: Animatable + Clone>(
from: &[T],
to: &[T],
output_len: usize,
progress: f32,
sizing: &Sizing,
current_color: Color,
) -> Vec<T> {
(0..output_len)
.map(|index| {
let from_value = &from[index % from.len()];
let to_value = &to[index % to.len()];
let mut value = from_value.clone();
value.interpolate(from_value, to_value, progress, sizing, current_color);
value
})
.collect()
}
fn interpolate_neutral_padded_list<T: Animatable + Clone>(
from: &[T],
to: &[T],
progress: f32,
sizing: &Sizing,
current_color: Color,
) -> Option<Vec<T>> {
let output_len = from.len().max(to.len());
(0..output_len)
.map(|index| {
let from_value = if index < from.len() {
from.get(index).cloned()
} else {
to.get(index).and_then(T::neutral_value_like)
}?;
let to_value = if index < to.len() {
to.get(index).cloned()
} else {
from.get(index).and_then(T::neutral_value_like)
}?;
let mut value = from_value.clone();
value.interpolate(&from_value, &to_value, progress, sizing, current_color);
Some(value)
})
.collect()
}
fn gcd(lhs: usize, rhs: usize) -> usize {
let mut lhs = lhs;
let mut rhs = rhs;
while rhs != 0 {
let remainder = lhs % rhs;
lhs = rhs;
rhs = remainder;
}
lhs
}
fn lcm(lhs: usize, rhs: usize) -> usize {
lhs / gcd(lhs, rhs) * rhs
}
impl<T: Animatable + Copy> Animatable for SpacePair<T> {
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
self
.x
.interpolate(&from.x, &to.x, progress, sizing, current_color);
self
.y
.interpolate(&from.y, &to.y, progress, sizing, current_color);
}
}
impl<T: Animatable + Copy> Animatable for Sides<T> {
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
for (index, value) in self.0.iter_mut().enumerate() {
value.interpolate(
&from.0[index],
&to.0[index],
progress,
sizing,
current_color,
);
}
}
}
macro_rules! unexpected_token {
(@build $type:ty, $location:expr, $token:expr $(,)?) => {{
let location = $location;
let token = $token;
let token = cssparser::ToCss::to_css_string(token);
let message = <$type as $crate::layout::style::FromCss>::EXPECT_MESSAGE
.build_message(&token, $crate::layout::style::merge_enum_values(<$type as $crate::layout::style::FromCss>::VALID_TOKENS));
cssparser::ParseError {
location,
kind: cssparser::ParseErrorKind::Custom(std::borrow::Cow::Owned(message)),
}
}};
($type:ty, $location:expr, $token:expr $(,)?) => {
$crate::layout::style::unexpected_token!(@build $type, $location, $token)
};
($location:expr, $token:expr $(,)?) => {
$crate::layout::style::unexpected_token!(@build Self, $location, $token)
};
}
pub(crate) use unexpected_token;
pub(crate) fn merge_enum_values(values: &[CssToken]) -> String {
match values.len() {
0 => String::new(),
1 => values[0].to_string(),
2 => format!("{} or {}", values[0], values[1]),
_ => {
let all_but_last = values[..values.len() - 1]
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
format!("{} or {}", all_but_last, values[values.len() - 1])
}
}
}
pub trait ToCss {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result;
}
impl<T: ToCss + ?Sized> ToCss for &T {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
(*self).to_css(dest)
}
}
impl<T: ToCss> ToCss for Option<T> {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
match self {
Some(v) => v.to_css(dest),
None => dest.write_str("none"),
}
}
}
impl<T: ToCss> ToCss for Box<[T]> {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
for (i, item) in self.iter().enumerate() {
if i > 0 {
dest.write_str(", ")?;
}
item.to_css(dest)?;
}
Ok(())
}
}
impl<T: ToCss> ToCss for Vec<T> {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
for (i, item) in self.iter().enumerate() {
if i > 0 {
dest.write_str(", ")?;
}
item.to_css(dest)?;
}
Ok(())
}
}
impl ToCss for f32 {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
write!(dest, "{}", self)
}
}
impl ToCss for u32 {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
write!(dest, "{}", self)
}
}
impl ToCss for i32 {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
write!(dest, "{}", self)
}
}
impl ToCss for String {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
dest.write_str(self)
}
}
impl ToCss for std::sync::Arc<str> {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
dest.write_str(self)
}
}
pub(crate) fn write_css_string<W: fmt::Write>(dest: &mut W, s: &str) -> fmt::Result {
dest.write_char('"')?;
for ch in s.chars() {
match ch {
'\\' => dest.write_str("\\\\")?,
'"' => dest.write_str("\\\"")?,
c => dest.write_char(c)?,
}
}
dest.write_char('"')
}
macro_rules! impl_from_taffy_enum {
($from_ty:ty, $to_ty:ty, $($variant:ident),*) => {
impl From<$from_ty> for $to_ty {
fn from(value: $from_ty) -> Self {
match value {
$(<$from_ty>::$variant => <$to_ty>::$variant,)*
}
}
}
};
}
macro_rules! declare_enum_from_css_impl {
(
$enum_type:ty,
$($css_value:expr => $variant:path),* $(,)?
) => {
impl crate::layout::style::MakeComputed for $enum_type {}
impl<'i> crate::layout::style::FromCss<'i> for $enum_type {
const VALID_TOKENS: &'static [crate::layout::style::CssToken] =
&[$(crate::layout::style::CssToken::Keyword($css_value)),*];
fn from_css(input: &mut cssparser::Parser<'i, '_>) -> crate::layout::style::ParseResult<'i, Self> {
let location = input.current_source_location();
let token = input.next()?;
let cssparser::Token::Ident(ident) = token else {
return Err($crate::layout::style::unexpected_token!(location, token));
};
cssparser::match_ignore_ascii_case! {&ident,
$(
$css_value => Ok($variant),
)*
_ => Err($crate::layout::style::unexpected_token!(location, token)),
}
}
}
impl crate::layout::style::properties::ToCss for $enum_type {
fn to_css<W: std::fmt::Write>(&self, dest: &mut W) -> std::fmt::Result {
match self {
$(
$variant => dest.write_str($css_value),
)*
}
}
}
};
}
pub(crate) use declare_enum_from_css_impl;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum ObjectFit {
#[default]
Fill,
Contain,
Cover,
ScaleDown,
None,
}
declare_enum_from_css_impl!(
ObjectFit,
"fill" => ObjectFit::Fill,
"contain" => ObjectFit::Contain,
"cover" => ObjectFit::Cover,
"scale-down" => ObjectFit::ScaleDown,
"none" => ObjectFit::None
);
impl TailwindPropertyParser for ObjectFit {
fn parse_tw(token: &str) -> Option<Self> {
Self::from_str(token).ok()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum BackgroundClip {
#[default]
BorderBox,
PaddingBox,
ContentBox,
Text,
BorderArea,
}
declare_enum_from_css_impl!(
BackgroundClip,
"border-box" => BackgroundClip::BorderBox,
"padding-box" => BackgroundClip::PaddingBox,
"content-box" => BackgroundClip::ContentBox,
"text" => BackgroundClip::Text,
"border-area" => BackgroundClip::BorderArea
);
impl TailwindPropertyParser for BackgroundClip {
fn parse_tw(token: &str) -> Option<Self> {
match_ignore_ascii_case! {token,
"border" => Some(BackgroundClip::BorderBox),
"padding" => Some(BackgroundClip::PaddingBox),
"content" => Some(BackgroundClip::ContentBox),
"text" => Some(BackgroundClip::Text),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct BorderRadius(pub Sides<SpacePair<LengthDefaultsToZero>>);
impl From<f32> for BorderRadius {
fn from(value: f32) -> Self {
Self(Sides(
[SpacePair::from_pair(Length::Px(value), Length::Px(value)); 4],
))
}
}
impl MakeComputed for BorderRadius {
fn make_computed(&mut self, sizing: &Sizing) {
self.0.make_computed(sizing);
}
}
impl Animatable for BorderRadius {
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
self
.0
.interpolate(&from.0, &to.0, progress, sizing, current_color);
}
}
impl<'i> FromCss<'i> for BorderRadius {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
let widths: Sides<LengthDefaultsToZero> = Sides::from_css(input)?;
let heights = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
Sides::from_css(input)?
} else {
widths
};
Ok(BorderRadius(Sides([
SpacePair::from_pair(widths.0[0], heights.0[0]),
SpacePair::from_pair(widths.0[1], heights.0[1]),
SpacePair::from_pair(widths.0[2], heights.0[2]),
SpacePair::from_pair(widths.0[3], heights.0[3]),
])))
}
const EXPECT_MESSAGE: CssExpectedMessage = CssExpectedMessage::BorderRadius;
const VALID_TOKENS: &'static [CssToken] = &[CssToken::Syntax(CssSyntaxKind::Length)];
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum BoxSizing {
ContentBox,
#[default]
BorderBox,
}
declare_enum_from_css_impl!(
BoxSizing,
"content-box" => BoxSizing::ContentBox,
"border-box" => BoxSizing::BorderBox
);
impl_from_taffy_enum!(BoxSizing, taffy::BoxSizing, ContentBox, BorderBox);
#[derive(Default, Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum TextAlign {
Left,
Right,
Center,
Justify,
#[default]
Start,
End,
}
declare_enum_from_css_impl!(
TextAlign,
"left" => TextAlign::Left,
"right" => TextAlign::Right,
"center" => TextAlign::Center,
"justify" => TextAlign::Justify,
"start" => TextAlign::Start,
"end" => TextAlign::End
);
impl TailwindPropertyParser for TextAlign {
fn parse_tw(token: &str) -> Option<Self> {
Self::from_str(token).ok()
}
}
impl_from_taffy_enum!(
TextAlign, Alignment, Left, Right, Center, Justify, Start, End
);
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum Isolation {
Isolate,
#[default]
Auto,
}
declare_enum_from_css_impl!(
Isolation,
"isolate" => Isolation::Isolate,
"auto" => Isolation::Auto
);
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum Visibility {
#[default]
Visible,
Hidden,
}
declare_enum_from_css_impl!(
Visibility,
"visible" => Visibility::Visible,
"hidden" => Visibility::Hidden
);
#[derive(Default, Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum LineJoin {
#[default]
Miter,
Round,
Bevel,
}
declare_enum_from_css_impl!(
LineJoin,
"miter" => LineJoin::Miter,
"round" => LineJoin::Round,
"bevel" => LineJoin::Bevel
);
impl From<LineJoin> for Join {
fn from(value: LineJoin) -> Self {
match value {
LineJoin::Miter => Join::Miter,
LineJoin::Round => Join::Round,
LineJoin::Bevel => Join::Bevel,
}
}
}
impl TailwindPropertyParser for LineJoin {
fn parse_tw(token: &str) -> Option<Self> {
Self::from_str(token).ok()
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum Position {
#[default]
Relative,
Absolute,
Static,
Fixed,
}
declare_enum_from_css_impl!(
Position,
"relative" => Position::Relative,
"absolute" => Position::Absolute,
"static" => Position::Static,
"fixed" => Position::Fixed
);
impl From<Position> for taffy::Position {
fn from(value: Position) -> Self {
match value {
Position::Relative | Position::Static => Self::Relative,
Position::Absolute | Position::Fixed => Self::Absolute,
}
}
}
impl Position {
pub(crate) const fn is_positioned(self) -> bool {
matches!(self, Self::Relative | Self::Absolute | Self::Fixed)
}
pub(crate) const fn is_out_of_flow(self) -> bool {
matches!(self, Self::Absolute | Self::Fixed)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Direction {
#[default]
Ltr,
Rtl,
}
declare_enum_from_css_impl!(
Direction,
"ltr" => Direction::Ltr,
"rtl" => Direction::Rtl
);
impl_from_taffy_enum!(Direction, taffy::Direction, Ltr, Rtl);
#[derive(Default, Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum Float {
#[default]
None,
Left,
Right,
InlineStart,
InlineEnd,
}
declare_enum_from_css_impl!(
Float,
"none" => Float::None,
"left" => Float::Left,
"right" => Float::Right,
"inline-start" => Float::InlineStart,
"inline-end" => Float::InlineEnd,
);
impl Float {
pub fn resolve(self, direction: Direction) -> taffy::Float {
match self {
Self::None => taffy::Float::None,
Self::Left => taffy::Float::Left,
Self::Right => taffy::Float::Right,
Self::InlineStart => {
if direction == Direction::Rtl {
taffy::Float::Right
} else {
taffy::Float::Left
}
}
Self::InlineEnd => {
if direction == Direction::Rtl {
taffy::Float::Left
} else {
taffy::Float::Right
}
}
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum Clear {
#[default]
None,
Left,
Right,
Both,
InlineStart,
InlineEnd,
}
declare_enum_from_css_impl!(
Clear,
"none" => Clear::None,
"left" => Clear::Left,
"right" => Clear::Right,
"both" => Clear::Both,
"inline-start" => Clear::InlineStart,
"inline-end" => Clear::InlineEnd,
);
impl Clear {
pub fn resolve(self, direction: Direction) -> taffy::Clear {
match self {
Self::None => taffy::Clear::None,
Self::Left => taffy::Clear::Left,
Self::Right => taffy::Clear::Right,
Self::Both => taffy::Clear::Both,
Self::InlineStart => {
if direction == Direction::Rtl {
taffy::Clear::Right
} else {
taffy::Clear::Left
}
}
Self::InlineEnd => {
if direction == Direction::Rtl {
taffy::Clear::Left
} else {
taffy::Clear::Right
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum FlexDirection {
#[default]
Row,
Column,
RowReverse,
ColumnReverse,
}
declare_enum_from_css_impl!(
FlexDirection,
"row" => FlexDirection::Row,
"column" => FlexDirection::Column,
"row-reverse" => FlexDirection::RowReverse,
"column-reverse" => FlexDirection::ColumnReverse
);
impl_from_taffy_enum!(
FlexDirection,
taffy::FlexDirection,
Row,
Column,
RowReverse,
ColumnReverse
);
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum JustifyContent {
#[default]
Normal,
Start,
End,
FlexStart,
FlexEnd,
Center,
Stretch,
SpaceBetween,
SpaceEvenly,
SpaceAround,
}
declare_enum_from_css_impl!(
JustifyContent,
"normal" => JustifyContent::Normal,
"start" => JustifyContent::Start,
"end" => JustifyContent::End,
"flex-start" => JustifyContent::FlexStart,
"flex-end" => JustifyContent::FlexEnd,
"center" => JustifyContent::Center,
"stretch" => JustifyContent::Stretch,
"space-between" => JustifyContent::SpaceBetween,
"space-around" => JustifyContent::SpaceAround,
"space-evenly" => JustifyContent::SpaceEvenly
);
impl TailwindPropertyParser for JustifyContent {
fn parse_tw(token: &str) -> Option<Self> {
match token {
"between" => Some(JustifyContent::SpaceBetween),
"around" => Some(JustifyContent::SpaceAround),
"evenly" => Some(JustifyContent::SpaceEvenly),
_ => Self::from_str(token).ok(),
}
}
}
impl From<JustifyContent> for Option<taffy::JustifyContent> {
fn from(value: JustifyContent) -> Self {
match value {
JustifyContent::Normal => None,
JustifyContent::Start => Some(taffy::JustifyContent::Start),
JustifyContent::End => Some(taffy::JustifyContent::End),
JustifyContent::FlexStart => Some(taffy::JustifyContent::FlexStart),
JustifyContent::FlexEnd => Some(taffy::JustifyContent::FlexEnd),
JustifyContent::Center => Some(taffy::JustifyContent::Center),
JustifyContent::Stretch => Some(taffy::JustifyContent::Stretch),
JustifyContent::SpaceBetween => Some(taffy::JustifyContent::SpaceBetween),
JustifyContent::SpaceAround => Some(taffy::JustifyContent::SpaceAround),
JustifyContent::SpaceEvenly => Some(taffy::JustifyContent::SpaceEvenly),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum Display {
None,
Flex,
InlineFlex,
Grid,
InlineGrid,
#[default]
Inline,
Block,
InlineBlock,
}
declare_enum_from_css_impl!(
Display,
"none" => Display::None,
"flex" => Display::Flex,
"inline-flex" => Display::InlineFlex,
"grid" => Display::Grid,
"inline-grid" => Display::InlineGrid,
"inline" => Display::Inline,
"block" => Display::Block,
"inline-block" => Display::InlineBlock
);
impl Display {
pub fn is_inline(&self) -> bool {
*self == Display::Inline
}
pub fn is_inline_level(&self) -> bool {
matches!(
self,
Display::Inline | Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
)
}
pub fn should_blockify_children(&self) -> bool {
matches!(
self,
Display::Flex | Display::InlineFlex | Display::Grid | Display::InlineGrid
)
}
pub fn as_blockified(self) -> Self {
match self {
Display::Inline => Display::Block,
Display::InlineBlock => Display::Block,
Display::InlineFlex => Display::Flex,
Display::InlineGrid => Display::Grid,
_ => self,
}
}
pub fn blockify(&mut self) {
*self = self.as_blockified();
}
}
impl From<Display> for taffy::Display {
fn from(value: Display) -> Self {
match value {
Display::Flex | Display::InlineFlex => taffy::Display::Flex,
Display::Grid | Display::InlineGrid => taffy::Display::Grid,
Display::Block | Display::InlineBlock | Display::Inline => taffy::Display::Block,
Display::None => taffy::Display::None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum AlignItems {
#[default]
Normal,
Start,
End,
FlexStart,
FlexEnd,
Center,
Baseline,
Stretch,
}
declare_enum_from_css_impl!(
AlignItems,
"normal" => AlignItems::Normal,
"start" => AlignItems::Start,
"end" => AlignItems::End,
"flex-start" => AlignItems::FlexStart,
"flex-end" => AlignItems::FlexEnd,
"center" => AlignItems::Center,
"baseline" => AlignItems::Baseline,
"stretch" => AlignItems::Stretch
);
impl TailwindPropertyParser for AlignItems {
fn parse_tw(token: &str) -> Option<Self> {
Self::from_str(token).ok()
}
}
impl From<AlignItems> for Option<taffy::AlignItems> {
fn from(value: AlignItems) -> Self {
match value {
AlignItems::Normal => None,
AlignItems::Start => Some(taffy::AlignItems::Start),
AlignItems::End => Some(taffy::AlignItems::End),
AlignItems::FlexStart => Some(taffy::AlignItems::FlexStart),
AlignItems::FlexEnd => Some(taffy::AlignItems::FlexEnd),
AlignItems::Center => Some(taffy::AlignItems::Center),
AlignItems::Baseline => Some(taffy::AlignItems::Baseline),
AlignItems::Stretch => Some(taffy::AlignItems::Stretch),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum FlexWrap {
#[default]
NoWrap,
Wrap,
WrapReverse,
}
declare_enum_from_css_impl!(
FlexWrap,
"nowrap" => FlexWrap::NoWrap,
"wrap" => FlexWrap::Wrap,
"wrap-reverse" => FlexWrap::WrapReverse
);
impl_from_taffy_enum!(FlexWrap, taffy::FlexWrap, NoWrap, Wrap, WrapReverse);
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum TextTransform {
#[default]
None,
Uppercase,
Lowercase,
Capitalize,
}
declare_enum_from_css_impl!(
TextTransform,
"none" => TextTransform::None,
"uppercase" => TextTransform::Uppercase,
"lowercase" => TextTransform::Lowercase,
"capitalize" => TextTransform::Capitalize
);
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum TextDecorationSkipInk {
#[default]
Auto,
None,
}
declare_enum_from_css_impl!(
TextDecorationSkipInk,
"auto" => TextDecorationSkipInk::Auto,
"none" => TextDecorationSkipInk::None
);
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum WhiteSpaceCollapse {
Preserve,
#[default]
Collapse,
PreserveSpaces,
PreserveBreaks,
}
declare_enum_from_css_impl!(
WhiteSpaceCollapse,
"preserve" => WhiteSpaceCollapse::Preserve,
"collapse" => WhiteSpaceCollapse::Collapse,
"preserve-spaces" => WhiteSpaceCollapse::PreserveSpaces,
"preserve-breaks" => WhiteSpaceCollapse::PreserveBreaks,
);
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum ImageScalingAlgorithm {
#[default]
Auto,
Smooth,
Pixelated,
}
declare_enum_from_css_impl!(
ImageScalingAlgorithm,
"auto" => ImageScalingAlgorithm::Auto,
"smooth" => ImageScalingAlgorithm::Smooth,
"pixelated" => ImageScalingAlgorithm::Pixelated
);
#[cfg(feature = "svg")]
impl From<ImageScalingAlgorithm> for resvg::usvg::ImageRendering {
fn from(algorithm: ImageScalingAlgorithm) -> Self {
match algorithm {
ImageScalingAlgorithm::Auto => resvg::usvg::ImageRendering::default(),
ImageScalingAlgorithm::Smooth => resvg::usvg::ImageRendering::Smooth,
ImageScalingAlgorithm::Pixelated => resvg::usvg::ImageRendering::Pixelated,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum BorderStyle {
#[default]
None,
Hidden,
Dotted,
Dashed,
Solid,
Double,
Groove,
Ridge,
Inset,
Outset,
}
impl BorderStyle {
pub const fn is_rendered(self) -> bool {
!matches!(self, Self::None | Self::Hidden)
}
}
declare_enum_from_css_impl!(
BorderStyle,
"none" => BorderStyle::None,
"hidden" => BorderStyle::Hidden,
"dotted" => BorderStyle::Dotted,
"dashed" => BorderStyle::Dashed,
"solid" => BorderStyle::Solid,
"double" => BorderStyle::Double,
"groove" => BorderStyle::Groove,
"ridge" => BorderStyle::Ridge,
"inset" => BorderStyle::Inset,
"outset" => BorderStyle::Outset,
);
impl TailwindPropertyParser for BorderStyle {
fn parse_tw(token: &str) -> Option<Self> {
Self::from_str(token).ok()
}
}
impl From<ImageScalingAlgorithm> for FilterType {
fn from(algorithm: ImageScalingAlgorithm) -> Self {
match algorithm {
ImageScalingAlgorithm::Auto => FilterType::CatmullRom,
ImageScalingAlgorithm::Smooth => FilterType::Lanczos3,
ImageScalingAlgorithm::Pixelated => FilterType::Nearest,
}
}
}