1use std::ops::RangeInclusive;
2use std::sync::Arc;
3
4use emath::{pos2, vec2, Align, GuiRounding as _, NumExt, Pos2, Rect, Vec2};
5
6use crate::{stroke::PathStroke, text::font::Font, Color32, Mesh, Stroke, Vertex};
7
8use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals};
9
10#[derive(Clone, Copy)]
14struct PointScale {
15 pub pixels_per_point: f32,
16}
17
18impl PointScale {
19 #[inline(always)]
20 pub fn new(pixels_per_point: f32) -> Self {
21 Self { pixels_per_point }
22 }
23
24 #[inline(always)]
25 pub fn pixels_per_point(&self) -> f32 {
26 self.pixels_per_point
27 }
28
29 #[inline(always)]
30 pub fn round_to_pixel(&self, point: f32) -> f32 {
31 (point * self.pixels_per_point).round() / self.pixels_per_point
32 }
33
34 #[inline(always)]
35 pub fn floor_to_pixel(&self, point: f32) -> f32 {
36 (point * self.pixels_per_point).floor() / self.pixels_per_point
37 }
38}
39
40#[derive(Clone)]
44struct Paragraph {
45 pub cursor_x: f32,
47
48 pub section_index_at_start: u32,
50
51 pub glyphs: Vec<Glyph>,
52
53 pub empty_paragraph_height: f32,
55}
56
57impl Paragraph {
58 pub fn from_section_index(section_index_at_start: u32) -> Self {
59 Self {
60 cursor_x: 0.0,
61 section_index_at_start,
62 glyphs: vec![],
63 empty_paragraph_height: 0.0,
64 }
65 }
66}
67
68pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
73 if job.wrap.max_rows == 0 {
74 return Galley {
76 job,
77 rows: Default::default(),
78 rect: Rect::from_min_max(Pos2::ZERO, Pos2::ZERO),
79 mesh_bounds: Rect::NOTHING,
80 num_vertices: 0,
81 num_indices: 0,
82 pixels_per_point: fonts.pixels_per_point(),
83 elided: true,
84 };
85 }
86
87 let mut paragraphs = vec![Paragraph::from_section_index(0)];
90 for (section_index, section) in job.sections.iter().enumerate() {
91 layout_section(fonts, &job, section_index as u32, section, &mut paragraphs);
92 }
93
94 let point_scale = PointScale::new(fonts.pixels_per_point());
95
96 let mut elided = false;
97 let mut rows = rows_from_paragraphs(paragraphs, &job, &mut elided);
98 if elided {
99 if let Some(last_row) = rows.last_mut() {
100 replace_last_glyph_with_overflow_character(fonts, &job, last_row);
101 if let Some(last) = last_row.glyphs.last() {
102 last_row.rect.max.x = last.max_x();
103 }
104 }
105 }
106
107 let justify = job.justify && job.wrap.max_width.is_finite();
108
109 if justify || job.halign != Align::LEFT {
110 let num_rows = rows.len();
111 for (i, row) in rows.iter_mut().enumerate() {
112 let is_last_row = i + 1 == num_rows;
113 let justify_row = justify && !row.ends_with_newline && !is_last_row;
114 halign_and_justify_row(
115 point_scale,
116 row,
117 job.halign,
118 job.wrap.max_width,
119 justify_row,
120 );
121 }
122 }
123
124 galley_from_rows(point_scale, job, rows, elided)
126}
127
128fn layout_section(
130 fonts: &mut FontsImpl,
131 job: &LayoutJob,
132 section_index: u32,
133 section: &LayoutSection,
134 out_paragraphs: &mut Vec<Paragraph>,
135) {
136 let LayoutSection {
137 leading_space,
138 byte_range,
139 format,
140 } = section;
141 let font = fonts.font(&format.font_id);
142 let line_height = section
143 .format
144 .line_height
145 .unwrap_or_else(|| font.row_height());
146 let extra_letter_spacing = section.format.extra_letter_spacing;
147
148 let mut paragraph = out_paragraphs.last_mut().unwrap();
149 if paragraph.glyphs.is_empty() {
150 paragraph.empty_paragraph_height = line_height; }
152
153 paragraph.cursor_x += leading_space;
154
155 let mut last_glyph_id = None;
156
157 for chr in job.text[byte_range.clone()].chars() {
158 if job.break_on_newline && chr == '\n' {
159 out_paragraphs.push(Paragraph::from_section_index(section_index));
160 paragraph = out_paragraphs.last_mut().unwrap();
161 paragraph.empty_paragraph_height = line_height; } else {
163 let (font_impl, glyph_info) = font.font_impl_and_glyph_info(chr);
164 if let Some(font_impl) = font_impl {
165 if let Some(last_glyph_id) = last_glyph_id {
166 paragraph.cursor_x += font_impl.pair_kerning(last_glyph_id, glyph_info.id);
167 paragraph.cursor_x += extra_letter_spacing;
168 }
169 }
170
171 paragraph.glyphs.push(Glyph {
172 chr,
173 pos: pos2(paragraph.cursor_x, f32::NAN),
174 advance_width: glyph_info.advance_width,
175 line_height,
176 font_impl_height: font_impl.map_or(0.0, |f| f.row_height()),
177 font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()),
178 font_height: font.row_height(),
179 font_ascent: font.ascent(),
180 uv_rect: glyph_info.uv_rect,
181 section_index,
182 });
183
184 paragraph.cursor_x += glyph_info.advance_width;
185 paragraph.cursor_x = font.round_to_pixel(paragraph.cursor_x);
186 last_glyph_id = Some(glyph_info.id);
187 }
188 }
189}
190
191fn rect_from_x_range(x_range: RangeInclusive<f32>) -> Rect {
193 Rect::from_x_y_ranges(x_range, 0.0..=0.0)
194}
195
196fn rows_from_paragraphs(
198 paragraphs: Vec<Paragraph>,
199 job: &LayoutJob,
200 elided: &mut bool,
201) -> Vec<Row> {
202 let num_paragraphs = paragraphs.len();
203
204 let mut rows = vec![];
205
206 for (i, paragraph) in paragraphs.into_iter().enumerate() {
207 if job.wrap.max_rows <= rows.len() {
208 *elided = true;
209 break;
210 }
211
212 let is_last_paragraph = (i + 1) == num_paragraphs;
213
214 if paragraph.glyphs.is_empty() {
215 rows.push(Row {
216 section_index_at_start: paragraph.section_index_at_start,
217 glyphs: vec![],
218 visuals: Default::default(),
219 rect: Rect::from_min_size(
220 pos2(paragraph.cursor_x, 0.0),
221 vec2(0.0, paragraph.empty_paragraph_height),
222 ),
223 ends_with_newline: !is_last_paragraph,
224 });
225 } else {
226 let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x();
227 if paragraph_max_x <= job.effective_wrap_width() {
228 let paragraph_min_x = paragraph.glyphs[0].pos.x;
230 rows.push(Row {
231 section_index_at_start: paragraph.section_index_at_start,
232 glyphs: paragraph.glyphs,
233 visuals: Default::default(),
234 rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x),
235 ends_with_newline: !is_last_paragraph,
236 });
237 } else {
238 line_break(¶graph, job, &mut rows, elided);
239 rows.last_mut().unwrap().ends_with_newline = !is_last_paragraph;
240 }
241 }
242 }
243
244 rows
245}
246
247fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec<Row>, elided: &mut bool) {
248 let wrap_width = job.effective_wrap_width();
249
250 let mut row_break_candidates = RowBreakCandidates::default();
252
253 let mut first_row_indentation = paragraph.glyphs[0].pos.x;
254 let mut row_start_x = 0.0;
255 let mut row_start_idx = 0;
256
257 for i in 0..paragraph.glyphs.len() {
258 if job.wrap.max_rows <= out_rows.len() {
259 *elided = true;
260 break;
261 }
262
263 let potential_row_width = paragraph.glyphs[i].max_x() - row_start_x;
264
265 if wrap_width < potential_row_width {
266 if first_row_indentation > 0.0
269 && !row_break_candidates.has_good_candidate(job.wrap.break_anywhere)
270 {
271 out_rows.push(Row {
274 section_index_at_start: paragraph.section_index_at_start,
275 glyphs: vec![],
276 visuals: Default::default(),
277 rect: rect_from_x_range(first_row_indentation..=first_row_indentation),
278 ends_with_newline: false,
279 });
280 row_start_x += first_row_indentation;
281 first_row_indentation = 0.0;
282 } else if let Some(last_kept_index) = row_break_candidates.get(job.wrap.break_anywhere)
283 {
284 let glyphs: Vec<Glyph> = paragraph.glyphs[row_start_idx..=last_kept_index]
285 .iter()
286 .copied()
287 .map(|mut glyph| {
288 glyph.pos.x -= row_start_x;
289 glyph
290 })
291 .collect();
292
293 let section_index_at_start = glyphs[0].section_index;
294 let paragraph_min_x = glyphs[0].pos.x;
295 let paragraph_max_x = glyphs.last().unwrap().max_x();
296
297 out_rows.push(Row {
298 section_index_at_start,
299 glyphs,
300 visuals: Default::default(),
301 rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x),
302 ends_with_newline: false,
303 });
304
305 row_start_idx = last_kept_index + 1;
307 row_start_x = paragraph.glyphs[row_start_idx].pos.x;
308 row_break_candidates.forget_before_idx(row_start_idx);
309 } else {
310 }
312 }
313
314 row_break_candidates.add(i, ¶graph.glyphs[i..]);
315 }
316
317 if row_start_idx < paragraph.glyphs.len() {
318 if job.wrap.max_rows <= out_rows.len() {
321 *elided = true; } else {
323 let glyphs: Vec<Glyph> = paragraph.glyphs[row_start_idx..]
324 .iter()
325 .copied()
326 .map(|mut glyph| {
327 glyph.pos.x -= row_start_x;
328 glyph
329 })
330 .collect();
331
332 let section_index_at_start = glyphs[0].section_index;
333 let paragraph_min_x = glyphs[0].pos.x;
334 let paragraph_max_x = glyphs.last().unwrap().max_x();
335
336 out_rows.push(Row {
337 section_index_at_start,
338 glyphs,
339 visuals: Default::default(),
340 rect: rect_from_x_range(paragraph_min_x..=paragraph_max_x),
341 ends_with_newline: false,
342 });
343 }
344 }
345}
346
347fn replace_last_glyph_with_overflow_character(
351 fonts: &mut FontsImpl,
352 job: &LayoutJob,
353 row: &mut Row,
354) {
355 fn row_width(row: &Row) -> f32 {
356 if let (Some(first), Some(last)) = (row.glyphs.first(), row.glyphs.last()) {
357 last.max_x() - first.pos.x
358 } else {
359 0.0
360 }
361 }
362
363 fn row_height(section: &LayoutSection, font: &Font) -> f32 {
364 section
365 .format
366 .line_height
367 .unwrap_or_else(|| font.row_height())
368 }
369
370 let Some(overflow_character) = job.wrap.overflow_character else {
371 return;
372 };
373
374 if let Some(last_glyph) = row.glyphs.last() {
376 let section_index = last_glyph.section_index;
377 let section = &job.sections[section_index as usize];
378 let font = fonts.font(§ion.format.font_id);
379 let line_height = row_height(section, font);
380
381 let (_, last_glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
382
383 let mut x = last_glyph.pos.x + last_glyph.advance_width;
384
385 let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character);
386
387 {
388 x += section.format.extra_letter_spacing;
390 if let Some(font_impl) = font_impl {
391 x += font_impl.pair_kerning(last_glyph_info.id, replacement_glyph_info.id);
392 }
393 }
394
395 row.glyphs.push(Glyph {
396 chr: overflow_character,
397 pos: pos2(x, f32::NAN),
398 advance_width: replacement_glyph_info.advance_width,
399 line_height,
400 font_impl_height: font_impl.map_or(0.0, |f| f.row_height()),
401 font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()),
402 font_height: font.row_height(),
403 font_ascent: font.ascent(),
404 uv_rect: replacement_glyph_info.uv_rect,
405 section_index,
406 });
407 } else {
408 let section_index = row.section_index_at_start;
409 let section = &job.sections[section_index as usize];
410 let font = fonts.font(§ion.format.font_id);
411 let line_height = row_height(section, font);
412
413 let x = 0.0; let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character);
416
417 row.glyphs.push(Glyph {
418 chr: overflow_character,
419 pos: pos2(x, f32::NAN),
420 advance_width: replacement_glyph_info.advance_width,
421 line_height,
422 font_impl_height: font_impl.map_or(0.0, |f| f.row_height()),
423 font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()),
424 font_height: font.row_height(),
425 font_ascent: font.ascent(),
426 uv_rect: replacement_glyph_info.uv_rect,
427 section_index,
428 });
429 }
430
431 if row_width(row) <= job.effective_wrap_width() || row.glyphs.len() == 1 {
432 return; }
434
435 row.glyphs.pop();
437
438 loop {
442 let (prev_glyph, last_glyph) = match row.glyphs.as_mut_slice() {
443 [.., prev, last] => (Some(prev), last),
444 [.., last] => (None, last),
445 _ => {
446 unreachable!("We've already explicitly handled the empty row");
447 }
448 };
449
450 let section = &job.sections[last_glyph.section_index as usize];
451 let extra_letter_spacing = section.format.extra_letter_spacing;
452 let font = fonts.font(§ion.format.font_id);
453
454 if let Some(prev_glyph) = prev_glyph {
455 let prev_glyph_id = font.font_impl_and_glyph_info(prev_glyph.chr).1.id;
456
457 let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
459 last_glyph.pos.x -= extra_letter_spacing;
460 if let Some(font_impl) = font_impl {
461 last_glyph.pos.x -= font_impl.pair_kerning(prev_glyph_id, glyph_info.id);
462 }
463
464 last_glyph.chr = overflow_character;
466 let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
467 last_glyph.advance_width = glyph_info.advance_width;
468 last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent());
469 last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height());
470 last_glyph.uv_rect = glyph_info.uv_rect;
471
472 last_glyph.pos.x += extra_letter_spacing;
474 if let Some(font_impl) = font_impl {
475 last_glyph.pos.x += font_impl.pair_kerning(prev_glyph_id, glyph_info.id);
476 }
477
478 if row_width(row) <= job.effective_wrap_width() || row.glyphs.len() == 1 {
480 return; }
482
483 row.glyphs.pop();
485 } else {
486 last_glyph.chr = overflow_character;
488 let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
489 last_glyph.advance_width = glyph_info.advance_width;
490 last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent());
491 last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height());
492 last_glyph.uv_rect = glyph_info.uv_rect;
493 return;
494 }
495 }
496}
497
498fn halign_and_justify_row(
502 point_scale: PointScale,
503 row: &mut Row,
504 halign: Align,
505 wrap_width: f32,
506 justify: bool,
507) {
508 if row.glyphs.is_empty() {
509 return;
510 }
511
512 let num_leading_spaces = row
513 .glyphs
514 .iter()
515 .take_while(|glyph| glyph.chr.is_whitespace())
516 .count();
517
518 let glyph_range = if num_leading_spaces == row.glyphs.len() {
519 (0, row.glyphs.len())
521 } else {
522 let num_trailing_spaces = row
523 .glyphs
524 .iter()
525 .rev()
526 .take_while(|glyph| glyph.chr.is_whitespace())
527 .count();
528
529 (num_leading_spaces, row.glyphs.len() - num_trailing_spaces)
530 };
531 let num_glyphs_in_range = glyph_range.1 - glyph_range.0;
532 assert!(num_glyphs_in_range > 0);
533
534 let original_min_x = row.glyphs[glyph_range.0].logical_rect().min.x;
535 let original_max_x = row.glyphs[glyph_range.1 - 1].logical_rect().max.x;
536 let original_width = original_max_x - original_min_x;
537
538 let target_width = if justify && num_glyphs_in_range > 1 {
539 wrap_width
540 } else {
541 original_width
542 };
543
544 let (target_min_x, target_max_x) = match halign {
545 Align::LEFT => (0.0, target_width),
546 Align::Center => (-target_width / 2.0, target_width / 2.0),
547 Align::RIGHT => (-target_width, 0.0),
548 };
549
550 let num_spaces_in_range = row.glyphs[glyph_range.0..glyph_range.1]
551 .iter()
552 .filter(|glyph| glyph.chr.is_whitespace())
553 .count();
554
555 let mut extra_x_per_glyph = if num_glyphs_in_range == 1 {
556 0.0
557 } else {
558 (target_width - original_width) / (num_glyphs_in_range as f32 - 1.0)
559 };
560 extra_x_per_glyph = extra_x_per_glyph.at_least(0.0); let mut extra_x_per_space = 0.0;
563 if 0 < num_spaces_in_range && num_spaces_in_range < num_glyphs_in_range {
564 extra_x_per_glyph = point_scale.floor_to_pixel(extra_x_per_glyph);
568
569 extra_x_per_space = (target_width
570 - original_width
571 - extra_x_per_glyph * (num_glyphs_in_range as f32 - 1.0))
572 / (num_spaces_in_range as f32);
573 }
574
575 let mut translate_x = target_min_x - original_min_x - extra_x_per_glyph * glyph_range.0 as f32;
576
577 for glyph in &mut row.glyphs {
578 glyph.pos.x += translate_x;
579 glyph.pos.x = point_scale.round_to_pixel(glyph.pos.x);
580 translate_x += extra_x_per_glyph;
581 if glyph.chr.is_whitespace() {
582 translate_x += extra_x_per_space;
583 }
584 }
585
586 row.rect.min.x = target_min_x;
588 row.rect.max.x = target_max_x;
589}
590
591fn galley_from_rows(
593 point_scale: PointScale,
594 job: Arc<LayoutJob>,
595 mut rows: Vec<Row>,
596 elided: bool,
597) -> Galley {
598 let mut first_row_min_height = job.first_row_min_height;
599 let mut cursor_y = 0.0;
600 let mut min_x: f32 = 0.0;
601 let mut max_x: f32 = 0.0;
602 for row in &mut rows {
603 let mut max_row_height = first_row_min_height.max(row.rect.height());
604 first_row_min_height = 0.0;
605 for glyph in &row.glyphs {
606 max_row_height = max_row_height.max(glyph.line_height);
607 }
608 max_row_height = point_scale.round_to_pixel(max_row_height);
609
610 for glyph in &mut row.glyphs {
612 let format = &job.sections[glyph.section_index as usize].format;
613
614 glyph.pos.y = cursor_y
615 + glyph.font_impl_ascent
616
617 + format.valign.to_factor() * (max_row_height - glyph.line_height)
619
620 + 0.5 * (glyph.font_height - glyph.font_impl_height);
623
624 glyph.pos.y = point_scale.round_to_pixel(glyph.pos.y);
625 }
626
627 row.rect.min.y = cursor_y;
628 row.rect.max.y = cursor_y + max_row_height;
629
630 min_x = min_x.min(row.rect.min.x);
631 max_x = max_x.max(row.rect.max.x);
632 cursor_y += max_row_height;
633 cursor_y = point_scale.round_to_pixel(cursor_y); }
635
636 let format_summary = format_summary(&job);
637
638 let mut mesh_bounds = Rect::NOTHING;
639 let mut num_vertices = 0;
640 let mut num_indices = 0;
641
642 for row in &mut rows {
643 row.visuals = tessellate_row(point_scale, &job, &format_summary, row);
644 mesh_bounds = mesh_bounds.union(row.visuals.mesh_bounds);
645 num_vertices += row.visuals.mesh.vertices.len();
646 num_indices += row.visuals.mesh.indices.len();
647 }
648
649 let mut rect = Rect::from_min_max(pos2(min_x, 0.0), pos2(max_x, cursor_y));
650
651 if job.round_output_to_gui {
652 for row in &mut rows {
653 row.rect = row.rect.round_ui();
654 }
655
656 let did_exceed_wrap_width_by_a_lot = rect.width() > job.wrap.max_width + 1.0;
657
658 rect = rect.round_ui();
659
660 if did_exceed_wrap_width_by_a_lot {
661 } else {
664 rect.max.x = rect
666 .max
667 .x
668 .at_most(rect.min.x + job.wrap.max_width)
669 .floor_ui();
670 }
671 }
672
673 Galley {
674 job,
675 rows,
676 elided,
677 rect,
678 mesh_bounds,
679 num_vertices,
680 num_indices,
681 pixels_per_point: point_scale.pixels_per_point,
682 }
683}
684
685#[derive(Default)]
686struct FormatSummary {
687 any_background: bool,
688 any_underline: bool,
689 any_strikethrough: bool,
690}
691
692fn format_summary(job: &LayoutJob) -> FormatSummary {
693 let mut format_summary = FormatSummary::default();
694 for section in &job.sections {
695 format_summary.any_background |= section.format.background != Color32::TRANSPARENT;
696 format_summary.any_underline |= section.format.underline != Stroke::NONE;
697 format_summary.any_strikethrough |= section.format.strikethrough != Stroke::NONE;
698 }
699 format_summary
700}
701
702fn tessellate_row(
703 point_scale: PointScale,
704 job: &LayoutJob,
705 format_summary: &FormatSummary,
706 row: &Row,
707) -> RowVisuals {
708 if row.glyphs.is_empty() {
709 return Default::default();
710 }
711
712 let mut mesh = Mesh::default();
713
714 mesh.reserve_triangles(row.glyphs.len() * 2);
715 mesh.reserve_vertices(row.glyphs.len() * 4);
716
717 if format_summary.any_background {
718 add_row_backgrounds(job, row, &mut mesh);
719 }
720
721 let glyph_index_start = mesh.indices.len();
722 let glyph_vertex_start = mesh.vertices.len();
723 tessellate_glyphs(point_scale, job, row, &mut mesh);
724 let glyph_vertex_end = mesh.vertices.len();
725
726 if format_summary.any_underline {
727 add_row_hline(point_scale, row, &mut mesh, |glyph| {
728 let format = &job.sections[glyph.section_index as usize].format;
729 let stroke = format.underline;
730 let y = glyph.logical_rect().bottom();
731 (stroke, y)
732 });
733 }
734
735 if format_summary.any_strikethrough {
736 add_row_hline(point_scale, row, &mut mesh, |glyph| {
737 let format = &job.sections[glyph.section_index as usize].format;
738 let stroke = format.strikethrough;
739 let y = glyph.logical_rect().center().y;
740 (stroke, y)
741 });
742 }
743
744 let mesh_bounds = mesh.calc_bounds();
745
746 RowVisuals {
747 mesh,
748 mesh_bounds,
749 glyph_index_start,
750 glyph_vertex_range: glyph_vertex_start..glyph_vertex_end,
751 }
752}
753
754fn add_row_backgrounds(job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
757 if row.glyphs.is_empty() {
758 return;
759 }
760
761 let mut end_run = |start: Option<(Color32, Rect)>, stop_x: f32| {
762 if let Some((color, start_rect)) = start {
763 let rect = Rect::from_min_max(start_rect.left_top(), pos2(stop_x, start_rect.bottom()));
764 let rect = rect.expand(1.0); mesh.add_colored_rect(rect, color);
766 }
767 };
768
769 let mut run_start = None;
770 let mut last_rect = Rect::NAN;
771
772 for glyph in &row.glyphs {
773 let format = &job.sections[glyph.section_index as usize].format;
774 let color = format.background;
775 let rect = glyph.logical_rect();
776
777 if color == Color32::TRANSPARENT {
778 end_run(run_start.take(), last_rect.right());
779 } else if let Some((existing_color, start)) = run_start {
780 if existing_color == color
781 && start.top() == rect.top()
782 && start.bottom() == rect.bottom()
783 {
784 } else {
786 end_run(run_start.take(), last_rect.right());
787 run_start = Some((color, rect));
788 }
789 } else {
790 run_start = Some((color, rect));
791 }
792
793 last_rect = rect;
794 }
795
796 end_run(run_start.take(), last_rect.right());
797}
798
799fn tessellate_glyphs(point_scale: PointScale, job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
800 for glyph in &row.glyphs {
801 let uv_rect = glyph.uv_rect;
802 if !uv_rect.is_nothing() {
803 let mut left_top = glyph.pos + uv_rect.offset;
804 left_top.x = point_scale.round_to_pixel(left_top.x);
805 left_top.y = point_scale.round_to_pixel(left_top.y);
806
807 let rect = Rect::from_min_max(left_top, left_top + uv_rect.size);
808 let uv = Rect::from_min_max(
809 pos2(uv_rect.min[0] as f32, uv_rect.min[1] as f32),
810 pos2(uv_rect.max[0] as f32, uv_rect.max[1] as f32),
811 );
812
813 let format = &job.sections[glyph.section_index as usize].format;
814
815 let color = format.color;
816
817 if format.italics {
818 let idx = mesh.vertices.len() as u32;
819 mesh.add_triangle(idx, idx + 1, idx + 2);
820 mesh.add_triangle(idx + 2, idx + 1, idx + 3);
821
822 let top_offset = rect.height() * 0.25 * Vec2::X;
823
824 mesh.vertices.push(Vertex {
825 pos: rect.left_top() + top_offset,
826 uv: uv.left_top(),
827 color,
828 });
829 mesh.vertices.push(Vertex {
830 pos: rect.right_top() + top_offset,
831 uv: uv.right_top(),
832 color,
833 });
834 mesh.vertices.push(Vertex {
835 pos: rect.left_bottom(),
836 uv: uv.left_bottom(),
837 color,
838 });
839 mesh.vertices.push(Vertex {
840 pos: rect.right_bottom(),
841 uv: uv.right_bottom(),
842 color,
843 });
844 } else {
845 mesh.add_rect_with_uv(rect, uv, color);
846 }
847 }
848 }
849}
850
851fn add_row_hline(
853 point_scale: PointScale,
854 row: &Row,
855 mesh: &mut Mesh,
856 stroke_and_y: impl Fn(&Glyph) -> (Stroke, f32),
857) {
858 let mut end_line = |start: Option<(Stroke, Pos2)>, stop_x: f32| {
859 if let Some((stroke, start)) = start {
860 add_hline(point_scale, [start, pos2(stop_x, start.y)], stroke, mesh);
861 }
862 };
863
864 let mut line_start = None;
865 let mut last_right_x = f32::NAN;
866
867 for glyph in &row.glyphs {
868 let (stroke, y) = stroke_and_y(glyph);
869
870 if stroke == Stroke::NONE {
871 end_line(line_start.take(), last_right_x);
872 } else if let Some((existing_stroke, start)) = line_start {
873 if existing_stroke == stroke && start.y == y {
874 } else {
876 end_line(line_start.take(), last_right_x);
877 line_start = Some((stroke, pos2(glyph.pos.x, y)));
878 }
879 } else {
880 line_start = Some((stroke, pos2(glyph.pos.x, y)));
881 }
882
883 last_right_x = glyph.max_x();
884 }
885
886 end_line(line_start.take(), last_right_x);
887}
888
889fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke, mesh: &mut Mesh) {
890 let antialiased = true;
891
892 if antialiased {
893 let mut path = crate::tessellator::Path::default(); path.add_line_segment([start, stop]);
895 let feathering = 1.0 / point_scale.pixels_per_point();
896 path.stroke_open(feathering, &PathStroke::from(stroke), mesh);
897 } else {
898 assert_eq!(start.y, stop.y);
901
902 let min_y = point_scale.round_to_pixel(start.y - 0.5 * stroke.width);
903 let max_y = point_scale.round_to_pixel(min_y + stroke.width);
904
905 let rect = Rect::from_min_max(
906 pos2(point_scale.round_to_pixel(start.x), min_y),
907 pos2(point_scale.round_to_pixel(stop.x), max_y),
908 );
909
910 mesh.add_colored_rect(rect, stroke.color);
911 }
912}
913
914#[derive(Clone, Copy, Default)]
919struct RowBreakCandidates {
920 space: Option<usize>,
923
924 cjk: Option<usize>,
926
927 pre_cjk: Option<usize>,
929
930 dash: Option<usize>,
933
934 punctuation: Option<usize>,
937
938 any: Option<usize>,
941}
942
943impl RowBreakCandidates {
944 fn add(&mut self, index: usize, glyphs: &[Glyph]) {
945 let chr = glyphs[0].chr;
946 const NON_BREAKING_SPACE: char = '\u{A0}';
947 if chr.is_whitespace() && chr != NON_BREAKING_SPACE {
948 self.space = Some(index);
949 } else if is_cjk(chr) && (glyphs.len() == 1 || is_cjk_break_allowed(glyphs[1].chr)) {
950 self.cjk = Some(index);
951 } else if chr == '-' {
952 self.dash = Some(index);
953 } else if chr.is_ascii_punctuation() {
954 self.punctuation = Some(index);
955 } else if glyphs.len() > 1 && is_cjk(glyphs[1].chr) {
956 self.pre_cjk = Some(index);
957 }
958 self.any = Some(index);
959 }
960
961 fn word_boundary(&self) -> Option<usize> {
962 [self.space, self.cjk, self.pre_cjk]
963 .into_iter()
964 .max()
965 .flatten()
966 }
967
968 fn has_good_candidate(&self, break_anywhere: bool) -> bool {
969 if break_anywhere {
970 self.any.is_some()
971 } else {
972 self.word_boundary().is_some()
973 }
974 }
975
976 fn get(&self, break_anywhere: bool) -> Option<usize> {
977 if break_anywhere {
978 self.any
979 } else {
980 self.word_boundary()
981 .or(self.dash)
982 .or(self.punctuation)
983 .or(self.any)
984 }
985 }
986
987 fn forget_before_idx(&mut self, index: usize) {
988 let Self {
989 space,
990 cjk,
991 pre_cjk,
992 dash,
993 punctuation,
994 any,
995 } = self;
996 if space.is_some_and(|s| s < index) {
997 *space = None;
998 }
999 if cjk.is_some_and(|s| s < index) {
1000 *cjk = None;
1001 }
1002 if pre_cjk.is_some_and(|s| s < index) {
1003 *pre_cjk = None;
1004 }
1005 if dash.is_some_and(|s| s < index) {
1006 *dash = None;
1007 }
1008 if punctuation.is_some_and(|s| s < index) {
1009 *punctuation = None;
1010 }
1011 if any.is_some_and(|s| s < index) {
1012 *any = None;
1013 }
1014 }
1015}
1016
1017#[inline]
1018fn is_cjk_ideograph(c: char) -> bool {
1019 ('\u{4E00}' <= c && c <= '\u{9FFF}')
1020 || ('\u{3400}' <= c && c <= '\u{4DBF}')
1021 || ('\u{2B740}' <= c && c <= '\u{2B81F}')
1022}
1023
1024#[inline]
1025fn is_kana(c: char) -> bool {
1026 ('\u{3040}' <= c && c <= '\u{309F}') || ('\u{30A0}' <= c && c <= '\u{30FF}') }
1029
1030#[inline]
1031fn is_cjk(c: char) -> bool {
1032 is_cjk_ideograph(c) || is_kana(c)
1034}
1035
1036#[inline]
1037fn is_cjk_break_allowed(c: char) -> bool {
1038 !")]}〕〉》」』】〙〗〟'\"⦆»ヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻‐゠–〜?!‼⁇⁈⁉・、:;,。.".contains(c)
1040}
1041
1042#[cfg(test)]
1045mod tests {
1046 use super::{super::*, *};
1047
1048 #[test]
1049 fn test_zero_max_width() {
1050 let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
1051 let mut layout_job = LayoutJob::single_section("W".into(), TextFormat::default());
1052 layout_job.wrap.max_width = 0.0;
1053 let galley = layout(&mut fonts, layout_job.into());
1054 assert_eq!(galley.rows.len(), 1);
1055 }
1056
1057 #[test]
1058 fn test_truncate_with_newline() {
1059 let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
1062 let text_format = TextFormat {
1063 font_id: FontId::monospace(12.0),
1064 ..Default::default()
1065 };
1066
1067 for text in ["Hello\nworld", "\nfoo"] {
1068 for break_anywhere in [false, true] {
1069 for max_width in [0.0, 5.0, 10.0, 20.0, f32::INFINITY] {
1070 let mut layout_job =
1071 LayoutJob::single_section(text.into(), text_format.clone());
1072 layout_job.wrap.max_width = max_width;
1073 layout_job.wrap.max_rows = 1;
1074 layout_job.wrap.break_anywhere = break_anywhere;
1075
1076 let galley = layout(&mut fonts, layout_job.into());
1077
1078 assert!(galley.elided);
1079 assert_eq!(galley.rows.len(), 1);
1080 let row_text = galley.rows[0].text();
1081 assert!(
1082 row_text.ends_with('…'),
1083 "Expected row to end with `…`, got {row_text:?} when line-breaking the text {text:?} with max_width {max_width} and break_anywhere {break_anywhere}.",
1084 );
1085 }
1086 }
1087 }
1088
1089 {
1090 let mut layout_job = LayoutJob::single_section("Hello\nworld".into(), text_format);
1091 layout_job.wrap.max_width = 50.0;
1092 layout_job.wrap.max_rows = 1;
1093 layout_job.wrap.break_anywhere = false;
1094
1095 let galley = layout(&mut fonts, layout_job.into());
1096
1097 assert!(galley.elided);
1098 assert_eq!(galley.rows.len(), 1);
1099 let row_text = galley.rows[0].text();
1100 assert_eq!(row_text, "Hello…");
1101 }
1102 }
1103
1104 #[test]
1105 fn test_cjk() {
1106 let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
1107 let mut layout_job = LayoutJob::single_section(
1108 "日本語とEnglishの混在した文章".into(),
1109 TextFormat::default(),
1110 );
1111 layout_job.wrap.max_width = 90.0;
1112 let galley = layout(&mut fonts, layout_job.into());
1113 assert_eq!(
1114 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1115 vec!["日本語と", "Englishの混在", "した文章"]
1116 );
1117 }
1118
1119 #[test]
1120 fn test_pre_cjk() {
1121 let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
1122 let mut layout_job = LayoutJob::single_section(
1123 "日本語とEnglishの混在した文章".into(),
1124 TextFormat::default(),
1125 );
1126 layout_job.wrap.max_width = 110.0;
1127 let galley = layout(&mut fonts, layout_job.into());
1128 assert_eq!(
1129 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1130 vec!["日本語とEnglish", "の混在した文章"]
1131 );
1132 }
1133
1134 #[test]
1135 fn test_truncate_width() {
1136 let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
1137 let mut layout_job =
1138 LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default());
1139 layout_job.wrap.max_width = f32::INFINITY;
1140 layout_job.wrap.max_rows = 1;
1141 layout_job.round_output_to_gui = false;
1142 let galley = layout(&mut fonts, layout_job.into());
1143 assert!(galley.elided);
1144 assert_eq!(
1145 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1146 vec!["# DNA…"]
1147 );
1148 let row = &galley.rows[0];
1149 assert_eq!(row.rect.max.x, row.glyphs.last().unwrap().max_x());
1150 }
1151}