1use crate::error::{DocError, DocResult};
12
13pub use super::vt_font_map::VtFontProvider;
14pub use super::vt_line::Line;
15pub use super::vt_section::Section;
16pub use super::vt_word_info::WordInfo;
17pub use super::vt_word_place::WordPlace;
18pub use super::vt_word_range::WordRange;
19
20pub type VtIterator<'a, P> = VtWordIterator<'a, P>;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
27pub enum Alignment {
28 #[default]
30 Left,
31 Center,
33 Right,
35}
36
37impl Alignment {
38 pub fn from_value(v: i32) -> Self {
40 match v {
41 1 => Self::Center,
42 2 => Self::Right,
43 _ => Self::Left,
44 }
45 }
46}
47
48const AUTO_FONT_SIZES: &[f32] = &[
50 4.0, 6.0, 8.0, 9.0, 10.0, 11.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, 36.0,
51 48.0, 72.0, 144.0,
52];
53
54pub struct VariableText<P: VtFontProvider> {
59 provider: P,
60 sections: Vec<Section>,
61 plate_rect: [f32; 4],
62 font_size: f32,
63 alignment: Alignment,
64 multi_line: bool,
65 auto_return: bool,
66 auto_font_size: bool,
67 char_array: Option<usize>,
68 password_char: Option<char>,
69 limit_char: Option<usize>,
70 content_rect: [f32; 4],
71}
72
73impl<P: VtFontProvider> VariableText<P> {
74 pub fn new(provider: P) -> Self {
76 Self {
77 provider,
78 sections: Vec::new(),
79 plate_rect: [0.0; 4],
80 font_size: 12.0,
81 alignment: Alignment::Left,
82 multi_line: false,
83 auto_return: true,
84 auto_font_size: false,
85 char_array: None,
86 password_char: None,
87 limit_char: None,
88 content_rect: [0.0; 4],
89 }
90 }
91
92 pub fn set_plate_rect(&mut self, rect: [f32; 4]) {
94 self.plate_rect = rect;
95 }
96
97 pub fn plate_rect(&self) -> [f32; 4] {
101 self.plate_rect
102 }
103
104 #[inline]
108 pub fn get_plate_rect(&self) -> [f32; 4] {
109 self.plate_rect()
110 }
111
112 pub fn set_font_size(&mut self, size: f32) {
114 self.font_size = size;
115 }
116
117 pub fn set_alignment(&mut self, alignment: Alignment) {
119 self.alignment = alignment;
120 }
121
122 pub fn set_multi_line(&mut self, multi_line: bool) {
124 self.multi_line = multi_line;
125 }
126
127 pub fn set_auto_return(&mut self, auto_return: bool) {
129 self.auto_return = auto_return;
130 }
131
132 pub fn set_auto_font_size(&mut self, auto: bool) {
134 self.auto_font_size = auto;
135 }
136
137 pub fn set_char_array(&mut self, count: Option<usize>) {
139 self.char_array = count;
140 }
141
142 pub fn set_password_char(&mut self, ch: Option<char>) {
144 self.password_char = ch;
145 }
146
147 pub fn set_limit_char(&mut self, limit: Option<usize>) {
149 self.limit_char = limit;
150 }
151
152 pub fn set_text(&mut self, text: &str) {
157 self.sections.clear();
158
159 let effective_text: String = if let Some(limit) = self.limit_char {
160 text.chars().take(limit).collect()
161 } else {
162 text.to_string()
163 };
164
165 let display_text = if let Some(pw) = self.password_char {
166 pw.to_string().repeat(effective_text.chars().count())
167 } else {
168 effective_text
169 };
170
171 let mut current_section = Section {
172 words: Vec::new(),
173 lines: Vec::new(),
174 rect: [0.0; 4],
175 };
176
177 let mut chars = display_text.chars().peekable();
178 while let Some(ch) = chars.next() {
179 if ch == '\r' {
180 if chars.peek() == Some(&'\n') {
182 chars.next(); }
184 self.sections.push(current_section);
185 current_section = Section {
186 words: Vec::new(),
187 lines: Vec::new(),
188 rect: [0.0; 4],
189 };
190 } else if ch == '\n' {
191 self.sections.push(current_section);
192 current_section = Section {
193 words: Vec::new(),
194 lines: Vec::new(),
195 rect: [0.0; 4],
196 };
197 } else {
198 current_section.words.push(WordInfo {
199 character: ch,
200 font_index: 0,
201 position_x: 0.0,
202 width: 0.0,
203 });
204 }
205 }
206 self.sections.push(current_section);
207 }
208
209 pub fn rearrange(&mut self) {
213 if self.auto_font_size {
214 self.font_size = self.find_auto_font_size();
215 }
216
217 let plate_width = self.plate_rect[2] - self.plate_rect[0];
218
219 let mut total_y = self.plate_rect[3]; for section in &mut self.sections {
222 for word in &mut section.words {
224 word.width = self.provider.char_width(
225 word.font_index,
226 word.character as u16,
227 self.font_size,
228 );
229 }
230
231 section.lines.clear();
232
233 if let Some(cell_count) = self.char_array {
234 let cell_width = if cell_count > 0 {
236 plate_width / cell_count as f32
237 } else {
238 plate_width
239 };
240 let ascent = self.provider.ascent(0, self.font_size);
241 let descent = self.provider.descent(0, self.font_size);
242
243 let mut x_pos = 0.0;
244 for (i, word) in section.words.iter_mut().enumerate() {
245 if i >= cell_count {
246 break;
247 }
248 word.position_x = x_pos + (cell_width - word.width) / 2.0;
250 x_pos += cell_width;
251 }
252
253 let line_width = section
254 .words
255 .iter()
256 .take(cell_count)
257 .next_back()
258 .map(|w| w.position_x + w.width)
259 .unwrap_or(0.0);
260
261 let word_count = section.words.len().min(cell_count);
262 total_y -= ascent;
263 section.lines.push(Line {
264 word_start: 0,
265 word_count,
266 x: 0.0,
267 y: total_y,
268 width: line_width,
269 ascent,
270 descent,
271 });
272 total_y += descent;
273 } else {
274 let ascent = self.provider.ascent(0, self.font_size);
276 let descent = self.provider.descent(0, self.font_size);
277
278 let mut line_start = 0;
279 let mut line_width = 0.0_f32;
280 let mut x_pos = 0.0_f32;
281
282 for (i, word) in section.words.iter_mut().enumerate() {
283 let char_width = word.width;
284
285 let should_break = self.multi_line
287 && self.auto_return
288 && line_width + char_width > plate_width
289 && i > line_start;
290
291 if should_break {
292 let word_count = i - line_start;
294 total_y -= ascent;
295 section.lines.push(Line {
296 word_start: line_start,
297 word_count,
298 x: 0.0,
299 y: total_y,
300 width: line_width,
301 ascent,
302 descent,
303 });
304 total_y += descent;
305
306 line_start = i;
308 line_width = 0.0;
309 x_pos = 0.0;
310 }
311
312 word.position_x = x_pos;
313 x_pos += char_width;
314 line_width += char_width;
315 }
316
317 let word_count = section.words.len() - line_start;
319 if word_count > 0 || section.lines.is_empty() {
320 total_y -= ascent;
321 section.lines.push(Line {
322 word_start: line_start,
323 word_count,
324 x: 0.0,
325 y: total_y,
326 width: line_width,
327 ascent,
328 descent,
329 });
330 total_y += descent;
331 }
332 }
333
334 for line in &mut section.lines {
336 line.x = match self.alignment {
337 Alignment::Left => 0.0,
338 Alignment::Center => (plate_width - line.width) / 2.0,
339 Alignment::Right => plate_width - line.width,
340 };
341 }
342
343 if let (Some(first), Some(last)) = (section.lines.first(), section.lines.last()) {
345 section.rect = [
346 self.plate_rect[0],
347 last.y + last.descent,
348 self.plate_rect[2],
349 first.y + first.ascent,
350 ];
351 }
352 }
353
354 if let (Some(first_sec), Some(last_sec)) = (self.sections.first(), self.sections.last()) {
356 self.content_rect = [
357 self.plate_rect[0],
358 last_sec.rect[1],
359 self.plate_rect[2],
360 first_sec.rect[3],
361 ];
362 }
363 }
364
365 pub fn content_rect(&self) -> [f32; 4] {
367 self.content_rect
368 }
369
370 #[inline]
374 pub fn get_content_rect(&self) -> [f32; 4] {
375 self.content_rect()
376 }
377
378 pub fn sections(&self) -> &[Section] {
380 &self.sections
381 }
382
383 pub fn font_size(&self) -> f32 {
385 self.font_size
386 }
387
388 pub fn hit_test(&self, x: f32, y: f32) -> Option<WordPlace> {
390 let rel_x = x - self.plate_rect[0];
391 let rel_y = y;
392
393 for (si, section) in self.sections.iter().enumerate() {
394 for (li, line) in section.lines.iter().enumerate() {
395 let line_top = line.y + line.ascent;
396 let line_bottom = line.y + line.descent;
397
398 if rel_y <= line_top && rel_y >= line_bottom {
399 let line_x = rel_x - line.x;
401 let end = line.word_start + line.word_count;
402 for wi in line.word_start..end {
403 let word = §ion.words[wi];
404 if line_x <= word.position_x + word.width / 2.0 {
405 return Some(WordPlace {
406 section: si,
407 line: li,
408 word: wi - line.word_start,
409 });
410 }
411 }
412 return Some(WordPlace {
414 section: si,
415 line: li,
416 word: line.word_count.saturating_sub(1),
417 });
418 }
419 }
420 }
421 if let Some(last_sec) = self.sections.last() {
423 if let Some(last_line) = last_sec.lines.last() {
424 return Some(WordPlace {
425 section: self.sections.len() - 1,
426 line: last_sec.lines.len() - 1,
427 word: last_line.word_count.saturating_sub(1),
428 });
429 }
430 }
431 None
432 }
433
434 pub fn word_place_to_index(&self, place: &WordPlace) -> usize {
436 let mut index = 0;
437 for (si, section) in self.sections.iter().enumerate() {
438 if si < place.section {
439 index += section.words.len();
440 index += 1; } else {
442 if let Some(line) = section.lines.get(place.line) {
444 index += line.word_start + place.word;
445 }
446 break;
447 }
448 }
449 index
450 }
451
452 pub fn insert(&mut self, place: &WordPlace, text: &str) -> DocResult<()> {
457 let si = place.section;
458 if si >= self.sections.len() {
459 return Err(DocError::InvalidIndex(si));
460 }
461
462 let abs_idx = {
464 let section = &self.sections[si];
465 if section.lines.is_empty() {
466 0
467 } else {
468 let li = place.line.min(section.lines.len() - 1);
469 let line = §ion.lines[li];
470 let wi = place.word.min(line.word_count);
471 line.word_start + wi
472 }
473 };
474
475 let font_index = self.sections[si]
477 .words
478 .get(abs_idx)
479 .or_else(|| self.sections[si].words.get(abs_idx.saturating_sub(1)))
480 .map(|w| w.font_index)
481 .unwrap_or(0);
482
483 for (offset, ch) in text.chars().enumerate() {
485 self.sections[si].words.insert(
486 abs_idx + offset,
487 WordInfo {
488 character: ch,
489 font_index,
490 position_x: 0.0,
491 width: 0.0,
492 },
493 );
494 }
495
496 self.rearrange();
497 Ok(())
498 }
499
500 pub fn delete(&mut self, range: &WordRange) -> DocResult<()> {
506 let si = range.begin.section;
507 if si >= self.sections.len() {
508 return Err(DocError::InvalidIndex(si));
509 }
510 if range.end.section != si {
511 return Err(DocError::TypeMismatch {
512 expected: "same-section range".into(),
513 got: "cross-section range".into(),
514 });
515 }
516
517 let begin_abs = {
519 let section = &self.sections[si];
520 if section.lines.is_empty() {
521 return Ok(());
522 }
523 let li = range.begin.line.min(section.lines.len() - 1);
524 let line = §ion.lines[li];
525 line.word_start + range.begin.word.min(line.word_count)
526 };
527 let end_abs = {
528 let section = &self.sections[si];
529 let li = range.end.line.min(section.lines.len() - 1);
530 let line = §ion.lines[li];
531 (line.word_start + range.end.word.min(line.word_count)).min(section.words.len())
532 };
533
534 if begin_abs < end_abs {
535 self.sections[si].words.drain(begin_abs..end_abs);
536 self.rearrange();
537 }
538 Ok(())
539 }
540
541 pub fn iter_lines(&self) -> impl Iterator<Item = (usize, &Line)> {
543 self.sections
544 .iter()
545 .flat_map(|section| section.lines.iter())
546 .enumerate()
547 }
548
549 pub fn iter_words(&self) -> impl Iterator<Item = &WordInfo> {
551 self.sections
552 .iter()
553 .flat_map(|section| section.words.iter())
554 }
555
556 pub fn total_lines(&self) -> usize {
558 self.sections.iter().map(|s| s.lines.len()).sum()
559 }
560
561 pub fn total_words(&self) -> usize {
563 self.sections.iter().map(|s| s.words.len()).sum()
564 }
565
566 pub fn get_total_words(&self) -> i32 {
573 self.total_words() as i32
574 }
575
576 pub fn begin_word_place(&self) -> WordPlace {
578 WordPlace::default()
579 }
580
581 #[inline]
583 pub fn get_begin_word_place(&self) -> WordPlace {
584 self.begin_word_place()
585 }
586
587 pub fn end_word_place(&self) -> WordPlace {
589 if self.sections.is_empty() {
590 return WordPlace::default();
591 }
592 let si = self.sections.len() - 1;
593 let section = &self.sections[si];
594 if section.lines.is_empty() {
595 return WordPlace {
596 section: si,
597 line: 0,
598 word: 0,
599 };
600 }
601 let li = section.lines.len() - 1;
602 let word_count = section.lines[li].word_count;
603 WordPlace {
604 section: si,
605 line: li,
606 word: word_count.saturating_sub(1),
607 }
608 }
609
610 #[inline]
612 pub fn get_end_word_place(&self) -> WordPlace {
613 self.end_word_place()
614 }
615
616 pub fn prev_word_place(&self, place: &WordPlace) -> WordPlace {
620 if self.sections.is_empty() {
621 return *place;
622 }
623 let mut si = place.section.min(self.sections.len() - 1);
624 let section = &self.sections[si];
625 if section.lines.is_empty() {
626 if si == 0 {
627 return WordPlace::default();
628 }
629 si -= 1;
630 return self.section_end_place_for(si);
631 }
632 let mut li = place.line.min(section.lines.len() - 1);
633 let mut wi = place.word;
634
635 if wi > 0 {
636 return WordPlace {
637 section: si,
638 line: li,
639 word: wi - 1,
640 };
641 }
642 if li > 0 {
643 li -= 1;
644 wi = self.sections[si].lines[li].word_count.saturating_sub(1);
645 return WordPlace {
646 section: si,
647 line: li,
648 word: wi,
649 };
650 }
651 if si > 0 {
652 si -= 1;
653 return self.section_end_place_for(si);
654 }
655 WordPlace::default()
656 }
657
658 #[inline]
660 pub fn get_prev_word_place(&self, place: &WordPlace) -> WordPlace {
661 self.prev_word_place(place)
662 }
663
664 pub fn next_word_place(&self, place: &WordPlace) -> WordPlace {
668 if self.sections.is_empty() {
669 return *place;
670 }
671 let si = place.section.min(self.sections.len() - 1);
672 let section = &self.sections[si];
673 if section.lines.is_empty() {
674 if si + 1 < self.sections.len() {
675 return WordPlace {
676 section: si + 1,
677 line: 0,
678 word: 0,
679 };
680 }
681 return *place;
682 }
683 let li = place.line.min(section.lines.len() - 1);
684 let line = §ion.lines[li];
685 let wi = place.word;
686
687 if wi + 1 < line.word_count {
688 return WordPlace {
689 section: si,
690 line: li,
691 word: wi + 1,
692 };
693 }
694 if li + 1 < section.lines.len() {
695 return WordPlace {
696 section: si,
697 line: li + 1,
698 word: 0,
699 };
700 }
701 if si + 1 < self.sections.len() {
702 return WordPlace {
703 section: si + 1,
704 line: 0,
705 word: 0,
706 };
707 }
708 *place
709 }
710
711 #[inline]
713 pub fn get_next_word_place(&self, place: &WordPlace) -> WordPlace {
714 self.next_word_place(place)
715 }
716
717 pub fn search_word_place(&self, point: (f32, f32)) -> WordPlace {
721 self.hit_test(point.0, point.1)
722 .unwrap_or_else(|| self.end_word_place())
723 }
724
725 pub fn up_word_place(&self, place: &WordPlace, point: (f32, f32)) -> WordPlace {
729 if self.sections.is_empty() {
730 return *place;
731 }
732 let si = place.section.min(self.sections.len() - 1);
733 let section = &self.sections[si];
734 if section.lines.is_empty() {
735 return *place;
736 }
737 let li = place.line.min(section.lines.len() - 1);
738
739 let (target_si, target_li) = if li > 0 {
740 (si, li - 1)
741 } else if si > 0 {
742 let prev_si = si - 1;
743 let prev_lines = self.sections[prev_si].lines.len();
744 if prev_lines == 0 {
745 return *place;
746 }
747 (prev_si, prev_lines - 1)
748 } else {
749 return *place;
750 };
751
752 self.closest_word_on_line(target_si, target_li, point.0)
753 }
754
755 #[inline]
757 pub fn get_up_word_place(&self, place: &WordPlace, point: (f32, f32)) -> WordPlace {
758 self.up_word_place(place, point)
759 }
760
761 pub fn down_word_place(&self, place: &WordPlace, point: (f32, f32)) -> WordPlace {
765 if self.sections.is_empty() {
766 return *place;
767 }
768 let si = place.section.min(self.sections.len() - 1);
769 let section = &self.sections[si];
770 if section.lines.is_empty() {
771 return *place;
772 }
773 let li = place.line.min(section.lines.len() - 1);
774
775 let (target_si, target_li) = if li + 1 < section.lines.len() {
776 (si, li + 1)
777 } else if si + 1 < self.sections.len() {
778 let next_si = si + 1;
779 if self.sections[next_si].lines.is_empty() {
780 return *place;
781 }
782 (next_si, 0)
783 } else {
784 return *place;
785 };
786
787 self.closest_word_on_line(target_si, target_li, point.0)
788 }
789
790 #[inline]
792 pub fn get_down_word_place(&self, place: &WordPlace, point: (f32, f32)) -> WordPlace {
793 self.down_word_place(place, point)
794 }
795
796 pub fn line_begin_place(&self, place: &WordPlace) -> WordPlace {
800 if self.sections.is_empty() {
801 return *place;
802 }
803 let si = place.section.min(self.sections.len() - 1);
804 let section = &self.sections[si];
805 if section.lines.is_empty() {
806 return WordPlace {
807 section: si,
808 line: 0,
809 word: 0,
810 };
811 }
812 let li = place.line.min(section.lines.len() - 1);
813 WordPlace {
814 section: si,
815 line: li,
816 word: 0,
817 }
818 }
819
820 #[inline]
822 pub fn get_line_begin_place(&self, place: &WordPlace) -> WordPlace {
823 self.line_begin_place(place)
824 }
825
826 pub fn line_end_place(&self, place: &WordPlace) -> WordPlace {
830 if self.sections.is_empty() {
831 return *place;
832 }
833 let si = place.section.min(self.sections.len() - 1);
834 let section = &self.sections[si];
835 if section.lines.is_empty() {
836 return WordPlace {
837 section: si,
838 line: 0,
839 word: 0,
840 };
841 }
842 let li = place.line.min(section.lines.len() - 1);
843 let word_count = section.lines[li].word_count;
844 WordPlace {
845 section: si,
846 line: li,
847 word: word_count.saturating_sub(1),
848 }
849 }
850
851 #[inline]
853 pub fn get_line_end_place(&self, place: &WordPlace) -> WordPlace {
854 self.line_end_place(place)
855 }
856
857 pub fn section_begin_place(&self, place: &WordPlace) -> WordPlace {
861 let si = if self.sections.is_empty() {
862 0
863 } else {
864 place.section.min(self.sections.len() - 1)
865 };
866 WordPlace {
867 section: si,
868 line: 0,
869 word: 0,
870 }
871 }
872
873 #[inline]
875 pub fn get_section_begin_place(&self, place: &WordPlace) -> WordPlace {
876 self.section_begin_place(place)
877 }
878
879 pub fn section_end_place(&self, place: &WordPlace) -> WordPlace {
883 if self.sections.is_empty() {
884 return WordPlace::default();
885 }
886 let si = place.section.min(self.sections.len() - 1);
887 self.section_end_place_for(si)
888 }
889
890 #[inline]
892 pub fn get_section_end_place(&self, place: &WordPlace) -> WordPlace {
893 self.section_end_place(place)
894 }
895
896 pub fn update_word_place(&self, place: &mut WordPlace) {
900 if self.sections.is_empty() {
901 *place = WordPlace::default();
902 return;
903 }
904 place.section = place.section.min(self.sections.len() - 1);
905 let section = &self.sections[place.section];
906 if section.lines.is_empty() {
907 place.line = 0;
908 place.word = 0;
909 return;
910 }
911 place.line = place.line.min(section.lines.len() - 1);
912 let word_count = section.lines[place.line].word_count;
913 if word_count == 0 {
914 place.word = 0;
915 } else {
916 place.word = place.word.min(word_count - 1);
917 }
918 }
919
920 #[inline]
925 pub fn word_place_to_word_index(&self, place: &WordPlace) -> usize {
926 self.word_place_to_index(place)
927 }
928
929 pub fn word_index_to_word_place(&self, index: usize) -> WordPlace {
933 let mut remaining = index;
934 for (si, section) in self.sections.iter().enumerate() {
935 let words_in_section = section.words.len();
936 if remaining < words_in_section {
937 for (li, line) in section.lines.iter().enumerate() {
938 if remaining < line.word_start + line.word_count {
939 let word = remaining.saturating_sub(line.word_start);
940 return WordPlace {
941 section: si,
942 line: li,
943 word,
944 };
945 }
946 }
947 return self.section_end_place_for(si);
948 }
949 remaining -= words_in_section;
950 if remaining == 0 && si + 1 < self.sections.len() {
951 return WordPlace {
952 section: si + 1,
953 line: 0,
954 word: 0,
955 };
956 }
957 if si + 1 < self.sections.len() {
958 remaining = remaining.saturating_sub(1);
959 }
960 }
961 self.end_word_place()
962 }
963
964 pub fn plate_width(&self) -> f32 {
972 self.plate_rect[2] - self.plate_rect[0]
973 }
974
975 #[inline]
977 pub fn get_plate_width(&self) -> f32 {
978 self.plate_width()
979 }
980
981 pub fn plate_height(&self) -> f32 {
985 self.plate_rect[3] - self.plate_rect[1]
986 }
987
988 #[inline]
990 pub fn get_plate_height(&self) -> f32 {
991 self.plate_height()
992 }
993
994 pub fn begin_text_point(&self) -> (f32, f32) {
998 (self.plate_rect[0], self.plate_rect[3])
999 }
1000
1001 #[inline]
1003 pub fn get_bt_point(&self) -> (f32, f32) {
1004 self.begin_text_point()
1005 }
1006
1007 pub fn end_text_point(&self) -> (f32, f32) {
1011 (self.plate_rect[2], self.plate_rect[1])
1012 }
1013
1014 #[inline]
1016 pub fn get_et_point(&self) -> (f32, f32) {
1017 self.end_text_point()
1018 }
1019
1020 pub fn in_to_out(&self, point: (f32, f32)) -> (f32, f32) {
1024 (point.0 + self.plate_rect[0], self.plate_rect[3] - point.1)
1025 }
1026
1027 pub fn out_to_in(&self, point: (f32, f32)) -> (f32, f32) {
1031 (point.0 - self.plate_rect[0], self.plate_rect[3] - point.1)
1032 }
1033
1034 pub fn password_char(&self) -> Option<char> {
1040 self.password_char
1041 }
1042
1043 #[inline]
1045 pub fn get_password_char(&self) -> Option<char> {
1046 self.password_char()
1047 }
1048
1049 #[deprecated(
1055 since = "0.1.0",
1056 note = "use password_char() or get_sub_word() instead"
1057 )]
1058 #[inline]
1059 pub fn sub_word(&self) -> Option<char> {
1060 self.password_char()
1061 }
1062
1063 #[inline]
1067 pub fn get_sub_word(&self) -> Option<char> {
1068 self.password_char()
1069 }
1070
1071 #[inline]
1073 pub fn get_font_size(&self) -> f32 {
1074 self.font_size()
1075 }
1076
1077 pub fn alignment(&self) -> Alignment {
1079 self.alignment
1080 }
1081
1082 #[inline]
1084 pub fn get_alignment(&self) -> Alignment {
1085 self.alignment()
1086 }
1087
1088 pub fn is_multi_line(&self) -> bool {
1090 self.multi_line
1091 }
1092
1093 pub fn is_auto_return(&self) -> bool {
1095 self.auto_return
1096 }
1097
1098 pub fn char_array(&self) -> Option<usize> {
1103 self.char_array
1104 }
1105
1106 #[inline]
1110 pub fn get_char_array(&self) -> Option<usize> {
1111 self.char_array()
1112 }
1113
1114 pub fn limit_char(&self) -> Option<usize> {
1119 self.limit_char
1120 }
1121
1122 #[inline]
1126 pub fn get_limit_char(&self) -> Option<usize> {
1127 self.limit_char()
1128 }
1129
1130 pub fn line_leading(&self) -> f32 {
1139 let asc = self.provider.ascent(0, self.font_size);
1140 let desc = self.provider.descent(0, self.font_size);
1141 asc - desc
1142 }
1143
1144 #[inline]
1146 pub fn get_line_leading(&self) -> f32 {
1147 self.line_leading()
1148 }
1149
1150 pub fn line_ascent(&self) -> f32 {
1154 self.provider.ascent(0, self.font_size)
1155 }
1156
1157 pub fn line_descent(&self) -> f32 {
1161 self.provider.descent(0, self.font_size)
1162 }
1163
1164 pub fn word_iterator(&self) -> VtWordIterator<'_, P> {
1168 VtWordIterator::new(self)
1169 }
1170
1171 fn section_end_place_for(&self, si: usize) -> WordPlace {
1177 if si >= self.sections.len() {
1178 return self.end_word_place();
1179 }
1180 let section = &self.sections[si];
1181 if section.lines.is_empty() {
1182 return WordPlace {
1183 section: si,
1184 line: 0,
1185 word: 0,
1186 };
1187 }
1188 let li = section.lines.len() - 1;
1189 let word_count = section.lines[li].word_count;
1190 WordPlace {
1191 section: si,
1192 line: li,
1193 word: word_count.saturating_sub(1),
1194 }
1195 }
1196
1197 fn closest_word_on_line(&self, si: usize, li: usize, x: f32) -> WordPlace {
1199 let section = &self.sections[si];
1200 let line = §ion.lines[li];
1201 if line.word_count == 0 {
1202 return WordPlace {
1203 section: si,
1204 line: li,
1205 word: 0,
1206 };
1207 }
1208 let rel_x = x - self.plate_rect[0] - line.x;
1209 let end = line.word_start + line.word_count;
1210 for wi in line.word_start..end {
1211 let word = §ion.words[wi];
1212 if rel_x <= word.position_x + word.width / 2.0 {
1213 return WordPlace {
1214 section: si,
1215 line: li,
1216 word: wi - line.word_start,
1217 };
1218 }
1219 }
1220 WordPlace {
1221 section: si,
1222 line: li,
1223 word: line.word_count - 1,
1224 }
1225 }
1226
1227 fn find_auto_font_size(&self) -> f32 {
1229 let plate_width = self.plate_rect[2] - self.plate_rect[0];
1230 let plate_height = self.plate_rect[3] - self.plate_rect[1];
1231
1232 if plate_width <= 0.0 || plate_height <= 0.0 {
1233 return 1.0;
1234 }
1235
1236 let total_chars: usize = self.sections.iter().map(|s| s.words.len()).sum();
1237 if total_chars == 0 {
1238 return self.font_size;
1239 }
1240
1241 let mut best = AUTO_FONT_SIZES[0];
1243
1244 for &size in AUTO_FONT_SIZES {
1245 if self.text_fits_at_size(size, plate_width, plate_height) {
1246 best = size;
1247 } else {
1248 break;
1249 }
1250 }
1251
1252 best
1253 }
1254
1255 fn text_fits_at_size(&self, size: f32, plate_width: f32, plate_height: f32) -> bool {
1257 let ascent = self.provider.ascent(0, size);
1258 let descent = self.provider.descent(0, size);
1259 let line_height = ascent - descent;
1260
1261 if line_height <= 0.0 {
1262 return false;
1263 }
1264
1265 let mut total_lines = 0_usize;
1266
1267 for section in &self.sections {
1268 if section.words.is_empty() {
1269 total_lines += 1;
1270 continue;
1271 }
1272
1273 let mut line_width = 0.0_f32;
1274 let mut lines_in_section = 1_usize;
1275
1276 for word in §ion.words {
1277 let w = self
1278 .provider
1279 .char_width(word.font_index, word.character as u16, size);
1280 if self.multi_line
1281 && self.auto_return
1282 && line_width + w > plate_width
1283 && line_width > 0.0
1284 {
1285 lines_in_section += 1;
1286 line_width = w;
1287 } else {
1288 line_width += w;
1289 }
1290
1291 if !self.multi_line && line_width > plate_width {
1293 return false;
1294 }
1295 }
1296
1297 total_lines += lines_in_section;
1298 }
1299
1300 let total_height = total_lines as f32 * line_height;
1301 total_height <= plate_height
1302 }
1303}
1304
1305pub struct VtWordIterator<'a, P: VtFontProvider> {
1316 vt: &'a VariableText<P>,
1317 current: WordPlace,
1318}
1319
1320impl<'a, P: VtFontProvider> VtWordIterator<'a, P> {
1321 pub fn new(vt: &'a VariableText<P>) -> Self {
1323 Self {
1324 vt,
1325 current: vt.begin_word_place(),
1326 }
1327 }
1328
1329 pub fn set_at_index(&mut self, word_index: i32) {
1333 let idx = if word_index < 0 {
1334 0
1335 } else {
1336 word_index as usize
1337 };
1338 self.current = self.vt.word_index_to_word_place(idx);
1339 }
1340
1341 pub fn set_at_place(&mut self, place: WordPlace) {
1345 self.current = place;
1346 }
1347
1348 pub fn next_word(&mut self) -> bool {
1353 let next = self.vt.next_word_place(&self.current);
1354 if next == self.current {
1355 return false;
1356 }
1357 self.current = next;
1358 true
1359 }
1360
1361 pub fn next_line(&mut self) -> bool {
1366 if self.vt.sections.is_empty() {
1367 return false;
1368 }
1369 let si = self.current.section.min(self.vt.sections.len() - 1);
1370 let section = &self.vt.sections[si];
1371 if section.lines.is_empty() {
1372 return false;
1373 }
1374 let li = self.current.line.min(section.lines.len() - 1);
1375 if li + 1 < section.lines.len() {
1376 self.current = WordPlace {
1377 section: si,
1378 line: li + 1,
1379 word: 0,
1380 };
1381 return true;
1382 }
1383 if si + 1 < self.vt.sections.len() {
1384 self.current = WordPlace {
1385 section: si + 1,
1386 line: 0,
1387 word: 0,
1388 };
1389 return true;
1390 }
1391 false
1392 }
1393
1394 pub fn word_place(&self) -> &WordPlace {
1398 &self.current
1399 }
1400
1401 #[inline]
1403 pub fn get_word_place(&self) -> &WordPlace {
1404 self.word_place()
1405 }
1406}
1407
1408#[cfg(test)]
1409mod tests {
1410 use super::*;
1411
1412 struct FixedWidthProvider {
1414 char_width: f32,
1415 ascent: f32,
1416 descent: f32,
1417 }
1418
1419 impl FixedWidthProvider {
1420 fn new(char_width: f32) -> Self {
1421 Self {
1422 char_width,
1423 ascent: 10.0,
1424 descent: -3.0,
1425 }
1426 }
1427 }
1428
1429 impl VtFontProvider for FixedWidthProvider {
1430 fn char_width(&self, _font_index: usize, _word: u16, font_size: f32) -> f32 {
1431 self.char_width * (font_size / 12.0)
1432 }
1433 fn ascent(&self, _font_index: usize, font_size: f32) -> f32 {
1434 self.ascent * (font_size / 12.0)
1435 }
1436 fn descent(&self, _font_index: usize, font_size: f32) -> f32 {
1437 self.descent * (font_size / 12.0)
1438 }
1439 }
1440
1441 fn make_vt(provider: FixedWidthProvider) -> VariableText<FixedWidthProvider> {
1442 let mut vt = VariableText::new(provider);
1443 vt.set_plate_rect([0.0, 0.0, 200.0, 100.0]);
1444 vt.set_font_size(12.0);
1445 vt
1446 }
1447
1448 #[test]
1449 fn test_empty_text() {
1450 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1451 vt.set_text("");
1452 vt.rearrange();
1453 assert_eq!(vt.sections().len(), 1);
1454 assert_eq!(vt.sections()[0].words.len(), 0);
1455 assert_eq!(vt.sections()[0].lines.len(), 1);
1456 }
1457
1458 #[test]
1459 fn test_single_line_text() {
1460 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1461 vt.set_text("Hello");
1462 vt.rearrange();
1463 assert_eq!(vt.sections().len(), 1);
1464 assert_eq!(vt.sections()[0].words.len(), 5);
1465 assert_eq!(vt.sections()[0].lines.len(), 1);
1466 assert_eq!(vt.sections()[0].lines[0].word_count, 5);
1467 }
1468
1469 #[test]
1470 fn test_multi_section_text() {
1471 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1472 vt.set_text("Line1\nLine2\nLine3");
1473 vt.rearrange();
1474 assert_eq!(vt.sections().len(), 3);
1475 assert_eq!(vt.sections()[0].words.len(), 5);
1476 assert_eq!(vt.sections()[1].words.len(), 5);
1477 assert_eq!(vt.sections()[2].words.len(), 5);
1478 }
1479
1480 #[test]
1481 fn test_line_breaking() {
1482 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1483 vt.set_multi_line(true);
1484 vt.set_auto_return(true);
1485 let text: String = "A".repeat(50);
1488 vt.set_text(&text);
1489 vt.rearrange();
1490 assert_eq!(vt.sections()[0].lines.len(), 2);
1491 }
1492
1493 #[test]
1494 fn test_no_line_breaking_single_line_mode() {
1495 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1496 vt.set_multi_line(false);
1497 let text: String = "A".repeat(50);
1498 vt.set_text(&text);
1499 vt.rearrange();
1500 assert_eq!(vt.sections()[0].lines.len(), 1);
1501 assert_eq!(vt.sections()[0].lines[0].word_count, 50);
1502 }
1503
1504 #[test]
1505 fn test_alignment_left() {
1506 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1507 vt.set_alignment(Alignment::Left);
1508 vt.set_text("Hi");
1509 vt.rearrange();
1510 assert!((vt.sections()[0].lines[0].x - 0.0).abs() < 0.01);
1511 }
1512
1513 #[test]
1514 fn test_alignment_center() {
1515 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1516 vt.set_alignment(Alignment::Center);
1517 vt.set_text("Hi");
1518 vt.rearrange();
1519 let line = &vt.sections()[0].lines[0];
1520 assert!((line.x - 94.0).abs() < 0.01);
1522 }
1523
1524 #[test]
1525 fn test_alignment_right() {
1526 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1527 vt.set_alignment(Alignment::Right);
1528 vt.set_text("Hi");
1529 vt.rearrange();
1530 let line = &vt.sections()[0].lines[0];
1531 assert!((line.x - 188.0).abs() < 0.01);
1533 }
1534
1535 #[test]
1536 fn test_alignment_from_value() {
1537 assert_eq!(Alignment::from_value(0), Alignment::Left);
1538 assert_eq!(Alignment::from_value(1), Alignment::Center);
1539 assert_eq!(Alignment::from_value(2), Alignment::Right);
1540 assert_eq!(Alignment::from_value(99), Alignment::Left);
1541 }
1542
1543 #[test]
1544 fn test_char_array_mode() {
1545 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1546 vt.set_char_array(Some(10));
1547 vt.set_text("ABC");
1548 vt.rearrange();
1549 let section = &vt.sections()[0];
1550 assert_eq!(section.lines.len(), 1);
1551 assert_eq!(section.lines[0].word_count, 3);
1552 let first_word = §ion.words[0];
1554 let expected_x = (20.0 - 6.0) / 2.0; assert!((first_word.position_x - expected_x).abs() < 0.01);
1556 }
1557
1558 #[test]
1559 fn test_char_array_limits_to_count() {
1560 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1561 vt.set_char_array(Some(3));
1562 vt.set_text("ABCDE");
1563 vt.rearrange();
1564 assert_eq!(vt.sections()[0].lines[0].word_count, 3);
1565 }
1566
1567 #[test]
1568 fn test_password_masking() {
1569 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1570 vt.set_password_char(Some('*'));
1571 vt.set_text("secret");
1572 vt.rearrange();
1573 let section = &vt.sections()[0];
1574 assert_eq!(section.words.len(), 6);
1575 for word in §ion.words {
1576 assert_eq!(word.character, '*');
1577 }
1578 }
1579
1580 #[test]
1581 fn test_limit_char() {
1582 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1583 vt.set_limit_char(Some(5));
1584 vt.set_text("Hello World");
1585 vt.rearrange();
1586 assert_eq!(vt.sections()[0].words.len(), 5);
1587 }
1588
1589 #[test]
1590 fn test_auto_font_size() {
1591 let mut vt = VariableText::new(FixedWidthProvider::new(6.0));
1592 vt.set_plate_rect([0.0, 0.0, 100.0, 20.0]);
1593 vt.set_auto_font_size(true);
1594 vt.set_text("Hello World This Is Long");
1595 vt.rearrange();
1596 let content_height = vt.content_rect()[3] - vt.content_rect()[1];
1598 assert!(content_height <= 20.0 + 1.0); }
1600
1601 #[test]
1602 fn test_auto_font_size_empty() {
1603 let mut vt = VariableText::new(FixedWidthProvider::new(6.0));
1604 vt.set_plate_rect([0.0, 0.0, 100.0, 20.0]);
1605 vt.set_font_size(12.0);
1606 vt.set_auto_font_size(true);
1607 vt.set_text("");
1608 vt.rearrange();
1609 assert_eq!(vt.font_size(), 12.0);
1610 }
1611
1612 #[test]
1613 fn test_hit_test_first_char() {
1614 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1615 vt.set_text("Hello");
1616 vt.rearrange();
1617 let line = &vt.sections()[0].lines[0];
1618 let y = line.y + line.ascent / 2.0;
1619 let place = vt.hit_test(1.0, y);
1620 assert!(place.is_some());
1621 assert_eq!(place.unwrap().word, 0);
1622 }
1623
1624 #[test]
1625 fn test_hit_test_past_end() {
1626 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1627 vt.set_text("Hi");
1628 vt.rearrange();
1629 let line = &vt.sections()[0].lines[0];
1630 let y = line.y + line.ascent / 2.0;
1631 let place = vt.hit_test(100.0, y);
1632 assert!(place.is_some());
1633 assert_eq!(place.unwrap().word, 1); }
1635
1636 #[test]
1637 fn test_word_place_to_index_first() {
1638 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1639 vt.set_text("Hello");
1640 vt.rearrange();
1641 let idx = vt.word_place_to_index(&WordPlace {
1642 section: 0,
1643 line: 0,
1644 word: 0,
1645 });
1646 assert_eq!(idx, 0);
1647 }
1648
1649 #[test]
1650 fn test_word_place_to_index_second_section() {
1651 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1652 vt.set_text("AB\nCD");
1653 vt.rearrange();
1654 let idx = vt.word_place_to_index(&WordPlace {
1655 section: 1,
1656 line: 0,
1657 word: 0,
1658 });
1659 assert_eq!(idx, 3); }
1661
1662 #[test]
1663 fn test_word_place_to_index_mid() {
1664 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1665 vt.set_text("Hello");
1666 vt.rearrange();
1667 let idx = vt.word_place_to_index(&WordPlace {
1668 section: 0,
1669 line: 0,
1670 word: 3,
1671 });
1672 assert_eq!(idx, 3);
1673 }
1674
1675 #[test]
1676 fn test_content_rect_non_empty() {
1677 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1678 vt.set_text("Hello");
1679 vt.rearrange();
1680 let cr = vt.content_rect();
1681 assert!(cr[3] > cr[1]); }
1683
1684 #[test]
1685 fn test_multi_line_many_breaks() {
1686 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1687 vt.set_multi_line(true);
1688 vt.set_auto_return(true);
1689 let text: String = "X".repeat(100);
1692 vt.set_text(&text);
1693 vt.rearrange();
1694 let line_count = vt.sections()[0].lines.len();
1695 assert!(line_count >= 3);
1696 assert!(line_count <= 4);
1697 }
1698
1699 #[test]
1700 fn test_word_info_positions_increase() {
1701 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1702 vt.set_text("ABCDE");
1703 vt.rearrange();
1704 let words = &vt.sections()[0].words;
1705 for i in 1..words.len() {
1706 assert!(words[i].position_x > words[i - 1].position_x);
1707 }
1708 }
1709
1710 #[test]
1711 fn test_cjk_characters_layout() {
1712 let mut vt = VariableText::new(FixedWidthProvider::new(12.0));
1714 vt.set_plate_rect([0.0, 0.0, 100.0, 100.0]);
1715 vt.set_font_size(12.0);
1716 vt.set_multi_line(true);
1717 vt.set_auto_return(true);
1718 let text: String = "\u{4E00}".repeat(15);
1720 vt.set_text(&text);
1721 vt.rearrange();
1722 assert!(vt.sections()[0].lines.len() >= 2);
1723 }
1724
1725 #[test]
1726 fn test_multi_line_auto_font_size() {
1727 let mut vt = VariableText::new(FixedWidthProvider::new(6.0));
1728 vt.set_plate_rect([0.0, 0.0, 50.0, 30.0]);
1729 vt.set_multi_line(true);
1730 vt.set_auto_return(true);
1731 vt.set_auto_font_size(true);
1732 vt.set_text("This is a fairly long text that needs to fit");
1733 vt.rearrange();
1734 assert!(vt.font_size() < 12.0); assert!(vt.font_size() >= 4.0); }
1737
1738 #[test]
1739 fn test_line_y_positions_decrease() {
1740 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1741 vt.set_text("Line1\nLine2\nLine3");
1742 vt.rearrange();
1743 let y0 = vt.sections()[0].lines[0].y;
1744 let y1 = vt.sections()[1].lines[0].y;
1745 let y2 = vt.sections()[2].lines[0].y;
1746 assert!(y0 > y1);
1747 assert!(y1 > y2);
1748 }
1749
1750 #[test]
1751 fn test_hit_test_below_all_lines() {
1752 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1753 vt.set_text("Hello");
1754 vt.rearrange();
1755 let place = vt.hit_test(5.0, -100.0);
1757 assert!(place.is_some());
1758 }
1759
1760 #[test]
1761 fn test_word_range_creation() {
1762 let range = WordRange {
1763 begin: WordPlace {
1764 section: 0,
1765 line: 0,
1766 word: 0,
1767 },
1768 end: WordPlace {
1769 section: 0,
1770 line: 0,
1771 word: 5,
1772 },
1773 };
1774 assert_eq!(range.begin.word, 0);
1775 assert_eq!(range.end.word, 5);
1776 }
1777
1778 #[test]
1779 fn test_cr_line_endings() {
1780 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1781 vt.set_text("Line1\rLine2\rLine3");
1782 vt.rearrange();
1783 assert_eq!(vt.sections().len(), 3);
1784 assert_eq!(vt.sections()[0].words.len(), 5);
1785 assert_eq!(vt.sections()[1].words.len(), 5);
1786 assert_eq!(vt.sections()[2].words.len(), 5);
1787 }
1788
1789 #[test]
1790 fn test_crlf_line_endings() {
1791 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1792 vt.set_text("Line1\r\nLine2\r\nLine3");
1793 vt.rearrange();
1794 assert_eq!(vt.sections().len(), 3);
1795 assert_eq!(vt.sections()[0].words.len(), 5);
1796 assert_eq!(vt.sections()[1].words.len(), 5);
1797 assert_eq!(vt.sections()[2].words.len(), 5);
1798 }
1799
1800 #[test]
1801 fn test_mixed_line_endings() {
1802 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1803 vt.set_text("A\r\nB\rC\nD");
1804 vt.rearrange();
1805 assert_eq!(vt.sections().len(), 4);
1806 assert_eq!(vt.sections()[0].words.len(), 1); assert_eq!(vt.sections()[1].words.len(), 1); assert_eq!(vt.sections()[2].words.len(), 1); assert_eq!(vt.sections()[3].words.len(), 1); }
1811
1812 #[test]
1813 fn test_insert_adds_chars_and_rearranges() {
1814 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1815 vt.set_text("Hello");
1816 vt.rearrange();
1817 let place = WordPlace {
1818 section: 0,
1819 line: 0,
1820 word: 2,
1821 };
1822 vt.insert(&place, "XY").unwrap();
1823 assert_eq!(vt.sections()[0].words.len(), 7);
1825 assert_eq!(vt.sections()[0].words[2].character, 'X');
1826 assert_eq!(vt.sections()[0].words[3].character, 'Y');
1827 }
1828
1829 #[test]
1830 fn test_delete_removes_chars_and_rearranges() {
1831 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1832 vt.set_text("Hello");
1833 vt.rearrange();
1834 let range = WordRange {
1835 begin: WordPlace {
1836 section: 0,
1837 line: 0,
1838 word: 0,
1839 },
1840 end: WordPlace {
1841 section: 0,
1842 line: 0,
1843 word: 3,
1844 },
1845 };
1846 vt.delete(&range).unwrap();
1847 assert_eq!(vt.sections()[0].words.len(), 2);
1849 assert_eq!(vt.sections()[0].words[0].character, 'l');
1850 assert_eq!(vt.sections()[0].words[1].character, 'o');
1851 }
1852
1853 #[test]
1854 fn test_iter_lines_count() {
1855 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1856 vt.set_text("Line1\nLine2\nLine3");
1857 vt.rearrange();
1858 assert_eq!(vt.iter_lines().count(), 3);
1859 assert_eq!(vt.total_lines(), 3);
1860 }
1861
1862 #[test]
1863 fn test_iter_words_count() {
1864 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1865 vt.set_text("Hello");
1866 vt.rearrange();
1867 assert_eq!(vt.iter_words().count(), 5);
1868 assert_eq!(vt.total_words(), 5);
1869 }
1870
1871 #[test]
1872 fn test_iter_words_across_sections() {
1873 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1874 vt.set_text("AB\nCD");
1875 vt.rearrange();
1876 assert_eq!(vt.iter_words().count(), 4);
1877 assert_eq!(vt.total_words(), 4);
1878 }
1879
1880 #[test]
1885 fn test_total_words_empty() {
1886 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1887 vt.set_text("");
1888 vt.rearrange();
1889 assert_eq!(vt.total_words(), 0);
1890 assert_eq!(vt.get_total_words(), 0);
1891 }
1892
1893 #[test]
1894 fn test_total_words_single() {
1895 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1896 vt.set_text("A");
1897 vt.rearrange();
1898 assert_eq!(vt.total_words(), 1);
1899 }
1900
1901 #[test]
1902 fn test_total_words_multi() {
1903 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1904 vt.set_text("Hello\nWorld");
1905 vt.rearrange();
1906 assert_eq!(vt.total_words(), 10);
1907 }
1908
1909 #[test]
1910 fn test_begin_and_end_word_place_empty() {
1911 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1912 vt.set_text("");
1913 vt.rearrange();
1914 let begin = vt.begin_word_place();
1915 assert_eq!(begin, WordPlace::default());
1916 let end = vt.end_word_place();
1917 assert_eq!(end.section, 0);
1918 }
1919
1920 #[test]
1921 fn test_begin_and_end_word_place_non_empty() {
1922 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1923 vt.set_text("Hello");
1924 vt.rearrange();
1925 let begin = vt.begin_word_place();
1926 assert_eq!(
1927 begin,
1928 WordPlace {
1929 section: 0,
1930 line: 0,
1931 word: 0
1932 }
1933 );
1934 let end = vt.end_word_place();
1935 assert_eq!(end.section, 0);
1936 assert_eq!(end.word, 4);
1937 }
1938
1939 #[test]
1940 fn test_word_place_to_word_index_round_trip() {
1941 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1942 vt.set_text("ABCDE");
1943 vt.rearrange();
1944 for i in 0..5 {
1945 let place = WordPlace {
1946 section: 0,
1947 line: 0,
1948 word: i,
1949 };
1950 let idx = vt.word_place_to_word_index(&place);
1951 assert_eq!(idx, i, "index mismatch at word {i}");
1952 let back = vt.word_index_to_word_place(idx);
1953 assert_eq!(back, place, "round-trip mismatch at word {i}");
1954 }
1955 }
1956
1957 #[test]
1958 fn test_word_index_to_place_across_sections() {
1959 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1960 vt.set_text("AB\nCD");
1961 vt.rearrange();
1962 let p0 = vt.word_index_to_word_place(0);
1963 assert_eq!(
1964 p0,
1965 WordPlace {
1966 section: 0,
1967 line: 0,
1968 word: 0
1969 }
1970 );
1971 let p1 = vt.word_index_to_word_place(1);
1972 assert_eq!(
1973 p1,
1974 WordPlace {
1975 section: 0,
1976 line: 0,
1977 word: 1
1978 }
1979 );
1980 let p3 = vt.word_index_to_word_place(3);
1981 assert_eq!(p3.section, 1);
1982 assert_eq!(p3.word, 0);
1983 }
1984
1985 #[test]
1986 fn test_prev_next_word_place_navigation() {
1987 let mut vt = make_vt(FixedWidthProvider::new(6.0));
1988 vt.set_text("ABCDE");
1989 vt.rearrange();
1990
1991 let mut place = vt.begin_word_place();
1992 let mut count = 0;
1993 loop {
1994 count += 1;
1995 let next = vt.next_word_place(&place);
1996 if next == place {
1997 break;
1998 }
1999 place = next;
2000 }
2001 assert_eq!(count, 5);
2002
2003 place = vt.end_word_place();
2004 count = 0;
2005 loop {
2006 count += 1;
2007 let prev = vt.prev_word_place(&place);
2008 if prev == place {
2009 break;
2010 }
2011 place = prev;
2012 }
2013 assert_eq!(count, 5);
2014 }
2015
2016 #[test]
2017 fn test_line_begin_and_end_place() {
2018 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2019 vt.set_text("Hello");
2020 vt.rearrange();
2021 let mid = WordPlace {
2022 section: 0,
2023 line: 0,
2024 word: 2,
2025 };
2026 let begin = vt.line_begin_place(&mid);
2027 assert_eq!(begin.word, 0);
2028 let end = vt.line_end_place(&mid);
2029 assert_eq!(end.word, 4);
2030 }
2031
2032 #[test]
2033 fn test_section_begin_and_end_place() {
2034 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2035 vt.set_text("Hello\nWorld");
2036 vt.rearrange();
2037 let mid_sec1 = WordPlace {
2038 section: 1,
2039 line: 0,
2040 word: 2,
2041 };
2042 let sec_begin = vt.section_begin_place(&mid_sec1);
2043 assert_eq!(
2044 sec_begin,
2045 WordPlace {
2046 section: 1,
2047 line: 0,
2048 word: 0
2049 }
2050 );
2051 let sec_end = vt.section_end_place(&mid_sec1);
2052 assert_eq!(sec_end.section, 1);
2053 assert_eq!(sec_end.word, 4);
2054 }
2055
2056 #[test]
2057 fn test_plate_width_and_height() {
2058 let vt = make_vt(FixedWidthProvider::new(6.0));
2059 assert!((vt.plate_width() - 200.0).abs() < 0.01);
2060 assert!((vt.plate_height() - 100.0).abs() < 0.01);
2061 assert!((vt.get_plate_width() - 200.0).abs() < 0.01);
2062 assert!((vt.get_plate_height() - 100.0).abs() < 0.01);
2063 }
2064
2065 #[test]
2066 fn test_in_to_out_and_out_to_in_inverse() {
2067 let vt = make_vt(FixedWidthProvider::new(6.0));
2068 let original = (50.0_f32, 30.0_f32);
2069 let out = vt.in_to_out(original);
2070 let back = vt.out_to_in(out);
2071 assert!((back.0 - original.0).abs() < 0.001, "x mismatch");
2072 assert!((back.1 - original.1).abs() < 0.001, "y mismatch");
2073 }
2074
2075 #[test]
2076 fn test_vt_word_iterator_next_word_advances() {
2077 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2078 vt.set_text("ABCDE");
2079 vt.rearrange();
2080 let mut it = vt.word_iterator();
2081 let mut count = 0;
2082 while it.next_word() {
2083 count += 1;
2084 }
2085 assert_eq!(count, 4);
2086 }
2087
2088 #[test]
2089 fn test_vt_word_iterator_set_at_index() {
2090 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2091 vt.set_text("ABCDE");
2092 vt.rearrange();
2093 let mut it = vt.word_iterator();
2094 it.set_at_index(3);
2095 assert_eq!(it.word_place().word, 3);
2096 }
2097
2098 #[test]
2099 fn test_vt_word_iterator_set_at_place() {
2100 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2101 vt.set_text("Hello");
2102 vt.rearrange();
2103 let target = WordPlace {
2104 section: 0,
2105 line: 0,
2106 word: 2,
2107 };
2108 let mut it = vt.word_iterator();
2109 it.set_at_place(target);
2110 assert_eq!(*it.get_word_place(), target);
2111 }
2112
2113 #[test]
2114 fn test_config_getters() {
2115 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2116 vt.set_text("Test");
2117 vt.rearrange();
2118 assert!((vt.font_size() - 12.0).abs() < 0.01);
2119 assert!((vt.get_font_size() - 12.0).abs() < 0.01);
2120 assert_eq!(vt.alignment(), Alignment::Left);
2121 assert_eq!(vt.get_alignment(), Alignment::Left);
2122 assert!(!vt.is_multi_line());
2123 assert!(vt.is_auto_return());
2124 }
2125
2126 #[test]
2127 fn test_password_char_getter() {
2128 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2129 assert_eq!(vt.password_char(), None);
2130 assert_eq!(vt.get_password_char(), None);
2131 assert_eq!(vt.get_sub_word(), None);
2132 vt.set_password_char(Some('*'));
2133 assert_eq!(vt.password_char(), Some('*'));
2134 assert_eq!(vt.get_sub_word(), Some('*'));
2135 assert_eq!(vt.get_password_char(), Some('*'));
2136 }
2137
2138 #[test]
2139 fn test_line_leading_positive() {
2140 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2141 vt.set_text("Hi");
2142 vt.rearrange();
2143 assert!((vt.line_leading() - 13.0).abs() < 0.01);
2145 assert!((vt.get_line_leading() - 13.0).abs() < 0.01);
2146 }
2147
2148 #[test]
2149 fn test_update_word_place_clamping() {
2150 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2151 vt.set_text("Hi");
2152 vt.rearrange();
2153 let mut out_of_bounds = WordPlace {
2154 section: 99,
2155 line: 99,
2156 word: 99,
2157 };
2158 vt.update_word_place(&mut out_of_bounds);
2159 assert_eq!(out_of_bounds.section, 0);
2160 assert_eq!(out_of_bounds.line, 0);
2161 assert!(out_of_bounds.word <= 1);
2162 }
2163
2164 #[test]
2165 fn test_search_word_place_returns_valid() {
2166 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2167 vt.set_text("Hello");
2168 vt.rearrange();
2169 let line = &vt.sections()[0].lines[0];
2170 let y = line.y + line.ascent / 2.0;
2171 let place = vt.search_word_place((10.0, y));
2172 assert_eq!(place.section, 0);
2173 }
2174
2175 #[test]
2176 fn test_vt_word_iterator_next_line_across_sections() {
2177 let mut vt = make_vt(FixedWidthProvider::new(6.0));
2178 vt.set_text("Hello\nWorld");
2179 vt.rearrange();
2180 let mut it = vt.word_iterator();
2181 assert_eq!(it.word_place().section, 0);
2182 assert!(it.next_line());
2183 assert_eq!(it.word_place().section, 1);
2184 assert_eq!(it.word_place().line, 0);
2185 assert!(!it.next_line());
2186 }
2187}