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 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 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 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 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 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 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 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 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 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._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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 fn fill_mask(&self, dist: f32) -> f32 {
1431 (-dist).clamp(0.0, 1.0)
1432 }
1433
1434 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 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 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 pub fn length(&self, v: (f32, f32)) -> f32 {
1490 ((v.0).powf(2.0) + (v.1).powf(2.0)).sqrt()
1491 }
1492}