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