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