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
32mod grid {
47 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;
63 pub const HEAVY_THICKNESS: f32 = 0.20;
64 pub const DOUBLE_THICKNESS: f32 = 0.08;
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum BlockCharType {
70 None,
72 BoxDrawing,
74 SolidBlock,
76 PartialBlock,
78 Shade,
80 Geometric,
82 Powerline,
84 Braille,
86}
87
88pub fn classify_char(ch: char) -> BlockCharType {
90 let code = ch as u32;
91
92 if (ranges::BOX_DRAWING_START..=ranges::BOX_DRAWING_END).contains(&code) {
94 return BlockCharType::BoxDrawing;
95 }
96
97 if (ranges::BLOCK_ELEMENTS_START..=ranges::BLOCK_ELEMENTS_END).contains(&code) {
99 return classify_block_element(ch);
100 }
101
102 if (ranges::GEOMETRIC_SHAPES_START..=ranges::GEOMETRIC_SHAPES_END).contains(&code) {
104 return BlockCharType::Geometric;
105 }
106
107 if (ranges::POWERLINE_START..=ranges::POWERLINE_END).contains(&code) {
109 return BlockCharType::Powerline;
110 }
111
112 if (ranges::BRAILLE_START..=ranges::BRAILLE_END).contains(&code) {
114 return BlockCharType::Braille;
115 }
116
117 BlockCharType::None
118}
119
120fn classify_block_element(ch: char) -> BlockCharType {
122 match ch {
123 '\u{2591}' | '\u{2592}' | '\u{2593}' => BlockCharType::Shade,
125
126 '\u{2588}' => BlockCharType::SolidBlock,
128
129 '\u{2580}'..='\u{2590}' | '\u{2594}'..='\u{259F}' => BlockCharType::PartialBlock,
131
132 _ => BlockCharType::PartialBlock,
133 }
134}
135
136pub fn should_snap_to_boundaries(char_type: BlockCharType) -> bool {
138 matches!(
139 char_type,
140 BlockCharType::BoxDrawing
141 | BlockCharType::SolidBlock
142 | BlockCharType::PartialBlock
143 | BlockCharType::Geometric
144 | BlockCharType::Powerline
145 )
146}
147
148pub fn should_render_geometrically(char_type: BlockCharType) -> bool {
150 matches!(
151 char_type,
152 BlockCharType::SolidBlock | BlockCharType::PartialBlock | BlockCharType::BoxDrawing
153 )
154}
155
156#[derive(Debug, Clone, Copy)]
159struct LineSegment {
160 x1: f32,
161 y1: f32,
162 x2: f32,
163 y2: f32,
164 thickness: f32,
165}
166
167impl LineSegment {
168 const fn new(x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32) -> Self {
169 Self {
170 x1,
171 y1,
172 x2,
173 y2,
174 thickness,
175 }
176 }
177
178 const fn horizontal(y: f32, x1: f32, x2: f32, thickness: f32) -> Self {
180 Self::new(x1, y, x2, y, thickness)
181 }
182
183 const fn vertical(x: f32, y1: f32, y2: f32, thickness: f32) -> Self {
185 Self::new(x, y1, x, y2, thickness)
186 }
187
188 fn to_block(self, aspect_ratio: f32) -> GeometricBlock {
190 let is_horizontal = (self.y1 - self.y2).abs() < 0.001;
191 let is_vertical = (self.x1 - self.x2).abs() < 0.001;
192
193 if is_horizontal {
194 let x = self.x1.min(self.x2);
196 let width = (self.x2 - self.x1).abs();
197 let height = self.thickness;
198 let y = self.y1 - height / 2.0;
199 GeometricBlock::new(x, y, width, height)
200 } else if is_vertical {
201 let y = self.y1.min(self.y2);
203 let height = (self.y2 - self.y1).abs();
204 let width = self.thickness * aspect_ratio;
205 let x = self.x1 - width / 2.0;
206 GeometricBlock::new(x, y, width, height)
207 } else {
208 let x = self.x1.min(self.x2);
210 let y = self.y1.min(self.y2);
211 let width = (self.x2 - self.x1).abs().max(self.thickness);
212 let height = (self.y2 - self.y1).abs().max(self.thickness);
213 GeometricBlock::new(x, y, width, height)
214 }
215 }
216}
217
218#[derive(Debug, Clone)]
220pub struct BoxDrawingGeometry {
221 pub segments: Vec<GeometricBlock>,
222}
223
224impl BoxDrawingGeometry {
225 fn from_lines(lines: &[LineSegment], aspect_ratio: f32) -> Self {
226 Self {
227 segments: lines.iter().map(|l| l.to_block(aspect_ratio)).collect(),
228 }
229 }
230}
231
232pub fn get_box_drawing_geometry(ch: char, aspect_ratio: f32) -> Option<BoxDrawingGeometry> {
236 use grid::*;
237
238 let lt = LIGHT_THICKNESS;
239 let ht = HEAVY_THICKNESS;
240 let dt = DOUBLE_THICKNESS;
241
242 let lines: &[LineSegment] = match ch {
243 '\u{2500}' => &[LineSegment::horizontal(V4, A, G, lt)],
249
250 '\u{2502}' => &[LineSegment::vertical(D, V1, V7, lt)],
252
253 '\u{250C}' => &[
255 LineSegment::horizontal(V4, D, G, lt),
256 LineSegment::vertical(D, V4, V7, lt),
257 ],
258
259 '\u{2510}' => &[
261 LineSegment::horizontal(V4, A, D, lt),
262 LineSegment::vertical(D, V4, V7, lt),
263 ],
264
265 '\u{2514}' => &[
267 LineSegment::horizontal(V4, D, G, lt),
268 LineSegment::vertical(D, V1, V4, lt),
269 ],
270
271 '\u{2518}' => &[
273 LineSegment::horizontal(V4, A, D, lt),
274 LineSegment::vertical(D, V1, V4, lt),
275 ],
276
277 '\u{251C}' => &[
279 LineSegment::vertical(D, V1, V7, lt),
280 LineSegment::horizontal(V4, D, G, lt),
281 ],
282
283 '\u{2524}' => &[
285 LineSegment::vertical(D, V1, V7, lt),
286 LineSegment::horizontal(V4, A, D, lt),
287 ],
288
289 '\u{252C}' => &[
291 LineSegment::horizontal(V4, A, G, lt),
292 LineSegment::vertical(D, V4, V7, lt),
293 ],
294
295 '\u{2534}' => &[
297 LineSegment::horizontal(V4, A, G, lt),
298 LineSegment::vertical(D, V1, V4, lt),
299 ],
300
301 '\u{253C}' => &[
303 LineSegment::horizontal(V4, A, G, lt),
304 LineSegment::vertical(D, V1, V7, lt),
305 ],
306
307 '\u{2501}' => &[LineSegment::horizontal(V4, A, G, ht)],
313
314 '\u{2503}' => &[LineSegment::vertical(D, V1, V7, ht)],
316
317 '\u{250F}' => &[
319 LineSegment::horizontal(V4, D, G, ht),
320 LineSegment::vertical(D, V4, V7, ht),
321 ],
322
323 '\u{2513}' => &[
325 LineSegment::horizontal(V4, A, D, ht),
326 LineSegment::vertical(D, V4, V7, ht),
327 ],
328
329 '\u{2517}' => &[
331 LineSegment::horizontal(V4, D, G, ht),
332 LineSegment::vertical(D, V1, V4, ht),
333 ],
334
335 '\u{251B}' => &[
337 LineSegment::horizontal(V4, A, D, ht),
338 LineSegment::vertical(D, V1, V4, ht),
339 ],
340
341 '\u{2523}' => &[
343 LineSegment::vertical(D, V1, V7, ht),
344 LineSegment::horizontal(V4, D, G, ht),
345 ],
346
347 '\u{252B}' => &[
349 LineSegment::vertical(D, V1, V7, ht),
350 LineSegment::horizontal(V4, A, D, ht),
351 ],
352
353 '\u{2533}' => &[
355 LineSegment::horizontal(V4, A, G, ht),
356 LineSegment::vertical(D, V4, V7, ht),
357 ],
358
359 '\u{253B}' => &[
361 LineSegment::horizontal(V4, A, G, ht),
362 LineSegment::vertical(D, V1, V4, ht),
363 ],
364
365 '\u{254B}' => &[
367 LineSegment::horizontal(V4, A, G, ht),
368 LineSegment::vertical(D, V1, V7, ht),
369 ],
370
371 '\u{250D}' => &[
377 LineSegment::horizontal(V4, D, G, ht),
378 LineSegment::vertical(D, V4, V7, lt),
379 ],
380
381 '\u{250E}' => &[
383 LineSegment::horizontal(V4, D, G, lt),
384 LineSegment::vertical(D, V4, V7, ht),
385 ],
386
387 '\u{2511}' => &[
389 LineSegment::horizontal(V4, A, D, ht),
390 LineSegment::vertical(D, V4, V7, lt),
391 ],
392
393 '\u{2512}' => &[
395 LineSegment::horizontal(V4, A, D, lt),
396 LineSegment::vertical(D, V4, V7, ht),
397 ],
398
399 '\u{2515}' => &[
401 LineSegment::horizontal(V4, D, G, ht),
402 LineSegment::vertical(D, V1, V4, lt),
403 ],
404
405 '\u{2516}' => &[
407 LineSegment::horizontal(V4, D, G, lt),
408 LineSegment::vertical(D, V1, V4, ht),
409 ],
410
411 '\u{2519}' => &[
413 LineSegment::horizontal(V4, A, D, ht),
414 LineSegment::vertical(D, V1, V4, lt),
415 ],
416
417 '\u{251A}' => &[
419 LineSegment::horizontal(V4, A, D, lt),
420 LineSegment::vertical(D, V1, V4, ht),
421 ],
422
423 '\u{251D}' => &[
425 LineSegment::vertical(D, V1, V7, lt),
426 LineSegment::horizontal(V4, D, G, ht),
427 ],
428
429 '\u{251E}' => &[
431 LineSegment::vertical(D, V1, V4, ht),
432 LineSegment::vertical(D, V4, V7, lt),
433 LineSegment::horizontal(V4, D, G, lt),
434 ],
435
436 '\u{251F}' => &[
438 LineSegment::vertical(D, V1, V4, lt),
439 LineSegment::vertical(D, V4, V7, ht),
440 LineSegment::horizontal(V4, D, G, lt),
441 ],
442
443 '\u{2520}' => &[
445 LineSegment::vertical(D, V1, V7, ht),
446 LineSegment::horizontal(V4, D, G, lt),
447 ],
448
449 '\u{2521}' => &[
451 LineSegment::vertical(D, V1, V4, ht),
452 LineSegment::vertical(D, V4, V7, lt),
453 LineSegment::horizontal(V4, D, G, ht),
454 ],
455
456 '\u{2522}' => &[
458 LineSegment::vertical(D, V1, V4, lt),
459 LineSegment::vertical(D, V4, V7, ht),
460 LineSegment::horizontal(V4, D, G, ht),
461 ],
462
463 '\u{2525}' => &[
465 LineSegment::vertical(D, V1, V7, lt),
466 LineSegment::horizontal(V4, A, D, ht),
467 ],
468
469 '\u{2526}' => &[
471 LineSegment::vertical(D, V1, V4, ht),
472 LineSegment::vertical(D, V4, V7, lt),
473 LineSegment::horizontal(V4, A, D, lt),
474 ],
475
476 '\u{2527}' => &[
478 LineSegment::vertical(D, V1, V4, lt),
479 LineSegment::vertical(D, V4, V7, ht),
480 LineSegment::horizontal(V4, A, D, lt),
481 ],
482
483 '\u{2528}' => &[
485 LineSegment::vertical(D, V1, V7, ht),
486 LineSegment::horizontal(V4, A, D, lt),
487 ],
488
489 '\u{2529}' => &[
491 LineSegment::vertical(D, V1, V4, ht),
492 LineSegment::vertical(D, V4, V7, lt),
493 LineSegment::horizontal(V4, A, D, ht),
494 ],
495
496 '\u{252A}' => &[
498 LineSegment::vertical(D, V1, V4, lt),
499 LineSegment::vertical(D, V4, V7, ht),
500 LineSegment::horizontal(V4, A, D, ht),
501 ],
502
503 '\u{252D}' => &[
505 LineSegment::horizontal(V4, A, D, ht),
506 LineSegment::horizontal(V4, D, G, lt),
507 LineSegment::vertical(D, V4, V7, lt),
508 ],
509
510 '\u{252E}' => &[
512 LineSegment::horizontal(V4, A, D, lt),
513 LineSegment::horizontal(V4, D, G, ht),
514 LineSegment::vertical(D, V4, V7, lt),
515 ],
516
517 '\u{252F}' => &[
519 LineSegment::horizontal(V4, A, G, ht),
520 LineSegment::vertical(D, V4, V7, lt),
521 ],
522
523 '\u{2530}' => &[
525 LineSegment::horizontal(V4, A, G, lt),
526 LineSegment::vertical(D, V4, V7, ht),
527 ],
528
529 '\u{2531}' => &[
531 LineSegment::horizontal(V4, A, D, ht),
532 LineSegment::horizontal(V4, D, G, lt),
533 LineSegment::vertical(D, V4, V7, ht),
534 ],
535
536 '\u{2532}' => &[
538 LineSegment::horizontal(V4, A, D, lt),
539 LineSegment::horizontal(V4, D, G, ht),
540 LineSegment::vertical(D, V4, V7, ht),
541 ],
542
543 '\u{2535}' => &[
545 LineSegment::horizontal(V4, A, D, ht),
546 LineSegment::horizontal(V4, D, G, lt),
547 LineSegment::vertical(D, V1, V4, lt),
548 ],
549
550 '\u{2536}' => &[
552 LineSegment::horizontal(V4, A, D, lt),
553 LineSegment::horizontal(V4, D, G, ht),
554 LineSegment::vertical(D, V1, V4, lt),
555 ],
556
557 '\u{2537}' => &[
559 LineSegment::horizontal(V4, A, G, ht),
560 LineSegment::vertical(D, V1, V4, lt),
561 ],
562
563 '\u{2538}' => &[
565 LineSegment::horizontal(V4, A, G, lt),
566 LineSegment::vertical(D, V1, V4, ht),
567 ],
568
569 '\u{2539}' => &[
571 LineSegment::horizontal(V4, A, D, ht),
572 LineSegment::horizontal(V4, D, G, lt),
573 LineSegment::vertical(D, V1, V4, ht),
574 ],
575
576 '\u{253A}' => &[
578 LineSegment::horizontal(V4, A, D, lt),
579 LineSegment::horizontal(V4, D, G, ht),
580 LineSegment::vertical(D, V1, V4, ht),
581 ],
582
583 '\u{253D}' => &[
585 LineSegment::horizontal(V4, A, D, ht),
586 LineSegment::horizontal(V4, D, G, lt),
587 LineSegment::vertical(D, V1, V7, lt),
588 ],
589
590 '\u{253E}' => &[
592 LineSegment::horizontal(V4, A, D, lt),
593 LineSegment::horizontal(V4, D, G, ht),
594 LineSegment::vertical(D, V1, V7, lt),
595 ],
596
597 '\u{253F}' => &[
599 LineSegment::horizontal(V4, A, G, ht),
600 LineSegment::vertical(D, V1, V7, lt),
601 ],
602
603 '\u{2540}' => &[
605 LineSegment::horizontal(V4, A, G, lt),
606 LineSegment::vertical(D, V1, V4, ht),
607 LineSegment::vertical(D, V4, V7, lt),
608 ],
609
610 '\u{2541}' => &[
612 LineSegment::horizontal(V4, A, G, lt),
613 LineSegment::vertical(D, V1, V4, lt),
614 LineSegment::vertical(D, V4, V7, ht),
615 ],
616
617 '\u{2542}' => &[
619 LineSegment::horizontal(V4, A, G, lt),
620 LineSegment::vertical(D, V1, V7, ht),
621 ],
622
623 '\u{2543}' => &[
625 LineSegment::horizontal(V4, A, D, ht),
626 LineSegment::horizontal(V4, D, G, lt),
627 LineSegment::vertical(D, V1, V4, ht),
628 LineSegment::vertical(D, V4, V7, lt),
629 ],
630
631 '\u{2544}' => &[
633 LineSegment::horizontal(V4, A, D, lt),
634 LineSegment::horizontal(V4, D, G, ht),
635 LineSegment::vertical(D, V1, V4, ht),
636 LineSegment::vertical(D, V4, V7, lt),
637 ],
638
639 '\u{2545}' => &[
641 LineSegment::horizontal(V4, A, D, ht),
642 LineSegment::horizontal(V4, D, G, lt),
643 LineSegment::vertical(D, V1, V4, lt),
644 LineSegment::vertical(D, V4, V7, ht),
645 ],
646
647 '\u{2546}' => &[
649 LineSegment::horizontal(V4, A, D, lt),
650 LineSegment::horizontal(V4, D, G, ht),
651 LineSegment::vertical(D, V1, V4, lt),
652 LineSegment::vertical(D, V4, V7, ht),
653 ],
654
655 '\u{2547}' => &[
657 LineSegment::horizontal(V4, A, G, ht),
658 LineSegment::vertical(D, V1, V4, ht),
659 LineSegment::vertical(D, V4, V7, lt),
660 ],
661
662 '\u{2548}' => &[
664 LineSegment::horizontal(V4, A, G, ht),
665 LineSegment::vertical(D, V1, V4, lt),
666 LineSegment::vertical(D, V4, V7, ht),
667 ],
668
669 '\u{2549}' => &[
671 LineSegment::horizontal(V4, A, D, ht),
672 LineSegment::horizontal(V4, D, G, lt),
673 LineSegment::vertical(D, V1, V7, ht),
674 ],
675
676 '\u{254A}' => &[
678 LineSegment::horizontal(V4, A, D, lt),
679 LineSegment::horizontal(V4, D, G, ht),
680 LineSegment::vertical(D, V1, V7, ht),
681 ],
682
683 '\u{2550}' => &[
689 LineSegment::horizontal(V3, A, G, dt),
690 LineSegment::horizontal(V5, A, G, dt),
691 ],
692
693 '\u{2551}' => &[
695 LineSegment::vertical(C, V1, V7, dt),
696 LineSegment::vertical(E, V1, V7, dt),
697 ],
698
699 '\u{2554}' => &[
701 LineSegment::horizontal(V3, E, G, dt),
702 LineSegment::horizontal(V5, C, G, dt),
703 LineSegment::vertical(C, V3, V7, dt),
704 LineSegment::vertical(E, V5, V7, dt),
705 ],
706
707 '\u{2557}' => &[
709 LineSegment::horizontal(V3, A, C, dt),
710 LineSegment::horizontal(V5, A, E, dt),
711 LineSegment::vertical(C, V5, V7, dt),
712 LineSegment::vertical(E, V3, V7, dt),
713 ],
714
715 '\u{255A}' => &[
717 LineSegment::horizontal(V3, C, G, dt),
718 LineSegment::horizontal(V5, E, G, dt),
719 LineSegment::vertical(C, V1, V3, dt),
720 LineSegment::vertical(E, V1, V5, dt),
721 ],
722
723 '\u{255D}' => &[
725 LineSegment::horizontal(V3, A, E, dt),
726 LineSegment::horizontal(V5, A, C, dt),
727 LineSegment::vertical(C, V1, V5, dt),
728 LineSegment::vertical(E, V1, V3, dt),
729 ],
730
731 '\u{2560}' => &[
733 LineSegment::vertical(C, V1, V7, dt),
734 LineSegment::vertical(E, V1, V3, dt),
735 LineSegment::vertical(E, V5, V7, dt),
736 LineSegment::horizontal(V3, E, G, dt),
737 LineSegment::horizontal(V5, E, G, dt),
738 ],
739
740 '\u{2563}' => &[
742 LineSegment::vertical(E, V1, V7, dt),
743 LineSegment::vertical(C, V1, V3, dt),
744 LineSegment::vertical(C, V5, V7, dt),
745 LineSegment::horizontal(V3, A, C, dt),
746 LineSegment::horizontal(V5, A, C, dt),
747 ],
748
749 '\u{2566}' => &[
751 LineSegment::horizontal(V3, A, G, dt),
752 LineSegment::horizontal(V5, A, C, dt),
753 LineSegment::horizontal(V5, E, G, dt),
754 LineSegment::vertical(C, V5, V7, dt),
755 LineSegment::vertical(E, V5, V7, dt),
756 ],
757
758 '\u{2569}' => &[
760 LineSegment::horizontal(V5, A, G, dt),
761 LineSegment::horizontal(V3, A, C, dt),
762 LineSegment::horizontal(V3, E, G, dt),
763 LineSegment::vertical(C, V1, V3, dt),
764 LineSegment::vertical(E, V1, V3, dt),
765 ],
766
767 '\u{256C}' => &[
769 LineSegment::horizontal(V3, A, C, dt),
770 LineSegment::horizontal(V3, E, G, dt),
771 LineSegment::horizontal(V5, A, C, dt),
772 LineSegment::horizontal(V5, E, G, dt),
773 LineSegment::vertical(C, V1, V3, dt),
774 LineSegment::vertical(C, V5, V7, dt),
775 LineSegment::vertical(E, V1, V3, dt),
776 LineSegment::vertical(E, V5, V7, dt),
777 ],
778
779 '\u{2552}' => &[
785 LineSegment::horizontal(V3, D, G, dt),
786 LineSegment::horizontal(V5, D, G, dt),
787 LineSegment::vertical(D, V4, V7, lt),
788 ],
789
790 '\u{2553}' => &[
792 LineSegment::horizontal(V4, D, G, lt),
793 LineSegment::vertical(C, V4, V7, dt),
794 LineSegment::vertical(E, V4, V7, dt),
795 ],
796
797 '\u{2555}' => &[
799 LineSegment::horizontal(V3, A, D, dt),
800 LineSegment::horizontal(V5, A, D, dt),
801 LineSegment::vertical(D, V4, V7, lt),
802 ],
803
804 '\u{2556}' => &[
806 LineSegment::horizontal(V4, A, D, lt),
807 LineSegment::vertical(C, V4, V7, dt),
808 LineSegment::vertical(E, V4, V7, dt),
809 ],
810
811 '\u{2558}' => &[
813 LineSegment::horizontal(V3, D, G, dt),
814 LineSegment::horizontal(V5, D, G, dt),
815 LineSegment::vertical(D, V1, V4, lt),
816 ],
817
818 '\u{2559}' => &[
820 LineSegment::horizontal(V4, D, G, lt),
821 LineSegment::vertical(C, V1, V4, dt),
822 LineSegment::vertical(E, V1, V4, dt),
823 ],
824
825 '\u{255B}' => &[
827 LineSegment::horizontal(V3, A, D, dt),
828 LineSegment::horizontal(V5, A, D, dt),
829 LineSegment::vertical(D, V1, V4, lt),
830 ],
831
832 '\u{255C}' => &[
834 LineSegment::horizontal(V4, A, D, lt),
835 LineSegment::vertical(C, V1, V4, dt),
836 LineSegment::vertical(E, V1, V4, dt),
837 ],
838
839 '\u{255E}' => &[
841 LineSegment::vertical(D, V1, V7, lt),
842 LineSegment::horizontal(V3, D, G, dt),
843 LineSegment::horizontal(V5, D, G, dt),
844 ],
845
846 '\u{255F}' => &[
848 LineSegment::vertical(C, V1, V7, dt),
849 LineSegment::vertical(E, V1, V7, dt),
850 LineSegment::horizontal(V4, E, G, lt),
851 ],
852
853 '\u{2561}' => &[
855 LineSegment::vertical(D, V1, V7, lt),
856 LineSegment::horizontal(V3, A, D, dt),
857 LineSegment::horizontal(V5, A, D, dt),
858 ],
859
860 '\u{2562}' => &[
862 LineSegment::vertical(C, V1, V7, dt),
863 LineSegment::vertical(E, V1, V7, dt),
864 LineSegment::horizontal(V4, A, C, lt),
865 ],
866
867 '\u{2564}' => &[
869 LineSegment::horizontal(V3, A, G, dt),
870 LineSegment::horizontal(V5, A, G, dt),
871 LineSegment::vertical(D, V5, V7, lt),
872 ],
873
874 '\u{2565}' => &[
876 LineSegment::horizontal(V4, A, G, lt),
877 LineSegment::vertical(C, V4, V7, dt),
878 LineSegment::vertical(E, V4, V7, dt),
879 ],
880
881 '\u{2567}' => &[
883 LineSegment::horizontal(V3, A, G, dt),
884 LineSegment::horizontal(V5, A, G, dt),
885 LineSegment::vertical(D, V1, V3, lt),
886 ],
887
888 '\u{2568}' => &[
890 LineSegment::horizontal(V4, A, G, lt),
891 LineSegment::vertical(C, V1, V4, dt),
892 LineSegment::vertical(E, V1, V4, dt),
893 ],
894
895 '\u{256A}' => &[
897 LineSegment::horizontal(V3, A, G, dt),
898 LineSegment::horizontal(V5, A, G, dt),
899 LineSegment::vertical(D, V1, V7, lt),
900 ],
901
902 '\u{256B}' => &[
904 LineSegment::vertical(C, V1, V7, dt),
905 LineSegment::vertical(E, V1, V7, dt),
906 LineSegment::horizontal(V4, A, G, lt),
907 ],
908
909 '\u{2504}' => &[LineSegment::horizontal(V4, A, G, lt)],
915
916 '\u{2505}' => &[LineSegment::horizontal(V4, A, G, ht)],
918
919 '\u{2506}' => &[LineSegment::vertical(D, V1, V7, lt)],
921
922 '\u{2507}' => &[LineSegment::vertical(D, V1, V7, ht)],
924
925 '\u{2508}' => &[LineSegment::horizontal(V4, A, G, lt)],
927
928 '\u{2509}' => &[LineSegment::horizontal(V4, A, G, ht)],
930
931 '\u{250A}' => &[LineSegment::vertical(D, V1, V7, lt)],
933
934 '\u{250B}' => &[LineSegment::vertical(D, V1, V7, ht)],
936
937 '\u{256D}' => &[
943 LineSegment::horizontal(V4, D, G, lt),
944 LineSegment::vertical(D, V4, V7, lt),
945 ],
946
947 '\u{256E}' => &[
949 LineSegment::horizontal(V4, A, D, lt),
950 LineSegment::vertical(D, V4, V7, lt),
951 ],
952
953 '\u{256F}' => &[
955 LineSegment::horizontal(V4, A, D, lt),
956 LineSegment::vertical(D, V1, V4, lt),
957 ],
958
959 '\u{2570}' => &[
961 LineSegment::horizontal(V4, D, G, lt),
962 LineSegment::vertical(D, V1, V4, lt),
963 ],
964
965 '\u{2571}' => &[LineSegment::new(G, V1, A, V7, lt)],
971
972 '\u{2572}' => &[LineSegment::new(A, V1, G, V7, lt)],
974
975 '\u{2573}' => &[
977 LineSegment::new(A, V1, G, V7, lt),
978 LineSegment::new(G, V1, A, V7, lt),
979 ],
980
981 '\u{2574}' => &[LineSegment::horizontal(V4, A, D, lt)],
987
988 '\u{2575}' => &[LineSegment::vertical(D, V1, V4, lt)],
990
991 '\u{2576}' => &[LineSegment::horizontal(V4, D, G, lt)],
993
994 '\u{2577}' => &[LineSegment::vertical(D, V4, V7, lt)],
996
997 '\u{2578}' => &[LineSegment::horizontal(V4, A, D, ht)],
999
1000 '\u{2579}' => &[LineSegment::vertical(D, V1, V4, ht)],
1002
1003 '\u{257A}' => &[LineSegment::horizontal(V4, D, G, ht)],
1005
1006 '\u{257B}' => &[LineSegment::vertical(D, V4, V7, ht)],
1008
1009 '\u{257C}' => &[
1011 LineSegment::horizontal(V4, A, D, lt),
1012 LineSegment::horizontal(V4, D, G, ht),
1013 ],
1014
1015 '\u{257D}' => &[
1017 LineSegment::vertical(D, V1, V4, lt),
1018 LineSegment::vertical(D, V4, V7, ht),
1019 ],
1020
1021 '\u{257E}' => &[
1023 LineSegment::horizontal(V4, A, D, ht),
1024 LineSegment::horizontal(V4, D, G, lt),
1025 ],
1026
1027 '\u{257F}' => &[
1029 LineSegment::vertical(D, V1, V4, ht),
1030 LineSegment::vertical(D, V4, V7, lt),
1031 ],
1032
1033 _ => return None,
1034 };
1035
1036 if lines.is_empty() {
1037 None
1038 } else {
1039 Some(BoxDrawingGeometry::from_lines(lines, aspect_ratio))
1040 }
1041}
1042
1043#[derive(Debug, Clone, Copy)]
1045pub struct GeometricBlock {
1046 pub x: f32,
1048 pub y: f32,
1050 pub width: f32,
1052 pub height: f32,
1054}
1055
1056impl GeometricBlock {
1057 pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
1058 Self {
1059 x,
1060 y,
1061 width,
1062 height,
1063 }
1064 }
1065
1066 pub const fn full() -> Self {
1068 Self::new(0.0, 0.0, 1.0, 1.0)
1069 }
1070
1071 pub fn to_pixel_rect(self, cell_x: f32, cell_y: f32, cell_w: f32, cell_h: f32) -> PixelRect {
1073 PixelRect {
1074 x: cell_x + self.x * cell_w,
1075 y: cell_y + self.y * cell_h,
1076 width: self.width * cell_w,
1077 height: self.height * cell_h,
1078 }
1079 }
1080}
1081
1082#[derive(Debug, Clone, Copy)]
1084pub struct PixelRect {
1085 pub x: f32,
1086 pub y: f32,
1087 pub width: f32,
1088 pub height: f32,
1089}
1090
1091pub fn get_geometric_block(ch: char) -> Option<GeometricBlock> {
1094 match ch {
1095 '\u{2588}' => Some(GeometricBlock::full()),
1097
1098 '\u{2580}' => Some(GeometricBlock::new(0.0, 0.0, 1.0, 0.5)),
1100
1101 '\u{2581}' => Some(GeometricBlock::new(0.0, 0.875, 1.0, 0.125)),
1103 '\u{2582}' => Some(GeometricBlock::new(0.0, 0.75, 1.0, 0.25)),
1104 '\u{2583}' => Some(GeometricBlock::new(0.0, 0.625, 1.0, 0.375)),
1105 '\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)),
1107 '\u{2586}' => Some(GeometricBlock::new(0.0, 0.25, 1.0, 0.75)),
1108 '\u{2587}' => Some(GeometricBlock::new(0.0, 0.125, 1.0, 0.875)),
1109
1110 '\u{2589}' => Some(GeometricBlock::new(0.0, 0.0, 0.875, 1.0)),
1112 '\u{258A}' => Some(GeometricBlock::new(0.0, 0.0, 0.75, 1.0)),
1113 '\u{258B}' => Some(GeometricBlock::new(0.0, 0.0, 0.625, 1.0)),
1114 '\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)),
1116 '\u{258E}' => Some(GeometricBlock::new(0.0, 0.0, 0.25, 1.0)),
1117 '\u{258F}' => Some(GeometricBlock::new(0.0, 0.0, 0.125, 1.0)),
1118
1119 '\u{2590}' => Some(GeometricBlock::new(0.5, 0.0, 0.5, 1.0)),
1121
1122 '\u{2594}' => Some(GeometricBlock::new(0.0, 0.0, 1.0, 0.125)),
1124
1125 '\u{2595}' => Some(GeometricBlock::new(0.875, 0.0, 0.125, 1.0)),
1127
1128 '\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,
1137
1138 _ => None,
1139 }
1140}
1141
1142#[allow(clippy::too_many_arguments)]
1162pub fn snap_glyph_to_cell(
1163 glyph_left: f32,
1164 glyph_top: f32,
1165 render_w: f32,
1166 render_h: f32,
1167 cell_x0: f32,
1168 cell_y0: f32,
1169 cell_x1: f32,
1170 cell_y1: f32,
1171 snap_threshold: f32,
1172 extension: f32,
1173) -> (f32, f32, f32, f32) {
1174 let mut new_left = glyph_left;
1175 let mut new_top = glyph_top;
1176 let mut new_w = render_w;
1177 let mut new_h = render_h;
1178
1179 let glyph_right = glyph_left + render_w;
1180 let glyph_bottom = glyph_top + render_h;
1181
1182 if (glyph_left - cell_x0).abs() < snap_threshold {
1184 new_left = cell_x0 - extension;
1185 new_w = glyph_right - new_left;
1186 }
1187
1188 if (glyph_right - cell_x1).abs() < snap_threshold {
1190 new_w = cell_x1 + extension - new_left;
1191 }
1192
1193 if (glyph_top - cell_y0).abs() < snap_threshold {
1195 new_top = cell_y0 - extension;
1196 new_h = glyph_bottom - new_top;
1197 }
1198
1199 if (glyph_bottom - cell_y1).abs() < snap_threshold {
1201 new_h = cell_y1 + extension - new_top;
1202 }
1203
1204 let cell_cx = (cell_x0 + cell_x1) / 2.0;
1206 let cell_cy = (cell_y0 + cell_y1) / 2.0;
1207
1208 if (glyph_bottom - cell_cy).abs() < snap_threshold {
1210 new_h = cell_cy - new_top;
1211 } else if (glyph_top - cell_cy).abs() < snap_threshold {
1212 let bottom = new_top + new_h;
1213 new_top = cell_cy;
1214 new_h = bottom - new_top;
1215 }
1216
1217 if (glyph_right - cell_cx).abs() < snap_threshold {
1219 new_w = cell_cx - new_left;
1220 } else if (glyph_left - cell_cx).abs() < snap_threshold {
1221 let right = new_left + new_w;
1222 new_left = cell_cx;
1223 new_w = right - new_left;
1224 }
1225
1226 (new_left, new_top, new_w, new_h)
1227}
1228
1229#[cfg(test)]
1230mod tests {
1231 use super::*;
1232
1233 #[test]
1234 fn test_classify_box_drawing() {
1235 assert_eq!(classify_char('─'), BlockCharType::BoxDrawing);
1237 assert_eq!(classify_char('━'), BlockCharType::BoxDrawing);
1238 assert_eq!(classify_char('═'), BlockCharType::BoxDrawing);
1239
1240 assert_eq!(classify_char('│'), BlockCharType::BoxDrawing);
1242 assert_eq!(classify_char('┃'), BlockCharType::BoxDrawing);
1243 assert_eq!(classify_char('║'), BlockCharType::BoxDrawing);
1244
1245 assert_eq!(classify_char('┌'), BlockCharType::BoxDrawing);
1247 assert_eq!(classify_char('┐'), BlockCharType::BoxDrawing);
1248 assert_eq!(classify_char('└'), BlockCharType::BoxDrawing);
1249 assert_eq!(classify_char('┘'), BlockCharType::BoxDrawing);
1250
1251 assert_eq!(classify_char('╔'), BlockCharType::BoxDrawing);
1253 assert_eq!(classify_char('╗'), BlockCharType::BoxDrawing);
1254 assert_eq!(classify_char('╚'), BlockCharType::BoxDrawing);
1255 assert_eq!(classify_char('╝'), BlockCharType::BoxDrawing);
1256 }
1257
1258 #[test]
1259 fn test_classify_block_elements() {
1260 assert_eq!(classify_char('█'), BlockCharType::SolidBlock);
1262
1263 assert_eq!(classify_char('▀'), BlockCharType::PartialBlock);
1265 assert_eq!(classify_char('▄'), BlockCharType::PartialBlock);
1266 assert_eq!(classify_char('▌'), BlockCharType::PartialBlock);
1267 assert_eq!(classify_char('▐'), BlockCharType::PartialBlock);
1268
1269 assert_eq!(classify_char('░'), BlockCharType::Shade);
1271 assert_eq!(classify_char('▒'), BlockCharType::Shade);
1272 assert_eq!(classify_char('▓'), BlockCharType::Shade);
1273
1274 assert_eq!(classify_char('▖'), BlockCharType::PartialBlock);
1276 assert_eq!(classify_char('▗'), BlockCharType::PartialBlock);
1277 assert_eq!(classify_char('▘'), BlockCharType::PartialBlock);
1278 assert_eq!(classify_char('▝'), BlockCharType::PartialBlock);
1279 }
1280
1281 #[test]
1282 fn test_classify_geometric_shapes() {
1283 assert_eq!(classify_char('■'), BlockCharType::Geometric);
1284 assert_eq!(classify_char('□'), BlockCharType::Geometric);
1285 assert_eq!(classify_char('▪'), BlockCharType::Geometric);
1286 assert_eq!(classify_char('▫'), BlockCharType::Geometric);
1287 }
1288
1289 #[test]
1290 fn test_classify_regular_chars() {
1291 assert_eq!(classify_char('a'), BlockCharType::None);
1292 assert_eq!(classify_char('Z'), BlockCharType::None);
1293 assert_eq!(classify_char('0'), BlockCharType::None);
1294 assert_eq!(classify_char(' '), BlockCharType::None);
1295 assert_eq!(classify_char('!'), BlockCharType::None);
1296 }
1297
1298 #[test]
1299 fn test_should_snap_to_boundaries() {
1300 assert!(should_snap_to_boundaries(BlockCharType::BoxDrawing));
1301 assert!(should_snap_to_boundaries(BlockCharType::SolidBlock));
1302 assert!(should_snap_to_boundaries(BlockCharType::PartialBlock));
1303 assert!(should_snap_to_boundaries(BlockCharType::Geometric));
1304 assert!(should_snap_to_boundaries(BlockCharType::Powerline));
1305
1306 assert!(!should_snap_to_boundaries(BlockCharType::None));
1307 assert!(!should_snap_to_boundaries(BlockCharType::Shade));
1308 assert!(!should_snap_to_boundaries(BlockCharType::Braille));
1309 }
1310
1311 #[test]
1312 fn test_should_render_geometrically() {
1313 assert!(should_render_geometrically(BlockCharType::SolidBlock));
1314 assert!(should_render_geometrically(BlockCharType::PartialBlock));
1315 assert!(should_render_geometrically(BlockCharType::BoxDrawing));
1316
1317 assert!(!should_render_geometrically(BlockCharType::None));
1318 assert!(!should_render_geometrically(BlockCharType::Shade));
1319 assert!(!should_render_geometrically(BlockCharType::Geometric));
1320 assert!(!should_render_geometrically(BlockCharType::Powerline));
1321 assert!(!should_render_geometrically(BlockCharType::Braille));
1322 }
1323
1324 #[test]
1325 fn test_geometric_block_full() {
1326 let block = get_geometric_block('█').unwrap();
1327 assert_eq!(block.x, 0.0);
1328 assert_eq!(block.y, 0.0);
1329 assert_eq!(block.width, 1.0);
1330 assert_eq!(block.height, 1.0);
1331 }
1332
1333 #[test]
1334 fn test_geometric_block_halves() {
1335 let block = get_geometric_block('▀').unwrap();
1337 assert_eq!(block.x, 0.0);
1338 assert_eq!(block.y, 0.0);
1339 assert_eq!(block.width, 1.0);
1340 assert_eq!(block.height, 0.5);
1341
1342 let block = get_geometric_block('▄').unwrap();
1344 assert_eq!(block.x, 0.0);
1345 assert_eq!(block.y, 0.5);
1346 assert_eq!(block.width, 1.0);
1347 assert_eq!(block.height, 0.5);
1348
1349 let block = get_geometric_block('▌').unwrap();
1351 assert_eq!(block.x, 0.0);
1352 assert_eq!(block.y, 0.0);
1353 assert_eq!(block.width, 0.5);
1354 assert_eq!(block.height, 1.0);
1355
1356 let block = get_geometric_block('▐').unwrap();
1358 assert_eq!(block.x, 0.5);
1359 assert_eq!(block.y, 0.0);
1360 assert_eq!(block.width, 0.5);
1361 assert_eq!(block.height, 1.0);
1362 }
1363
1364 #[test]
1365 fn test_geometric_block_quadrants() {
1366 let block = get_geometric_block('▖').unwrap();
1368 assert_eq!(block.x, 0.0);
1369 assert_eq!(block.y, 0.5);
1370 assert_eq!(block.width, 0.5);
1371 assert_eq!(block.height, 0.5);
1372
1373 let block = get_geometric_block('▗').unwrap();
1375 assert_eq!(block.x, 0.5);
1376 assert_eq!(block.y, 0.5);
1377 assert_eq!(block.width, 0.5);
1378 assert_eq!(block.height, 0.5);
1379
1380 let block = get_geometric_block('▘').unwrap();
1382 assert_eq!(block.x, 0.0);
1383 assert_eq!(block.y, 0.0);
1384 assert_eq!(block.width, 0.5);
1385 assert_eq!(block.height, 0.5);
1386
1387 let block = get_geometric_block('▝').unwrap();
1389 assert_eq!(block.x, 0.5);
1390 assert_eq!(block.y, 0.0);
1391 assert_eq!(block.width, 0.5);
1392 assert_eq!(block.height, 0.5);
1393 }
1394
1395 #[test]
1396 fn test_geometric_block_to_pixel_rect() {
1397 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);
1399
1400 assert_eq!(rect.x, 10.0);
1401 assert_eq!(rect.y, 28.0); assert_eq!(rect.width, 8.0);
1403 assert_eq!(rect.height, 8.0);
1404 }
1405
1406 #[test]
1407 fn test_box_drawing_light_horizontal() {
1408 let geo = get_box_drawing_geometry('─', 2.0).unwrap();
1409 assert_eq!(geo.segments.len(), 1);
1410 let seg = &geo.segments[0];
1411 assert_eq!(seg.x, 0.0);
1412 assert!(seg.width > 0.99); }
1414
1415 #[test]
1416 fn test_box_drawing_light_corner() {
1417 let geo = get_box_drawing_geometry('┌', 2.0).unwrap();
1418 assert_eq!(geo.segments.len(), 2);
1419 }
1421
1422 #[test]
1423 fn test_box_drawing_double_lines() {
1424 let geo = get_box_drawing_geometry('═', 2.0).unwrap();
1425 assert_eq!(geo.segments.len(), 2);
1426 }
1428
1429 #[test]
1430 fn test_snap_glyph_to_cell_basic() {
1431 let (left, top, w, h) = snap_glyph_to_cell(
1433 10.5, 20.5, 7.8, 15.8, 10.0, 20.0, 18.0, 36.0, 3.0, 0.5, );
1440
1441 assert!((left - 9.5).abs() < 0.01);
1443 assert!((top - 19.5).abs() < 0.01);
1445 assert!((left + w - 18.5).abs() < 0.01);
1447 assert!((top + h - 36.5).abs() < 0.01);
1449 }
1450
1451 #[test]
1452 fn test_snap_glyph_no_snap_when_far() {
1453 let (left, top, w, h) = snap_glyph_to_cell(
1457 12.0, 24.0, 5.0, 10.0, 10.0, 20.0, 20.0, 40.0, 1.5, 0.5, );
1464
1465 assert_eq!(left, 12.0);
1467 assert_eq!(top, 24.0);
1468 assert_eq!(w, 5.0);
1469 assert_eq!(h, 10.0);
1470 }
1471
1472 #[test]
1473 fn test_snap_glyph_middle_snap() {
1474 let (_left, top, _w, h) = snap_glyph_to_cell(
1476 10.0, 20.0, 8.0, 9.8, 10.0, 20.0, 18.0, 40.0, 1.0, 0.0, );
1483
1484 assert!((top + h - 30.0).abs() < 0.01);
1486 }
1487}