1use std::collections::HashSet;
2use std::ops::Range;
3
4use unicode_linebreak::{BreakOpportunity, linebreaks};
5
6use crate::layout::line::{LayoutLine, PositionedRun, RunDecorations};
7use crate::shaping::run::ShapedRun;
8use crate::shaping::shaper::FontMetricsPx;
9
10fn byte_offset_to_char_offset(text: &str, byte_offset: usize) -> usize {
18 let mut off = byte_offset.min(text.len());
19 while off > 0 && !text.is_char_boundary(off) {
20 off -= 1;
21 }
22 text[..off].chars().count()
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
27pub enum Alignment {
28 #[default]
29 Left,
30 Right,
31 Center,
32 Justify,
33}
34
35pub fn break_into_lines(
45 runs: Vec<ShapedRun>,
46 text: &str,
47 available_width: f32,
48 alignment: Alignment,
49 first_line_indent: f32,
50 metrics: &FontMetricsPx,
51) -> Vec<LayoutLine> {
52 if runs.is_empty() || text.is_empty() {
53 return vec![make_empty_line(metrics, 0..0)];
55 }
56
57 let flat = flatten_runs(&runs);
59 if flat.is_empty() {
60 return vec![make_empty_line(metrics, 0..0)];
61 }
62
63 let breaks: Vec<(usize, BreakOpportunity)> = linebreaks(text).collect();
65
66 let (break_points, mandatory_breaks) = map_breaks_to_glyph_indices(&flat, &breaks);
68
69 let mut lines = Vec::new();
71 let mut line_start_glyph = 0usize;
72 let mut line_width = 0.0f32;
73 let mut last_break_glyph: Option<usize> = None;
74 let mut effective_width = available_width - first_line_indent;
76
77 for i in 0..flat.len() {
78 let glyph_advance = flat[i].x_advance;
79 line_width += glyph_advance;
80
81 let is_mandatory = mandatory_breaks.contains(&(i + 1));
83
84 let exceeds_width = line_width > effective_width && line_start_glyph < i;
85
86 if is_mandatory || exceeds_width {
87 let break_at = if is_mandatory {
88 i + 1
89 } else if let Some(bp) = last_break_glyph {
90 if bp > line_start_glyph {
91 bp
92 } else {
93 i + 1 }
95 } else {
96 i + 1 };
98
99 let indent = if lines.is_empty() {
100 first_line_indent
101 } else {
102 0.0
103 };
104 let line = build_line(
105 &runs,
106 &flat,
107 line_start_glyph,
108 break_at,
109 metrics,
110 indent,
111 text,
112 );
113 lines.push(line);
114
115 line_start_glyph = break_at;
116 effective_width = available_width;
118 line_width = 0.0;
120 for j in break_at..=i {
121 if j < flat.len() {
122 line_width += flat[j].x_advance;
123 }
124 }
125 last_break_glyph = None;
126 }
127
128 if break_points.contains(&(i + 1)) {
133 last_break_glyph = Some(i + 1);
134 }
135 }
136
137 if line_start_glyph < flat.len() {
139 let line = build_line(
140 &runs,
141 &flat,
142 line_start_glyph,
143 flat.len(),
144 metrics,
145 if lines.is_empty() {
146 first_line_indent
147 } else {
148 0.0
149 },
150 text,
151 );
152 lines.push(line);
153 }
154
155 let effective_width = available_width;
157 let last_idx = lines.len().saturating_sub(1);
158 for (i, line) in lines.iter_mut().enumerate() {
159 let indent = if i == 0 { first_line_indent } else { 0.0 };
160 let line_avail = effective_width - indent;
161 match alignment {
162 Alignment::Left => {} Alignment::Right => {
164 let shift = (line_avail - line.width).max(0.0);
165 for run in &mut line.runs {
166 run.x += shift;
167 }
168 }
169 Alignment::Center => {
170 let shift = ((line_avail - line.width) / 2.0).max(0.0);
171 for run in &mut line.runs {
172 run.x += shift;
173 }
174 }
175 Alignment::Justify => {
176 if i < last_idx && line.width > 0.0 {
178 justify_line(line, line_avail, text);
179 }
180 }
181 }
182 }
183
184 if lines.is_empty() {
185 lines.push(make_empty_line(metrics, 0..0));
186 }
187
188 for line in &mut lines {
192 for run in &mut line.runs {
193 for glyph in &mut run.shaped_run.glyphs {
194 glyph.cluster = byte_offset_to_char_offset(text, glyph.cluster as usize) as u32;
195 }
196 }
197 }
198
199 lines
200}
201
202struct FlatGlyph {
204 x_advance: f32,
205 cluster: u32,
206 run_index: usize,
207 glyph_index_in_run: usize,
208}
209
210fn flatten_runs(runs: &[ShapedRun]) -> Vec<FlatGlyph> {
211 let mut flat = Vec::new();
212 for (run_idx, run) in runs.iter().enumerate() {
213 let cluster_offset = run.text_range.start as u32;
217 for (glyph_idx, glyph) in run.glyphs.iter().enumerate() {
218 flat.push(FlatGlyph {
219 x_advance: glyph.x_advance,
220 cluster: glyph.cluster + cluster_offset,
221 run_index: run_idx,
222 glyph_index_in_run: glyph_idx,
223 });
224 }
225 }
226 flat
227}
228
229fn map_breaks_to_glyph_indices(
235 flat: &[FlatGlyph],
236 breaks: &[(usize, BreakOpportunity)],
237) -> (HashSet<usize>, HashSet<usize>) {
238 let mut break_points = HashSet::new();
239 let mut mandatory_breaks = HashSet::new();
240 let mut glyph_cursor = 0usize;
241
242 for &(byte_offset, opportunity) in breaks {
243 while glyph_cursor < flat.len() && (flat[glyph_cursor].cluster as usize) < byte_offset {
245 glyph_cursor += 1;
246 }
247 let glyph_idx = if glyph_cursor < flat.len() {
248 glyph_cursor
249 } else {
250 flat.len()
251 };
252 break_points.insert(glyph_idx);
253 if opportunity == BreakOpportunity::Mandatory {
254 mandatory_breaks.insert(glyph_idx);
255 }
256 }
257
258 (break_points, mandatory_breaks)
259}
260
261fn build_line(
263 runs: &[ShapedRun],
264 flat: &[FlatGlyph],
265 start: usize,
266 end: usize,
267 metrics: &FontMetricsPx,
268 indent: f32,
269 text: &str,
270) -> LayoutLine {
271 let mut positioned_runs = Vec::new();
273 let mut x = indent;
274 let mut current_run_idx: Option<usize> = None;
275 let mut run_glyph_start = 0usize;
276
277 for i in start..end {
278 let fg = &flat[i];
279 if current_run_idx != Some(fg.run_index) {
280 if let Some(prev_run_idx) = current_run_idx {
282 let prev_end = if i > start {
284 flat[i - 1].glyph_index_in_run + 1
285 } else {
286 run_glyph_start
287 };
288 let sub_run = extract_sub_run(runs, prev_run_idx, run_glyph_start, prev_end);
289 if let Some((pr, advance)) = sub_run {
290 positioned_runs.push(PositionedRun {
291 decorations: RunDecorations {
292 underline_style: pr.underline_style,
293 overline: pr.overline,
294 strikeout: pr.strikeout,
295 is_link: pr.is_link,
296 foreground_color: pr.foreground_color,
297 underline_color: pr.underline_color,
298 background_color: pr.background_color,
299 anchor_href: pr.anchor_href.clone(),
300 tooltip: pr.tooltip.clone(),
301 vertical_alignment: pr.vertical_alignment,
302 },
303 shaped_run: pr,
304 x,
305 });
306 x += advance;
307 }
308 }
309 current_run_idx = Some(fg.run_index);
310 run_glyph_start = fg.glyph_index_in_run;
311 }
312 }
313
314 if let Some(run_idx) = current_run_idx {
316 let end_in_run = if end < flat.len() && flat[end].run_index == run_idx {
317 flat[end].glyph_index_in_run
318 } else if end > start {
319 flat[end - 1].glyph_index_in_run + 1
320 } else {
321 run_glyph_start
322 };
323 let sub_run = extract_sub_run(runs, run_idx, run_glyph_start, end_in_run);
324 if let Some((pr, advance)) = sub_run {
325 positioned_runs.push(PositionedRun {
326 decorations: RunDecorations {
327 underline_style: pr.underline_style,
328 overline: pr.overline,
329 strikeout: pr.strikeout,
330 is_link: pr.is_link,
331 foreground_color: pr.foreground_color,
332 underline_color: pr.underline_color,
333 background_color: pr.background_color,
334 anchor_href: pr.anchor_href.clone(),
335 tooltip: pr.tooltip.clone(),
336 vertical_alignment: pr.vertical_alignment,
337 },
338 shaped_run: pr,
339 x,
340 });
341 x += advance;
342 }
343 }
344
345 let width = x - indent;
346
347 let byte_start = if start < flat.len() {
351 flat[start].cluster as usize
352 } else {
353 0
354 };
355 let byte_end = if end > 0 && end <= flat.len() {
356 if end < flat.len() {
357 flat[end].cluster as usize
358 } else {
359 text.len()
367 }
368 } else {
369 byte_start
370 };
371 let char_start = byte_offset_to_char_offset(text, byte_start);
372 let char_end = byte_offset_to_char_offset(text, byte_end);
373
374 let mut ascent = metrics.ascent;
376 for run in &positioned_runs {
377 if run.shaped_run.image_name.is_some() && run.shaped_run.image_height > ascent {
378 ascent = run.shaped_run.image_height;
379 }
380 }
381 let line_height = ascent + metrics.descent + metrics.leading;
382
383 LayoutLine {
384 runs: positioned_runs,
385 y: 0.0, ascent,
387 descent: metrics.descent,
388 leading: metrics.leading,
389 width,
390 char_range: char_start..char_end,
391 line_height,
392 }
393}
394
395fn extract_sub_run(
398 runs: &[ShapedRun],
399 run_index: usize,
400 glyph_start: usize,
401 glyph_end: usize,
402) -> Option<(ShapedRun, f32)> {
403 let run = &runs[run_index];
404 let end = glyph_end.min(run.glyphs.len());
405 if glyph_start >= end {
406 return None;
407 }
408 let cluster_offset = run.text_range.start as u32;
409 let mut sub_glyphs = run.glyphs[glyph_start..end].to_vec();
410 for g in &mut sub_glyphs {
412 g.cluster += cluster_offset;
413 }
414 let advance: f32 = sub_glyphs.iter().map(|g| g.x_advance).sum();
415
416 let sub_run = ShapedRun {
417 font_face_id: run.font_face_id,
418 size_px: run.size_px,
419 weight: run.weight,
420 glyphs: sub_glyphs,
421 advance_width: advance,
422 text_range: run.text_range.clone(),
423 underline_style: run.underline_style,
424 overline: run.overline,
425 strikeout: run.strikeout,
426 is_link: run.is_link,
427 foreground_color: run.foreground_color,
428 underline_color: run.underline_color,
429 background_color: run.background_color,
430 anchor_href: run.anchor_href.clone(),
431 tooltip: run.tooltip.clone(),
432 vertical_alignment: run.vertical_alignment,
433 image_name: run.image_name.clone(),
434 image_height: run.image_height,
435 };
436 Some((sub_run, advance))
437}
438
439fn make_empty_line(metrics: &FontMetricsPx, char_range: Range<usize>) -> LayoutLine {
440 LayoutLine {
441 runs: Vec::new(),
442 y: 0.0,
443 ascent: metrics.ascent,
444 descent: metrics.descent,
445 leading: metrics.leading,
446 width: 0.0,
447 char_range,
448 line_height: metrics.ascent + metrics.descent + metrics.leading,
449 }
450}
451
452fn justify_line(line: &mut LayoutLine, target_width: f32, text: &str) {
457 let extra = target_width - line.width;
458 if extra <= 0.0 {
459 return;
460 }
461
462 let mut space_count = 0usize;
464 for run in &line.runs {
465 for glyph in &run.shaped_run.glyphs {
466 let byte_offset = glyph.cluster as usize;
467 if let Some(ch) = text.get(byte_offset..).and_then(|s| s.chars().next())
468 && ch == ' '
469 {
470 space_count += 1;
471 }
472 }
473 }
474
475 if space_count == 0 {
476 return;
477 }
478
479 let extra_per_space = extra / space_count as f32;
480
481 for run in &mut line.runs {
483 for glyph in &mut run.shaped_run.glyphs {
484 let byte_offset = glyph.cluster as usize;
485 if let Some(ch) = text.get(byte_offset..).and_then(|s| s.chars().next())
486 && ch == ' '
487 {
488 glyph.x_advance += extra_per_space;
489 }
490 }
491 run.shaped_run.advance_width = run.shaped_run.glyphs.iter().map(|g| g.x_advance).sum();
493 }
494
495 let first_x = line.runs.first().map(|r| r.x).unwrap_or(0.0);
497 let mut x = first_x;
498 for run in &mut line.runs {
499 run.x = x;
500 x += run.shaped_run.advance_width;
501 }
502
503 line.width = target_width;
504}