1use std::ops::Range;
2
3use unicode_linebreak::{BreakOpportunity, linebreaks};
4
5use crate::layout::line::{LayoutLine, PositionedRun, RunDecorations};
6use crate::shaping::run::ShapedRun;
7use crate::shaping::shaper::FontMetricsPx;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum Alignment {
12 #[default]
13 Left,
14 Right,
15 Center,
16 Justify,
17}
18
19pub fn break_into_lines(
29 runs: Vec<ShapedRun>,
30 text: &str,
31 available_width: f32,
32 alignment: Alignment,
33 first_line_indent: f32,
34 metrics: &FontMetricsPx,
35) -> Vec<LayoutLine> {
36 if runs.is_empty() || text.is_empty() {
37 return vec![make_empty_line(metrics, 0..0)];
39 }
40
41 let flat = flatten_runs(&runs);
43 if flat.is_empty() {
44 return vec![make_empty_line(metrics, 0..0)];
45 }
46
47 let breaks: Vec<(usize, BreakOpportunity)> = linebreaks(text).collect();
49
50 let break_points = map_breaks_to_glyph_indices(&flat, &breaks);
52
53 let mut lines = Vec::new();
55 let mut line_start_glyph = 0usize;
56 let mut line_width = 0.0f32;
57 let mut last_break_glyph: Option<usize> = None;
58 let mut effective_width = available_width - first_line_indent;
60
61 for i in 0..flat.len() {
62 let glyph_advance = flat[i].x_advance;
63 line_width += glyph_advance;
64
65 if break_points.contains(&(i + 1)) {
67 last_break_glyph = Some(i + 1);
68 }
69
70 let is_mandatory = break_points_mandatory(&breaks, &flat, i + 1);
72
73 let exceeds_width = line_width > effective_width && line_start_glyph < i;
74
75 if is_mandatory || exceeds_width {
76 let break_at = if is_mandatory {
77 i + 1
78 } else if let Some(bp) = last_break_glyph {
79 if bp > line_start_glyph {
80 bp
81 } else {
82 i + 1 }
84 } else {
85 i + 1 };
87
88 let indent = if lines.is_empty() {
89 first_line_indent
90 } else {
91 0.0
92 };
93 let line = build_line(&runs, &flat, line_start_glyph, break_at, metrics, indent);
94 lines.push(line);
95
96 line_start_glyph = break_at;
97 effective_width = available_width;
99 line_width = 0.0;
101 for j in break_at..=i {
102 if j < flat.len() {
103 line_width += flat[j].x_advance;
104 }
105 }
106 last_break_glyph = None;
107 }
108 }
109
110 if line_start_glyph < flat.len() {
112 let line = build_line(
113 &runs,
114 &flat,
115 line_start_glyph,
116 flat.len(),
117 metrics,
118 if lines.is_empty() {
119 first_line_indent
120 } else {
121 0.0
122 },
123 );
124 lines.push(line);
125 }
126
127 let effective_width = available_width;
129 let last_idx = lines.len().saturating_sub(1);
130 for (i, line) in lines.iter_mut().enumerate() {
131 let indent = if i == 0 { first_line_indent } else { 0.0 };
132 let line_avail = effective_width - indent;
133 match alignment {
134 Alignment::Left => {} Alignment::Right => {
136 let shift = (line_avail - line.width).max(0.0);
137 for run in &mut line.runs {
138 run.x += shift;
139 }
140 }
141 Alignment::Center => {
142 let shift = ((line_avail - line.width) / 2.0).max(0.0);
143 for run in &mut line.runs {
144 run.x += shift;
145 }
146 }
147 Alignment::Justify => {
148 if i < last_idx && line.width > 0.0 {
150 justify_line(line, line_avail, text);
151 }
152 }
153 }
154 }
155
156 if lines.is_empty() {
157 lines.push(make_empty_line(metrics, 0..0));
158 }
159
160 lines
161}
162
163struct FlatGlyph {
165 x_advance: f32,
166 cluster: u32,
167 run_index: usize,
168 glyph_index_in_run: usize,
169}
170
171fn flatten_runs(runs: &[ShapedRun]) -> Vec<FlatGlyph> {
172 let mut flat = Vec::new();
173 for (run_idx, run) in runs.iter().enumerate() {
174 let cluster_offset = run.text_range.start as u32;
178 for (glyph_idx, glyph) in run.glyphs.iter().enumerate() {
179 flat.push(FlatGlyph {
180 x_advance: glyph.x_advance,
181 cluster: glyph.cluster + cluster_offset,
182 run_index: run_idx,
183 glyph_index_in_run: glyph_idx,
184 });
185 }
186 }
187 flat
188}
189
190fn map_breaks_to_glyph_indices(
193 flat: &[FlatGlyph],
194 breaks: &[(usize, BreakOpportunity)],
195) -> Vec<usize> {
196 let mut result = Vec::new();
197 for &(byte_offset, _opportunity) in breaks {
198 if let Some(glyph_idx) = flat.iter().position(|g| g.cluster as usize >= byte_offset) {
200 if !result.contains(&glyph_idx) {
201 result.push(glyph_idx);
202 }
203 } else {
204 result.push(flat.len());
206 }
207 }
208 result
209}
210
211fn break_points_mandatory(
213 breaks: &[(usize, BreakOpportunity)],
214 flat: &[FlatGlyph],
215 glyph_idx: usize,
216) -> bool {
217 if glyph_idx == 0 || glyph_idx > flat.len() {
218 return false;
219 }
220
221 let byte_offset = if glyph_idx < flat.len() {
223 flat[glyph_idx].cluster as usize
224 } else {
225 return false;
227 };
228
229 breaks
230 .iter()
231 .any(|&(bo, op)| bo == byte_offset && op == BreakOpportunity::Mandatory)
232}
233
234fn build_line(
236 runs: &[ShapedRun],
237 flat: &[FlatGlyph],
238 start: usize,
239 end: usize,
240 metrics: &FontMetricsPx,
241 indent: f32,
242) -> LayoutLine {
243 let mut positioned_runs = Vec::new();
245 let mut x = indent;
246 let mut current_run_idx: Option<usize> = None;
247 let mut run_glyph_start = 0usize;
248
249 for i in start..end {
250 let fg = &flat[i];
251 if current_run_idx != Some(fg.run_index) {
252 if let Some(prev_run_idx) = current_run_idx {
254 let prev_end = if i > start {
256 flat[i - 1].glyph_index_in_run + 1
257 } else {
258 run_glyph_start
259 };
260 let sub_run = extract_sub_run(runs, prev_run_idx, run_glyph_start, prev_end);
261 if let Some((pr, advance)) = sub_run {
262 positioned_runs.push(PositionedRun {
263 decorations: RunDecorations {
264 underline: pr.underline,
265 overline: pr.overline,
266 strikeout: pr.strikeout,
267 is_link: pr.is_link,
268 },
269 shaped_run: pr,
270 x,
271 });
272 x += advance;
273 }
274 }
275 current_run_idx = Some(fg.run_index);
276 run_glyph_start = fg.glyph_index_in_run;
277 }
278 }
279
280 if let Some(run_idx) = current_run_idx {
282 let end_in_run = if end < flat.len() && flat[end].run_index == run_idx {
283 flat[end].glyph_index_in_run
284 } else if end > start {
285 flat[end - 1].glyph_index_in_run + 1
286 } else {
287 run_glyph_start
288 };
289 let sub_run = extract_sub_run(runs, run_idx, run_glyph_start, end_in_run);
290 if let Some((pr, advance)) = sub_run {
291 positioned_runs.push(PositionedRun {
292 decorations: RunDecorations {
293 underline: pr.underline,
294 overline: pr.overline,
295 strikeout: pr.strikeout,
296 is_link: pr.is_link,
297 },
298 shaped_run: pr,
299 x,
300 });
301 x += advance;
302 }
303 }
304
305 let width = x - indent;
306
307 let char_start = if start < flat.len() {
309 flat[start].cluster as usize
310 } else {
311 0
312 };
313 let char_end = if end > 0 && end <= flat.len() {
314 if end < flat.len() {
315 flat[end].cluster as usize
316 } else {
317 flat[end - 1].cluster as usize + 1 }
320 } else {
321 char_start
322 };
323
324 let line_height = metrics.ascent + metrics.descent + metrics.leading;
325
326 LayoutLine {
327 runs: positioned_runs,
328 y: 0.0, ascent: metrics.ascent,
330 descent: metrics.descent,
331 leading: metrics.leading,
332 width,
333 char_range: char_start..char_end,
334 line_height,
335 }
336}
337
338fn extract_sub_run(
341 runs: &[ShapedRun],
342 run_index: usize,
343 glyph_start: usize,
344 glyph_end: usize,
345) -> Option<(ShapedRun, f32)> {
346 let run = &runs[run_index];
347 let end = glyph_end.min(run.glyphs.len());
348 if glyph_start >= end {
349 return None;
350 }
351 let cluster_offset = run.text_range.start as u32;
352 let mut sub_glyphs = run.glyphs[glyph_start..end].to_vec();
353 for g in &mut sub_glyphs {
355 g.cluster += cluster_offset;
356 }
357 let advance: f32 = sub_glyphs.iter().map(|g| g.x_advance).sum();
358
359 let sub_run = ShapedRun {
360 font_face_id: run.font_face_id,
361 size_px: run.size_px,
362 glyphs: sub_glyphs,
363 advance_width: advance,
364 text_range: run.text_range.clone(),
365 underline: run.underline,
366 overline: run.overline,
367 strikeout: run.strikeout,
368 is_link: run.is_link,
369 };
370 Some((sub_run, advance))
371}
372
373fn make_empty_line(metrics: &FontMetricsPx, char_range: Range<usize>) -> LayoutLine {
374 LayoutLine {
375 runs: Vec::new(),
376 y: 0.0,
377 ascent: metrics.ascent,
378 descent: metrics.descent,
379 leading: metrics.leading,
380 width: 0.0,
381 char_range,
382 line_height: metrics.ascent + metrics.descent + metrics.leading,
383 }
384}
385
386fn justify_line(line: &mut LayoutLine, target_width: f32, text: &str) {
391 let extra = target_width - line.width;
392 if extra <= 0.0 {
393 return;
394 }
395
396 let mut space_count = 0usize;
398 for run in &line.runs {
399 for glyph in &run.shaped_run.glyphs {
400 let byte_offset = glyph.cluster as usize;
401 if let Some(ch) = text.get(byte_offset..).and_then(|s| s.chars().next())
402 && ch == ' '
403 {
404 space_count += 1;
405 }
406 }
407 }
408
409 if space_count == 0 {
410 return;
411 }
412
413 let extra_per_space = extra / space_count as f32;
414
415 for run in &mut line.runs {
417 for glyph in &mut run.shaped_run.glyphs {
418 let byte_offset = glyph.cluster as usize;
419 if let Some(ch) = text.get(byte_offset..).and_then(|s| s.chars().next())
420 && ch == ' '
421 {
422 glyph.x_advance += extra_per_space;
423 }
424 }
425 run.shaped_run.advance_width = run.shaped_run.glyphs.iter().map(|g| g.x_advance).sum();
427 }
428
429 let first_x = line.runs.first().map(|r| r.x).unwrap_or(0.0);
431 let mut x = first_x;
432 for run in &mut line.runs {
433 run.x = x;
434 x += run.shaped_run.advance_width;
435 }
436
437 line.width = target_width;
438}