Skip to main content

takumi_css/style/properties/
mod.rs

1//! Style properties and related types for the takumi styling system.
2//!
3//! This module contains CSS-like properties including layout properties,
4//! typography settings, positioning, and visual effects.
5
6mod animation;
7mod aspect_ratio;
8mod background;
9mod background_image;
10mod background_position;
11mod background_repeat;
12mod background_size;
13mod background_size_resolve;
14mod blend_mode;
15mod border;
16mod box_alignment;
17mod box_shadow;
18mod clip_path;
19mod color;
20mod conic_gradient;
21mod content;
22mod filter;
23mod flex;
24mod flex_grow;
25mod font_family;
26mod font_feature_settings;
27mod font_size;
28mod font_stretch;
29mod font_style;
30mod font_synthesis;
31mod font_variation_settings;
32mod font_weight;
33mod gradient_utils;
34mod grid;
35mod length;
36mod line_clamp;
37mod line_height;
38mod linear_gradient;
39mod order;
40mod overflow;
41mod overflow_wrap;
42mod percentage_number;
43mod radial_gradient;
44mod sides;
45mod space_pair;
46mod text_decoration;
47mod text_fit;
48mod text_indent;
49mod text_overflow;
50mod text_shadow;
51mod text_stroke;
52mod text_wrap;
53mod traits;
54mod transform;
55mod vertical_align;
56mod white_space;
57mod word_break;
58mod z_index;
59
60pub use animation::*;
61pub use aspect_ratio::*;
62pub use background::*;
63pub use background_image::*;
64pub use background_position::*;
65pub use background_repeat::*;
66pub use background_size::*;
67pub use background_size_resolve::*;
68pub use blend_mode::*;
69pub use border::*;
70pub use box_alignment::*;
71pub use box_shadow::*;
72pub use clip_path::*;
73pub use color::*;
74pub use conic_gradient::*;
75pub use content::*;
76pub use filter::*;
77pub use flex::*;
78pub use flex_grow::*;
79pub use font_family::*;
80pub use font_feature_settings::*;
81pub use font_size::*;
82pub use font_stretch::*;
83pub use font_style::*;
84pub use font_synthesis::*;
85pub use font_variation_settings::*;
86pub use font_weight::*;
87pub use gradient_utils::{GradientOverlayTile, overlay_gradient_tile_fast_normal_unconstrained};
88pub use grid::*;
89pub use length::*;
90pub use line_clamp::*;
91pub use line_height::*;
92pub use linear_gradient::*;
93pub use order::*;
94pub use overflow::*;
95pub use overflow_wrap::*;
96pub use percentage_number::*;
97pub use radial_gradient::*;
98use serde::Deserialize;
99pub use sides::*;
100pub use space_pair::*;
101pub use text_decoration::*;
102pub use text_fit::*;
103pub use text_indent::*;
104pub use text_overflow::*;
105pub use text_shadow::*;
106pub use text_stroke::*;
107pub use text_wrap::*;
108pub use traits::*;
109pub(crate) use traits::{
110  declare_box_alignment_enum_impl, declare_enum_from_css_impl, impl_from_taffy_enum,
111};
112pub use transform::*;
113pub use vertical_align::*;
114pub use white_space::*;
115pub use word_break::*;
116pub use z_index::*;
117
118use cssparser::{Parser, match_ignore_ascii_case};
119use image::imageops::FilterType;
120use parley::Alignment;
121use std::fmt;
122
123use crate::style::{SizingContext, tw::TailwindPropertyParser};
124
125pub(crate) fn next_is_comma<'i>(input: &mut Parser<'i, '_>) -> bool {
126  let state = input.state();
127  let is_comma = input.expect_comma().is_ok();
128  input.reset(&state);
129  is_comma
130}
131
132impl<T: Animatable + Copy> Animatable for SpacePair<T> {
133  fn interpolate(
134    &mut self,
135    from: &Self,
136    to: &Self,
137    progress: f32,
138    sizing: &SizingContext,
139    current_color: Color,
140  ) {
141    self
142      .x
143      .interpolate(&from.x, &to.x, progress, sizing, current_color);
144    self
145      .y
146      .interpolate(&from.y, &to.y, progress, sizing, current_color);
147  }
148}
149
150impl<T: Animatable + Copy> Animatable for Sides<T> {
151  fn interpolate(
152    &mut self,
153    from: &Self,
154    to: &Self,
155    progress: f32,
156    sizing: &SizingContext,
157    current_color: Color,
158  ) {
159    for (index, value) in self.0.iter_mut().enumerate() {
160      value.interpolate(
161        &from.0[index],
162        &to.0[index],
163        progress,
164        sizing,
165        current_color,
166      );
167    }
168  }
169}
170
171macro_rules! unexpected_token {
172  (@build $type:ty, $location:expr, $token:expr $(,)?) => {{
173    let location = $location;
174    let token = $token;
175    let token = cssparser::ToCss::to_css_string(token);
176    let message = <$type as $crate::style::FromCss>::EXPECT_MESSAGE
177      .build_message(&token, $crate::style::merge_enum_values(<$type as $crate::style::FromCss>::VALID_TOKENS));
178
179    cssparser::ParseError {
180      location,
181      kind: cssparser::ParseErrorKind::Custom(std::borrow::Cow::Owned(message)),
182    }
183  }};
184  ($type:ty, $location:expr, $token:expr $(,)?) => {
185    $crate::style::unexpected_token!(@build $type, $location, $token)
186  };
187  ($location:expr, $token:expr $(,)?) => {
188    $crate::style::unexpected_token!(@build Self, $location, $token)
189  };
190}
191
192pub(crate) use unexpected_token;
193
194/// Helper function to merge enum values into a human-readable format.
195/// - `["fill"]` → `"'fill'"`
196/// - `["fill", "contain"]` → `"'fill' or 'contain'"`
197/// - `["fill", "contain", "cover"]` → `"'fill', 'contain' or 'cover'"`
198pub(crate) fn merge_enum_values(values: &[CssToken]) -> String {
199  match values.len() {
200    0 => String::new(),
201    1 => values[0].to_string(),
202    2 => format!("{} or {}", values[0], values[1]),
203    _ => {
204      let all_but_last = values[..values.len() - 1]
205        .iter()
206        .map(ToString::to_string)
207        .collect::<Vec<_>>()
208        .join(", ");
209      format!("{} or {}", all_but_last, values[values.len() - 1])
210    }
211  }
212}
213
214/// Write a CSS quoted string, escaping backslashes and double quotes.
215pub(crate) fn write_css_string<W: fmt::Write>(dest: &mut W, s: &str) -> fmt::Result {
216  dest.write_char('"')?;
217  for ch in s.chars() {
218    match ch {
219      '\\' => dest.write_str("\\\\")?,
220      '"' => dest.write_str("\\\"")?,
221      c => dest.write_char(c)?,
222    }
223  }
224  dest.write_char('"')
225}
226
227/// Defines how an image should be resized to fit its container.
228///
229/// Similar to CSS object-fit property.
230#[derive(Debug, Clone, Copy, PartialEq, Default)]
231pub enum ObjectFit {
232  /// The replaced content is sized to fill the element's content box exactly, without maintaining aspect ratio
233  #[default]
234  Fill,
235  /// The replaced content is scaled to maintain its aspect ratio while fitting within the element's content box
236  Contain,
237  /// The replaced content is sized to maintain its aspect ratio while filling the element's entire content box
238  Cover,
239  /// The content is sized as if none or contain were specified, whichever would result in a smaller concrete object size
240  ScaleDown,
241  /// The replaced content is not resized and maintains its intrinsic dimensions
242  None,
243}
244
245declare_enum_from_css_impl!(
246  ObjectFit,
247  "fill" => ObjectFit::Fill,
248  "contain" => ObjectFit::Contain,
249  "cover" => ObjectFit::Cover,
250  "scale-down" => ObjectFit::ScaleDown,
251  "none" => ObjectFit::None
252);
253
254impl TailwindPropertyParser for ObjectFit {
255  fn parse_tw(token: &str) -> Option<Self> {
256    Self::from_str(token).ok()
257  }
258}
259
260/// Defines how the background is clipped.
261#[derive(Debug, Clone, Copy, PartialEq, Default)]
262#[non_exhaustive]
263pub enum BackgroundClip {
264  /// The background extends to the outside edge of the border
265  #[default]
266  BorderBox,
267  /// The background extends to the outside edge of the padding
268  PaddingBox,
269  /// The background extends to the inside edge of the content box
270  ContentBox,
271  /// The background extends to the outside edge of the text
272  Text,
273  /// The background extends to the outside edge of the border area
274  BorderArea,
275}
276
277declare_enum_from_css_impl!(
278  BackgroundClip,
279  "border-box" => BackgroundClip::BorderBox,
280  "padding-box" => BackgroundClip::PaddingBox,
281  "content-box" => BackgroundClip::ContentBox,
282  "text" => BackgroundClip::Text,
283  "border-area" => BackgroundClip::BorderArea
284);
285
286impl TailwindPropertyParser for BackgroundClip {
287  fn parse_tw(token: &str) -> Option<Self> {
288    match_ignore_ascii_case! {token,
289      "border" => Some(BackgroundClip::BorderBox),
290      "padding" => Some(BackgroundClip::PaddingBox),
291      "content" => Some(BackgroundClip::ContentBox),
292      "text" => Some(BackgroundClip::Text),
293      _ => None,
294    }
295  }
296}
297
298/// Represents the CSS `border-radius` property, supporting elliptical corners.
299///
300/// Each corner has independent horizontal and vertical radii, allowing for both circular and elliptical shapes.
301#[derive(Debug, Clone, Copy, PartialEq, Default)]
302pub struct BorderRadius(pub Sides<SpacePair<LengthDefaultsToZero>>);
303
304impl From<f32> for BorderRadius {
305  fn from(value: f32) -> Self {
306    Self(Sides(
307      [SpacePair::from_pair(Length::Px(value), Length::Px(value)); 4],
308    ))
309  }
310}
311
312impl MakeComputed for BorderRadius {
313  fn make_computed(&mut self, sizing: &SizingContext) {
314    self.0.make_computed(sizing);
315  }
316}
317
318impl Animatable for BorderRadius {
319  fn interpolate(
320    &mut self,
321    from: &Self,
322    to: &Self,
323    progress: f32,
324    sizing: &SizingContext,
325    current_color: Color,
326  ) {
327    self
328      .0
329      .interpolate(&from.0, &to.0, progress, sizing, current_color);
330  }
331}
332
333impl<'i> FromCss<'i> for BorderRadius {
334  fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
335    let widths: Sides<LengthDefaultsToZero> = Sides::from_css(input)?;
336
337    let heights = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
338      Sides::from_css(input)?
339    } else {
340      widths
341    };
342
343    Ok(BorderRadius(Sides([
344      SpacePair::from_pair(widths.0[0], heights.0[0]),
345      SpacePair::from_pair(widths.0[1], heights.0[1]),
346      SpacePair::from_pair(widths.0[2], heights.0[2]),
347      SpacePair::from_pair(widths.0[3], heights.0[3]),
348    ])))
349  }
350
351  const EXPECT_MESSAGE: CssExpectedMessage = CssExpectedMessage::BorderRadius;
352
353  const VALID_TOKENS: &'static [CssToken] = &[CssToken::Syntax(CssSyntaxKind::Length)];
354}
355
356/// Defines how the width and height of an element are calculated.
357///
358/// This enum determines whether the width and height properties include padding and border, or just the content area.
359#[derive(Default, Debug, Clone, Copy, PartialEq)]
360#[non_exhaustive]
361pub enum BoxSizing {
362  /// The width and height properties include padding and border, but not the content area
363  ContentBox,
364  /// The width and height properties include the content area, but not padding and border
365  #[default]
366  BorderBox,
367}
368
369declare_enum_from_css_impl!(
370  BoxSizing,
371  "content-box" => BoxSizing::ContentBox,
372  "border-box" => BoxSizing::BorderBox
373);
374
375impl_from_taffy_enum!(BoxSizing, taffy::BoxSizing, ContentBox, BorderBox);
376
377/// Text alignment options for text rendering.
378///
379/// Corresponds to CSS text-align property values.
380#[derive(Default, Debug, Clone, Copy, PartialEq)]
381#[non_exhaustive]
382pub enum TextAlign {
383  /// Aligns inline content to the left edge of the line box
384  Left,
385  /// Aligns inline content to the right edge of the line box
386  Right,
387  /// Centers inline content within the line box
388  Center,
389  /// Expands inline content to fill the entire line box
390  Justify,
391  /// Aligns inline content to the start edge of the line box (language-dependent)
392  #[default]
393  Start,
394  /// Aligns inline content to the end edge of the line box (language-dependent)
395  End,
396}
397
398declare_enum_from_css_impl!(
399  TextAlign,
400  "left" => TextAlign::Left,
401  "right" => TextAlign::Right,
402  "center" => TextAlign::Center,
403  "justify" => TextAlign::Justify,
404  "start" => TextAlign::Start,
405  "end" => TextAlign::End
406);
407
408impl TailwindPropertyParser for TextAlign {
409  fn parse_tw(token: &str) -> Option<Self> {
410    Self::from_str(token).ok()
411  }
412}
413
414impl_from_taffy_enum!(
415  TextAlign, Alignment, Left, Right, Center, Justify, Start, End
416);
417
418/// Defines whether an element creates a new stacking context.
419#[derive(Debug, Clone, Copy, PartialEq, Default)]
420#[non_exhaustive]
421pub enum Isolation {
422  /// The element creates a new stacking context.
423  Isolate,
424  /// Determine by other properties.
425  #[default]
426  Auto,
427}
428
429declare_enum_from_css_impl!(
430  Isolation,
431  "isolate" => Isolation::Isolate,
432  "auto" => Isolation::Auto
433);
434
435/// Defines whether an element is visible.
436///
437/// This controls whether an element is rendered, but unlike `display: none`,
438/// it still takes up space in the layout.
439#[derive(Debug, Clone, Copy, PartialEq, Default)]
440#[non_exhaustive]
441pub enum Visibility {
442  /// The element is visible.
443  #[default]
444  Visible,
445  /// The element is invisible (not rendered) but still takes up space.
446  Hidden,
447}
448
449declare_enum_from_css_impl!(
450  Visibility,
451  "visible" => Visibility::Visible,
452  "hidden" => Visibility::Hidden
453);
454
455/// Defines how the corners of text strokes are rendered.
456#[derive(Default, Debug, Clone, Copy, PartialEq)]
457pub enum LineJoin {
458  /// The corners are sharp and pointed.
459  #[default]
460  Miter,
461  /// The corners are rounded.
462  Round,
463  /// The corners are cut off at a 45-degree angle.
464  Bevel,
465}
466
467declare_enum_from_css_impl!(
468  LineJoin,
469  "miter" => LineJoin::Miter,
470  "round" => LineJoin::Round,
471  "bevel" => LineJoin::Bevel
472);
473
474impl TailwindPropertyParser for LineJoin {
475  fn parse_tw(token: &str) -> Option<Self> {
476    Self::from_str(token).ok()
477  }
478}
479
480/// Defines the positioning method for an element.
481///
482/// This enum determines how an element is positioned within its containing element.
483#[derive(Default, Debug, Clone, Copy, PartialEq)]
484pub enum Position {
485  /// The element is positioned according to the normal flow of the document.
486  /// Offsets (top, right, bottom, left) have no effect.
487  #[default]
488  Relative,
489  /// The element is removed from the normal document flow and positioned relative to its nearest positioned ancestor.
490  /// Offsets (top, right, bottom, left) specify the distance from the ancestor.
491  Absolute,
492  /// The element is laid out in the normal flow and is not a containing block.
493  /// Offsets (top, right, bottom, left) have no effect.
494  Static,
495  /// The element is removed from the normal document flow and positioned relative to the viewport (root).
496  Fixed,
497}
498
499declare_enum_from_css_impl!(
500  Position,
501  "relative" => Position::Relative,
502  "absolute" => Position::Absolute,
503  "static" => Position::Static,
504  "fixed" => Position::Fixed
505);
506
507impl From<Position> for taffy::Position {
508  fn from(value: Position) -> Self {
509    match value {
510      Position::Relative | Position::Static => Self::Relative,
511      Position::Absolute | Position::Fixed => Self::Absolute,
512    }
513  }
514}
515
516impl Position {
517  /// A positioned element (anything but `static`): establishes a containing
518  /// block for absolutely-positioned descendants and honors `z-index`.
519  pub const fn is_positioned(self) -> bool {
520    matches!(self, Self::Relative | Self::Absolute | Self::Fixed)
521  }
522
523  /// Removed from normal flow.
524  pub const fn is_out_of_flow(self) -> bool {
525    matches!(self, Self::Absolute | Self::Fixed)
526  }
527}
528
529/// Defines the direction of layout.
530#[derive(Default, Debug, Clone, Copy, PartialEq, Deserialize)]
531#[serde(rename_all = "kebab-case")]
532pub enum Direction {
533  /// The layout direction is left-to-right.
534  #[default]
535  Ltr,
536  /// The layout direction is right-to-left.
537  Rtl,
538}
539
540declare_enum_from_css_impl!(
541  Direction,
542  "ltr" => Direction::Ltr,
543  "rtl" => Direction::Rtl
544);
545
546impl_from_taffy_enum!(Direction, taffy::Direction, Ltr, Rtl);
547
548/// Defines whether an element should be placed along the left or right side of its container.
549#[derive(Default, Debug, Clone, Copy, PartialEq)]
550#[non_exhaustive]
551pub enum Float {
552  /// The element is not floated.
553  #[default]
554  None,
555  /// The element floats to the left.
556  Left,
557  /// The element floats to the right.
558  Right,
559  /// The element floats to the logical start side.
560  InlineStart,
561  /// The element floats to the logical end side.
562  InlineEnd,
563}
564
565declare_enum_from_css_impl!(
566  Float,
567  "none" => Float::None,
568  "left" => Float::Left,
569  "right" => Float::Right,
570  "inline-start" => Float::InlineStart,
571  "inline-end" => Float::InlineEnd,
572);
573
574impl Float {
575  /// Resolves the floating direction based on the layout direction.
576  pub fn resolve(self, direction: Direction) -> taffy::Float {
577    match self {
578      Self::None => taffy::Float::None,
579      Self::Left => taffy::Float::Left,
580      Self::Right => taffy::Float::Right,
581      Self::InlineStart => {
582        if direction == Direction::Rtl {
583          taffy::Float::Right
584        } else {
585          taffy::Float::Left
586        }
587      }
588      Self::InlineEnd => {
589        if direction == Direction::Rtl {
590          taffy::Float::Left
591        } else {
592          taffy::Float::Right
593        }
594      }
595    }
596  }
597}
598
599/// Defines whether an element must be moved below preceding floated elements.
600#[derive(Default, Debug, Clone, Copy, PartialEq)]
601#[non_exhaustive]
602pub enum Clear {
603  /// The element is not moved down.
604  #[default]
605  None,
606  /// The element is moved below left-floated elements.
607  Left,
608  /// The element is moved below right-floated elements.
609  Right,
610  /// The element is moved below both left- and right-floated elements.
611  Both,
612  /// The element is moved below logical start-floated elements.
613  InlineStart,
614  /// The element is moved below logical end-floated elements.
615  InlineEnd,
616}
617
618declare_enum_from_css_impl!(
619  Clear,
620  "none" => Clear::None,
621  "left" => Clear::Left,
622  "right" => Clear::Right,
623  "both" => Clear::Both,
624  "inline-start" => Clear::InlineStart,
625  "inline-end" => Clear::InlineEnd,
626);
627
628impl Clear {
629  /// Resolves the clearing direction based on the layout direction.
630  pub fn resolve(self, direction: Direction) -> taffy::Clear {
631    match self {
632      Self::None => taffy::Clear::None,
633      Self::Left => taffy::Clear::Left,
634      Self::Right => taffy::Clear::Right,
635      Self::Both => taffy::Clear::Both,
636      Self::InlineStart => {
637        if direction == Direction::Rtl {
638          taffy::Clear::Right
639        } else {
640          taffy::Clear::Left
641        }
642      }
643      Self::InlineEnd => {
644        if direction == Direction::Rtl {
645          taffy::Clear::Left
646        } else {
647          taffy::Clear::Right
648        }
649      }
650    }
651  }
652}
653
654/// Defines the direction of flex items within a flex container.
655///
656/// This enum determines how flex items are laid out along the main axis.
657#[derive(Debug, Clone, Copy, PartialEq, Default)]
658#[non_exhaustive]
659pub enum FlexDirection {
660  /// Items are laid out in the same direction as the text direction (left-to-right for English)
661  #[default]
662  Row,
663  /// Items are laid out perpendicular to the text direction (top-to-bottom)
664  Column,
665  /// Items are laid out in the opposite direction to the text direction (right-to-left for English)
666  RowReverse,
667  /// Items are laid out opposite to the column direction (bottom-to-top)
668  ColumnReverse,
669}
670
671declare_enum_from_css_impl!(
672  FlexDirection,
673  "row" => FlexDirection::Row,
674  "column" => FlexDirection::Column,
675  "row-reverse" => FlexDirection::RowReverse,
676  "column-reverse" => FlexDirection::ColumnReverse
677);
678
679impl_from_taffy_enum!(
680  FlexDirection,
681  taffy::FlexDirection,
682  Row,
683  Column,
684  RowReverse,
685  ColumnReverse
686);
687
688/// Defines how flex items are aligned along the main axis.
689///
690/// This enum determines how space is distributed between and around flex items
691/// along the main axis of the flex container.
692#[derive(Debug, Clone, Copy, PartialEq, Default)]
693#[non_exhaustive]
694pub enum JustifyContent {
695  /// The items are distributed using the normal flow of the flex container.
696  #[default]
697  Normal,
698  /// Items are packed toward the start of the line.
699  Start,
700  /// Items are packed toward the end of the line.
701  End,
702  /// Items are packed toward the flex container's main-start side.
703  /// For flex containers with flex_direction RowReverse or ColumnReverse, this is equivalent
704  /// to End. In all other cases it is equivalent to Start.
705  FlexStart,
706  /// Items are packed toward the flex container's main-end side.
707  /// For flex containers with flex_direction RowReverse or ColumnReverse, this is equivalent
708  /// to Start. In all other cases it is equivalent to End.
709  FlexEnd,
710  /// Items are packed toward the center of the line.
711  Center,
712  /// Items are stretched to fill the container (only applies to flex containers)
713  Stretch,
714  /// Items are evenly distributed in the line; first item is on the start line,
715  /// last item on the end line.
716  SpaceBetween,
717  /// Items are evenly distributed in the line with equal space around them.
718  SpaceEvenly,
719  /// Items are evenly distributed in the line; first item is on the start line,
720  /// last item on the end line, and the space between items is twice the space
721  /// between the start/end items and the container edges.
722  SpaceAround,
723  /// `safe start`: like `Start`, falling back to start-edge alignment on overflow.
724  SafeStart,
725  /// `safe end`: like `End`, falling back to start-edge alignment on overflow.
726  SafeEnd,
727  /// `safe flex-start`: like `FlexStart`, falling back to start-edge alignment on overflow.
728  SafeFlexStart,
729  /// `safe flex-end`: like `FlexEnd`, falling back to start-edge alignment on overflow.
730  SafeFlexEnd,
731  /// `safe center`: like `Center`, falling back to start-edge alignment on overflow.
732  SafeCenter,
733}
734
735declare_box_alignment_enum_impl!(
736  JustifyContent,
737  safe {
738    "start" => Start / SafeStart,
739    "end" => End / SafeEnd,
740    "flex-start" => FlexStart / SafeFlexStart,
741    "flex-end" => FlexEnd / SafeFlexEnd,
742    "center" => Center / SafeCenter,
743  },
744  plain {
745    "normal" => Normal,
746    "stretch" => Stretch,
747    "space-between" => SpaceBetween,
748    "space-around" => SpaceAround,
749    "space-evenly" => SpaceEvenly,
750  }
751);
752
753impl TailwindPropertyParser for JustifyContent {
754  fn parse_tw(token: &str) -> Option<Self> {
755    match token {
756      "between" => Some(JustifyContent::SpaceBetween),
757      "around" => Some(JustifyContent::SpaceAround),
758      "evenly" => Some(JustifyContent::SpaceEvenly),
759      _ => Self::from_str(token).ok(),
760    }
761  }
762}
763
764impl From<JustifyContent> for Option<taffy::JustifyContent> {
765  fn from(value: JustifyContent) -> Self {
766    match value {
767      JustifyContent::Normal => None,
768      JustifyContent::Start => Some(taffy::JustifyContent::START),
769      JustifyContent::End => Some(taffy::JustifyContent::END),
770      JustifyContent::FlexStart => Some(taffy::JustifyContent::FLEX_START),
771      JustifyContent::FlexEnd => Some(taffy::JustifyContent::FLEX_END),
772      JustifyContent::Center => Some(taffy::JustifyContent::CENTER),
773      JustifyContent::Stretch => Some(taffy::JustifyContent::STRETCH),
774      JustifyContent::SpaceBetween => Some(taffy::JustifyContent::SPACE_BETWEEN),
775      JustifyContent::SpaceAround => Some(taffy::JustifyContent::SPACE_AROUND),
776      JustifyContent::SpaceEvenly => Some(taffy::JustifyContent::SPACE_EVENLY),
777      JustifyContent::SafeStart => Some(taffy::JustifyContent::SAFE_START),
778      JustifyContent::SafeEnd => Some(taffy::JustifyContent::SAFE_END),
779      JustifyContent::SafeFlexStart => Some(taffy::JustifyContent::SAFE_FLEX_START),
780      JustifyContent::SafeFlexEnd => Some(taffy::JustifyContent::SAFE_FLEX_END),
781      JustifyContent::SafeCenter => Some(taffy::JustifyContent::SAFE_CENTER),
782    }
783  }
784}
785
786/// This enum determines the layout algorithm used for the children of a node.
787#[derive(Debug, Clone, Copy, PartialEq, Default)]
788#[non_exhaustive]
789pub enum Display {
790  /// The element is not displayed
791  None,
792  /// The element generates a flex container and its children follow the flexbox layout algorithm
793  Flex,
794  /// The element generates an inline-level flex container
795  InlineFlex,
796  /// The element generates a grid container and its children follow the CSS Grid layout algorithm
797  Grid,
798  /// The element generates an inline-level grid container
799  InlineGrid,
800  /// The element generates an inline container and its children follow the inline layout algorithm
801  #[default]
802  Inline,
803  /// The element creates a block container and its children follow the block layout algorithm
804  Block,
805  /// The element generates an inline-level block container
806  InlineBlock,
807}
808
809declare_enum_from_css_impl!(
810  Display,
811  "none" => Display::None,
812  "flex" => Display::Flex,
813  "inline-flex" => Display::InlineFlex,
814  "grid" => Display::Grid,
815  "inline-grid" => Display::InlineGrid,
816  "inline" => Display::Inline,
817  "block" => Display::Block,
818  "inline-block" => Display::InlineBlock
819);
820
821impl Display {
822  /// Returns true if the display creates an inline formatting context.
823  pub fn is_inline(&self) -> bool {
824    *self == Display::Inline
825  }
826
827  /// Returns true if the display participates in the inline flow as an atomic box.
828  pub fn is_inline_level(&self) -> bool {
829    matches!(
830      self,
831      Display::Inline | Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
832    )
833  }
834
835  /// Returns true if the display makes the children blockified (e.g., flex or grid).
836  pub fn should_blockify_children(&self) -> bool {
837    matches!(
838      self,
839      Display::Flex | Display::InlineFlex | Display::Grid | Display::InlineGrid
840    )
841  }
842
843  /// Cast the display to block level.
844  pub fn as_blockified(self) -> Self {
845    match self {
846      Display::Inline => Display::Block,
847      Display::InlineBlock => Display::Block,
848      Display::InlineFlex => Display::Flex,
849      Display::InlineGrid => Display::Grid,
850      _ => self,
851    }
852  }
853
854  /// Mutate the display to be block level.
855  pub fn blockify(&mut self) {
856    *self = self.as_blockified();
857  }
858}
859
860impl From<Display> for taffy::Display {
861  fn from(value: Display) -> Self {
862    match value {
863      Display::Flex | Display::InlineFlex => taffy::Display::Flex,
864      Display::Grid | Display::InlineGrid => taffy::Display::Grid,
865      Display::Block | Display::InlineBlock | Display::Inline => taffy::Display::Block,
866      Display::None => taffy::Display::None,
867    }
868  }
869}
870
871/// Defines how flex items are aligned along the cross axis.
872///
873/// This enum determines how items are aligned within the flex container
874/// along the cross axis (perpendicular to the main axis).
875#[derive(Debug, Clone, Copy, PartialEq, Default)]
876#[non_exhaustive]
877pub enum AlignItems {
878  /// The items are distributed using the normal flow of the flex container.
879  #[default]
880  Normal,
881  /// Items are aligned to the start of the line in the cross axis
882  Start,
883  /// Items are aligned to the end of the line in the cross axis
884  End,
885  /// Items are aligned to the flex container's cross-start side
886  FlexStart,
887  /// Items are aligned to the flex container's cross-end side
888  FlexEnd,
889  /// Items are centered in the cross axis
890  Center,
891  /// Items are aligned so that their baselines align
892  Baseline,
893  /// Items are stretched to fill the container in the cross axis
894  Stretch,
895  /// `safe start`: like `Start`, falling back to start-edge alignment on overflow.
896  SafeStart,
897  /// `safe end`: like `End`, falling back to start-edge alignment on overflow.
898  SafeEnd,
899  /// `safe flex-start`: like `FlexStart`, falling back to start-edge alignment on overflow.
900  SafeFlexStart,
901  /// `safe flex-end`: like `FlexEnd`, falling back to start-edge alignment on overflow.
902  SafeFlexEnd,
903  /// `safe center`: like `Center`, falling back to start-edge alignment on overflow.
904  SafeCenter,
905}
906
907declare_box_alignment_enum_impl!(
908  AlignItems,
909  safe {
910    "start" => Start / SafeStart,
911    "end" => End / SafeEnd,
912    "flex-start" => FlexStart / SafeFlexStart,
913    "flex-end" => FlexEnd / SafeFlexEnd,
914    "center" => Center / SafeCenter,
915  },
916  plain {
917    "normal" => Normal,
918    "baseline" => Baseline,
919    "stretch" => Stretch,
920  }
921);
922
923impl TailwindPropertyParser for AlignItems {
924  fn parse_tw(token: &str) -> Option<Self> {
925    Self::from_str(token).ok()
926  }
927}
928
929impl From<AlignItems> for Option<taffy::AlignItems> {
930  fn from(value: AlignItems) -> Self {
931    match value {
932      AlignItems::Normal => None,
933      AlignItems::Start => Some(taffy::AlignItems::START),
934      AlignItems::End => Some(taffy::AlignItems::END),
935      AlignItems::FlexStart => Some(taffy::AlignItems::FLEX_START),
936      AlignItems::FlexEnd => Some(taffy::AlignItems::FLEX_END),
937      AlignItems::Center => Some(taffy::AlignItems::CENTER),
938      AlignItems::Baseline => Some(taffy::AlignItems::BASELINE),
939      AlignItems::Stretch => Some(taffy::AlignItems::STRETCH),
940      AlignItems::SafeStart => Some(taffy::AlignItems::SAFE_START),
941      AlignItems::SafeEnd => Some(taffy::AlignItems::SAFE_END),
942      AlignItems::SafeFlexStart => Some(taffy::AlignItems::SAFE_FLEX_START),
943      AlignItems::SafeFlexEnd => Some(taffy::AlignItems::SAFE_FLEX_END),
944      AlignItems::SafeCenter => Some(taffy::AlignItems::SAFE_CENTER),
945    }
946  }
947}
948
949/// Defines how flex items should wrap.
950///
951/// This enum determines how flex items should wrap within the flex container.
952#[derive(Debug, Clone, Copy, PartialEq, Default)]
953#[non_exhaustive]
954pub enum FlexWrap {
955  /// Flex items will all be displayed in a single line, shrinking as needed
956  #[default]
957  NoWrap,
958  /// Flex items will wrap onto multiple lines, with new lines stacking in the flex direction
959  Wrap,
960  /// Flex items will wrap onto multiple lines, with new lines stacking in the reverse flex direction
961  WrapReverse,
962}
963
964declare_enum_from_css_impl!(
965  FlexWrap,
966  "nowrap" => FlexWrap::NoWrap,
967  "wrap" => FlexWrap::Wrap,
968  "wrap-reverse" => FlexWrap::WrapReverse
969);
970
971impl_from_taffy_enum!(FlexWrap, taffy::FlexWrap, NoWrap, Wrap, WrapReverse);
972
973/// Controls text case transformation when rendering.
974#[derive(Debug, Clone, Copy, PartialEq, Default)]
975pub enum TextTransform {
976  /// Do not transform text
977  #[default]
978  None,
979  /// Transform all characters to uppercase
980  Uppercase,
981  /// Transform all characters to lowercase
982  Lowercase,
983  /// Uppercase the first letter of each word
984  Capitalize,
985}
986
987declare_enum_from_css_impl!(
988  TextTransform,
989  "none" => TextTransform::None,
990  "uppercase" => TextTransform::Uppercase,
991  "lowercase" => TextTransform::Lowercase,
992  "capitalize" => TextTransform::Capitalize
993);
994
995/// Controls whether text decoration should skip descenders.
996#[derive(Debug, Clone, Copy, PartialEq, Default)]
997#[non_exhaustive]
998pub enum TextDecorationSkipInk {
999  /// Skip descenders and glyph interiors when painting decorations.
1000  #[default]
1001  Auto,
1002  /// Do not skip ink; paint decoration continuously.
1003  None,
1004}
1005
1006declare_enum_from_css_impl!(
1007  TextDecorationSkipInk,
1008  "auto" => TextDecorationSkipInk::Auto,
1009  "none" => TextDecorationSkipInk::None
1010);
1011
1012/// Controls how whitespace should be collapsed.
1013#[derive(Debug, Clone, Copy, PartialEq, Default)]
1014pub enum WhiteSpaceCollapse {
1015  /// Preserve whitespace as is—spaces and tabs are not collapsed.
1016  Preserve,
1017  /// Collapse whitespace—spaces and tabs are collapsed.
1018  #[default]
1019  Collapse,
1020  /// Preserve spaces and remove breaks.
1021  PreserveSpaces,
1022  /// Preserve breaks and collapse spaces.
1023  PreserveBreaks,
1024}
1025
1026declare_enum_from_css_impl!(
1027  WhiteSpaceCollapse,
1028  "preserve" => WhiteSpaceCollapse::Preserve,
1029  "collapse" => WhiteSpaceCollapse::Collapse,
1030  "preserve-spaces" => WhiteSpaceCollapse::PreserveSpaces,
1031  "preserve-breaks" => WhiteSpaceCollapse::PreserveBreaks,
1032);
1033
1034/// Defines how images should be scaled when rendered.
1035#[derive(Debug, Clone, Copy, PartialEq, Default)]
1036pub enum ImageScalingAlgorithm {
1037  /// The image is scaled using Catmull-Rom interpolation.
1038  /// This is balanced for speed and quality.
1039  #[default]
1040  Auto,
1041  /// The image is scaled using Lanczos3 resampling.
1042  /// This provides high-quality scaling but may be slower.
1043  Smooth,
1044  /// The image is scaled using nearest neighbor interpolation,
1045  /// which is suitable for pixel art or images where sharp edges are desired.
1046  Pixelated,
1047}
1048
1049declare_enum_from_css_impl!(
1050  ImageScalingAlgorithm,
1051  "auto" => ImageScalingAlgorithm::Auto,
1052  "smooth" => ImageScalingAlgorithm::Smooth,
1053  "pixelated" => ImageScalingAlgorithm::Pixelated
1054);
1055
1056/// Represents border style options.
1057#[derive(Debug, Default, Clone, Copy, PartialEq)]
1058pub enum BorderStyle {
1059  /// No border will be rendered.
1060  #[default]
1061  None,
1062  /// Forces the border to be hidden.
1063  Hidden,
1064  /// Dotted border style.
1065  Dotted,
1066  /// Dashed border style.
1067  Dashed,
1068  /// Solid border style.
1069  Solid,
1070  /// Double border style.
1071  Double,
1072  /// Groove border style.
1073  Groove,
1074  /// Ridge border style.
1075  Ridge,
1076  /// Inset border style.
1077  Inset,
1078  /// Outset border style.
1079  Outset,
1080}
1081
1082impl BorderStyle {
1083  /// Returns whether this border style should paint and reserve border width.
1084  pub const fn is_rendered(self) -> bool {
1085    !matches!(self, Self::None | Self::Hidden)
1086  }
1087}
1088
1089declare_enum_from_css_impl!(
1090  BorderStyle,
1091  "none" => BorderStyle::None,
1092  "hidden" => BorderStyle::Hidden,
1093  "dotted" => BorderStyle::Dotted,
1094  "dashed" => BorderStyle::Dashed,
1095  "solid" => BorderStyle::Solid,
1096  "double" => BorderStyle::Double,
1097  "groove" => BorderStyle::Groove,
1098  "ridge" => BorderStyle::Ridge,
1099  "inset" => BorderStyle::Inset,
1100  "outset" => BorderStyle::Outset,
1101);
1102
1103impl TailwindPropertyParser for BorderStyle {
1104  fn parse_tw(token: &str) -> Option<Self> {
1105    Self::from_str(token).ok()
1106  }
1107}
1108
1109impl From<ImageScalingAlgorithm> for FilterType {
1110  fn from(algorithm: ImageScalingAlgorithm) -> Self {
1111    match algorithm {
1112      ImageScalingAlgorithm::Auto => FilterType::CatmullRom,
1113      ImageScalingAlgorithm::Smooth => FilterType::Lanczos3,
1114      ImageScalingAlgorithm::Pixelated => FilterType::Nearest,
1115    }
1116  }
1117}