1use core::marker::PhantomData;
2
3use embedded_graphics_core::{
4 Pixel,
5 draw_target::DrawTarget,
6 geometry::Point,
7 pixelcolor::{Rgb565, RgbColor},
8};
9
10#[cfg(not(feature = "std"))]
11use crate::math::F32Ext as _;
12use crate::{
13 font::{FontId, glyph_rows},
14 geometry::Rect,
15 image::{ImageFit, ImageRef, TileMode, TileRef},
16 style::{AlphaLinearGradient, AlphaRadialGradient, Border, GradientDirection, LinearGradient},
17 text,
18};
19
20pub const CHAR_WIDTH: u32 = 4;
21pub const CHAR_HEIGHT: u32 = 6;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum TextAlign {
25 Left,
26 Center,
27 Right,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum VerticalAlign {
32 Top,
33 Middle,
34 Bottom,
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum TextWrap {
39 None,
40 Character,
41 Word,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum TextOverflow {
46 Clip,
47 Ellipsis,
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum EllipsisMode {
52 ThreeDots,
53 SingleGlyph,
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum TextOverflowPolicy {
58 Global(TextOverflow),
59 WrapThenEllipsis { max_lines: u8 },
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub struct TextStyle {
64 pub color: Rgb565,
65 pub font: FontId,
66 pub opacity: u8,
67 pub align: TextAlign,
68 pub vertical_align: VerticalAlign,
69 pub wrap: TextWrap,
70 pub overflow: TextOverflow,
71 pub overflow_policy: TextOverflowPolicy,
72 pub kerning: bool,
73 pub max_lines: Option<u8>,
74 pub ellipsis: EllipsisMode,
75 pub line_spacing: u8,
76}
77
78impl TextStyle {
79 pub const fn new(color: Rgb565) -> Self {
80 Self {
81 color,
82 font: FontId::Tiny3x5,
83 opacity: 255,
84 align: TextAlign::Left,
85 vertical_align: VerticalAlign::Top,
86 wrap: TextWrap::None,
87 overflow: TextOverflow::Clip,
88 overflow_policy: TextOverflowPolicy::Global(TextOverflow::Clip),
89 kerning: false,
90 max_lines: None,
91 ellipsis: EllipsisMode::ThreeDots,
92 line_spacing: 1,
93 }
94 }
95
96 pub const fn centered(mut self) -> Self {
97 self.align = TextAlign::Center;
98 self.vertical_align = VerticalAlign::Middle;
99 self
100 }
101
102 pub const fn with_align(mut self, align: TextAlign) -> Self {
103 self.align = align;
104 self
105 }
106
107 pub const fn with_vertical_align(mut self, align: VerticalAlign) -> Self {
108 self.vertical_align = align;
109 self
110 }
111
112 pub const fn with_wrap(mut self, wrap: TextWrap) -> Self {
113 self.wrap = wrap;
114 self
115 }
116
117 pub const fn with_line_spacing(mut self, spacing: u8) -> Self {
118 self.line_spacing = spacing;
119 self
120 }
121
122 pub const fn with_overflow(mut self, overflow: TextOverflow) -> Self {
123 self.overflow = overflow;
124 self.overflow_policy = TextOverflowPolicy::Global(overflow);
125 self
126 }
127
128 pub const fn with_kerning(mut self, kerning: bool) -> Self {
129 self.kerning = kerning;
130 self
131 }
132
133 pub const fn with_max_lines(mut self, max_lines: Option<u8>) -> Self {
134 self.max_lines = max_lines;
135 self
136 }
137
138 pub const fn with_ellipsis_mode(mut self, ellipsis: EllipsisMode) -> Self {
139 self.ellipsis = ellipsis;
140 self
141 }
142
143 pub const fn with_overflow_policy(mut self, policy: TextOverflowPolicy) -> Self {
144 self.overflow_policy = policy;
145 self
146 }
147
148 pub const fn with_opacity(mut self, opacity: u8) -> Self {
149 self.opacity = opacity;
150 self
151 }
152
153 pub const fn with_font(mut self, font: FontId) -> Self {
154 self.font = font;
155 self
156 }
157}
158
159#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub struct TextMetrics {
161 pub width: u32,
162 pub height: u32,
163}
164
165#[derive(Clone, Copy, Debug, PartialEq, Eq)]
166pub enum RenderQuality {
167 Low,
168 Medium,
169 High,
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
173pub enum AntiAliasMode {
174 None,
175 Coverage,
176 Subpixel,
177}
178
179#[derive(Clone, Copy, Debug, PartialEq, Eq)]
180pub struct StrokeStyle {
181 pub color: Rgb565,
182 pub width: u8,
183 pub antialias: bool,
184 pub antialias_mode: AntiAliasMode,
185 pub cap: StrokeCap,
186 pub join: StrokeJoin,
187}
188
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190pub enum StrokeCap {
191 Butt,
192 Round,
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq)]
196pub enum StrokeJoin {
197 Miter,
198 Round,
199}
200
201#[derive(Clone, Copy, Debug, PartialEq)]
202pub struct Transform2D {
203 pub m11: f32,
204 pub m12: f32,
205 pub m21: f32,
206 pub m22: f32,
207 pub tx: f32,
208 pub ty: f32,
209}
210
211impl Transform2D {
212 pub const IDENTITY: Self = Self {
213 m11: 1.0,
214 m12: 0.0,
215 m21: 0.0,
216 m22: 1.0,
217 tx: 0.0,
218 ty: 0.0,
219 };
220
221 pub const fn translation(x: f32, y: f32) -> Self {
222 Self {
223 tx: x,
224 ty: y,
225 ..Self::IDENTITY
226 }
227 }
228
229 pub const fn scale(x: f32, y: f32) -> Self {
230 Self {
231 m11: x,
232 m22: y,
233 ..Self::IDENTITY
234 }
235 }
236
237 pub fn rotation(deg: f32) -> Self {
238 let r = deg.to_radians();
239 Self {
240 m11: r.cos(),
241 m12: -r.sin(),
242 m21: r.sin(),
243 m22: r.cos(),
244 ..Self::IDENTITY
245 }
246 }
247
248 pub fn skew(x_deg: f32, y_deg: f32) -> Self {
249 Self {
250 m12: x_deg.to_radians().tan(),
251 m21: y_deg.to_radians().tan(),
252 ..Self::IDENTITY
253 }
254 }
255
256 pub fn then(self, rhs: Self) -> Self {
257 Self {
258 m11: self.m11 * rhs.m11 + self.m12 * rhs.m21,
259 m12: self.m11 * rhs.m12 + self.m12 * rhs.m22,
260 m21: self.m21 * rhs.m11 + self.m22 * rhs.m21,
261 m22: self.m21 * rhs.m12 + self.m22 * rhs.m22,
262 tx: self.m11 * rhs.tx + self.m12 * rhs.ty + self.tx,
263 ty: self.m21 * rhs.tx + self.m22 * rhs.ty + self.ty,
264 }
265 }
266
267 #[inline(always)]
268 pub fn is_identity(self) -> bool {
269 self.m11 == 1.0
270 && self.m12 == 0.0
271 && self.m21 == 0.0
272 && self.m22 == 1.0
273 && self.tx == 0.0
274 && self.ty == 0.0
275 }
276
277 #[inline(always)]
278 pub fn apply(self, x: i32, y: i32) -> (i32, i32) {
279 if self.is_identity() {
280 (x, y)
281 } else {
282 let xf = x as f32;
283 let yf = y as f32;
284 (
285 (self.m11 * xf + self.m12 * yf + self.tx).round() as i32,
286 (self.m21 * xf + self.m22 * yf + self.ty).round() as i32,
287 )
288 }
289 }
290
291 #[inline(always)]
292 pub fn apply_f32(self, x: f32, y: f32) -> (f32, f32) {
293 if self.is_identity() {
294 (x, y)
295 } else {
296 (
297 self.m11 * x + self.m12 * y + self.tx,
298 self.m21 * x + self.m22 * y + self.ty,
299 )
300 }
301 }
302
303 pub fn inverse(self) -> Option<Self> {
304 let det = self.m11 * self.m22 - self.m12 * self.m21;
305 if det.abs() < 1e-7 {
306 return None;
307 }
308 let inv_det = 1.0 / det;
309 let m11 = self.m22 * inv_det;
310 let m12 = -self.m12 * inv_det;
311 let m21 = -self.m21 * inv_det;
312 let m22 = self.m11 * inv_det;
313 let tx = (self.m12 * self.ty - self.m22 * self.tx) * inv_det;
314 let ty = (self.m21 * self.tx - self.m11 * self.ty) * inv_det;
315 Some(Self {
316 m11,
317 m12,
318 m21,
319 m22,
320 tx,
321 ty,
322 })
323 }
324}
325
326#[derive(Clone, Copy, Debug, PartialEq, Eq)]
327pub enum BlendMode {
328 Normal,
329 Add,
330 Multiply,
331 Screen,
332}
333
334pub trait PixelRead: DrawTarget {
342 fn get_pixel(&self, point: Point) -> Self::Color;
343}
344
345pub trait WindowedDrawTarget: DrawTarget {
350 fn set_window(
351 &mut self,
352 area: &embedded_graphics_core::primitives::Rectangle,
353 ) -> Result<(), Self::Error>;
354}
355
356pub trait Compositor<D: DrawTarget<Color = Rgb565>> {
369 fn plot(
370 target: &mut D,
371 x: i32,
372 y: i32,
373 color: Rgb565,
374 opacity: u8,
375 blend: BlendMode,
376 backdrop: Rgb565,
377 ) -> Result<(), D::Error>;
378}
379
380#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
382pub struct Dither;
383
384#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
386pub struct Blend;
387
388impl<D: DrawTarget<Color = Rgb565>> Compositor<D> for Dither {
389 fn plot(
390 target: &mut D,
391 x: i32,
392 y: i32,
393 color: Rgb565,
394 opacity: u8,
395 blend: BlendMode,
396 backdrop: Rgb565,
397 ) -> Result<(), D::Error> {
398 if !should_draw_at_opacity(x, y, opacity) {
399 return Ok(());
400 }
401 let color = apply_blend_mode(color, blend, backdrop);
402 target.draw_iter([Pixel(Point::new(x, y), color)])
403 }
404}
405
406impl<D: DrawTarget<Color = Rgb565> + PixelRead> Compositor<D> for Blend {
407 fn plot(
408 target: &mut D,
409 x: i32,
410 y: i32,
411 color: Rgb565,
412 opacity: u8,
413 blend: BlendMode,
414 backdrop: Rgb565,
415 ) -> Result<(), D::Error> {
416 if opacity == 0 {
417 return Ok(());
418 }
419 let bg = target.get_pixel(Point::new(x, y));
420 let blended = lerp_rgb565(bg, color, opacity);
421 let blended = apply_blend_mode(blended, blend, backdrop);
422 target.draw_iter([Pixel(Point::new(x, y), blended)])
423 }
424}
425
426#[derive(Clone, Copy, Debug, PartialEq, Eq)]
427pub enum ColorFormat {
428 Rgb565,
429 Rgb888,
430 Argb8888,
431}
432
433#[derive(Clone, Copy, Debug, PartialEq, Eq)]
434pub struct RenderBackendCaps {
435 pub color_format: ColorFormat,
436 pub supports_layers: bool,
437 pub supports_subpixel: bool,
438}
439
440impl RenderBackendCaps {
441 pub const fn software_rgb565() -> Self {
442 Self {
443 color_format: ColorFormat::Rgb565,
444 supports_layers: true,
445 supports_subpixel: false,
446 }
447 }
448}
449
450#[derive(Clone, Copy, Debug, PartialEq, Eq)]
451pub struct LayerState {
452 pub opacity: u8,
453 pub blend: BlendMode,
454 pub backdrop: Rgb565,
455}
456
457impl LayerState {
458 pub const fn normal() -> Self {
459 Self {
460 opacity: 255,
461 blend: BlendMode::Normal,
462 backdrop: Rgb565::BLACK,
463 }
464 }
465}
466
467impl StrokeStyle {
468 pub const fn new(color: Rgb565) -> Self {
469 Self {
470 color,
471 width: 1,
472 antialias: false,
473 antialias_mode: AntiAliasMode::None,
474 cap: StrokeCap::Butt,
475 join: StrokeJoin::Miter,
476 }
477 }
478
479 pub const fn with_width(mut self, width: u8) -> Self {
480 self.width = if width == 0 { 1 } else { width };
481 self
482 }
483
484 pub const fn with_antialias(mut self, antialias: bool) -> Self {
485 self.antialias = antialias;
486 if antialias {
487 if let AntiAliasMode::None = self.antialias_mode {
488 self.antialias_mode = AntiAliasMode::Coverage;
489 }
490 }
491 if !antialias {
492 self.antialias_mode = AntiAliasMode::None;
493 }
494 self
495 }
496
497 pub const fn with_antialias_mode(mut self, mode: AntiAliasMode) -> Self {
498 self.antialias_mode = mode;
499 self.antialias = !matches!(mode, AntiAliasMode::None);
500 self
501 }
502
503 pub const fn with_cap(mut self, cap: StrokeCap) -> Self {
504 self.cap = cap;
505 self
506 }
507
508 pub const fn with_join(mut self, join: StrokeJoin) -> Self {
509 self.join = join;
510 self
511 }
512}
513
514pub struct RenderCtx<'a, D, C = Dither>
515where
516 D: DrawTarget<Color = Rgb565>,
517{
518 target: &'a mut D,
519 clip: Rect,
520 dirty: Option<Rect>,
521 quality: RenderQuality,
522 backend_caps: RenderBackendCaps,
523 transform_stack: [Transform2D; 8],
524 transform_len: usize,
525 layer_stack: [LayerState; 8],
526 layer_len: usize,
527 _compositor: PhantomData<C>,
528}
529
530impl<'a, D> RenderCtx<'a, D, Dither>
531where
532 D: DrawTarget<Color = Rgb565>,
533{
534 pub fn new(target: &'a mut D, viewport: Rect) -> Self {
535 Self {
536 target,
537 clip: viewport,
538 dirty: None,
539 quality: RenderQuality::High,
540 backend_caps: RenderBackendCaps::software_rgb565(),
541 transform_stack: [Transform2D::IDENTITY; 8],
542 transform_len: 1,
543 layer_stack: [LayerState::normal(); 8],
544 layer_len: 1,
545 _compositor: PhantomData,
546 }
547 }
548
549 pub fn with_dirty(target: &'a mut D, viewport: Rect, dirty: Rect) -> Self {
550 Self {
551 target,
552 clip: viewport,
553 dirty: Some(dirty),
554 quality: RenderQuality::High,
555 backend_caps: RenderBackendCaps::software_rgb565(),
556 transform_stack: [Transform2D::IDENTITY; 8],
557 transform_len: 1,
558 layer_stack: [LayerState::normal(); 8],
559 layer_len: 1,
560 _compositor: PhantomData,
561 }
562 }
563}
564
565impl<'a, D> RenderCtx<'a, D, Blend>
566where
567 D: DrawTarget<Color = Rgb565> + PixelRead,
568{
569 pub fn compositing(target: &'a mut D, viewport: Rect) -> Self {
574 Self {
575 target,
576 clip: viewport,
577 dirty: None,
578 quality: RenderQuality::High,
579 backend_caps: RenderBackendCaps::software_rgb565(),
580 transform_stack: [Transform2D::IDENTITY; 8],
581 transform_len: 1,
582 layer_stack: [LayerState::normal(); 8],
583 layer_len: 1,
584 _compositor: PhantomData,
585 }
586 }
587
588 pub fn blur_rect(&mut self, rect: Rect, blur_degree: u8) -> Result<(), D::Error> {
590 let draw = self.visible_rect(rect);
591 if draw.is_empty() || blur_degree == 0 {
592 return Ok(());
593 }
594 let x0 = draw.x;
595 let y0 = draw.y;
596 let x1 = draw.right();
597 let y1 = draw.bottom();
598 let alpha = 256 - (blur_degree as i32);
599
600 let mut row_buf = [Rgb565::BLACK; 1024];
601 let w_buf = ((x1 - x0) as usize).min(1024);
602
603 for y in y0..y1 {
605 let r_len = ((x1 - x0) as usize).min(w_buf);
606 if r_len == 0 {
607 continue;
608 }
609 for (i, x) in (x0..x1).take(r_len).enumerate() {
610 row_buf[i] = self.target.get_pixel(Point::new(x, y));
611 }
612
613 let p0 = row_buf[0];
615 let (r5, g6, b5) = (p0.r(), p0.g(), p0.b());
616 let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
617 let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
618 let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
619
620 for p in row_buf[..r_len].iter_mut() {
621 let (r5, g6, b5) = (p.r(), p.g(), p.b());
622 let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
623 let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
624 let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
625 acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
626 acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
627 acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
628 *p = Rgb565::new(
629 ((acc_r >> 8).clamp(0, 255) as u8) >> 3,
630 ((acc_g >> 8).clamp(0, 255) as u8) >> 2,
631 ((acc_b >> 8).clamp(0, 255) as u8) >> 3,
632 );
633 }
634
635 let p_last = row_buf[r_len - 1];
637 let (r5, g6, b5) = (p_last.r(), p_last.g(), p_last.b());
638 let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
639 let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
640 let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
641
642 for p in row_buf[..r_len].iter_mut().rev() {
643 let (r5, g6, b5) = (p.r(), p.g(), p.b());
644 let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
645 let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
646 let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
647 acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
648 acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
649 acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
650 *p = Rgb565::new(
651 ((acc_r >> 8).clamp(0, 255) as u8) >> 3,
652 ((acc_g >> 8).clamp(0, 255) as u8) >> 2,
653 ((acc_b >> 8).clamp(0, 255) as u8) >> 3,
654 );
655 }
656
657 for (i, x) in (x0..x1).take(r_len).enumerate() {
658 self.target
659 .draw_iter([Pixel(Point::new(x, y), row_buf[i])])?;
660 }
661 }
662
663 let mut col_buf = [Rgb565::BLACK; 1024];
665 let h_buf = ((y1 - y0) as usize).min(1024);
666
667 for x in x0..x1 {
668 let c_len = ((y1 - y0) as usize).min(h_buf);
669 if c_len == 0 {
670 continue;
671 }
672 for (i, y) in (y0..y1).take(c_len).enumerate() {
673 col_buf[i] = self.target.get_pixel(Point::new(x, y));
674 }
675
676 let p0 = col_buf[0];
678 let (r5, g6, b5) = (p0.r(), p0.g(), p0.b());
679 let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
680 let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
681 let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
682
683 for p in col_buf[..c_len].iter_mut() {
684 let (r5, g6, b5) = (p.r(), p.g(), p.b());
685 let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
686 let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
687 let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
688 acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
689 acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
690 acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
691 *p = Rgb565::new(
692 ((acc_r >> 8).clamp(0, 255) as u8) >> 3,
693 ((acc_g >> 8).clamp(0, 255) as u8) >> 2,
694 ((acc_b >> 8).clamp(0, 255) as u8) >> 3,
695 );
696 }
697
698 let p_last = col_buf[c_len - 1];
700 let (r5, g6, b5) = (p_last.r(), p_last.g(), p_last.b());
701 let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
702 let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
703 let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
704
705 for p in col_buf[..c_len].iter_mut().rev() {
706 let (r5, g6, b5) = (p.r(), p.g(), p.b());
707 let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
708 let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
709 let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
710 acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
711 acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
712 acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
713 *p = Rgb565::new(
714 ((acc_r >> 8).clamp(0, 255) as u8) >> 3,
715 ((acc_g >> 8).clamp(0, 255) as u8) >> 2,
716 ((acc_b >> 8).clamp(0, 255) as u8) >> 3,
717 );
718 }
719
720 for (i, y) in (y0..y1).take(c_len).enumerate() {
721 self.target
722 .draw_iter([Pixel(Point::new(x, y), col_buf[i])])?;
723 }
724 }
725
726 Ok(())
727 }
728
729 pub fn reverse_colour_rect(&mut self, rect: Rect) -> Result<(), D::Error> {
731 let bounds = self.clip.intersection(rect);
732 if bounds.is_empty() {
733 return Ok(());
734 }
735 let x0 = bounds.x;
736 let y0 = bounds.y;
737 let x1 = bounds.right();
738 let y1 = bounds.bottom();
739
740 for y in y0..y1 {
741 for x in x0..x1 {
742 let pt = Point::new(x, y);
743 let c = self.target.get_pixel(pt);
744 let inv = Rgb565::new(31 - c.r(), 63 - c.g(), 31 - c.b());
745 self.target.draw_iter([Pixel(pt, inv)])?;
746 }
747 }
748 Ok(())
749 }
750
751 pub fn fill_rect_horizontal_line_mask(
753 &mut self,
754 rect: Rect,
755 mask: &[u8],
756 color: Rgb565,
757 opacity: u8,
758 ) -> Result<(), D::Error> {
759 if mask.is_empty() || opacity == 0 {
760 return Ok(());
761 }
762 let bounds = self.clip.intersection(rect);
763 if bounds.is_empty() {
764 return Ok(());
765 }
766
767 for y in bounds.y..bounds.bottom() {
768 for x in bounds.x..bounds.right() {
769 let mask_x = ((x - rect.x) as usize) % mask.len();
770 let alpha = ((mask[mask_x] as u32 * opacity as u32) >> 8) as u8;
771 if alpha == 0 {
772 continue;
773 }
774 let pt = Point::new(x, y);
775 let bg = self.target.get_pixel(pt);
776 let blended = lerp_rgb565(bg, color, alpha);
777 self.target.draw_iter([Pixel(pt, blended)])?;
778 }
779 }
780 Ok(())
781 }
782
783 pub fn fill_rect_vertical_line_mask(
785 &mut self,
786 rect: Rect,
787 mask: &[u8],
788 color: Rgb565,
789 opacity: u8,
790 ) -> Result<(), D::Error> {
791 if mask.is_empty() || opacity == 0 {
792 return Ok(());
793 }
794 let bounds = self.clip.intersection(rect);
795 if bounds.is_empty() {
796 return Ok(());
797 }
798
799 for y in bounds.y..bounds.bottom() {
800 let mask_y = ((y - rect.y) as usize) % mask.len();
801 let alpha = ((mask[mask_y] as u32 * opacity as u32) >> 8) as u8;
802 if alpha == 0 {
803 continue;
804 }
805 for x in bounds.x..bounds.right() {
806 let pt = Point::new(x, y);
807 let bg = self.target.get_pixel(pt);
808 let blended = lerp_rgb565(bg, color, alpha);
809 self.target.draw_iter([Pixel(pt, blended)])?;
810 }
811 }
812 Ok(())
813 }
814}
815
816impl<'a, D, C> RenderCtx<'a, D, C>
817where
818 D: DrawTarget<Color = Rgb565>,
819 C: Compositor<D>,
820{
821 pub const fn clip(&self) -> Rect {
822 self.clip
823 }
824
825 pub fn set_clip(&mut self, clip: Rect) {
826 self.clip = clip;
827 }
828
829 #[cfg(any(
834 feature = "embedded-text",
835 feature = "embedded-layout",
836 feature = "embedded-graphics"
837 ))]
838 pub fn draw_embedded_graphics<T>(&mut self, drawable: &T) -> Result<T::Output, D::Error>
839 where
840 T: embedded_graphics::Drawable<Color = Rgb565>,
841 {
842 use embedded_graphics::draw_target::DrawTargetExt;
843
844 let clip_rect: embedded_graphics::primitives::Rectangle = self.clip.into();
845 let mut clipped = self.target.clipped(&clip_rect);
846 drawable.draw(&mut clipped)
847 }
848
849 pub const fn quality(&self) -> RenderQuality {
850 self.quality
851 }
852
853 pub fn set_quality(&mut self, quality: RenderQuality) {
854 self.quality = quality;
855 }
856
857 pub const fn backend_caps(&self) -> RenderBackendCaps {
858 self.backend_caps
859 }
860
861 pub fn set_backend_caps(&mut self, caps: RenderBackendCaps) {
862 self.backend_caps = caps;
863 }
864
865 pub fn push_transform(&mut self, transform: Transform2D) {
866 if self.transform_len >= self.transform_stack.len() {
867 return;
868 }
869 let current = self.current_transform();
870 self.transform_stack[self.transform_len] = current.then(transform);
871 self.transform_len += 1;
872 }
873
874 pub fn pop_transform(&mut self) {
875 if self.transform_len > 1 {
876 self.transform_len -= 1;
877 }
878 }
879
880 pub fn translate(&mut self, x: f32, y: f32) {
881 self.push_transform(Transform2D::translation(x, y));
882 }
883
884 pub fn scale(&mut self, x: f32, y: f32) {
885 self.push_transform(Transform2D::scale(x, y));
886 }
887
888 pub fn rotate(&mut self, deg: f32) {
889 self.push_transform(Transform2D::rotation(deg));
890 }
891
892 pub fn skew(&mut self, x_deg: f32, y_deg: f32) {
893 self.push_transform(Transform2D::skew(x_deg, y_deg));
894 }
895
896 pub fn push_layer(&mut self, layer: LayerState) {
897 if self.layer_len >= self.layer_stack.len() {
898 return;
899 }
900 let current = self.current_layer();
901 self.layer_stack[self.layer_len] = LayerState {
902 opacity: ((current.opacity as u16 * layer.opacity as u16) / 255) as u8,
903 blend: layer.blend,
904 backdrop: layer.backdrop,
905 };
906 self.layer_len += 1;
907 }
908
909 pub fn pop_layer(&mut self) {
910 if self.layer_len > 1 {
911 self.layer_len -= 1;
912 }
913 }
914
915 pub const fn shadow_spread_for(&self, spread: u8) -> u8 {
916 match self.quality {
917 RenderQuality::Low => 0,
918 RenderQuality::Medium => {
919 if spread > 1 {
920 1
921 } else {
922 spread
923 }
924 }
925 RenderQuality::High => spread,
926 }
927 }
928
929 pub fn fill_rect(&mut self, rect: Rect, color: Rgb565) -> Result<(), D::Error> {
930 self.fill_rect_alpha(rect, color, 255)
931 }
932
933 pub fn fill_rect_alpha(
934 &mut self,
935 rect: Rect,
936 color: Rgb565,
937 opacity: u8,
938 ) -> Result<(), D::Error> {
939 self.fill_rounded_rect_alpha(rect, 0, color, opacity)
940 }
941
942 pub fn fill_rounded_rect(
943 &mut self,
944 rect: Rect,
945 radius: u8,
946 color: Rgb565,
947 ) -> Result<(), D::Error> {
948 self.fill_rounded_rect_alpha(rect, radius, color, 255)
949 }
950
951 pub fn fill_rounded_rect_alpha(
952 &mut self,
953 rect: Rect,
954 radius: u8,
955 color: Rgb565,
956 opacity: u8,
957 ) -> Result<(), D::Error> {
958 let draw = self.visible_rect(rect);
959 if draw.is_empty() || opacity == 0 {
960 return Ok(());
961 }
962 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
963
964 let layer = self.current_layer();
965 let combined_opacity = ((opacity as u16 * layer.opacity as u16) / 255) as u8;
966
967 if radius == 0
970 && combined_opacity == 255
971 && self.current_transform().is_identity()
972 && layer.blend == BlendMode::Normal
973 {
974 let eg_rect = embedded_graphics_core::primitives::Rectangle::new(
975 embedded_graphics_core::geometry::Point::new(draw.x, draw.y),
976 embedded_graphics_core::geometry::Size::new(draw.w, draw.h),
977 );
978 return self.target.fill_solid(&eg_rect, color);
979 }
980
981 for y in draw.y..draw.bottom() {
982 for x in draw.x..draw.right() {
983 if !in_rounded_rect(x, y, rect, radius) {
984 continue;
985 }
986 self.pixel(x, y, color, opacity)?;
987 }
988 }
989 Ok(())
990 }
991
992 pub fn fill_rounded_rect_gradient_alpha(
993 &mut self,
994 rect: Rect,
995 radius: u8,
996 gradient: LinearGradient,
997 opacity: u8,
998 ) -> Result<(), D::Error> {
999 let draw = self.visible_rect(rect);
1000 if draw.is_empty() || opacity == 0 {
1001 return Ok(());
1002 }
1003 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
1004 let denom = match gradient.direction {
1005 GradientDirection::Horizontal => rect.w.saturating_sub(1).max(1),
1006 GradientDirection::Vertical => rect.h.saturating_sub(1).max(1),
1007 };
1008
1009 for y in draw.y..draw.bottom() {
1010 for x in draw.x..draw.right() {
1011 if !in_rounded_rect(x, y, rect, radius) {
1012 continue;
1013 }
1014 let numer = match gradient.direction {
1015 GradientDirection::Horizontal => (x - rect.x).max(0) as u32,
1016 GradientDirection::Vertical => (y - rect.y).max(0) as u32,
1017 }
1018 .min(denom);
1019 let mut t = ((numer * 255) / denom) as u8;
1020 t = match self.quality {
1021 RenderQuality::Low => 128,
1022 RenderQuality::Medium => (t / 64) * 64,
1023 RenderQuality::High => t,
1024 };
1025 let color = lerp_rgb565(gradient.start, gradient.end, t);
1026 self.pixel(x, y, color, opacity)?;
1027 }
1028 }
1029 Ok(())
1030 }
1031
1032 pub fn stroke_rect(&mut self, rect: Rect, border: Border) -> Result<(), D::Error> {
1033 self.stroke_rect_alpha(rect, border, 255)
1034 }
1035
1036 pub fn stroke_rect_alpha(
1037 &mut self,
1038 rect: Rect,
1039 border: Border,
1040 opacity: u8,
1041 ) -> Result<(), D::Error> {
1042 if border.width == 0 || rect.is_empty() {
1043 return Ok(());
1044 }
1045
1046 for i in 0..border.width as i32 {
1047 let w = rect.w.saturating_sub((i as u32).saturating_mul(2));
1048 let h = rect.h.saturating_sub((i as u32).saturating_mul(2));
1049 if w == 0 || h == 0 {
1050 break;
1051 }
1052 let r = Rect::new(rect.x + i, rect.y + i, w, h);
1053 self.fill_rect_alpha(Rect::new(r.x, r.y, r.w, 1), border.color, opacity)?;
1054 if r.h > 1 {
1055 self.fill_rect_alpha(
1056 Rect::new(r.x, r.bottom() - 1, r.w, 1),
1057 border.color,
1058 opacity,
1059 )?;
1060 }
1061 if r.h > 2 {
1062 self.fill_rect_alpha(Rect::new(r.x, r.y + 1, 1, r.h - 2), border.color, opacity)?;
1063 if r.w > 1 {
1064 self.fill_rect_alpha(
1065 Rect::new(r.right() - 1, r.y + 1, 1, r.h - 2),
1066 border.color,
1067 opacity,
1068 )?;
1069 }
1070 }
1071 }
1072 Ok(())
1073 }
1074
1075 pub fn stroke_rounded_rect(
1076 &mut self,
1077 rect: Rect,
1078 radius: u8,
1079 border: Border,
1080 ) -> Result<(), D::Error> {
1081 self.stroke_rounded_rect_alpha(rect, radius, border, 255)
1082 }
1083
1084 pub fn stroke_rounded_rect_alpha(
1085 &mut self,
1086 rect: Rect,
1087 radius: u8,
1088 border: Border,
1089 opacity: u8,
1090 ) -> Result<(), D::Error> {
1091 if border.width == 0 || rect.is_empty() || opacity == 0 {
1092 return Ok(());
1093 }
1094 let draw = self.visible_rect(rect);
1095 if draw.is_empty() {
1096 return Ok(());
1097 }
1098
1099 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
1100 for y in draw.y..draw.bottom() {
1101 for x in draw.x..draw.right() {
1102 if !in_rounded_rect(x, y, rect, radius) {
1103 continue;
1104 }
1105
1106 let mut inner_hit = false;
1107 let mut i = 1u8;
1108 while i < border.width {
1109 let inset = i as i32;
1110 let inner = Rect::new(
1111 rect.x + inset,
1112 rect.y + inset,
1113 rect.w.saturating_sub((i as u32) * 2),
1114 rect.h.saturating_sub((i as u32) * 2),
1115 );
1116 let inner_radius = radius.saturating_sub(i);
1117 if !inner.is_empty() && in_rounded_rect(x, y, inner, inner_radius) {
1118 inner_hit = true;
1119 break;
1120 }
1121 i += 1;
1122 }
1123
1124 if !inner_hit {
1125 self.pixel(x, y, border.color, opacity)?;
1126 }
1127 }
1128 }
1129 Ok(())
1130 }
1131
1132 pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Rgb565) -> Result<(), D::Error> {
1133 self.draw_text_with_font(x, y, text, color, FontId::Tiny3x5)
1134 }
1135
1136 pub fn draw_text_with_font(
1137 &mut self,
1138 x: i32,
1139 y: i32,
1140 text: &str,
1141 color: Rgb565,
1142 font: FontId,
1143 ) -> Result<(), D::Error> {
1144 let advance = font.advance() as i32;
1145 let line_h = font.line_height() as i32;
1146 let mut cursor_x = x;
1147 let mut cursor_y = y;
1148 for ch in text.chars() {
1149 if ch == '\n' {
1150 cursor_x = x;
1151 cursor_y += line_h;
1152 continue;
1153 }
1154 self.draw_char_with_font(cursor_x, cursor_y, ch, color, 255, font)?;
1155 cursor_x += advance;
1156 }
1157 Ok(())
1158 }
1159
1160 pub fn draw_text_in(
1161 &mut self,
1162 rect: Rect,
1163 text: &str,
1164 style: TextStyle,
1165 ) -> Result<(), D::Error> {
1166 self.draw_text_in_with_font(rect, text, style, style.font)
1167 }
1168
1169 pub fn draw_text_shaped_in<S, const N: usize>(
1170 &mut self,
1171 rect: Rect,
1172 text: &str,
1173 style: TextStyle,
1174 shaper: &S,
1175 config: crate::text::ShapingConfig,
1176 ) -> Result<(), D::Error>
1177 where
1178 S: crate::text::TextShaper,
1179 {
1180 if rect.is_empty() {
1181 return Ok(());
1182 }
1183 let mut shaped = heapless::Vec::<crate::text::ShapedGlyph, N>::new();
1184 shaper.shape(text, config, &mut shaped);
1185 if shaped.is_empty() {
1186 return Ok(());
1187 }
1188 let mut x = rect.x;
1189 let y = rect.y + rect.h.saturating_sub(style.font.line_height()) as i32 / 2;
1190 for glyph in shaped {
1191 self.draw_char_with_font(x, y, glyph.ch, style.color, style.opacity, style.font)?;
1192 x += (glyph.x_advance as i32).max(1) * style.font.advance() as i32;
1193 if x >= rect.right() {
1194 break;
1195 }
1196 }
1197 Ok(())
1198 }
1199
1200 pub fn draw_text_in_with_font(
1201 &mut self,
1202 rect: Rect,
1203 text: &str,
1204 style: TextStyle,
1205 font: FontId,
1206 ) -> Result<(), D::Error> {
1207 if rect.is_empty() {
1208 return Ok(());
1209 }
1210
1211 let advance = font.advance();
1212 let line_h = font.line_height();
1213 let max_chars = (rect.w / advance).max(1) as usize;
1214 let char_count = text.chars().count();
1215 let line_count = count_lines(text, max_chars, style.wrap).max(1);
1216 let line_step = line_h + style.line_spacing as u32;
1217 let total_h = line_count as u32 * line_h
1218 + line_count.saturating_sub(1) as u32 * style.line_spacing as u32;
1219 let mut y = match style.vertical_align {
1220 VerticalAlign::Top => rect.y,
1221 VerticalAlign::Middle => rect.y + rect.h.saturating_sub(total_h) as i32 / 2,
1222 VerticalAlign::Bottom => rect.y + rect.h.saturating_sub(total_h) as i32,
1223 };
1224
1225 let mut start = 0;
1226 let mut rendered_lines = 0u8;
1227 let max_lines = match style.overflow_policy {
1228 TextOverflowPolicy::WrapThenEllipsis { max_lines } => max_lines.max(1),
1229 TextOverflowPolicy::Global(_) => style.max_lines.unwrap_or(u8::MAX),
1230 };
1231 while start < char_count {
1232 if rendered_lines >= max_lines {
1233 break;
1234 }
1235 let (len, consumed_newline) = line_len_at(text, start, max_chars, style.wrap);
1236 let mut draw_len = len;
1237 let is_last_allowed_line = rendered_lines.saturating_add(1) >= max_lines;
1238 let use_ellipsis = match style.overflow_policy {
1239 TextOverflowPolicy::WrapThenEllipsis { .. } => is_last_allowed_line,
1240 TextOverflowPolicy::Global(mode) => mode == TextOverflow::Ellipsis,
1241 };
1242 if use_ellipsis
1243 && ((!consumed_newline && start + len < char_count) || is_last_allowed_line)
1244 {
1245 let ellipsis_width = match style.ellipsis {
1246 EllipsisMode::ThreeDots => 3usize,
1247 EllipsisMode::SingleGlyph => 1usize,
1248 };
1249 if len > ellipsis_width {
1250 draw_len = len - ellipsis_width;
1251 }
1252 }
1253 let line_w = self.substring_width(text, start, draw_len, font, style.kerning);
1254 let x = match style.align {
1255 TextAlign::Left => rect.x,
1256 TextAlign::Center => rect.x + rect.w.saturating_sub(line_w) as i32 / 2,
1257 TextAlign::Right => rect.x + rect.w.saturating_sub(line_w) as i32,
1258 };
1259 self.draw_chars_with_font(
1260 x,
1261 y,
1262 text,
1263 start,
1264 draw_len,
1265 style.color,
1266 style.opacity,
1267 font,
1268 style.kerning,
1269 )?;
1270 if draw_len < len && use_ellipsis {
1271 let token = match style.ellipsis {
1272 EllipsisMode::ThreeDots => "...",
1273 EllipsisMode::SingleGlyph => ".",
1274 };
1275 self.draw_text_with_font(x + line_w as i32, y, token, style.color, font)?;
1276 }
1277 y += line_step as i32;
1278 rendered_lines = rendered_lines.saturating_add(1);
1279 start += len + usize::from(consumed_newline);
1280 if style.wrap == TextWrap::Word && start < char_count {
1281 while text.chars().nth(start).is_some_and(|ch| ch == ' ') {
1282 start += 1;
1283 }
1284 }
1285 if len == 0 && !consumed_newline {
1286 break;
1287 }
1288 }
1289
1290 Ok(())
1291 }
1292
1293 pub fn draw_line_in(&mut self, rect: Rect, line: text::Line<'_>) -> Result<(), D::Error> {
1294 if rect.is_empty() {
1295 return Ok(());
1296 }
1297
1298 self.draw_line_segment_in(rect, line, 0, line.width_chars())
1299 }
1300
1301 pub fn draw_line(
1302 &mut self,
1303 x0: i32,
1304 y0: i32,
1305 x1: i32,
1306 y1: i32,
1307 color: Rgb565,
1308 ) -> Result<(), D::Error> {
1309 self.draw_line_styled(x0, y0, x1, y1, StrokeStyle::new(color))
1310 }
1311
1312 pub fn draw_line_styled(
1313 &mut self,
1314 x0: i32,
1315 y0: i32,
1316 x1: i32,
1317 y1: i32,
1318 style: StrokeStyle,
1319 ) -> Result<(), D::Error> {
1320 let mut x = x0;
1321 let mut y = y0;
1322 let dx = (x1 - x0).abs();
1323 let sx = if x0 < x1 { 1 } else { -1 };
1324 let dy = -(y1 - y0).abs();
1325 let sy = if y0 < y1 { 1 } else { -1 };
1326 let mut err = dx + dy;
1327 let half = (style.width as i32 / 2).max(0);
1328 let opacity = self.stroke_opacity(style);
1329
1330 loop {
1331 for oy in -half..=half {
1332 for ox in -half..=half {
1333 self.pixel(x + ox, y + oy, style.color, opacity)?;
1334 }
1335 }
1336 if style.cap == StrokeCap::Round {
1337 self.fill_circle(x0, y0, half.max(1) as u32, style.color)?;
1338 self.fill_circle(x1, y1, half.max(1) as u32, style.color)?;
1339 }
1340 if x == x1 && y == y1 {
1341 break;
1342 }
1343 let e2 = 2 * err;
1344 if e2 >= dy {
1345 err += dy;
1346 x += sx;
1347 }
1348 if e2 <= dx {
1349 err += dx;
1350 y += sy;
1351 }
1352 }
1353 Ok(())
1354 }
1355
1356 pub fn fill_circle(
1357 &mut self,
1358 center_x: i32,
1359 center_y: i32,
1360 radius: u32,
1361 color: Rgb565,
1362 ) -> Result<(), D::Error> {
1363 let radius = radius as i32;
1364 if radius <= 0 {
1365 return Ok(());
1366 }
1367 let r_sq = radius * radius;
1368 for dy in -radius..=radius {
1369 let dx = ((r_sq - dy * dy) as f32).sqrt() as i32;
1370 if dx >= 0 {
1371 let w = (dx * 2 + 1) as u32;
1372 self.fill_rect(Rect::new(center_x - dx, center_y + dy, w, 1), color)?;
1373 }
1374 }
1375 Ok(())
1376 }
1377
1378 pub fn stroke_circle(
1379 &mut self,
1380 center_x: i32,
1381 center_y: i32,
1382 radius: u32,
1383 color: Rgb565,
1384 ) -> Result<(), D::Error> {
1385 let radius = radius as i32;
1386 if radius <= 0 {
1387 return Ok(());
1388 }
1389 let mut x = radius;
1390 let mut y = 0;
1391 let mut err = 1 - x;
1392 while x >= y {
1393 self.pixel(center_x + x, center_y + y, color, 255)?;
1394 self.pixel(center_x + y, center_y + x, color, 255)?;
1395 self.pixel(center_x - y, center_y + x, color, 255)?;
1396 self.pixel(center_x - x, center_y + y, color, 255)?;
1397 self.pixel(center_x - x, center_y - y, color, 255)?;
1398 self.pixel(center_x - y, center_y - x, color, 255)?;
1399 self.pixel(center_x + y, center_y - x, color, 255)?;
1400 self.pixel(center_x + x, center_y - y, color, 255)?;
1401 y += 1;
1402 if err < 0 {
1403 err += 2 * y + 1;
1404 } else {
1405 x -= 1;
1406 err += 2 * (y - x) + 1;
1407 }
1408 }
1409 Ok(())
1410 }
1411
1412 pub fn stroke_arc(
1413 &mut self,
1414 center_x: i32,
1415 center_y: i32,
1416 radius: u32,
1417 start_deg: i32,
1418 end_deg: i32,
1419 color: Rgb565,
1420 ) -> Result<(), D::Error> {
1421 self.stroke_arc_styled(
1422 center_x,
1423 center_y,
1424 radius,
1425 start_deg,
1426 end_deg,
1427 StrokeStyle::new(color),
1428 )
1429 }
1430
1431 pub fn stroke_arc_styled(
1432 &mut self,
1433 center_x: i32,
1434 center_y: i32,
1435 radius: u32,
1436 start_deg: i32,
1437 end_deg: i32,
1438 style: StrokeStyle,
1439 ) -> Result<(), D::Error> {
1440 let mut start = start_deg;
1441 let mut end = end_deg;
1442 if end < start {
1443 core::mem::swap(&mut start, &mut end);
1444 }
1445 let mut deg = start;
1446 let step = match self.quality {
1447 RenderQuality::Low => 8,
1448 RenderQuality::Medium => 4,
1449 RenderQuality::High => 2,
1450 };
1451 while deg <= end {
1452 let rad = (deg as f32).to_radians();
1453 let x = center_x + (radius as f32 * rad.cos()) as i32;
1454 let y = center_y + (radius as f32 * rad.sin()) as i32;
1455 let half = (style.width as i32 / 2).max(0);
1456 let opacity = self.stroke_opacity(style);
1457 for oy in -half..=half {
1458 for ox in -half..=half {
1459 self.pixel(x + ox, y + oy, style.color, opacity)?;
1460 }
1461 }
1462 if style.join == StrokeJoin::Round {
1463 self.fill_circle(x, y, half.max(1) as u32, style.color)?;
1464 }
1465 deg += step;
1466 }
1467 Ok(())
1468 }
1469
1470 pub fn fill_sector_sweep(
1474 &mut self,
1475 center_x: i32,
1476 center_y: i32,
1477 radius: u32,
1478 start_deg: f32,
1479 sweep_deg: f32,
1480 color: Rgb565,
1481 ) -> Result<(), D::Error> {
1482 if radius == 0 {
1483 return Ok(());
1484 }
1485
1486 let draw = self.visible_rect(Rect::new(
1487 center_x - radius as i32,
1488 center_y - radius as i32,
1489 radius.saturating_mul(2).saturating_add(1),
1490 radius.saturating_mul(2).saturating_add(1),
1491 ));
1492 if draw.is_empty() {
1493 return Ok(());
1494 }
1495
1496 let max_sweep = sweep_deg.abs().min(360.0);
1497 if max_sweep <= 0.0 {
1498 return Ok(());
1499 }
1500
1501 let rr = (radius as i32) * (radius as i32);
1502 let start = normalize_angle_deg(start_deg);
1503 let ccw = sweep_deg >= 0.0;
1504
1505 let (lo_deg, hi_deg) = if ccw {
1515 (start, start + max_sweep)
1516 } else {
1517 (start - max_sweep, start)
1518 };
1519 let (lo_c, lo_s) = cardinal_unit(lo_deg)
1520 .unwrap_or_else(|| (lo_deg.to_radians().cos(), lo_deg.to_radians().sin()));
1521 let (hi_c, hi_s) = cardinal_unit(hi_deg)
1522 .unwrap_or_else(|| (hi_deg.to_radians().cos(), hi_deg.to_radians().sin()));
1523 let full_circle = max_sweep >= 360.0;
1529 let reflex = max_sweep > 180.0;
1530
1531 for y in draw.y..draw.bottom() {
1532 for x in draw.x..draw.right() {
1533 let dx = x - center_x;
1534 let dy = y - center_y;
1535 let d2 = dx * dx + dy * dy;
1536 if d2 > rr {
1537 continue;
1538 }
1539
1540 let in_sweep = if full_circle {
1541 true
1542 } else {
1543 let (fx, fy) = (dx as f32, dy as f32);
1544 if !reflex {
1545 cross(lo_c, lo_s, fx, fy) >= 0.0 && cross(fx, fy, hi_c, hi_s) >= 0.0
1546 } else {
1547 !(cross(hi_c, hi_s, fx, fy) >= 0.0 && cross(fx, fy, lo_c, lo_s) >= 0.0)
1548 }
1549 };
1550 if in_sweep {
1551 self.pixel(x, y, color, 255)?;
1552 }
1553 }
1554 }
1555 Ok(())
1556 }
1557
1558 pub fn fill_polygon(&mut self, points: &[Point], color: Rgb565) -> Result<(), D::Error> {
1559 if points.len() < 3 {
1560 return Ok(());
1561 }
1562 let min_y = points.iter().map(|p| p.y).min().unwrap_or(0);
1563 let max_y = points.iter().map(|p| p.y).max().unwrap_or(-1);
1564 for y in min_y..=max_y {
1565 let mut intersections = [i32::MIN; 16];
1566 let mut count = 0usize;
1567 for i in 0..points.len() {
1568 let p1 = points[i];
1569 let p2 = points[(i + 1) % points.len()];
1570 let (y1, y2) = if p1.y <= p2.y {
1571 (p1.y, p2.y)
1572 } else {
1573 (p2.y, p1.y)
1574 };
1575 if y < y1 || y >= y2 || y1 == y2 {
1576 continue;
1577 }
1578 if count >= intersections.len() {
1579 break;
1580 }
1581 let x = p1.x + ((y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y);
1582 intersections[count] = x;
1583 count += 1;
1584 }
1585 intersections[..count].sort_unstable();
1586 let mut i = 0;
1587 while i + 1 < count {
1588 let x0 = intersections[i];
1589 let x1 = intersections[i + 1];
1590 for x in x0..=x1 {
1591 self.pixel(x, y, color, 255)?;
1592 }
1593 i += 2;
1594 }
1595 }
1596 Ok(())
1597 }
1598
1599 pub fn draw_image(
1600 &mut self,
1601 rect: Rect,
1602 image: ImageRef<'_>,
1603 fit: ImageFit,
1604 ) -> Result<(), D::Error> {
1605 self.draw_image_region(rect, image, fit, Rect::new(0, 0, image.width, image.height))
1606 }
1607
1608 pub fn draw_image_region(
1609 &mut self,
1610 rect: Rect,
1611 image: ImageRef<'_>,
1612 fit: ImageFit,
1613 src_rect: Rect,
1614 ) -> Result<(), D::Error> {
1615 let bounds = image.bounds_at(rect, fit);
1616 if bounds.is_empty() || image.width == 0 || image.height == 0 {
1617 return Ok(());
1618 }
1619 let src_w = image.width as usize;
1620 for y in 0..bounds.h {
1621 let src_y = match fit {
1622 ImageFit::Stretch => {
1623 src_rect.y.max(0) as usize
1624 + ((y as u64 * src_rect.h as u64) / bounds.h as u64) as usize
1625 }
1626 ImageFit::Center => src_rect.y.max(0) as usize + y as usize,
1627 };
1628 for x in 0..bounds.w {
1629 let src_x = match fit {
1630 ImageFit::Stretch => {
1631 src_rect.x.max(0) as usize
1632 + ((x as u64 * src_rect.w as u64) / bounds.w as u64) as usize
1633 }
1634 ImageFit::Center => src_rect.x.max(0) as usize + x as usize,
1635 };
1636 let idx = src_y.saturating_mul(src_w).saturating_add(src_x);
1637 if let Some(raw) = image.pixels.get(idx) {
1638 let color = Rgb565::new(
1639 ((raw >> 11) & 0x1F) as u8,
1640 ((raw >> 5) & 0x3F) as u8,
1641 (raw & 0x1F) as u8,
1642 );
1643 self.pixel(bounds.x + x as i32, bounds.y + y as i32, color, 255)?;
1644 }
1645 }
1646 }
1647 Ok(())
1648 }
1649
1650 pub fn draw_image_transformed(
1651 &mut self,
1652 rect: Rect,
1653 image: ImageRef<'_>,
1654 scale: f32,
1655 rotation_deg: f32,
1656 ) -> Result<(), D::Error> {
1657 if rect.is_empty() || image.width == 0 || image.height == 0 || scale <= 0.0 {
1658 return Ok(());
1659 }
1660 let cx = rect.x + rect.w as i32 / 2;
1661 let cy = rect.y + rect.h as i32 / 2;
1662 let rad = rotation_deg.to_radians();
1663 let cos_r = rad.cos();
1664 let sin_r = rad.sin();
1665 let src_w = image.width as usize;
1666 let src_cx = image.width as f32 / 2.0;
1667 let src_cy = image.height as f32 / 2.0;
1668 for y in rect.y..rect.bottom() {
1669 for x in rect.x..rect.right() {
1670 let dx = (x - cx) as f32 / scale;
1671 let dy = (y - cy) as f32 / scale;
1672 let sx = cos_r * dx + sin_r * dy + src_cx;
1673 let sy = -sin_r * dx + cos_r * dy + src_cy;
1674 if sx < 0.0 || sy < 0.0 || sx >= image.width as f32 || sy >= image.height as f32 {
1675 continue;
1676 }
1677 let idx = (sy as usize)
1678 .saturating_mul(src_w)
1679 .saturating_add(sx as usize);
1680 if let Some(raw) = image.pixels.get(idx) {
1681 let color = Rgb565::new(
1682 ((raw >> 11) & 0x1F) as u8,
1683 ((raw >> 5) & 0x3F) as u8,
1684 (raw & 0x1F) as u8,
1685 );
1686 self.pixel(x, y, color, 255)?;
1687 }
1688 }
1689 }
1690 Ok(())
1691 }
1692
1693 pub fn fill_rect_masked(
1694 &mut self,
1695 rect: Rect,
1696 color: Rgb565,
1697 mask: fn(i32, i32) -> bool,
1698 ) -> Result<(), D::Error> {
1699 let draw = self.visible_rect(rect);
1700 if draw.is_empty() {
1701 return Ok(());
1702 }
1703 for y in draw.y..draw.bottom() {
1704 for x in draw.x..draw.right() {
1705 if mask(x, y) {
1706 self.pixel(x, y, color, 255)?;
1707 }
1708 }
1709 }
1710 Ok(())
1711 }
1712
1713 pub fn draw_text_model_in(&mut self, rect: Rect, text: text::Text<'_>) -> Result<(), D::Error> {
1714 if rect.is_empty() || text.lines.is_empty() {
1715 return Ok(());
1716 }
1717
1718 let metrics = text.metrics(rect.w);
1719 let max_line_height = text
1720 .lines
1721 .iter()
1722 .map(|line| line.max_line_height())
1723 .max()
1724 .unwrap_or(CHAR_HEIGHT);
1725 let line_step = max_line_height + text.line_spacing as u32;
1726 let mut y = match text.vertical_align {
1727 VerticalAlign::Top => rect.y,
1728 VerticalAlign::Middle => rect.y + rect.h.saturating_sub(metrics.height) as i32 / 2,
1729 VerticalAlign::Bottom => rect.y + rect.h.saturating_sub(metrics.height) as i32,
1730 };
1731 for line in text.lines {
1732 let align = if line.align == TextAlign::Left {
1733 text.align
1734 } else {
1735 line.align
1736 };
1737 let line = text::Line { align, ..*line };
1738
1739 let mut start = 0;
1740 let char_count = line.char_count();
1741 if char_count == 0 {
1742 y += line_step as i32;
1743 continue;
1744 }
1745 while start < char_count {
1746 if y >= rect.bottom() {
1747 return Ok(());
1748 }
1749 let (len, consumed_newline) = line.segment_len_at(start, rect.w, text.wrap);
1750 self.draw_line_segment_in(
1751 Rect::new(rect.x, y, rect.w, max_line_height),
1752 line,
1753 start,
1754 len,
1755 )?;
1756 y += line_step as i32;
1757 start += len + usize::from(consumed_newline);
1758 if len == 0 && !consumed_newline {
1759 break;
1760 }
1761 }
1762 }
1763
1764 Ok(())
1765 }
1766
1767 pub fn text_metrics(text: &str) -> TextMetrics {
1768 Self::text_metrics_with_font(text, FontId::Tiny3x5)
1769 }
1770
1771 pub fn text_metrics_with_font(text: &str, font: FontId) -> TextMetrics {
1772 TextMetrics {
1773 width: text.chars().count() as u32 * font.advance(),
1774 height: font.line_height(),
1775 }
1776 }
1777
1778 pub fn text_metrics_wrapped(text: &str, max_width: u32, wrap: TextWrap) -> TextMetrics {
1779 Self::text_metrics_wrapped_with_font(text, max_width, wrap, FontId::Tiny3x5)
1780 }
1781
1782 pub fn text_metrics_wrapped_with_font(
1783 text: &str,
1784 max_width: u32,
1785 wrap: TextWrap,
1786 font: FontId,
1787 ) -> TextMetrics {
1788 let max_chars = (max_width / font.advance()).max(1) as usize;
1789 let lines = count_lines(text, max_chars, wrap).max(1);
1790 let widest = widest_line(text, max_chars, wrap) as u32 * font.advance();
1791 TextMetrics {
1792 width: widest.min(max_width),
1793 height: lines as u32 * font.line_height() + lines.saturating_sub(1) as u32,
1794 }
1795 }
1796
1797 #[allow(clippy::too_many_arguments)]
1798 fn draw_chars_with_font(
1799 &mut self,
1800 x: i32,
1801 y: i32,
1802 text: &str,
1803 start: usize,
1804 len: usize,
1805 color: Rgb565,
1806 opacity: u8,
1807 font: FontId,
1808 kerning: bool,
1809 ) -> Result<(), D::Error> {
1810 let advance = font.advance() as i32;
1811 let mut cursor_x = x;
1812 let mut prev: Option<char> = None;
1813 for ch in text.chars().skip(start).take(len) {
1814 self.draw_char_with_font(cursor_x, y, ch, color, opacity, font)?;
1815 cursor_x += advance + kerning_adjust(prev, ch, kerning);
1816 prev = Some(ch);
1817 }
1818 Ok(())
1819 }
1820
1821 fn substring_width(
1822 &self,
1823 text: &str,
1824 start: usize,
1825 len: usize,
1826 font: FontId,
1827 kerning: bool,
1828 ) -> u32 {
1829 let mut width = 0u32;
1830 let mut prev = None;
1831 for ch in text.chars().skip(start).take(len) {
1832 width = width.saturating_add(font.advance());
1833 let adjust = kerning_adjust(prev, ch, kerning);
1834 if adjust < 0 {
1835 width = width.saturating_sub((-adjust) as u32);
1836 } else {
1837 width = width.saturating_add(adjust as u32);
1838 }
1839 prev = Some(ch);
1840 }
1841 width
1842 }
1843
1844 fn draw_line_segment_in(
1845 &mut self,
1846 rect: Rect,
1847 line: text::Line<'_>,
1848 start: usize,
1849 len: usize,
1850 ) -> Result<(), D::Error> {
1851 if rect.is_empty() || len == 0 {
1852 return Ok(());
1853 }
1854
1855 let line_w = self.line_segment_width(line, start, len);
1856 let x = match line.align {
1857 TextAlign::Left => rect.x,
1858 TextAlign::Center => rect.x + rect.w.saturating_sub(line_w) as i32 / 2,
1859 TextAlign::Right => rect.x + rect.w.saturating_sub(line_w) as i32,
1860 };
1861
1862 let old_clip = self.clip;
1863 self.clip = self.clip.intersection(rect);
1864 let result = self.draw_span_chars(x, rect.y, line, start, len);
1865 self.clip = old_clip;
1866 result
1867 }
1868
1869 fn draw_span_chars(
1870 &mut self,
1871 x: i32,
1872 y: i32,
1873 line: text::Line<'_>,
1874 start: usize,
1875 len: usize,
1876 ) -> Result<(), D::Error> {
1877 let mut cursor_x = x;
1878 for (idx, (ch, style)) in line
1879 .spans
1880 .iter()
1881 .flat_map(|span| span.content.chars().map(move |ch| (ch, span.style)))
1882 .enumerate()
1883 {
1884 if idx < start {
1885 continue;
1886 }
1887 if idx >= start + len {
1888 break;
1889 }
1890 if ch != '\n' {
1891 self.draw_char_with_font(cursor_x, y, ch, style.color, 255, style.font)?;
1892 cursor_x += style.font.advance() as i32;
1893 }
1894 }
1895 Ok(())
1896 }
1897
1898 fn line_segment_width(&self, line: text::Line<'_>, start: usize, len: usize) -> u32 {
1899 line.spans
1900 .iter()
1901 .flat_map(|span| span.content.chars().map(move |ch| (ch, span.style.font)))
1902 .enumerate()
1903 .filter_map(|(idx, (ch, font))| {
1904 if idx < start || idx >= start + len || ch == '\n' {
1905 None
1906 } else {
1907 Some(font.advance())
1908 }
1909 })
1910 .sum()
1911 }
1912
1913 fn draw_char_with_font(
1914 &mut self,
1915 x: i32,
1916 y: i32,
1917 ch: char,
1918 color: Rgb565,
1919 opacity: u8,
1920 font: FontId,
1921 ) -> Result<(), D::Error> {
1922 let glyph = glyph_rows(font, ch);
1923 let layer = self.current_layer();
1924 let fast_spans = opacity == 255
1925 && layer.opacity == 255
1926 && self.current_transform().is_identity()
1927 && layer.blend == BlendMode::Normal;
1928
1929 match font {
1930 FontId::Tiny3x5 | FontId::Medium4x7 | FontId::Custom(_) => {
1931 for (row, bits) in glyph.iter().enumerate() {
1932 let ry = y + row as i32;
1933 if fast_spans && *bits == 0b111 {
1934 self.fill_rect(Rect::new(x, ry, 3, 1), color)?;
1935 } else if fast_spans && *bits == 0b110 {
1936 self.fill_rect(Rect::new(x, ry, 2, 1), color)?;
1937 } else if fast_spans && *bits == 0b011 {
1938 self.fill_rect(Rect::new(x + 1, ry, 2, 1), color)?;
1939 } else {
1940 for col in 0..3 {
1941 if bits & (1 << (2 - col)) != 0 {
1942 self.pixel(x + col, ry, color, opacity)?;
1943 }
1944 }
1945 }
1946 }
1947 }
1948 FontId::Scaled6x10 => {
1949 for (row, bits) in glyph.iter().enumerate() {
1950 for col in 0..3 {
1951 if bits & (1 << (2 - col)) != 0 {
1952 let px = x + (col * 2);
1953 let py = y + (row as i32 * 2);
1954 self.pixel(px, py, color, opacity)?;
1955 self.pixel(px + 1, py, color, opacity)?;
1956 self.pixel(px, py + 1, color, opacity)?;
1957 self.pixel(px + 1, py + 1, color, opacity)?;
1958 }
1959 }
1960 }
1961 }
1962 }
1963 Ok(())
1964 }
1965
1966 fn pixel(&mut self, x: i32, y: i32, color: Rgb565, opacity: u8) -> Result<(), D::Error> {
1967 let (x, y) = self.current_transform().apply(x, y);
1968 if !self.clip.contains(x, y) {
1969 return Ok(());
1970 }
1971 if let Some(dirty) = self.dirty {
1972 if !dirty.contains(x, y) {
1973 return Ok(());
1974 }
1975 }
1976 let layer = self.current_layer();
1977 let combined_opacity = ((opacity as u16 * layer.opacity as u16) / 255) as u8;
1978 C::plot(
1982 self.target,
1983 x,
1984 y,
1985 color,
1986 combined_opacity,
1987 layer.blend,
1988 layer.backdrop,
1989 )
1990 }
1991
1992 fn visible_rect(&self, rect: Rect) -> Rect {
1993 let mut draw = rect.intersection(self.clip);
1994 if let Some(dirty) = self.dirty {
1995 draw = draw.intersection(dirty);
1996 }
1997 draw
1998 }
1999
2000 fn current_transform(&self) -> Transform2D {
2001 self.transform_stack[self.transform_len - 1]
2002 }
2003
2004 fn current_layer(&self) -> LayerState {
2005 self.layer_stack[self.layer_len - 1]
2006 }
2007
2008 fn stroke_opacity(&self, style: StrokeStyle) -> u8 {
2009 if !style.antialias || matches!(style.antialias_mode, AntiAliasMode::None) {
2010 return 255;
2011 }
2012 match style.antialias_mode {
2013 AntiAliasMode::None => 255,
2014 AntiAliasMode::Coverage => match self.quality {
2015 RenderQuality::Low => 96,
2016 RenderQuality::Medium => 160,
2017 RenderQuality::High => 220,
2018 },
2019 AntiAliasMode::Subpixel => {
2020 if self.backend_caps.supports_subpixel {
2021 match self.quality {
2022 RenderQuality::Low => 128,
2023 RenderQuality::Medium => 192,
2024 RenderQuality::High => 240,
2025 }
2026 } else {
2027 match self.quality {
2028 RenderQuality::Low => 96,
2029 RenderQuality::Medium => 160,
2030 RenderQuality::High => 220,
2031 }
2032 }
2033 }
2034 }
2035 }
2036}
2037
2038impl<'a, D, C> RenderCtx<'a, D, C>
2039where
2040 D: DrawTarget<Color = Rgb565> + PixelRead,
2041 C: Compositor<D>,
2042{
2043 fn pixel_blended(&mut self, x: i32, y: i32, color: Rgb565, alpha: u8) -> Result<(), D::Error> {
2047 let (x, y) = self.current_transform().apply(x, y);
2048 if !self.clip.contains(x, y) {
2049 return Ok(());
2050 }
2051 if let Some(dirty) = self.dirty {
2052 if !dirty.contains(x, y) {
2053 return Ok(());
2054 }
2055 }
2056 let layer = self.current_layer();
2057 let combined_alpha = ((alpha as u16 * layer.opacity as u16) / 255) as u8;
2058 if combined_alpha == 0 {
2059 return Ok(());
2060 }
2061 let backdrop = self.target.get_pixel(Point::new(x, y));
2062 let blended = lerp_rgb565(backdrop, color, combined_alpha);
2063 let blended = apply_blend_mode(blended, layer.blend, layer.backdrop);
2064 self.target.draw_iter([Pixel(Point::new(x, y), blended)])
2065 }
2066
2067 pub fn fill_rect_true_alpha(
2070 &mut self,
2071 rect: Rect,
2072 color: Rgb565,
2073 alpha: u8,
2074 ) -> Result<(), D::Error> {
2075 self.fill_rounded_rect_true_alpha(rect, 0, color, alpha)
2076 }
2077
2078 pub fn fill_rounded_rect_true_alpha(
2081 &mut self,
2082 rect: Rect,
2083 radius: u8,
2084 color: Rgb565,
2085 alpha: u8,
2086 ) -> Result<(), D::Error> {
2087 let draw = self.visible_rect(rect);
2088 if draw.is_empty() || alpha == 0 {
2089 return Ok(());
2090 }
2091 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
2092
2093 for y in draw.y..draw.bottom() {
2094 for x in draw.x..draw.right() {
2095 if !in_rounded_rect(x, y, rect, radius) {
2096 continue;
2097 }
2098 self.pixel_blended(x, y, color, alpha)?;
2099 }
2100 }
2101 Ok(())
2102 }
2103
2104 pub fn fill_rect_alpha_mask(
2106 &mut self,
2107 rect: Rect,
2108 mask: &[u8],
2109 mask_stride: usize,
2110 color: Rgb565,
2111 opacity: u8,
2112 ) -> Result<(), D::Error> {
2113 let draw = self.visible_rect(rect);
2114 if draw.is_empty() || opacity == 0 || mask_stride == 0 {
2115 return Ok(());
2116 }
2117 for y in draw.y..draw.bottom() {
2118 let my = (y - rect.y) as usize;
2119 for x in draw.x..draw.right() {
2120 let mx = (x - rect.x) as usize;
2121 let idx = my * mask_stride + mx;
2122 if let Some(&m_val) = mask.get(idx) {
2123 if m_val > 0 {
2124 let pix_opacity = ((m_val as u16 * opacity as u16) / 255) as u8;
2125 self.pixel(x, y, color, pix_opacity)?;
2126 }
2127 }
2128 }
2129 }
2130 Ok(())
2131 }
2132
2133 pub fn fill_rounded_rect_alpha_gradient(
2135 &mut self,
2136 rect: Rect,
2137 radius: u8,
2138 gradient: &AlphaLinearGradient,
2139 opacity: u8,
2140 ) -> Result<(), D::Error> {
2141 let draw = self.visible_rect(rect);
2142 if draw.is_empty() || opacity == 0 {
2143 return Ok(());
2144 }
2145 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
2146 let denom = match gradient.direction {
2147 GradientDirection::Horizontal => rect.w.saturating_sub(1).max(1),
2148 GradientDirection::Vertical => rect.h.saturating_sub(1).max(1),
2149 };
2150
2151 for y in draw.y..draw.bottom() {
2152 for x in draw.x..draw.right() {
2153 if !in_rounded_rect(x, y, rect, radius) {
2154 continue;
2155 }
2156 let numer = match gradient.direction {
2157 GradientDirection::Horizontal => (x - rect.x).max(0) as u32,
2158 GradientDirection::Vertical => (y - rect.y).max(0) as u32,
2159 }
2160 .min(denom);
2161 let t = ((numer * 255) / denom) as u8;
2162 let (color, grad_alpha) = gradient.sample(t);
2163 let combined_alpha = ((grad_alpha as u16 * opacity as u16) / 255) as u8;
2164 self.pixel(x, y, color, combined_alpha)?;
2165 }
2166 }
2167 Ok(())
2168 }
2169
2170 pub fn fill_rounded_rect_radial_gradient(
2172 &mut self,
2173 rect: Rect,
2174 radius: u8,
2175 gradient: &AlphaRadialGradient,
2176 opacity: u8,
2177 ) -> Result<(), D::Error> {
2178 let draw = self.visible_rect(rect);
2179 if draw.is_empty() || opacity == 0 {
2180 return Ok(());
2181 }
2182 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
2183 let cx = rect.x as f32 + rect.w as f32 * gradient.center_x;
2184 let cy = rect.y as f32 + rect.h as f32 * gradient.center_y;
2185
2186 for y in draw.y..draw.bottom() {
2187 let dy = y as f32 - cy;
2188 for x in draw.x..draw.right() {
2189 if !in_rounded_rect(x, y, rect, radius) {
2190 continue;
2191 }
2192 let dx = x as f32 - cx;
2193 let dist = (dx * dx + dy * dy).sqrt();
2194 let (color, grad_alpha) = gradient.sample_at_dist(dist);
2195 let combined_alpha = ((grad_alpha as u16 * opacity as u16) / 255) as u8;
2196 self.pixel(x, y, color, combined_alpha)?;
2197 }
2198 }
2199 Ok(())
2200 }
2201
2202 pub fn draw_drop_shadow(
2204 &mut self,
2205 rect: Rect,
2206 _radius: u8,
2207 shadow_color: Rgb565,
2208 shadow_opacity: u8,
2209 shadow_spread: u8,
2210 blur_radius: u8,
2211 ) -> Result<(), D::Error> {
2212 if shadow_opacity == 0 {
2213 return Ok(());
2214 }
2215 let margin = (shadow_spread as i32) + (blur_radius as i32);
2216 let shadow_rect = Rect::new(
2217 rect.x - margin,
2218 rect.y - margin,
2219 rect.w + (margin as u32 * 2),
2220 rect.h + (margin as u32 * 2),
2221 );
2222 let draw = self.visible_rect(shadow_rect);
2223 if draw.is_empty() {
2224 return Ok(());
2225 }
2226
2227 for y in draw.y..draw.bottom() {
2228 for x in draw.x..draw.right() {
2229 let dx = if x < rect.x {
2230 rect.x - x
2231 } else if x >= rect.right() {
2232 x - rect.right() + 1
2233 } else {
2234 0
2235 };
2236 let dy = if y < rect.y {
2237 rect.y - y
2238 } else if y >= rect.bottom() {
2239 y - rect.bottom() + 1
2240 } else {
2241 0
2242 };
2243
2244 let dist = ((dx * dx + dy * dy) as f32).sqrt();
2245 if dist > margin as f32 {
2246 continue;
2247 }
2248
2249 let factor = (1.0 - (dist / (margin as f32 + 1.0))).clamp(0.0, 1.0);
2250 let alpha = (shadow_opacity as f32 * factor) as u8;
2251 if alpha > 0 {
2252 self.pixel(x, y, shadow_color, alpha)?;
2253 }
2254 }
2255 }
2256 Ok(())
2257 }
2258
2259 pub fn draw_card_fill(
2261 &mut self,
2262 rect: Rect,
2263 radius: u8,
2264 bg_gradient: &AlphaLinearGradient,
2265 shadow_color: Rgb565,
2266 shadow_opacity: u8,
2267 blur_radius: u8,
2268 ) -> Result<(), D::Error> {
2269 if shadow_opacity > 0 && blur_radius > 0 {
2270 self.draw_drop_shadow(rect, radius, shadow_color, shadow_opacity, 2, blur_radius)?;
2271 }
2272 self.fill_rounded_rect_alpha_gradient(rect, radius, bg_gradient, 255)
2273 }
2274
2275 pub fn draw_tile(
2277 &mut self,
2278 rect: Rect,
2279 tile: TileRef<'_>,
2280 opacity: u8,
2281 ) -> Result<(), D::Error> {
2282 self.draw_tile_transformed_ssaa(rect, tile, Transform2D::IDENTITY, opacity, false)
2283 }
2284
2285 pub fn draw_tile_transformed(
2287 &mut self,
2288 rect: Rect,
2289 tile: TileRef<'_>,
2290 transform: Transform2D,
2291 opacity: u8,
2292 ) -> Result<(), D::Error> {
2293 self.draw_tile_transformed_ssaa(rect, tile, transform, opacity, false)
2294 }
2295
2296 pub fn draw_tile_transformed_ssaa(
2298 &mut self,
2299 rect: Rect,
2300 tile: TileRef<'_>,
2301 transform: Transform2D,
2302 opacity: u8,
2303 enable_ssaa: bool,
2304 ) -> Result<(), D::Error> {
2305 let draw = self.visible_rect(rect);
2306 if draw.is_empty() || opacity == 0 || tile.width == 0 || tile.height == 0 {
2307 return Ok(());
2308 }
2309
2310 let inv_transform = match transform.inverse() {
2311 Some(inv) => inv,
2312 None => return Ok(()),
2313 };
2314
2315 let cx = rect.x as f32 + rect.w as f32 * 0.5;
2316 let cy = rect.y as f32 + rect.h as f32 * 0.5;
2317
2318 let offsets = [
2319 (0.25f32, 0.25f32),
2320 (0.75f32, 0.25f32),
2321 (0.25f32, 0.75f32),
2322 (0.75f32, 0.75f32),
2323 ];
2324
2325 for y in draw.y..draw.bottom() {
2326 for x in draw.x..draw.right() {
2327 if !enable_ssaa {
2328 let px = (x as f32 + 0.5) - cx;
2329 let py = (y as f32 + 0.5) - cy;
2330 let (tx, ty) = inv_transform.apply_f32(px, py);
2331 let u = (tx + tile.width as f32 * 0.5).floor() as i32;
2332 let v = (ty + tile.height as f32 * 0.5).floor() as i32;
2333 if let Some(col) = tile.get_pixel(u, v) {
2334 self.pixel(x, y, col, opacity)?;
2335 }
2336 } else {
2337 let mut r_sum = 0u32;
2338 let mut g_sum = 0u32;
2339 let mut b_sum = 0u32;
2340 let mut weight = 0u32;
2341
2342 for &(ox, oy) in &offsets {
2343 let px = (x as f32 + ox) - cx;
2344 let py = (y as f32 + oy) - cy;
2345 let (tx, ty) = inv_transform.apply_f32(px, py);
2346 let u = (tx + tile.width as f32 * 0.5).floor() as i32;
2347 let v = (ty + tile.height as f32 * 0.5).floor() as i32;
2348 if let Some(col) = tile.get_pixel(u, v) {
2349 r_sum += col.r() as u32;
2350 g_sum += col.g() as u32;
2351 b_sum += col.b() as u32;
2352 weight += 1;
2353 }
2354 }
2355
2356 if let Some(w) = core::num::NonZeroU32::new(weight) {
2357 let weight_val = w.get();
2358 let r_avg = (r_sum / weight_val) as u8;
2359 let g_avg = (g_sum / weight_val) as u8;
2360 let b_avg = (b_sum / weight_val) as u8;
2361 let color = Rgb565::new(r_avg, g_avg, b_avg);
2362 let pix_opacity = ((weight * opacity as u32 + 2) / 4) as u8;
2363 self.pixel(x, y, color, pix_opacity)?;
2364 }
2365 }
2366 }
2367 }
2368 Ok(())
2369 }
2370
2371 pub fn draw_image_transformed_ssaa(
2373 &mut self,
2374 rect: Rect,
2375 image: ImageRef<'_>,
2376 scale: f32,
2377 rotation_deg: f32,
2378 opacity: u8,
2379 enable_ssaa: bool,
2380 ) -> Result<(), D::Error> {
2381 let transform = Transform2D::rotation(rotation_deg).then(Transform2D::scale(scale, scale));
2382 let tile = TileRef::from_image(image, TileMode::None);
2383 self.draw_tile_transformed_ssaa(rect, tile, transform, opacity, enable_ssaa)
2384 }
2385}
2386
2387fn should_draw_at_opacity(x: i32, y: i32, opacity: u8) -> bool {
2388 if opacity == 255 {
2389 return true;
2390 }
2391 if opacity == 0 {
2392 return false;
2393 }
2394 let bayer4 = [
2395 [0u8, 8, 2, 10],
2396 [12, 4, 14, 6],
2397 [3, 11, 1, 9],
2398 [15, 7, 13, 5],
2399 ];
2400 let threshold = ((opacity as u16 * 16) / 255) as u8;
2401 let sample = bayer4[(y as usize) & 3][(x as usize) & 3];
2402 sample < threshold.max(1)
2403}
2404
2405fn lerp_rgb565(a: Rgb565, b: Rgb565, t: u8) -> Rgb565 {
2406 let t = t as u16;
2407 let inv = 255u16.saturating_sub(t);
2408 let r = ((a.r() as u16 * inv) + (b.r() as u16 * t)) / 255;
2409 let g = ((a.g() as u16 * inv) + (b.g() as u16 * t)) / 255;
2410 let bb = ((a.b() as u16 * inv) + (b.b() as u16 * t)) / 255;
2411 Rgb565::new(r as u8, g as u8, bb as u8)
2412}
2413
2414#[inline]
2415fn normalize_angle_deg(mut deg: f32) -> f32 {
2416 while deg < 0.0 {
2417 deg += 360.0;
2418 }
2419 while deg >= 360.0 {
2420 deg -= 360.0;
2421 }
2422 deg
2423}
2424
2425#[inline]
2434fn cardinal_unit(deg: f32) -> Option<(f32, f32)> {
2435 const EPS: f32 = 1e-4;
2436 let normalized = normalize_angle_deg(deg);
2437 if (normalized - 0.0).abs() < EPS {
2438 Some((1.0, 0.0))
2439 } else if (normalized - 90.0).abs() < EPS {
2440 Some((0.0, 1.0))
2441 } else if (normalized - 180.0).abs() < EPS {
2442 Some((-1.0, 0.0))
2443 } else if (normalized - 270.0).abs() < EPS {
2444 Some((0.0, -1.0))
2445 } else {
2446 None
2447 }
2448}
2449
2450#[inline]
2451fn cross(ux: f32, uy: f32, vx: f32, vy: f32) -> f32 {
2452 ux * vy - uy * vx
2453}
2454
2455fn apply_blend_mode(src: Rgb565, mode: BlendMode, backdrop: Rgb565) -> Rgb565 {
2456 match mode {
2457 BlendMode::Normal => src,
2458 BlendMode::Add => Rgb565::new(
2459 src.r().saturating_add(backdrop.r()),
2460 src.g().saturating_add(backdrop.g()),
2461 src.b().saturating_add(backdrop.b()),
2462 ),
2463 BlendMode::Multiply => Rgb565::new(
2464 ((src.r() as u16 * backdrop.r() as u16) / 31) as u8,
2465 ((src.g() as u16 * backdrop.g() as u16) / 63) as u8,
2466 ((src.b() as u16 * backdrop.b() as u16) / 31) as u8,
2467 ),
2468 BlendMode::Screen => Rgb565::new(
2469 (31 - ((31 - src.r() as u16) * (31 - backdrop.r() as u16) / 31)) as u8,
2470 (63 - ((63 - src.g() as u16) * (63 - backdrop.g() as u16) / 63)) as u8,
2471 (31 - ((31 - src.b() as u16) * (31 - backdrop.b() as u16) / 31)) as u8,
2472 ),
2473 }
2474}
2475
2476fn in_rounded_rect(x: i32, y: i32, rect: Rect, radius: u8) -> bool {
2477 if rect.is_empty() {
2478 return false;
2479 }
2480 let radius = radius as i32;
2481 if radius <= 0 {
2482 return rect.contains(x, y);
2483 }
2484
2485 let left = rect.x;
2486 let top = rect.y;
2487 let right = rect.right() - 1;
2488 let bottom = rect.bottom() - 1;
2489 let inner_left = left + radius;
2490 let inner_right = right - radius;
2491 let inner_top = top + radius;
2492 let inner_bottom = bottom - radius;
2493
2494 if (x >= inner_left && x <= inner_right) || (y >= inner_top && y <= inner_bottom) {
2495 return rect.contains(x, y);
2496 }
2497
2498 let (cx, cy) = if x < inner_left && y < inner_top {
2499 (inner_left, inner_top)
2500 } else if x > inner_right && y < inner_top {
2501 (inner_right, inner_top)
2502 } else if x < inner_left && y > inner_bottom {
2503 (inner_left, inner_bottom)
2504 } else if x > inner_right && y > inner_bottom {
2505 (inner_right, inner_bottom)
2506 } else {
2507 return rect.contains(x, y);
2508 };
2509
2510 let dx = x - cx;
2511 let dy = y - cy;
2512 dx * dx + dy * dy <= radius * radius
2513}
2514
2515fn line_len_at(text: &str, start: usize, max_chars: usize, wrap: TextWrap) -> (usize, bool) {
2516 let mut len = 0;
2517 let limit = match wrap {
2518 TextWrap::None => usize::MAX,
2519 TextWrap::Character => max_chars.max(1),
2520 TextWrap::Word => max_chars.max(1),
2521 };
2522 let mut last_ws_break = None;
2523
2524 for ch in text.chars().skip(start) {
2525 if ch == '\n' {
2526 return (len, true);
2527 }
2528 if matches!(wrap, TextWrap::Word) && ch.is_whitespace() {
2529 last_ws_break = Some(len + 1);
2530 }
2531 if len >= limit {
2532 if matches!(wrap, TextWrap::Word) {
2533 if let Some(idx) = last_ws_break {
2534 return (idx, false);
2535 }
2536 }
2537 return (len, false);
2538 }
2539 len += 1;
2540 }
2541
2542 (len, false)
2543}
2544
2545fn count_lines(text: &str, max_chars: usize, wrap: TextWrap) -> usize {
2546 if text.is_empty() {
2547 return 1;
2548 }
2549 let char_count = text.chars().count();
2550 let mut lines = 0;
2551 let mut start = 0;
2552 while start < char_count {
2553 let (len, consumed_newline) = line_len_at(text, start, max_chars, wrap);
2554 lines += 1;
2555 start += len + usize::from(consumed_newline);
2556 if len == 0 && !consumed_newline {
2557 break;
2558 }
2559 }
2560 lines
2561}
2562
2563fn widest_line(text: &str, max_chars: usize, wrap: TextWrap) -> usize {
2564 let char_count = text.chars().count();
2565 let mut widest = 0;
2566 let mut start = 0;
2567 while start < char_count {
2568 let (len, consumed_newline) = line_len_at(text, start, max_chars, wrap);
2569 widest = widest.max(len);
2570 start += len + usize::from(consumed_newline);
2571 if len == 0 && !consumed_newline {
2572 break;
2573 }
2574 }
2575 widest
2576}
2577
2578fn kerning_adjust(prev: Option<char>, next: char, enabled: bool) -> i32 {
2579 if !enabled {
2580 return 0;
2581 }
2582 match (prev, next) {
2583 (Some('A'), 'V') | (Some('A'), 'W') | (Some('T'), 'o') | (Some('L'), 'T') => -1,
2584 _ => 0,
2585 }
2586}
2587
2588#[cfg(test)]
2589mod tests {
2590 use super::*;
2591
2592 #[test]
2593 fn test_transform2d_is_identity() {
2594 let id = Transform2D::IDENTITY;
2595 assert!(id.is_identity());
2596 assert_eq!(id.apply(10, 20), (10, 20));
2597
2598 let tr = Transform2D::translation(5.0, 10.0);
2599 assert!(!tr.is_identity());
2600 assert_eq!(tr.apply(10, 20), (15, 30));
2601 }
2602
2603 #[test]
2604 fn test_fill_circle_scanline_spans_correctness() {
2605 let mut buf = crate::test_buffer::TestBuffer::new(50, 50);
2606 let mut ctx = RenderCtx::new(&mut buf, Rect::new(0, 0, 50, 50));
2607
2608 ctx.fill_circle(25, 25, 10, Rgb565::RED).unwrap();
2610
2611 assert_eq!(buf.pixel_at(25, 25), Some(Rgb565::RED));
2613
2614 assert_eq!(buf.pixel_at(32, 32), Some(Rgb565::RED));
2616
2617 assert_eq!(buf.pixel_at(37, 25), Some(Rgb565::BLACK));
2619 assert_eq!(buf.pixel_at(25, 37), Some(Rgb565::BLACK));
2620 }
2621
2622 #[test]
2623 fn test_cardinal_unit_exact_values_and_fallback() {
2624 assert_eq!(cardinal_unit(0.0), Some((1.0, 0.0)));
2625 assert_eq!(cardinal_unit(90.0), Some((0.0, 1.0)));
2626 assert_eq!(cardinal_unit(180.0), Some((-1.0, 0.0)));
2627 assert_eq!(cardinal_unit(270.0), Some((0.0, -1.0)));
2628 assert_eq!(cardinal_unit(-90.0), Some((0.0, -1.0)));
2631 assert_eq!(cardinal_unit(45.0), None);
2634 assert_eq!(cardinal_unit(89.99), None);
2635 }
2636
2637 #[test]
2638 fn test_cardinal_unit_agrees_with_real_trig_at_cardinal_angles() {
2639 for deg in [0.0_f32, 90.0, 180.0, 270.0, -90.0, 450.0] {
2645 let (fast_c, fast_s) = cardinal_unit(deg).expect("cardinal angle");
2646 let (real_c, real_s) = (deg.to_radians().cos(), deg.to_radians().sin());
2647 assert!(
2648 (fast_c - real_c).abs() < 1e-6,
2649 "cos mismatch at {deg}: fast={fast_c} real={real_c}"
2650 );
2651 assert!(
2652 (fast_s - real_s).abs() < 1e-6,
2653 "sin mismatch at {deg}: fast={fast_s} real={real_s}"
2654 );
2655 }
2656 }
2657
2658 #[test]
2659 fn test_fill_sector_sweep_cardinal_fast_path_renders() {
2660 let mut buf = crate::test_buffer::TestBuffer::new(50, 50);
2665 let mut ctx = RenderCtx::new(&mut buf, Rect::new(0, 0, 50, 50));
2666 ctx.fill_sector_sweep(25, 25, 20, -90.0, 90.0, Rgb565::RED)
2667 .unwrap();
2668 assert!(buf.count_color(Rgb565::RED) > 0);
2669 assert_eq!(buf.pixel_at(40, 25), Some(Rgb565::RED));
2674 assert_eq!(buf.pixel_at(25, 40), Some(Rgb565::BLACK));
2675 }
2676}