1use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem};
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 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 #[inline(always)]
91 fn width_for_char(&mut self, c: char) -> Pixels {
92 if (c as u32) < 128 {
93 if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
94 cached_width
95 } else {
96 let width = self.compute_width_for_char(c);
97 self.cached_ascii_char_widths[c as usize] = Some(width);
98 width
99 }
100 } else {
101 if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
102 *cached_width
103 } else {
104 let width = self.compute_width_for_char(c);
105 self.cached_other_char_widths.insert(c, width);
106 width
107 }
108 }
109 }
110
111 fn compute_width_for_char(&self, c: char) -> Pixels {
112 let mut buffer = [0; 4];
113 let buffer = c.encode_utf8(&mut buffer);
114 self.platform_text_system
115 .layout_line(
116 buffer,
117 self.font_size,
118 &[FontRun {
119 len: 1,
120 font_id: self.font_id,
121 }],
122 )
123 .width
124 }
125}
126
127#[derive(Copy, Clone, Debug, PartialEq, Eq)]
128pub struct Boundary {
129 pub ix: usize,
130 pub next_indent: u32,
131}
132
133impl Boundary {
134 fn new(ix: usize, next_indent: u32) -> Self {
135 Self { ix, next_indent }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::{font, TestAppContext, TestDispatcher};
143 use rand::prelude::*;
144
145 #[test]
146 fn test_wrap_line() {
147 let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
148 let cx = TestAppContext::new(dispatcher);
149
150 cx.update(|cx| {
151 let text_system = cx.text_system().clone();
152 let mut wrapper = LineWrapper::new(
153 text_system.font_id(&font("Courier")).unwrap(),
154 px(16.),
155 text_system.platform_text_system.clone(),
156 );
157 assert_eq!(
158 wrapper
159 .wrap_line("aa bbb cccc ddddd eeee", px(72.))
160 .collect::<Vec<_>>(),
161 &[
162 Boundary::new(7, 0),
163 Boundary::new(12, 0),
164 Boundary::new(18, 0)
165 ],
166 );
167 assert_eq!(
168 wrapper
169 .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
170 .collect::<Vec<_>>(),
171 &[
172 Boundary::new(4, 0),
173 Boundary::new(11, 0),
174 Boundary::new(18, 0)
175 ],
176 );
177 assert_eq!(
178 wrapper
179 .wrap_line(" aaaaaaa", px(72.))
180 .collect::<Vec<_>>(),
181 &[
182 Boundary::new(7, 5),
183 Boundary::new(9, 5),
184 Boundary::new(11, 5),
185 ]
186 );
187 assert_eq!(
188 wrapper
189 .wrap_line(" ", px(72.))
190 .collect::<Vec<_>>(),
191 &[
192 Boundary::new(7, 0),
193 Boundary::new(14, 0),
194 Boundary::new(21, 0)
195 ]
196 );
197 assert_eq!(
198 wrapper
199 .wrap_line(" aaaaaaaaaaaaaa", px(72.))
200 .collect::<Vec<_>>(),
201 &[
202 Boundary::new(7, 0),
203 Boundary::new(14, 3),
204 Boundary::new(18, 3),
205 Boundary::new(22, 3),
206 ]
207 );
208 });
209 }
210
211 // todo!("move this to a test on TextSystem::layout_text")
212 // todo! repeat this test
213 // #[test]
214 // fn test_wrap_shaped_line() {
215 // App::test().run(|cx| {
216 // let text_system = cx.text_system().clone();
217
218 // let normal = TextRun {
219 // len: 0,
220 // font: font("Helvetica"),
221 // color: Default::default(),
222 // underline: Default::default(),
223 // };
224 // let bold = TextRun {
225 // len: 0,
226 // font: font("Helvetica").bold(),
227 // color: Default::default(),
228 // underline: Default::default(),
229 // };
230
231 // impl TextRun {
232 // fn with_len(&self, len: usize) -> Self {
233 // let mut this = self.clone();
234 // this.len = len;
235 // this
236 // }
237 // }
238
239 // let text = "aa bbb cccc ddddd eeee".into();
240 // let lines = text_system
241 // .layout_text(
242 // &text,
243 // px(16.),
244 // &[
245 // normal.with_len(4),
246 // bold.with_len(5),
247 // normal.with_len(6),
248 // bold.with_len(1),
249 // normal.with_len(7),
250 // ],
251 // None,
252 // )
253 // .unwrap();
254 // let line = &lines[0];
255
256 // let mut wrapper = LineWrapper::new(
257 // text_system.font_id(&normal.font).unwrap(),
258 // px(16.),
259 // text_system.platform_text_system.clone(),
260 // );
261 // assert_eq!(
262 // wrapper
263 // .wrap_shaped_line(&text, &line, px(72.))
264 // .collect::<Vec<_>>(),
265 // &[
266 // ShapedBoundary {
267 // run_ix: 1,
268 // glyph_ix: 3
269 // },
270 // ShapedBoundary {
271 // run_ix: 2,
272 // glyph_ix: 3
273 // },
274 // ShapedBoundary {
275 // run_ix: 4,
276 // glyph_ix: 2
277 // }
278 // ],
279 // );
280 // });
281 // }
282}