1use crate::style::Style;
2use unicode_width::UnicodeWidthStr;
3
4const NBSP: &str = "\u{00a0}";
5
6#[derive(Copy, Clone, Debug)]
7pub struct Styled<'a>(pub &'a str, pub Style);
8
9pub trait LineComposer<'a> {
13 fn next_line(&mut self) -> Option<(&[Styled<'a>], u16)>;
14}
15
16pub struct WordWrapper<'a, 'b> {
18 symbols: &'b mut Iterator<Item = Styled<'a>>,
19 max_line_width: u16,
20 current_line: Vec<Styled<'a>>,
21 next_line: Vec<Styled<'a>>,
22}
23
24impl<'a, 'b> WordWrapper<'a, 'b> {
25 pub fn new(
26 symbols: &'b mut Iterator<Item = Styled<'a>>,
27 max_line_width: u16,
28 ) -> WordWrapper<'a, 'b> {
29 WordWrapper {
30 symbols,
31 max_line_width,
32 current_line: vec![],
33 next_line: vec![],
34 }
35 }
36}
37
38impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> {
39 fn next_line(&mut self) -> Option<(&[Styled<'a>], u16)> {
40 if self.max_line_width == 0 {
41 return None;
42 }
43 std::mem::swap(&mut self.current_line, &mut self.next_line);
44 self.next_line.truncate(0);
45
46 let mut current_line_width = self
47 .current_line
48 .iter()
49 .map(|Styled(c, _)| c.width() as u16)
50 .sum();
51
52 let mut symbols_to_last_word_end: usize = 0;
53 let mut width_to_last_word_end: u16 = 0;
54 let mut prev_whitespace = false;
55 let mut symbols_exhausted = true;
56 for Styled(symbol, style) in &mut self.symbols {
57 symbols_exhausted = false;
58 let symbol_whitespace = symbol.chars().all(&char::is_whitespace);
59
60 if symbol.width() as u16 > self.max_line_width
62 || symbol_whitespace && symbol != "\n" && current_line_width == 0
64 {
65 continue;
66 }
67
68 if symbol == "\n" {
70 if prev_whitespace {
71 current_line_width = width_to_last_word_end;
72 self.current_line.truncate(symbols_to_last_word_end);
73 }
74 break;
75 }
76
77 if symbol_whitespace && !prev_whitespace && symbol != NBSP {
79 symbols_to_last_word_end = self.current_line.len();
80 width_to_last_word_end = current_line_width;
81 }
82
83 self.current_line.push(Styled(symbol, style));
84 current_line_width += symbol.width() as u16;
85
86 if current_line_width > self.max_line_width {
87 let (truncate_at, truncated_width) = if symbols_to_last_word_end != 0 {
89 (symbols_to_last_word_end, width_to_last_word_end)
90 } else {
91 (self.current_line.len() - 1, self.max_line_width)
92 };
93
94 {
96 let remainder = &self.current_line[truncate_at..];
97 if let Some(remainder_nonwhite) = remainder
98 .iter()
99 .position(|Styled(c, _)| !c.chars().all(&char::is_whitespace))
100 {
101 self.next_line
102 .extend_from_slice(&remainder[remainder_nonwhite..]);
103 }
104 }
105 self.current_line.truncate(truncate_at);
106 current_line_width = truncated_width;
107 break;
108 }
109
110 prev_whitespace = symbol_whitespace;
111 }
112
113 if symbols_exhausted && self.current_line.is_empty() {
115 None
116 } else {
117 Some((&self.current_line[..], current_line_width))
118 }
119 }
120}
121
122pub struct LineTruncator<'a, 'b> {
124 symbols: &'b mut Iterator<Item = Styled<'a>>,
125 max_line_width: u16,
126 current_line: Vec<Styled<'a>>,
127}
128
129impl<'a, 'b> LineTruncator<'a, 'b> {
130 pub fn new(
131 symbols: &'b mut Iterator<Item = Styled<'a>>,
132 max_line_width: u16,
133 ) -> LineTruncator<'a, 'b> {
134 LineTruncator {
135 symbols,
136 max_line_width,
137 current_line: vec![],
138 }
139 }
140}
141
142impl<'a, 'b> LineComposer<'a> for LineTruncator<'a, 'b> {
143 fn next_line(&mut self) -> Option<(&[Styled<'a>], u16)> {
144 if self.max_line_width == 0 {
145 return None;
146 }
147
148 self.current_line.truncate(0);
149 let mut current_line_width = 0;
150
151 let mut skip_rest = false;
152 let mut symbols_exhausted = true;
153 for Styled(symbol, style) in &mut self.symbols {
154 symbols_exhausted = false;
155
156 if symbol.width() as u16 > self.max_line_width {
158 continue;
159 }
160
161 if symbol == "\n" {
163 break;
164 }
165
166 if current_line_width + symbol.width() as u16 > self.max_line_width {
167 skip_rest = true;
169 break;
170 }
171
172 current_line_width += symbol.width() as u16;
173 self.current_line.push(Styled(symbol, style));
174 }
175
176 if skip_rest {
177 for Styled(symbol, _) in &mut self.symbols {
178 if symbol == "\n" {
179 break;
180 }
181 }
182 }
183
184 if symbols_exhausted && self.current_line.is_empty() {
185 None
186 } else {
187 Some((&self.current_line[..], current_line_width))
188 }
189 }
190}
191
192#[cfg(test)]
193mod test {
194 use super::*;
195 use unicode_segmentation::UnicodeSegmentation;
196
197 enum Composer {
198 WordWrapper,
199 LineTruncator,
200 }
201
202 fn run_composer(which: Composer, text: &str, text_area_width: u16) -> (Vec<String>, Vec<u16>) {
203 let style = Default::default();
204 let mut styled = UnicodeSegmentation::graphemes(text, true).map(|g| Styled(g, style));
205 let mut composer: Box<dyn LineComposer> = match which {
206 Composer::WordWrapper => Box::new(WordWrapper::new(&mut styled, text_area_width)),
207 Composer::LineTruncator => Box::new(LineTruncator::new(&mut styled, text_area_width)),
208 };
209 let mut lines = vec![];
210 let mut widths = vec![];
211 while let Some((styled, width)) = composer.next_line() {
212 let line = styled
213 .iter()
214 .map(|Styled(g, _style)| *g)
215 .collect::<String>();
216 assert!(width <= text_area_width);
217 lines.push(line);
218 widths.push(width);
219 }
220 (lines, widths)
221 }
222
223 #[test]
224 fn line_composer_one_line() {
225 let width = 40;
226 for i in 1..width {
227 let text = "a".repeat(i);
228 let (word_wrapper, _) = run_composer(Composer::WordWrapper, &text, width as u16);
229 let (line_truncator, _) = run_composer(Composer::LineTruncator, &text, width as u16);
230 let expected = vec![text];
231 assert_eq!(word_wrapper, expected);
232 assert_eq!(line_truncator, expected);
233 }
234 }
235
236 #[test]
237 fn line_composer_short_lines() {
238 let width = 20;
239 let text =
240 "abcdefg\nhijklmno\npabcdefg\nhijklmn\nopabcdefghijk\nlmnopabcd\n\n\nefghijklmno";
241 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
242 let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
243
244 let wrapped: Vec<&str> = text.split('\n').collect();
245 assert_eq!(word_wrapper, wrapped);
246 assert_eq!(line_truncator, wrapped);
247 }
248
249 #[test]
250 fn line_composer_long_word() {
251 let width = 20;
252 let text = "abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmno";
253 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width as u16);
254 let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width as u16);
255
256 let wrapped = vec![
257 &text[..width],
258 &text[width..width * 2],
259 &text[width * 2..width * 3],
260 &text[width * 3..],
261 ];
262 assert_eq!(
263 word_wrapper, wrapped,
264 "WordWrapper should deect the line cannot be broken on word boundary and \
265 break it at line width limit."
266 );
267 assert_eq!(line_truncator, vec![&text[..width]]);
268 }
269
270 #[test]
271 fn line_composer_long_sentence() {
272 let width = 20;
273 let text =
274 "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l m n o";
275 let text_multi_space =
276 "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l \
277 m n o";
278 let (word_wrapper_single_space, _) =
279 run_composer(Composer::WordWrapper, text, width as u16);
280 let (word_wrapper_multi_space, _) =
281 run_composer(Composer::WordWrapper, text_multi_space, width as u16);
282 let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width as u16);
283
284 let word_wrapped = vec![
285 "abcd efghij",
286 "klmnopabcd efgh",
287 "ijklmnopabcdefg",
288 "hijkl mnopab c d e f",
289 "g h i j k l m n o",
290 ];
291 assert_eq!(word_wrapper_single_space, word_wrapped);
292 assert_eq!(word_wrapper_multi_space, word_wrapped);
293
294 assert_eq!(line_truncator, vec![&text[..width]]);
295 }
296
297 #[test]
298 fn line_composer_zero_width() {
299 let width = 0;
300 let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab ";
301 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
302 let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
303
304 let expected: Vec<&str> = Vec::new();
305 assert_eq!(word_wrapper, expected);
306 assert_eq!(line_truncator, expected);
307 }
308
309 #[test]
310 fn line_composer_max_line_width_of_1() {
311 let width = 1;
312 let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab ";
313 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
314 let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
315
316 let expected: Vec<&str> = UnicodeSegmentation::graphemes(text, true)
317 .filter(|g| g.chars().any(|c| !c.is_whitespace()))
318 .collect();
319 assert_eq!(word_wrapper, expected);
320 assert_eq!(line_truncator, vec!["a"]);
321 }
322
323 #[test]
324 fn line_composer_max_line_width_of_1_double_width_characters() {
325 let width = 1;
326 let text = "コンピュータ上で文字を扱う場合、典型的には文字\naaaによる通信を行う場合にその\
327 両端点では、";
328 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
329 let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
330 assert_eq!(word_wrapper, vec!["", "a", "a", "a"]);
331 assert_eq!(line_truncator, vec!["", "a"]);
332 }
333
334 #[test]
336 fn line_composer_word_wrapper_mixed_length() {
337 let width = 20;
338 let text = "abcd efghij klmnopabcdefghijklmnopabcdefghijkl mnopab cdefghi j klmno";
339 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
340 assert_eq!(
341 word_wrapper,
342 vec![
343 "abcd efghij",
344 "klmnopabcdefghijklmn",
345 "opabcdefghijkl",
346 "mnopab cdefghi j",
347 "klmno",
348 ]
349 )
350 }
351
352 #[test]
353 fn line_composer_double_width_chars() {
354 let width = 20;
355 let text = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点\
356 では、";
357 let (word_wrapper, word_wrapper_width) = run_composer(Composer::WordWrapper, &text, width);
358 let (line_truncator, _) = run_composer(Composer::LineTruncator, &text, width);
359 assert_eq!(line_truncator, vec!["コンピュータ上で文字"]);
360 let wrapped = vec![
361 "コンピュータ上で文字",
362 "を扱う場合、典型的に",
363 "は文字による通信を行",
364 "う場合にその両端点で",
365 "は、",
366 ];
367 assert_eq!(word_wrapper, wrapped);
368 assert_eq!(word_wrapper_width, vec![width, width, width, width, 4]);
369 }
370
371 #[test]
372 fn line_composer_leading_whitespace_removal() {
373 let width = 20;
374 let text = "AAAAAAAAAAAAAAAAAAAA AAA";
375 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
376 let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
377 assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAAAAAAA", "AAA",]);
378 assert_eq!(line_truncator, vec!["AAAAAAAAAAAAAAAAAAAA"]);
379 }
380
381 #[test]
383 fn line_composer_lots_of_spaces() {
384 let width = 20;
385 let text = " ";
386 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
387 let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
388 assert_eq!(word_wrapper, vec![""]);
389 assert_eq!(line_truncator, vec![" "]);
390 }
391
392 #[test]
395 fn line_composer_char_plus_lots_of_spaces() {
396 let width = 20;
397 let text = "a ";
398 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
399 let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
400 assert_eq!(word_wrapper, vec!["a", ""]);
405 assert_eq!(line_truncator, vec!["a "]);
406 }
407
408 #[test]
409 fn line_composer_word_wrapper_double_width_chars_mixed_with_spaces() {
410 let width = 20;
411 let text = "コンピュ ータ上で文字を扱う場合、 典型的には文 字による 通信を行 う場合にその両端点では、";
417 let (word_wrapper, word_wrapper_width) = run_composer(Composer::WordWrapper, text, width);
418 assert_eq!(
419 word_wrapper,
420 vec![
421 "コンピュ",
422 "ータ上で文字を扱う場",
423 "合、 典型的には文",
424 "字による 通信を行",
425 "う場合にその両端点で",
426 "は、",
427 ]
428 );
429 assert_eq!(word_wrapper_width, vec![8, 20, 17, 17, 20, 4]);
431 }
432
433 #[test]
435 fn line_composer_word_wrapper_nbsp() {
436 let width = 20;
437 let text = "AAAAAAAAAAAAAAA AAAA\u{00a0}AAA";
438 let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
439 assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAA", "AAAA\u{00a0}AAA",]);
440
441 let text_space = text.replace("\u{00a0}", " ");
443 let (word_wrapper_space, _) = run_composer(Composer::WordWrapper, &text_space, width);
444 assert_eq!(word_wrapper_space, vec!["AAAAAAAAAAAAAAA AAAA", "AAA",]);
445 }
446}