1pub mod ranges {
11 pub const BOX_DRAWING_START: u32 = 0x2500;
13 pub const BOX_DRAWING_END: u32 = 0x257F;
14
15 pub const BLOCK_ELEMENTS_START: u32 = 0x2580;
17 pub const BLOCK_ELEMENTS_END: u32 = 0x259F;
18
19 pub const GEOMETRIC_SHAPES_START: u32 = 0x25A0;
21 pub const GEOMETRIC_SHAPES_END: u32 = 0x25FF;
22
23 pub const POWERLINE_START: u32 = 0xE0A0;
25 pub const POWERLINE_END: u32 = 0xE0D4;
26
27 pub const BRAILLE_START: u32 = 0x2800;
29 pub const BRAILLE_END: u32 = 0x28FF;
30
31 pub const MISC_SYMBOLS_START: u32 = 0x2600;
33 pub const MISC_SYMBOLS_END: u32 = 0x26FF;
34
35 pub const DINGBATS_START: u32 = 0x2700;
37 pub const DINGBATS_END: u32 = 0x27BF;
38}
39
40mod grid {
55 pub const A: f32 = 0.0; pub const C: f32 = 0.40; pub const D: f32 = 0.50; pub const E: f32 = 0.60; pub const G: f32 = 1.0; pub const V1: f32 = 0.0; pub const V3: f32 = 0.40; pub const V4: f32 = 0.50; pub const V5: f32 = 0.60; pub const V7: f32 = 1.0; pub const LIGHT_THICKNESS: f32 = 0.12;
71 pub const HEAVY_THICKNESS: f32 = 0.20;
72 pub const DOUBLE_THICKNESS: f32 = 0.08;
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum BlockCharType {
78 None,
80 BoxDrawing,
82 SolidBlock,
84 PartialBlock,
86 Shade,
88 Geometric,
90 Powerline,
92 Braille,
94 Symbol,
96}
97
98pub fn classify_char(ch: char) -> BlockCharType {
100 let code = ch as u32;
101
102 if (ranges::BOX_DRAWING_START..=ranges::BOX_DRAWING_END).contains(&code) {
104 return BlockCharType::BoxDrawing;
105 }
106
107 if (ranges::BLOCK_ELEMENTS_START..=ranges::BLOCK_ELEMENTS_END).contains(&code) {
109 return classify_block_element(ch);
110 }
111
112 if (ranges::GEOMETRIC_SHAPES_START..=ranges::GEOMETRIC_SHAPES_END).contains(&code) {
114 return BlockCharType::Geometric;
115 }
116
117 if (ranges::POWERLINE_START..=ranges::POWERLINE_END).contains(&code) {
119 return BlockCharType::Powerline;
120 }
121
122 if (ranges::BRAILLE_START..=ranges::BRAILLE_END).contains(&code) {
124 return BlockCharType::Braille;
125 }
126
127 if (ranges::MISC_SYMBOLS_START..=ranges::MISC_SYMBOLS_END).contains(&code) {
129 return BlockCharType::Symbol;
130 }
131
132 if (ranges::DINGBATS_START..=ranges::DINGBATS_END).contains(&code) {
134 return BlockCharType::Symbol;
135 }
136
137 BlockCharType::None
138}
139
140fn classify_block_element(ch: char) -> BlockCharType {
142 match ch {
143 '\u{2591}' | '\u{2592}' | '\u{2593}' => BlockCharType::Shade,
145
146 '\u{2588}' => BlockCharType::SolidBlock,
148
149 '\u{2580}'..='\u{2590}' | '\u{2594}'..='\u{259F}' => BlockCharType::PartialBlock,
151
152 _ => BlockCharType::PartialBlock,
153 }
154}
155
156pub fn should_snap_to_boundaries(char_type: BlockCharType) -> bool {
158 matches!(
159 char_type,
160 BlockCharType::BoxDrawing
161 | BlockCharType::SolidBlock
162 | BlockCharType::PartialBlock
163 | BlockCharType::Geometric
164 | BlockCharType::Powerline
165 | BlockCharType::Symbol
166 )
167}
168
169pub fn should_render_geometrically(char_type: BlockCharType) -> bool {
171 matches!(
172 char_type,
173 BlockCharType::SolidBlock
174 | BlockCharType::PartialBlock
175 | BlockCharType::BoxDrawing
176 | BlockCharType::Geometric
177 )
178}
179
180#[derive(Debug, Clone, Copy)]
183struct LineSegment {
184 x1: f32,
185 y1: f32,
186 x2: f32,
187 y2: f32,
188 thickness: f32,
189}
190
191impl LineSegment {
192 const fn new(x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32) -> Self {
193 Self {
194 x1,
195 y1,
196 x2,
197 y2,
198 thickness,
199 }
200 }
201
202 const fn horizontal(y: f32, x1: f32, x2: f32, thickness: f32) -> Self {
204 Self::new(x1, y, x2, y, thickness)
205 }
206
207 const fn vertical(x: f32, y1: f32, y2: f32, thickness: f32) -> Self {
209 Self::new(x, y1, x, y2, thickness)
210 }
211
212 fn to_block(self, aspect_ratio: f32) -> GeometricBlock {
214 let is_horizontal = (self.y1 - self.y2).abs() < 0.001;
215 let is_vertical = (self.x1 - self.x2).abs() < 0.001;
216
217 if is_horizontal {
218 let x = self.x1.min(self.x2);
220 let width = (self.x2 - self.x1).abs();
221 let height = self.thickness;
222 let y = self.y1 - height / 2.0;
223 GeometricBlock::new(x, y, width, height)
224 } else if is_vertical {
225 let y = self.y1.min(self.y2);
227 let height = (self.y2 - self.y1).abs();
228 let width = self.thickness * aspect_ratio;
229 let x = self.x1 - width / 2.0;
230 GeometricBlock::new(x, y, width, height)
231 } else {
232 let x = self.x1.min(self.x2);
234 let y = self.y1.min(self.y2);
235 let width = (self.x2 - self.x1).abs().max(self.thickness);
236 let height = (self.y2 - self.y1).abs().max(self.thickness);
237 GeometricBlock::new(x, y, width, height)
238 }
239 }
240}
241
242#[derive(Debug, Clone)]
244pub struct BoxDrawingGeometry {
245 pub segments: Vec<GeometricBlock>,
246}
247
248impl BoxDrawingGeometry {
249 fn from_lines(lines: &[LineSegment], aspect_ratio: f32) -> Self {
250 Self {
251 segments: lines.iter().map(|l| l.to_block(aspect_ratio)).collect(),
252 }
253 }
254}
255
256pub fn get_box_drawing_geometry(ch: char, aspect_ratio: f32) -> Option<BoxDrawingGeometry> {
260 use grid::*;
261
262 let lt = LIGHT_THICKNESS;
263 let ht = HEAVY_THICKNESS;
264 let dt = DOUBLE_THICKNESS;
265
266 let lines: &[LineSegment] = match ch {
267 '\u{2500}' => &[LineSegment::horizontal(V4, A, G, lt)],
273
274 '\u{2502}' => &[LineSegment::vertical(D, V1, V7, lt)],
276
277 '\u{250C}' => &[
279 LineSegment::horizontal(V4, D, G, lt),
280 LineSegment::vertical(D, V4, V7, lt),
281 ],
282
283 '\u{2510}' => &[
285 LineSegment::horizontal(V4, A, D, lt),
286 LineSegment::vertical(D, V4, V7, lt),
287 ],
288
289 '\u{2514}' => &[
291 LineSegment::horizontal(V4, D, G, lt),
292 LineSegment::vertical(D, V1, V4, lt),
293 ],
294
295 '\u{2518}' => &[
297 LineSegment::horizontal(V4, A, D, lt),
298 LineSegment::vertical(D, V1, V4, lt),
299 ],
300
301 '\u{251C}' => &[
303 LineSegment::vertical(D, V1, V7, lt),
304 LineSegment::horizontal(V4, D, G, lt),
305 ],
306
307 '\u{2524}' => &[
309 LineSegment::vertical(D, V1, V7, lt),
310 LineSegment::horizontal(V4, A, D, lt),
311 ],
312
313 '\u{252C}' => &[
315 LineSegment::horizontal(V4, A, G, lt),
316 LineSegment::vertical(D, V4, V7, lt),
317 ],
318
319 '\u{2534}' => &[
321 LineSegment::horizontal(V4, A, G, lt),
322 LineSegment::vertical(D, V1, V4, lt),
323 ],
324
325 '\u{253C}' => &[
327 LineSegment::horizontal(V4, A, G, lt),
328 LineSegment::vertical(D, V1, V7, lt),
329 ],
330
331 '\u{2501}' => &[LineSegment::horizontal(V4, A, G, ht)],
337
338 '\u{2503}' => &[LineSegment::vertical(D, V1, V7, ht)],
340
341 '\u{250F}' => &[
343 LineSegment::horizontal(V4, D, G, ht),
344 LineSegment::vertical(D, V4, V7, ht),
345 ],
346
347 '\u{2513}' => &[
349 LineSegment::horizontal(V4, A, D, ht),
350 LineSegment::vertical(D, V4, V7, ht),
351 ],
352
353 '\u{2517}' => &[
355 LineSegment::horizontal(V4, D, G, ht),
356 LineSegment::vertical(D, V1, V4, ht),
357 ],
358
359 '\u{251B}' => &[
361 LineSegment::horizontal(V4, A, D, ht),
362 LineSegment::vertical(D, V1, V4, ht),
363 ],
364
365 '\u{2523}' => &[
367 LineSegment::vertical(D, V1, V7, ht),
368 LineSegment::horizontal(V4, D, G, ht),
369 ],
370
371 '\u{252B}' => &[
373 LineSegment::vertical(D, V1, V7, ht),
374 LineSegment::horizontal(V4, A, D, ht),
375 ],
376
377 '\u{2533}' => &[
379 LineSegment::horizontal(V4, A, G, ht),
380 LineSegment::vertical(D, V4, V7, ht),
381 ],
382
383 '\u{253B}' => &[
385 LineSegment::horizontal(V4, A, G, ht),
386 LineSegment::vertical(D, V1, V4, ht),
387 ],
388
389 '\u{254B}' => &[
391 LineSegment::horizontal(V4, A, G, ht),
392 LineSegment::vertical(D, V1, V7, ht),
393 ],
394
395 '\u{250D}' => &[
401 LineSegment::horizontal(V4, D, G, ht),
402 LineSegment::vertical(D, V4, V7, lt),
403 ],
404
405 '\u{250E}' => &[
407 LineSegment::horizontal(V4, D, G, lt),
408 LineSegment::vertical(D, V4, V7, ht),
409 ],
410
411 '\u{2511}' => &[
413 LineSegment::horizontal(V4, A, D, ht),
414 LineSegment::vertical(D, V4, V7, lt),
415 ],
416
417 '\u{2512}' => &[
419 LineSegment::horizontal(V4, A, D, lt),
420 LineSegment::vertical(D, V4, V7, ht),
421 ],
422
423 '\u{2515}' => &[
425 LineSegment::horizontal(V4, D, G, ht),
426 LineSegment::vertical(D, V1, V4, lt),
427 ],
428
429 '\u{2516}' => &[
431 LineSegment::horizontal(V4, D, G, lt),
432 LineSegment::vertical(D, V1, V4, ht),
433 ],
434
435 '\u{2519}' => &[
437 LineSegment::horizontal(V4, A, D, ht),
438 LineSegment::vertical(D, V1, V4, lt),
439 ],
440
441 '\u{251A}' => &[
443 LineSegment::horizontal(V4, A, D, lt),
444 LineSegment::vertical(D, V1, V4, ht),
445 ],
446
447 '\u{251D}' => &[
449 LineSegment::vertical(D, V1, V7, lt),
450 LineSegment::horizontal(V4, D, G, ht),
451 ],
452
453 '\u{251E}' => &[
455 LineSegment::vertical(D, V1, V4, ht),
456 LineSegment::vertical(D, V4, V7, lt),
457 LineSegment::horizontal(V4, D, G, lt),
458 ],
459
460 '\u{251F}' => &[
462 LineSegment::vertical(D, V1, V4, lt),
463 LineSegment::vertical(D, V4, V7, ht),
464 LineSegment::horizontal(V4, D, G, lt),
465 ],
466
467 '\u{2520}' => &[
469 LineSegment::vertical(D, V1, V7, ht),
470 LineSegment::horizontal(V4, D, G, lt),
471 ],
472
473 '\u{2521}' => &[
475 LineSegment::vertical(D, V1, V4, ht),
476 LineSegment::vertical(D, V4, V7, lt),
477 LineSegment::horizontal(V4, D, G, ht),
478 ],
479
480 '\u{2522}' => &[
482 LineSegment::vertical(D, V1, V4, lt),
483 LineSegment::vertical(D, V4, V7, ht),
484 LineSegment::horizontal(V4, D, G, ht),
485 ],
486
487 '\u{2525}' => &[
489 LineSegment::vertical(D, V1, V7, lt),
490 LineSegment::horizontal(V4, A, D, ht),
491 ],
492
493 '\u{2526}' => &[
495 LineSegment::vertical(D, V1, V4, ht),
496 LineSegment::vertical(D, V4, V7, lt),
497 LineSegment::horizontal(V4, A, D, lt),
498 ],
499
500 '\u{2527}' => &[
502 LineSegment::vertical(D, V1, V4, lt),
503 LineSegment::vertical(D, V4, V7, ht),
504 LineSegment::horizontal(V4, A, D, lt),
505 ],
506
507 '\u{2528}' => &[
509 LineSegment::vertical(D, V1, V7, ht),
510 LineSegment::horizontal(V4, A, D, lt),
511 ],
512
513 '\u{2529}' => &[
515 LineSegment::vertical(D, V1, V4, ht),
516 LineSegment::vertical(D, V4, V7, lt),
517 LineSegment::horizontal(V4, A, D, ht),
518 ],
519
520 '\u{252A}' => &[
522 LineSegment::vertical(D, V1, V4, lt),
523 LineSegment::vertical(D, V4, V7, ht),
524 LineSegment::horizontal(V4, A, D, ht),
525 ],
526
527 '\u{252D}' => &[
529 LineSegment::horizontal(V4, A, D, ht),
530 LineSegment::horizontal(V4, D, G, lt),
531 LineSegment::vertical(D, V4, V7, lt),
532 ],
533
534 '\u{252E}' => &[
536 LineSegment::horizontal(V4, A, D, lt),
537 LineSegment::horizontal(V4, D, G, ht),
538 LineSegment::vertical(D, V4, V7, lt),
539 ],
540
541 '\u{252F}' => &[
543 LineSegment::horizontal(V4, A, G, ht),
544 LineSegment::vertical(D, V4, V7, lt),
545 ],
546
547 '\u{2530}' => &[
549 LineSegment::horizontal(V4, A, G, lt),
550 LineSegment::vertical(D, V4, V7, ht),
551 ],
552
553 '\u{2531}' => &[
555 LineSegment::horizontal(V4, A, D, ht),
556 LineSegment::horizontal(V4, D, G, lt),
557 LineSegment::vertical(D, V4, V7, ht),
558 ],
559
560 '\u{2532}' => &[
562 LineSegment::horizontal(V4, A, D, lt),
563 LineSegment::horizontal(V4, D, G, ht),
564 LineSegment::vertical(D, V4, V7, ht),
565 ],
566
567 '\u{2535}' => &[
569 LineSegment::horizontal(V4, A, D, ht),
570 LineSegment::horizontal(V4, D, G, lt),
571 LineSegment::vertical(D, V1, V4, lt),
572 ],
573
574 '\u{2536}' => &[
576 LineSegment::horizontal(V4, A, D, lt),
577 LineSegment::horizontal(V4, D, G, ht),
578 LineSegment::vertical(D, V1, V4, lt),
579 ],
580
581 '\u{2537}' => &[
583 LineSegment::horizontal(V4, A, G, ht),
584 LineSegment::vertical(D, V1, V4, lt),
585 ],
586
587 '\u{2538}' => &[
589 LineSegment::horizontal(V4, A, G, lt),
590 LineSegment::vertical(D, V1, V4, ht),
591 ],
592
593 '\u{2539}' => &[
595 LineSegment::horizontal(V4, A, D, ht),
596 LineSegment::horizontal(V4, D, G, lt),
597 LineSegment::vertical(D, V1, V4, ht),
598 ],
599
600 '\u{253A}' => &[
602 LineSegment::horizontal(V4, A, D, lt),
603 LineSegment::horizontal(V4, D, G, ht),
604 LineSegment::vertical(D, V1, V4, ht),
605 ],
606
607 '\u{253D}' => &[
609 LineSegment::horizontal(V4, A, D, ht),
610 LineSegment::horizontal(V4, D, G, lt),
611 LineSegment::vertical(D, V1, V7, lt),
612 ],
613
614 '\u{253E}' => &[
616 LineSegment::horizontal(V4, A, D, lt),
617 LineSegment::horizontal(V4, D, G, ht),
618 LineSegment::vertical(D, V1, V7, lt),
619 ],
620
621 '\u{253F}' => &[
623 LineSegment::horizontal(V4, A, G, ht),
624 LineSegment::vertical(D, V1, V7, lt),
625 ],
626
627 '\u{2540}' => &[
629 LineSegment::horizontal(V4, A, G, lt),
630 LineSegment::vertical(D, V1, V4, ht),
631 LineSegment::vertical(D, V4, V7, lt),
632 ],
633
634 '\u{2541}' => &[
636 LineSegment::horizontal(V4, A, G, lt),
637 LineSegment::vertical(D, V1, V4, lt),
638 LineSegment::vertical(D, V4, V7, ht),
639 ],
640
641 '\u{2542}' => &[
643 LineSegment::horizontal(V4, A, G, lt),
644 LineSegment::vertical(D, V1, V7, ht),
645 ],
646
647 '\u{2543}' => &[
649 LineSegment::horizontal(V4, A, D, ht),
650 LineSegment::horizontal(V4, D, G, lt),
651 LineSegment::vertical(D, V1, V4, ht),
652 LineSegment::vertical(D, V4, V7, lt),
653 ],
654
655 '\u{2544}' => &[
657 LineSegment::horizontal(V4, A, D, lt),
658 LineSegment::horizontal(V4, D, G, ht),
659 LineSegment::vertical(D, V1, V4, ht),
660 LineSegment::vertical(D, V4, V7, lt),
661 ],
662
663 '\u{2545}' => &[
665 LineSegment::horizontal(V4, A, D, ht),
666 LineSegment::horizontal(V4, D, G, lt),
667 LineSegment::vertical(D, V1, V4, lt),
668 LineSegment::vertical(D, V4, V7, ht),
669 ],
670
671 '\u{2546}' => &[
673 LineSegment::horizontal(V4, A, D, lt),
674 LineSegment::horizontal(V4, D, G, ht),
675 LineSegment::vertical(D, V1, V4, lt),
676 LineSegment::vertical(D, V4, V7, ht),
677 ],
678
679 '\u{2547}' => &[
681 LineSegment::horizontal(V4, A, G, ht),
682 LineSegment::vertical(D, V1, V4, ht),
683 LineSegment::vertical(D, V4, V7, lt),
684 ],
685
686 '\u{2548}' => &[
688 LineSegment::horizontal(V4, A, G, ht),
689 LineSegment::vertical(D, V1, V4, lt),
690 LineSegment::vertical(D, V4, V7, ht),
691 ],
692
693 '\u{2549}' => &[
695 LineSegment::horizontal(V4, A, D, ht),
696 LineSegment::horizontal(V4, D, G, lt),
697 LineSegment::vertical(D, V1, V7, ht),
698 ],
699
700 '\u{254A}' => &[
702 LineSegment::horizontal(V4, A, D, lt),
703 LineSegment::horizontal(V4, D, G, ht),
704 LineSegment::vertical(D, V1, V7, ht),
705 ],
706
707 '\u{2550}' => &[
713 LineSegment::horizontal(V3, A, G, dt),
714 LineSegment::horizontal(V5, A, G, dt),
715 ],
716
717 '\u{2551}' => &[
719 LineSegment::vertical(C, V1, V7, dt),
720 LineSegment::vertical(E, V1, V7, dt),
721 ],
722
723 '\u{2554}' => &[
725 LineSegment::horizontal(V3, E, G, dt),
726 LineSegment::horizontal(V5, C, G, dt),
727 LineSegment::vertical(C, V3, V7, dt),
728 LineSegment::vertical(E, V5, V7, dt),
729 ],
730
731 '\u{2557}' => &[
733 LineSegment::horizontal(V3, A, C, dt),
734 LineSegment::horizontal(V5, A, E, dt),
735 LineSegment::vertical(C, V5, V7, dt),
736 LineSegment::vertical(E, V3, V7, dt),
737 ],
738
739 '\u{255A}' => &[
741 LineSegment::horizontal(V3, C, G, dt),
742 LineSegment::horizontal(V5, E, G, dt),
743 LineSegment::vertical(C, V1, V3, dt),
744 LineSegment::vertical(E, V1, V5, dt),
745 ],
746
747 '\u{255D}' => &[
749 LineSegment::horizontal(V3, A, E, dt),
750 LineSegment::horizontal(V5, A, C, dt),
751 LineSegment::vertical(C, V1, V5, dt),
752 LineSegment::vertical(E, V1, V3, dt),
753 ],
754
755 '\u{2560}' => &[
757 LineSegment::vertical(C, V1, V7, dt),
758 LineSegment::vertical(E, V1, V3, dt),
759 LineSegment::vertical(E, V5, V7, dt),
760 LineSegment::horizontal(V3, E, G, dt),
761 LineSegment::horizontal(V5, E, G, dt),
762 ],
763
764 '\u{2563}' => &[
766 LineSegment::vertical(E, V1, V7, dt),
767 LineSegment::vertical(C, V1, V3, dt),
768 LineSegment::vertical(C, V5, V7, dt),
769 LineSegment::horizontal(V3, A, C, dt),
770 LineSegment::horizontal(V5, A, C, dt),
771 ],
772
773 '\u{2566}' => &[
775 LineSegment::horizontal(V3, A, G, dt),
776 LineSegment::horizontal(V5, A, C, dt),
777 LineSegment::horizontal(V5, E, G, dt),
778 LineSegment::vertical(C, V5, V7, dt),
779 LineSegment::vertical(E, V5, V7, dt),
780 ],
781
782 '\u{2569}' => &[
784 LineSegment::horizontal(V5, A, G, dt),
785 LineSegment::horizontal(V3, A, C, dt),
786 LineSegment::horizontal(V3, E, G, dt),
787 LineSegment::vertical(C, V1, V3, dt),
788 LineSegment::vertical(E, V1, V3, dt),
789 ],
790
791 '\u{256C}' => &[
793 LineSegment::horizontal(V3, A, C, dt),
794 LineSegment::horizontal(V3, E, G, dt),
795 LineSegment::horizontal(V5, A, C, dt),
796 LineSegment::horizontal(V5, E, G, dt),
797 LineSegment::vertical(C, V1, V3, dt),
798 LineSegment::vertical(C, V5, V7, dt),
799 LineSegment::vertical(E, V1, V3, dt),
800 LineSegment::vertical(E, V5, V7, dt),
801 ],
802
803 '\u{2552}' => &[
809 LineSegment::horizontal(V3, D, G, dt),
810 LineSegment::horizontal(V5, D, G, dt),
811 LineSegment::vertical(D, V4, V7, lt),
812 ],
813
814 '\u{2553}' => &[
816 LineSegment::horizontal(V4, D, G, lt),
817 LineSegment::vertical(C, V4, V7, dt),
818 LineSegment::vertical(E, V4, V7, dt),
819 ],
820
821 '\u{2555}' => &[
823 LineSegment::horizontal(V3, A, D, dt),
824 LineSegment::horizontal(V5, A, D, dt),
825 LineSegment::vertical(D, V4, V7, lt),
826 ],
827
828 '\u{2556}' => &[
830 LineSegment::horizontal(V4, A, D, lt),
831 LineSegment::vertical(C, V4, V7, dt),
832 LineSegment::vertical(E, V4, V7, dt),
833 ],
834
835 '\u{2558}' => &[
837 LineSegment::horizontal(V3, D, G, dt),
838 LineSegment::horizontal(V5, D, G, dt),
839 LineSegment::vertical(D, V1, V4, lt),
840 ],
841
842 '\u{2559}' => &[
844 LineSegment::horizontal(V4, D, G, lt),
845 LineSegment::vertical(C, V1, V4, dt),
846 LineSegment::vertical(E, V1, V4, dt),
847 ],
848
849 '\u{255B}' => &[
851 LineSegment::horizontal(V3, A, D, dt),
852 LineSegment::horizontal(V5, A, D, dt),
853 LineSegment::vertical(D, V1, V4, lt),
854 ],
855
856 '\u{255C}' => &[
858 LineSegment::horizontal(V4, A, D, lt),
859 LineSegment::vertical(C, V1, V4, dt),
860 LineSegment::vertical(E, V1, V4, dt),
861 ],
862
863 '\u{255E}' => &[
865 LineSegment::vertical(D, V1, V7, lt),
866 LineSegment::horizontal(V3, D, G, dt),
867 LineSegment::horizontal(V5, D, G, dt),
868 ],
869
870 '\u{255F}' => &[
872 LineSegment::vertical(C, V1, V7, dt),
873 LineSegment::vertical(E, V1, V7, dt),
874 LineSegment::horizontal(V4, E, G, lt),
875 ],
876
877 '\u{2561}' => &[
879 LineSegment::vertical(D, V1, V7, lt),
880 LineSegment::horizontal(V3, A, D, dt),
881 LineSegment::horizontal(V5, A, D, dt),
882 ],
883
884 '\u{2562}' => &[
886 LineSegment::vertical(C, V1, V7, dt),
887 LineSegment::vertical(E, V1, V7, dt),
888 LineSegment::horizontal(V4, A, C, lt),
889 ],
890
891 '\u{2564}' => &[
893 LineSegment::horizontal(V3, A, G, dt),
894 LineSegment::horizontal(V5, A, G, dt),
895 LineSegment::vertical(D, V5, V7, lt),
896 ],
897
898 '\u{2565}' => &[
900 LineSegment::horizontal(V4, A, G, lt),
901 LineSegment::vertical(C, V4, V7, dt),
902 LineSegment::vertical(E, V4, V7, dt),
903 ],
904
905 '\u{2567}' => &[
907 LineSegment::horizontal(V3, A, G, dt),
908 LineSegment::horizontal(V5, A, G, dt),
909 LineSegment::vertical(D, V1, V3, lt),
910 ],
911
912 '\u{2568}' => &[
914 LineSegment::horizontal(V4, A, G, lt),
915 LineSegment::vertical(C, V1, V4, dt),
916 LineSegment::vertical(E, V1, V4, dt),
917 ],
918
919 '\u{256A}' => &[
921 LineSegment::horizontal(V3, A, G, dt),
922 LineSegment::horizontal(V5, A, G, dt),
923 LineSegment::vertical(D, V1, V7, lt),
924 ],
925
926 '\u{256B}' => &[
928 LineSegment::vertical(C, V1, V7, dt),
929 LineSegment::vertical(E, V1, V7, dt),
930 LineSegment::horizontal(V4, A, G, lt),
931 ],
932
933 '\u{2504}' => &[LineSegment::horizontal(V4, A, G, lt)],
939
940 '\u{2505}' => &[LineSegment::horizontal(V4, A, G, ht)],
942
943 '\u{2506}' => &[LineSegment::vertical(D, V1, V7, lt)],
945
946 '\u{2507}' => &[LineSegment::vertical(D, V1, V7, ht)],
948
949 '\u{2508}' => &[LineSegment::horizontal(V4, A, G, lt)],
951
952 '\u{2509}' => &[LineSegment::horizontal(V4, A, G, ht)],
954
955 '\u{250A}' => &[LineSegment::vertical(D, V1, V7, lt)],
957
958 '\u{250B}' => &[LineSegment::vertical(D, V1, V7, ht)],
960
961 '\u{256D}' => &[
967 LineSegment::horizontal(V4, D, G, lt),
968 LineSegment::vertical(D, V4, V7, lt),
969 ],
970
971 '\u{256E}' => &[
973 LineSegment::horizontal(V4, A, D, lt),
974 LineSegment::vertical(D, V4, V7, lt),
975 ],
976
977 '\u{256F}' => &[
979 LineSegment::horizontal(V4, A, D, lt),
980 LineSegment::vertical(D, V1, V4, lt),
981 ],
982
983 '\u{2570}' => &[
985 LineSegment::horizontal(V4, D, G, lt),
986 LineSegment::vertical(D, V1, V4, lt),
987 ],
988
989 '\u{2571}' => &[LineSegment::new(G, V1, A, V7, lt)],
995
996 '\u{2572}' => &[LineSegment::new(A, V1, G, V7, lt)],
998
999 '\u{2573}' => &[
1001 LineSegment::new(A, V1, G, V7, lt),
1002 LineSegment::new(G, V1, A, V7, lt),
1003 ],
1004
1005 '\u{2574}' => &[LineSegment::horizontal(V4, A, D, lt)],
1011
1012 '\u{2575}' => &[LineSegment::vertical(D, V1, V4, lt)],
1014
1015 '\u{2576}' => &[LineSegment::horizontal(V4, D, G, lt)],
1017
1018 '\u{2577}' => &[LineSegment::vertical(D, V4, V7, lt)],
1020
1021 '\u{2578}' => &[LineSegment::horizontal(V4, A, D, ht)],
1023
1024 '\u{2579}' => &[LineSegment::vertical(D, V1, V4, ht)],
1026
1027 '\u{257A}' => &[LineSegment::horizontal(V4, D, G, ht)],
1029
1030 '\u{257B}' => &[LineSegment::vertical(D, V4, V7, ht)],
1032
1033 '\u{257C}' => &[
1035 LineSegment::horizontal(V4, A, D, lt),
1036 LineSegment::horizontal(V4, D, G, ht),
1037 ],
1038
1039 '\u{257D}' => &[
1041 LineSegment::vertical(D, V1, V4, lt),
1042 LineSegment::vertical(D, V4, V7, ht),
1043 ],
1044
1045 '\u{257E}' => &[
1047 LineSegment::horizontal(V4, A, D, ht),
1048 LineSegment::horizontal(V4, D, G, lt),
1049 ],
1050
1051 '\u{257F}' => &[
1053 LineSegment::vertical(D, V1, V4, ht),
1054 LineSegment::vertical(D, V4, V7, lt),
1055 ],
1056
1057 _ => return None,
1058 };
1059
1060 if lines.is_empty() {
1061 None
1062 } else {
1063 Some(BoxDrawingGeometry::from_lines(lines, aspect_ratio))
1064 }
1065}
1066
1067#[derive(Debug, Clone, Copy)]
1069pub struct GeometricBlock {
1070 pub x: f32,
1072 pub y: f32,
1074 pub width: f32,
1076 pub height: f32,
1078}
1079
1080impl GeometricBlock {
1081 pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
1082 Self {
1083 x,
1084 y,
1085 width,
1086 height,
1087 }
1088 }
1089
1090 pub const fn full() -> Self {
1092 Self::new(0.0, 0.0, 1.0, 1.0)
1093 }
1094
1095 pub fn to_pixel_rect(self, cell_x: f32, cell_y: f32, cell_w: f32, cell_h: f32) -> PixelRect {
1097 PixelRect {
1098 x: cell_x + self.x * cell_w,
1099 y: cell_y + self.y * cell_h,
1100 width: self.width * cell_w,
1101 height: self.height * cell_h,
1102 }
1103 }
1104}
1105
1106#[derive(Debug, Clone, Copy)]
1108pub struct PixelRect {
1109 pub x: f32,
1110 pub y: f32,
1111 pub width: f32,
1112 pub height: f32,
1113}
1114
1115pub fn get_geometric_block(ch: char) -> Option<GeometricBlock> {
1118 match ch {
1119 '\u{2588}' => Some(GeometricBlock::full()),
1121
1122 '\u{2580}' => Some(GeometricBlock::new(0.0, 0.0, 1.0, 0.5)),
1124
1125 '\u{2581}' => Some(GeometricBlock::new(0.0, 0.875, 1.0, 0.125)),
1127 '\u{2582}' => Some(GeometricBlock::new(0.0, 0.75, 1.0, 0.25)),
1128 '\u{2583}' => Some(GeometricBlock::new(0.0, 0.625, 1.0, 0.375)),
1129 '\u{2584}' => Some(GeometricBlock::new(0.0, 0.5, 1.0, 0.5)), '\u{2585}' => Some(GeometricBlock::new(0.0, 0.375, 1.0, 0.625)),
1131 '\u{2586}' => Some(GeometricBlock::new(0.0, 0.25, 1.0, 0.75)),
1132 '\u{2587}' => Some(GeometricBlock::new(0.0, 0.125, 1.0, 0.875)),
1133
1134 '\u{2589}' => Some(GeometricBlock::new(0.0, 0.0, 0.875, 1.0)),
1136 '\u{258A}' => Some(GeometricBlock::new(0.0, 0.0, 0.75, 1.0)),
1137 '\u{258B}' => Some(GeometricBlock::new(0.0, 0.0, 0.625, 1.0)),
1138 '\u{258C}' => Some(GeometricBlock::new(0.0, 0.0, 0.5, 1.0)), '\u{258D}' => Some(GeometricBlock::new(0.0, 0.0, 0.375, 1.0)),
1140 '\u{258E}' => Some(GeometricBlock::new(0.0, 0.0, 0.25, 1.0)),
1141 '\u{258F}' => Some(GeometricBlock::new(0.0, 0.0, 0.125, 1.0)),
1142
1143 '\u{2590}' => Some(GeometricBlock::new(0.5, 0.0, 0.5, 1.0)),
1145
1146 '\u{2594}' => Some(GeometricBlock::new(0.0, 0.0, 1.0, 0.125)),
1148
1149 '\u{2595}' => Some(GeometricBlock::new(0.875, 0.0, 0.125, 1.0)),
1151
1152 '\u{2596}' => Some(GeometricBlock::new(0.0, 0.5, 0.5, 0.5)), '\u{2597}' => Some(GeometricBlock::new(0.5, 0.5, 0.5, 0.5)), '\u{2598}' => Some(GeometricBlock::new(0.0, 0.0, 0.5, 0.5)), '\u{259D}' => Some(GeometricBlock::new(0.5, 0.0, 0.5, 0.5)), '\u{2599}'..='\u{259C}' | '\u{259E}' | '\u{259F}' => None,
1161
1162 _ => None,
1163 }
1164}
1165
1166pub fn get_geometric_shape_rect(
1174 ch: char,
1175 cell_x: f32,
1176 cell_y: f32,
1177 cell_w: f32,
1178 cell_h: f32,
1179) -> Option<PixelRect> {
1180 match ch {
1181 '\u{25A0}' => {
1183 let size = cell_w;
1184 Some(PixelRect {
1185 x: cell_x,
1186 y: cell_y + (cell_h - size) / 2.0,
1187 width: size,
1188 height: size,
1189 })
1190 }
1191 '\u{25AA}' => {
1193 let size = cell_w * 0.5;
1194 Some(PixelRect {
1195 x: cell_x + (cell_w - size) / 2.0,
1196 y: cell_y + (cell_h - size) / 2.0,
1197 width: size,
1198 height: size,
1199 })
1200 }
1201 '\u{25AC}' => {
1203 let h = cell_h * 0.33;
1204 Some(PixelRect {
1205 x: cell_x,
1206 y: cell_y + (cell_h - h) / 2.0,
1207 width: cell_w,
1208 height: h,
1209 })
1210 }
1211 '\u{25AE}' => {
1213 let w = cell_w * 0.5;
1214 Some(PixelRect {
1215 x: cell_x + (cell_w - w) / 2.0,
1216 y: cell_y,
1217 width: w,
1218 height: cell_h,
1219 })
1220 }
1221 '\u{25FC}' => {
1223 let size = cell_w * 0.75;
1224 Some(PixelRect {
1225 x: cell_x + (cell_w - size) / 2.0,
1226 y: cell_y + (cell_h - size) / 2.0,
1227 width: size,
1228 height: size,
1229 })
1230 }
1231 '\u{25FE}' => {
1233 let size = cell_w * 0.625;
1234 Some(PixelRect {
1235 x: cell_x + (cell_w - size) / 2.0,
1236 y: cell_y + (cell_h - size) / 2.0,
1237 width: size,
1238 height: size,
1239 })
1240 }
1241 _ => None,
1242 }
1243}
1244
1245#[allow(clippy::too_many_arguments)]
1265pub fn snap_glyph_to_cell(
1266 glyph_left: f32,
1267 glyph_top: f32,
1268 render_w: f32,
1269 render_h: f32,
1270 cell_x0: f32,
1271 cell_y0: f32,
1272 cell_x1: f32,
1273 cell_y1: f32,
1274 snap_threshold: f32,
1275 extension: f32,
1276) -> (f32, f32, f32, f32) {
1277 let mut new_left = glyph_left;
1278 let mut new_top = glyph_top;
1279 let mut new_w = render_w;
1280 let mut new_h = render_h;
1281
1282 let glyph_right = glyph_left + render_w;
1283 let glyph_bottom = glyph_top + render_h;
1284
1285 if (glyph_left - cell_x0).abs() < snap_threshold {
1287 new_left = cell_x0 - extension;
1288 new_w = glyph_right - new_left;
1289 }
1290
1291 if (glyph_right - cell_x1).abs() < snap_threshold {
1293 new_w = cell_x1 + extension - new_left;
1294 }
1295
1296 if (glyph_top - cell_y0).abs() < snap_threshold {
1298 new_top = cell_y0 - extension;
1299 new_h = glyph_bottom - new_top;
1300 }
1301
1302 if (glyph_bottom - cell_y1).abs() < snap_threshold {
1304 new_h = cell_y1 + extension - new_top;
1305 }
1306
1307 let cell_cx = (cell_x0 + cell_x1) / 2.0;
1309 let cell_cy = (cell_y0 + cell_y1) / 2.0;
1310
1311 if (glyph_bottom - cell_cy).abs() < snap_threshold {
1313 new_h = cell_cy - new_top;
1314 } else if (glyph_top - cell_cy).abs() < snap_threshold {
1315 let bottom = new_top + new_h;
1316 new_top = cell_cy;
1317 new_h = bottom - new_top;
1318 }
1319
1320 if (glyph_right - cell_cx).abs() < snap_threshold {
1322 new_w = cell_cx - new_left;
1323 } else if (glyph_left - cell_cx).abs() < snap_threshold {
1324 let right = new_left + new_w;
1325 new_left = cell_cx;
1326 new_w = right - new_left;
1327 }
1328
1329 (new_left, new_top, new_w, new_h)
1330}
1331
1332#[cfg(test)]
1333mod tests {
1334 use super::*;
1335
1336 #[test]
1337 fn test_classify_box_drawing() {
1338 assert_eq!(classify_char('─'), BlockCharType::BoxDrawing);
1340 assert_eq!(classify_char('━'), BlockCharType::BoxDrawing);
1341 assert_eq!(classify_char('═'), BlockCharType::BoxDrawing);
1342
1343 assert_eq!(classify_char('│'), BlockCharType::BoxDrawing);
1345 assert_eq!(classify_char('┃'), BlockCharType::BoxDrawing);
1346 assert_eq!(classify_char('║'), BlockCharType::BoxDrawing);
1347
1348 assert_eq!(classify_char('┌'), BlockCharType::BoxDrawing);
1350 assert_eq!(classify_char('┐'), BlockCharType::BoxDrawing);
1351 assert_eq!(classify_char('└'), BlockCharType::BoxDrawing);
1352 assert_eq!(classify_char('┘'), BlockCharType::BoxDrawing);
1353
1354 assert_eq!(classify_char('╔'), BlockCharType::BoxDrawing);
1356 assert_eq!(classify_char('╗'), BlockCharType::BoxDrawing);
1357 assert_eq!(classify_char('╚'), BlockCharType::BoxDrawing);
1358 assert_eq!(classify_char('╝'), BlockCharType::BoxDrawing);
1359 }
1360
1361 #[test]
1362 fn test_classify_block_elements() {
1363 assert_eq!(classify_char('█'), BlockCharType::SolidBlock);
1365
1366 assert_eq!(classify_char('▀'), BlockCharType::PartialBlock);
1368 assert_eq!(classify_char('▄'), BlockCharType::PartialBlock);
1369 assert_eq!(classify_char('▌'), BlockCharType::PartialBlock);
1370 assert_eq!(classify_char('▐'), BlockCharType::PartialBlock);
1371
1372 assert_eq!(classify_char('░'), BlockCharType::Shade);
1374 assert_eq!(classify_char('▒'), BlockCharType::Shade);
1375 assert_eq!(classify_char('▓'), BlockCharType::Shade);
1376
1377 assert_eq!(classify_char('▖'), BlockCharType::PartialBlock);
1379 assert_eq!(classify_char('▗'), BlockCharType::PartialBlock);
1380 assert_eq!(classify_char('▘'), BlockCharType::PartialBlock);
1381 assert_eq!(classify_char('▝'), BlockCharType::PartialBlock);
1382 }
1383
1384 #[test]
1385 fn test_classify_geometric_shapes() {
1386 assert_eq!(classify_char('■'), BlockCharType::Geometric);
1387 assert_eq!(classify_char('□'), BlockCharType::Geometric);
1388 assert_eq!(classify_char('▪'), BlockCharType::Geometric);
1389 assert_eq!(classify_char('▫'), BlockCharType::Geometric);
1390 }
1391
1392 #[test]
1393 fn test_classify_regular_chars() {
1394 assert_eq!(classify_char('a'), BlockCharType::None);
1395 assert_eq!(classify_char('Z'), BlockCharType::None);
1396 assert_eq!(classify_char('0'), BlockCharType::None);
1397 assert_eq!(classify_char(' '), BlockCharType::None);
1398 assert_eq!(classify_char('!'), BlockCharType::None);
1399 }
1400
1401 #[test]
1402 fn test_should_snap_to_boundaries() {
1403 assert!(should_snap_to_boundaries(BlockCharType::BoxDrawing));
1404 assert!(should_snap_to_boundaries(BlockCharType::SolidBlock));
1405 assert!(should_snap_to_boundaries(BlockCharType::PartialBlock));
1406 assert!(should_snap_to_boundaries(BlockCharType::Geometric));
1407 assert!(should_snap_to_boundaries(BlockCharType::Powerline));
1408
1409 assert!(!should_snap_to_boundaries(BlockCharType::None));
1410 assert!(!should_snap_to_boundaries(BlockCharType::Shade));
1411 assert!(!should_snap_to_boundaries(BlockCharType::Braille));
1412 }
1413
1414 #[test]
1415 fn test_should_render_geometrically() {
1416 assert!(should_render_geometrically(BlockCharType::SolidBlock));
1417 assert!(should_render_geometrically(BlockCharType::PartialBlock));
1418 assert!(should_render_geometrically(BlockCharType::BoxDrawing));
1419 assert!(should_render_geometrically(BlockCharType::Geometric));
1420
1421 assert!(!should_render_geometrically(BlockCharType::None));
1422 assert!(!should_render_geometrically(BlockCharType::Shade));
1423 assert!(!should_render_geometrically(BlockCharType::Powerline));
1424 assert!(!should_render_geometrically(BlockCharType::Braille));
1425 }
1426
1427 #[test]
1428 fn test_geometric_block_full() {
1429 let block = get_geometric_block('█').unwrap();
1430 assert_eq!(block.x, 0.0);
1431 assert_eq!(block.y, 0.0);
1432 assert_eq!(block.width, 1.0);
1433 assert_eq!(block.height, 1.0);
1434 }
1435
1436 #[test]
1437 fn test_geometric_block_halves() {
1438 let block = get_geometric_block('▀').unwrap();
1440 assert_eq!(block.x, 0.0);
1441 assert_eq!(block.y, 0.0);
1442 assert_eq!(block.width, 1.0);
1443 assert_eq!(block.height, 0.5);
1444
1445 let block = get_geometric_block('▄').unwrap();
1447 assert_eq!(block.x, 0.0);
1448 assert_eq!(block.y, 0.5);
1449 assert_eq!(block.width, 1.0);
1450 assert_eq!(block.height, 0.5);
1451
1452 let block = get_geometric_block('▌').unwrap();
1454 assert_eq!(block.x, 0.0);
1455 assert_eq!(block.y, 0.0);
1456 assert_eq!(block.width, 0.5);
1457 assert_eq!(block.height, 1.0);
1458
1459 let block = get_geometric_block('▐').unwrap();
1461 assert_eq!(block.x, 0.5);
1462 assert_eq!(block.y, 0.0);
1463 assert_eq!(block.width, 0.5);
1464 assert_eq!(block.height, 1.0);
1465 }
1466
1467 #[test]
1468 fn test_geometric_block_quadrants() {
1469 let block = get_geometric_block('▖').unwrap();
1471 assert_eq!(block.x, 0.0);
1472 assert_eq!(block.y, 0.5);
1473 assert_eq!(block.width, 0.5);
1474 assert_eq!(block.height, 0.5);
1475
1476 let block = get_geometric_block('▗').unwrap();
1478 assert_eq!(block.x, 0.5);
1479 assert_eq!(block.y, 0.5);
1480 assert_eq!(block.width, 0.5);
1481 assert_eq!(block.height, 0.5);
1482
1483 let block = get_geometric_block('▘').unwrap();
1485 assert_eq!(block.x, 0.0);
1486 assert_eq!(block.y, 0.0);
1487 assert_eq!(block.width, 0.5);
1488 assert_eq!(block.height, 0.5);
1489
1490 let block = get_geometric_block('▝').unwrap();
1492 assert_eq!(block.x, 0.5);
1493 assert_eq!(block.y, 0.0);
1494 assert_eq!(block.width, 0.5);
1495 assert_eq!(block.height, 0.5);
1496 }
1497
1498 #[test]
1499 fn test_geometric_block_to_pixel_rect() {
1500 let block = GeometricBlock::new(0.0, 0.5, 1.0, 0.5); let rect = block.to_pixel_rect(10.0, 20.0, 8.0, 16.0);
1502
1503 assert_eq!(rect.x, 10.0);
1504 assert_eq!(rect.y, 28.0); assert_eq!(rect.width, 8.0);
1506 assert_eq!(rect.height, 8.0);
1507 }
1508
1509 #[test]
1510 fn test_box_drawing_light_horizontal() {
1511 let geo = get_box_drawing_geometry('─', 2.0).unwrap();
1512 assert_eq!(geo.segments.len(), 1);
1513 let seg = &geo.segments[0];
1514 assert_eq!(seg.x, 0.0);
1515 assert!(seg.width > 0.99); }
1517
1518 #[test]
1519 fn test_box_drawing_light_corner() {
1520 let geo = get_box_drawing_geometry('┌', 2.0).unwrap();
1521 assert_eq!(geo.segments.len(), 2);
1522 }
1524
1525 #[test]
1526 fn test_box_drawing_double_lines() {
1527 let geo = get_box_drawing_geometry('═', 2.0).unwrap();
1528 assert_eq!(geo.segments.len(), 2);
1529 }
1531
1532 #[test]
1533 fn test_snap_glyph_to_cell_basic() {
1534 let (left, top, w, h) = snap_glyph_to_cell(
1536 10.5, 20.5, 7.8, 15.8, 10.0, 20.0, 18.0, 36.0, 3.0, 0.5, );
1543
1544 assert!((left - 9.5).abs() < 0.01);
1546 assert!((top - 19.5).abs() < 0.01);
1548 assert!((left + w - 18.5).abs() < 0.01);
1550 assert!((top + h - 36.5).abs() < 0.01);
1552 }
1553
1554 #[test]
1555 fn test_snap_glyph_no_snap_when_far() {
1556 let (left, top, w, h) = snap_glyph_to_cell(
1560 12.0, 24.0, 5.0, 10.0, 10.0, 20.0, 20.0, 40.0, 1.5, 0.5, );
1567
1568 assert_eq!(left, 12.0);
1570 assert_eq!(top, 24.0);
1571 assert_eq!(w, 5.0);
1572 assert_eq!(h, 10.0);
1573 }
1574
1575 #[test]
1576 fn test_snap_glyph_middle_snap() {
1577 let (_left, top, _w, h) = snap_glyph_to_cell(
1579 10.0, 20.0, 8.0, 9.8, 10.0, 20.0, 18.0, 40.0, 1.0, 0.0, );
1586
1587 assert!((top + h - 30.0).abs() < 0.01);
1589 }
1590
1591 #[test]
1592 fn test_geometric_shape_rect_black_square() {
1593 let rect = get_geometric_shape_rect('\u{25A0}', 10.0, 20.0, 8.0, 16.0).unwrap();
1595 assert_eq!(rect.x, 10.0);
1596 assert_eq!(rect.y, 24.0); assert_eq!(rect.width, 8.0);
1598 assert_eq!(rect.height, 8.0);
1599 }
1600
1601 #[test]
1602 fn test_geometric_shape_rect_medium_square() {
1603 let rect = get_geometric_shape_rect('\u{25FC}', 10.0, 20.0, 8.0, 16.0).unwrap();
1605 let size = 8.0 * 0.75; assert_eq!(rect.x, 10.0 + (8.0 - size) / 2.0);
1607 assert_eq!(rect.y, 20.0 + (16.0 - size) / 2.0);
1608 assert_eq!(rect.width, size);
1609 assert_eq!(rect.height, size);
1610 }
1611
1612 #[test]
1613 fn test_geometric_shape_rect_small_square() {
1614 let rect = get_geometric_shape_rect('\u{25AA}', 10.0, 20.0, 8.0, 16.0).unwrap();
1616 let size = 8.0 * 0.5; assert_eq!(rect.x, 10.0 + (8.0 - size) / 2.0);
1618 assert_eq!(rect.y, 20.0 + (16.0 - size) / 2.0);
1619 assert_eq!(rect.width, size);
1620 assert_eq!(rect.height, size);
1621 }
1622
1623 #[test]
1624 fn test_geometric_shape_rect_rectangle() {
1625 let rect = get_geometric_shape_rect('\u{25AC}', 10.0, 20.0, 8.0, 16.0).unwrap();
1627 let h = 16.0 * 0.33;
1628 assert_eq!(rect.x, 10.0);
1629 assert!((rect.y - (20.0 + (16.0 - h) / 2.0)).abs() < 0.01);
1630 assert_eq!(rect.width, 8.0);
1631 assert!((rect.height - h).abs() < 0.01);
1632 }
1633
1634 #[test]
1635 fn test_geometric_shape_rect_vertical_rectangle() {
1636 let rect = get_geometric_shape_rect('\u{25AE}', 10.0, 20.0, 8.0, 16.0).unwrap();
1638 let w = 8.0 * 0.5;
1639 assert_eq!(rect.x, 10.0 + (8.0 - w) / 2.0);
1640 assert_eq!(rect.y, 20.0);
1641 assert_eq!(rect.width, w);
1642 assert_eq!(rect.height, 16.0);
1643 }
1644
1645 #[test]
1646 fn test_geometric_shape_rect_outline_returns_none() {
1647 assert!(get_geometric_shape_rect('\u{25A1}', 0.0, 0.0, 8.0, 16.0).is_none()); assert!(get_geometric_shape_rect('\u{25AB}', 0.0, 0.0, 8.0, 16.0).is_none()); assert!(get_geometric_shape_rect('\u{25FB}', 0.0, 0.0, 8.0, 16.0).is_none()); assert!(get_geometric_shape_rect('\u{25FD}', 0.0, 0.0, 8.0, 16.0).is_none()); }
1653}