1use alloc::vec::Vec;
7
8use crate::cmd::CommandList;
9use crate::draw::{GradientDesc, ShadowDesc};
10use crate::font::ShapedText;
11use crate::image::{BlitOpts, ImageDescriptor, PixelFormat};
12use crate::invalidation::InvalidationList;
13use crate::mask::AlphaMask;
14use crate::raster::{self, CoverageSink, Obb};
15use crate::widget::{Color, Rect};
16
17pub trait Renderer {
22 fn fill_rect(&mut self, rect: Rect, color: Color);
24
25 fn draw_text(&mut self, position: (i32, i32), text: &str, color: Color);
27
28 fn draw_text_shaped(&mut self, shaped: &ShapedText<'_>, origin: (i32, i32), color: Color) {
39 for glyph in &shaped.glyphs {
40 let mut extent = glyph.extent();
41 extent.x += origin.0;
42 extent.y += origin.1;
43 if extent.width <= 0 || extent.height <= 0 {
44 continue;
45 }
46 if let Some(font) = shaped.font
47 && draw_glyph_coverage(self, font, glyph.ch, extent, color)
48 {
49 continue;
50 }
51 self.blend_rect(extent, color);
52 }
53 }
54
55 fn draw_glyph(
72 &mut self,
73 font: &dyn crate::font::FontMetrics,
74 ch: char,
75 origin: (i32, i32),
76 color: Color,
77 ) {
78 let Some(info) = font.glyph_metrics(ch) else {
79 return;
80 };
81 let extent = Rect {
82 x: origin.0 + info.bearing_x as i32,
83 y: origin.1 - info.bearing_y as i32,
84 width: info.width as i32,
85 height: info.height as i32,
86 };
87 if extent.width <= 0 || extent.height <= 0 {
88 return;
89 }
90 if draw_glyph_coverage(self, font, ch, extent, color) {
91 return;
92 }
93 self.blend_rect(extent, color);
94 }
95
96 fn blend_rect(&mut self, rect: Rect, color: Color) {
103 self.fill_rect(rect, color);
104 }
105
106 fn draw_pixels(&mut self, position: (i32, i32), pixels: &[Color], width: u32, height: u32) {
111 for y in 0..height as i32 {
112 for x in 0..width as i32 {
113 let idx = (y as u32 * width + x as u32) as usize;
114 if let Some(&c) = pixels.get(idx) {
115 self.fill_rect(
116 Rect {
117 x: position.0 + x,
118 y: position.1 + y,
119 width: 1,
120 height: 1,
121 },
122 c,
123 );
124 }
125 }
126 }
127 }
128
129 fn blit_image(&mut self, dest: Rect, descriptor: &ImageDescriptor<'_>, opts: &BlitOpts) {
139 if dest.width <= 0
140 || dest.height <= 0
141 || descriptor.width == 0
142 || descriptor.height == 0
143 || opts.scale_x == 0
144 || opts.scale_y == 0
145 {
146 return;
147 }
148
149 let output = Rect {
150 x: 0,
151 y: 0,
152 width: dest.width,
153 height: dest.height,
154 };
155 let visible = match opts.clip {
156 Some(clip) => output.intersect(clip),
157 None => Some(output),
158 };
159 let Some(visible) = visible else {
160 return;
161 };
162
163 let mut row = Vec::new();
164 row.resize(visible.width as usize, Color(0, 0, 0, 0));
165 for local_y in visible.y..visible.y + visible.height {
166 for local_x in visible.x..visible.x + visible.width {
167 let out_idx = (local_x - visible.x) as usize;
168 row[out_idx] = sample_image_pixel(descriptor, opts, local_x, local_y)
169 .unwrap_or(Color(0, 0, 0, 0));
170 }
171 self.draw_pixels(
172 (dest.x + visible.x, dest.y + local_y),
173 &row,
174 visible.width as u32,
175 1,
176 );
177 }
178 }
179
180 fn blend_row(&mut self, x: i32, y: i32, color: Color, coverage: &[u8]) {
191 for (i, &cov) in coverage.iter().enumerate() {
192 if cov == 0 {
193 continue;
194 }
195 let alpha = ((color.3 as u16 * cov as u16) / 255) as u8;
196 self.blend_rect(
197 Rect {
198 x: x + i as i32,
199 y,
200 width: 1,
201 height: 1,
202 },
203 Color(color.0, color.1, color.2, alpha),
204 );
205 }
206 }
207
208 fn fill_masked(&mut self, rect: Rect, color: Color, mask: &dyn AlphaMask) {
215 if rect.width <= 0 || rect.height <= 0 || color.3 == 0 {
216 return;
217 }
218 let mut coverage = [0u8; 64];
219 for y in rect.y..rect.y + rect.height {
220 let mut x = rect.x;
221 let end = rect.x + rect.width;
222 while x < end {
223 let run = (end - x).min(coverage.len() as i32) as usize;
224 let row = &mut coverage[..run];
225 mask.row(x, y, row);
226 self.blend_row(x, y, color, row);
227 x += run as i32;
228 }
229 }
230 }
231
232 fn fill_gradient(&mut self, rect: Rect, gradient: &GradientDesc<'_>) {
238 if rect.width <= 0 || rect.height <= 0 {
239 return;
240 }
241 for y in rect.y..rect.y + rect.height {
242 for x in rect.x..rect.x + rect.width {
243 let Some(color) = gradient.color_at(rect, x, y) else {
244 continue;
245 };
246 if color.3 == 0 {
247 continue;
248 }
249 let pixel = Rect {
250 x,
251 y,
252 width: 1,
253 height: 1,
254 };
255 if color.3 == 255 {
256 self.fill_rect(pixel, color);
257 } else {
258 self.blend_rect(pixel, color);
259 }
260 }
261 }
262 }
263
264 fn draw_shadow(&mut self, rect: Rect, radius: u8, shadow: &ShadowDesc) {
271 if rect.width <= 0 || rect.height <= 0 || shadow.color.3 == 0 {
272 return;
273 }
274 let spread = shadow.spread as i32;
275 let blur = shadow.blur as i32;
276 let base = Rect {
277 x: rect.x + shadow.offset_x as i32 - spread,
278 y: rect.y + shadow.offset_y as i32 - spread,
279 width: rect.width + spread * 2,
280 height: rect.height + spread * 2,
281 };
282 if base.width <= 0 || base.height <= 0 {
283 return;
284 }
285 let draw_rect = Rect {
286 x: base.x - blur,
287 y: base.y - blur,
288 width: base.width + blur * 2,
289 height: base.height + blur * 2,
290 };
291 let mask = ShadowMask {
292 base,
293 blur,
294 radius: radius as i32 + spread,
295 };
296 self.fill_masked(draw_rect, shadow.color, &mask);
297 }
298
299 fn fill_obb_aa(&mut self, obb: Obb, color: Color) {
313 let clip = obb.aabb();
314 let mut sink = RowBlendSink { r: self, color };
315 raster::rasterize_obb(&obb, clip, &mut sink);
316 }
317
318 fn fill_disc_aa(&mut self, center: crate::raster::PointF, radius: f32, color: Color) {
327 let pad = radius + 1.0;
328 let clip = Rect {
329 x: (center.x - pad) as i32 - 1,
330 y: (center.y - pad) as i32 - 1,
331 width: (pad * 2.0) as i32 + 3,
332 height: (pad * 2.0) as i32 + 3,
333 };
334 let mut sink = RowBlendSink { r: self, color };
335 raster::rasterize_disc(center, radius, clip, &mut sink);
336 }
337
338 fn stroke_line_aa(
345 &mut self,
346 a: crate::raster::PointF,
347 b: crate::raster::PointF,
348 width: f32,
349 color: Color,
350 ) {
351 let clip = Rect {
355 x: i32::MIN / 2,
356 y: i32::MIN / 2,
357 width: i32::MAX / 2,
358 height: i32::MAX / 2,
359 };
360 let mut sink = RowBlendSink { r: self, color };
361 raster::rasterize_line(a, b, width, clip, &mut sink);
362 }
363
364 #[allow(clippy::too_many_arguments)]
374 fn fill_arc_aa(
375 &mut self,
376 center: crate::raster::PointF,
377 r_outer: f32,
378 r_inner: f32,
379 start_cos: f32,
380 start_sin: f32,
381 end_cos: f32,
382 end_sin: f32,
383 extent: f32,
384 color: Color,
385 ) {
386 let pad = r_outer + 1.0;
387 let clip = Rect {
388 x: (center.x - pad) as i32 - 1,
389 y: (center.y - pad) as i32 - 1,
390 width: (pad * 2.0) as i32 + 3,
391 height: (pad * 2.0) as i32 + 3,
392 };
393 let mut sink = RowBlendSink { r: self, color };
394 raster::rasterize_arc(
395 center, r_outer, r_inner, start_cos, start_sin, end_cos, end_sin, extent, clip,
396 &mut sink,
397 );
398 }
399
400 fn submit(&mut self, list: &CommandList) {
415 list.replay(self);
416 }
417}
418
419struct RowBlendSink<'r, R: Renderer + ?Sized> {
420 r: &'r mut R,
421 color: Color,
422}
423
424impl<R: Renderer + ?Sized> CoverageSink for RowBlendSink<'_, R> {
425 fn row(&mut self, x: i32, y: i32, coverage: &[u8]) {
426 self.r.blend_row(x, y, self.color, coverage);
427 }
428}
429
430pub struct DirtyTrackingRenderer<'a, 'b, R: Renderer + ?Sized, const N: usize> {
433 inner: &'a mut R,
435 invalidation: &'b mut InvalidationList<N>,
437 clip: Option<Rect>,
439}
440
441impl<'a, 'b, R: Renderer + ?Sized, const N: usize> DirtyTrackingRenderer<'a, 'b, R, N> {
442 pub fn new(inner: &'a mut R, invalidation: &'b mut InvalidationList<N>) -> Self {
447 Self {
448 inner,
449 invalidation,
450 clip: None,
451 }
452 }
453
454 pub fn with_clip(
457 inner: &'a mut R,
458 invalidation: &'b mut InvalidationList<N>,
459 clip: Rect,
460 ) -> Self {
461 Self {
462 inner,
463 invalidation,
464 clip: (clip.width > 0 && clip.height > 0).then_some(clip),
465 }
466 }
467
468 pub fn inner(&self) -> &R {
470 self.inner
471 }
472
473 pub fn inner_mut(&mut self) -> &mut R {
475 self.inner
476 }
477
478 fn push_rect(&mut self, rect: Rect) {
479 if rect.width <= 0 || rect.height <= 0 {
480 return;
481 }
482 if let Some(clip) = self.clip {
483 if let Some(visible) = rect.intersect(clip) {
484 self.invalidation.push(visible);
485 }
486 } else {
487 self.invalidation.push(rect);
488 }
489 }
490
491 fn push_blend_row(&mut self, x: i32, y: i32, coverage: &[u8]) {
492 let Some(start) = coverage.iter().position(|c| *c != 0) else {
493 return;
494 };
495 let Some(end) = coverage.iter().rposition(|c| *c != 0) else {
496 return;
497 };
498
499 let width = i32::try_from(end - start + 1).unwrap_or(i32::MAX);
500 let rect = Rect {
501 x: x + i32::try_from(start).unwrap_or(i32::MAX),
502 y,
503 width,
504 height: 1,
505 };
506 self.push_rect(rect);
507 }
508
509 fn push_text_estimate(&mut self, position: (i32, i32), text: &str) {
510 if text.is_empty() {
511 return;
512 }
513 let glyph_count = text.len().min((i32::MAX as usize) / 8);
514 let width = (glyph_count as u32).saturating_mul(8) as i32;
515 self.push_rect(Rect {
516 x: position.0,
517 y: position.1 - TEXT_NOMINAL_LINE_PX,
518 width,
519 height: TEXT_NOMINAL_LINE_PX,
520 });
521 }
522}
523
524impl<R: Renderer + ?Sized, const N: usize> Renderer for DirtyTrackingRenderer<'_, '_, R, N> {
525 fn fill_rect(&mut self, rect: Rect, color: Color) {
526 self.push_rect(rect);
527 self.inner.fill_rect(rect, color);
528 }
529
530 fn draw_text(&mut self, position: (i32, i32), text: &str, color: Color) {
531 self.push_text_estimate(position, text);
532 self.inner.draw_text(position, text, color);
533 }
534
535 fn draw_text_shaped(&mut self, shaped: &ShapedText<'_>, origin: (i32, i32), color: Color) {
536 let bounds = Rect {
537 x: shaped.bounds.x + origin.0,
538 y: shaped.bounds.y + origin.1,
539 width: shaped.bounds.width,
540 height: shaped.bounds.height,
541 };
542 self.push_rect(bounds);
543 self.inner.draw_text_shaped(shaped, origin, color);
544 }
545
546 fn blend_rect(&mut self, rect: Rect, color: Color) {
547 self.push_rect(rect);
548 self.inner.blend_rect(rect, color);
549 }
550
551 fn draw_pixels(&mut self, position: (i32, i32), pixels: &[Color], width: u32, height: u32) {
552 let width_i32 = i32::try_from(width).unwrap_or(i32::MAX);
553 let height_i32 = i32::try_from(height).unwrap_or(i32::MAX);
554 self.push_rect(Rect {
555 x: position.0,
556 y: position.1,
557 width: width_i32,
558 height: height_i32,
559 });
560 self.inner.draw_pixels(position, pixels, width, height);
561 }
562
563 fn blit_image(
564 &mut self,
565 dest: Rect,
566 descriptor: &crate::image::ImageDescriptor<'_>,
567 opts: &crate::image::BlitOpts,
568 ) {
569 if dest.width <= 0 || dest.height <= 0 {
570 return;
571 }
572 let output = Rect {
573 x: 0,
574 y: 0,
575 width: dest.width,
576 height: dest.height,
577 };
578 let visible = match opts.clip {
579 Some(clip) => output.intersect(clip),
580 None => Some(output),
581 };
582 let Some(visible) = visible else {
583 return;
584 };
585 self.push_rect(Rect {
586 x: dest.x + visible.x,
587 y: dest.y + visible.y,
588 width: visible.width,
589 height: visible.height,
590 });
591 self.inner.blit_image(dest, descriptor, opts);
592 }
593
594 fn blend_row(&mut self, x: i32, y: i32, color: Color, coverage: &[u8]) {
595 self.push_blend_row(x, y, coverage);
596 self.inner.blend_row(x, y, color, coverage);
597 }
598
599 fn fill_masked(&mut self, rect: Rect, color: Color, mask: &dyn crate::mask::AlphaMask) {
600 self.push_rect(rect);
601 self.inner.fill_masked(rect, color, mask);
602 }
603
604 fn fill_gradient(&mut self, rect: Rect, gradient: &crate::draw::GradientDesc<'_>) {
605 self.push_rect(rect);
606 self.inner.fill_gradient(rect, gradient);
607 }
608
609 fn draw_shadow(&mut self, rect: Rect, radius: u8, shadow: &crate::draw::ShadowDesc) {
610 let spread = shadow.spread as i32;
611 let blur = shadow.blur as i32;
612 let base = Rect {
613 x: rect.x + shadow.offset_x as i32 - spread,
614 y: rect.y + shadow.offset_y as i32 - spread,
615 width: rect.width + spread * 2,
616 height: rect.height + spread * 2,
617 };
618 let draw_rect = Rect {
619 x: base.x - blur,
620 y: base.y - blur,
621 width: base.width + blur * 2,
622 height: base.height + blur * 2,
623 };
624 self.push_rect(draw_rect);
625 self.inner.draw_shadow(rect, radius, shadow);
626 }
627
628 fn fill_obb_aa(&mut self, obb: crate::raster::Obb, color: Color) {
629 self.push_rect(obb.aabb());
630 self.inner.fill_obb_aa(obb, color);
631 }
632
633 fn fill_disc_aa(&mut self, center: crate::raster::PointF, radius: f32, color: Color) {
634 let pad = radius + 1.0;
635 self.push_rect(Rect {
636 x: (center.x - pad) as i32 - 1,
637 y: (center.y - pad) as i32 - 1,
638 width: (pad * 2.0) as i32 + 3,
639 height: (pad * 2.0) as i32 + 3,
640 });
641 self.inner.fill_disc_aa(center, radius, color);
642 }
643
644 fn stroke_line_aa(
645 &mut self,
646 a: crate::raster::PointF,
647 b: crate::raster::PointF,
648 width: f32,
649 color: Color,
650 ) {
651 let pad = width * 0.5 + 1.0;
652 self.push_rect(Rect {
653 x: (a.x.min(b.x) - pad) as i32 - 1,
654 y: (a.y.min(b.y) - pad) as i32 - 1,
655 width: ((a.x.max(b.x) + pad) as i32 - (a.x.min(b.x) - pad) as i32) + 2,
656 height: ((a.y.max(b.y) + pad) as i32 - (a.y.min(b.y) - pad) as i32) + 2,
657 });
658 self.inner.stroke_line_aa(a, b, width, color);
659 }
660
661 fn fill_arc_aa(
662 &mut self,
663 center: crate::raster::PointF,
664 r_outer: f32,
665 r_inner: f32,
666 start_cos: f32,
667 start_sin: f32,
668 end_cos: f32,
669 end_sin: f32,
670 extent: f32,
671 color: Color,
672 ) {
673 let pad = r_outer + 1.0;
674 self.push_rect(Rect {
675 x: (center.x - pad) as i32 - 1,
676 y: (center.y - pad) as i32 - 1,
677 width: (pad * 2.0) as i32 + 3,
678 height: (pad * 2.0) as i32 + 3,
679 });
680 self.inner.fill_arc_aa(
681 center, r_outer, r_inner, start_cos, start_sin, end_cos, end_sin, extent, color,
682 );
683 }
684
685 fn submit(&mut self, list: &crate::cmd::CommandList) {
686 if let Some(dirty) = list.dirty_union() {
687 self.push_rect(dirty);
688 }
689 self.inner.submit(list);
690 }
691}
692
693fn draw_glyph_coverage<R: Renderer + ?Sized>(
694 renderer: &mut R,
695 font: &dyn crate::font::FontMetrics,
696 ch: char,
697 extent: Rect,
698 color: Color,
699) -> bool {
700 let mut coverage = [0u8; 64];
701 let height = extent.height.min(u16::MAX as i32) as u16;
702 let width = extent.width.min(u16::MAX as i32) as u16;
703 for row in 0..height {
704 let mut x_offset = 0u16;
705 while x_offset < width {
706 let run = (width - x_offset).min(coverage.len() as u16) as usize;
707 let row_coverage = &mut coverage[..run];
708 if !font.glyph_coverage_row(ch, row, x_offset, row_coverage) {
709 return false;
710 }
711 renderer.blend_row(
712 extent.x + i32::from(x_offset),
713 extent.y + i32::from(row),
714 color,
715 row_coverage,
716 );
717 x_offset += run as u16;
718 }
719 }
720 true
721}
722
723fn sample_image_pixel(
724 descriptor: &ImageDescriptor<'_>,
725 opts: &BlitOpts,
726 local_x: i32,
727 local_y: i32,
728) -> Option<Color> {
729 let (pre_x, pre_y) = inverse_cardinal_transform(local_x, local_y, opts);
730 if pre_x < 0 || pre_y < 0 {
731 return None;
732 }
733
734 let src_x = (i64::from(pre_x) * 256 / i64::from(opts.scale_x)) as u32;
735 let src_y = (i64::from(pre_y) * 256 / i64::from(opts.scale_y)) as u32;
736 if src_x >= u32::from(descriptor.width) || src_y >= u32::from(descriptor.height) {
737 return None;
738 }
739
740 decode_image_pixel(descriptor, src_x as usize, src_y as usize)
741 .map(|color| recolor_image_pixel(color, opts.recolor, opts.recolor_alpha))
742}
743
744fn inverse_cardinal_transform(local_x: i32, local_y: i32, opts: &BlitOpts) -> (i32, i32) {
745 let pivot_x = i32::from(opts.pivot.0);
746 let pivot_y = i32::from(opts.pivot.1);
747 let x = local_x - pivot_x;
748 let y = local_y - pivot_y;
749 let (x, y) = match nearest_cardinal_rotation(opts.rotation_deg) {
750 0 => (x, y),
751 90 => (y, -x),
752 180 => (-x, -y),
753 _ => (-y, x),
754 };
755 (x + pivot_x, y + pivot_y)
756}
757
758fn nearest_cardinal_rotation(degrees: i16) -> i16 {
759 let degrees = i32::from(degrees).rem_euclid(360);
760 (((degrees + 45) / 90 * 90) % 360) as i16
761}
762
763fn decode_image_pixel(descriptor: &ImageDescriptor<'_>, x: usize, y: usize) -> Option<Color> {
764 if let Some(colors) = descriptor.data.as_color_slice() {
765 return colors
766 .get(y.checked_mul(descriptor.width as usize)?.checked_add(x)?)
767 .copied();
768 }
769
770 let bytes = descriptor.data.as_bytes()?;
771 let stride = descriptor.stride_bytes() as usize;
772 let offset = y
773 .checked_mul(stride)?
774 .checked_add(x.checked_mul(descriptor.format.bytes_per_pixel() as usize)?)?;
775 match descriptor.format {
776 PixelFormat::Rgb565 => {
777 let raw = u16::from_le_bytes([*bytes.get(offset)?, *bytes.get(offset + 1)?]);
778 let r = (((raw >> 11) & 0x1f) as u32 * 255 / 31) as u8;
779 let g = (((raw >> 5) & 0x3f) as u32 * 255 / 63) as u8;
780 let b = ((raw & 0x1f) as u32 * 255 / 31) as u8;
781 Some(Color(r, g, b, 255))
782 }
783 PixelFormat::Argb8888 => {
784 let raw = u32::from_le_bytes([
785 *bytes.get(offset)?,
786 *bytes.get(offset + 1)?,
787 *bytes.get(offset + 2)?,
788 *bytes.get(offset + 3)?,
789 ]);
790 Some(Color(
791 ((raw >> 16) & 0xff) as u8,
792 ((raw >> 8) & 0xff) as u8,
793 (raw & 0xff) as u8,
794 ((raw >> 24) & 0xff) as u8,
795 ))
796 }
797 PixelFormat::L8 => bytes.get(offset).map(|&luma| Color(luma, luma, luma, 255)),
798 }
799}
800
801fn recolor_image_pixel(color: Color, recolor: Option<Color>, alpha: u8) -> Color {
802 let Some(tint) = recolor else {
803 return color;
804 };
805 if alpha == 0 {
806 return color;
807 }
808 Color(
809 lerp_channel(color.0, tint.0, alpha),
810 lerp_channel(color.1, tint.1, alpha),
811 lerp_channel(color.2, tint.2, alpha),
812 color.3,
813 )
814}
815
816fn lerp_channel(from: u8, to: u8, alpha: u8) -> u8 {
817 let value = i32::from(from) + (i32::from(to) - i32::from(from)) * i32::from(alpha) / 255;
818 value.clamp(0, 255) as u8
819}
820
821struct ShadowMask {
822 base: Rect,
823 blur: i32,
824 radius: i32,
825}
826
827impl AlphaMask for ShadowMask {
828 fn row(&self, x: i32, y: i32, coverage: &mut [u8]) {
829 for (offset, alpha) in coverage.iter_mut().enumerate() {
830 let px = x.saturating_add(i32::try_from(offset).unwrap_or(i32::MAX));
831 *alpha = self.alpha(px, y);
832 }
833 }
834}
835
836impl ShadowMask {
837 fn alpha(&self, x: i32, y: i32) -> u8 {
838 let x0 = self.base.x;
839 let y0 = self.base.y;
840 let x1 = self.base.x + self.base.width - 1;
841 let y1 = self.base.y + self.base.height - 1;
842 let outside_dx = if x < x0 {
843 x0 - x
844 } else if x > x1 {
845 x - x1
846 } else {
847 0
848 };
849 let outside_dy = if y < y0 {
850 y0 - y
851 } else if y > y1 {
852 y - y1
853 } else {
854 0
855 };
856 let dist = outside_dx.max(outside_dy);
857 if dist > self.blur {
858 return 0;
859 }
860
861 let rect_alpha = if self.blur == 0 {
862 255
863 } else {
864 (((self.blur + 1 - dist) * 255) / (self.blur + 1)) as u8
865 };
866 rect_alpha.min(self.rounded_corner_alpha(x, y))
867 }
868
869 fn rounded_corner_alpha(&self, x: i32, y: i32) -> u8 {
870 let r = self
871 .radius
872 .max(0)
873 .min(self.base.width / 2)
874 .min(self.base.height / 2);
875 if r <= 0 {
876 return 255;
877 }
878
879 let cx = if x < self.base.x + r {
880 self.base.x + r
881 } else if x >= self.base.x + self.base.width - r {
882 self.base.x + self.base.width - r - 1
883 } else {
884 return 255;
885 };
886 let cy = if y < self.base.y + r {
887 self.base.y + r
888 } else if y >= self.base.y + self.base.height - r {
889 self.base.y + self.base.height - r - 1
890 } else {
891 return 255;
892 };
893 let dx = (x - cx).unsigned_abs();
894 let dy = (y - cy).unsigned_abs();
895 let dist_sq = dx.saturating_mul(dx).saturating_add(dy.saturating_mul(dy));
896 let r_sq = (r as u32).saturating_mul(r as u32);
897 if dist_sq <= r_sq { 255 } else { 0 }
898 }
899}
900
901pub const TEXT_NOMINAL_LINE_PX: i32 = 16;
909
910pub struct ClipRenderer<'a> {
959 inner: &'a mut dyn Renderer,
960 dx: i32,
962 dy: i32,
963 clip: Option<Rect>,
965}
966
967impl<'a> ClipRenderer<'a> {
968 pub fn new(inner: &'a mut dyn Renderer, clip: Rect) -> Self {
971 Self::with_offset(inner, clip, 0, 0)
972 }
973
974 pub fn with_offset(inner: &'a mut dyn Renderer, clip: Rect, dx: i32, dy: i32) -> Self {
977 let clip = (clip.width > 0 && clip.height > 0).then_some(clip);
978 Self {
979 inner,
980 dx,
981 dy,
982 clip,
983 }
984 }
985
986 pub fn clip(&self) -> Option<Rect> {
988 self.clip
989 }
990}
991
992impl Renderer for ClipRenderer<'_> {
993 fn fill_rect(&mut self, rect: Rect, color: Color) {
994 let Some(clip) = self.clip else { return };
995 let moved = Rect {
996 x: rect.x + self.dx,
997 y: rect.y + self.dy,
998 ..rect
999 };
1000 if let Some(visible) = moved.intersect(clip) {
1001 self.inner.fill_rect(visible, color);
1002 }
1003 }
1004
1005 fn blend_rect(&mut self, rect: Rect, color: Color) {
1006 let Some(clip) = self.clip else { return };
1007 let moved = Rect {
1008 x: rect.x + self.dx,
1009 y: rect.y + self.dy,
1010 ..rect
1011 };
1012 if let Some(visible) = moved.intersect(clip) {
1013 self.inner.blend_rect(visible, color);
1014 }
1015 }
1016
1017 fn draw_text(&mut self, position: (i32, i32), text: &str, color: Color) {
1018 let Some(clip) = self.clip else { return };
1019 let (x, y) = (position.0 + self.dx, position.1 + self.dy);
1020 let line_top = y - TEXT_NOMINAL_LINE_PX;
1024 let inside_v = line_top >= clip.y && y <= clip.y + clip.height;
1025 let inside_h = x >= clip.x && x < clip.x + clip.width;
1026 if inside_v && inside_h {
1027 self.inner.draw_text((x, y), text, color);
1028 }
1029 }
1030
1031 fn draw_pixels(&mut self, position: (i32, i32), pixels: &[Color], width: u32, height: u32) {
1032 let Some(clip) = self.clip else { return };
1033 if width == 0 || height == 0 {
1034 return;
1035 }
1036 let dest = Rect {
1037 x: position.0 + self.dx,
1038 y: position.1 + self.dy,
1039 width: width as i32,
1040 height: height as i32,
1041 };
1042 let Some(visible) = dest.intersect(clip) else {
1043 return;
1044 };
1045 if visible == dest {
1047 self.inner
1048 .draw_pixels((dest.x, dest.y), pixels, width, height);
1049 return;
1050 }
1051 let col0 = (visible.x - dest.x) as u32;
1053 let run = visible.width as u32;
1054 for row in 0..visible.height {
1055 let src_row = (visible.y - dest.y + row) as u32;
1056 let start = (src_row * width + col0) as usize;
1057 let Some(slice) = pixels.get(start..start + run as usize) else {
1058 return;
1059 };
1060 self.inner
1061 .draw_pixels((visible.x, visible.y + row), slice, run, 1);
1062 }
1063 }
1064
1065 fn blend_row(&mut self, x: i32, y: i32, color: Color, coverage: &[u8]) {
1066 let Some(clip) = self.clip else { return };
1067 let (x, y) = (x + self.dx, y + self.dy);
1068 if y < clip.y || y >= clip.y + clip.height || coverage.is_empty() {
1069 return;
1070 }
1071 let row = Rect {
1072 x,
1073 y,
1074 width: coverage.len() as i32,
1075 height: 1,
1076 };
1077 let Some(visible) = row.intersect(clip) else {
1078 return;
1079 };
1080 let start = (visible.x - x) as usize;
1081 self.inner.blend_row(
1082 visible.x,
1083 y,
1084 color,
1085 &coverage[start..start + visible.width as usize],
1086 );
1087 }
1088
1089 }