1#[cfg(test)]
30extern crate alloc;
31
32use denise::{Point, Rect, Size};
33
34use crate::blend::Paint;
35use crate::canvas::Canvas;
36
37pub const CELL_WIDTH: i32 = 5;
39pub const CELL_HEIGHT: i32 = 8;
41pub const ADVANCE: i32 = 6;
43pub const LINE_HEIGHT: i32 = 9;
45
46pub type Glyph = [u8; CELL_HEIGHT as usize];
48
49const fn pack(art: &str) -> Glyph {
53 let bytes = art.as_bytes();
54 assert!(
55 bytes.len() == (CELL_WIDTH * CELL_HEIGHT) as usize,
56 "glyph art must be exactly CELL_WIDTH by CELL_HEIGHT characters"
57 );
58 let mut rows = [0u8; CELL_HEIGHT as usize];
59 let mut y = 0;
60 while y < CELL_HEIGHT as usize {
61 let mut x = 0;
62 while x < CELL_WIDTH as usize {
63 if bytes[y * CELL_WIDTH as usize + x] == b'#' {
64 rows[y] |= 0x80 >> x;
65 }
66 x += 1;
67 }
68 y += 1;
69 }
70 rows
71}
72
73#[derive(Clone, Copy, Debug)]
75pub struct BitmapFont {
76 ascii: &'static [Glyph; 95],
77 extras: &'static [(char, Glyph)],
79 fallback: Glyph,
80}
81
82pub static BUILT_IN: BitmapFont = BitmapFont {
84 ascii: &ASCII,
85 extras: &EXTRAS,
86 fallback: pack(concat!(
87 "#####", "#...#", "#...#", "#...#", "#...#", "#...#", "#####", ".....",
88 )),
89};
90
91impl BitmapFont {
92 pub fn glyph(&self, ch: char) -> &Glyph {
94 let code = ch as u32;
95 if (0x20..0x7F).contains(&code) {
96 return &self.ascii[(code - 0x20) as usize];
97 }
98 match self.extras.binary_search_by_key(&ch, |&(c, _)| c) {
99 Ok(index) => &self.extras[index].1,
100 Err(_) => &self.fallback,
101 }
102 }
103
104 pub fn contains(&self, ch: char) -> bool {
106 let code = ch as u32;
107 (0x20..0x7F).contains(&code) || self.extras.binary_search_by_key(&ch, |&(c, _)| c).is_ok()
108 }
109
110 pub fn line_width(&self, line: &str, scale: i32) -> i32 {
115 let scale = scale.max(1);
116 let count = line.chars().count() as i32;
117 if count == 0 {
118 0
119 } else {
120 (count * ADVANCE - (ADVANCE - CELL_WIDTH)) * scale
121 }
122 }
123
124 pub fn measure(&self, text: &str, scale: i32) -> Size {
126 let scale = scale.max(1);
127 let mut widest = 0;
128 let mut lines = 0;
129 for line in text.split('\n') {
130 widest = widest.max(self.line_width(line, scale));
131 lines += 1;
132 }
133 Size::new(
134 widest.max(0) as u32,
135 ((lines - 1) * LINE_HEIGHT * scale + CELL_HEIGHT * scale).max(0) as u32,
136 )
137 }
138
139 #[inline]
145 pub const fn caret_offset(&self, index: usize, scale: i32) -> i32 {
146 index as i32 * ADVANCE * if scale > 1 { scale } else { 1 }
147 }
148}
149
150impl Canvas<'_> {
151 pub fn draw_glyph(&mut self, glyph: &Glyph, at: Point, scale: i32, color: impl Into<Paint>) {
153 let scale = scale.max(1);
154 let cell = Rect::new(at.x, at.y, CELL_WIDTH * scale, CELL_HEIGHT * scale);
155 if self.visible(cell).is_none() {
156 return;
157 }
158 let paint = color.into();
159 for (row, bits) in glyph.iter().enumerate() {
160 if *bits == 0 {
161 continue;
162 }
163 let y = at.y + row as i32 * scale;
164 let mut x = 0;
168 while x < CELL_WIDTH {
169 if bits & (0x80 >> x) == 0 {
170 x += 1;
171 continue;
172 }
173 let mut end = x + 1;
174 while end < CELL_WIDTH && bits & (0x80 >> end) != 0 {
175 end += 1;
176 }
177 self.fill_rect(
178 Rect::new(at.x + x * scale, y, (end - x) * scale, scale),
179 paint,
180 );
181 x = end;
182 }
183 }
184 }
185
186 pub fn draw_text(
190 &mut self,
191 font: &BitmapFont,
192 at: Point,
193 scale: i32,
194 text: &str,
195 color: impl Into<Paint>,
196 ) -> Size {
197 let scale = scale.max(1);
198 let paint = color.into();
199 let mut pen = at;
200 for ch in text.chars() {
201 if ch == '\n' {
202 pen = Point::new(at.x, pen.y + LINE_HEIGHT * scale);
203 continue;
204 }
205 self.draw_glyph(font.glyph(ch), pen, scale, paint);
206 pen.x += ADVANCE * scale;
207 }
208 font.measure(text, scale)
209 }
210}
211
212const ASCII_ART: [&str; 95] = [
214 concat!(
216 ".....", ".....", ".....", ".....", ".....", ".....", ".....", ".....",
217 ),
218 concat!(
220 "..#..", "..#..", "..#..", "..#..", "..#..", ".....", "..#..", ".....",
221 ),
222 concat!(
224 ".#.#.", ".#.#.", ".....", ".....", ".....", ".....", ".....", ".....",
225 ),
226 concat!(
228 ".#.#.", ".#.#.", "#####", ".#.#.", "#####", ".#.#.", ".#.#.", ".....",
229 ),
230 concat!(
232 "..#..", ".####", "#.#..", ".###.", "..#.#", "####.", "..#..", ".....",
233 ),
234 concat!(
236 "##...", "##..#", "...#.", "..#..", ".#...", "#..##", "...##", ".....",
237 ),
238 concat!(
240 ".##..", "#..#.", "#.#..", ".#...", "#.#.#", "#..#.", ".##.#", ".....",
241 ),
242 concat!(
244 "..#..", "..#..", ".....", ".....", ".....", ".....", ".....", ".....",
245 ),
246 concat!(
248 "...#.", "..#..", ".#...", ".#...", ".#...", "..#..", "...#.", ".....",
249 ),
250 concat!(
252 ".#...", "..#..", "...#.", "...#.", "...#.", "..#..", ".#...", ".....",
253 ),
254 concat!(
256 ".....", "#.#.#", ".###.", "#####", ".###.", "#.#.#", ".....", ".....",
257 ),
258 concat!(
260 ".....", "..#..", "..#..", "#####", "..#..", "..#..", ".....", ".....",
261 ),
262 concat!(
264 ".....", ".....", ".....", ".....", ".....", "..##.", "..#..", ".#...",
265 ),
266 concat!(
268 ".....", ".....", ".....", ".###.", ".....", ".....", ".....", ".....",
269 ),
270 concat!(
272 ".....", ".....", ".....", ".....", ".....", ".##..", ".##..", ".....",
273 ),
274 concat!(
276 "....#", "....#", "...#.", "..#..", ".#...", "#....", "#....", ".....",
277 ),
278 concat!(
280 ".###.", "#...#", "#..##", "#.#.#", "##..#", "#...#", ".###.", ".....",
281 ),
282 concat!(
284 "..#..", ".##..", "..#..", "..#..", "..#..", "..#..", ".###.", ".....",
285 ),
286 concat!(
288 ".###.", "#...#", "....#", "...#.", "..#..", ".#...", "#####", ".....",
289 ),
290 concat!(
292 "#####", "...#.", "..#..", "...#.", "....#", "#...#", ".###.", ".....",
293 ),
294 concat!(
296 "...#.", "..##.", ".#.#.", "#..#.", "#####", "...#.", "...#.", ".....",
297 ),
298 concat!(
300 "#####", "#....", "####.", "....#", "....#", "#...#", ".###.", ".....",
301 ),
302 concat!(
304 "..##.", ".#...", "#....", "####.", "#...#", "#...#", ".###.", ".....",
305 ),
306 concat!(
308 "#####", "....#", "...#.", "..#..", ".#...", ".#...", ".#...", ".....",
309 ),
310 concat!(
312 ".###.", "#...#", "#...#", ".###.", "#...#", "#...#", ".###.", ".....",
313 ),
314 concat!(
316 ".###.", "#...#", "#...#", ".####", "....#", "...#.", ".##..", ".....",
317 ),
318 concat!(
320 ".....", ".##..", ".##..", ".....", ".##..", ".##..", ".....", ".....",
321 ),
322 concat!(
324 ".....", ".##..", ".##..", ".....", ".##..", "..#..", ".#...", ".....",
325 ),
326 concat!(
328 "...#.", "..#..", ".#...", "#....", ".#...", "..#..", "...#.", ".....",
329 ),
330 concat!(
332 ".....", ".....", "#####", ".....", "#####", ".....", ".....", ".....",
333 ),
334 concat!(
336 ".#...", "..#..", "...#.", "....#", "...#.", "..#..", ".#...", ".....",
337 ),
338 concat!(
340 ".###.", "#...#", "....#", "...#.", "..#..", ".....", "..#..", ".....",
341 ),
342 concat!(
344 ".###.", "#...#", "#.###", "#.#.#", "#.###", "#....", ".###.", ".....",
345 ),
346 concat!(
348 ".###.", "#...#", "#...#", "#####", "#...#", "#...#", "#...#", ".....",
349 ),
350 concat!(
352 "####.", "#...#", "#...#", "####.", "#...#", "#...#", "####.", ".....",
353 ),
354 concat!(
356 ".###.", "#...#", "#....", "#....", "#....", "#...#", ".###.", ".....",
357 ),
358 concat!(
360 "####.", "#...#", "#...#", "#...#", "#...#", "#...#", "####.", ".....",
361 ),
362 concat!(
364 "#####", "#....", "#....", "####.", "#....", "#....", "#####", ".....",
365 ),
366 concat!(
368 "#####", "#....", "#....", "####.", "#....", "#....", "#....", ".....",
369 ),
370 concat!(
372 ".###.", "#...#", "#....", "#.###", "#...#", "#...#", ".###.", ".....",
373 ),
374 concat!(
376 "#...#", "#...#", "#...#", "#####", "#...#", "#...#", "#...#", ".....",
377 ),
378 concat!(
380 ".###.", "..#..", "..#..", "..#..", "..#..", "..#..", ".###.", ".....",
381 ),
382 concat!(
384 "..###", "...#.", "...#.", "...#.", "...#.", "#..#.", ".##..", ".....",
385 ),
386 concat!(
388 "#...#", "#..#.", "#.#..", "##...", "#.#..", "#..#.", "#...#", ".....",
389 ),
390 concat!(
392 "#....", "#....", "#....", "#....", "#....", "#....", "#####", ".....",
393 ),
394 concat!(
396 "#...#", "##.##", "#.#.#", "#.#.#", "#...#", "#...#", "#...#", ".....",
397 ),
398 concat!(
400 "#...#", "##..#", "#.#.#", "#..##", "#...#", "#...#", "#...#", ".....",
401 ),
402 concat!(
404 ".###.", "#...#", "#...#", "#...#", "#...#", "#...#", ".###.", ".....",
405 ),
406 concat!(
408 "####.", "#...#", "#...#", "####.", "#....", "#....", "#....", ".....",
409 ),
410 concat!(
412 ".###.", "#...#", "#...#", "#...#", "#.#.#", "#..#.", ".##.#", ".....",
413 ),
414 concat!(
416 "####.", "#...#", "#...#", "####.", "#.#..", "#..#.", "#...#", ".....",
417 ),
418 concat!(
420 ".####", "#....", "#....", ".###.", "....#", "....#", "####.", ".....",
421 ),
422 concat!(
424 "#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#..", ".....",
425 ),
426 concat!(
428 "#...#", "#...#", "#...#", "#...#", "#...#", "#...#", ".###.", ".....",
429 ),
430 concat!(
432 "#...#", "#...#", "#...#", "#...#", "#...#", ".#.#.", "..#..", ".....",
433 ),
434 concat!(
436 "#...#", "#...#", "#...#", "#.#.#", "#.#.#", "##.##", "#...#", ".....",
437 ),
438 concat!(
440 "#...#", "#...#", ".#.#.", "..#..", ".#.#.", "#...#", "#...#", ".....",
441 ),
442 concat!(
444 "#...#", "#...#", ".#.#.", "..#..", "..#..", "..#..", "..#..", ".....",
445 ),
446 concat!(
448 "#####", "....#", "...#.", "..#..", ".#...", "#....", "#####", ".....",
449 ),
450 concat!(
452 ".###.", ".#...", ".#...", ".#...", ".#...", ".#...", ".###.", ".....",
453 ),
454 concat!(
456 "#....", "#....", ".#...", "..#..", "...#.", "....#", "....#", ".....",
457 ),
458 concat!(
460 ".###.", "...#.", "...#.", "...#.", "...#.", "...#.", ".###.", ".....",
461 ),
462 concat!(
464 "..#..", ".#.#.", "#...#", ".....", ".....", ".....", ".....", ".....",
465 ),
466 concat!(
468 ".....", ".....", ".....", ".....", ".....", ".....", ".....", "#####",
469 ),
470 concat!(
472 "..#..", "...#.", ".....", ".....", ".....", ".....", ".....", ".....",
473 ),
474 concat!(
476 ".....", ".....", ".###.", "....#", ".####", "#...#", ".####", ".....",
477 ),
478 concat!(
480 "#....", "#....", "####.", "#...#", "#...#", "#...#", "####.", ".....",
481 ),
482 concat!(
484 ".....", ".....", ".###.", "#....", "#....", "#....", ".###.", ".....",
485 ),
486 concat!(
488 "....#", "....#", ".####", "#...#", "#...#", "#...#", ".####", ".....",
489 ),
490 concat!(
492 ".....", ".....", ".###.", "#...#", "#####", "#....", ".###.", ".....",
493 ),
494 concat!(
496 "..##.", ".#...", ".#...", "####.", ".#...", ".#...", ".#...", ".....",
497 ),
498 concat!(
500 ".....", ".....", ".####", "#...#", "#...#", ".####", "....#", ".###.",
501 ),
502 concat!(
504 "#....", "#....", "####.", "#...#", "#...#", "#...#", "#...#", ".....",
505 ),
506 concat!(
508 "..#..", ".....", ".##..", "..#..", "..#..", "..#..", ".###.", ".....",
509 ),
510 concat!(
512 "...#.", ".....", "..##.", "...#.", "...#.", "...#.", "#..#.", ".##..",
513 ),
514 concat!(
516 "#....", "#....", "#..#.", "#.#..", "##...", "#.#..", "#..#.", ".....",
517 ),
518 concat!(
520 ".##..", "..#..", "..#..", "..#..", "..#..", "..#..", ".###.", ".....",
521 ),
522 concat!(
524 ".....", ".....", "##.#.", "#.#.#", "#.#.#", "#...#", "#...#", ".....",
525 ),
526 concat!(
528 ".....", ".....", "####.", "#...#", "#...#", "#...#", "#...#", ".....",
529 ),
530 concat!(
532 ".....", ".....", ".###.", "#...#", "#...#", "#...#", ".###.", ".....",
533 ),
534 concat!(
536 ".....", ".....", "####.", "#...#", "#...#", "####.", "#....", "#....",
537 ),
538 concat!(
540 ".....", ".....", ".####", "#...#", "#...#", ".####", "....#", "....#",
541 ),
542 concat!(
544 ".....", ".....", "#.##.", "##..#", "#....", "#....", "#....", ".....",
545 ),
546 concat!(
548 ".....", ".....", ".####", "#....", ".###.", "....#", "####.", ".....",
549 ),
550 concat!(
552 ".#...", ".#...", "####.", ".#...", ".#...", ".#..#", "..##.", ".....",
553 ),
554 concat!(
556 ".....", ".....", "#...#", "#...#", "#...#", "#..##", ".##.#", ".....",
557 ),
558 concat!(
560 ".....", ".....", "#...#", "#...#", "#...#", ".#.#.", "..#..", ".....",
561 ),
562 concat!(
564 ".....", ".....", "#...#", "#...#", "#.#.#", "#.#.#", ".#.#.", ".....",
565 ),
566 concat!(
568 ".....", ".....", "#...#", ".#.#.", "..#..", ".#.#.", "#...#", ".....",
569 ),
570 concat!(
572 ".....", ".....", "#...#", "#...#", "#...#", ".####", "....#", ".###.",
573 ),
574 concat!(
576 ".....", ".....", "#####", "...#.", "..#..", ".#...", "#####", ".....",
577 ),
578 concat!(
580 "...##", "..#..", "..#..", ".#...", "..#..", "..#..", "...##", ".....",
581 ),
582 concat!(
584 "..#..", "..#..", "..#..", "..#..", "..#..", "..#..", "..#..", ".....",
585 ),
586 concat!(
588 "##...", "..#..", "..#..", "...#.", "..#..", "..#..", "##...", ".....",
589 ),
590 concat!(
592 ".....", ".....", ".#..#", "#.#.#", "#..#.", ".....", ".....", ".....",
593 ),
594];
595
596const EXTRA_ART: [(char, &str); 23] = [
598 (
600 '\u{00ab}',
601 concat!(
602 ".....", ".....", "..#.#", ".#.#.", "#.#..", ".#.#.", "..#.#", ".....",
603 ),
604 ),
605 (
607 '\u{00b0}',
608 concat!(
609 ".##..", "#..#.", ".##..", ".....", ".....", ".....", ".....", ".....",
610 ),
611 ),
612 (
614 '\u{00b1}',
615 concat!(
616 "..#..", "..#..", "#####", "..#..", "..#..", ".....", "#####", ".....",
617 ),
618 ),
619 (
621 '\u{00b5}',
622 concat!(
623 ".....", ".....", "#...#", "#...#", "#...#", "#..##", "#.##.", "#....",
624 ),
625 ),
626 (
628 '\u{00bb}',
629 concat!(
630 ".....", ".....", "#.#..", ".#.#.", "..#.#", ".#.#.", "#.#..", ".....",
631 ),
632 ),
633 (
635 '\u{00c4}',
636 concat!(
637 ".#.#.", ".###.", "#...#", "#...#", "#####", "#...#", "#...#", ".....",
638 ),
639 ),
640 (
642 '\u{00c5}',
643 concat!(
644 "..#..", ".###.", "#...#", "#...#", "#####", "#...#", "#...#", ".....",
645 ),
646 ),
647 (
649 '\u{00c6}',
650 concat!(
651 ".####", "#.#..", "#.#..", "#####", "#.#..", "#.#..", "#.###", ".....",
652 ),
653 ),
654 (
656 '\u{00c9}',
657 concat!(
658 "...#.", "#####", "#....", "####.", "#....", "#....", "#####", ".....",
659 ),
660 ),
661 (
663 '\u{00d6}',
664 concat!(
665 ".#.#.", ".###.", "#...#", "#...#", "#...#", "#...#", ".###.", ".....",
666 ),
667 ),
668 (
670 '\u{00d7}',
671 concat!(
672 ".....", ".....", ".....", ".#.#.", "..#..", ".#.#.", ".....", ".....",
673 ),
674 ),
675 (
677 '\u{00d8}',
678 concat!(
679 ".####", "#..##", "#..##", "#.#.#", "##..#", "##..#", "####.", ".....",
680 ),
681 ),
682 (
684 '\u{00dc}',
685 concat!(
686 ".#.#.", "#...#", "#...#", "#...#", "#...#", "#...#", ".###.", ".....",
687 ),
688 ),
689 (
691 '\u{00df}',
692 concat!(
693 ".....", ".##..", "#..#.", "#.#..", "#..#.", "#..#.", "#.##.", ".....",
694 ),
695 ),
696 (
698 '\u{00e4}',
699 concat!(
700 ".#.#.", ".....", ".###.", "....#", ".####", "#...#", ".####", ".....",
701 ),
702 ),
703 (
705 '\u{00e5}',
706 concat!(
707 "..#..", ".....", ".###.", "....#", ".####", "#...#", ".####", ".....",
708 ),
709 ),
710 (
712 '\u{00e6}',
713 concat!(
714 ".....", ".....", "##.##", "..#.#", ".####", "#.#..", ".####", ".....",
715 ),
716 ),
717 (
719 '\u{00e9}',
720 concat!(
721 "...#.", ".....", ".###.", "#...#", "#####", "#....", ".###.", ".....",
722 ),
723 ),
724 (
726 '\u{00f6}',
727 concat!(
728 ".#.#.", ".....", ".###.", "#...#", "#...#", "#...#", ".###.", ".....",
729 ),
730 ),
731 (
733 '\u{00f8}',
734 concat!(
735 ".....", ".....", ".####", "#..##", "#.#.#", "##..#", "####.", ".....",
736 ),
737 ),
738 (
740 '\u{00fc}',
741 concat!(
742 ".#.#.", ".....", "#...#", "#...#", "#...#", "#..##", ".##.#", ".....",
743 ),
744 ),
745 (
747 '\u{2013}',
748 concat!(
749 ".....", ".....", ".....", "#####", ".....", ".....", ".....", ".....",
750 ),
751 ),
752 (
754 '\u{2014}',
755 concat!(
756 ".....", ".....", ".....", "#####", ".....", ".....", ".....", ".....",
757 ),
758 ),
759];
760
761const ASCII: [Glyph; 95] = {
762 let mut packed = [[0u8; CELL_HEIGHT as usize]; 95];
763 let mut i = 0;
764 while i < 95 {
765 packed[i] = pack(ASCII_ART[i]);
766 i += 1;
767 }
768 packed
769};
770
771const EXTRAS: [(char, Glyph); EXTRA_ART.len()] = {
772 let mut packed = [('\0', [0u8; CELL_HEIGHT as usize]); EXTRA_ART.len()];
773 let mut i = 0;
774 while i < EXTRA_ART.len() {
775 packed[i] = (EXTRA_ART[i].0, pack(EXTRA_ART[i].1));
776 assert!(
778 i == 0 || (EXTRA_ART[i - 1].0 as u32) < (EXTRA_ART[i].0 as u32),
779 "EXTRA_ART must be sorted by code point"
780 );
781 i += 1;
782 }
783 packed
784};
785
786#[cfg(test)]
787mod tests {
788 use super::*;
789 use crate::testing::TestCanvas;
790 use denise::Color;
791
792 const TIED: [(char, char); 1] = [('\u{2013}', '\u{2014}')];
796
797 #[test]
798 fn every_glyph_is_distinct() {
799 let mut seen: alloc::vec::Vec<(char, Glyph)> = alloc::vec::Vec::new();
803 let chars = (0x20u32..0x7F)
804 .filter_map(char::from_u32)
805 .chain(EXTRAS.iter().map(|&(c, _)| c));
806 for ch in chars {
807 let glyph = *BUILT_IN.glyph(ch);
808 if ch == ' ' {
809 assert_eq!(glyph, [0; 8], "space must be blank");
810 continue;
811 }
812 assert_ne!(glyph, [0; 8], "{ch:?} has no ink");
813 if let Some((other, _)) = seen.iter().find(|(_, g)| *g == glyph) {
814 assert!(
815 TIED.contains(&(*other, ch)),
816 "{ch:?} and {other:?} are the same picture"
817 );
818 }
819 seen.push((ch, glyph));
820 }
821 }
822
823 #[test]
824 fn nordic_letters_are_present_and_ascii_is_complete() {
825 for ch in "ÆØÅæøåÄÖÜäöüÉéß°".chars() {
826 assert!(BUILT_IN.contains(ch), "{ch:?} is missing");
827 }
828 for code in 0x20u32..0x7F {
829 let ch = char::from_u32(code).expect("ascii");
830 assert!(BUILT_IN.contains(ch), "{ch:?} is missing");
831 }
832 }
833
834 #[test]
835 fn an_unmapped_character_draws_a_visible_box() {
836 assert!(!BUILT_IN.contains('\u{4e2d}'));
837 assert_eq!(*BUILT_IN.glyph('\u{4e2d}'), BUILT_IN.fallback);
838 assert_ne!(BUILT_IN.fallback, [0; 8], "a missing glyph must be visible");
839 }
840
841 #[test]
842 fn extras_are_sorted_so_lookup_can_bisect() {
843 assert!(EXTRAS.windows(2).all(|w| w[0].0 < w[1].0));
844 }
845
846 #[test]
847 fn measurement_matches_what_is_drawn() {
848 let size = BUILT_IN.measure("Hi", 2);
849 assert_eq!(size, Size::new(22, 16));
851 assert_eq!(BUILT_IN.measure("", 1), Size::new(0, 8));
852 assert_eq!(BUILT_IN.measure("a\nbb", 1).height, LINE_HEIGHT as u32 + 8);
853 assert_eq!(BUILT_IN.measure("a\nbb", 1).width, 11);
854 }
855
856 #[test]
857 fn scale_is_clamped_to_at_least_one() {
858 assert_eq!(BUILT_IN.line_width("abc", 0), BUILT_IN.line_width("abc", 1));
859 assert_eq!(BUILT_IN.caret_offset(3, -4), BUILT_IN.caret_offset(3, 1));
860 }
861
862 #[test]
863 fn text_stays_inside_the_rectangle_it_measures() {
864 let mut t = TestCanvas::new(80, 40);
865 let text = "Wg|";
866 let scale = 2;
867 let extent = {
868 let mut c = t.canvas();
869 c.draw_text(&BUILT_IN, Point::new(4, 4), scale, text, Color::WHITE)
870 };
871 let bounds = Rect::new(4, 4, extent.width as i32, extent.height as i32);
872 for y in 0..40i32 {
873 for x in 0..80i32 {
874 if !bounds.contains(Point::new(x, y)) {
875 assert_eq!(
876 t.pixels()[(y * 80 + x) as usize],
877 0,
878 "ink outside the measured extent at {x},{y}"
879 );
880 }
881 }
882 }
883 }
884
885 #[test]
886 fn drawing_is_clipped_like_everything_else() {
887 let mut t = TestCanvas::new(64, 16);
888 {
889 let mut c = t.canvas();
890 let mut clipped = c.with_clip(Rect::new(0, 0, 12, 16));
891 clipped.draw_text(&BUILT_IN, Point::new(0, 0), 1, "MMMMMMMM", Color::WHITE);
892 }
893 for y in 0..16usize {
894 for x in 12..64usize {
895 assert_eq!(t.pixels()[y * 64 + x], 0, "drew past the clip at {x},{y}");
896 }
897 }
898 assert!(
899 t.pixels().iter().any(|&p| p != 0),
900 "nothing was drawn at all"
901 );
902 }
903
904 #[test]
905 fn a_newline_starts_a_second_line() {
906 let mut t = TestCanvas::new(40, 32);
907 {
908 let mut c = t.canvas();
909 c.draw_text(&BUILT_IN, Point::new(0, 0), 1, "A\nA", Color::WHITE);
910 }
911 let row_of = |y: usize| t.pixels()[y * 40..y * 40 + 5].to_vec();
912 assert_eq!(row_of(1), row_of(1 + LINE_HEIGHT as usize));
913 }
914}