tailwind_css_fixes/modules/background/image/
mod.rs

1use super::*;
2
3#[doc=include_str!("readme.md")]
4#[derive(Clone, Debug)]
5pub struct TailwindBackgroundImage {
6    // The type of image/gradient
7    kind: BgImageKind,
8    // The value (e.g., direction, angle, url). Optional for defaults like `bg-radial`.
9    value: Option<UnitValue>,
10    // Flag for negative angles
11    is_negative: bool,
12}
13
14#[derive(Clone, Debug, PartialEq)]
15enum BgImageKind {
16    None,
17    Url,
18    Linear,
19    Radial,
20    Conic,
21    RepeatingLinear,
22    RepeatingRadial,
23    RepeatingConic,
24}
25
26impl Display for TailwindBackgroundImage {
27    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28        if self.is_negative {
29            write!(f, "-")?;
30        }
31
32        match (&self.kind, &self.value) {
33            (BgImageKind::None, _) => write!(f, "bg-none"),
34            (BgImageKind::Url, Some(val)) => write!(f, "bg-[url({})]", val),
35            (BgImageKind::Linear, Some(val)) => write!(f, "bg-linear-{}", val),
36            (BgImageKind::Radial, Some(val)) => write!(f, "bg-radial-{}", val),
37            (BgImageKind::Conic, Some(val)) => write!(f, "bg-conic-{}", val),
38            (BgImageKind::Linear, None) => write!(f, "bg-linear"),
39            (BgImageKind::Radial, None) => write!(f, "bg-radial"),
40            (BgImageKind::Conic, None) => write!(f, "bg-conic"),
41            (BgImageKind::RepeatingLinear, Some(val)) => write!(f, "bg-[repeating-linear-gradient({})]", val),
42            (BgImageKind::RepeatingRadial, Some(val)) => write!(f, "bg-[repeating-radial-gradient({})]", val),
43            (BgImageKind::RepeatingConic, Some(val)) => write!(f, "bg-[repeating-conic-gradient({})]", val),
44            _ => Ok(()), // Should not happen
45        }
46    }
47}
48
49impl TailwindInstance for TailwindBackgroundImage {
50    fn attributes(&self, _: &TailwindBuilder) -> CssAttributes {
51        match &self.kind {
52            BgImageKind::None => css_attributes! { "background-image" => "none" },
53            BgImageKind::Url => {
54                let url_value = self.value.as_ref().unwrap().to_string();
55                css_attributes! { "background-image" => format!("url({})", url_value) }
56            }
57            BgImageKind::RepeatingLinear | BgImageKind::RepeatingRadial | BgImageKind::RepeatingConic => {
58                let arbitrary_gradient_val = self.value.as_ref().unwrap().to_string().replace('_', " ");
59                
60                let gradient_type = match self.kind {
61                    BgImageKind::RepeatingLinear => "repeating-linear-gradient",
62                    BgImageKind::RepeatingRadial => "repeating-radial-gradient",
63                    BgImageKind::RepeatingConic => "repeating-conic-gradient",
64                    _ => unreachable!(),
65                };
66                css_attributes! { "background-image" => format!("{}({})", gradient_type, arbitrary_gradient_val) }
67            }
68            BgImageKind::Linear | BgImageKind::Radial | BgImageKind::Conic => {
69                let gradient_type = match self.kind {
70                    BgImageKind::Linear => "linear-gradient",
71                    BgImageKind::Radial => "radial-gradient",
72                    BgImageKind::Conic => "conic-gradient",
73                    _ => unreachable!(),
74                };
75
76                let direction_or_angle = match self.value.as_ref() {
77                    // No value, e.g., `bg-radial` -> `in oklab`
78                    None => "in oklab".to_string(),
79                    // Directional keyword, e.g., `to-t` -> `to top in oklab`
80                    Some(UnitValue::Keyword(k)) if k.starts_with("to-") => {
81                        let direction = k.strip_prefix("to-").unwrap_or(k);
82                        let mut full_direction = String::new();
83                        if direction.contains('t') { full_direction.push_str("top "); }
84                        if direction.contains('b') { full_direction.push_str("bottom "); }
85                        if direction.contains('l') { full_direction.push_str("left "); }
86                        if direction.contains('r') { full_direction.push_str("right "); }
87                        format!("to {} in oklab", full_direction.trim())
88                    }
89                    // An angle or other arbitrary value, e.g., `45deg`
90                    Some(value) => {
91                        // Get the inner value of an arbitrary, e.g., `45deg` from `[45deg]`
92                        let inner_value = match value {
93                             UnitValue::Arbitrary(a) => a.get_properties(),
94                             _ => value.to_string()
95                        };
96                    
97                        if self.is_negative {
98                            format!("calc({} * -1)", inner_value)
99                        } else {
100                            format!("{} in oklab", inner_value)
101                        }
102                    }
103                };
104
105                // For arbitrary angles, the position is added as a fallback variable
106                let image = match self.value.as_ref() {
107                     Some(UnitValue::Arbitrary(_)) | Some(UnitValue::Number {..}) | Some(UnitValue::Length(_)) => {
108                        format!("{}(var(--tw-gradient-stops, {}))", gradient_type, direction_or_angle)
109                     },
110                     _ => format!("{}(var(--tw-gradient-stops))", gradient_type)
111                };
112
113                css_attributes! {
114                    "--tw-gradient-position" => direction_or_angle,
115                    "background-image" => image
116                }
117            }
118        }
119    }
120}
121
122impl TailwindBackgroundImage {
123    /// Should only be called if arbitrary.as_str().starts_with("url(") || arbitrary.as_str().contains("gradient(")
124    pub fn parse(pattern: &[&str], arbitrary: &TailwindArbitrary, neg: Negative) -> Result<Self> {
125        let (kind, rest) = match pattern {
126            ["none"] => (BgImageKind::None, &pattern[1..]),
127            ["gradient", rest @ ..] | ["linear", rest @ ..] => (BgImageKind::Linear, rest),
128            ["radial", rest @ ..] => (BgImageKind::Radial, rest),
129            ["conic", rest @ ..] => (BgImageKind::Conic, rest),
130            [] if arbitrary.as_str().starts_with("url(") => (BgImageKind::Url, pattern),
131            [] if arbitrary.as_str().starts_with("repeating-linear-gradient(") => (BgImageKind::RepeatingLinear, pattern),
132            [] if arbitrary.as_str().starts_with("repeating-radial-gradient(") => (BgImageKind::RepeatingRadial, pattern),
133            [] if arbitrary.as_str().starts_with("repeating-conic-gradient(") => (BgImageKind::RepeatingConic, pattern),
134            _ => return syntax_error!("Unknown background-image pattern: {}", pattern.join("-")),
135        };
136
137        let value = match kind {
138            BgImageKind::None => None,
139            BgImageKind::Url => {
140                let url = arbitrary.as_str().strip_prefix("url(").and_then(|s| s.strip_suffix(')')).unwrap_or("");
141                Some(UnitValue::Keyword(url.to_string()))
142            }
143            BgImageKind::RepeatingLinear | BgImageKind::RepeatingRadial | BgImageKind::RepeatingConic => {
144                let prefix = match kind {
145                    BgImageKind::RepeatingLinear => "repeating-linear-gradient(",
146                    BgImageKind::RepeatingRadial => "repeating-radial-gradient(",
147                    BgImageKind::RepeatingConic => "repeating-conic-gradient(",
148                    _ => unreachable!(),
149                };
150                let content = arbitrary.as_str().strip_prefix(prefix).and_then(|s| s.strip_suffix(')')).unwrap_or("");
151                Some(UnitValue::Keyword(content.to_string()))
152            }
153            BgImageKind::Linear 
154            | BgImageKind::Radial 
155            | BgImageKind::Conic => {
156                if rest.is_empty() && arbitrary.is_none() {
157                    // This is a default gradient, like `bg-radial`
158                    None
159                } else {
160                    let joined = rest.join("-");
161                    if joined.starts_with("to-") {
162                        // It's a directional keyword like `to-t` or `to-b-r`
163                        Some(UnitValue::Keyword(joined.replace("-b-r", "-br").replace("-b-l", "-bl").replace("-t-r", "-tr").replace("-t-l", "-tl")))
164                    } else {
165                        // Otherwise, parse as a number, angle, or other length value.
166                        // The `is_length: false` flag tells the parser to prefer angles over lengths.
167                        Some(UnitValue::negative_parser("bg-image", |_| false, false, false, false)(rest, arbitrary, neg)?)
168                    }
169                }
170            }
171        };
172
173        Ok(Self { kind, value, is_negative: neg.0 })
174    }
175}