1use core::ops::Range;
5
6use euclid::num::Zero;
7
8use super::glyphclusters::GlyphClusterIterator;
9use super::{BreakOpportunity, CheckedAdd, LineBreakIterator, ShapeBuffer};
10
11#[derive(Debug, PartialEq, Eq, Default)]
12pub struct TextFragment<Length> {
13 pub byte_range: Range<usize>,
14 pub glyph_range: Range<usize>,
15 pub width: Length,
16 pub trailing_whitespace_width: Length,
17 pub trailing_whitespace_bytes: usize,
18 pub trailing_mandatory_break: bool,
19}
20
21#[derive(Clone)]
22pub struct TextFragmentIterator<'a, Length> {
23 line_breaks: LineBreakIterator<'a>,
24 glyph_clusters: GlyphClusterIterator<'a, Length>,
25 text_len: usize,
26 pub break_anywhere: bool,
27 pub truncated: bool,
30}
31
32impl<'a, Length> TextFragmentIterator<'a, Length> {
33 pub fn new(text: &'a str, shape_buffer: &'a ShapeBuffer<Length>) -> Self {
34 Self {
35 line_breaks: LineBreakIterator::new(text),
36 glyph_clusters: GlyphClusterIterator::new(text, shape_buffer),
37 text_len: text.len(),
38 break_anywhere: false,
39 truncated: false,
40 }
41 }
42}
43
44impl<Length: Clone + Default + core::ops::AddAssign + CheckedAdd + Zero + Copy> Iterator
45 for TextFragmentIterator<'_, Length>
46{
47 type Item = TextFragment<Length>;
48
49 fn next(&mut self) -> Option<Self::Item> {
50 let first_glyph_cluster = self.glyph_clusters.next()?;
51
52 let mut fragment = Self::Item::default();
53 self.truncated = false;
54
55 let next_break_offset = if self.break_anywhere {
56 if first_glyph_cluster.is_line_or_paragraph_separator {
57 fragment.trailing_mandatory_break = true;
58 }
59 0
60 } else if let Some((next_break_offset, break_type)) = self.line_breaks.next() {
61 if matches!(break_type, BreakOpportunity::Mandatory) {
62 fragment.trailing_mandatory_break = true;
63 }
64 next_break_offset
65 } else {
66 self.text_len
67 };
68
69 if first_glyph_cluster.is_whitespace {
70 fragment.trailing_whitespace_width = first_glyph_cluster.width;
71 fragment.trailing_whitespace_bytes = first_glyph_cluster.byte_range.len();
72 fragment.byte_range.start = first_glyph_cluster.byte_range.start;
73 fragment.byte_range.end = first_glyph_cluster.byte_range.start;
74 } else {
75 fragment.width = first_glyph_cluster.width;
76 fragment.byte_range = first_glyph_cluster.byte_range.clone();
77 }
78
79 let start = first_glyph_cluster.glyph_range.start;
80 let mut last_glyph_cluster = first_glyph_cluster;
81
82 while last_glyph_cluster.byte_range.end < next_break_offset {
83 let next_glyph_cluster = match self.glyph_clusters.next() {
84 Some(cluster) => cluster,
85 None => break,
86 };
87
88 if next_glyph_cluster.is_line_or_paragraph_separator {
89 break;
90 }
91
92 if next_glyph_cluster.is_whitespace {
93 let Some(width) =
94 fragment.trailing_whitespace_width.checked_add(next_glyph_cluster.width)
95 else {
96 self.truncated = true;
97 break;
98 };
99 fragment.trailing_whitespace_width = width;
100 fragment.trailing_whitespace_bytes += next_glyph_cluster.byte_range.len();
101 } else {
102 let folded_whitespace = if last_glyph_cluster.is_whitespace {
105 fragment.trailing_whitespace_width
106 } else {
107 Length::zero()
108 };
109 let Some(width) = fragment
112 .width
113 .checked_add(folded_whitespace)
114 .and_then(|width| width.checked_add(next_glyph_cluster.width))
115 else {
116 self.truncated = true;
117 break;
118 };
119 fragment.width = width;
120 fragment.byte_range.end = next_glyph_cluster.byte_range.end;
121 if last_glyph_cluster.is_whitespace {
122 fragment.trailing_whitespace_width = Length::zero();
123 fragment.trailing_whitespace_bytes = 0;
124 }
125 }
126
127 last_glyph_cluster = next_glyph_cluster.clone();
128 }
129
130 fragment.glyph_range = Range { start, end: last_glyph_cluster.glyph_range.end };
131
132 if fragment.trailing_mandatory_break && !self.break_anywhere {
136 fragment.trailing_whitespace_bytes = next_break_offset - fragment.byte_range.end;
137 }
138
139 Some(fragment)
140 }
141}
142
143#[cfg(test)]
144use super::{FixedTestFont, TextLayout};
145#[cfg(test)]
146use std::{vec, vec::Vec};
147
148#[test]
149fn fragment_iterator_simple() {
150 let font = FixedTestFont;
151 let text = "H WX";
152 let shape_buffer = ShapeBuffer::new(
153 &TextLayout { font: &font, letter_spacing: None, line_height: None },
154 text,
155 );
156 let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
157 let expected = vec![
158 TextFragment {
159 byte_range: Range { start: 0, end: 1 },
160 glyph_range: Range { start: 0, end: 2 },
161 width: 10.,
162 trailing_whitespace_width: 10.,
163 trailing_mandatory_break: false,
164 trailing_whitespace_bytes: 1,
165 },
166 TextFragment {
167 byte_range: Range { start: 2, end: text.len() },
168 glyph_range: Range { start: 2, end: text.len() },
169 width: 20.,
170 trailing_whitespace_width: 0.,
171 trailing_mandatory_break: false,
172 trailing_whitespace_bytes: 0,
173 },
174 ];
175 assert_eq!(fragments, expected);
176}
177
178#[test]
179fn fragment_iterator_simple_v2() {
180 let font = FixedTestFont;
181 let text = "Hello World";
182 let shape_buffer = ShapeBuffer::new(
183 &TextLayout { font: &font, letter_spacing: None, line_height: None },
184 text,
185 );
186 let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
187 let expected = vec![
188 TextFragment {
189 byte_range: Range { start: 0, end: 5 },
190 glyph_range: Range { start: 0, end: 6 },
191 width: 50.,
192 trailing_whitespace_width: 10.,
193 trailing_mandatory_break: false,
194 trailing_whitespace_bytes: 1,
195 },
196 TextFragment {
197 byte_range: Range { start: 6, end: text.len() },
198 glyph_range: Range { start: 6, end: text.len() },
199 width: 10. * (text.len() - 6) as f32,
200 trailing_whitespace_width: 0.,
201 trailing_whitespace_bytes: 0,
202 trailing_mandatory_break: false,
203 },
204 ];
205 assert_eq!(fragments, expected);
206}
207
208#[test]
209fn fragment_iterator_forced_break() {
210 let font = FixedTestFont;
211 let text = "H\nW";
212 let shape_buffer = ShapeBuffer::new(
213 &TextLayout { font: &font, letter_spacing: None, line_height: None },
214 text,
215 );
216 let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
217 assert_eq!(
218 fragments,
219 vec![
220 TextFragment {
221 byte_range: Range { start: 0, end: 1 },
222 glyph_range: Range { start: 0, end: 1 },
223 width: 10.,
224 trailing_whitespace_width: 0.,
225 trailing_whitespace_bytes: 1,
226 trailing_mandatory_break: true,
227 },
228 TextFragment {
229 byte_range: Range { start: 2, end: 3 },
230 glyph_range: Range { start: 2, end: 3 },
231 width: 10.,
232 trailing_whitespace_width: 0.,
233 trailing_whitespace_bytes: 0,
234 trailing_mandatory_break: false,
235 },
236 ]
237 );
238}
239
240#[test]
241fn fragment_iterator_forced_break_multi() {
242 let font = FixedTestFont;
243 let text = "H\n\n\nW";
244 let shape_buffer = ShapeBuffer::new(
245 &TextLayout { font: &font, letter_spacing: None, line_height: None },
246 text,
247 );
248 let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
249 assert_eq!(
250 fragments,
251 vec![
252 TextFragment {
253 byte_range: Range { start: 0, end: 1 },
254 glyph_range: Range { start: 0, end: 1 },
255 width: 10.,
256 trailing_whitespace_width: 0.,
257 trailing_whitespace_bytes: 1,
258 trailing_mandatory_break: true,
259 },
260 TextFragment {
261 byte_range: Range { start: 2, end: 2 },
262 glyph_range: Range { start: 2, end: 3 },
263 width: 0.,
264 trailing_whitespace_width: 10.,
265 trailing_whitespace_bytes: 1,
266 trailing_mandatory_break: true,
267 },
268 TextFragment {
269 byte_range: Range { start: 3, end: 3 },
270 glyph_range: Range { start: 3, end: 4 },
271 width: 0.,
272 trailing_whitespace_width: 10.,
273 trailing_whitespace_bytes: 1,
274 trailing_mandatory_break: true,
275 },
276 TextFragment {
277 byte_range: Range { start: 4, end: 5 },
278 glyph_range: Range { start: 4, end: 5 },
279 width: 10.,
280 trailing_whitespace_width: 0.,
281 trailing_whitespace_bytes: 0,
282 trailing_mandatory_break: false,
283 },
284 ]
285 );
286}
287
288#[test]
289fn fragment_iterator_nbsp() {
290 let font = FixedTestFont;
291 let text = "X H\u{00a0}W";
292 let shape_buffer = ShapeBuffer::new(
293 &TextLayout { font: &font, letter_spacing: None, line_height: None },
294 text,
295 );
296 let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
297 assert_eq!(
298 fragments,
299 vec![
300 TextFragment {
301 byte_range: Range { start: 0, end: 1 },
302 glyph_range: Range { start: 0, end: 2 },
303 width: 10.,
304 trailing_whitespace_width: 10.,
305 trailing_whitespace_bytes: 1,
306 trailing_mandatory_break: false,
307 },
308 TextFragment {
309 byte_range: Range { start: 2, end: 6 },
310 glyph_range: Range { start: 2, end: 5 },
311 width: 30.,
312 trailing_whitespace_width: 0.,
313 trailing_whitespace_bytes: 0,
314 trailing_mandatory_break: false,
315 }
316 ]
317 );
318}
319
320#[test]
321fn fragment_iterator_break_anywhere() {
322 let font = FixedTestFont;
323 let text = "AB\nCD\nEF";
324 let shape_buffer = ShapeBuffer::new(
325 &TextLayout { font: &font, letter_spacing: None, line_height: None },
326 text,
327 );
328 let mut fragments = TextFragmentIterator::new(text, &shape_buffer);
329 assert_eq!(
330 fragments.next(),
331 Some(TextFragment {
332 byte_range: Range { start: 0, end: 2 },
333 glyph_range: Range { start: 0, end: 2 },
334 width: 20.,
335 trailing_whitespace_width: 0.,
336 trailing_whitespace_bytes: 1,
337 trailing_mandatory_break: true,
338 })
339 );
340 assert_eq!(
341 fragments.next(),
342 Some(TextFragment {
343 byte_range: Range { start: 3, end: 5 },
344 glyph_range: Range { start: 3, end: 5 },
345 width: 20.,
346 trailing_whitespace_width: 0.,
347 trailing_whitespace_bytes: 1,
348 trailing_mandatory_break: true,
349 },)
350 );
351 fragments.break_anywhere = true;
352 let last_two = fragments.by_ref().take(2).collect::<Vec<_>>();
353 assert_eq!(
354 last_two,
355 vec![
356 TextFragment {
357 byte_range: Range { start: 6, end: 7 },
358 glyph_range: Range { start: 6, end: 7 },
359 width: 10.,
360 trailing_whitespace_width: 0.,
361 trailing_whitespace_bytes: 0,
362 trailing_mandatory_break: false,
363 },
364 TextFragment {
365 byte_range: Range { start: 7, end: 8 },
366 glyph_range: Range { start: 7, end: 8 },
367 width: 10.,
368 trailing_whitespace_width: 0.,
369 trailing_whitespace_bytes: 0,
370 trailing_mandatory_break: false,
371 },
372 ]
373 );
374}
375
376#[test]
377fn fragment_iterator_leading_nbsp() {
378 let font = FixedTestFont;
379 let text = "A\n\u{00a0}\u{00a0}AB";
380 let shape_buffer = ShapeBuffer::new(
381 &TextLayout { font: &font, letter_spacing: None, line_height: None },
382 text,
383 );
384 let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
385 assert_eq!(
386 fragments,
387 vec![
388 TextFragment {
389 byte_range: Range { start: 0, end: 1 },
390 glyph_range: Range { start: 0, end: 1 },
391 width: 10.,
392 trailing_whitespace_width: 0.,
393 trailing_whitespace_bytes: 1,
394 trailing_mandatory_break: true,
395 },
396 TextFragment {
397 byte_range: Range { start: 2, end: 8 },
398 glyph_range: Range { start: 2, end: 6 },
399 width: 40.,
400 trailing_whitespace_width: 0.,
401 trailing_whitespace_bytes: 0,
402 trailing_mandatory_break: false,
403 }
404 ]
405 );
406}