1use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary};
2use collections::HashMap;
3use std::{iter, sync::Arc};
4
5pub struct LineWrapper {
6 platform_text_system: Arc<dyn PlatformTextSystem>,
7 pub(crate) font_id: FontId,
8 pub(crate) font_size: Pixels,
9 cached_ascii_char_widths: [Option<Pixels>; 128],
10 cached_other_char_widths: HashMap<char, Pixels>,
11}
12
13impl LineWrapper {
14 pub const MAX_INDENT: u32 = 256;
15
16 pub fn new(
17 font_id: FontId,
18 font_size: Pixels,
19 text_system: Arc<dyn PlatformTextSystem>,
20 ) -> Self {
21 Self {
22 platform_text_system: text_system,
23 font_id,
24 font_size,
25 cached_ascii_char_widths: [None; 128],
26 cached_other_char_widths: HashMap::default(),
27 }
28 }
29
30 pub fn wrap_line<'a>(
31 &'a mut self,
32 line: &'a str,
33 wrap_width: Pixels,
34 ) -> impl Iterator<Item = Boundary> + 'a {
35 let mut width = px(0.);
36 let mut first_non_whitespace_ix = None;
37 let mut indent = None;
38 let mut last_candidate_ix = 0;
39 let mut last_candidate_width = px(0.);
40 let mut last_wrap_ix = 0;
41 let mut prev_c = '\0';
42 let mut char_indices = line.char_indices();
43 iter::from_fn(move || {
44 for (ix, c) in char_indices.by_ref() {
45 if c == '\n' {
46 continue;
47 }
48
49 if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
50 last_candidate_ix = ix;
51 last_candidate_width = width;
52 }
53
54 if c != ' ' && first_non_whitespace_ix.is_none() {
55 first_non_whitespace_ix = Some(ix);
56 }
57
58 let char_width = self.width_for_char(c);
59 width += char_width;
60 if width > wrap_width && ix > last_wrap_ix {
61 if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
62 {
63 indent = Some(
64 Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
65 );
66 }
67
68 if last_candidate_ix > 0 {
69 last_wrap_ix = last_candidate_ix;
70 width -= last_candidate_width;
71 last_candidate_ix = 0;
72 } else {
73 last_wrap_ix = ix;
74 width = char_width;
75 }
76
77 if let Some(indent) = indent {
78 width += self.width_for_char(' ') * indent as f32;
79 }
80
81 return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
82 }
83 prev_c = c;
84 }
85
86 None
87 })
88 }
89
90 pub fn wrap_shaped_line<'a>(
91 &'a mut self,
92 str: &'a str,
93 line: &'a Line,
94 wrap_width: Pixels,
95 ) -> impl Iterator<Item = ShapedBoundary> + 'a {
96 let mut first_non_whitespace_ix = None;
97 let mut last_candidate_ix = None;
98 let mut last_candidate_x = px(0.);
99 let mut last_wrap_ix = ShapedBoundary {
100 run_ix: 0,
101 glyph_ix: 0,
102 };
103 let mut last_wrap_x = px(0.);
104 let mut prev_c = '\0';
105 let mut glyphs = line
106 .runs()
107 .iter()
108 .enumerate()
109 .flat_map(move |(run_ix, run)| {
110 run.glyphs()
111 .iter()
112 .enumerate()
113 .map(move |(glyph_ix, glyph)| {
114 let character = str[glyph.index..].chars().next().unwrap();
115 (
116 ShapedBoundary { run_ix, glyph_ix },
117 character,
118 glyph.position.x,
119 )
120 })
121 })
122 .peekable();
123
124 iter::from_fn(move || {
125 while let Some((ix, c, x)) = glyphs.next() {
126 if c == '\n' {
127 continue;
128 }
129
130 if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
131 last_candidate_ix = Some(ix);
132 last_candidate_x = x;
133 }
134
135 if c != ' ' && first_non_whitespace_ix.is_none() {
136 first_non_whitespace_ix = Some(ix);
137 }
138
139 let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
140 let width = next_x - last_wrap_x;
141 if width > wrap_width && ix > last_wrap_ix {
142 if let Some(last_candidate_ix) = last_candidate_ix.take() {
143 last_wrap_ix = last_candidate_ix;
144 last_wrap_x = last_candidate_x;
145 } else {
146 last_wrap_ix = ix;
147 last_wrap_x = x;
148 }
149
150 return Some(last_wrap_ix);
151 }
152 prev_c = c;
153 }
154
155 None
156 })
157 }
158
159 fn is_boundary(&self, prev: char, next: char) -> bool {
160 (prev == ' ') && (next != ' ')
161 }
162
163 #[inline(always)]
164 fn width_for_char(&mut self, c: char) -> Pixels {
165 if (c as u32) < 128 {
166 if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
167 cached_width
168 } else {
169 let width = self.compute_width_for_char(c);
170 self.cached_ascii_char_widths[c as usize] = Some(width);
171 width
172 }
173 } else {
174 if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
175 *cached_width
176 } else {
177 let width = self.compute_width_for_char(c);
178 self.cached_other_char_widths.insert(c, width);
179 width
180 }
181 }
182 }
183
184 fn compute_width_for_char(&self, c: char) -> Pixels {
185 self.platform_text_system
186 .layout_line(&c.to_string(), self.font_size, &[(1, self.font_id)])
187 .width
188 }
189}
190
191#[derive(Copy, Clone, Debug, PartialEq, Eq)]
192pub struct Boundary {
193 pub ix: usize,
194 pub next_indent: u32,
195}
196
197impl Boundary {
198 fn new(ix: usize, next_indent: u32) -> Self {
199 Self { ix, next_indent }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::{font, App, RunStyle};
207
208 #[test]
209 fn test_wrap_line() {
210 App::test().run(|cx| {
211 let text_system = cx.text_system().clone();
212 let mut wrapper = LineWrapper::new(
213 text_system.font_id(&font("Courier")).unwrap(),
214 px(16.),
215 text_system.platform_text_system.clone(),
216 );
217 assert_eq!(
218 wrapper
219 .wrap_line("aa bbb cccc ddddd eeee", px(72.))
220 .collect::<Vec<_>>(),
221 &[
222 Boundary::new(7, 0),
223 Boundary::new(12, 0),
224 Boundary::new(18, 0)
225 ],
226 );
227 assert_eq!(
228 wrapper
229 .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
230 .collect::<Vec<_>>(),
231 &[
232 Boundary::new(4, 0),
233 Boundary::new(11, 0),
234 Boundary::new(18, 0)
235 ],
236 );
237 assert_eq!(
238 wrapper
239 .wrap_line(" aaaaaaa", px(72.))
240 .collect::<Vec<_>>(),
241 &[
242 Boundary::new(7, 5),
243 Boundary::new(9, 5),
244 Boundary::new(11, 5),
245 ]
246 );
247 assert_eq!(
248 wrapper
249 .wrap_line(" ", px(72.))
250 .collect::<Vec<_>>(),
251 &[
252 Boundary::new(7, 0),
253 Boundary::new(14, 0),
254 Boundary::new(21, 0)
255 ]
256 );
257 assert_eq!(
258 wrapper
259 .wrap_line(" aaaaaaaaaaaaaa", px(72.))
260 .collect::<Vec<_>>(),
261 &[
262 Boundary::new(7, 0),
263 Boundary::new(14, 3),
264 Boundary::new(18, 3),
265 Boundary::new(22, 3),
266 ]
267 );
268 });
269 }
270
271 // todo! repeat this test
272 #[test]
273 fn test_wrap_shaped_line() {
274 App::test().run(|cx| {
275 let text_system = cx.text_system().clone();
276
277 let normal = RunStyle {
278 font: font("Helvetica"),
279 color: Default::default(),
280 underline: Default::default(),
281 };
282 let bold = RunStyle {
283 font: font("Helvetica").bold(),
284 color: Default::default(),
285 underline: Default::default(),
286 };
287
288 let text = "aa bbb cccc ddddd eeee";
289 let line = text_system
290 .layout_line(
291 text,
292 px(16.),
293 &[
294 (4, normal.clone()),
295 (5, bold.clone()),
296 (6, normal.clone()),
297 (1, bold.clone()),
298 (7, normal.clone()),
299 ],
300 )
301 .unwrap();
302
303 let mut wrapper = LineWrapper::new(
304 text_system.font_id(&normal.font).unwrap(),
305 px(16.),
306 text_system.platform_text_system.clone(),
307 );
308 assert_eq!(
309 wrapper
310 .wrap_shaped_line(text, &line, px(72.))
311 .collect::<Vec<_>>(),
312 &[
313 ShapedBoundary {
314 run_ix: 1,
315 glyph_ix: 3
316 },
317 ShapedBoundary {
318 run_ix: 2,
319 glyph_ix: 3
320 },
321 ShapedBoundary {
322 run_ix: 4,
323 glyph_ix: 2
324 }
325 ],
326 );
327 });
328 }
329}