1use super::FontRegistry;
10
11#[derive(Debug, Clone)]
13pub struct FontRun {
14 pub start: usize,
16 pub end: usize,
18 pub family: String,
20}
21
22pub fn segment_by_font(
30 chars: &[char],
31 families: &str,
32 weight: u32,
33 italic: bool,
34 registry: &FontRegistry,
35) -> Vec<FontRun> {
36 if chars.is_empty() {
37 return vec![];
38 }
39
40 if !families.contains(',') {
44 let family = families.trim().trim_matches('"').trim_matches('\'');
45 let font = registry.resolve(family, weight, italic);
46 let all_covered = chars.iter().all(|&ch| {
47 ch.is_whitespace()
48 || ch == crate::layout::PAGE_NUMBER_SENTINEL
49 || ch == crate::layout::TOTAL_PAGES_SENTINEL
50 || font.has_char(ch)
51 });
52 if all_covered {
53 return vec![FontRun {
54 start: 0,
55 end: chars.len(),
56 family: family.to_string(),
57 }];
58 }
59 }
62
63 let mut runs = Vec::new();
65 let (_, first_family) = registry.resolve_for_char(families, chars[0], weight, italic);
66 let mut current_family = first_family;
67 let mut run_start = 0;
68
69 for (i, &ch) in chars.iter().enumerate().skip(1) {
70 let (_, family) = registry.resolve_for_char(families, ch, weight, italic);
71 if family != current_family {
72 runs.push(FontRun {
73 start: run_start,
74 end: i,
75 family: current_family,
76 });
77 current_family = family;
78 run_start = i;
79 }
80 }
81
82 runs.push(FontRun {
84 start: run_start,
85 end: chars.len(),
86 family: current_family,
87 });
88
89 runs
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_single_font_fast_path() {
98 let registry = FontRegistry::new();
99 let chars: Vec<char> = "Hello world".chars().collect();
100 let runs = segment_by_font(&chars, "Helvetica", 400, false, ®istry);
101 assert_eq!(runs.len(), 1);
102 assert_eq!(runs[0].family, "Helvetica");
103 assert_eq!(runs[0].start, 0);
104 assert_eq!(runs[0].end, 11);
105 }
106
107 #[test]
108 fn test_empty_input() {
109 let registry = FontRegistry::new();
110 let chars: Vec<char> = vec![];
111 let runs = segment_by_font(&chars, "Helvetica, Times", 400, false, ®istry);
112 assert!(runs.is_empty());
113 }
114
115 #[test]
116 fn test_single_font_builtin_fallback() {
117 let registry = FontRegistry::new();
118 let chars: Vec<char> = "\u{041F}\u{0440}\u{0438}\u{0432}\u{0435}\u{0442}"
120 .chars()
121 .collect();
122 let runs = segment_by_font(&chars, "Helvetica", 400, false, ®istry);
123 assert!(!runs.is_empty(), "Should produce at least one run");
124 assert_eq!(runs[0].family, "Noto Sans", "Cyrillic should use Noto Sans");
126 }
127
128 #[test]
129 fn test_single_font_mixed_latin_cyrillic() {
130 let registry = FontRegistry::new();
131 let chars: Vec<char> = "Hi \u{041F}".chars().collect();
133 let runs = segment_by_font(&chars, "Helvetica", 400, false, ®istry);
134 assert!(
135 runs.len() >= 2,
136 "Should have at least 2 runs (Latin + Cyrillic), got {}",
137 runs.len()
138 );
139 }
140
141 #[test]
142 fn test_all_chars_same_font() {
143 let registry = FontRegistry::new();
144 let chars: Vec<char> = "ABC".chars().collect();
145 let runs = segment_by_font(&chars, "Helvetica, Times", 400, false, ®istry);
147 assert_eq!(runs.len(), 1);
148 assert_eq!(runs[0].family, "Helvetica");
149 }
150}