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},
16 style::{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 pub fn apply(self, x: i32, y: i32) -> (i32, i32) {
268 let xf = x as f32;
269 let yf = y as f32;
270 (
271 (self.m11 * xf + self.m12 * yf + self.tx).round() as i32,
272 (self.m21 * xf + self.m22 * yf + self.ty).round() as i32,
273 )
274 }
275}
276
277#[derive(Clone, Copy, Debug, PartialEq, Eq)]
278pub enum BlendMode {
279 Normal,
280 Add,
281 Multiply,
282 Screen,
283}
284
285pub trait PixelRead: DrawTarget {
293 fn get_pixel(&self, point: Point) -> Self::Color;
294}
295
296pub trait Compositor<D: DrawTarget<Color = Rgb565>> {
309 fn plot(
310 target: &mut D,
311 x: i32,
312 y: i32,
313 color: Rgb565,
314 opacity: u8,
315 blend: BlendMode,
316 backdrop: Rgb565,
317 ) -> Result<(), D::Error>;
318}
319
320#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
322pub struct Dither;
323
324#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
326pub struct Blend;
327
328impl<D: DrawTarget<Color = Rgb565>> Compositor<D> for Dither {
329 fn plot(
330 target: &mut D,
331 x: i32,
332 y: i32,
333 color: Rgb565,
334 opacity: u8,
335 blend: BlendMode,
336 backdrop: Rgb565,
337 ) -> Result<(), D::Error> {
338 if !should_draw_at_opacity(x, y, opacity) {
339 return Ok(());
340 }
341 let color = apply_blend_mode(color, blend, backdrop);
342 target.draw_iter([Pixel(Point::new(x, y), color)])
343 }
344}
345
346impl<D: DrawTarget<Color = Rgb565> + PixelRead> Compositor<D> for Blend {
347 fn plot(
348 target: &mut D,
349 x: i32,
350 y: i32,
351 color: Rgb565,
352 opacity: u8,
353 blend: BlendMode,
354 backdrop: Rgb565,
355 ) -> Result<(), D::Error> {
356 if opacity == 0 {
357 return Ok(());
358 }
359 let bg = target.get_pixel(Point::new(x, y));
360 let blended = lerp_rgb565(bg, color, opacity);
361 let blended = apply_blend_mode(blended, blend, backdrop);
362 target.draw_iter([Pixel(Point::new(x, y), blended)])
363 }
364}
365
366#[derive(Clone, Copy, Debug, PartialEq, Eq)]
367pub enum ColorFormat {
368 Rgb565,
369 Rgb888,
370 Argb8888,
371}
372
373#[derive(Clone, Copy, Debug, PartialEq, Eq)]
374pub struct RenderBackendCaps {
375 pub color_format: ColorFormat,
376 pub supports_layers: bool,
377 pub supports_subpixel: bool,
378}
379
380impl RenderBackendCaps {
381 pub const fn software_rgb565() -> Self {
382 Self {
383 color_format: ColorFormat::Rgb565,
384 supports_layers: true,
385 supports_subpixel: false,
386 }
387 }
388}
389
390#[derive(Clone, Copy, Debug, PartialEq, Eq)]
391pub struct LayerState {
392 pub opacity: u8,
393 pub blend: BlendMode,
394 pub backdrop: Rgb565,
395}
396
397impl LayerState {
398 pub const fn normal() -> Self {
399 Self {
400 opacity: 255,
401 blend: BlendMode::Normal,
402 backdrop: Rgb565::BLACK,
403 }
404 }
405}
406
407impl StrokeStyle {
408 pub const fn new(color: Rgb565) -> Self {
409 Self {
410 color,
411 width: 1,
412 antialias: false,
413 antialias_mode: AntiAliasMode::None,
414 cap: StrokeCap::Butt,
415 join: StrokeJoin::Miter,
416 }
417 }
418
419 pub const fn with_width(mut self, width: u8) -> Self {
420 self.width = if width == 0 { 1 } else { width };
421 self
422 }
423
424 pub const fn with_antialias(mut self, antialias: bool) -> Self {
425 self.antialias = antialias;
426 if antialias {
427 if let AntiAliasMode::None = self.antialias_mode {
428 self.antialias_mode = AntiAliasMode::Coverage;
429 }
430 }
431 if !antialias {
432 self.antialias_mode = AntiAliasMode::None;
433 }
434 self
435 }
436
437 pub const fn with_antialias_mode(mut self, mode: AntiAliasMode) -> Self {
438 self.antialias_mode = mode;
439 self.antialias = !matches!(mode, AntiAliasMode::None);
440 self
441 }
442
443 pub const fn with_cap(mut self, cap: StrokeCap) -> Self {
444 self.cap = cap;
445 self
446 }
447
448 pub const fn with_join(mut self, join: StrokeJoin) -> Self {
449 self.join = join;
450 self
451 }
452}
453
454pub struct RenderCtx<'a, D, C = Dither>
455where
456 D: DrawTarget<Color = Rgb565>,
457{
458 target: &'a mut D,
459 clip: Rect,
460 dirty: Option<Rect>,
461 quality: RenderQuality,
462 backend_caps: RenderBackendCaps,
463 transform_stack: [Transform2D; 8],
464 transform_len: usize,
465 layer_stack: [LayerState; 8],
466 layer_len: usize,
467 _compositor: PhantomData<C>,
468}
469
470impl<'a, D> RenderCtx<'a, D, Dither>
471where
472 D: DrawTarget<Color = Rgb565>,
473{
474 pub fn new(target: &'a mut D, viewport: Rect) -> Self {
475 Self {
476 target,
477 clip: viewport,
478 dirty: None,
479 quality: RenderQuality::High,
480 backend_caps: RenderBackendCaps::software_rgb565(),
481 transform_stack: [Transform2D::IDENTITY; 8],
482 transform_len: 1,
483 layer_stack: [LayerState::normal(); 8],
484 layer_len: 1,
485 _compositor: PhantomData,
486 }
487 }
488
489 pub fn with_dirty(target: &'a mut D, viewport: Rect, dirty: Rect) -> Self {
490 Self {
491 target,
492 clip: viewport,
493 dirty: Some(dirty),
494 quality: RenderQuality::High,
495 backend_caps: RenderBackendCaps::software_rgb565(),
496 transform_stack: [Transform2D::IDENTITY; 8],
497 transform_len: 1,
498 layer_stack: [LayerState::normal(); 8],
499 layer_len: 1,
500 _compositor: PhantomData,
501 }
502 }
503}
504
505impl<'a, D> RenderCtx<'a, D, Blend>
506where
507 D: DrawTarget<Color = Rgb565> + PixelRead,
508{
509 pub fn compositing(target: &'a mut D, viewport: Rect) -> Self {
514 Self {
515 target,
516 clip: viewport,
517 dirty: None,
518 quality: RenderQuality::High,
519 backend_caps: RenderBackendCaps::software_rgb565(),
520 transform_stack: [Transform2D::IDENTITY; 8],
521 transform_len: 1,
522 layer_stack: [LayerState::normal(); 8],
523 layer_len: 1,
524 _compositor: PhantomData,
525 }
526 }
527}
528
529impl<'a, D, C> RenderCtx<'a, D, C>
530where
531 D: DrawTarget<Color = Rgb565>,
532 C: Compositor<D>,
533{
534 pub const fn clip(&self) -> Rect {
535 self.clip
536 }
537
538 pub fn set_clip(&mut self, clip: Rect) {
539 self.clip = clip;
540 }
541
542 #[cfg(any(feature = "embedded-text", feature = "embedded-layout"))]
547 pub fn draw_embedded_graphics<T>(&mut self, drawable: &T) -> Result<T::Output, D::Error>
548 where
549 T: embedded_graphics::Drawable<Color = Rgb565>,
550 {
551 use embedded_graphics::draw_target::DrawTargetExt;
552
553 let clip_rect: embedded_graphics::primitives::Rectangle = self.clip.into();
554 let mut clipped = self.target.clipped(&clip_rect);
555 drawable.draw(&mut clipped)
556 }
557
558 pub const fn quality(&self) -> RenderQuality {
559 self.quality
560 }
561
562 pub fn set_quality(&mut self, quality: RenderQuality) {
563 self.quality = quality;
564 }
565
566 pub const fn backend_caps(&self) -> RenderBackendCaps {
567 self.backend_caps
568 }
569
570 pub fn set_backend_caps(&mut self, caps: RenderBackendCaps) {
571 self.backend_caps = caps;
572 }
573
574 pub fn push_transform(&mut self, transform: Transform2D) {
575 if self.transform_len >= self.transform_stack.len() {
576 return;
577 }
578 let current = self.current_transform();
579 self.transform_stack[self.transform_len] = current.then(transform);
580 self.transform_len += 1;
581 }
582
583 pub fn pop_transform(&mut self) {
584 if self.transform_len > 1 {
585 self.transform_len -= 1;
586 }
587 }
588
589 pub fn translate(&mut self, x: f32, y: f32) {
590 self.push_transform(Transform2D::translation(x, y));
591 }
592
593 pub fn scale(&mut self, x: f32, y: f32) {
594 self.push_transform(Transform2D::scale(x, y));
595 }
596
597 pub fn rotate(&mut self, deg: f32) {
598 self.push_transform(Transform2D::rotation(deg));
599 }
600
601 pub fn skew(&mut self, x_deg: f32, y_deg: f32) {
602 self.push_transform(Transform2D::skew(x_deg, y_deg));
603 }
604
605 pub fn push_layer(&mut self, layer: LayerState) {
606 if self.layer_len >= self.layer_stack.len() {
607 return;
608 }
609 let current = self.current_layer();
610 self.layer_stack[self.layer_len] = LayerState {
611 opacity: ((current.opacity as u16 * layer.opacity as u16) / 255) as u8,
612 blend: layer.blend,
613 backdrop: layer.backdrop,
614 };
615 self.layer_len += 1;
616 }
617
618 pub fn pop_layer(&mut self) {
619 if self.layer_len > 1 {
620 self.layer_len -= 1;
621 }
622 }
623
624 pub const fn shadow_spread_for(&self, spread: u8) -> u8 {
625 match self.quality {
626 RenderQuality::Low => 0,
627 RenderQuality::Medium => {
628 if spread > 1 {
629 1
630 } else {
631 spread
632 }
633 }
634 RenderQuality::High => spread,
635 }
636 }
637
638 pub fn fill_rect(&mut self, rect: Rect, color: Rgb565) -> Result<(), D::Error> {
639 self.fill_rect_alpha(rect, color, 255)
640 }
641
642 pub fn fill_rect_alpha(
643 &mut self,
644 rect: Rect,
645 color: Rgb565,
646 opacity: u8,
647 ) -> Result<(), D::Error> {
648 self.fill_rounded_rect_alpha(rect, 0, color, opacity)
649 }
650
651 pub fn fill_rounded_rect(
652 &mut self,
653 rect: Rect,
654 radius: u8,
655 color: Rgb565,
656 ) -> Result<(), D::Error> {
657 self.fill_rounded_rect_alpha(rect, radius, color, 255)
658 }
659
660 pub fn fill_rounded_rect_alpha(
661 &mut self,
662 rect: Rect,
663 radius: u8,
664 color: Rgb565,
665 opacity: u8,
666 ) -> Result<(), D::Error> {
667 let draw = self.visible_rect(rect);
668 if draw.is_empty() || opacity == 0 {
669 return Ok(());
670 }
671 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
672
673 for y in draw.y..draw.bottom() {
674 for x in draw.x..draw.right() {
675 if !in_rounded_rect(x, y, rect, radius) {
676 continue;
677 }
678 self.pixel(x, y, color, opacity)?;
679 }
680 }
681 Ok(())
682 }
683
684 pub fn fill_rounded_rect_gradient_alpha(
685 &mut self,
686 rect: Rect,
687 radius: u8,
688 gradient: LinearGradient,
689 opacity: u8,
690 ) -> Result<(), D::Error> {
691 let draw = self.visible_rect(rect);
692 if draw.is_empty() || opacity == 0 {
693 return Ok(());
694 }
695 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
696 let denom = match gradient.direction {
697 GradientDirection::Horizontal => rect.w.saturating_sub(1).max(1),
698 GradientDirection::Vertical => rect.h.saturating_sub(1).max(1),
699 };
700
701 for y in draw.y..draw.bottom() {
702 for x in draw.x..draw.right() {
703 if !in_rounded_rect(x, y, rect, radius) {
704 continue;
705 }
706 let numer = match gradient.direction {
707 GradientDirection::Horizontal => (x - rect.x).max(0) as u32,
708 GradientDirection::Vertical => (y - rect.y).max(0) as u32,
709 }
710 .min(denom);
711 let mut t = ((numer * 255) / denom) as u8;
712 t = match self.quality {
713 RenderQuality::Low => 128,
714 RenderQuality::Medium => (t / 64) * 64,
715 RenderQuality::High => t,
716 };
717 let color = lerp_rgb565(gradient.start, gradient.end, t);
718 self.pixel(x, y, color, opacity)?;
719 }
720 }
721 Ok(())
722 }
723
724 pub fn stroke_rect(&mut self, rect: Rect, border: Border) -> Result<(), D::Error> {
725 self.stroke_rect_alpha(rect, border, 255)
726 }
727
728 pub fn stroke_rect_alpha(
729 &mut self,
730 rect: Rect,
731 border: Border,
732 opacity: u8,
733 ) -> Result<(), D::Error> {
734 if border.width == 0 || rect.is_empty() {
735 return Ok(());
736 }
737
738 for i in 0..border.width as i32 {
739 let w = rect.w.saturating_sub((i as u32).saturating_mul(2));
740 let h = rect.h.saturating_sub((i as u32).saturating_mul(2));
741 if w == 0 || h == 0 {
742 break;
743 }
744 let r = Rect::new(rect.x + i, rect.y + i, w, h);
745 self.fill_rect_alpha(Rect::new(r.x, r.y, r.w, 1), border.color, opacity)?;
746 if r.h > 1 {
747 self.fill_rect_alpha(
748 Rect::new(r.x, r.bottom() - 1, r.w, 1),
749 border.color,
750 opacity,
751 )?;
752 }
753 if r.h > 2 {
754 self.fill_rect_alpha(Rect::new(r.x, r.y + 1, 1, r.h - 2), border.color, opacity)?;
755 if r.w > 1 {
756 self.fill_rect_alpha(
757 Rect::new(r.right() - 1, r.y + 1, 1, r.h - 2),
758 border.color,
759 opacity,
760 )?;
761 }
762 }
763 }
764 Ok(())
765 }
766
767 pub fn stroke_rounded_rect(
768 &mut self,
769 rect: Rect,
770 radius: u8,
771 border: Border,
772 ) -> Result<(), D::Error> {
773 self.stroke_rounded_rect_alpha(rect, radius, border, 255)
774 }
775
776 pub fn stroke_rounded_rect_alpha(
777 &mut self,
778 rect: Rect,
779 radius: u8,
780 border: Border,
781 opacity: u8,
782 ) -> Result<(), D::Error> {
783 if border.width == 0 || rect.is_empty() || opacity == 0 {
784 return Ok(());
785 }
786 let draw = self.visible_rect(rect);
787 if draw.is_empty() {
788 return Ok(());
789 }
790
791 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
792 for y in draw.y..draw.bottom() {
793 for x in draw.x..draw.right() {
794 if !in_rounded_rect(x, y, rect, radius) {
795 continue;
796 }
797
798 let mut inner_hit = false;
799 let mut i = 1u8;
800 while i < border.width {
801 let inset = i as i32;
802 let inner = Rect::new(
803 rect.x + inset,
804 rect.y + inset,
805 rect.w.saturating_sub((i as u32) * 2),
806 rect.h.saturating_sub((i as u32) * 2),
807 );
808 let inner_radius = radius.saturating_sub(i);
809 if !inner.is_empty() && in_rounded_rect(x, y, inner, inner_radius) {
810 inner_hit = true;
811 break;
812 }
813 i += 1;
814 }
815
816 if !inner_hit {
817 self.pixel(x, y, border.color, opacity)?;
818 }
819 }
820 }
821 Ok(())
822 }
823
824 pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Rgb565) -> Result<(), D::Error> {
825 self.draw_text_with_font(x, y, text, color, FontId::Tiny3x5)
826 }
827
828 pub fn draw_text_with_font(
829 &mut self,
830 x: i32,
831 y: i32,
832 text: &str,
833 color: Rgb565,
834 font: FontId,
835 ) -> Result<(), D::Error> {
836 let advance = font.advance() as i32;
837 let line_h = font.line_height() as i32;
838 let mut cursor_x = x;
839 let mut cursor_y = y;
840 for ch in text.chars() {
841 if ch == '\n' {
842 cursor_x = x;
843 cursor_y += line_h;
844 continue;
845 }
846 self.draw_char_with_font(cursor_x, cursor_y, ch, color, 255, font)?;
847 cursor_x += advance;
848 }
849 Ok(())
850 }
851
852 pub fn draw_text_in(
853 &mut self,
854 rect: Rect,
855 text: &str,
856 style: TextStyle,
857 ) -> Result<(), D::Error> {
858 self.draw_text_in_with_font(rect, text, style, style.font)
859 }
860
861 pub fn draw_text_shaped_in<S, const N: usize>(
862 &mut self,
863 rect: Rect,
864 text: &str,
865 style: TextStyle,
866 shaper: &S,
867 config: crate::text::ShapingConfig,
868 ) -> Result<(), D::Error>
869 where
870 S: crate::text::TextShaper,
871 {
872 if rect.is_empty() {
873 return Ok(());
874 }
875 let mut shaped = heapless::Vec::<crate::text::ShapedGlyph, N>::new();
876 shaper.shape(text, config, &mut shaped);
877 if shaped.is_empty() {
878 return Ok(());
879 }
880 let mut x = rect.x;
881 let y = rect.y + rect.h.saturating_sub(style.font.line_height()) as i32 / 2;
882 for glyph in shaped {
883 self.draw_char_with_font(x, y, glyph.ch, style.color, style.opacity, style.font)?;
884 x += (glyph.x_advance as i32).max(1) * style.font.advance() as i32;
885 if x >= rect.right() {
886 break;
887 }
888 }
889 Ok(())
890 }
891
892 pub fn draw_text_in_with_font(
893 &mut self,
894 rect: Rect,
895 text: &str,
896 style: TextStyle,
897 font: FontId,
898 ) -> Result<(), D::Error> {
899 if rect.is_empty() {
900 return Ok(());
901 }
902
903 let advance = font.advance();
904 let line_h = font.line_height();
905 let max_chars = (rect.w / advance).max(1) as usize;
906 let char_count = text.chars().count();
907 let line_count = count_lines(text, max_chars, style.wrap).max(1);
908 let line_step = line_h + style.line_spacing as u32;
909 let total_h = line_count as u32 * line_h
910 + line_count.saturating_sub(1) as u32 * style.line_spacing as u32;
911 let mut y = match style.vertical_align {
912 VerticalAlign::Top => rect.y,
913 VerticalAlign::Middle => rect.y + rect.h.saturating_sub(total_h) as i32 / 2,
914 VerticalAlign::Bottom => rect.y + rect.h.saturating_sub(total_h) as i32,
915 };
916
917 let mut start = 0;
918 let mut rendered_lines = 0u8;
919 let max_lines = match style.overflow_policy {
920 TextOverflowPolicy::WrapThenEllipsis { max_lines } => max_lines.max(1),
921 TextOverflowPolicy::Global(_) => style.max_lines.unwrap_or(u8::MAX),
922 };
923 while start < char_count {
924 if rendered_lines >= max_lines {
925 break;
926 }
927 let (len, consumed_newline) = line_len_at(text, start, max_chars, style.wrap);
928 let mut draw_len = len;
929 let is_last_allowed_line = rendered_lines.saturating_add(1) >= max_lines;
930 let use_ellipsis = match style.overflow_policy {
931 TextOverflowPolicy::WrapThenEllipsis { .. } => is_last_allowed_line,
932 TextOverflowPolicy::Global(mode) => mode == TextOverflow::Ellipsis,
933 };
934 if use_ellipsis
935 && ((!consumed_newline && start + len < char_count) || is_last_allowed_line)
936 {
937 let ellipsis_width = match style.ellipsis {
938 EllipsisMode::ThreeDots => 3usize,
939 EllipsisMode::SingleGlyph => 1usize,
940 };
941 if len > ellipsis_width {
942 draw_len = len - ellipsis_width;
943 }
944 }
945 let line_w = self.substring_width(text, start, draw_len, font, style.kerning);
946 let x = match style.align {
947 TextAlign::Left => rect.x,
948 TextAlign::Center => rect.x + rect.w.saturating_sub(line_w) as i32 / 2,
949 TextAlign::Right => rect.x + rect.w.saturating_sub(line_w) as i32,
950 };
951 self.draw_chars_with_font(
952 x,
953 y,
954 text,
955 start,
956 draw_len,
957 style.color,
958 style.opacity,
959 font,
960 style.kerning,
961 )?;
962 if draw_len < len && use_ellipsis {
963 let token = match style.ellipsis {
964 EllipsisMode::ThreeDots => "...",
965 EllipsisMode::SingleGlyph => ".",
966 };
967 self.draw_text_with_font(x + line_w as i32, y, token, style.color, font)?;
968 }
969 y += line_step as i32;
970 rendered_lines = rendered_lines.saturating_add(1);
971 start += len + usize::from(consumed_newline);
972 if style.wrap == TextWrap::Word && start < char_count {
973 while text.chars().nth(start).is_some_and(|ch| ch == ' ') {
974 start += 1;
975 }
976 }
977 if len == 0 && !consumed_newline {
978 break;
979 }
980 }
981
982 Ok(())
983 }
984
985 pub fn draw_line_in(&mut self, rect: Rect, line: text::Line<'_>) -> Result<(), D::Error> {
986 if rect.is_empty() {
987 return Ok(());
988 }
989
990 self.draw_line_segment_in(rect, line, 0, line.width_chars())
991 }
992
993 pub fn draw_line(
994 &mut self,
995 x0: i32,
996 y0: i32,
997 x1: i32,
998 y1: i32,
999 color: Rgb565,
1000 ) -> Result<(), D::Error> {
1001 self.draw_line_styled(x0, y0, x1, y1, StrokeStyle::new(color))
1002 }
1003
1004 pub fn draw_line_styled(
1005 &mut self,
1006 x0: i32,
1007 y0: i32,
1008 x1: i32,
1009 y1: i32,
1010 style: StrokeStyle,
1011 ) -> Result<(), D::Error> {
1012 let mut x = x0;
1013 let mut y = y0;
1014 let dx = (x1 - x0).abs();
1015 let sx = if x0 < x1 { 1 } else { -1 };
1016 let dy = -(y1 - y0).abs();
1017 let sy = if y0 < y1 { 1 } else { -1 };
1018 let mut err = dx + dy;
1019 let half = (style.width as i32 / 2).max(0);
1020 let opacity = self.stroke_opacity(style);
1021
1022 loop {
1023 for oy in -half..=half {
1024 for ox in -half..=half {
1025 self.pixel(x + ox, y + oy, style.color, opacity)?;
1026 }
1027 }
1028 if style.cap == StrokeCap::Round {
1029 self.fill_circle(x0, y0, half.max(1) as u32, style.color)?;
1030 self.fill_circle(x1, y1, half.max(1) as u32, style.color)?;
1031 }
1032 if x == x1 && y == y1 {
1033 break;
1034 }
1035 let e2 = 2 * err;
1036 if e2 >= dy {
1037 err += dy;
1038 x += sx;
1039 }
1040 if e2 <= dx {
1041 err += dx;
1042 y += sy;
1043 }
1044 }
1045 Ok(())
1046 }
1047
1048 pub fn fill_circle(
1049 &mut self,
1050 center_x: i32,
1051 center_y: i32,
1052 radius: u32,
1053 color: Rgb565,
1054 ) -> Result<(), D::Error> {
1055 let radius = radius as i32;
1056 if radius <= 0 {
1057 return Ok(());
1058 }
1059 for y in -radius..=radius {
1060 for x in -radius..=radius {
1061 if x * x + y * y <= radius * radius {
1062 self.pixel(center_x + x, center_y + y, color, 255)?;
1063 }
1064 }
1065 }
1066 Ok(())
1067 }
1068
1069 pub fn stroke_circle(
1070 &mut self,
1071 center_x: i32,
1072 center_y: i32,
1073 radius: u32,
1074 color: Rgb565,
1075 ) -> Result<(), D::Error> {
1076 let radius = radius as i32;
1077 if radius <= 0 {
1078 return Ok(());
1079 }
1080 let mut x = radius;
1081 let mut y = 0;
1082 let mut err = 1 - x;
1083 while x >= y {
1084 self.pixel(center_x + x, center_y + y, color, 255)?;
1085 self.pixel(center_x + y, center_y + x, color, 255)?;
1086 self.pixel(center_x - y, center_y + x, color, 255)?;
1087 self.pixel(center_x - x, center_y + y, color, 255)?;
1088 self.pixel(center_x - x, center_y - y, color, 255)?;
1089 self.pixel(center_x - y, center_y - x, color, 255)?;
1090 self.pixel(center_x + y, center_y - x, color, 255)?;
1091 self.pixel(center_x + x, center_y - y, color, 255)?;
1092 y += 1;
1093 if err < 0 {
1094 err += 2 * y + 1;
1095 } else {
1096 x -= 1;
1097 err += 2 * (y - x) + 1;
1098 }
1099 }
1100 Ok(())
1101 }
1102
1103 pub fn stroke_arc(
1104 &mut self,
1105 center_x: i32,
1106 center_y: i32,
1107 radius: u32,
1108 start_deg: i32,
1109 end_deg: i32,
1110 color: Rgb565,
1111 ) -> Result<(), D::Error> {
1112 self.stroke_arc_styled(
1113 center_x,
1114 center_y,
1115 radius,
1116 start_deg,
1117 end_deg,
1118 StrokeStyle::new(color),
1119 )
1120 }
1121
1122 pub fn stroke_arc_styled(
1123 &mut self,
1124 center_x: i32,
1125 center_y: i32,
1126 radius: u32,
1127 start_deg: i32,
1128 end_deg: i32,
1129 style: StrokeStyle,
1130 ) -> Result<(), D::Error> {
1131 let mut start = start_deg;
1132 let mut end = end_deg;
1133 if end < start {
1134 core::mem::swap(&mut start, &mut end);
1135 }
1136 let mut deg = start;
1137 let step = match self.quality {
1138 RenderQuality::Low => 8,
1139 RenderQuality::Medium => 4,
1140 RenderQuality::High => 2,
1141 };
1142 while deg <= end {
1143 let rad = (deg as f32).to_radians();
1144 let x = center_x + (radius as f32 * rad.cos()) as i32;
1145 let y = center_y + (radius as f32 * rad.sin()) as i32;
1146 let half = (style.width as i32 / 2).max(0);
1147 let opacity = self.stroke_opacity(style);
1148 for oy in -half..=half {
1149 for ox in -half..=half {
1150 self.pixel(x + ox, y + oy, style.color, opacity)?;
1151 }
1152 }
1153 if style.join == StrokeJoin::Round {
1154 self.fill_circle(x, y, half.max(1) as u32, style.color)?;
1155 }
1156 deg += step;
1157 }
1158 Ok(())
1159 }
1160
1161 pub fn fill_sector_sweep(
1165 &mut self,
1166 center_x: i32,
1167 center_y: i32,
1168 radius: u32,
1169 start_deg: f32,
1170 sweep_deg: f32,
1171 color: Rgb565,
1172 ) -> Result<(), D::Error> {
1173 if radius == 0 {
1174 return Ok(());
1175 }
1176
1177 let draw = self.visible_rect(Rect::new(
1178 center_x - radius as i32,
1179 center_y - radius as i32,
1180 radius.saturating_mul(2).saturating_add(1),
1181 radius.saturating_mul(2).saturating_add(1),
1182 ));
1183 if draw.is_empty() {
1184 return Ok(());
1185 }
1186
1187 let max_sweep = sweep_deg.abs().min(360.0);
1188 if max_sweep <= 0.0 {
1189 return Ok(());
1190 }
1191
1192 let rr = (radius as i32) * (radius as i32);
1193 let start = normalize_angle_deg(start_deg);
1194 let ccw = sweep_deg >= 0.0;
1195
1196 for y in draw.y..draw.bottom() {
1197 for x in draw.x..draw.right() {
1198 let dx = x - center_x;
1199 let dy = y - center_y;
1200 let d2 = dx * dx + dy * dy;
1201 if d2 > rr {
1202 continue;
1203 }
1204
1205 let mut angle = (dy as f32).atan2(dx as f32).to_degrees();
1206 if angle < 0.0 {
1207 angle += 360.0;
1208 }
1209 let in_sweep = if ccw {
1210 ccw_distance_deg(start, angle) <= max_sweep
1211 } else {
1212 ccw_distance_deg(angle, start) <= max_sweep
1213 };
1214 if in_sweep {
1215 self.pixel(x, y, color, 255)?;
1216 }
1217 }
1218 }
1219 Ok(())
1220 }
1221
1222 pub fn fill_polygon(&mut self, points: &[Point], color: Rgb565) -> Result<(), D::Error> {
1223 if points.len() < 3 {
1224 return Ok(());
1225 }
1226 let min_y = points.iter().map(|p| p.y).min().unwrap_or(0);
1227 let max_y = points.iter().map(|p| p.y).max().unwrap_or(-1);
1228 for y in min_y..=max_y {
1229 let mut intersections = [i32::MIN; 16];
1230 let mut count = 0usize;
1231 for i in 0..points.len() {
1232 let p1 = points[i];
1233 let p2 = points[(i + 1) % points.len()];
1234 let (y1, y2) = if p1.y <= p2.y {
1235 (p1.y, p2.y)
1236 } else {
1237 (p2.y, p1.y)
1238 };
1239 if y < y1 || y >= y2 || y1 == y2 {
1240 continue;
1241 }
1242 if count >= intersections.len() {
1243 break;
1244 }
1245 let x = p1.x + ((y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y);
1246 intersections[count] = x;
1247 count += 1;
1248 }
1249 intersections[..count].sort_unstable();
1250 let mut i = 0;
1251 while i + 1 < count {
1252 let x0 = intersections[i];
1253 let x1 = intersections[i + 1];
1254 for x in x0..=x1 {
1255 self.pixel(x, y, color, 255)?;
1256 }
1257 i += 2;
1258 }
1259 }
1260 Ok(())
1261 }
1262
1263 pub fn draw_image(
1264 &mut self,
1265 rect: Rect,
1266 image: ImageRef<'_>,
1267 fit: ImageFit,
1268 ) -> Result<(), D::Error> {
1269 self.draw_image_region(rect, image, fit, Rect::new(0, 0, image.width, image.height))
1270 }
1271
1272 pub fn draw_image_region(
1273 &mut self,
1274 rect: Rect,
1275 image: ImageRef<'_>,
1276 fit: ImageFit,
1277 src_rect: Rect,
1278 ) -> Result<(), D::Error> {
1279 let bounds = image.bounds_at(rect, fit);
1280 if bounds.is_empty() || image.width == 0 || image.height == 0 {
1281 return Ok(());
1282 }
1283 let src_w = image.width as usize;
1284 for y in 0..bounds.h {
1285 let src_y = match fit {
1286 ImageFit::Stretch => {
1287 src_rect.y.max(0) as usize
1288 + ((y as u64 * src_rect.h as u64) / bounds.h as u64) as usize
1289 }
1290 ImageFit::Center => src_rect.y.max(0) as usize + y as usize,
1291 };
1292 for x in 0..bounds.w {
1293 let src_x = match fit {
1294 ImageFit::Stretch => {
1295 src_rect.x.max(0) as usize
1296 + ((x as u64 * src_rect.w as u64) / bounds.w as u64) as usize
1297 }
1298 ImageFit::Center => src_rect.x.max(0) as usize + x as usize,
1299 };
1300 let idx = src_y.saturating_mul(src_w).saturating_add(src_x);
1301 if let Some(raw) = image.pixels.get(idx) {
1302 let color = Rgb565::new(
1303 ((raw >> 11) & 0x1F) as u8,
1304 ((raw >> 5) & 0x3F) as u8,
1305 (raw & 0x1F) as u8,
1306 );
1307 self.pixel(bounds.x + x as i32, bounds.y + y as i32, color, 255)?;
1308 }
1309 }
1310 }
1311 Ok(())
1312 }
1313
1314 pub fn draw_image_transformed(
1315 &mut self,
1316 rect: Rect,
1317 image: ImageRef<'_>,
1318 scale: f32,
1319 rotation_deg: f32,
1320 ) -> Result<(), D::Error> {
1321 if rect.is_empty() || image.width == 0 || image.height == 0 || scale <= 0.0 {
1322 return Ok(());
1323 }
1324 let cx = rect.x + rect.w as i32 / 2;
1325 let cy = rect.y + rect.h as i32 / 2;
1326 let rad = rotation_deg.to_radians();
1327 let cos_r = rad.cos();
1328 let sin_r = rad.sin();
1329 let src_w = image.width as usize;
1330 let src_cx = image.width as f32 / 2.0;
1331 let src_cy = image.height as f32 / 2.0;
1332 for y in rect.y..rect.bottom() {
1333 for x in rect.x..rect.right() {
1334 let dx = (x - cx) as f32 / scale;
1335 let dy = (y - cy) as f32 / scale;
1336 let sx = cos_r * dx + sin_r * dy + src_cx;
1337 let sy = -sin_r * dx + cos_r * dy + src_cy;
1338 if sx < 0.0 || sy < 0.0 || sx >= image.width as f32 || sy >= image.height as f32 {
1339 continue;
1340 }
1341 let idx = (sy as usize)
1342 .saturating_mul(src_w)
1343 .saturating_add(sx as usize);
1344 if let Some(raw) = image.pixels.get(idx) {
1345 let color = Rgb565::new(
1346 ((raw >> 11) & 0x1F) as u8,
1347 ((raw >> 5) & 0x3F) as u8,
1348 (raw & 0x1F) as u8,
1349 );
1350 self.pixel(x, y, color, 255)?;
1351 }
1352 }
1353 }
1354 Ok(())
1355 }
1356
1357 pub fn fill_rect_masked(
1358 &mut self,
1359 rect: Rect,
1360 color: Rgb565,
1361 mask: fn(i32, i32) -> bool,
1362 ) -> Result<(), D::Error> {
1363 let draw = self.visible_rect(rect);
1364 if draw.is_empty() {
1365 return Ok(());
1366 }
1367 for y in draw.y..draw.bottom() {
1368 for x in draw.x..draw.right() {
1369 if mask(x, y) {
1370 self.pixel(x, y, color, 255)?;
1371 }
1372 }
1373 }
1374 Ok(())
1375 }
1376
1377 pub fn draw_text_model_in(&mut self, rect: Rect, text: text::Text<'_>) -> Result<(), D::Error> {
1378 if rect.is_empty() || text.lines.is_empty() {
1379 return Ok(());
1380 }
1381
1382 let metrics = text.metrics(rect.w);
1383 let max_line_height = text
1384 .lines
1385 .iter()
1386 .map(|line| line.max_line_height())
1387 .max()
1388 .unwrap_or(CHAR_HEIGHT);
1389 let line_step = max_line_height + text.line_spacing as u32;
1390 let mut y = match text.vertical_align {
1391 VerticalAlign::Top => rect.y,
1392 VerticalAlign::Middle => rect.y + rect.h.saturating_sub(metrics.height) as i32 / 2,
1393 VerticalAlign::Bottom => rect.y + rect.h.saturating_sub(metrics.height) as i32,
1394 };
1395 for line in text.lines {
1396 let align = if line.align == TextAlign::Left {
1397 text.align
1398 } else {
1399 line.align
1400 };
1401 let line = text::Line { align, ..*line };
1402
1403 let mut start = 0;
1404 let char_count = line.char_count();
1405 if char_count == 0 {
1406 y += line_step as i32;
1407 continue;
1408 }
1409 while start < char_count {
1410 if y >= rect.bottom() {
1411 return Ok(());
1412 }
1413 let (len, consumed_newline) = line.segment_len_at(start, rect.w, text.wrap);
1414 self.draw_line_segment_in(
1415 Rect::new(rect.x, y, rect.w, max_line_height),
1416 line,
1417 start,
1418 len,
1419 )?;
1420 y += line_step as i32;
1421 start += len + usize::from(consumed_newline);
1422 if len == 0 && !consumed_newline {
1423 break;
1424 }
1425 }
1426 }
1427
1428 Ok(())
1429 }
1430
1431 pub fn text_metrics(text: &str) -> TextMetrics {
1432 Self::text_metrics_with_font(text, FontId::Tiny3x5)
1433 }
1434
1435 pub fn text_metrics_with_font(text: &str, font: FontId) -> TextMetrics {
1436 TextMetrics {
1437 width: text.chars().count() as u32 * font.advance(),
1438 height: font.line_height(),
1439 }
1440 }
1441
1442 pub fn text_metrics_wrapped(text: &str, max_width: u32, wrap: TextWrap) -> TextMetrics {
1443 Self::text_metrics_wrapped_with_font(text, max_width, wrap, FontId::Tiny3x5)
1444 }
1445
1446 pub fn text_metrics_wrapped_with_font(
1447 text: &str,
1448 max_width: u32,
1449 wrap: TextWrap,
1450 font: FontId,
1451 ) -> TextMetrics {
1452 let max_chars = (max_width / font.advance()).max(1) as usize;
1453 let lines = count_lines(text, max_chars, wrap).max(1);
1454 let widest = widest_line(text, max_chars, wrap) as u32 * font.advance();
1455 TextMetrics {
1456 width: widest.min(max_width),
1457 height: lines as u32 * font.line_height() + lines.saturating_sub(1) as u32,
1458 }
1459 }
1460
1461 #[allow(clippy::too_many_arguments)]
1462 fn draw_chars_with_font(
1463 &mut self,
1464 x: i32,
1465 y: i32,
1466 text: &str,
1467 start: usize,
1468 len: usize,
1469 color: Rgb565,
1470 opacity: u8,
1471 font: FontId,
1472 kerning: bool,
1473 ) -> Result<(), D::Error> {
1474 let advance = font.advance() as i32;
1475 let mut cursor_x = x;
1476 let mut prev: Option<char> = None;
1477 for ch in text.chars().skip(start).take(len) {
1478 self.draw_char_with_font(cursor_x, y, ch, color, opacity, font)?;
1479 cursor_x += advance + kerning_adjust(prev, ch, kerning);
1480 prev = Some(ch);
1481 }
1482 Ok(())
1483 }
1484
1485 fn substring_width(
1486 &self,
1487 text: &str,
1488 start: usize,
1489 len: usize,
1490 font: FontId,
1491 kerning: bool,
1492 ) -> u32 {
1493 let mut width = 0u32;
1494 let mut prev = None;
1495 for ch in text.chars().skip(start).take(len) {
1496 width = width.saturating_add(font.advance());
1497 let adjust = kerning_adjust(prev, ch, kerning);
1498 if adjust < 0 {
1499 width = width.saturating_sub((-adjust) as u32);
1500 } else {
1501 width = width.saturating_add(adjust as u32);
1502 }
1503 prev = Some(ch);
1504 }
1505 width
1506 }
1507
1508 fn draw_line_segment_in(
1509 &mut self,
1510 rect: Rect,
1511 line: text::Line<'_>,
1512 start: usize,
1513 len: usize,
1514 ) -> Result<(), D::Error> {
1515 if rect.is_empty() || len == 0 {
1516 return Ok(());
1517 }
1518
1519 let line_w = self.line_segment_width(line, start, len);
1520 let x = match line.align {
1521 TextAlign::Left => rect.x,
1522 TextAlign::Center => rect.x + rect.w.saturating_sub(line_w) as i32 / 2,
1523 TextAlign::Right => rect.x + rect.w.saturating_sub(line_w) as i32,
1524 };
1525
1526 let old_clip = self.clip;
1527 self.clip = self.clip.intersection(rect);
1528 let result = self.draw_span_chars(x, rect.y, line, start, len);
1529 self.clip = old_clip;
1530 result
1531 }
1532
1533 fn draw_span_chars(
1534 &mut self,
1535 x: i32,
1536 y: i32,
1537 line: text::Line<'_>,
1538 start: usize,
1539 len: usize,
1540 ) -> Result<(), D::Error> {
1541 let mut cursor_x = x;
1542 for (idx, (ch, style)) in line
1543 .spans
1544 .iter()
1545 .flat_map(|span| span.content.chars().map(move |ch| (ch, span.style)))
1546 .enumerate()
1547 {
1548 if idx < start {
1549 continue;
1550 }
1551 if idx >= start + len {
1552 break;
1553 }
1554 if ch != '\n' {
1555 self.draw_char_with_font(cursor_x, y, ch, style.color, 255, style.font)?;
1556 cursor_x += style.font.advance() as i32;
1557 }
1558 }
1559 Ok(())
1560 }
1561
1562 fn line_segment_width(&self, line: text::Line<'_>, start: usize, len: usize) -> u32 {
1563 line.spans
1564 .iter()
1565 .flat_map(|span| span.content.chars().map(move |ch| (ch, span.style.font)))
1566 .enumerate()
1567 .filter_map(|(idx, (ch, font))| {
1568 if idx < start || idx >= start + len || ch == '\n' {
1569 None
1570 } else {
1571 Some(font.advance())
1572 }
1573 })
1574 .sum()
1575 }
1576
1577 fn draw_char_with_font(
1578 &mut self,
1579 x: i32,
1580 y: i32,
1581 ch: char,
1582 color: Rgb565,
1583 opacity: u8,
1584 font: FontId,
1585 ) -> Result<(), D::Error> {
1586 let glyph = glyph_rows(font, ch);
1587 match font {
1588 FontId::Tiny3x5 => {
1589 for (row, bits) in glyph.iter().enumerate() {
1590 for col in 0..3 {
1591 if bits & (1 << (2 - col)) != 0 {
1592 self.pixel(x + col, y + row as i32, color, opacity)?;
1593 }
1594 }
1595 }
1596 }
1597 FontId::Medium4x7 => {
1598 for (row, bits) in glyph.iter().enumerate() {
1599 for col in 0..3 {
1600 if bits & (1 << (2 - col)) != 0 {
1601 self.pixel(x + col, y + row as i32, color, opacity)?;
1602 }
1603 }
1604 }
1605 }
1606 FontId::Scaled6x10 => {
1607 for (row, bits) in glyph.iter().enumerate() {
1608 for col in 0..3 {
1609 if bits & (1 << (2 - col)) != 0 {
1610 let px = x + (col * 2);
1611 let py = y + (row as i32 * 2);
1612 self.pixel(px, py, color, opacity)?;
1613 self.pixel(px + 1, py, color, opacity)?;
1614 self.pixel(px, py + 1, color, opacity)?;
1615 self.pixel(px + 1, py + 1, color, opacity)?;
1616 }
1617 }
1618 }
1619 }
1620 }
1621 Ok(())
1622 }
1623
1624 fn pixel(&mut self, x: i32, y: i32, color: Rgb565, opacity: u8) -> Result<(), D::Error> {
1625 let (x, y) = self.current_transform().apply(x, y);
1626 if !self.clip.contains(x, y) {
1627 return Ok(());
1628 }
1629 if let Some(dirty) = self.dirty {
1630 if !dirty.contains(x, y) {
1631 return Ok(());
1632 }
1633 }
1634 let layer = self.current_layer();
1635 let combined_opacity = ((opacity as u16 * layer.opacity as u16) / 255) as u8;
1636 C::plot(
1640 self.target,
1641 x,
1642 y,
1643 color,
1644 combined_opacity,
1645 layer.blend,
1646 layer.backdrop,
1647 )
1648 }
1649
1650 fn visible_rect(&self, rect: Rect) -> Rect {
1651 let mut draw = rect.intersection(self.clip);
1652 if let Some(dirty) = self.dirty {
1653 draw = draw.intersection(dirty);
1654 }
1655 draw
1656 }
1657
1658 fn current_transform(&self) -> Transform2D {
1659 self.transform_stack[self.transform_len - 1]
1660 }
1661
1662 fn current_layer(&self) -> LayerState {
1663 self.layer_stack[self.layer_len - 1]
1664 }
1665
1666 fn stroke_opacity(&self, style: StrokeStyle) -> u8 {
1667 if !style.antialias || matches!(style.antialias_mode, AntiAliasMode::None) {
1668 return 255;
1669 }
1670 match style.antialias_mode {
1671 AntiAliasMode::None => 255,
1672 AntiAliasMode::Coverage => match self.quality {
1673 RenderQuality::Low => 96,
1674 RenderQuality::Medium => 160,
1675 RenderQuality::High => 220,
1676 },
1677 AntiAliasMode::Subpixel => {
1678 if self.backend_caps.supports_subpixel {
1679 match self.quality {
1680 RenderQuality::Low => 128,
1681 RenderQuality::Medium => 192,
1682 RenderQuality::High => 240,
1683 }
1684 } else {
1685 match self.quality {
1686 RenderQuality::Low => 96,
1687 RenderQuality::Medium => 160,
1688 RenderQuality::High => 220,
1689 }
1690 }
1691 }
1692 }
1693 }
1694}
1695
1696impl<'a, D, C> RenderCtx<'a, D, C>
1697where
1698 D: DrawTarget<Color = Rgb565> + PixelRead,
1699 C: Compositor<D>,
1700{
1701 fn pixel_blended(&mut self, x: i32, y: i32, color: Rgb565, alpha: u8) -> Result<(), D::Error> {
1705 let (x, y) = self.current_transform().apply(x, y);
1706 if !self.clip.contains(x, y) {
1707 return Ok(());
1708 }
1709 if let Some(dirty) = self.dirty {
1710 if !dirty.contains(x, y) {
1711 return Ok(());
1712 }
1713 }
1714 let layer = self.current_layer();
1715 let combined_alpha = ((alpha as u16 * layer.opacity as u16) / 255) as u8;
1716 if combined_alpha == 0 {
1717 return Ok(());
1718 }
1719 let backdrop = self.target.get_pixel(Point::new(x, y));
1720 let blended = lerp_rgb565(backdrop, color, combined_alpha);
1721 let blended = apply_blend_mode(blended, layer.blend, layer.backdrop);
1722 self.target.draw_iter([Pixel(Point::new(x, y), blended)])
1723 }
1724
1725 pub fn fill_rect_true_alpha(
1728 &mut self,
1729 rect: Rect,
1730 color: Rgb565,
1731 alpha: u8,
1732 ) -> Result<(), D::Error> {
1733 self.fill_rounded_rect_true_alpha(rect, 0, color, alpha)
1734 }
1735
1736 pub fn fill_rounded_rect_true_alpha(
1739 &mut self,
1740 rect: Rect,
1741 radius: u8,
1742 color: Rgb565,
1743 alpha: u8,
1744 ) -> Result<(), D::Error> {
1745 let draw = self.visible_rect(rect);
1746 if draw.is_empty() || alpha == 0 {
1747 return Ok(());
1748 }
1749 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
1750
1751 for y in draw.y..draw.bottom() {
1752 for x in draw.x..draw.right() {
1753 if !in_rounded_rect(x, y, rect, radius) {
1754 continue;
1755 }
1756 self.pixel_blended(x, y, color, alpha)?;
1757 }
1758 }
1759 Ok(())
1760 }
1761}
1762
1763fn should_draw_at_opacity(x: i32, y: i32, opacity: u8) -> bool {
1764 if opacity == 255 {
1765 return true;
1766 }
1767 if opacity == 0 {
1768 return false;
1769 }
1770 let bayer4 = [
1771 [0u8, 8, 2, 10],
1772 [12, 4, 14, 6],
1773 [3, 11, 1, 9],
1774 [15, 7, 13, 5],
1775 ];
1776 let threshold = ((opacity as u16 * 16) / 255) as u8;
1777 let sample = bayer4[(y as usize) & 3][(x as usize) & 3];
1778 sample < threshold.max(1)
1779}
1780
1781fn lerp_rgb565(a: Rgb565, b: Rgb565, t: u8) -> Rgb565 {
1782 let t = t as u16;
1783 let inv = 255u16.saturating_sub(t);
1784 let r = ((a.r() as u16 * inv) + (b.r() as u16 * t)) / 255;
1785 let g = ((a.g() as u16 * inv) + (b.g() as u16 * t)) / 255;
1786 let bb = ((a.b() as u16 * inv) + (b.b() as u16 * t)) / 255;
1787 Rgb565::new(r as u8, g as u8, bb as u8)
1788}
1789
1790#[inline]
1791fn normalize_angle_deg(mut deg: f32) -> f32 {
1792 while deg < 0.0 {
1793 deg += 360.0;
1794 }
1795 while deg >= 360.0 {
1796 deg -= 360.0;
1797 }
1798 deg
1799}
1800
1801#[inline]
1802fn ccw_distance_deg(from: f32, to: f32) -> f32 {
1803 let mut d = normalize_angle_deg(to) - normalize_angle_deg(from);
1804 if d < 0.0 {
1805 d += 360.0;
1806 }
1807 d
1808}
1809
1810fn apply_blend_mode(src: Rgb565, mode: BlendMode, backdrop: Rgb565) -> Rgb565 {
1811 match mode {
1812 BlendMode::Normal => src,
1813 BlendMode::Add => Rgb565::new(
1814 src.r().saturating_add(backdrop.r()),
1815 src.g().saturating_add(backdrop.g()),
1816 src.b().saturating_add(backdrop.b()),
1817 ),
1818 BlendMode::Multiply => Rgb565::new(
1819 ((src.r() as u16 * backdrop.r() as u16) / 31) as u8,
1820 ((src.g() as u16 * backdrop.g() as u16) / 63) as u8,
1821 ((src.b() as u16 * backdrop.b() as u16) / 31) as u8,
1822 ),
1823 BlendMode::Screen => Rgb565::new(
1824 (31 - ((31 - src.r() as u16) * (31 - backdrop.r() as u16) / 31)) as u8,
1825 (63 - ((63 - src.g() as u16) * (63 - backdrop.g() as u16) / 63)) as u8,
1826 (31 - ((31 - src.b() as u16) * (31 - backdrop.b() as u16) / 31)) as u8,
1827 ),
1828 }
1829}
1830
1831fn in_rounded_rect(x: i32, y: i32, rect: Rect, radius: u8) -> bool {
1832 if rect.is_empty() {
1833 return false;
1834 }
1835 let radius = radius as i32;
1836 if radius <= 0 {
1837 return rect.contains(x, y);
1838 }
1839
1840 let left = rect.x;
1841 let top = rect.y;
1842 let right = rect.right() - 1;
1843 let bottom = rect.bottom() - 1;
1844 let inner_left = left + radius;
1845 let inner_right = right - radius;
1846 let inner_top = top + radius;
1847 let inner_bottom = bottom - radius;
1848
1849 if (x >= inner_left && x <= inner_right) || (y >= inner_top && y <= inner_bottom) {
1850 return rect.contains(x, y);
1851 }
1852
1853 let (cx, cy) = if x < inner_left && y < inner_top {
1854 (inner_left, inner_top)
1855 } else if x > inner_right && y < inner_top {
1856 (inner_right, inner_top)
1857 } else if x < inner_left && y > inner_bottom {
1858 (inner_left, inner_bottom)
1859 } else if x > inner_right && y > inner_bottom {
1860 (inner_right, inner_bottom)
1861 } else {
1862 return rect.contains(x, y);
1863 };
1864
1865 let dx = x - cx;
1866 let dy = y - cy;
1867 dx * dx + dy * dy <= radius * radius
1868}
1869
1870fn line_len_at(text: &str, start: usize, max_chars: usize, wrap: TextWrap) -> (usize, bool) {
1871 let mut len = 0;
1872 let limit = match wrap {
1873 TextWrap::None => usize::MAX,
1874 TextWrap::Character => max_chars.max(1),
1875 TextWrap::Word => max_chars.max(1),
1876 };
1877 let mut last_ws_break = None;
1878
1879 for ch in text.chars().skip(start) {
1880 if ch == '\n' {
1881 return (len, true);
1882 }
1883 if matches!(wrap, TextWrap::Word) && ch.is_whitespace() {
1884 last_ws_break = Some(len + 1);
1885 }
1886 if len >= limit {
1887 if matches!(wrap, TextWrap::Word) {
1888 if let Some(idx) = last_ws_break {
1889 return (idx, false);
1890 }
1891 }
1892 return (len, false);
1893 }
1894 len += 1;
1895 }
1896
1897 (len, false)
1898}
1899
1900fn count_lines(text: &str, max_chars: usize, wrap: TextWrap) -> usize {
1901 if text.is_empty() {
1902 return 1;
1903 }
1904 let char_count = text.chars().count();
1905 let mut lines = 0;
1906 let mut start = 0;
1907 while start < char_count {
1908 let (len, consumed_newline) = line_len_at(text, start, max_chars, wrap);
1909 lines += 1;
1910 start += len + usize::from(consumed_newline);
1911 if len == 0 && !consumed_newline {
1912 break;
1913 }
1914 }
1915 lines
1916}
1917
1918fn widest_line(text: &str, max_chars: usize, wrap: TextWrap) -> usize {
1919 let char_count = text.chars().count();
1920 let mut widest = 0;
1921 let mut start = 0;
1922 while start < char_count {
1923 let (len, consumed_newline) = line_len_at(text, start, max_chars, wrap);
1924 widest = widest.max(len);
1925 start += len + usize::from(consumed_newline);
1926 if len == 0 && !consumed_newline {
1927 break;
1928 }
1929 }
1930 widest
1931}
1932
1933fn kerning_adjust(prev: Option<char>, next: char, enabled: bool) -> i32 {
1934 if !enabled {
1935 return 0;
1936 }
1937 match (prev, next) {
1938 (Some('A'), 'V') | (Some('A'), 'W') | (Some('T'), 'o') | (Some('L'), 'T') => -1,
1939 _ => 0,
1940 }
1941}