1#![expect(clippy::unwrap_used)] use std::sync::Arc;
4
5use emath::{Align, GuiRounding as _, NumExt as _, Pos2, Rect, Vec2, pos2, vec2};
6
7use crate::{
8 Color32, Mesh, Stroke, Vertex,
9 stroke::PathStroke,
10 text::{
11 font::{StyledMetrics, is_cjk, is_cjk_break_allowed},
12 fonts::FontFaceKey,
13 },
14};
15
16use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, PlacedRow, Row, RowVisuals};
17
18#[derive(Clone, Copy)]
22struct PointScale {
23 pub pixels_per_point: f32,
24}
25
26impl PointScale {
27 #[inline(always)]
28 pub fn new(pixels_per_point: f32) -> Self {
29 Self { pixels_per_point }
30 }
31
32 #[inline(always)]
33 pub fn pixels_per_point(&self) -> f32 {
34 self.pixels_per_point
35 }
36
37 #[inline(always)]
38 pub fn round_to_pixel(&self, point: f32) -> f32 {
39 (point * self.pixels_per_point).round() / self.pixels_per_point
40 }
41
42 #[inline(always)]
43 pub fn floor_to_pixel(&self, point: f32) -> f32 {
44 (point * self.pixels_per_point).floor() / self.pixels_per_point
45 }
46}
47
48#[derive(Clone)]
52struct Paragraph {
53 pub cursor_x_px: f32,
55
56 pub section_index_at_start: u32,
58
59 pub glyphs: Vec<Glyph>,
60
61 pub empty_paragraph_height: f32,
63}
64
65impl Paragraph {
66 pub fn from_section_index(section_index_at_start: u32) -> Self {
67 Self {
68 cursor_x_px: 0.0,
69 section_index_at_start,
70 glyphs: vec![],
71 empty_paragraph_height: 0.0,
72 }
73 }
74}
75
76pub fn layout(fonts: &mut FontsImpl, pixels_per_point: f32, job: Arc<LayoutJob>) -> Galley {
81 profiling::function_scope!();
82
83 if job.wrap.max_rows == 0 {
84 return Galley {
86 job,
87 rows: Default::default(),
88 rect: Rect::ZERO,
89 mesh_bounds: Rect::NOTHING,
90 num_vertices: 0,
91 num_indices: 0,
92 pixels_per_point,
93 elided: true,
94 intrinsic_size: Vec2::ZERO,
95 };
96 }
97
98 let mut paragraphs = vec![Paragraph::from_section_index(0)];
101 for (section_index, section) in job.sections.iter().enumerate() {
102 layout_section(
103 fonts,
104 pixels_per_point,
105 &job,
106 section_index as u32,
107 section,
108 &mut paragraphs,
109 );
110 }
111
112 let point_scale = PointScale::new(pixels_per_point);
113
114 let intrinsic_size = calculate_intrinsic_size(point_scale, &job, ¶graphs);
115
116 let mut elided = false;
117 let mut rows = rows_from_paragraphs(paragraphs, &job, pixels_per_point, &mut elided);
118 if elided && let Some(last_placed) = rows.last_mut() {
119 let last_row = Arc::make_mut(&mut last_placed.row);
120 replace_last_glyph_with_overflow_character(fonts, pixels_per_point, &job, last_row);
121 if let Some(last) = last_row.glyphs.last() {
122 last_row.size.x = last.max_x();
123 }
124 }
125
126 let justify = job.justify && job.wrap.max_width.is_finite();
127
128 if justify || job.halign != Align::LEFT {
129 let num_rows = rows.len();
130 for (i, placed_row) in rows.iter_mut().enumerate() {
131 let is_last_row = i + 1 == num_rows;
132 let justify_row = justify && !placed_row.ends_with_newline && !is_last_row;
133 halign_and_justify_row(
134 point_scale,
135 placed_row,
136 job.halign,
137 job.wrap.max_width,
138 justify_row,
139 );
140 }
141 }
142
143 galley_from_rows(point_scale, job, rows, elided, intrinsic_size)
145}
146
147fn layout_section(
149 fonts: &mut FontsImpl,
150 pixels_per_point: f32,
151 job: &LayoutJob,
152 section_index: u32,
153 section: &LayoutSection,
154 out_paragraphs: &mut Vec<Paragraph>,
155) {
156 let LayoutSection {
157 leading_space,
158 byte_range,
159 format,
160 } = section;
161 let mut font = fonts.font(&format.font_id.family);
162 let font_size = format.font_id.size;
163 let font_metrics = font.styled_metrics(pixels_per_point, font_size, &format.coords);
164 let line_height = section
165 .format
166 .line_height
167 .unwrap_or(font_metrics.row_height);
168 let extra_letter_spacing = section.format.extra_letter_spacing;
169
170 let mut paragraph = out_paragraphs.last_mut().unwrap();
171 if paragraph.glyphs.is_empty() {
172 paragraph.empty_paragraph_height = line_height; }
174
175 paragraph.cursor_x_px += leading_space * pixels_per_point;
176
177 let mut last_glyph_id = None;
178
179 let mut current_font = FontFaceKey::INVALID;
181 let mut current_font_face_metrics = StyledMetrics::default();
182
183 for chr in job.text[byte_range.clone()].chars() {
184 if job.break_on_newline && chr == '\n' {
185 out_paragraphs.push(Paragraph::from_section_index(section_index));
186 paragraph = out_paragraphs.last_mut().unwrap();
187 paragraph.empty_paragraph_height = line_height; } else {
189 let (font_id, glyph_info) = font.glyph_info(chr);
190 let mut font_face = font.fonts_by_id.get_mut(&font_id);
191 if current_font != font_id {
192 current_font = font_id;
193 current_font_face_metrics = font_face
194 .as_ref()
195 .map(|font_face| {
196 font_face.styled_metrics(pixels_per_point, font_size, &format.coords)
197 })
198 .unwrap_or_default();
199 }
200
201 if let (Some(font_face), Some(last_glyph_id), Some(glyph_id)) =
202 (&font_face, last_glyph_id, glyph_info.id)
203 {
204 paragraph.cursor_x_px += font_face.pair_kerning_pixels(
205 ¤t_font_face_metrics,
206 last_glyph_id,
207 glyph_id,
208 );
209
210 paragraph.cursor_x_px += extra_letter_spacing * pixels_per_point;
212 }
213
214 let (glyph_alloc, physical_x) = if let Some(font_face) = font_face.as_mut() {
215 font_face.allocate_glyph(
216 font.atlas,
217 ¤t_font_face_metrics,
218 glyph_info,
219 chr,
220 paragraph.cursor_x_px,
221 )
222 } else {
223 Default::default()
224 };
225
226 paragraph.glyphs.push(Glyph {
227 chr,
228 pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN),
229 advance_width: glyph_alloc.advance_width_px / pixels_per_point,
230 line_height,
231 font_face_height: current_font_face_metrics.row_height,
232 font_face_ascent: current_font_face_metrics.ascent,
233 font_height: font_metrics.row_height,
234 font_ascent: font_metrics.ascent,
235 uv_rect: glyph_alloc.uv_rect,
236 section_index,
237 first_vertex: 0, });
239
240 paragraph.cursor_x_px += glyph_alloc.advance_width_px;
241 last_glyph_id = Some(glyph_alloc.id);
242 }
243 }
244}
245
246fn calculate_intrinsic_size(
251 point_scale: PointScale,
252 job: &LayoutJob,
253 paragraphs: &[Paragraph],
254) -> Vec2 {
255 let mut intrinsic_size = Vec2::ZERO;
256 for (idx, paragraph) in paragraphs.iter().enumerate() {
257 let width = paragraph.cursor_x_px / point_scale.pixels_per_point;
263 intrinsic_size.x = f32::max(intrinsic_size.x, width);
264
265 let mut height = paragraph
266 .glyphs
267 .iter()
268 .map(|g| g.line_height)
269 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
270 .unwrap_or(paragraph.empty_paragraph_height);
271 if idx == 0 {
272 height = f32::max(height, job.first_row_min_height);
273 }
274 intrinsic_size.y += point_scale.round_to_pixel(height);
275 }
276 intrinsic_size
277}
278
279fn rows_from_paragraphs(
281 paragraphs: Vec<Paragraph>,
282 job: &LayoutJob,
283 pixels_per_point: f32,
284 elided: &mut bool,
285) -> Vec<PlacedRow> {
286 let num_paragraphs = paragraphs.len();
287
288 let mut rows = vec![];
289
290 for (i, paragraph) in paragraphs.into_iter().enumerate() {
291 if job.wrap.max_rows <= rows.len() {
292 *elided = true;
293 break;
294 }
295
296 let is_last_paragraph = (i + 1) == num_paragraphs;
297
298 if paragraph.glyphs.is_empty() {
299 rows.push(PlacedRow {
300 pos: pos2(0.0, f32::NAN),
301 row: Arc::new(Row {
302 section_index_at_start: paragraph.section_index_at_start,
303 glyphs: vec![],
304 visuals: Default::default(),
305 size: vec2(0.0, paragraph.empty_paragraph_height),
306 }),
307 ends_with_newline: !is_last_paragraph,
308 });
309 } else {
310 let paragraph_width = paragraph.cursor_x_px / pixels_per_point;
314 if paragraph_width <= job.effective_wrap_width() {
315 rows.push(PlacedRow {
317 pos: pos2(0.0, f32::NAN),
318 row: Arc::new(Row {
319 section_index_at_start: paragraph.section_index_at_start,
320 glyphs: paragraph.glyphs,
321 visuals: Default::default(),
322 size: vec2(paragraph_width, 0.0),
323 }),
324 ends_with_newline: !is_last_paragraph,
325 });
326 } else {
327 line_break(¶graph, job, &mut rows, elided);
328 let placed_row = rows.last_mut().unwrap();
329 placed_row.ends_with_newline = !is_last_paragraph;
330 }
331 }
332 }
333
334 rows
335}
336
337fn line_break(
338 paragraph: &Paragraph,
339 job: &LayoutJob,
340 out_rows: &mut Vec<PlacedRow>,
341 elided: &mut bool,
342) {
343 let wrap_width = job.effective_wrap_width();
344
345 let mut row_break_candidates = RowBreakCandidates::default();
347
348 let mut first_row_indentation = paragraph.glyphs[0].pos.x;
349 let mut row_start_x = 0.0;
350 let mut row_start_idx = 0;
351
352 for i in 0..paragraph.glyphs.len() {
353 if job.wrap.max_rows <= out_rows.len() {
354 *elided = true;
355 break;
356 }
357
358 let potential_row_width = paragraph.glyphs[i].max_x() - row_start_x;
359
360 if wrap_width < potential_row_width {
361 if first_row_indentation > 0.0
364 && !row_break_candidates.has_good_candidate(job.wrap.break_anywhere)
365 {
366 out_rows.push(PlacedRow {
369 pos: pos2(0.0, f32::NAN),
370 row: Arc::new(Row {
371 section_index_at_start: paragraph.section_index_at_start,
372 glyphs: vec![],
373 visuals: Default::default(),
374 size: Vec2::ZERO,
375 }),
376 ends_with_newline: false,
377 });
378 row_start_x += first_row_indentation;
379 first_row_indentation = 0.0;
380 } else if let Some(last_kept_index) = row_break_candidates.get(job.wrap.break_anywhere)
381 {
382 let glyphs: Vec<Glyph> = paragraph.glyphs[row_start_idx..=last_kept_index]
383 .iter()
384 .copied()
385 .map(|mut glyph| {
386 glyph.pos.x -= row_start_x;
387 glyph
388 })
389 .collect();
390
391 let section_index_at_start = glyphs[0].section_index;
392 let paragraph_max_x = glyphs.last().unwrap().max_x();
393
394 out_rows.push(PlacedRow {
395 pos: pos2(0.0, f32::NAN),
396 row: Arc::new(Row {
397 section_index_at_start,
398 glyphs,
399 visuals: Default::default(),
400 size: vec2(paragraph_max_x, 0.0),
401 }),
402 ends_with_newline: false,
403 });
404
405 row_start_idx = last_kept_index + 1;
407 row_start_x = paragraph.glyphs[row_start_idx].pos.x;
408 row_break_candidates.forget_before_idx(row_start_idx);
409 } else {
410 }
412 }
413
414 row_break_candidates.add(i, ¶graph.glyphs[i..]);
415 }
416
417 if row_start_idx < paragraph.glyphs.len() {
418 if job.wrap.max_rows <= out_rows.len() {
421 *elided = true; } else {
423 let paragraph_min_x = paragraph.glyphs[row_start_idx].pos.x - row_start_x;
424 let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x() - row_start_x;
425
426 let glyphs: Vec<Glyph> = paragraph.glyphs[row_start_idx..]
427 .iter()
428 .copied()
429 .map(|mut glyph| {
430 glyph.pos.x -= row_start_x + paragraph_min_x;
431 glyph
432 })
433 .collect();
434
435 let section_index_at_start = glyphs[0].section_index;
436
437 out_rows.push(PlacedRow {
438 pos: pos2(paragraph_min_x, 0.0),
439 row: Arc::new(Row {
440 section_index_at_start,
441 glyphs,
442 visuals: Default::default(),
443 size: vec2(paragraph_max_x - paragraph_min_x, 0.0),
444 }),
445 ends_with_newline: false,
446 });
447 }
448 }
449}
450
451fn replace_last_glyph_with_overflow_character(
455 fonts: &mut FontsImpl,
456 pixels_per_point: f32,
457 job: &LayoutJob,
458 row: &mut Row,
459) {
460 let Some(overflow_character) = job.wrap.overflow_character else {
461 return;
462 };
463
464 let mut section_index = row
465 .glyphs
466 .last()
467 .map(|g| g.section_index)
468 .unwrap_or(row.section_index_at_start);
469 loop {
470 let section = &job.sections[section_index as usize];
471 let extra_letter_spacing = section.format.extra_letter_spacing;
472 let mut font = fonts.font(§ion.format.font_id.family);
473 let font_size = section.format.font_id.size;
474
475 let (font_id, glyph_info) = font.glyph_info(overflow_character);
476 let mut font_face = font.fonts_by_id.get_mut(&font_id);
477 let font_face_metrics = font_face
478 .as_mut()
479 .map(|f| f.styled_metrics(pixels_per_point, font_size, §ion.format.coords))
480 .unwrap_or_default();
481
482 let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() {
483 let pair_kerning = font_face
485 .as_mut()
486 .map(|font_face| {
487 if let (Some(prev_glyph_id), Some(overflow_glyph_id)) = (
488 font_face.glyph_info(prev_glyph.chr).and_then(|g| g.id),
489 font_face.glyph_info(overflow_character).and_then(|g| g.id),
490 ) {
491 font_face.pair_kerning(&font_face_metrics, prev_glyph_id, overflow_glyph_id)
492 } else {
493 0.0
494 }
495 })
496 .unwrap_or_default();
497
498 prev_glyph.max_x() + extra_letter_spacing + pair_kerning
499 } else {
500 0.0 };
502
503 let replacement_glyph_width = font_face
504 .as_mut()
505 .and_then(|f| f.glyph_info(overflow_character))
506 .map(|i| {
507 i.advance_width_unscaled.0 * font_face_metrics.px_scale_factor / pixels_per_point
508 })
509 .unwrap_or_default();
510
511 if overflow_glyph_x + replacement_glyph_width <= job.effective_wrap_width()
513 || row.glyphs.is_empty()
514 {
515 let (replacement_glyph_alloc, physical_x) = font_face
518 .as_mut()
519 .map(|f| {
520 f.allocate_glyph(
521 font.atlas,
522 &font_face_metrics,
523 glyph_info,
524 overflow_character,
525 overflow_glyph_x * pixels_per_point,
526 )
527 })
528 .unwrap_or_default();
529
530 let font_metrics =
531 font.styled_metrics(pixels_per_point, font_size, §ion.format.coords);
532 let line_height = section
533 .format
534 .line_height
535 .unwrap_or(font_metrics.row_height);
536
537 row.glyphs.push(Glyph {
538 chr: overflow_character,
539 pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN),
540 advance_width: replacement_glyph_alloc.advance_width_px / pixels_per_point,
541 line_height,
542 font_face_height: font_face_metrics.row_height,
543 font_face_ascent: font_face_metrics.ascent,
544 font_height: font_metrics.row_height,
545 font_ascent: font_metrics.ascent,
546 uv_rect: replacement_glyph_alloc.uv_rect,
547 section_index,
548 first_vertex: 0, });
550 return;
551 }
552
553 if let Some(last_glyph) = row.glyphs.pop() {
555 section_index = last_glyph.section_index;
556 } else {
557 section_index = row.section_index_at_start;
558 }
559 }
560}
561
562fn halign_and_justify_row(
566 point_scale: PointScale,
567 placed_row: &mut PlacedRow,
568 halign: Align,
569 wrap_width: f32,
570 justify: bool,
571) {
572 #![expect(clippy::useless_let_if_seq)] let row = Arc::make_mut(&mut placed_row.row);
575
576 if row.glyphs.is_empty() {
577 return;
578 }
579
580 let num_leading_spaces = row
581 .glyphs
582 .iter()
583 .take_while(|glyph| glyph.chr.is_whitespace())
584 .count();
585
586 let glyph_range = if num_leading_spaces == row.glyphs.len() {
587 (0, row.glyphs.len())
589 } else {
590 let num_trailing_spaces = row
591 .glyphs
592 .iter()
593 .rev()
594 .take_while(|glyph| glyph.chr.is_whitespace())
595 .count();
596
597 (num_leading_spaces, row.glyphs.len() - num_trailing_spaces)
598 };
599 let num_glyphs_in_range = glyph_range.1 - glyph_range.0;
600 assert!(num_glyphs_in_range > 0, "Should have at least one glyph");
601
602 let original_min_x = row.glyphs[glyph_range.0].logical_rect().min.x;
603 let original_max_x = row.glyphs[glyph_range.1 - 1].logical_rect().max.x;
604 let original_width = original_max_x - original_min_x;
605
606 let target_width = if justify && num_glyphs_in_range > 1 {
607 wrap_width
608 } else {
609 original_width
610 };
611
612 let (target_min_x, target_max_x) = match halign {
613 Align::LEFT => (0.0, target_width),
614 Align::Center => (-target_width / 2.0, target_width / 2.0),
615 Align::RIGHT => (-target_width, 0.0),
616 };
617
618 let num_spaces_in_range = row.glyphs[glyph_range.0..glyph_range.1]
619 .iter()
620 .filter(|glyph| glyph.chr.is_whitespace())
621 .count();
622
623 let mut extra_x_per_glyph = if num_glyphs_in_range == 1 {
624 0.0
625 } else {
626 (target_width - original_width) / (num_glyphs_in_range as f32 - 1.0)
627 };
628 extra_x_per_glyph = extra_x_per_glyph.at_least(0.0); let mut extra_x_per_space = 0.0;
631 if 0 < num_spaces_in_range && num_spaces_in_range < num_glyphs_in_range {
632 extra_x_per_glyph = point_scale.floor_to_pixel(extra_x_per_glyph);
636
637 extra_x_per_space = (target_width
638 - original_width
639 - extra_x_per_glyph * (num_glyphs_in_range as f32 - 1.0))
640 / (num_spaces_in_range as f32);
641 }
642
643 placed_row.pos.x = point_scale.round_to_pixel(target_min_x);
644 let mut translate_x = -original_min_x - extra_x_per_glyph * glyph_range.0 as f32;
645
646 for glyph in &mut row.glyphs {
647 glyph.pos.x += translate_x;
648 glyph.pos.x = point_scale.round_to_pixel(glyph.pos.x);
649 translate_x += extra_x_per_glyph;
650 if glyph.chr.is_whitespace() {
651 translate_x += extra_x_per_space;
652 }
653 }
654
655 row.size.x = target_max_x - target_min_x;
657}
658
659fn galley_from_rows(
661 point_scale: PointScale,
662 job: Arc<LayoutJob>,
663 mut rows: Vec<PlacedRow>,
664 elided: bool,
665 intrinsic_size: Vec2,
666) -> Galley {
667 let mut first_row_min_height = job.first_row_min_height;
668 let mut cursor_y = 0.0;
669
670 for placed_row in &mut rows {
671 let mut max_row_height = first_row_min_height.at_least(placed_row.height());
672 let row = Arc::make_mut(&mut placed_row.row);
673
674 first_row_min_height = 0.0;
675 for glyph in &row.glyphs {
676 max_row_height = max_row_height.at_least(glyph.line_height);
677 }
678 max_row_height = point_scale.round_to_pixel(max_row_height);
679
680 for glyph in &mut row.glyphs {
682 let format = &job.sections[glyph.section_index as usize].format;
683
684 glyph.pos.y = glyph.font_face_ascent
685
686 + format.valign.to_factor() * (max_row_height - glyph.line_height)
688
689 + 0.5 * (glyph.font_height - glyph.font_face_height);
692
693 glyph.pos.y = point_scale.round_to_pixel(glyph.pos.y);
694 }
695
696 placed_row.pos.y = cursor_y;
697 row.size.y = max_row_height;
698
699 cursor_y += max_row_height;
700 cursor_y = point_scale.round_to_pixel(cursor_y); }
702
703 let format_summary = format_summary(&job);
704
705 let mut rect = Rect::ZERO;
706 let mut mesh_bounds = Rect::NOTHING;
707 let mut num_vertices = 0;
708 let mut num_indices = 0;
709
710 for placed_row in &mut rows {
711 rect |= placed_row.rect();
712
713 let row = Arc::make_mut(&mut placed_row.row);
714 row.visuals = tessellate_row(point_scale, &job, &format_summary, row);
715
716 mesh_bounds |= row.visuals.mesh_bounds.translate(placed_row.pos.to_vec2());
717 num_vertices += row.visuals.mesh.vertices.len();
718 num_indices += row.visuals.mesh.indices.len();
719
720 row.section_index_at_start = u32::MAX; for glyph in &mut row.glyphs {
722 glyph.section_index = u32::MAX; }
724 }
725
726 let mut galley = Galley {
727 job,
728 rows,
729 elided,
730 rect,
731 mesh_bounds,
732 num_vertices,
733 num_indices,
734 pixels_per_point: point_scale.pixels_per_point,
735 intrinsic_size,
736 };
737
738 if galley.job.round_output_to_gui {
739 galley.round_output_to_gui();
740 }
741
742 galley
743}
744
745#[derive(Default)]
746struct FormatSummary {
747 any_background: bool,
748 any_underline: bool,
749 any_strikethrough: bool,
750}
751
752fn format_summary(job: &LayoutJob) -> FormatSummary {
753 let mut format_summary = FormatSummary::default();
754 for section in &job.sections {
755 format_summary.any_background |= section.format.background != Color32::TRANSPARENT;
756 format_summary.any_underline |= section.format.underline != Stroke::NONE;
757 format_summary.any_strikethrough |= section.format.strikethrough != Stroke::NONE;
758 }
759 format_summary
760}
761
762fn tessellate_row(
763 point_scale: PointScale,
764 job: &LayoutJob,
765 format_summary: &FormatSummary,
766 row: &mut Row,
767) -> RowVisuals {
768 if row.glyphs.is_empty() {
769 return Default::default();
770 }
771
772 let mut mesh = Mesh::default();
773
774 mesh.reserve_triangles(row.glyphs.len() * 2);
775 mesh.reserve_vertices(row.glyphs.len() * 4);
776
777 if format_summary.any_background {
778 add_row_backgrounds(point_scale, job, row, &mut mesh);
779 }
780
781 let glyph_index_start = mesh.indices.len();
782 let glyph_vertex_start = mesh.vertices.len();
783 tessellate_glyphs(point_scale, job, row, &mut mesh);
784 let glyph_vertex_end = mesh.vertices.len();
785
786 if format_summary.any_underline {
787 add_row_hline(point_scale, row, &mut mesh, |glyph| {
788 let format = &job.sections[glyph.section_index as usize].format;
789 let stroke = format.underline;
790 let y = glyph.logical_rect().bottom();
791 (stroke, y)
792 });
793 }
794
795 if format_summary.any_strikethrough {
796 add_row_hline(point_scale, row, &mut mesh, |glyph| {
797 let format = &job.sections[glyph.section_index as usize].format;
798 let stroke = format.strikethrough;
799 let y = glyph.logical_rect().center().y;
800 (stroke, y)
801 });
802 }
803
804 let mesh_bounds = mesh.calc_bounds();
805
806 RowVisuals {
807 mesh,
808 mesh_bounds,
809 glyph_index_start,
810 glyph_vertex_range: glyph_vertex_start..glyph_vertex_end,
811 }
812}
813
814fn add_row_backgrounds(point_scale: PointScale, job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
817 if row.glyphs.is_empty() {
818 return;
819 }
820
821 let mut end_run = |start: Option<(Color32, Rect, f32)>, stop_x: f32| {
822 if let Some((color, start_rect, expand)) = start {
823 let rect = Rect::from_min_max(start_rect.left_top(), pos2(stop_x, start_rect.bottom()));
824 let rect = rect.expand(expand);
825 let rect = rect.round_to_pixels(point_scale.pixels_per_point());
826 mesh.add_colored_rect(rect, color);
827 }
828 };
829
830 let mut run_start = None;
831 let mut last_rect = Rect::NAN;
832
833 for glyph in &row.glyphs {
834 let format = &job.sections[glyph.section_index as usize].format;
835 let color = format.background;
836 let rect = glyph.logical_rect();
837
838 if color == Color32::TRANSPARENT {
839 end_run(run_start.take(), last_rect.right());
840 } else if let Some((existing_color, start, expand)) = run_start {
841 if existing_color == color
842 && start.top() == rect.top()
843 && start.bottom() == rect.bottom()
844 && format.expand_bg == expand
845 {
846 } else {
848 end_run(run_start.take(), last_rect.right());
849 run_start = Some((color, rect, format.expand_bg));
850 }
851 } else {
852 run_start = Some((color, rect, format.expand_bg));
853 }
854
855 last_rect = rect;
856 }
857
858 end_run(run_start.take(), last_rect.right());
859}
860
861fn tessellate_glyphs(point_scale: PointScale, job: &LayoutJob, row: &mut Row, mesh: &mut Mesh) {
862 for glyph in &mut row.glyphs {
863 glyph.first_vertex = mesh.vertices.len() as u32;
864 let uv_rect = glyph.uv_rect;
865 if !uv_rect.is_nothing() {
866 let mut left_top = glyph.pos + uv_rect.offset;
867 left_top.x = point_scale.round_to_pixel(left_top.x);
868 left_top.y = point_scale.round_to_pixel(left_top.y);
869
870 let rect = Rect::from_min_max(left_top, left_top + uv_rect.size);
871 let uv = Rect::from_min_max(
872 pos2(uv_rect.min[0] as f32, uv_rect.min[1] as f32),
873 pos2(uv_rect.max[0] as f32, uv_rect.max[1] as f32),
874 );
875
876 let format = &job.sections[glyph.section_index as usize].format;
877
878 let color = format.color;
879
880 if format.italics {
881 let idx = mesh.vertices.len() as u32;
882 mesh.add_triangle(idx, idx + 1, idx + 2);
883 mesh.add_triangle(idx + 2, idx + 1, idx + 3);
884
885 let top_offset = rect.height() * 0.25 * Vec2::X;
886
887 mesh.vertices.push(Vertex {
888 pos: rect.left_top() + top_offset,
889 uv: uv.left_top(),
890 color,
891 });
892 mesh.vertices.push(Vertex {
893 pos: rect.right_top() + top_offset,
894 uv: uv.right_top(),
895 color,
896 });
897 mesh.vertices.push(Vertex {
898 pos: rect.left_bottom(),
899 uv: uv.left_bottom(),
900 color,
901 });
902 mesh.vertices.push(Vertex {
903 pos: rect.right_bottom(),
904 uv: uv.right_bottom(),
905 color,
906 });
907 } else {
908 mesh.add_rect_with_uv(rect, uv, color);
909 }
910 }
911 }
912}
913
914fn add_row_hline(
916 point_scale: PointScale,
917 row: &Row,
918 mesh: &mut Mesh,
919 stroke_and_y: impl Fn(&Glyph) -> (Stroke, f32),
920) {
921 let mut path = crate::tessellator::Path::default(); let mut end_line = |start: Option<(Stroke, Pos2)>, stop_x: f32| {
924 if let Some((stroke, start)) = start {
925 let stop = pos2(stop_x, start.y);
926 path.clear();
927 path.add_line_segment([start, stop]);
928 let feathering = 1.0 / point_scale.pixels_per_point();
929 path.stroke_open(feathering, &PathStroke::from(stroke), mesh);
930 }
931 };
932
933 let mut line_start = None;
934 let mut last_right_x = f32::NAN;
935
936 for glyph in &row.glyphs {
937 let (stroke, mut y) = stroke_and_y(glyph);
938 stroke.round_center_to_pixel(point_scale.pixels_per_point, &mut y);
939
940 if stroke.is_empty() {
941 end_line(line_start.take(), last_right_x);
942 } else if let Some((existing_stroke, start)) = line_start {
943 if existing_stroke == stroke && start.y == y {
944 } else {
946 end_line(line_start.take(), last_right_x);
947 line_start = Some((stroke, pos2(glyph.pos.x, y)));
948 }
949 } else {
950 line_start = Some((stroke, pos2(glyph.pos.x, y)));
951 }
952
953 last_right_x = glyph.max_x();
954 }
955
956 end_line(line_start.take(), last_right_x);
957}
958
959#[derive(Clone, Copy, Default)]
964struct RowBreakCandidates {
965 space: Option<usize>,
968
969 cjk: Option<usize>,
971
972 pre_cjk: Option<usize>,
974
975 dash: Option<usize>,
978
979 punctuation: Option<usize>,
982
983 any: Option<usize>,
986}
987
988impl RowBreakCandidates {
989 fn add(&mut self, index: usize, glyphs: &[Glyph]) {
990 let chr = glyphs[0].chr;
991 const NON_BREAKING_SPACE: char = '\u{A0}';
992 if chr.is_whitespace() && chr != NON_BREAKING_SPACE {
993 self.space = Some(index);
994 } else if is_cjk(chr) && (glyphs.len() == 1 || is_cjk_break_allowed(glyphs[1].chr)) {
995 self.cjk = Some(index);
996 } else if chr == '-' {
997 self.dash = Some(index);
998 } else if chr.is_ascii_punctuation() {
999 self.punctuation = Some(index);
1000 } else if glyphs.len() > 1 && is_cjk(glyphs[1].chr) {
1001 self.pre_cjk = Some(index);
1002 }
1003 self.any = Some(index);
1004 }
1005
1006 fn word_boundary(&self) -> Option<usize> {
1007 [self.space, self.cjk, self.pre_cjk]
1008 .into_iter()
1009 .max()
1010 .flatten()
1011 }
1012
1013 fn has_good_candidate(&self, break_anywhere: bool) -> bool {
1014 if break_anywhere {
1015 self.any.is_some()
1016 } else {
1017 self.word_boundary().is_some()
1018 }
1019 }
1020
1021 fn get(&self, break_anywhere: bool) -> Option<usize> {
1022 if break_anywhere {
1023 self.any
1024 } else {
1025 self.word_boundary()
1026 .or(self.dash)
1027 .or(self.punctuation)
1028 .or(self.any)
1029 }
1030 }
1031
1032 fn forget_before_idx(&mut self, index: usize) {
1033 let Self {
1034 space,
1035 cjk,
1036 pre_cjk,
1037 dash,
1038 punctuation,
1039 any,
1040 } = self;
1041 if space.is_some_and(|s| s < index) {
1042 *space = None;
1043 }
1044 if cjk.is_some_and(|s| s < index) {
1045 *cjk = None;
1046 }
1047 if pre_cjk.is_some_and(|s| s < index) {
1048 *pre_cjk = None;
1049 }
1050 if dash.is_some_and(|s| s < index) {
1051 *dash = None;
1052 }
1053 if punctuation.is_some_and(|s| s < index) {
1054 *punctuation = None;
1055 }
1056 if any.is_some_and(|s| s < index) {
1057 *any = None;
1058 }
1059 }
1060}
1061
1062#[cfg(test)]
1065mod tests {
1066
1067 use super::{super::*, *};
1068
1069 #[test]
1070 fn test_zero_max_width() {
1071 let pixels_per_point = 1.0;
1072 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1073 let mut layout_job = LayoutJob::single_section("W".into(), TextFormat::default());
1074 layout_job.wrap.max_width = 0.0;
1075 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1076 assert_eq!(galley.rows.len(), 1);
1077 }
1078
1079 #[test]
1080 fn test_truncate_with_newline() {
1081 let pixels_per_point = 1.0;
1084
1085 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1086 let text_format = TextFormat {
1087 font_id: FontId::monospace(12.0),
1088 ..Default::default()
1089 };
1090
1091 for text in ["Hello\nworld", "\nfoo"] {
1092 for break_anywhere in [false, true] {
1093 for max_width in [0.0, 5.0, 10.0, 20.0, f32::INFINITY] {
1094 let mut layout_job =
1095 LayoutJob::single_section(text.into(), text_format.clone());
1096 layout_job.wrap.max_width = max_width;
1097 layout_job.wrap.max_rows = 1;
1098 layout_job.wrap.break_anywhere = break_anywhere;
1099
1100 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1101
1102 assert!(galley.elided);
1103 assert_eq!(galley.rows.len(), 1);
1104 let row_text = galley.rows[0].text();
1105 assert!(
1106 row_text.ends_with('…'),
1107 "Expected row to end with `…`, got {row_text:?} when line-breaking the text {text:?} with max_width {max_width} and break_anywhere {break_anywhere}.",
1108 );
1109 }
1110 }
1111 }
1112
1113 {
1114 let mut layout_job = LayoutJob::single_section("Hello\nworld".into(), text_format);
1115 layout_job.wrap.max_width = 50.0;
1116 layout_job.wrap.max_rows = 1;
1117 layout_job.wrap.break_anywhere = false;
1118
1119 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1120
1121 assert!(galley.elided);
1122 assert_eq!(galley.rows.len(), 1);
1123 let row_text = galley.rows[0].text();
1124 assert_eq!(row_text, "Hello…");
1125 }
1126 }
1127
1128 #[test]
1129 fn test_cjk() {
1130 let pixels_per_point = 1.0;
1131 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1132 let mut layout_job = LayoutJob::single_section(
1133 "日本語とEnglishの混在した文章".into(),
1134 TextFormat::default(),
1135 );
1136 layout_job.wrap.max_width = 90.0;
1137 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1138 assert_eq!(
1139 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1140 vec!["日本語と", "Englishの混在", "した文章"]
1141 );
1142 }
1143
1144 #[test]
1145 fn test_pre_cjk() {
1146 let pixels_per_point = 1.0;
1147 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1148 let mut layout_job = LayoutJob::single_section(
1149 "日本語とEnglishの混在した文章".into(),
1150 TextFormat::default(),
1151 );
1152 layout_job.wrap.max_width = 110.0;
1153 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1154 assert_eq!(
1155 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1156 vec!["日本語とEnglish", "の混在した文章"]
1157 );
1158 }
1159
1160 #[test]
1161 fn test_truncate_width() {
1162 let pixels_per_point = 1.0;
1163 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1164 let mut layout_job =
1165 LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default());
1166 layout_job.wrap.max_width = f32::INFINITY;
1167 layout_job.wrap.max_rows = 1;
1168 layout_job.round_output_to_gui = false;
1169 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1170 assert!(galley.elided);
1171 assert_eq!(
1172 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1173 vec!["# DNA…"]
1174 );
1175 let row = &galley.rows[0];
1176 assert_eq!(row.pos, Pos2::ZERO);
1177 assert_eq!(row.rect().max.x, row.glyphs.last().unwrap().max_x());
1178 }
1179
1180 #[test]
1181 fn test_truncate_with_pixels_per_point() {
1182 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1183
1184 for pixels_per_point in [
1185 0.33, 0.5, 0.67, 1.0, 1.25, 1.33, 1.5, 1.75, 2.0, 3.0, 4.0, 5.0,
1186 ] {
1187 for ch in ['W', 'A', 'n', 't', 'i'] {
1188 let target_width = 50.0;
1189 let text = (0..20).map(|_| ch).collect::<String>();
1190
1191 let mut job = LayoutJob::single_section(text, TextFormat::default());
1192 job.wrap.max_width = target_width;
1193 job.wrap.max_rows = 1;
1194 let elided_galley = layout(&mut fonts, pixels_per_point, job.into());
1195 assert!(elided_galley.elided);
1196
1197 let test_galley = layout(
1198 &mut fonts,
1199 pixels_per_point,
1200 Arc::new(LayoutJob::single_section(
1201 (0..elided_galley.rows[0].char_count_excluding_newline())
1202 .map(|_| ch)
1203 .chain(std::iter::once('…'))
1204 .collect::<String>(),
1205 TextFormat::default(),
1206 )),
1207 );
1208
1209 assert!(elided_galley.size().x >= 0.0);
1210 assert!(elided_galley.size().x <= target_width);
1211 assert!(test_galley.size().x > target_width);
1212 }
1213 }
1214 }
1215
1216 #[test]
1217 fn test_empty_row() {
1218 let pixels_per_point = 1.0;
1219 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1220
1221 let font_id = FontId::default();
1222 let font_height = fonts
1223 .font(&font_id.family)
1224 .styled_metrics(pixels_per_point, font_id.size, &VariationCoords::default())
1225 .row_height;
1226
1227 let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY);
1228
1229 let galley = layout(&mut fonts, pixels_per_point, job.into());
1230
1231 assert_eq!(galley.rows.len(), 1, "Expected one row");
1232 assert_eq!(
1233 galley.rows[0].row.glyphs.len(),
1234 0,
1235 "Expected no glyphs in the empty row"
1236 );
1237 assert_eq!(
1238 galley.size(),
1239 Vec2::new(0.0, font_height.round()),
1240 "Unexpected galley size"
1241 );
1242 assert_eq!(
1243 galley.intrinsic_size(),
1244 Vec2::new(0.0, font_height.round()),
1245 "Unexpected intrinsic size"
1246 );
1247 }
1248
1249 #[test]
1250 fn test_end_with_newline() {
1251 let pixels_per_point = 1.0;
1252 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1253
1254 let font_id = FontId::default();
1255 let font_height = fonts
1256 .font(&font_id.family)
1257 .styled_metrics(pixels_per_point, font_id.size, &VariationCoords::default())
1258 .row_height;
1259
1260 let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY);
1261
1262 let galley = layout(&mut fonts, pixels_per_point, job.into());
1263
1264 assert_eq!(galley.rows.len(), 2, "Expected two rows");
1265 assert_eq!(
1266 galley.rows[1].row.glyphs.len(),
1267 0,
1268 "Expected no glyphs in the empty row"
1269 );
1270 assert_eq!(
1271 galley.size().round(),
1272 Vec2::new(17.0, font_height.round() * 2.0),
1273 "Unexpected galley size"
1274 );
1275 assert_eq!(
1276 galley.intrinsic_size().round(),
1277 Vec2::new(17.0, font_height.round() * 2.0),
1278 "Unexpected intrinsic size"
1279 );
1280 }
1281}