1#[cfg(any(
7 feature = "canvas",
8 feature = "gif",
9 feature = "apng",
10 feature = "nes",
11 feature = "png",
12 feature = "jpeg",
13 feature = "qrcode",
14 feature = "lottie",
15 feature = "fontdue",
16 test,
17))]
18use alloc::vec::Vec;
19#[cfg(feature = "fontdue")]
20use alloc::{collections::BTreeMap, vec};
21use bitflags::bitflags;
22use heapless::Vec as HVec;
23use rlvgl_core::cmd::{BlendMode, Cmd, CommandList};
24use rlvgl_core::font::{FontMetrics, ShapedText};
25#[cfg(feature = "fontdue")]
26use rlvgl_core::fontdue::{Metrics, line_metrics, rasterize_glyph};
27use rlvgl_core::renderer::Renderer;
28use rlvgl_core::widget::{Color, Rect as WidgetRect};
29
30#[cfg(feature = "fontdue")]
31const FONT_DATA: &[u8] = include_bytes!("../assets/fonts/DejaVuSans.ttf");
32
33#[cfg(feature = "fontdue")]
34fn round_to_i32(value: f32) -> i32 {
35 if value.is_nan() {
36 0
37 } else if value >= 0.0 {
38 (value + 0.5) as i32
39 } else {
40 (value - 0.5) as i32
41 }
42}
43
44#[cfg(feature = "fontdue")]
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
46struct GlyphKey {
48 font: *const u8,
50 size: u32,
52 ch: char,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum PixelFmt {
59 Argb8888,
61 Rgb565,
63 L8,
65 A8,
67 A4,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct Rect {
74 pub x: i32,
76 pub y: i32,
78 pub w: u32,
80 pub h: u32,
82}
83
84pub struct Surface<'a> {
86 pub buf: &'a mut [u8],
88 pub stride: usize,
90 pub format: PixelFmt,
92 pub width: u32,
94 pub height: u32,
96}
97
98impl<'a> Surface<'a> {
99 pub fn new(
101 buf: &'a mut [u8],
102 stride: usize,
103 format: PixelFmt,
104 width: u32,
105 height: u32,
106 ) -> Self {
107 Self {
108 buf,
109 stride,
110 format,
111 width,
112 height,
113 }
114 }
115}
116
117bitflags! {
118 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
120 pub struct BlitCaps: u32 {
121 const FILL = 0b0001;
123 const BLIT = 0b0010;
125 const BLEND = 0b0100;
127 const PFC = 0b1000;
129 }
130}
131
132pub trait Blitter {
134 fn caps(&self) -> BlitCaps;
136
137 fn fill(&mut self, dst: &mut Surface, area: Rect, color: u32);
139
140 fn blit(&mut self, src: &Surface, src_area: Rect, dst: &mut Surface, dst_pos: (i32, i32));
142
143 fn blend(&mut self, src: &Surface, src_area: Rect, dst: &mut Surface, dst_pos: (i32, i32));
145}
146
147pub struct BlitPlanner<const N: usize> {
158 rects: HVec<Rect, N>,
159 overflowed: bool,
160}
161
162impl<const N: usize> BlitPlanner<N> {
163 pub fn new() -> Self {
165 Self {
166 rects: HVec::new(),
167 overflowed: false,
168 }
169 }
170
171 pub fn add(&mut self, rect: Rect) {
173 if self.rects.push(rect).is_err() {
174 self.overflowed = true;
175 }
176 }
177
178 pub fn rects(&self) -> &[Rect] {
180 &self.rects
181 }
182
183 pub fn overflowed(&self) -> bool {
185 self.overflowed
186 }
187
188 pub fn clear(&mut self) {
190 self.rects.clear();
191 self.overflowed = false;
192 }
193}
194
195impl<const N: usize> Default for BlitPlanner<N> {
196 fn default() -> Self {
197 Self::new()
198 }
199}
200
201pub struct BlitterRenderer<'a, B: Blitter, const N: usize> {
207 blitter: &'a mut B,
208 surface: Surface<'a>,
209 planner: BlitPlanner<N>,
210 #[cfg(any(
211 feature = "canvas",
212 feature = "gif",
213 feature = "apng",
214 feature = "nes",
215 all(feature = "png", not(target_os = "none")),
216 all(feature = "jpeg", not(target_os = "none")),
217 all(feature = "qrcode", not(target_os = "none")),
218 feature = "lottie",
219 test,
220 ))]
221 scratch: Option<Vec<u8>>,
222 #[cfg(feature = "fontdue")]
223 glyph_cache: BTreeMap<GlyphKey, (Metrics, Vec<u8>)>,
224}
225
226impl<'a, B: Blitter, const N: usize> BlitterRenderer<'a, B, N> {
227 pub fn new(blitter: &'a mut B, surface: Surface<'a>) -> Self {
229 Self {
230 blitter,
231 surface,
232 planner: BlitPlanner::new(),
233 #[cfg(any(
234 feature = "canvas",
235 feature = "gif",
236 feature = "apng",
237 feature = "nes",
238 all(feature = "png", not(target_os = "none")),
239 all(feature = "jpeg", not(target_os = "none")),
240 all(feature = "qrcode", not(target_os = "none")),
241 feature = "lottie",
242 test,
243 ))]
244 scratch: None,
245 #[cfg(feature = "fontdue")]
246 glyph_cache: BTreeMap::new(),
247 }
248 }
249
250 pub fn planner(&mut self) -> &mut BlitPlanner<N> {
252 &mut self.planner
253 }
254
255 #[cfg(any(
256 feature = "canvas",
257 feature = "gif",
258 feature = "apng",
259 feature = "nes",
260 all(feature = "png", not(target_os = "none")),
261 all(feature = "jpeg", not(target_os = "none")),
262 all(feature = "qrcode", not(target_os = "none")),
263 feature = "lottie",
264 test,
265 ))]
266 fn blit_colors(&mut self, position: (i32, i32), pixels: &[Color], w: u32, h: u32) {
267 let required = (w * h * 4) as usize;
268 let buf = self.scratch.get_or_insert_with(Vec::new);
269 if buf.len() < required {
270 buf.resize(required, 0);
271 }
272 for (i, c) in pixels.iter().enumerate() {
273 buf[i * 4..i * 4 + 4].copy_from_slice(&c.to_argb8888().to_le_bytes());
274 }
275 let src = Surface::new(
276 &mut buf[..required],
277 (w * 4) as usize,
278 PixelFmt::Argb8888,
279 w,
280 h,
281 );
282 self.blitter
283 .blit(&src, Rect { x: 0, y: 0, w, h }, &mut self.surface, position);
284 self.planner.add(Rect {
285 x: position.0,
286 y: position.1,
287 w,
288 h,
289 });
290 }
291
292 #[cfg(all(feature = "png", not(target_os = "none")))]
293 pub fn draw_png(
295 &mut self,
296 position: (i32, i32),
297 data: &[u8],
298 ) -> Result<(), rlvgl_core::png::DecodingError> {
299 let (pixels, w, h) = rlvgl_core::png::decode(data)?;
300 self.blit_colors(position, &pixels, w, h);
301 Ok(())
302 }
303
304 #[cfg(all(feature = "jpeg", not(target_os = "none")))]
305 pub fn draw_jpeg(
307 &mut self,
308 position: (i32, i32),
309 data: &[u8],
310 ) -> Result<(), rlvgl_core::jpeg::Error> {
311 let (pixels, w, h) = rlvgl_core::jpeg::decode(data)?;
312 self.blit_colors(position, &pixels, w as u32, h as u32);
313 Ok(())
314 }
315
316 #[cfg(all(feature = "qrcode", not(target_os = "none")))]
317 pub fn draw_qr(
319 &mut self,
320 position: (i32, i32),
321 data: &[u8],
322 ) -> Result<(), rlvgl_core::qrcode::QrError> {
323 let (pixels, w, h) = rlvgl_core::qrcode::generate(data)?;
324 self.blit_colors(position, &pixels, w, h);
325 Ok(())
326 }
327
328 #[cfg(feature = "lottie")]
329 pub fn draw_lottie_frame(
333 &mut self,
334 position: (i32, i32),
335 json: &str,
336 frame: usize,
337 width: u32,
338 height: u32,
339 ) -> Result<(), rlvgl_core::lottie::Error> {
340 let pixels =
341 rlvgl_core::lottie::render_lottie_frame(json, frame, width as usize, height as usize)?;
342 self.blit_colors(position, &pixels, width, height);
343 Ok(())
344 }
345
346 #[cfg(feature = "canvas")]
347 pub fn draw_canvas(&mut self, position: (i32, i32), canvas: &rlvgl_core::canvas::Canvas) {
349 let (w, h) = canvas.size();
350 let pixels = canvas.pixels();
351 self.blit_colors(position, &pixels, w, h);
352 }
353
354 #[cfg(feature = "gif")]
355 pub fn draw_gif_frame(
357 &mut self,
358 position: (i32, i32),
359 data: &[u8],
360 frame: usize,
361 ) -> Result<(), rlvgl_core::gif::DecodingError> {
362 let (frames, w, h) = rlvgl_core::gif::decode(data)?;
363 if let Some(f) = frames.get(frame) {
364 self.blit_colors(position, &f.pixels, w as u32, h as u32);
365 }
366 Ok(())
367 }
368
369 #[cfg(feature = "apng")]
370 pub fn draw_apng_frame(
372 &mut self,
373 position: (i32, i32),
374 data: &[u8],
375 frame: usize,
376 ) -> Result<(), image::ImageError> {
377 let (frames, w, h) = rlvgl_core::apng::decode(data)?;
378 if let Some(f) = frames.get(frame) {
379 self.blit_colors(position, &f.pixels, w, h);
380 }
381 Ok(())
382 }
383
384 #[cfg(all(feature = "pinyin", feature = "fontdue"))]
385 pub fn draw_pinyin_candidates(
389 &mut self,
390 position: (i32, i32),
391 ime: &rlvgl_core::pinyin::PinyinInputMethod,
392 input: &str,
393 color: Color,
394 ) -> bool {
395 if let Some(chars) = ime.candidates(input) {
396 let max_w = self.surface.width as i32 - position.0;
398 let max_h = self.surface.height as i32 - position.1;
399 if max_w <= 0 || max_h < 16 {
400 return false;
401 }
402
403 let text: alloc::string::String = chars.into_iter().collect();
405 let max_chars = (max_w / 16) as usize;
406 let clipped: alloc::string::String = text.chars().take(max_chars).collect();
407 if clipped.is_empty() {
408 return false;
409 }
410 Renderer::draw_text(self, position, &clipped, color);
411 true
412 } else {
413 false
414 }
415 }
416
417 #[cfg(all(feature = "fatfs", feature = "fontdue"))]
418 pub fn draw_fatfs_dir<T>(
420 &mut self,
421 position: (i32, i32),
422 image: &mut T,
423 dir: &str,
424 color: Color,
425 ) -> Result<(), std::io::Error>
426 where
427 T: std::io::Read + std::io::Write + std::io::Seek,
428 {
429 let max_w = self.surface.width as i32 - position.0;
430 let max_h = self.surface.height as i32 - position.1;
431 if max_w <= 0 || max_h <= 0 {
432 return Ok(());
433 }
434 let line_h = 16;
435 let max_lines = (max_h / line_h) as usize;
436 let max_chars = (max_w / 16) as usize;
437 let names = rlvgl_core::fatfs::list_dir(image, dir)?;
438 for (i, name) in names.iter().take(max_lines).enumerate() {
439 let y = position.1 + (i as i32) * line_h;
440 let clipped: alloc::string::String = name.chars().take(max_chars).collect();
441 if clipped.is_empty() {
442 break;
443 }
444 Renderer::draw_text(self, (position.0, y), &clipped, color);
445 }
446 Ok(())
447 }
448
449 #[cfg(feature = "nes")]
450 pub fn draw_nes_frame(
452 &mut self,
453 position: (i32, i32),
454 pixels: &[Color],
455 width: u32,
456 height: u32,
457 ) {
458 self.blit_colors(position, pixels, width, height);
459 }
460
461 #[cfg(feature = "fontdue")]
462 pub fn draw_text(
464 &mut self,
465 position: (i32, i32),
466 text: &str,
467 color: Color,
468 font_data: &[u8],
469 px: f32,
470 ) {
471 let vm = line_metrics(font_data, px).unwrap();
472 let ascent = round_to_i32(vm.ascent);
473 let baseline = position.1 + ascent;
474 let mut x_cursor = position.0;
475 for ch in text.chars() {
476 let key = GlyphKey {
477 font: font_data.as_ptr(),
478 size: px.to_bits(),
479 ch,
480 };
481 let (metrics, bitmap) = {
482 let entry = self
483 .glyph_cache
484 .entry(key)
485 .or_insert_with(|| rasterize_glyph(font_data, ch, px).unwrap());
486 (entry.0, entry.1.clone())
487 };
488 let w = metrics.width as i32;
489 let h = metrics.height as i32;
490 if w == 0 || h == 0 {
491 x_cursor += round_to_i32(metrics.advance_width);
492 continue;
493 }
494 let mut argb = vec![0u8; (w * h * 4) as usize];
495 for y in 0..h {
496 for x in 0..w {
497 let alpha = bitmap[(y) as usize * metrics.width + x as usize];
498 let idx = ((y * w + x) * 4) as usize;
499 argb[idx] = (color.0 as u16 * alpha as u16 / 255) as u8;
500 argb[idx + 1] = (color.1 as u16 * alpha as u16 / 255) as u8;
501 argb[idx + 2] = (color.2 as u16 * alpha as u16 / 255) as u8;
502 argb[idx + 3] = alpha;
503 }
504 }
505 let src = Surface::new(
506 argb.as_mut_slice(),
507 (w * 4) as usize,
508 PixelFmt::Argb8888,
509 w as u32,
510 h as u32,
511 );
512 let dst_pos = (
513 x_cursor + metrics.xmin,
514 baseline - ascent - metrics.ymin - (h - 1),
515 );
516 self.blitter.blend(
517 &src,
518 Rect {
519 x: 0,
520 y: 0,
521 w: w as u32,
522 h: h as u32,
523 },
524 &mut self.surface,
525 dst_pos,
526 );
527 self.planner.add(Rect {
528 x: dst_pos.0,
529 y: dst_pos.1,
530 w: w as u32,
531 h: h as u32,
532 });
533 x_cursor += round_to_i32(metrics.advance_width);
534 }
535 }
536
537 #[cfg(not(feature = "fontdue"))]
538 pub fn draw_text(
540 &mut self,
541 position: (i32, i32),
542 text: &str,
543 color: Color,
544 _font_data: &[u8],
545 _px: f32,
546 ) {
547 let _ = (position, text, color);
548 }
549}
550
551impl<B: Blitter, const N: usize> Renderer for BlitterRenderer<'_, B, N> {
552 fn blend_rect(&mut self, rect: WidgetRect, color: Color) {
553 let alpha = color.3 as u16;
554 if alpha == 0 {
555 return;
556 }
557 if alpha == 255 {
558 self.fill_rect(rect, color);
559 return;
560 }
561 if self.surface.format == PixelFmt::Argb8888 {
563 let sw = self.surface.width as i32;
564 let sh = self.surface.height as i32;
565 let stride = self.surface.stride;
566 let inv = 255 - alpha;
567 let x0 = rect.x.max(0);
568 let y0 = rect.y.max(0);
569 let x1 = (rect.x + rect.width).min(sw);
570 let y1 = (rect.y + rect.height).min(sh);
571 for y in y0..y1 {
572 for x in x0..x1 {
573 let off = y as usize * stride + x as usize * 4;
574 let bg_b = self.surface.buf[off] as u16;
575 let bg_g = self.surface.buf[off + 1] as u16;
576 let bg_r = self.surface.buf[off + 2] as u16;
577 self.surface.buf[off] = ((color.2 as u16 * alpha + bg_b * inv) / 255) as u8;
578 self.surface.buf[off + 1] = ((color.1 as u16 * alpha + bg_g * inv) / 255) as u8;
579 self.surface.buf[off + 2] = ((color.0 as u16 * alpha + bg_r * inv) / 255) as u8;
580 self.surface.buf[off + 3] = 0xff;
581 }
582 }
583 self.planner.add(Rect {
584 x: rect.x,
585 y: rect.y,
586 w: rect.width as u32,
587 h: rect.height as u32,
588 });
589 } else {
590 self.fill_rect(rect, color);
592 }
593 }
594
595 fn fill_rect(&mut self, rect: WidgetRect, color: Color) {
596 let r = Rect {
597 x: rect.x,
598 y: rect.y,
599 w: rect.width as u32,
600 h: rect.height as u32,
601 };
602 self.planner.add(r);
603 self.blitter.fill(&mut self.surface, r, color.to_argb8888());
604 }
605
606 fn draw_text(&mut self, position: (i32, i32), text: &str, color: Color) {
607 #[cfg(feature = "fontdue")]
608 {
609 const PX: f32 = 16.0;
610 BlitterRenderer::draw_text(self, position, text, color, FONT_DATA, PX);
611 }
612 #[cfg(not(feature = "fontdue"))]
613 {
614 let _ = (position, text, color);
615 }
616 }
617
618 fn draw_pixels(&mut self, position: (i32, i32), pixels: &[Color], width: u32, height: u32) {
619 if self.surface.format == PixelFmt::Argb8888 {
622 let sw = self.surface.width as i32;
623 let sh = self.surface.height as i32;
624 let stride = self.surface.stride;
625 for y in 0..height as i32 {
626 let dy = position.1 + y;
627 if dy < 0 || dy >= sh {
628 continue;
629 }
630 for x in 0..width as i32 {
631 let dx = position.0 + x;
632 if dx < 0 || dx >= sw {
633 continue;
634 }
635 let src_idx = (y as u32 * width + x as u32) as usize;
636 if let Some(&c) = pixels.get(src_idx) {
637 let off = dy as usize * stride + dx as usize * 4;
638 self.surface.buf[off..off + 4]
639 .copy_from_slice(&c.to_argb8888().to_le_bytes());
640 }
641 }
642 }
643 self.planner.add(Rect {
644 x: position.0,
645 y: position.1,
646 w: width,
647 h: height,
648 });
649 } else {
650 for y in 0..height as i32 {
652 for x in 0..width as i32 {
653 let idx = (y as u32 * width + x as u32) as usize;
654 if let Some(&c) = pixels.get(idx) {
655 self.fill_rect(
656 WidgetRect {
657 x: position.0 + x,
658 y: position.1 + y,
659 width: 1,
660 height: 1,
661 },
662 c,
663 );
664 }
665 }
666 }
667 }
668 }
669
670 fn blend_row(&mut self, x: i32, y: i32, color: Color, coverage: &[u8]) {
671 if color.3 == 0 || coverage.is_empty() {
675 return;
676 }
677 if self.surface.format != PixelFmt::Argb8888 {
678 for (i, &cov) in coverage.iter().enumerate() {
681 if cov == 0 {
682 continue;
683 }
684 let alpha = ((color.3 as u16 * cov as u16) / 255) as u8;
685 if alpha == 0 {
686 continue;
687 }
688 self.blend_rect(
689 WidgetRect {
690 x: x + i as i32,
691 y,
692 width: 1,
693 height: 1,
694 },
695 Color(color.0, color.1, color.2, alpha),
696 );
697 }
698 return;
699 }
700 let sw = self.surface.width as i32;
701 let sh = self.surface.height as i32;
702 if y < 0 || y >= sh {
703 return;
704 }
705 let stride = self.surface.stride;
706 let src_a = color.3 as u16;
707 let row_off = y as usize * stride;
708 let mut written_x0 = i32::MAX;
709 let mut written_x1 = i32::MIN;
710 for (i, &cov) in coverage.iter().enumerate() {
711 if cov == 0 {
712 continue;
713 }
714 let px = x + i as i32;
715 if px < 0 || px >= sw {
716 continue;
717 }
718 let alpha = (src_a * cov as u16) / 255;
719 if alpha == 0 {
720 continue;
721 }
722 let inv = 255 - alpha;
723 let off = row_off + (px as usize) * 4;
724 let bg_b = self.surface.buf[off] as u16;
725 let bg_g = self.surface.buf[off + 1] as u16;
726 let bg_r = self.surface.buf[off + 2] as u16;
727 self.surface.buf[off] = ((color.2 as u16 * alpha + bg_b * inv) / 255) as u8;
728 self.surface.buf[off + 1] = ((color.1 as u16 * alpha + bg_g * inv) / 255) as u8;
729 self.surface.buf[off + 2] = ((color.0 as u16 * alpha + bg_r * inv) / 255) as u8;
730 self.surface.buf[off + 3] = 0xff;
731 if px < written_x0 {
732 written_x0 = px;
733 }
734 if px > written_x1 {
735 written_x1 = px;
736 }
737 }
738 if written_x0 <= written_x1 {
739 self.planner.add(Rect {
740 x: written_x0,
741 y,
742 w: (written_x1 - written_x0 + 1) as u32,
743 h: 1,
744 });
745 }
746 }
747
748 fn submit(&mut self, list: &CommandList) {
749 submit_with_occlusion(self, list);
750 }
751}
752
753fn submit_with_occlusion<B: Blitter, const N: usize>(
791 r: &mut BlitterRenderer<'_, B, N>,
792 list: &CommandList,
793) {
794 let mut pending: Option<(WidgetRect, Color, BlendMode)> = None;
795
796 for (i, cmd) in list.iter().enumerate() {
797 let occluded = match cmd.aabb() {
799 None => false,
800 Some(bb_i) => {
801 let mut hit = false;
802 for later in list.iter().skip(i + 1) {
803 if matches!(later, Cmd::Barrier | Cmd::SetClip { .. }) {
804 break;
805 }
806 if !is_opaque_filler(later) {
807 continue;
808 }
809 let Some(bb_j) = later.aabb() else { continue };
810 if rect_contains(bb_j, bb_i) {
811 hit = true;
812 break;
813 }
814 }
815 hit
816 }
817 };
818 if occluded {
819 continue;
822 }
823
824 if let Cmd::FillRect { rect, color, blend } = cmd {
826 if let Some((prect, pcolor, pblend)) = pending.as_ref() {
827 if pcolor == color
828 && pblend == blend
829 && let Some(merged) = merge_rects_axis_aligned(*prect, *rect)
830 {
831 pending = Some((merged, *color, *blend));
832 continue;
833 }
834 flush_pending(r, pending.take());
836 }
837 pending = Some((*rect, *color, *blend));
838 } else {
839 flush_pending(r, pending.take());
841 cmd.dispatch_to(r);
842 }
843 }
844 flush_pending(r, pending.take());
845}
846
847#[inline]
848fn flush_pending<B: Blitter, const N: usize>(
849 r: &mut BlitterRenderer<'_, B, N>,
850 pending: Option<(WidgetRect, Color, BlendMode)>,
851) {
852 if let Some((rect, color, blend)) = pending {
853 Cmd::FillRect { rect, color, blend }.dispatch_to(r);
854 }
855}
856
857#[inline]
862fn merge_rects_axis_aligned(a: WidgetRect, b: WidgetRect) -> Option<WidgetRect> {
863 if a.y == b.y && a.height == b.height {
864 if a.x + a.width == b.x {
865 return Some(WidgetRect {
866 x: a.x,
867 y: a.y,
868 width: a.width + b.width,
869 height: a.height,
870 });
871 }
872 if b.x + b.width == a.x {
873 return Some(WidgetRect {
874 x: b.x,
875 y: b.y,
876 width: a.width + b.width,
877 height: a.height,
878 });
879 }
880 }
881 if a.x == b.x && a.width == b.width {
882 if a.y + a.height == b.y {
883 return Some(WidgetRect {
884 x: a.x,
885 y: a.y,
886 width: a.width,
887 height: a.height + b.height,
888 });
889 }
890 if b.y + b.height == a.y {
891 return Some(WidgetRect {
892 x: b.x,
893 y: b.y,
894 width: a.width,
895 height: a.height + b.height,
896 });
897 }
898 }
899 None
900}
901
902#[inline]
903fn is_opaque_filler(cmd: &Cmd) -> bool {
904 if let Cmd::FillRect { color, .. } = cmd {
905 color.3 == 255
906 } else {
907 false
908 }
909}
910
911#[inline]
912fn rect_contains(outer: WidgetRect, inner: WidgetRect) -> bool {
913 outer.x <= inner.x
914 && outer.y <= inner.y
915 && outer.x + outer.width >= inner.x + inner.width
916 && outer.y + outer.height >= inner.y + inner.height
917}
918
919pub struct RotatedRenderer<'a> {
929 inner: &'a mut dyn Renderer,
930 fb_width: i32,
932}
933
934impl<'a> RotatedRenderer<'a> {
935 pub fn new(inner: &'a mut dyn Renderer, fb_width: u32) -> Self {
939 Self {
940 inner,
941 fb_width: fb_width as i32,
942 }
943 }
944
945 fn blit_glyph_coverage_rotated(
962 &mut self,
963 font: &dyn FontMetrics,
964 ch: char,
965 extent: WidgetRect,
966 color: Color,
967 ) -> bool {
968 let w = extent.width;
969 let h = extent.height;
970 if w <= 0 || h <= 0 {
971 return true; }
973 let (wu, hu) = (w as usize, h as usize);
974
975 const BOUND: usize = 64 * 64;
977 if wu <= 64 && hu <= 64 && wu * hu <= BOUND {
978 #[cfg_attr(target_os = "none", unsafe(link_section = ".rlvgl_blit_scratch"))]
983 static mut SCRATCH_COV: [u8; BOUND] = [0u8; BOUND]; let scratch = unsafe { &mut SCRATCH_COV[..wu * hu] };
990
991 let mut row_buf = [0u8; 64];
994 for row in 0..hu {
995 let cov_row = &mut row_buf[..wu];
996 if !font.glyph_coverage_row(ch, row as u16, 0, cov_row) {
997 return false;
998 }
999 for (col, &cov) in cov_row.iter().enumerate() {
1000 scratch[col * hu + (hu - 1 - row)] = cov;
1001 }
1002 }
1003
1004 let fb_x = self.fb_width - extent.y - h;
1006 let fb_y = extent.x;
1007 for py in 0..wu {
1010 let run = &scratch[py * hu..py * hu + hu];
1011 self.inner.blend_row(fb_x, fb_y + py as i32, color, run);
1012 }
1013 true
1014 } else {
1015 let mut chunk = [0u8; 64];
1019 for row in 0..hu {
1020 let mut x_off = 0usize;
1021 while x_off < wu {
1022 let n = (wu - x_off).min(chunk.len());
1023 let part = &mut chunk[..n];
1024 if !font.glyph_coverage_row(ch, row as u16, x_off as u16, part) {
1025 return false;
1026 }
1027 self.blend_row(extent.x + x_off as i32, extent.y + row as i32, color, part);
1028 x_off += n;
1029 }
1030 }
1031 true
1032 }
1033 }
1034}
1035
1036impl Renderer for RotatedRenderer<'_> {
1037 fn fill_rect(&mut self, rect: WidgetRect, color: Color) {
1038 let mut fb_x = self.fb_width - rect.y - rect.height;
1039 let fb_y = rect.x;
1040 let mut fb_w = rect.height;
1041 let fb_h = rect.width;
1042
1043 if fb_w <= 0 || fb_h <= 0 {
1044 return;
1045 }
1046
1047 if fb_x < 0 {
1049 fb_w += fb_x; fb_x = 0;
1051 }
1052 if fb_x + fb_w > self.fb_width {
1054 fb_w = self.fb_width - fb_x;
1055 }
1056 if fb_w <= 0 {
1057 return;
1058 }
1059
1060 self.inner.fill_rect(
1061 WidgetRect {
1062 x: fb_x,
1063 y: fb_y,
1064 width: fb_w,
1065 height: fb_h,
1066 },
1067 color,
1068 );
1069 }
1070
1071 fn blend_rect(&mut self, rect: WidgetRect, color: Color) {
1072 let mut fb_x = self.fb_width - rect.y - rect.height;
1073 let fb_y = rect.x;
1074 let mut fb_w = rect.height;
1075 let fb_h = rect.width;
1076
1077 if fb_w <= 0 || fb_h <= 0 {
1078 return;
1079 }
1080
1081 if fb_x < 0 {
1082 fb_w += fb_x;
1083 fb_x = 0;
1084 }
1085 if fb_x + fb_w > self.fb_width {
1086 fb_w = self.fb_width - fb_x;
1087 }
1088 if fb_w <= 0 {
1089 return;
1090 }
1091
1092 self.inner.blend_rect(
1093 WidgetRect {
1094 x: fb_x,
1095 y: fb_y,
1096 width: fb_w,
1097 height: fb_h,
1098 },
1099 color,
1100 );
1101 }
1102
1103 fn draw_text(&mut self, position: (i32, i32), text: &str, color: Color) {
1104 let fx = self.fb_width - 1 - position.1;
1105 if fx >= 0 {
1106 self.inner.draw_text((fx, position.0), text, color);
1107 }
1108 }
1109
1110 fn draw_glyph(&mut self, font: &dyn FontMetrics, ch: char, origin: (i32, i32), color: Color) {
1111 let Some(info) = font.glyph_metrics(ch) else {
1117 return;
1118 };
1119 let extent = WidgetRect {
1120 x: origin.0 + info.bearing_x as i32,
1121 y: origin.1 - info.bearing_y as i32,
1122 width: info.width as i32,
1123 height: info.height as i32,
1124 };
1125 if extent.width <= 0 || extent.height <= 0 {
1126 return;
1127 }
1128 if !self.blit_glyph_coverage_rotated(font, ch, extent, color) {
1129 self.blend_rect(extent, color);
1132 }
1133 }
1134
1135 fn draw_text_shaped(&mut self, shaped: &ShapedText<'_>, origin: (i32, i32), color: Color) {
1136 let font = shaped.font;
1142 for glyph in &shaped.glyphs {
1143 let mut extent = glyph.extent();
1144 extent.x += origin.0;
1145 extent.y += origin.1;
1146 if extent.width <= 0 || extent.height <= 0 {
1147 continue;
1148 }
1149 let drawn = match font {
1150 Some(font) => self.blit_glyph_coverage_rotated(font, glyph.ch, extent, color),
1151 None => false,
1152 };
1153 if !drawn {
1154 self.blend_rect(extent, color);
1155 }
1156 }
1157 }
1158
1159 fn draw_pixels(&mut self, position: (i32, i32), pixels: &[Color], width: u32, height: u32) {
1160 if width == 0 || height == 0 {
1178 return;
1179 }
1180
1181 let fb_x = self.fb_width - position.1 - height as i32;
1185 let fb_y = position.0;
1186 let phys_w = height; let phys_h = width; const SCRATCH_PIXELS: usize = 64 * 64;
1209 #[cfg_attr(target_os = "none", unsafe(link_section = ".rlvgl_blit_scratch"))]
1249 static mut SCRATCH: [Color; SCRATCH_PIXELS] = [Color(0, 0, 0, 0); SCRATCH_PIXELS]; let len = (width * height) as usize;
1252 if len <= SCRATCH_PIXELS {
1253 let scratch = unsafe { &mut SCRATCH[..len] };
1258 for ly in 0..height as i32 {
1263 for lx in 0..width as i32 {
1264 let src_idx = (ly as u32 * width + lx as u32) as usize;
1265 let dst_idx = (lx as u32 * phys_w + (height as i32 - 1 - ly) as u32) as usize;
1266 scratch[dst_idx] = pixels.get(src_idx).copied().unwrap_or(Color(0, 0, 0, 0));
1267 }
1268 }
1269 self.inner
1270 .draw_pixels((fb_x, fb_y), scratch, phys_w, phys_h);
1271 } else {
1272 let mut rotated: alloc::vec::Vec<Color> = alloc::vec::Vec::with_capacity(len);
1274 rotated.resize(len, Color(0, 0, 0, 0));
1275 for ly in 0..height as i32 {
1276 for lx in 0..width as i32 {
1277 let src_idx = (ly as u32 * width + lx as u32) as usize;
1278 let dst_idx = (lx as u32 * phys_w + (height as i32 - 1 - ly) as u32) as usize;
1279 if let Some(&c) = pixels.get(src_idx) {
1280 rotated[dst_idx] = c;
1281 }
1282 }
1283 }
1284 self.inner
1285 .draw_pixels((fb_x, fb_y), &rotated, phys_w, phys_h);
1286 }
1287 }
1288
1289 fn submit(&mut self, list: &CommandList) {
1290 self.inner.submit(list);
1294 }
1295}
1296
1297#[cfg(test)]
1298mod scratch_tests {
1299 use super::*;
1300 use crate::cpu_blitter::CpuBlitter;
1301
1302 #[test]
1303 fn blit_colors_reuses_scratch_buffer() {
1304 let mut buf = [0u8; 4 * 4 * 4];
1305 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1306 let mut blit = CpuBlitter;
1307 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1308 BlitterRenderer::new(&mut blit, surface);
1309 let pixels = [Color(0, 0, 0, 0)];
1310 renderer.blit_colors((0, 0), &pixels, 1, 1);
1311 let first_ptr = renderer.scratch.as_ref().unwrap().as_ptr();
1312 renderer.blit_colors((1, 1), &pixels, 1, 1);
1313 let second_ptr = renderer.scratch.as_ref().unwrap().as_ptr();
1314 assert_eq!(first_ptr, second_ptr);
1315 }
1316}
1317
1318#[cfg(all(test, feature = "fontdue"))]
1319mod text_tests {
1320 use super::*;
1321 use crate::cpu_blitter::CpuBlitter;
1322
1323 #[test]
1324 fn blitter_draws_text() {
1325 let mut buf = [0u8; 64 * 64 * 4];
1326 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
1327 let mut blit = CpuBlitter;
1328 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1329 BlitterRenderer::new(&mut blit, surface);
1330 Renderer::draw_text(&mut renderer, (0, 32), "A", Color(255, 255, 255, 255));
1331 assert!(buf.iter().any(|&p| p != 0));
1332 }
1333
1334 #[test]
1335 fn cache_accounts_for_size() {
1336 let mut buf = [0u8; 64 * 64 * 4];
1337 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
1338 let mut blit = CpuBlitter;
1339 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1340 BlitterRenderer::new(&mut blit, surface);
1341 renderer.draw_text((0, 32), "Hi", Color(255, 255, 255, 255), FONT_DATA, 16.0);
1342 let len_after_small = renderer.glyph_cache.len();
1343 renderer.draw_text((0, 32), "Hi", Color(255, 255, 255, 255), FONT_DATA, 16.0);
1344 assert_eq!(len_after_small, renderer.glyph_cache.len());
1345 renderer.draw_text((0, 32), "Hi", Color(255, 255, 255, 255), FONT_DATA, 24.0);
1346 assert!(renderer.glyph_cache.len() > len_after_small);
1347 }
1348}
1349
1350#[cfg(all(test, feature = "png", not(target_os = "none")))]
1351mod png_tests {
1352 use super::*;
1353 use crate::cpu_blitter::CpuBlitter;
1354 use base64::Engine;
1355 use rlvgl_core::png::DecodingError;
1356
1357 const RED_DOT_PNG: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC";
1358
1359 #[test]
1360 fn blitter_draws_png() {
1361 let data = base64::engine::general_purpose::STANDARD
1362 .decode(RED_DOT_PNG)
1363 .unwrap();
1364 let mut buf = [0u8; 4 * 4 * 4];
1365 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1366 let mut blit = CpuBlitter;
1367 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1368 BlitterRenderer::new(&mut blit, surface);
1369 renderer.draw_png((0, 0), &data).unwrap();
1370 assert!(buf.iter().any(|&p| p != 0));
1371 }
1372
1373 #[test]
1374 fn blitter_rejects_invalid_png() {
1375 let mut buf = [0u8; 4 * 4 * 4];
1376 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1377 let mut blit = CpuBlitter;
1378 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1379 BlitterRenderer::new(&mut blit, surface);
1380 let err = renderer.draw_png((0, 0), b"not a png").unwrap_err();
1381 assert!(matches!(err, DecodingError::Format(_)));
1382 }
1383}
1384
1385#[cfg(all(test, feature = "jpeg", not(target_os = "none")))]
1386mod jpeg_tests {
1387 use super::*;
1388 use crate::cpu_blitter::CpuBlitter;
1389 use base64::Engine;
1390 use rlvgl_core::jpeg::Error as JpegError;
1391
1392 const RED_DOT_JPEG: &str = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDi6KKK+ZP3E//Z";
1393
1394 #[test]
1395 fn blitter_draws_jpeg() {
1396 let data = base64::engine::general_purpose::STANDARD
1397 .decode(RED_DOT_JPEG)
1398 .unwrap();
1399 let mut buf = [0u8; 4 * 4 * 4];
1400 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1401 let mut blit = CpuBlitter;
1402 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1403 BlitterRenderer::new(&mut blit, surface);
1404 renderer.draw_jpeg((0, 0), &data).unwrap();
1405 assert!(buf.iter().any(|&p| p != 0));
1406 }
1407
1408 #[test]
1409 fn blitter_rejects_invalid_jpeg() {
1410 let mut buf = [0u8; 4 * 4 * 4];
1411 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1412 let mut blit = CpuBlitter;
1413 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1414 BlitterRenderer::new(&mut blit, surface);
1415 let err = renderer.draw_jpeg((0, 0), b"not a jpeg").unwrap_err();
1416 assert!(matches!(err, JpegError::Format(_)));
1417 }
1418}
1419
1420#[cfg(all(test, feature = "gif"))]
1421mod gif_tests {
1422 use super::*;
1423 use crate::cpu_blitter::CpuBlitter;
1424 use base64::Engine;
1425 use rlvgl_core::gif::DecodingError as GifDecodingError;
1426
1427 const RED_DOT_GIF: &str = "R0lGODdhAQABAPAAAP8AAP///yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
1428
1429 #[test]
1430 fn blitter_draws_gif() {
1431 let data = base64::engine::general_purpose::STANDARD
1432 .decode(RED_DOT_GIF)
1433 .unwrap();
1434 let mut buf = [0u8; 4 * 4 * 4];
1435 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1436 let mut blit = CpuBlitter;
1437 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1438 BlitterRenderer::new(&mut blit, surface);
1439 renderer.draw_gif_frame((0, 0), &data, 0).unwrap();
1440 assert!(buf.iter().any(|&p| p != 0));
1441 }
1442
1443 #[test]
1444 fn blitter_rejects_invalid_gif() {
1445 let mut buf = [0u8; 4 * 4 * 4];
1446 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1447 let mut blit = CpuBlitter;
1448 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1449 BlitterRenderer::new(&mut blit, surface);
1450 let err = renderer
1451 .draw_gif_frame((0, 0), b"not a gif", 0)
1452 .unwrap_err();
1453 assert!(matches!(err, GifDecodingError::Format(_)));
1454 }
1455}
1456
1457#[cfg(all(test, feature = "apng"))]
1458mod apng_tests {
1459 use super::*;
1460 use crate::cpu_blitter::CpuBlitter;
1461 use base64::Engine;
1462
1463 const RED_DOT_APNG: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACGFjVEwAAAABAAAAALQt6aAAAAAaZmNUTAAAAAAAAAABAAAAAQAAAAAAAAAAAGQD6AEAqmVSjAAAAA1JREFUeJxj+M/A8B8ABQAB/4mZPR0AAAAASUVORK5CYII=";
1464
1465 #[test]
1466 fn blitter_draws_apng() {
1467 let data = base64::engine::general_purpose::STANDARD
1468 .decode(RED_DOT_APNG)
1469 .unwrap();
1470 let mut buf = [0u8; 4 * 4 * 4];
1471 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1472 let mut blit = CpuBlitter;
1473 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1474 BlitterRenderer::new(&mut blit, surface);
1475 renderer.draw_apng_frame((0, 0), &data, 0).unwrap();
1476 assert!(buf.iter().any(|&p| p != 0));
1477 }
1478}
1479
1480#[cfg(all(test, feature = "canvas"))]
1481mod canvas_tests {
1482 use super::*;
1483 use crate::cpu_blitter::CpuBlitter;
1484 use embedded_graphics::prelude::Point;
1485 use rlvgl_core::canvas::Canvas;
1486
1487 #[test]
1488 fn blitter_draws_canvas() {
1489 let mut canvas = Canvas::new(1, 1);
1490 canvas.draw_pixel(Point::new(0, 0), Color(255, 0, 0, 255));
1491 let mut buf = [0u8; 4];
1492 let surface = Surface::new(&mut buf, 4, PixelFmt::Argb8888, 1, 1);
1493 let mut blit = CpuBlitter;
1494 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1495 BlitterRenderer::new(&mut blit, surface);
1496 renderer.draw_canvas((0, 0), &canvas);
1497 assert!(buf.iter().any(|&p| p != 0));
1498 }
1499}
1500
1501#[cfg(all(test, feature = "qrcode", not(target_os = "none")))]
1502mod qrcode_tests {
1503 use super::*;
1504 use crate::cpu_blitter::CpuBlitter;
1505 use rlvgl_core::qrcode::QrError;
1506
1507 #[test]
1508 fn blitter_draws_qr() {
1509 let mut buf = [0u8; 64 * 64 * 4];
1510 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
1511 let mut blit = CpuBlitter;
1512 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1513 BlitterRenderer::new(&mut blit, surface);
1514 renderer.draw_qr((0, 0), b"hi").unwrap();
1515 assert!(buf.iter().any(|&p| p != 0));
1516 }
1517
1518 #[test]
1519 fn blitter_rejects_invalid_qr_data() {
1520 let mut buf = [0u8; 64 * 64 * 4];
1521 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
1522 let mut blit = CpuBlitter;
1523 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1524 BlitterRenderer::new(&mut blit, surface);
1525 let data = vec![0u8; 3000];
1526 let err = renderer.draw_qr((0, 0), &data).unwrap_err();
1527 assert!(matches!(err, QrError::DataTooLong));
1528 }
1529}
1530
1531#[cfg(all(test, feature = "lottie"))]
1532mod lottie_tests {
1533 use super::*;
1534 use crate::cpu_blitter::CpuBlitter;
1535
1536 const SIMPLE_JSON: &str =
1537 "{\"v\":\"5.7\",\"fr\":30,\"ip\":0,\"op\":0,\"w\":1,\"h\":1,\"layers\":[]}";
1538
1539 #[test]
1540 fn blitter_draws_lottie() {
1541 let mut buf = [0u8; 4 * 4 * 4];
1542 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1543 let mut blit = CpuBlitter;
1544 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1545 BlitterRenderer::new(&mut blit, surface);
1546 renderer
1547 .draw_lottie_frame((0, 0), SIMPLE_JSON, 0, 1, 1)
1548 .unwrap();
1549 assert!(buf.iter().any(|&p| p != 0));
1550 }
1551
1552 #[test]
1553 fn blitter_rejects_invalid_lottie() {
1554 let mut buf = [0u8; 4 * 4 * 4];
1555 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1556 let mut blit = CpuBlitter;
1557 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1558 BlitterRenderer::new(&mut blit, surface);
1559 assert!(
1560 renderer
1561 .draw_lottie_frame((0, 0), "not json", 0, 1, 1)
1562 .is_err()
1563 );
1564 }
1565}
1566
1567#[cfg(all(test, feature = "pinyin", feature = "fontdue"))]
1568mod pinyin_tests {
1569 use super::*;
1570 use crate::cpu_blitter::CpuBlitter;
1571 use rlvgl_core::pinyin::PinyinInputMethod;
1572
1573 #[test]
1574 fn blitter_draws_pinyin() {
1575 let mut buf = [0u8; 64 * 64 * 4];
1576 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
1577 let mut blit = CpuBlitter;
1578 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1579 BlitterRenderer::new(&mut blit, surface);
1580 let ime = PinyinInputMethod;
1581 assert!(renderer.draw_pinyin_candidates((0, 0), &ime, "zhong", Color(255, 255, 255, 255)));
1582 assert!(buf.iter().any(|&p| p != 0));
1583 }
1584
1585 #[test]
1586 fn pinyin_candidates_clipped_to_surface() {
1587 let mut buf = [0u8; 32 * 16 * 4];
1588 let surface = Surface::new(&mut buf, 32 * 4, PixelFmt::Argb8888, 32, 16);
1589 let mut blit = CpuBlitter;
1590 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1591 BlitterRenderer::new(&mut blit, surface);
1592 let ime = PinyinInputMethod;
1593 assert!(renderer.draw_pinyin_candidates((0, 0), &ime, "zhong", Color(255, 255, 255, 255)));
1594
1595 let mut expected = [0u8; 32 * 16 * 4];
1596 let surface_e = Surface::new(&mut expected, 32 * 4, PixelFmt::Argb8888, 32, 16);
1597 let mut blit_e = CpuBlitter;
1598 let mut renderer_e: BlitterRenderer<'_, CpuBlitter, 4> =
1599 BlitterRenderer::new(&mut blit_e, surface_e);
1600 let chars = ime.candidates("zhong").unwrap();
1601 let text: alloc::string::String = chars.into_iter().collect();
1602 let clipped: alloc::string::String = text.chars().take(2).collect();
1603 Renderer::draw_text(&mut renderer_e, (0, 0), &clipped, Color(255, 255, 255, 255));
1604 assert_eq!(buf[..], expected[..]);
1605 }
1606}
1607
1608#[cfg(all(test, feature = "fatfs", feature = "fontdue"))]
1609mod fatfs_tests {
1610 use super::*;
1611 use crate::cpu_blitter::CpuBlitter;
1612 use fatfs::{FileSystem, FormatVolumeOptions, FsOptions};
1613 use fscommon::BufStream;
1614 use std::io::{Cursor, Seek, SeekFrom, Write};
1615
1616 #[test]
1617 fn blitter_draws_fatfs_listing() {
1618 let mut img = Cursor::new(vec![0u8; 1024 * 512]);
1619 fatfs::format_volume(&mut img, FormatVolumeOptions::new()).unwrap();
1620 img.seek(SeekFrom::Start(0)).unwrap();
1621 {
1622 let buf_stream = BufStream::new(&mut img);
1623 let fs = FileSystem::new(buf_stream, FsOptions::new()).unwrap();
1624 fs.root_dir()
1625 .create_file("foo.txt")
1626 .unwrap()
1627 .write_all(b"hi")
1628 .unwrap();
1629 }
1630 img.seek(SeekFrom::Start(0)).unwrap();
1631 let mut buf = [0u8; 64 * 64 * 4];
1632 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
1633 let mut blit = CpuBlitter;
1634 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1635 BlitterRenderer::new(&mut blit, surface);
1636 renderer
1637 .draw_fatfs_dir((0, 0), &mut img, "/", Color(255, 255, 255, 255))
1638 .unwrap();
1639 assert!(buf.iter().any(|&p| p != 0));
1640 }
1641
1642 #[test]
1643 fn fatfs_listing_clipped_to_surface() {
1644 let mut img = Cursor::new(vec![0u8; 1024 * 512]);
1645 fatfs::format_volume(&mut img, FormatVolumeOptions::new()).unwrap();
1646 img.seek(SeekFrom::Start(0)).unwrap();
1647 {
1648 let buf_stream = BufStream::new(&mut img);
1649 let fs = FileSystem::new(buf_stream, FsOptions::new()).unwrap();
1650 fs.root_dir().create_file("first_long_name.txt").unwrap();
1651 fs.root_dir().create_file("second_long_name.txt").unwrap();
1652 fs.root_dir().create_file("third_long_name.txt").unwrap();
1653 }
1654 img.seek(SeekFrom::Start(0)).unwrap();
1655 let image_vec = img.get_ref().clone();
1656 let mut img_expected = Cursor::new(image_vec.clone());
1657 let mut img_actual = Cursor::new(image_vec);
1658
1659 let mut buf = [0u8; 32 * 32 * 4];
1660 let surface = Surface::new(&mut buf, 32 * 4, PixelFmt::Argb8888, 32, 32);
1661 let mut blit = CpuBlitter;
1662 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1663 BlitterRenderer::new(&mut blit, surface);
1664 renderer
1665 .draw_fatfs_dir((0, 0), &mut img_actual, "/", Color(255, 255, 255, 255))
1666 .unwrap();
1667
1668 let names = rlvgl_core::fatfs::list_dir(&mut img_expected, "/").unwrap();
1669 let mut expected = [0u8; 32 * 32 * 4];
1670 let surface_e = Surface::new(&mut expected, 32 * 4, PixelFmt::Argb8888, 32, 32);
1671 let mut blit_e = CpuBlitter;
1672 let mut renderer_e: BlitterRenderer<'_, CpuBlitter, 4> =
1673 BlitterRenderer::new(&mut blit_e, surface_e);
1674 for (i, name) in names.iter().take(2).enumerate() {
1675 let clipped: alloc::string::String = name.chars().take(2).collect();
1676 Renderer::draw_text(
1677 &mut renderer_e,
1678 (0, (i as i32) * 16),
1679 &clipped,
1680 Color(255, 255, 255, 255),
1681 );
1682 }
1683 assert_eq!(buf[..], expected[..]);
1684 }
1685}
1686
1687#[cfg(all(test, feature = "nes"))]
1688mod nes_tests {
1689 use super::*;
1690 use crate::cpu_blitter::CpuBlitter;
1691
1692 #[test]
1693 fn blitter_draws_nes_frame() {
1694 let pixels = [Color(255, 0, 0, 255)];
1695 let mut buf = [0u8; 4];
1696 let surface = Surface::new(&mut buf, 4, PixelFmt::Argb8888, 1, 1);
1697 let mut blit = CpuBlitter;
1698 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1699 BlitterRenderer::new(&mut blit, surface);
1700 renderer.draw_nes_frame((0, 0), &pixels, 1, 1);
1701 assert!(buf.iter().any(|&p| p != 0));
1702 }
1703
1704 #[test]
1705 fn blitter_draws_full_nes_frame() {
1706 let mut pixels = [Color(0, 0, 0, 255); 256 * 240];
1707 for y in 0..240 {
1708 for x in 0..256 {
1709 pixels[y * 256 + x] = Color(x as u8, y as u8, 0, 255);
1710 }
1711 }
1712 let mut buf = [0u8; 256 * 240 * 4];
1713 let surface = Surface::new(&mut buf, 256 * 4, PixelFmt::Argb8888, 256, 240);
1714 let mut blit = CpuBlitter;
1715 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1716 BlitterRenderer::new(&mut blit, surface);
1717 renderer.draw_nes_frame((0, 0), &pixels, 256, 240);
1718 let x = 128usize;
1719 let y = 120usize;
1720 let idx = (y * 256 + x) * 4;
1721 let actual = u32::from_le_bytes(buf[idx..idx + 4].try_into().unwrap());
1722 let expected = Color(x as u8, y as u8, 0, 255).to_argb8888();
1723 assert_eq!(actual, expected);
1724 }
1725}