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