1use cssparser::{ParseError, Parser, ParserInput};
2use std::borrow::Cow;
3use std::fmt;
4use std::sync::Arc;
5
6use crate::style::{Color, SizingContext, math::lcm};
7
8pub type ParseResult<'i, T> = Result<T, ParseError<'i, Cow<'i, str>>>;
10
11#[derive(Clone, Copy)]
13#[non_exhaustive]
14pub enum CssSyntaxKind {
15 Angle,
17 BorderStyle,
19 Clip,
21 Color,
23 CustomIdent,
25 EasingFunction,
27 FamilyName,
29 GenericName,
31 Ident,
33 Image,
35 Integer,
37 Length,
39 LineNames,
41 Number,
43 Percentage,
45 Position,
47 Repeat,
49 String,
51 Time,
53 TrackSize,
55 TransformFunction,
57}
58
59impl CssSyntaxKind {
60 const fn as_str(self) -> &'static str {
61 match self {
62 Self::Angle => "angle",
63 Self::BorderStyle => "border-style",
64 Self::Clip => "clip",
65 Self::Color => "color",
66 Self::CustomIdent => "custom-ident",
67 Self::EasingFunction => "easing-function",
68 Self::FamilyName => "family-name",
69 Self::GenericName => "generic-name",
70 Self::Ident => "ident",
71 Self::Image => "image",
72 Self::Integer => "integer",
73 Self::Length => "length",
74 Self::LineNames => "line-names",
75 Self::Number => "number",
76 Self::Percentage => "percentage",
77 Self::Position => "position",
78 Self::Repeat => "repeat",
79 Self::String => "string",
80 Self::Time => "time",
81 Self::TrackSize => "track-size",
82 Self::TransformFunction => "transform-function",
83 }
84 }
85}
86
87#[derive(Clone, Copy)]
89pub enum CssDescriptorKind {
90 BlurFn,
92 BlendMode,
94 BrightnessFn,
96 CircleFn,
98 ColorAndPercentage,
100 ColorMixFn,
102 ConicGradientFn,
104 RepeatingConicGradientFn,
106 ContrastFn,
108 CubicBezierFn,
110 DropShadowFn,
112 EllipseFn,
114 GrayscaleFn,
116 HueRotateFn,
118 InColorSpace,
120 InsetFn,
122 InvertFn,
124 LinearGradientFn,
126 RepeatingLinearGradientFn,
128 MinmaxFn,
130 OpacityFn,
132 PathFn,
134 PolygonFn,
136 RadialGradientFn,
138 RepeatingRadialGradientFn,
140 RepeatFn,
142 SaturateFn,
144 SepiaFn,
146 StepsFn,
148 TextWrapMode,
150 TextWrapStyle,
152 UrlFn,
154 WhiteSpaceCollapse,
156}
157
158impl CssDescriptorKind {
159 const fn as_str(self) -> &'static str {
160 match self {
161 Self::BlurFn => "blur()",
162 Self::BlendMode => "blend-mode",
163 Self::BrightnessFn => "brightness()",
164 Self::CircleFn => "circle()",
165 Self::ColorAndPercentage => "color and percentage",
166 Self::ColorMixFn => "color-mix()",
167 Self::ConicGradientFn => "conic-gradient()",
168 Self::RepeatingConicGradientFn => "repeating-conic-gradient()",
169 Self::ContrastFn => "contrast()",
170 Self::CubicBezierFn => "cubic-bezier()",
171 Self::DropShadowFn => "drop-shadow()",
172 Self::EllipseFn => "ellipse()",
173 Self::GrayscaleFn => "grayscale()",
174 Self::HueRotateFn => "hue-rotate()",
175 Self::InColorSpace => "in <color-space>",
176 Self::InsetFn => "inset()",
177 Self::InvertFn => "invert()",
178 Self::LinearGradientFn => "linear-gradient()",
179 Self::RepeatingLinearGradientFn => "repeating-linear-gradient()",
180 Self::MinmaxFn => "minmax()",
181 Self::OpacityFn => "opacity()",
182 Self::PathFn => "path()",
183 Self::PolygonFn => "polygon()",
184 Self::RadialGradientFn => "radial-gradient()",
185 Self::RepeatingRadialGradientFn => "repeating-radial-gradient()",
186 Self::RepeatFn => "repeat()",
187 Self::SaturateFn => "saturate()",
188 Self::SepiaFn => "sepia()",
189 Self::StepsFn => "steps()",
190 Self::TextWrapMode => "text-wrap-mode",
191 Self::TextWrapStyle => "text-wrap-style",
192 Self::UrlFn => "url()",
193 Self::WhiteSpaceCollapse => "white-space-collapse",
194 }
195 }
196}
197
198#[non_exhaustive]
200pub enum CssToken {
201 Keyword(&'static str),
203 Syntax(CssSyntaxKind),
205 Descriptor(CssDescriptorKind),
207}
208
209impl std::fmt::Display for CssToken {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 match self {
212 CssToken::Keyword(keyword) => write!(f, "'{}'", keyword),
213 CssToken::Syntax(token) => write!(f, "<{}>", token.as_str()),
214 CssToken::Descriptor(token) => write!(f, "<{}>", token.as_str()),
215 }
216 }
217}
218
219#[non_exhaustive]
221pub enum CssExpectedMessage {
222 ValueOrNone,
224 OneValue,
226 OneOrTwoValues,
228 OneToFourValues,
230 BorderRadius,
232}
233
234impl CssExpectedMessage {
235 pub fn build_message(&self, token: &str, valid_tokens: String) -> String {
236 match self {
237 Self::ValueOrNone => {
238 format!("Unexpected token: {token}, expected a value of {valid_tokens} or 'none'")
239 }
240 Self::OneValue => format!("Unexpected token: {token}, expected a value of {valid_tokens}"),
241 Self::OneOrTwoValues => {
242 format!("Unexpected token: {token}, expected 1 ~ 2 values of {valid_tokens}")
243 }
244 Self::OneToFourValues => {
245 format!("Unexpected token: {token}, expected 1 ~ 4 values of {valid_tokens}")
246 }
247 Self::BorderRadius => format!(
248 "Unexpected token: {token}, expected 1 to 4 length values for width, optionally followed by '/' and 1 to 4 length values for height"
249 ),
250 }
251 }
252}
253
254pub trait FromCss<'i> {
256 fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self>
258 where
259 Self: Sized;
260
261 fn from_str(source: &'i str) -> ParseResult<'i, Self>
263 where
264 Self: Sized,
265 {
266 let mut input = ParserInput::new(source);
267 let mut parser = Parser::new(&mut input);
268
269 Self::from_css(&mut parser)
270 }
271
272 const VALID_TOKENS: &'static [CssToken];
274
275 const EXPECT_MESSAGE: CssExpectedMessage = CssExpectedMessage::OneValue;
277}
278
279impl<'i, T: FromCss<'i>> FromCss<'i> for Option<T> {
280 const VALID_TOKENS: &'static [CssToken] = T::VALID_TOKENS;
282
283 const EXPECT_MESSAGE: CssExpectedMessage = CssExpectedMessage::ValueOrNone;
284
285 fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
286 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
287 return Ok(None);
288 }
289
290 T::from_css(input).map(Some)
291 }
292}
293
294impl<'i> FromCss<'i> for String {
295 const VALID_TOKENS: &'static [CssToken] = &[
296 CssToken::Syntax(CssSyntaxKind::String),
297 CssToken::Syntax(CssSyntaxKind::CustomIdent),
298 ];
299
300 fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
301 Ok(input.expect_ident_or_string()?.to_string())
302 }
303}
304
305pub(crate) trait MakeComputed {
307 fn make_computed(&mut self, _sizing: &SizingContext) {}
309}
310
311pub(crate) trait Animatable: Sized + Clone {
312 fn interpolate(
313 &mut self,
314 from: &Self,
315 to: &Self,
316 progress: f32,
317 _sizing: &SizingContext,
318 _current_color: Color,
319 ) {
320 *self = if progress >= 0.5 {
321 to.clone()
322 } else {
323 from.clone()
324 };
325 }
326
327 fn list_interpolation_strategy() -> ListInterpolationStrategy {
328 ListInterpolationStrategy::Discrete
329 }
330
331 fn neutral_value_like(_other: &Self) -> Option<Self> {
332 None
333 }
334
335 fn missing_value() -> Option<Self> {
336 None
337 }
338}
339
340pub(crate) enum ListInterpolationStrategy {
341 Discrete,
342 RepeatToLcm,
343 PadToLongestWithNeutral,
344}
345
346impl<T: MakeComputed> MakeComputed for Option<T> {
347 fn make_computed(&mut self, sizing: &SizingContext) {
348 if let Some(value) = self.as_mut() {
349 value.make_computed(sizing);
350 }
351 }
352}
353
354impl<T: MakeComputed> MakeComputed for Box<[T]> {
355 fn make_computed(&mut self, sizing: &SizingContext) {
356 for value in self.iter_mut() {
357 value.make_computed(sizing);
358 }
359 }
360}
361
362impl<T: MakeComputed> MakeComputed for Vec<T> {
363 fn make_computed(&mut self, sizing: &SizingContext) {
364 for value in self.iter_mut() {
365 value.make_computed(sizing);
366 }
367 }
368}
369
370impl<T: Animatable + Clone> Animatable for Option<T> {
371 fn interpolate(
372 &mut self,
373 from: &Self,
374 to: &Self,
375 progress: f32,
376 sizing: &SizingContext,
377 current_color: Color,
378 ) {
379 *self = match (from, to) {
380 (Some(from), Some(to)) => {
381 let mut value = from.clone();
382 value.interpolate(from, to, progress, sizing, current_color);
383 Some(value)
384 }
385 (Some(from), None) => T::missing_value().map_or_else(
386 || {
387 if progress >= 0.5 {
388 None
389 } else {
390 Some(from.clone())
391 }
392 },
393 |missing| {
394 let mut value = from.clone();
395 value.interpolate(from, &missing, progress, sizing, current_color);
396 Some(value)
397 },
398 ),
399 (None, Some(to)) => T::missing_value().map_or_else(
400 || {
401 if progress >= 0.5 {
402 Some(to.clone())
403 } else {
404 None
405 }
406 },
407 |missing| {
408 let mut value = missing.clone();
409 value.interpolate(&missing, to, progress, sizing, current_color);
410 Some(value)
411 },
412 ),
413 (None, None) => None,
414 };
415 }
416}
417
418impl<T: Animatable + Clone> Animatable for Box<[T]> {
419 fn missing_value() -> Option<Self> {
420 match T::list_interpolation_strategy() {
421 ListInterpolationStrategy::Discrete => None,
422 ListInterpolationStrategy::RepeatToLcm
423 | ListInterpolationStrategy::PadToLongestWithNeutral => Some(Box::default()),
424 }
425 }
426
427 fn interpolate(
428 &mut self,
429 from: &Self,
430 to: &Self,
431 progress: f32,
432 sizing: &SizingContext,
433 current_color: Color,
434 ) {
435 *self = interpolate_list(
436 from,
437 to,
438 progress,
439 sizing,
440 current_color,
441 Vec::into_boxed_slice,
442 )
443 .unwrap_or_else(|| {
444 if progress >= 0.5 {
445 to.clone()
446 } else {
447 from.clone()
448 }
449 });
450 }
451}
452
453impl<T: Animatable + Clone> Animatable for Vec<T> {
454 fn missing_value() -> Option<Self> {
455 match T::list_interpolation_strategy() {
456 ListInterpolationStrategy::Discrete => None,
457 ListInterpolationStrategy::RepeatToLcm
458 | ListInterpolationStrategy::PadToLongestWithNeutral => Some(Vec::new()),
459 }
460 }
461
462 fn interpolate(
463 &mut self,
464 from: &Self,
465 to: &Self,
466 progress: f32,
467 sizing: &SizingContext,
468 current_color: Color,
469 ) {
470 *self = interpolate_list(from, to, progress, sizing, current_color, |values| values)
471 .unwrap_or_else(|| {
472 if progress >= 0.5 {
473 to.clone()
474 } else {
475 from.clone()
476 }
477 });
478 }
479}
480
481fn interpolate_list<T: Animatable + Clone, C: AsRef<[T]>, O>(
482 from: &C,
483 to: &C,
484 progress: f32,
485 sizing: &SizingContext,
486 current_color: Color,
487 build: impl FnOnce(Vec<T>) -> O,
488) -> Option<O> {
489 let from = from.as_ref();
490 let to = to.as_ref();
491
492 let values = match T::list_interpolation_strategy() {
493 ListInterpolationStrategy::Discrete => {
494 if from.len() != to.len() {
495 return None;
496 }
497 interpolate_pairwise_list(from, to, from.len(), progress, sizing, current_color)
498 }
499 ListInterpolationStrategy::RepeatToLcm => {
500 if from.is_empty() || to.is_empty() {
501 return None;
502 }
503 interpolate_pairwise_list(
504 from,
505 to,
506 lcm(from.len(), to.len()),
507 progress,
508 sizing,
509 current_color,
510 )
511 }
512 ListInterpolationStrategy::PadToLongestWithNeutral => {
513 interpolate_neutral_padded_list(from, to, progress, sizing, current_color)?
514 }
515 };
516
517 Some(build(values))
518}
519
520fn interpolate_pairwise_list<T: Animatable + Clone>(
521 from: &[T],
522 to: &[T],
523 output_len: usize,
524 progress: f32,
525 sizing: &SizingContext,
526 current_color: Color,
527) -> Vec<T> {
528 (0..output_len)
529 .map(|index| {
530 let from_value = &from[index % from.len()];
531 let to_value = &to[index % to.len()];
532 let mut value = from_value.clone();
533 value.interpolate(from_value, to_value, progress, sizing, current_color);
534 value
535 })
536 .collect()
537}
538
539fn interpolate_neutral_padded_list<T: Animatable + Clone>(
540 from: &[T],
541 to: &[T],
542 progress: f32,
543 sizing: &SizingContext,
544 current_color: Color,
545) -> Option<Vec<T>> {
546 let output_len = from.len().max(to.len());
547
548 (0..output_len)
549 .map(|index| {
550 let from_value = if index < from.len() {
551 from.get(index).cloned()
552 } else {
553 to.get(index).and_then(T::neutral_value_like)
554 }?;
555 let to_value = if index < to.len() {
556 to.get(index).cloned()
557 } else {
558 from.get(index).and_then(T::neutral_value_like)
559 }?;
560
561 let mut value = from_value.clone();
562 value.interpolate(&from_value, &to_value, progress, sizing, current_color);
563 Some(value)
564 })
565 .collect()
566}
567
568pub trait ToCss {
570 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result;
572}
573
574impl<T: ToCss + ?Sized> ToCss for &T {
575 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
576 (*self).to_css(dest)
577 }
578}
579
580impl<T: ToCss> ToCss for Option<T> {
581 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
582 match self {
583 Some(v) => v.to_css(dest),
584 None => dest.write_str("none"),
585 }
586 }
587}
588
589impl<T: ToCss> ToCss for Box<[T]> {
590 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
591 for (i, item) in self.iter().enumerate() {
592 if i > 0 {
593 dest.write_str(", ")?;
594 }
595 item.to_css(dest)?;
596 }
597 Ok(())
598 }
599}
600
601impl<T: ToCss> ToCss for Vec<T> {
602 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
603 for (i, item) in self.iter().enumerate() {
604 if i > 0 {
605 dest.write_str(", ")?;
606 }
607 item.to_css(dest)?;
608 }
609 Ok(())
610 }
611}
612
613impl ToCss for f32 {
614 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
615 write!(dest, "{}", self)
616 }
617}
618
619impl ToCss for u32 {
620 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
621 write!(dest, "{}", self)
622 }
623}
624
625impl ToCss for i32 {
626 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
627 write!(dest, "{}", self)
628 }
629}
630
631impl ToCss for String {
632 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
633 dest.write_str(self)
634 }
635}
636
637impl ToCss for Arc<str> {
638 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
639 dest.write_str(self)
640 }
641}
642
643macro_rules! impl_from_taffy_enum {
645 ($from_ty:ty, $to_ty:ty, $($variant:ident),*) => {
646 impl From<$from_ty> for $to_ty {
647 fn from(value: $from_ty) -> Self {
648 match value {
649 $(<$from_ty>::$variant => <$to_ty>::$variant,)*
650 }
651 }
652 }
653 };
654}
655
656pub(crate) use impl_from_taffy_enum;
657
658macro_rules! declare_enum_from_css_impl {
660 (
661 $enum_type:ty,
662 $($css_value:expr => $variant:path),* $(,)?
663 ) => {
664 impl crate::style::MakeComputed for $enum_type {}
665
666 impl<'i> crate::style::FromCss<'i> for $enum_type {
667 const VALID_TOKENS: &'static [crate::style::CssToken] =
668 &[$(crate::style::CssToken::Keyword($css_value)),*];
669
670 fn from_css(input: &mut cssparser::Parser<'i, '_>) -> crate::style::ParseResult<'i, Self> {
671 let location = input.current_source_location();
672 let token = input.next()?;
673
674 let cssparser::Token::Ident(ident) = token else {
675 return Err($crate::style::unexpected_token!(location, token));
676 };
677
678 cssparser::match_ignore_ascii_case! {&ident,
679 $(
680 $css_value => Ok($variant),
681 )*
682 _ => Err($crate::style::unexpected_token!(location, token)),
683 }
684 }
685 }
686
687 impl crate::style::properties::ToCss for $enum_type {
688 fn to_css<W: std::fmt::Write>(&self, dest: &mut W) -> std::fmt::Result {
689 match self {
690 $(
691 $variant => dest.write_str($css_value),
692 )*
693 }
694 }
695 }
696 };
697}
698
699pub(crate) use declare_enum_from_css_impl;
700
701macro_rules! declare_box_alignment_enum_impl {
704 (
705 $enum_type:ty,
706 safe { $($safe_css:literal => $base_variant:ident / $safe_variant:ident),+ $(,)? },
707 plain { $($plain_css:literal => $plain_variant:ident),* $(,)? }
708 ) => {
709 impl crate::style::MakeComputed for $enum_type {}
710
711 impl<'i> crate::style::FromCss<'i> for $enum_type {
712 const VALID_TOKENS: &'static [crate::style::CssToken] = &[
713 $(crate::style::CssToken::Keyword($plain_css),)*
714 $(crate::style::CssToken::Keyword($safe_css),)*
715 crate::style::CssToken::Keyword("safe"),
716 crate::style::CssToken::Keyword("unsafe"),
717 ];
718
719 fn from_css(input: &mut cssparser::Parser<'i, '_>) -> crate::style::ParseResult<'i, Self> {
720 let mut safe = false;
721
722 loop {
723 let location = input.current_source_location();
724 let token = input.next()?;
725
726 let cssparser::Token::Ident(ident) = token else {
727 return Err($crate::style::unexpected_token!(location, token));
728 };
729
730 cssparser::match_ignore_ascii_case! {&ident,
731 "safe" => safe = true,
732 "unsafe" => safe = false,
733 $($safe_css => return Ok(if safe { Self::$safe_variant } else { Self::$base_variant }),)*
734 $($plain_css => return if safe {
735 Err($crate::style::unexpected_token!(location, token))
736 } else {
737 Ok(Self::$plain_variant)
738 },)*
739 _ => return Err($crate::style::unexpected_token!(location, token)),
740 }
741 }
742 }
743 }
744
745 impl crate::style::properties::ToCss for $enum_type {
746 fn to_css<W: std::fmt::Write>(&self, dest: &mut W) -> std::fmt::Result {
747 match self {
748 $(Self::$plain_variant => dest.write_str($plain_css),)*
749 $(Self::$base_variant => dest.write_str($safe_css),)*
750 $(Self::$safe_variant => {
751 dest.write_str("safe ")?;
752 dest.write_str($safe_css)
753 })*
754 }
755 }
756 }
757 };
758}
759
760pub(crate) use declare_box_alignment_enum_impl;