Skip to main content

euv_chart/paint/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`Color`].
4impl Color {
5    /// Returns an opaque colour from its red, green, and blue channels.
6    ///
7    /// # Arguments
8    ///
9    /// - `u8` - A `u8` parameter.
10    pub fn rgb(red: u8, green: u8, blue: u8) -> Self {
11        Self {
12            red,
13            green,
14            blue,
15            alpha: 255,
16        }
17    }
18
19    /// Returns a colour from its red, green, blue, and alpha channels.
20    ///
21    /// # Arguments
22    ///
23    /// - `u8` - A `u8` parameter.
24    pub fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
25        Self {
26            red,
27            green,
28            blue,
29            alpha,
30        }
31    }
32
33    /// Returns a colour from a packed `0xRRGGBBAA` value.
34    ///
35    /// # Arguments
36    ///
37    /// - `u32` - A 32-bit unsigned integer (`u32`).
38    pub fn from_u32(packed: u32) -> Self {
39        Self {
40            red: ((packed >> 24) & 0xFF) as u8,
41            green: ((packed >> 16) & 0xFF) as u8,
42            blue: ((packed >> 8) & 0xFF) as u8,
43            alpha: (packed & 0xFF) as u8,
44        }
45    }
46
47    /// Returns the colour packed as `0xRRGGBBAA`.
48    ///
49    /// # Returns
50    ///
51    /// - `u32` - A 32-bit unsigned integer.
52    pub fn to_u32(self) -> u32 {
53        ((self.get_red() as u32) << 24)
54            | ((self.get_green() as u32) << 16)
55            | ((self.get_blue() as u32) << 8)
56            | (self.get_alpha() as u32)
57    }
58
59    /// Returns the colour as a CSS `rgba()` string.
60    ///
61    /// # Returns
62    ///
63    /// - `String` - A `String` value.
64    pub fn to_css(self) -> String {
65        if self.get_alpha() == 255 {
66            format!(
67                "#{:02x}{:02x}{:02x}",
68                self.get_red(),
69                self.get_green(),
70                self.get_blue()
71            )
72        } else {
73            format!(
74                "rgba({},{},{},{})",
75                self.get_red(),
76                self.get_green(),
77                self.get_blue(),
78                format!("{:.3}", self.get_alpha() as f64 / 255.0)
79                    .trim_end_matches('0')
80                    .trim_end_matches('.')
81            )
82        }
83    }
84
85    /// Returns the colour that lies between two colours.
86    ///
87    /// The channels are mixed one by one, which is what the ramp of a
88    /// visual map does: the middle of the ramp is the middle of its
89    /// two ends.
90    ///
91    /// # Arguments
92    ///
93    /// - `f64` - The opposite endpoint of the interpolation.
94    /// - `Self` - The opposite endpoint of the interpolation.
95    pub fn lerp(self, other: Self, amount: f64) -> Self {
96        let amount: f64 = amount.clamp(0.0, 1.0);
97        let mix = move |from: u8, to: u8| {
98            (from as f64 + (to as f64 - from as f64) * amount)
99                .round()
100                .clamp(0.0, 255.0) as u8
101        };
102        Self {
103            red: mix(self.get_red(), other.get_red()),
104            green: mix(self.get_green(), other.get_green()),
105            blue: mix(self.get_blue(), other.get_blue()),
106            alpha: mix(self.get_alpha(), other.get_alpha()),
107        }
108    }
109
110    /// Parses a CSS colour string.
111    ///
112    /// The accepted forms are the ones ECharts accepts: `#rgb`, `#rgba`,
113    /// `#rrggbb`, `#rrggbbaa`, `rgb()`, `rgba()`, `hsl()`, `hsla()`,
114    /// `transparent`, and the CSS named colours. Returns `None` when the
115    /// string is not a colour.
116    ///
117    /// # Arguments
118    ///
119    /// - `&str` - Shared reference to a `str`.
120    ///
121    /// # Returns
122    ///
123    /// - `Option<Self>` - `Some(...)` on success, `None` otherwise.
124    pub fn parse(input: &str) -> Option<Self> {
125        parse_css_color(input)
126    }
127
128    /// Returns the colour with its alpha replaced.
129    ///
130    /// # Arguments
131    ///
132    /// - `u8` - A `u8` parameter.
133    pub fn with_alpha(self, alpha: u8) -> Self {
134        Self { alpha, ..self }
135    }
136
137    /// Returns the colour mixed with another one.
138    ///
139    /// `ratio` is the weight of `other`: `0.0` returns this colour
140    /// unchanged, `1.0` returns `other`.
141    ///
142    /// # Arguments
143    ///
144    /// - `f64` - A 64-bit float (`f64`).
145    /// - `Self` - A `Self` parameter.
146    pub fn mix(self, other: Self, ratio: f64) -> Self {
147        let ratio: f64 = ratio.clamp(0.0, 1.0);
148        let blend = move |a: u8, b: u8| (a as f64 * (1.0 - ratio) + b as f64 * ratio).round() as u8;
149        Self {
150            red: blend(self.get_red(), other.get_red()),
151            green: blend(self.get_green(), other.get_green()),
152            blue: blend(self.get_blue(), other.get_blue()),
153            alpha: blend(self.get_alpha(), other.get_alpha()),
154        }
155    }
156
157    /// Returns the colour brightened towards white.
158    ///
159    /// # Arguments
160    ///
161    /// - `f64` - A 64-bit float (`f64`).
162    pub fn lighten(self, ratio: f64) -> Self {
163        self.mix(Self::rgb(255, 255, 255), ratio)
164    }
165
166    /// Returns the colour darkened towards black.
167    ///
168    /// # Arguments
169    ///
170    /// - `f64` - A 64-bit float (`f64`).
171    pub fn darken(self, ratio: f64) -> Self {
172        self.mix(Self::rgb(0, 0, 0), ratio)
173    }
174}
175
176/// Inherent implementation of [`LinearGradient`].
177impl LinearGradient {
178    /// Returns a linear gradient between the two points.
179    ///
180    /// # Arguments
181    ///
182    /// - `(f64, f64)` - A `(f64, f64)` parameter.
183    pub fn new(start: (f64, f64), end: (f64, f64)) -> Self {
184        Self {
185            start,
186            end,
187            stops: Vec::new(),
188        }
189    }
190
191    /// Adds a colour stop and returns the gradient.
192    ///
193    /// # Arguments
194    ///
195    /// - `f64` - A 64-bit float (`f64`).
196    /// - `Color` - A `Color` parameter.
197    pub fn with_stop(mut self, offset: f64, color: Color) -> Self {
198        self.get_mut_stops().push(GradientStop { offset, color });
199        self.get_mut_stops()
200            .sort_by(|a: &GradientStop, b: &GradientStop| {
201                a.offset
202                    .partial_cmp(&b.offset)
203                    .unwrap_or(core::cmp::Ordering::Equal)
204            });
205        self
206    }
207}
208
209/// Inherent implementation of [`RadialGradient`].
210impl RadialGradient {
211    /// Returns a radial gradient at the given centre and radius.
212    ///
213    /// # Arguments
214    ///
215    /// - `(f64, f64)` - A `(f64, f64)` parameter.
216    /// - `f64` - A 64-bit float (`f64`).
217    pub fn new(center: (f64, f64), radius: f64) -> Self {
218        Self {
219            center,
220            radius,
221            stops: Vec::new(),
222        }
223    }
224
225    /// Adds a colour stop and returns the gradient.
226    ///
227    /// # Arguments
228    ///
229    /// - `f64` - A 64-bit float (`f64`).
230    /// - `Color` - A `Color` parameter.
231    pub fn with_stop(mut self, offset: f64, color: Color) -> Self {
232        self.get_mut_stops().push(GradientStop { offset, color });
233        self.get_mut_stops()
234            .sort_by(|a: &GradientStop, b: &GradientStop| {
235                a.offset
236                    .partial_cmp(&b.offset)
237                    .unwrap_or(core::cmp::Ordering::Equal)
238            });
239        self
240    }
241}
242
243/// Inherent implementation of [`TextStyle`].
244impl TextStyle {
245    /// Returns the default text style.
246    pub fn new() -> Self {
247        Self {
248            family: DEFAULT_FONT_FAMILY.to_string(),
249            size: 12.0,
250            weight: DEFAULT_FONT_WEIGHT.to_string(),
251            style: DEFAULT_FONT_STYLE.to_string(),
252            align: TextAlign::default(),
253            vertical_align: TextVerticalAlign::default(),
254            line_height: 1.2,
255            wrap: false,
256            overflow: TextOverflow::default(),
257            color: Color::rgb(51, 51, 51),
258            background: None,
259            padding: (0.0, 0.0, 0.0, 0.0),
260            border_width: 0.0,
261            border_color: None,
262            border_radius: 0.0,
263            rotate: 0.0,
264            stroke_width: 0.0,
265            stroke_color: None,
266            rich: Vec::new(),
267        }
268    }
269
270    /// Returns the CSS `font` shorthand for this style.
271    ///
272    /// # Returns
273    ///
274    /// - `String` - A `String` value.
275    pub fn font(&self) -> String {
276        format!(
277            "{} {} {} {}",
278            self.get_style(),
279            self.get_weight(),
280            self.get_size(),
281            self.get_family()
282        )
283    }
284
285    /// Returns the line height in pixels.
286    ///
287    /// # Returns
288    ///
289    /// - `f64` - A 64-bit float.
290    pub fn line_height_px(&self) -> f64 {
291        self.get_size() * self.get_line_height()
292    }
293
294    /// Adds a rich-text run and returns the style.
295    ///
296    /// # Arguments
297    ///
298    /// - `&str` - Shared reference to a `str`.
299    /// - `RichTextStyle` - A `RichTextStyle` parameter.
300    pub fn with_rich(mut self, name: &str, style: RichTextStyle) -> Self {
301        self.get_mut_rich().push((name.to_string(), style));
302        self
303    }
304}
305
306/// Default construction for [`TextStyle`].
307impl Default for TextStyle {
308    /// Constructs a default [`TextStyle`] value.
309    fn default() -> Self {
310        Self::new()
311    }
312}
313
314/// Inherent implementation of [`RichTextStyle`].
315impl RichTextStyle {
316    /// Returns an empty rich-text run style.
317    pub fn new() -> Self {
318        Self {
319            family: None,
320            size: None,
321            weight: None,
322            style: None,
323            color: None,
324            background: None,
325            padding: None,
326            line_height: None,
327        }
328    }
329}
330
331/// Default construction for [`RichTextStyle`].
332impl Default for RichTextStyle {
333    /// Constructs a default [`RichTextStyle`] value.
334    fn default() -> Self {
335        Self::new()
336    }
337}
338
339/// Inherent implementation of [`LineStyle`].
340impl LineStyle {
341    /// Returns the default line style.
342    pub fn new() -> Self {
343        Self {
344            width: 1.0,
345            cap: LineCap::default(),
346            join: LineJoin::default(),
347            dash: DashPattern::solid(),
348            opacity: 1.0,
349            shadow: None,
350            color: Color::rgb(51, 51, 51),
351        }
352    }
353
354    /// Returns the geometry parameters the tessellator needs.
355    ///
356    /// # Returns
357    ///
358    /// - `StrokeStyle` - A `StrokeStyle` value.
359    pub fn geometry(&self) -> StrokeStyle {
360        StrokeStyle {
361            width: self.get_width() as f32,
362            cap: *self.get_cap(),
363            join: *self.get_join(),
364            dash: self.get_dash().clone(),
365        }
366    }
367
368    /// Returns the dash pattern for the given dash type.
369    ///
370    /// # Arguments
371    ///
372    /// - `LineDashType` - A `LineDashType` parameter.
373    /// - `f64` - A 64-bit float (`f64`).
374    ///
375    /// # Returns
376    ///
377    /// - `DashPattern` - A `DashPattern` value.
378    pub fn dash_for(dash_type: LineDashType, width: f64) -> DashPattern {
379        let width: f32 = width as f32;
380        match dash_type {
381            LineDashType::Solid => DashPattern::solid(),
382            LineDashType::Dashed => DashPattern::new(vec![width * 5.0, width * 5.0], 0.0),
383            LineDashType::Dotted => DashPattern::new(vec![width, width * 2.0], 0.0),
384        }
385    }
386}
387
388/// Default construction for [`LineStyle`].
389impl Default for LineStyle {
390    /// Constructs a default [`LineStyle`] value.
391    fn default() -> Self {
392        Self::new()
393    }
394}
395
396/// Inherent implementation of [`ShapeStyle`].
397impl ShapeStyle {
398    /// Returns the default shape style: a transparent fill and a visible
399    /// stroke.
400    pub fn new() -> Self {
401        Self {
402            fill: Fill::None,
403            stroke: Stroke::None,
404            line_style: LineStyle::new(),
405            shadow: None,
406            opacity: 1.0,
407        }
408    }
409}
410
411/// Default construction for [`ShapeStyle`].
412impl Default for ShapeStyle {
413    /// Constructs a default [`ShapeStyle`] value.
414    fn default() -> Self {
415        Self::new()
416    }
417}
418
419/// Inherent implementation of [`Fill`].
420impl Fill {
421    /// Returns the fill's solid colour, when the fill is solid.
422    ///
423    /// # Returns
424    ///
425    /// - `Option<Color>` - `Some(...)` on success, `None` otherwise.
426    pub fn solid_color(&self) -> Option<Color> {
427        match self {
428            Fill::Solid(color) => Some(*color),
429            _ => None,
430        }
431    }
432}
433
434/// Inherent implementation of [`Stroke`].
435impl Stroke {
436    /// Returns the stroke's solid colour, when the stroke is solid.
437    ///
438    /// # Returns
439    ///
440    /// - `Option<Color>` - `Some(...)` on success, `None` otherwise.
441    pub fn solid_color(&self) -> Option<Color> {
442        match self {
443            Stroke::Solid(color) => Some(*color),
444            _ => None,
445        }
446    }
447}