encre_css/plugins/background/background_image/
mod.rs

1#![doc = include_str!("README.md")]
2#![doc(alias("background", "bg", "gradient"))]
3use std::borrow::Cow;
4
5use crate::prelude::build_plugin::*;
6
7const INTERPOLATION_MODES: &[&str] = &[
8    "srgb",
9    "hsl",
10    "oklab",
11    "oklch",
12    "longer",
13    "shorter",
14    "increasing",
15    "decreasing",
16];
17
18#[derive(Debug)]
19pub(crate) struct PluginDefinition;
20
21impl Plugin for PluginDefinition {
22    fn can_handle(&self, context: ContextCanHandle) -> bool {
23        match context.modifier {
24            Modifier::Builtin { value, .. } => [
25                "none",
26                "gradient-to-t",
27                "gradient-to-tr",
28                "gradient-to-r",
29                "gradient-to-br",
30                "gradient-to-b",
31                "gradient-to-bl",
32                "gradient-to-l",
33                "gradient-to-tl",
34            ]
35            .contains(value),
36            Modifier::Arbitrary { hint, value, .. } => {
37                *hint == "image" || *hint == "url" || (hint.is_empty() && is_matching_image(value))
38            }
39        }
40    }
41
42    fn handle(&self, context: &mut ContextHandle) {
43        match context.modifier {
44            Modifier::Builtin { value, .. } => match *value {
45                "none" => context.buffer.line("background-image: none;"),
46                "gradient-to-t" => context
47                    .buffer
48                    .line("background-image: linear-gradient(to top in oklab, var(--en-gradient-stops));"),
49                "gradient-to-tr" => context.buffer.line(
50                    "background-image: linear-gradient(to top right in oklab, var(--en-gradient-stops));",
51                ),
52                "gradient-to-r" => context
53                    .buffer
54                    .line("background-image: linear-gradient(to right in oklab, var(--en-gradient-stops));"),
55                "gradient-to-br" => context.buffer.line(
56                    "background-image: linear-gradient(to bottom right in oklab, var(--en-gradient-stops));",
57                ),
58                "gradient-to-b" => context.buffer.line(
59                    "background-image: linear-gradient(to bottom in oklab, var(--en-gradient-stops));",
60                ),
61                "gradient-to-bl" => context.buffer.line(
62                    "background-image: linear-gradient(to bottom left in oklab, var(--en-gradient-stops));",
63                ),
64                "gradient-to-l" => context
65                    .buffer
66                    .line("background-image: linear-gradient(to left in oklab, var(--en-gradient-stops));"),
67                "gradient-to-tl" => context.buffer.line(
68                    "background-image: linear-gradient(to top left in oklab, var(--en-gradient-stops));",
69                ),
70                _ => unreachable!(),
71            },
72            Modifier::Arbitrary { value, .. } => {
73                context
74                    .buffer
75                    .line(format_args!("background-image: {value};"));
76            }
77        }
78    }
79}
80
81#[derive(Debug)]
82pub(crate) struct PluginLinearDefinition;
83
84impl Plugin for PluginLinearDefinition {
85    fn can_handle(&self, context: ContextCanHandle) -> bool {
86        match context.modifier {
87            Modifier::Builtin { value, .. } => {
88                let (gradient_type, interpolation_mode) = if let Some(index) = value.find('/') {
89                    let (before, after) = value.split_at(index);
90                    (before, &after[1..])
91                } else {
92                    (*value, "oklab")
93                };
94
95                ([
96                    "to-t", "to-tr", "to-r", "to-br", "to-b", "to-bl", "to-l", "to-tl",
97                ]
98                .contains(&gradient_type)
99                    || gradient_type.parse::<usize>().is_ok())
100                    && INTERPOLATION_MODES.contains(&interpolation_mode)
101            }
102            Modifier::Arbitrary { value, .. } => is_matching_all(value),
103        }
104    }
105
106    fn handle(&self, context: &mut ContextHandle) {
107        match context.modifier {
108            Modifier::Builtin { is_negative, value } => {
109                let (gradient_type, interpolation_mode) = if let Some(index) = value.find('/') {
110                    let (before, after) = value.split_at(index);
111                    (before, &after[1..])
112                } else {
113                    // The interpolation mode defaults to `oklab`
114                    (*value, "oklab")
115                };
116
117                let interpolation_mode = match interpolation_mode {
118                    "longer" | "shorter" | "increasing" | "decreasing" => {
119                        Cow::Owned(format!("oklch {interpolation_mode} hue"))
120                    }
121                    _ => Cow::Borrowed(interpolation_mode),
122                };
123
124                match gradient_type {
125                    "none" => context.buffer.line("background-image: none;"),
126                    "to-t" => context
127                        .buffer
128                        .line(format_args!("background-image: linear-gradient(to top in {interpolation_mode}, var(--en-gradient-stops));")),
129                    "to-tr" => context.buffer.line(
130                        format_args!("background-image: linear-gradient(to top right in {interpolation_mode}, var(--en-gradient-stops));"),
131                    ),
132                    "to-r" => context
133                        .buffer
134                        .line(format_args!("background-image: linear-gradient(to right in {interpolation_mode}, var(--en-gradient-stops));")),
135                    "to-br" => context.buffer.line(
136                        format_args!("background-image: linear-gradient(to bottom right in {interpolation_mode}, var(--en-gradient-stops));"),
137                    ),
138                    "to-b" => context.buffer.line(
139                        format_args!("background-image: linear-gradient(to bottom in {interpolation_mode}, var(--en-gradient-stops));"),
140                    ),
141                    "to-bl" => context.buffer.line(
142                        format_args!("background-image: linear-gradient(to bottom left in {interpolation_mode}, var(--en-gradient-stops));"),
143                    ),
144                    "to-l" => context
145                        .buffer
146                        .line(format_args!("background-image: linear-gradient(to left in {interpolation_mode}, var(--en-gradient-stops));")),
147                    "to-tl" => context.buffer.line(
148                        format_args!("background-image: linear-gradient(to top left in {interpolation_mode}, var(--en-gradient-stops));"),
149                    ),
150                    _ => {
151                        let angle = value.parse::<usize>().unwrap();
152                        context.buffer.line(
153                            format_args!("background-image: linear-gradient({}{angle}deg in {interpolation_mode}, var(--en-gradient-stops));", if *is_negative { "-" } else { "" }),
154                        );
155                    },
156                }
157            }
158            Modifier::Arbitrary { value, .. } => {
159                context
160                    .buffer
161                    .line(format_args!("background-image: linear-gradient({value});"));
162            }
163        }
164    }
165}
166
167#[derive(Debug)]
168pub(crate) struct PluginRadialDefinition;
169
170impl Plugin for PluginRadialDefinition {
171    fn can_handle(&self, context: ContextCanHandle) -> bool {
172        match context.modifier {
173            Modifier::Builtin { value, .. } => {
174                let (gradient_type, interpolation_mode) = if let Some(index) = value.find('/') {
175                    let (before, after) = value.split_at(index);
176                    (before, &after[1..])
177                } else {
178                    (*value, "oklab")
179                };
180
181                gradient_type.is_empty() && INTERPOLATION_MODES.contains(&interpolation_mode)
182            }
183            Modifier::Arbitrary { value, .. } => is_matching_all(value),
184        }
185    }
186
187    fn handle(&self, context: &mut ContextHandle) {
188        match context.modifier {
189            Modifier::Builtin { value, .. } => {
190                let (_, interpolation_mode) = if let Some(index) = value.find('/') {
191                    let (before, after) = value.split_at(index);
192                    (before, &after[1..])
193                } else {
194                    // The interpolation mode defaults to `oklab`
195                    (*value, "oklab")
196                };
197
198                let interpolation_mode = match interpolation_mode {
199                    "longer" | "shorter" | "increasing" | "decreasing" => {
200                        Cow::Owned(format!("oklch {interpolation_mode} hue"))
201                    }
202                    _ => Cow::Borrowed(interpolation_mode),
203                };
204
205                context
206                    .buffer
207                    .line(format_args!("background-image: radial-gradient(in {interpolation_mode}, var(--en-gradient-stops));"));
208            }
209            Modifier::Arbitrary { value, .. } => {
210                context
211                    .buffer
212                    .line(format_args!("background-image: radial-gradient({value});"));
213            }
214        }
215    }
216}
217
218#[derive(Debug)]
219pub(crate) struct PluginConicDefinition;
220
221impl Plugin for PluginConicDefinition {
222    fn can_handle(&self, context: ContextCanHandle) -> bool {
223        match context.modifier {
224            Modifier::Builtin { value, .. } => {
225                let (gradient_type, interpolation_mode) = if let Some(index) = value.find('/') {
226                    let (before, after) = value.split_at(index);
227                    (before, &after[1..])
228                } else {
229                    (*value, "oklab")
230                };
231
232                (gradient_type.is_empty() || gradient_type.parse::<usize>().is_ok())
233                    && INTERPOLATION_MODES.contains(&interpolation_mode)
234            }
235            Modifier::Arbitrary { value, .. } => is_matching_all(value),
236        }
237    }
238
239    fn handle(&self, context: &mut ContextHandle) {
240        match context.modifier {
241            Modifier::Builtin { is_negative, value } => {
242                let (gradient_type, interpolation_mode) = if let Some(index) = value.find('/') {
243                    let (before, after) = value.split_at(index);
244                    (before, &after[1..])
245                } else {
246                    // The interpolation mode defaults to `oklab`
247                    (*value, "oklab")
248                };
249
250                let interpolation_mode = match interpolation_mode {
251                    "longer" | "shorter" | "increasing" | "decreasing" => {
252                        Cow::Owned(format!("oklch {interpolation_mode} hue"))
253                    }
254                    _ => Cow::Borrowed(interpolation_mode),
255                };
256
257                if gradient_type.is_empty() {
258                    context
259                        .buffer
260                        .line(format_args!("background-image: conic-gradient(in {interpolation_mode}, var(--en-gradient-stops));"));
261                } else {
262                    let angle = value.parse::<usize>().unwrap();
263                    context
264                        .buffer
265                        .line(format_args!("background-image: conic-gradient(from {}{angle}deg in {interpolation_mode}, var(--en-gradient-stops));", if *is_negative { "-" } else { "" }));
266                }
267            }
268            Modifier::Arbitrary { value, .. } => {
269                context
270                    .buffer
271                    .line(format_args!("background-image: conic-gradient({value});"));
272            }
273        }
274    }
275}