Skip to main content

theframework/
thedraw2d.rs

1use std::ops::Deref;
2
3use fontdue::layout::{
4    CoordinateSystem, GlyphPosition, HorizontalAlign, Layout, LayoutSettings, TextStyle,
5    VerticalAlign,
6};
7use fontdue::{Font, Metrics};
8use vek::*;
9
10use crate::Embedded;
11
12#[derive(Default, Clone)]
13pub enum TheFontPreference {
14    #[default]
15    Default,
16    Code,
17}
18
19#[derive(PartialEq, Clone, Eq)]
20pub enum TheHorizontalAlign {
21    Left,
22    Center,
23    Right,
24}
25
26#[derive(PartialEq)]
27pub enum TheVerticalAlign {
28    Top,
29    Center,
30    Bottom,
31}
32
33#[derive(Default)]
34pub struct TheFontSettings {
35    pub preference: TheFontPreference,
36    pub size: f32,
37}
38
39#[derive(Debug)]
40pub struct TheDraw2D {
41    pub mask: Option<Vec<f32>>,
42    pub mask_size: (usize, usize),
43    pub fonts: Vec<Font>,
44    pub code_fonts: Vec<Font>,
45}
46
47impl Default for TheDraw2D {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl TheDraw2D {
54    pub fn new() -> Self {
55        let mut fonts = vec![];
56        if let Some(font_bytes) = Embedded::get("fonts/Roboto-Bold.ttf") {
57            if let Ok(f) = Font::from_bytes(font_bytes.data, fontdue::FontSettings::default()) {
58                fonts.push(f);
59            }
60        }
61
62        let mut code_fonts = vec![];
63        if let Some(font_bytes) = Embedded::get("fonts/SourceCodePro-Bold.ttf") {
64            if let Ok(f) = Font::from_bytes(font_bytes.data, fontdue::FontSettings::default()) {
65                code_fonts.push(f);
66            }
67        }
68
69        Self {
70            mask: None,
71            mask_size: (0, 0),
72            fonts,
73            code_fonts,
74        }
75    }
76
77    /// Draws the mask
78    pub fn blend_mask(
79        &self,
80        frame: &mut [u8],
81        rect: &(usize, usize, usize, usize),
82        stride: usize,
83        mask_frame: &[u8],
84        mask_size: &(usize, usize),
85        color: &[u8; 4],
86    ) {
87        for y in 0..mask_size.1 {
88            for x in 0..mask_size.0 {
89                let i = (x + rect.0) * 4 + (y + rect.1) * stride * 4;
90                let m = mask_frame[x + y * mask_size.0];
91                let c: [u8; 4] = [color[0], color[1], color[2], m];
92
93                let background = &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
94                frame[i..i + 4].copy_from_slice(&self.mix_color(background, &c, m as f32 / 255.0));
95            }
96        }
97    }
98
99    /// Draws the given rectangle
100    pub fn rect(
101        &self,
102        frame: &mut [u8],
103        rect: &(usize, usize, usize, usize),
104        stride: usize,
105        color: &[u8; 4],
106    ) {
107        for y in rect.1..rect.1 + rect.3 {
108            for x in rect.0..rect.0 + rect.2 {
109                let i = x * 4 + y * stride * 4;
110                frame[i..i + 4].copy_from_slice(color);
111            }
112        }
113    }
114
115    /// Draws the given rectangle and checks the frame boundaries.
116    pub fn rect_safe(
117        &self,
118        frame: &mut [u8],
119        rect: &(isize, isize, usize, usize),
120        stride: usize,
121        color: &[u8; 4],
122        safe_rect: &(usize, usize, usize, usize),
123    ) {
124        let dest_stride_isize: isize = stride as isize;
125        for y in rect.1..rect.1 + rect.3 as isize {
126            if y >= safe_rect.1 as isize && y < (safe_rect.1 + safe_rect.3) as isize {
127                for x in rect.0..rect.0 + rect.2 as isize {
128                    if x >= safe_rect.0 as isize && x < (safe_rect.0 + safe_rect.2) as isize {
129                        let i = (x * 4 + y * dest_stride_isize * 4) as usize;
130                        frame[i..i + 4].copy_from_slice(color);
131                    }
132                }
133            }
134        }
135    }
136
137    /// Blend the given rectangle
138    pub fn blend_rect(
139        &self,
140        frame: &mut [u8],
141        rect: &(usize, usize, usize, usize),
142        stride: usize,
143        color: &[u8; 4],
144    ) {
145        for y in rect.1..rect.1 + rect.3 {
146            for x in rect.0..rect.0 + rect.2 {
147                let i = x * 4 + y * stride * 4;
148
149                let background = &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
150                frame[i..i + 4].copy_from_slice(&self.mix_color(
151                    background,
152                    color,
153                    color[3] as f32 / 255.0,
154                ));
155            }
156        }
157    }
158
159    /// Draws the outline of a given rectangle
160    pub fn rect_outline(
161        &self,
162        frame: &mut [u8],
163        rect: &(usize, usize, usize, usize),
164        stride: usize,
165        color: &[u8; 4],
166    ) {
167        let y = rect.1;
168        for x in rect.0..rect.0 + rect.2 {
169            let mut i = x * 4 + y * stride * 4;
170            frame[i..i + 4].copy_from_slice(color);
171
172            i = x * 4 + (y + rect.3 - 1) * stride * 4;
173            frame[i..i + 4].copy_from_slice(color);
174        }
175
176        let x = rect.0;
177        for y in rect.1..rect.1 + rect.3 {
178            let mut i = x * 4 + y * stride * 4;
179            frame[i..i + 4].copy_from_slice(color);
180
181            i = (x + rect.2 - 1) * 4 + y * stride * 4;
182            frame[i..i + 4].copy_from_slice(color);
183        }
184    }
185
186    /// Draws the outline of a given rectangle
187    pub fn rect_outline_border(
188        &self,
189        frame: &mut [u8],
190        rect: &(usize, usize, usize, usize),
191        stride: usize,
192        color: &[u8; 4],
193        border: usize,
194    ) {
195        let y = rect.1;
196        for x in rect.0 + border..rect.0 + rect.2 - border {
197            let mut i = x * 4 + y * stride * 4;
198            frame[i..i + 4].copy_from_slice(color);
199
200            i = x * 4 + (y + rect.3 - 1) * stride * 4;
201            frame[i..i + 4].copy_from_slice(color);
202        }
203
204        let x = rect.0;
205        for y in rect.1 + border..rect.1 + rect.3 - border {
206            let mut i = x * 4 + y * stride * 4;
207            frame[i..i + 4].copy_from_slice(color);
208
209            i = (x + rect.2 - 1) * 4 + y * stride * 4;
210            frame[i..i + 4].copy_from_slice(color);
211        }
212    }
213
214    /// Draws the outline of a given rectangle with the right open
215    pub fn rect_outline_border_open(
216        &self,
217        frame: &mut [u8],
218        rect: &(usize, usize, usize, usize),
219        stride: usize,
220        color: &[u8; 4],
221        border: usize,
222    ) {
223        let y = rect.1;
224        for x in rect.0 + border..rect.0 + rect.2 - border {
225            let mut i = x * 4 + y * stride * 4;
226            frame[i..i + 4].copy_from_slice(color);
227
228            i = x * 4 + (y + rect.3 - 1) * stride * 4;
229            frame[i..i + 4].copy_from_slice(color);
230        }
231
232        let x = rect.0;
233        for y in rect.1 + border..rect.1 + rect.3 - border {
234            let i = x * 4 + y * stride * 4;
235            frame[i..i + 4].copy_from_slice(color);
236        }
237    }
238
239    /// Draws the outline of a given rectangle
240    pub fn rect_outline_border_safe(
241        &self,
242        frame: &mut [u8],
243        rect: &(isize, isize, usize, usize),
244        stride: usize,
245        color: &[u8; 4],
246        border: isize,
247        safe_rect: &(usize, usize, usize, usize),
248    ) {
249        let dest_stride_isize: isize = stride as isize;
250        let y = rect.1;
251        if y >= safe_rect.1 as isize && y < (safe_rect.1 + safe_rect.3) as isize {
252            for x in rect.0 + border..rect.0 + rect.2 as isize - border {
253                if x >= safe_rect.0 as isize && x < (safe_rect.0 + safe_rect.2) as isize {
254                    let mut i = (x * 4 + y * dest_stride_isize * 4) as usize;
255                    frame[i..i + 4].copy_from_slice(color);
256
257                    if (y + rect.3 as isize - 1) >= safe_rect.1 as isize
258                        && (y + rect.3 as isize - 1) < (safe_rect.1 + safe_rect.3) as isize
259                    {
260                        i = (x * 4 + (y + rect.3 as isize - 1) * dest_stride_isize * 4) as usize;
261                        frame[i..i + 4].copy_from_slice(color);
262                    }
263                }
264            }
265        }
266
267        let x = rect.0;
268        if x >= safe_rect.0 as isize && x < (safe_rect.0 + safe_rect.2) as isize {
269            for y in rect.1 + border..rect.1 + rect.3 as isize - border {
270                if y >= safe_rect.1 as isize && y < (safe_rect.1 + safe_rect.3) as isize {
271                    let mut i = (x * 4 + y * dest_stride_isize * 4) as usize;
272                    frame[i..i + 4].copy_from_slice(color);
273
274                    if (x + rect.2 as isize - 1) >= safe_rect.0 as isize
275                        && (x + rect.2 as isize - 1) < (safe_rect.0 + safe_rect.2) as isize
276                    {
277                        i = ((x + rect.2 as isize - 1) * 4 + y * dest_stride_isize * 4) as usize;
278                        frame[i..i + 4].copy_from_slice(color);
279                    }
280                }
281            }
282        }
283    }
284
285    /// Draws a circle
286    pub fn circle(
287        &self,
288        frame: &mut [u8],
289        rect: &(usize, usize, usize, usize),
290        stride: usize,
291        color: &[u8; 4],
292        radius: f32,
293    ) {
294        let center = (
295            rect.0 as f32 + rect.2 as f32 / 2.0,
296            rect.1 as f32 + rect.3 as f32 / 2.0,
297        );
298        for y in rect.1..rect.1 + rect.3 {
299            for x in rect.0..rect.0 + rect.2 {
300                let i = x * 4 + y * stride * 4;
301
302                let mut d = (x as f32 - center.0).powf(2.0) + (y as f32 - center.1).powf(2.0);
303                d = d.sqrt() - radius;
304
305                if d <= 0.0 {
306                    // let t = self.fill_mask(d);
307                    let t = self._smoothstep(0.0, -2.0, d);
308
309                    let background = &[frame[i], frame[i + 1], frame[i + 2], 255];
310                    let mixed_color = self.mix_color(background, color, t);
311
312                    frame[i..i + 4].copy_from_slice(&mixed_color);
313                }
314            }
315        }
316    }
317
318    #[allow(clippy::too_many_arguments)]
319    /// Draws a circle with a border of a given size
320    pub fn circle_with_border(
321        &self,
322        frame: &mut [u8],
323        rect: &(usize, usize, usize, usize),
324        stride: usize,
325        color: &[u8; 4],
326        radius: f32,
327        border_color: &[u8; 4],
328        border_size: f32,
329    ) {
330        let center = (
331            rect.0 as f32 + rect.2 as f32 / 2.0,
332            rect.1 as f32 + rect.3 as f32 / 2.0,
333        );
334        for y in rect.1..rect.1 + rect.3 {
335            for x in rect.0..rect.0 + rect.2 {
336                let i = x * 4 + y * stride * 4;
337
338                let mut d = (x as f32 - center.0).powf(2.0) + (y as f32 - center.1).powf(2.0);
339                d = d.sqrt() - radius;
340
341                if d < 1.0 {
342                    let t = self.fill_mask(d);
343
344                    let background = &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
345                    let mut mixed_color = self.mix_color(background, color, t);
346
347                    let b = self.border_mask(d, border_size);
348                    mixed_color = self.mix_color(&mixed_color, border_color, b);
349
350                    frame[i..i + 4].copy_from_slice(&mixed_color);
351                }
352            }
353        }
354    }
355
356    /// Draws a rounded rect
357    pub fn rounded_rect(
358        &self,
359        frame: &mut [u8],
360        rect: &(usize, usize, usize, usize),
361        stride: usize,
362        color: &[u8; 4],
363        rounding: &(f32, f32, f32, f32),
364    ) {
365        let center = (
366            (rect.0 as f32 + rect.2 as f32 / 2.0).round(),
367            (rect.1 as f32 + rect.3 as f32 / 2.0).round(),
368        );
369        for y in rect.1..rect.1 + rect.3 {
370            for x in rect.0..rect.0 + rect.2 {
371                let i = x * 4 + y * stride * 4;
372
373                let p = (x as f32 - center.0, y as f32 - center.1);
374                let mut r: (f32, f32);
375
376                if p.0 > 0.0 {
377                    r = (rounding.0, rounding.1);
378                } else {
379                    r = (rounding.2, rounding.3);
380                }
381
382                if p.1 <= 0.0 {
383                    r.0 = r.1;
384                }
385
386                let q: (f32, f32) = (
387                    p.0.abs() - rect.2 as f32 / 2.0 + r.0,
388                    p.1.abs() - rect.3 as f32 / 2.0 + r.0,
389                );
390                let d = f32::min(f32::max(q.0, q.1), 0.0)
391                    + self.length((f32::max(q.0, 0.0), f32::max(q.1, 0.0)))
392                    - r.0;
393
394                if d < 0.0 {
395                    let t = self.fill_mask(d);
396
397                    let background = &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
398                    let mut mixed_color =
399                        self.mix_color(background, color, t * (color[3] as f32 / 255.0));
400                    mixed_color[3] = (mixed_color[3] as f32 * (color[3] as f32 / 255.0)) as u8;
401                    frame[i..i + 4].copy_from_slice(&mixed_color);
402                }
403            }
404        }
405    }
406
407    #[allow(clippy::too_many_arguments)]
408    /// Draws a rounded rect with a border
409    pub fn rounded_rect_with_border(
410        &self,
411        frame: &mut [u8],
412        rect: &(usize, usize, usize, usize),
413        stride: usize,
414        color: &[u8; 4],
415        rounding: &(f32, f32, f32, f32),
416        border_color: &[u8; 4],
417        border_size: f32,
418    ) {
419        let hb = border_size / 2.0;
420        let center = (
421            (rect.0 as f32 + rect.2 as f32 / 2.0 - hb).round(),
422            (rect.1 as f32 + rect.3 as f32 / 2.0 - hb).round(),
423        );
424        for y in rect.1..rect.1 + rect.3 {
425            for x in rect.0..rect.0 + rect.2 {
426                let i = x * 4 + y * stride * 4;
427
428                let p = (x as f32 - center.0, y as f32 - center.1);
429                let mut r: (f32, f32);
430
431                if p.0 > 0.0 {
432                    r = (rounding.0, rounding.1);
433                } else {
434                    r = (rounding.2, rounding.3);
435                }
436
437                if p.1 <= 0.0 {
438                    r.0 = r.1;
439                }
440
441                let q: (f32, f32) = (
442                    p.0.abs() - rect.2 as f32 / 2.0 + hb + r.0,
443                    p.1.abs() - rect.3 as f32 / 2.0 + hb + r.0,
444                );
445                let d = f32::min(f32::max(q.0, q.1), 0.0)
446                    + self.length((f32::max(q.0, 0.0), f32::max(q.1, 0.0)))
447                    - r.0;
448
449                if d < 1.0 {
450                    let t = self.fill_mask(d);
451
452                    let background = &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
453                    let mut mixed_color =
454                        self.mix_color(background, color, t * (color[3] as f32 / 255.0));
455
456                    let b = self.border_mask(d, border_size);
457                    mixed_color = self.mix_color(&mixed_color, border_color, b);
458
459                    frame[i..i + 4].copy_from_slice(&mixed_color);
460                }
461            }
462        }
463    }
464
465    #[allow(clippy::too_many_arguments)]
466    /// Draws a hexagon with a border
467    pub fn hexagon_with_border(
468        &self,
469        frame: &mut [u8],
470        rect: &(usize, usize, usize, usize),
471        stride: usize,
472        color: &[u8; 4],
473        border_color: &[u8; 4],
474        border_size: f32,
475    ) {
476        let hb = border_size / 2.0;
477        let center = (
478            (rect.0 as f32 + rect.2 as f32 / 2.0 - hb).round(),
479            (rect.1 as f32 + rect.3 as f32 / 2.0 - hb).round(),
480        );
481        for y in rect.1..rect.1 + rect.3 {
482            for x in rect.0..rect.0 + rect.2 {
483                let i = x * 4 + y * stride * 4;
484
485                let mut p: Vec2<f32> =
486                    Vec2::new((x as f32 - center.0).abs(), (y as f32 - center.1).abs());
487                let r = rect.2 as f32 / 2.33;
488                let k: Vec3<f32> = Vec3::new(-0.866_025_4, 0.5, 0.577_350_26);
489                p -= 2.0 * k.xy() * k.xy().dot(p).min(0.0);
490                p = p.clamped(Vec2::broadcast(-k.z * r), Vec2::broadcast(k.z * r));
491                let d = p.magnitude() * p.y.signum();
492
493                if d < 1.0 {
494                    let t = self.fill_mask(d);
495                    // let t = self._smoothstep(0.0, -2.0, d);
496
497                    let background: &[u8; 4] =
498                        &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
499                    let mut mixed_color =
500                        self.mix_color(background, color, t * (color[3] as f32 / 255.0));
501
502                    let b = self.border_mask(d, border_size);
503                    mixed_color = self.mix_color(&mixed_color, border_color, b);
504
505                    frame[i..i + 4].copy_from_slice(&mixed_color);
506                }
507            }
508        }
509    }
510
511    #[allow(clippy::too_many_arguments)]
512    /// Draws a rhombus rect with a border
513    pub fn rhombus_with_border(
514        &self,
515        frame: &mut [u8],
516        rect: &(usize, usize, usize, usize),
517        stride: usize,
518        color: &[u8; 4],
519        border_color: &[u8; 4],
520        border_size: f32,
521    ) {
522        let hb = border_size / 2.0;
523        let center = (
524            (rect.0 as f32 + rect.2 as f32 / 2.0 - hb).round(),
525            (rect.1 as f32 + rect.3 as f32 / 2.0 - hb).round(),
526        );
527
528        // fn ndot(a: Vec2<f32>, b: Vec2<f32>) -> f32 {
529        //     a.x * b.x - a.y * b.y
530        // }
531
532        for y in rect.1..rect.1 + rect.3 {
533            for x in rect.0..rect.0 + rect.2 {
534                let i = x * 4 + y * stride * 4;
535
536                /*
537                float ndot(vec2 a, vec2 b ) { return a.x*b.x - a.y*b.y; }
538                float sdRhombus( in vec2 p, in vec2 b )
539                {
540                    p = abs(p);
541                    float h = clamp( ndot(b-2.0*p,b)/dot(b,b), -1.0, 1.0 );
542                    float d = length( p-0.5*b*vec2(1.0-h,1.0+h) );
543                    return d * sign( p.x*b.y + p.y*b.x - b.x*b.y );
544                }*/
545
546                let p = Vec2::new((x as f32 - center.0).abs(), (y as f32 - center.1).abs());
547                let b = Vec2::new(rect.2 as f32 / 2.0, rect.3 as f32 / 2.0);
548
549                let h = (Vec2::dot(b - 2.0 * p, b) / Vec2::dot(b, b)).clamp(-1.0, 1.0);
550                let mut d = (p - 0.5 * b * Vec2::new(1.0 - h, 1.0 + h)).magnitude();
551                d *= (p.x * b.y + p.y * b.x - b.x * b.y).signum();
552
553                if d < 1.0 {
554                    let t = self.fill_mask(d);
555
556                    let background: &[u8; 4] =
557                        &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
558                    let mut mixed_color =
559                        self.mix_color(background, color, t * (color[3] as f32 / 255.0));
560
561                    let b = self.border_mask(d, border_size);
562                    mixed_color = self.mix_color(&mixed_color, border_color, b);
563
564                    frame[i..i + 4].copy_from_slice(&mixed_color);
565                }
566            }
567        }
568    }
569
570    /// Draws a square pattern
571    pub fn square_pattern(
572        &self,
573        frame: &mut [u8],
574        rect: &(usize, usize, usize, usize),
575        stride: usize,
576        color: &[u8; 4],
577        line_color: &[u8; 4],
578        pattern_size: usize,
579    ) {
580        for y in rect.1..rect.1 + rect.3 {
581            for x in rect.0..rect.0 + rect.2 {
582                let i = x * 4 + y * stride * 4;
583
584                if x % pattern_size == 0 || y % pattern_size == 0 {
585                    frame[i..i + 4].copy_from_slice(line_color);
586                } else {
587                    frame[i..i + 4].copy_from_slice(color);
588                }
589            }
590        }
591    }
592
593    #[allow(clippy::too_many_arguments)]
594    pub fn wavy_line(
595        &self,
596        frame: &mut [u8],
597        left: i32,
598        base: i32,
599        length: usize,
600        amplitude: f32,
601        _period: f32,
602        stride: usize,
603        color: &[u8; 4],
604    ) {
605        for x in left..left + length as i32 {
606            let y = ((x as f32).sin() * amplitude) as i32 + base;
607
608            let i = x * 4 + y * stride as i32 * 4;
609            if i < 0 {
610                continue;
611            }
612
613            let i = i as usize;
614            frame[i..i + 4].copy_from_slice(color);
615        }
616    }
617
618    #[allow(clippy::too_many_arguments)]
619    /// Draws a text aligned inside a rect
620    pub fn text_rect(
621        &self,
622        frame: &mut [u8],
623        rect: &(usize, usize, usize, usize),
624        stride: usize,
625        text: &str,
626        settings: TheFontSettings,
627        color: &[u8; 4],
628        background: &[u8; 4],
629        halign: TheHorizontalAlign,
630        valign: TheVerticalAlign,
631    ) {
632        let mut text_to_use = text.trim_end().to_string().clone();
633        text_to_use = text_to_use.replace('\n', "");
634        if text_to_use.trim_end().is_empty() {
635            return;
636        }
637
638        let mut text_size = self.get_text_size(text_to_use.as_str(), &settings);
639
640        let mut add_trail = false;
641        // Text is too long ??
642        while text_size.0 >= rect.2 {
643            text_to_use.pop();
644            text_size = self.get_text_size((text_to_use.clone() + "...").as_str(), &settings);
645            add_trail = true;
646        }
647
648        if add_trail {
649            text_to_use += "...";
650        }
651
652        let fonts = self.fonts_iter(&settings.preference);
653
654        let layout = self.get_text_layout(
655            &text_to_use,
656            &settings,
657            LayoutSettings {
658                max_width: Some(rect.2 as f32),
659                max_height: Some(rect.3 as f32),
660                horizontal_align: if halign == TheHorizontalAlign::Left {
661                    HorizontalAlign::Left
662                } else if halign == TheHorizontalAlign::Right {
663                    HorizontalAlign::Right
664                } else {
665                    HorizontalAlign::Center
666                },
667                vertical_align: if valign == TheVerticalAlign::Top {
668                    VerticalAlign::Top
669                } else if valign == TheVerticalAlign::Bottom {
670                    VerticalAlign::Bottom
671                } else {
672                    VerticalAlign::Middle
673                },
674                ..LayoutSettings::default()
675            },
676        );
677        for glyph in layout.glyphs() {
678            let Some((metrics, alphamap)) = self.rasterize_glyph(glyph, &fonts) else {
679                continue;
680            };
681
682            for y in 0..metrics.height {
683                for x in 0..metrics.width {
684                    let i = (x + rect.0 + glyph.x as usize) * 4
685                        + (y + rect.1 + glyph.y as usize) * stride * 4;
686                    let m = alphamap[x + y * metrics.width];
687
688                    frame[i..i + 4].copy_from_slice(&self.mix_color(
689                        background,
690                        color,
691                        m as f32 / 255.0,
692                    ));
693                }
694            }
695        }
696    }
697
698    #[allow(clippy::too_many_arguments)]
699    /// Draws a text aligned inside a rect
700    pub fn text_rect_clip(
701        &self,
702        frame: &mut [u8],
703        top_left: &Vec2<i32>,
704        clip_rect: &(usize, usize, usize, usize),
705        stride: usize,
706        text: &str,
707        settings: TheFontSettings,
708        color: &[u8; 4],
709        background: &[u8; 4],
710        halign: TheHorizontalAlign,
711        valign: TheVerticalAlign,
712    ) {
713        let mut text_to_use = text.trim_end().to_string().clone();
714        text_to_use = text_to_use.replace('\n', "");
715        if text_to_use.trim_end().is_empty() {
716            return;
717        }
718
719        let fonts = self.fonts_iter(&settings.preference);
720
721        let layout = self.get_text_layout(
722            &text_to_use,
723            &settings,
724            LayoutSettings {
725                horizontal_align: if halign == TheHorizontalAlign::Left {
726                    HorizontalAlign::Left
727                } else if halign == TheHorizontalAlign::Right {
728                    HorizontalAlign::Right
729                } else {
730                    HorizontalAlign::Center
731                },
732                vertical_align: if valign == TheVerticalAlign::Top {
733                    VerticalAlign::Top
734                } else if valign == TheVerticalAlign::Bottom {
735                    VerticalAlign::Bottom
736                } else {
737                    VerticalAlign::Middle
738                },
739                wrap_hard_breaks: false,
740                ..LayoutSettings::default()
741            },
742        );
743        for glyph in layout.glyphs() {
744            let Some((metrics, alphamap)) = self.rasterize_glyph(glyph, &fonts) else {
745                continue;
746            };
747
748            for y in 0..metrics.height {
749                for x in 0..metrics.width {
750                    let coord_x = top_left.x + (x + glyph.x.ceil() as usize) as i32;
751                    let coord_y = top_left.y + (y + glyph.y.ceil() as usize) as i32;
752                    if coord_x < 0 || coord_y < 0 {
753                        continue;
754                    }
755
756                    let coord_x = coord_x as usize;
757                    let coord_y = coord_y as usize;
758                    if coord_x < clip_rect.0
759                        || coord_x > clip_rect.0 + clip_rect.2
760                        || coord_y < clip_rect.1
761                        || coord_y > clip_rect.1 + clip_rect.3
762                    {
763                        continue;
764                    }
765
766                    let i = coord_x * 4 + coord_y * stride * 4;
767                    let m = alphamap[x + y * metrics.width];
768
769                    frame[i..i + 4].copy_from_slice(&self.mix_color(
770                        background,
771                        color,
772                        m as f32 / 255.0,
773                    ));
774                }
775            }
776        }
777    }
778
779    #[allow(clippy::too_many_arguments)]
780    /// Blends a text aligned inside a rect and blends it with the existing background
781    pub fn text_rect_blend(
782        &self,
783        frame: &mut [u8],
784        rect: &(usize, usize, usize, usize),
785        stride: usize,
786        text: &str,
787        settings: TheFontSettings,
788        color: &[u8; 4],
789        halign: TheHorizontalAlign,
790        valign: TheVerticalAlign,
791    ) {
792        let mut text_to_use = text.trim_end().to_string().clone();
793        if text_to_use.trim_end().is_empty() {
794            return;
795        }
796
797        let mut text_size = self.get_text_size(text_to_use.as_str(), &settings);
798
799        let mut add_trail = false;
800        // Text is too long ??
801        while text_size.0 >= rect.2 {
802            text_to_use.pop();
803            text_size = self.get_text_size((text_to_use.clone() + "...").as_str(), &settings);
804            add_trail = true;
805        }
806
807        if add_trail {
808            text_to_use += "...";
809        }
810
811        let fonts = self.fonts_iter(&settings.preference);
812
813        let layout = self.get_text_layout(
814            &text_to_use,
815            &settings,
816            LayoutSettings {
817                max_width: Some(rect.2 as f32),
818                max_height: Some(rect.3 as f32),
819                horizontal_align: if halign == TheHorizontalAlign::Left {
820                    HorizontalAlign::Left
821                } else if halign == TheHorizontalAlign::Right {
822                    HorizontalAlign::Right
823                } else {
824                    HorizontalAlign::Center
825                },
826                vertical_align: if valign == TheVerticalAlign::Top {
827                    VerticalAlign::Top
828                } else if valign == TheVerticalAlign::Bottom {
829                    VerticalAlign::Bottom
830                } else {
831                    VerticalAlign::Middle
832                },
833                ..LayoutSettings::default()
834            },
835        );
836        for glyph in layout.glyphs() {
837            let Some((metrics, alphamap)) = self.rasterize_glyph(glyph, &fonts) else {
838                continue;
839            };
840
841            for y in 0..metrics.height {
842                for x in 0..metrics.width {
843                    // if (y + rect.1) >= rect.1
844                    //     && (y + rect.1) < (rect.1 + rect.3)
845                    //     && (x + rect.0) >= rect.0
846                    //     && (x + rect.0) < (rect.0 + rect.2)
847                    // {
848
849                    let i = (x + rect.0 + glyph.x as usize) * 4
850                        + (y + rect.1 + glyph.y as usize) * stride * 4;
851                    let m = alphamap[x + y * metrics.width];
852
853                    let background = &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
854                    frame[i..i + 4].copy_from_slice(&self.mix_color(
855                        background,
856                        color,
857                        m as f32 / 255.0,
858                    ));
859                }
860            }
861        }
862    }
863
864    #[allow(clippy::too_many_arguments)]
865    /// Blends a text aligned inside a rect and blends it with the existing background
866    pub fn text_rect_blend_clip(
867        &self,
868        frame: &mut [u8],
869        top_left: &Vec2<i32>,
870        clip_rect: &(usize, usize, usize, usize),
871        stride: usize,
872        text: &str,
873        settings: TheFontSettings,
874        color: &[u8; 4],
875        halign: TheHorizontalAlign,
876        valign: TheVerticalAlign,
877    ) {
878        let text_to_use = text.trim_end().to_string().clone();
879        if text_to_use.trim_end().is_empty() {
880            return;
881        }
882
883        let fonts = self.fonts_iter(&settings.preference);
884
885        let layout = self.get_text_layout(
886            &text_to_use,
887            &settings,
888            LayoutSettings {
889                horizontal_align: if halign == TheHorizontalAlign::Left {
890                    HorizontalAlign::Left
891                } else if halign == TheHorizontalAlign::Right {
892                    HorizontalAlign::Right
893                } else {
894                    HorizontalAlign::Center
895                },
896                vertical_align: if valign == TheVerticalAlign::Top {
897                    VerticalAlign::Top
898                } else if valign == TheVerticalAlign::Bottom {
899                    VerticalAlign::Bottom
900                } else {
901                    VerticalAlign::Middle
902                },
903                ..LayoutSettings::default()
904            },
905        );
906        for glyph in layout.glyphs() {
907            let Some((metrics, alphamap)) = self.rasterize_glyph(glyph, &fonts) else {
908                continue;
909            };
910
911            for y in 0..metrics.height {
912                for x in 0..metrics.width {
913                    let coord_x = top_left.x + (x + glyph.x.ceil() as usize) as i32;
914                    let coord_y = top_left.y + (y + glyph.y.ceil() as usize) as i32;
915                    if coord_x < 0 || coord_y < 0 {
916                        continue;
917                    }
918
919                    let coord_x = coord_x as usize;
920                    let coord_y = coord_y as usize;
921                    if coord_x < clip_rect.0
922                        || coord_x > clip_rect.0 + clip_rect.2
923                        || coord_y < clip_rect.1
924                        || coord_y > clip_rect.1 + clip_rect.3
925                    {
926                        continue;
927                    }
928
929                    let i = coord_x * 4 + coord_y * stride * 4;
930                    let m = alphamap[x + y * metrics.width];
931
932                    let background = &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
933                    frame[i..i + 4].copy_from_slice(&self.mix_color(
934                        background,
935                        color,
936                        m as f32 / 255.0,
937                    ));
938                }
939            }
940        }
941    }
942
943    #[allow(clippy::too_many_arguments)]
944    /// Draws the given text
945    pub fn text(
946        &self,
947        frame: &mut [u8],
948        pos: &(usize, usize),
949        stride: usize,
950        text: &str,
951        settings: TheFontSettings,
952        color: &[u8; 4],
953        background: &[u8; 4],
954    ) {
955        if text.is_empty() {
956            return;
957        }
958
959        let fonts = self.fonts_iter(&settings.preference);
960
961        let layout = self.get_text_layout(text, &settings, LayoutSettings::default());
962        for glyph in layout.glyphs() {
963            let Some((metrics, alphamap)) = self.rasterize_glyph(glyph, &fonts) else {
964                continue;
965            };
966
967            for y in 0..metrics.height {
968                for x in 0..metrics.width {
969                    let i = (x + pos.0 + glyph.x as usize) * 4
970                        + (y + pos.1 + glyph.y as usize) * stride * 4;
971                    let m = alphamap[x + y * metrics.width];
972
973                    frame[i..i + 4].copy_from_slice(&self.mix_color(
974                        background,
975                        color,
976                        m as f32 / 255.0,
977                    ));
978                }
979            }
980        }
981    }
982
983    #[allow(clippy::too_many_arguments)]
984    /// Draws the given text
985    pub fn text_blend(
986        &self,
987        frame: &mut [u8],
988        pos: &(usize, usize),
989        stride: usize,
990        text: &str,
991        settings: TheFontSettings,
992        color: &[u8; 4],
993    ) {
994        if text.is_empty() {
995            return;
996        }
997
998        let fonts = self.fonts_iter(&settings.preference);
999
1000        let layout = self.get_text_layout(text, &settings, LayoutSettings::default());
1001        for glyph in layout.glyphs() {
1002            let Some((metrics, alphamap)) = self.rasterize_glyph(glyph, &fonts) else {
1003                continue;
1004            };
1005
1006            for y in 0..metrics.height {
1007                for x in 0..metrics.width {
1008                    let i = (x + pos.0 + glyph.x as usize) * 4
1009                        + (y + pos.1 + glyph.y as usize) * stride * 4;
1010                    let m = alphamap[x + y * metrics.width];
1011
1012                    let background = &[frame[i], frame[i + 1], frame[i + 2], frame[i + 3]];
1013                    frame[i..i + 4].copy_from_slice(&self.mix_color(
1014                        background,
1015                        color,
1016                        m as f32 / 255.0,
1017                    ));
1018                }
1019            }
1020        }
1021    }
1022
1023    /// Returns the layout of the given text
1024    pub fn get_text_layout(
1025        &self,
1026        text: &str,
1027        font_settings: &TheFontSettings,
1028        layout_settings: LayoutSettings,
1029    ) -> Layout {
1030        let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
1031        layout.reset(&layout_settings);
1032
1033        let mut segment = String::default();
1034        let mut last_font_index = 0;
1035
1036        let fonts = self.fonts_iter(&font_settings.preference);
1037
1038        for ch in text.chars() {
1039            for (index, font) in fonts.iter().enumerate() {
1040                if font.lookup_glyph_index(ch) != 0 {
1041                    if index != last_font_index {
1042                        layout.append(
1043                            &fonts,
1044                            &TextStyle::new(&segment, font_settings.size, last_font_index),
1045                        );
1046                        last_font_index = index;
1047                        segment = String::default();
1048                    }
1049                    break;
1050                }
1051            }
1052            segment.push(ch);
1053        }
1054
1055        if !segment.is_empty() {
1056            layout.append(
1057                &fonts,
1058                &TextStyle::new(&segment, font_settings.size, last_font_index),
1059            );
1060        }
1061
1062        layout
1063    }
1064
1065    /// Returns the size of the given text
1066    pub fn get_text_size(&self, text: &str, settings: &TheFontSettings) -> (usize, usize) {
1067        if text.is_empty() {
1068            return (0, 0);
1069        }
1070
1071        let layout = self.get_text_layout(text, settings, LayoutSettings::default());
1072        let glyphs = layout.glyphs();
1073
1074        let x = glyphs[glyphs.len() - 1].x.ceil() as usize + glyphs[glyphs.len() - 1].width + 1;
1075        (x, layout.height() as usize)
1076    }
1077
1078    /// Copies rect from the source frame into the dest frame
1079    pub fn copy_slice(
1080        &self,
1081        dest: &mut [u8],
1082        source: &[u8],
1083        rect: &(usize, usize, usize, usize),
1084        dest_stride: usize,
1085    ) {
1086        for y in 0..rect.3 {
1087            let d = rect.0 * 4 + (y + rect.1) * dest_stride * 4;
1088            let s = y * rect.2 * 4;
1089            dest[d..d + rect.2 * 4].copy_from_slice(&source[s..s + rect.2 * 4]);
1090        }
1091    }
1092
1093    /// Blends rect from the source frame into the dest frame
1094    pub fn blend_slice(
1095        &self,
1096        dest: &mut [u8],
1097        source: &[u8],
1098        rect: &(usize, usize, usize, usize),
1099        dest_stride: usize,
1100    ) {
1101        for y in 0..rect.3 {
1102            let d = rect.0 * 4 + (y + rect.1) * dest_stride * 4;
1103            let s = y * rect.2 * 4;
1104
1105            for x in 0..rect.2 {
1106                let dd = d + x * 4;
1107                let ss = s + x * 4;
1108
1109                let background = &[dest[dd], dest[dd + 1], dest[dd + 2], dest[dd + 3]];
1110                let color = &[source[ss], source[ss + 1], source[ss + 2], source[ss + 3]];
1111                dest[dd..dd + 4].copy_from_slice(&self.mix_color(
1112                    background,
1113                    color,
1114                    (color[3] as f32) / 255.0,
1115                ));
1116            }
1117        }
1118    }
1119
1120    /// Blends rect from the source frame into the dest frame
1121    pub fn blend_slice_alpha(
1122        &self,
1123        dest: &mut [u8],
1124        source: &[u8],
1125        rect: &(usize, usize, usize, usize),
1126        dest_stride: usize,
1127        alpha: f32,
1128    ) {
1129        for y in 0..rect.3 {
1130            let d = rect.0 * 4 + (y + rect.1) * dest_stride * 4;
1131            let s = y * rect.2 * 4;
1132
1133            for x in 0..rect.2 {
1134                let dd = d + x * 4;
1135                let ss = s + x * 4;
1136
1137                let background = &[dest[dd], dest[dd + 1], dest[dd + 2], dest[dd + 3]];
1138                let color = &[source[ss], source[ss + 1], source[ss + 2], source[ss + 3]];
1139                dest[dd..dd + 4].copy_from_slice(&self.mix_color(
1140                    background,
1141                    color,
1142                    (color[3] as f32 * alpha) / 255.0,
1143                ));
1144            }
1145        }
1146    }
1147
1148    /// Blends rect from the source frame into the dest frame
1149    pub fn blend_slice_f32(
1150        &self,
1151        dest: &mut [u8],
1152        source: &[f32],
1153        rect: &(usize, usize, usize, usize),
1154        dest_stride: usize,
1155    ) {
1156        for y in 0..rect.3 {
1157            let d = rect.0 * 4 + (y + rect.1) * dest_stride * 4;
1158            let s = y * rect.2 * 4;
1159
1160            for x in 0..rect.2 {
1161                let dd = d + x * 4;
1162                let ss = s + x * 4;
1163
1164                let background = &[dest[dd], dest[dd + 1], dest[dd + 2], dest[dd + 3]];
1165                let color = &[
1166                    (source[ss] * 255.0) as u8,
1167                    (source[ss + 1] * 255.0) as u8,
1168                    (source[ss + 2] * 255.0) as u8,
1169                    (source[ss + 3] * 255.0) as u8,
1170                ];
1171                dest[dd..dd + 4].copy_from_slice(&self.mix_color(
1172                    background,
1173                    color,
1174                    (color[3] as f32) / 255.0,
1175                ));
1176            }
1177        }
1178    }
1179
1180    /// Blends rect from the source frame into the dest frame with a vertical source offset (used by scrolling containers)
1181    pub fn blend_slice_offset(
1182        &self,
1183        dest: &mut [u8],
1184        source: &[u8],
1185        rect: &(usize, usize, usize, usize),
1186        offset: usize,
1187        dest_stride: usize,
1188    ) {
1189        for y in 0..rect.3 {
1190            let d = rect.0 * 4 + (y + rect.1) * dest_stride * 4;
1191            let s = (y + offset) * rect.2 * 4;
1192
1193            for x in 0..rect.2 {
1194                let dd = d + x * 4;
1195                let ss = s + x * 4;
1196
1197                let background = &[dest[dd], dest[dd + 1], dest[dd + 2], dest[dd + 3]];
1198                let color = &[source[ss], source[ss + 1], source[ss + 2], source[ss + 3]];
1199                dest[dd..dd + 4].copy_from_slice(&self.mix_color(
1200                    background,
1201                    color,
1202                    (color[3] as f32) / 255.0,
1203                ));
1204            }
1205        }
1206    }
1207
1208    /// Blends rect from the source frame into the dest frame and honors the safe rect
1209    pub fn blend_slice_safe(
1210        &self,
1211        dest: &mut [u8],
1212        source: &[u8],
1213        rect: &(isize, isize, usize, usize),
1214        dest_stride: usize,
1215        safe_rect: &(usize, usize, usize, usize),
1216    ) {
1217        let dest_stride_isize = dest_stride as isize;
1218        for y in 0..rect.3 as isize {
1219            let d = rect.0 * 4 + (y + rect.1) * dest_stride_isize * 4;
1220            let s = y * (rect.2 as isize) * 4;
1221
1222            // TODO: Make this faster
1223
1224            if (y + rect.1) >= safe_rect.1 as isize
1225                && (y + rect.1) < (safe_rect.1 + safe_rect.3) as isize
1226            {
1227                for x in 0..rect.2 as isize {
1228                    if (x + rect.0) >= safe_rect.0 as isize
1229                        && (x + rect.0) < (safe_rect.0 + safe_rect.2) as isize
1230                    {
1231                        let dd = (d + x * 4) as usize;
1232                        let ss = (s + x * 4) as usize;
1233
1234                        let background = &[dest[dd], dest[dd + 1], dest[dd + 2], dest[dd + 3]];
1235                        let color = &[source[ss], source[ss + 1], source[ss + 2], source[ss + 3]];
1236                        dest[dd..dd + 4].copy_from_slice(&self.mix_color(
1237                            background,
1238                            color,
1239                            (color[3] as f32) / 255.0,
1240                        ));
1241                    }
1242                }
1243            }
1244        }
1245    }
1246
1247    /// Scale a chunk to the destination size
1248    pub fn scale_chunk(
1249        &self,
1250        frame: &mut [u8],
1251        rect: &(usize, usize, usize, usize),
1252        stride: usize,
1253        source_frame: &[u8],
1254        source_size: &(usize, usize),
1255        blend_factor: f32,
1256    ) {
1257        let x_ratio = source_size.0 as f32 / rect.2 as f32;
1258        let y_ratio = source_size.1 as f32 / rect.3 as f32;
1259
1260        for sy in 0..rect.3 {
1261            let y = (sy as f32 * y_ratio) as usize;
1262
1263            for sx in 0..rect.2 {
1264                let x = (sx as f32 * x_ratio) as usize;
1265
1266                let d = (rect.0 + sx) * 4 + (sy + rect.1) * stride * 4;
1267                let s = x * 4 + y * source_size.0 * 4;
1268
1269                frame[d..d + 4].copy_from_slice(&[
1270                    source_frame[s],
1271                    source_frame[s + 1],
1272                    source_frame[s + 2],
1273                    ((source_frame[s + 3] as f32) * blend_factor) as u8,
1274                ]);
1275            }
1276        }
1277    }
1278
1279    /// Scale a chunk to the destination size
1280    pub fn blend_scale_chunk(
1281        &self,
1282        frame: &mut [u8],
1283        rect: &(usize, usize, usize, usize),
1284        stride: usize,
1285        source_frame: &[u8],
1286        source_size: &(usize, usize),
1287    ) {
1288        let x_ratio = source_size.0 as f32 / rect.2 as f32;
1289        let y_ratio = source_size.1 as f32 / rect.3 as f32;
1290
1291        for sy in 0..rect.3 {
1292            let y = (sy as f32 * y_ratio) as usize;
1293
1294            for sx in 0..rect.2 {
1295                let x = (sx as f32 * x_ratio) as usize;
1296
1297                let d = (rect.0 + sx) * 4 + (sy + rect.1) * stride * 4;
1298                let s = x * 4 + y * source_size.0 * 4;
1299
1300                let color = &[
1301                    source_frame[s],
1302                    source_frame[s + 1],
1303                    source_frame[s + 2],
1304                    source_frame[s + 3],
1305                ];
1306                let background = &[frame[d], frame[d + 1], frame[d + 2], frame[d + 3]];
1307                frame[d..d + 4].copy_from_slice(&self.mix_color(
1308                    background,
1309                    color,
1310                    (color[3] as f32) / 255.0,
1311                ));
1312            }
1313        }
1314    }
1315
1316    /// Scale a chunk to the destination size with a global alpha
1317    pub fn blend_scale_chunk_alpha(
1318        &self,
1319        frame: &mut [u8],
1320        rect: &(usize, usize, usize, usize),
1321        stride: usize,
1322        source_frame: &[u8],
1323        source_size: &(usize, usize),
1324        alpha: f32,
1325    ) {
1326        let x_ratio = source_size.0 as f32 / rect.2 as f32;
1327        let y_ratio = source_size.1 as f32 / rect.3 as f32;
1328
1329        for sy in 0..rect.3 {
1330            let y = (sy as f32 * y_ratio) as usize;
1331
1332            for sx in 0..rect.2 {
1333                let x = (sx as f32 * x_ratio) as usize;
1334
1335                let d = (rect.0 + sx) * 4 + (sy + rect.1) * stride * 4;
1336                let s = x * 4 + y * source_size.0 * 4;
1337
1338                let color = &[
1339                    source_frame[s],
1340                    source_frame[s + 1],
1341                    source_frame[s + 2],
1342                    source_frame[s + 3],
1343                ];
1344                let background = &[frame[d], frame[d + 1], frame[d + 2], frame[d + 3]];
1345                frame[d..d + 4].copy_from_slice(&self.mix_color(
1346                    background,
1347                    color,
1348                    (color[3] as f32 * alpha) / 255.0,
1349                ));
1350            }
1351        }
1352    }
1353
1354    /// Scale a chunk to the destination size with linear interpolation and blend onto destination
1355    pub fn blend_scale_chunk_linear(
1356        &self,
1357        dest: &mut [u8],
1358        dest_rect: &(usize, usize, usize, usize),
1359        dest_stride: usize,
1360        source: &[u8],
1361        source_size: &(usize, usize),
1362    ) {
1363        let x_ratio = (source_size.0 - 1) as f32 / dest_rect.2 as f32;
1364        let y_ratio = (source_size.1 - 1) as f32 / dest_rect.3 as f32;
1365
1366        for dy in 0..dest_rect.3 {
1367            let sy = (dy as f32 * y_ratio).round() as usize;
1368            let sy_frac = dy as f32 * y_ratio - sy as f32;
1369
1370            for dx in 0..dest_rect.2 {
1371                let sx = (dx as f32 * x_ratio).round() as usize;
1372                let sx_frac = dx as f32 * x_ratio - sx as f32;
1373
1374                let d = (dest_rect.0 + dx) * 4 + (dest_rect.1 + dy) * dest_stride * 4;
1375
1376                // Interpolate between four neighboring pixels
1377                let mut interpolated_color = [0; 4];
1378                for c in 0..4 {
1379                    let tl = source[(sy * source_size.0 + sx) * 4 + c] as f32;
1380                    let tr = source[(sy * source_size.0 + sx + 1) * 4 + c] as f32;
1381                    let bl = source[((sy + 1) * source_size.0 + sx) * 4 + c] as f32;
1382                    let br = source[((sy + 1) * source_size.0 + sx + 1) * 4 + c] as f32;
1383
1384                    let top = tl * (1.0 - sx_frac) + tr * sx_frac;
1385                    let bottom = bl * (1.0 - sx_frac) + br * sx_frac;
1386
1387                    interpolated_color[c] = (top * (1.0 - sy_frac) + bottom * sy_frac) as u8;
1388                }
1389
1390                // Blend the interpolated color onto the destination
1391                let background = &[dest[d], dest[d + 1], dest[d + 2], dest[d + 3]];
1392                dest[d..d + 4].copy_from_slice(&self.mix_color(
1393                    background,
1394                    &interpolated_color,
1395                    interpolated_color[3] as f32 / 255.0,
1396                ));
1397            }
1398        }
1399    }
1400
1401    pub fn add_font_data<Data>(&mut self, data: Data)
1402    where
1403        Data: Deref<Target = [u8]>,
1404    {
1405        match Font::from_bytes(data, fontdue::FontSettings::default()) {
1406            Ok(font) => {
1407                self.fonts.push(font);
1408            }
1409            Err(err) => {
1410                println!("Failed to load font from data: {err:?}");
1411            }
1412        }
1413    }
1414
1415    pub fn add_code_font_data<Data>(&mut self, data: Data)
1416    where
1417        Data: Deref<Target = [u8]>,
1418    {
1419        match Font::from_bytes(data, fontdue::FontSettings::default()) {
1420            Ok(font) => {
1421                self.code_fonts.push(font);
1422            }
1423            Err(err) => {
1424                println!("Failed to load font from data: {err:?}");
1425            }
1426        }
1427    }
1428
1429    /// The fill mask for an SDF distance
1430    fn fill_mask(&self, dist: f32) -> f32 {
1431        (-dist).clamp(0.0, 1.0)
1432    }
1433
1434    /// The border mask for an SDF distance
1435    fn border_mask(&self, dist: f32, width: f32) -> f32 {
1436        (dist + width).clamp(0.0, 1.0) - dist.clamp(0.0, 1.0)
1437    }
1438
1439    fn fonts_iter(&self, font_preference: &TheFontPreference) -> Vec<&Font> {
1440        let mut fonts_ref = self.fonts.iter().collect::<Vec<&Font>>();
1441        let mut code_fonts_ref = self.code_fonts.iter().collect::<Vec<&Font>>();
1442
1443        match font_preference {
1444            TheFontPreference::Default => {
1445                fonts_ref.append(&mut code_fonts_ref);
1446                fonts_ref
1447            }
1448            TheFontPreference::Code => {
1449                code_fonts_ref.append(&mut fonts_ref);
1450                code_fonts_ref
1451            }
1452        }
1453    }
1454
1455    fn rasterize_glyph(
1456        &self,
1457        glyph: &GlyphPosition,
1458        fonts: &[&Font],
1459    ) -> Option<(Metrics, Vec<u8>)> {
1460        if fonts.is_empty() {
1461            return None;
1462        }
1463
1464        let font = fonts
1465            .iter()
1466            .find(|font| font.lookup_glyph_index(glyph.parent) != 0)
1467            .map_or(fonts.first().unwrap(), |font| font);
1468
1469        Some(font.rasterize(glyph.parent, glyph.key.px))
1470    }
1471
1472    /// Smoothstep for f32
1473    pub fn _smoothstep(&self, e0: f32, e1: f32, x: f32) -> f32 {
1474        let t = ((x - e0) / (e1 - e0)).clamp(0.0, 1.0);
1475        t * t * (3.0 - 2.0 * t)
1476    }
1477
1478    /// Mixes two colors based on v
1479    pub fn mix_color(&self, a: &[u8; 4], b: &[u8; 4], v: f32) -> [u8; 4] {
1480        [
1481            (((1.0 - v) * (a[0] as f32 / 255.0) + b[0] as f32 / 255.0 * v) * 255.0) as u8,
1482            (((1.0 - v) * (a[1] as f32 / 255.0) + b[1] as f32 / 255.0 * v) * 255.0) as u8,
1483            (((1.0 - v) * (a[2] as f32 / 255.0) + b[2] as f32 / 255.0 * v) * 255.0) as u8,
1484            (((1.0 - v) * (a[3] as f32 / 255.0) + b[3] as f32 / 255.0 * v) * 255.0) as u8,
1485        ]
1486    }
1487
1488    // Length of a 2d vector
1489    pub fn length(&self, v: (f32, f32)) -> f32 {
1490        ((v.0).powf(2.0) + (v.1).powf(2.0)).sqrt()
1491    }
1492}