buffer_graphics_lib/text/
wrapping.rs1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6#[derive(Debug, Clone, Eq, PartialEq, Copy, Default)]
7pub enum WrappingStrategy {
8 #[default]
9 None,
11 AtCol(usize),
13 SpaceBeforeCol(usize),
15 AtColWithHyphen(usize),
17 Cutoff(usize),
19 Ellipsis(usize),
21}
22
23impl WrappingStrategy {
24 pub fn wrap(&self, input: &str) -> Vec<String> {
25 match self {
26 WrappingStrategy::None => input.split('\n').map(|s| s.to_string()).collect(),
27 WrappingStrategy::AtCol(col) => {
28 if *col == 0 {
29 return split_str_to_col(input);
30 }
31 let mut output = vec![];
32 let lines = WrappingStrategy::None.wrap(input);
33 for mut line in lines {
34 while line.chars().count() > *col {
35 output.push(line.chars().take(*col).collect());
36 line = line.chars().skip(*col).collect();
37 }
38 output.push(line);
39 }
40 output
41 }
42 WrappingStrategy::SpaceBeforeCol(col) => {
43 if *col == 0 {
44 return split_str_to_col(input);
45 }
46 let mut output = vec![];
47 let lines = WrappingStrategy::None.wrap(input);
48 for mut text in lines {
49 while text.chars().count() > *col {
50 let chars: Vec<char> = text.chars().collect();
51 let line: String = text.chars().take(*col).collect();
52 if line.ends_with(|c: char| c.is_whitespace())
53 || text
54 .chars()
55 .nth(*col)
56 .map(|c| c.is_whitespace())
57 .unwrap_or(false)
58 {
59 output.push(text.chars().take(*col).collect());
60 text = text.chars().skip(*col).collect();
61 } else {
62 let mut whitespace = chars
63 .iter()
64 .take(*col)
65 .rposition(|c| c.is_whitespace())
66 .unwrap_or(0);
67 if whitespace == 0 {
68 whitespace = *col
69 }
70 output.push(text.chars().take(whitespace).collect());
71 text = text.chars().skip(whitespace).collect();
72 }
73 }
74 output.push(text);
75 }
76 output.iter().map(|s| s.trim().to_string()).collect()
77 }
78 WrappingStrategy::AtColWithHyphen(col) => {
79 if *col == 0 {
80 return split_str_to_col(input);
81 }
82 let mut output = vec![];
83 let lines = WrappingStrategy::None.wrap(input);
84 for mut line in lines {
85 while line.chars().count() > *col {
86 let text: String = line.chars().take(*col).collect();
87 if text.chars().last().unwrap_or(' ').is_alphabetic()
88 && line.chars().nth(*col).unwrap_or(' ').is_alphabetic()
89 {
90 output.push(format!("{text}-"));
91 } else {
92 output.push(text.clone());
93 }
94 line = line.chars().skip(*col).collect();
95 }
96 output.push(line)
97 }
98 output
99 }
100 WrappingStrategy::Cutoff(col) => {
101 if *col == 0 {
102 return split_str_to_col(input);
103 }
104 input
105 .split('\n')
106 .map(|line| line.chars().take(*col).collect())
107 .collect()
108 }
109 WrappingStrategy::Ellipsis(col) => {
110 if *col == 0 {
111 return split_str_to_col(input);
112 }
113 input
114 .split('\n')
115 .map(|line| {
116 if line.chars().count() >= *col {
117 format!("{}…", line.chars().take(*col).collect::<String>())
118 } else {
119 line.to_string()
120 }
121 })
122 .collect()
123 }
124 }
125 }
126}
127
128fn split_str_to_col(str: &str) -> Vec<String> {
129 str.chars()
130 .filter(|c| !c.is_whitespace())
131 .map(|c| c.to_string())
132 .collect()
133}
134
135#[cfg(test)]
136mod test {
137 use super::*;
138 use WrappingStrategy::*;
139
140 fn c(list: &[&str]) -> Vec<String> {
141 list.iter().map(|s| s.to_string()).collect()
142 }
143
144 #[test]
145 fn none() {
146 assert_eq!(None.wrap("this is a test"), c(&["this is a test"]));
147 assert_eq!(None.wrap("this is\na test"), c(&["this is", "a test"]));
148 assert_eq!(None.wrap("this is \n a test"), c(&["this is ", " a test"]));
149 }
150
151 #[test]
152 fn at_col() {
153 assert_eq!(
154 AtCol(4).wrap("some words, and some are longer"),
155 c(&["some", " wor", "ds, ", "and ", "some", " are", " lon", "ger"])
156 );
157
158 assert_eq!(
159 AtCol(5).wrap("Split Here\nBut mid word\nhere"),
160 c(&["Split", " Here", "But m", "id wo", "rd", "here"])
161 )
162 }
163
164 #[test]
165 fn at_col_hyphen() {
166 assert_eq!(
167 AtColWithHyphen(4).wrap("some words, and some are longer"),
168 c(&["some", " wor-", "ds, ", "and ", "some", " are", " lon-", "ger"])
169 );
170 assert_eq!(
171 AtColWithHyphen(4).wrap("smol large massive"),
172 c(&["smol", " lar-", "ge m-", "assi-", "ve"])
173 );
174 assert_eq!(
175 AtColWithHyphen(4).wrap("smol large\nmassive"),
176 c(&["smol", " lar-", "ge", "mass-", "ive"])
177 );
178 }
179
180 #[test]
181 fn space_before_col() {
182 assert_eq!(
183 SpaceBeforeCol(4).wrap("some words, and some are longer"),
184 c(&["some", "wor", "ds,", "and", "some", "are", "lon", "ger"])
185 );
186 assert_eq!(
187 SpaceBeforeCol(8).wrap("some words, and some are longer"),
188 c(&["some", "words,", "and some", "are", "longer"])
189 );
190 }
191
192 #[test]
193 fn cutoff() {
194 assert_eq!(Cutoff(30).wrap("short test"), c(&["short test"]));
195 assert_eq!(Cutoff(10).wrap("longer test string"), c(&["longer tes"]));
196 assert_eq!(
197 Cutoff(10).wrap("longer\ntest string"),
198 c(&["longer", "test strin"])
199 );
200 }
201
202 #[test]
203 fn ellipsis() {
204 assert_eq!(Ellipsis(30).wrap("short test"), c(&["short test"]));
205 assert_eq!(Ellipsis(10).wrap("longer test string"), c(&["longer tes…"]));
206 assert_eq!(
207 Ellipsis(10).wrap("longer\ntest string"),
208 c(&["longer", "test strin…"])
209 );
210 }
211}