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 glyphs: Vec<Glyph> = paragraph.glyphs[row_start_idx..]
424 .iter()
425 .copied()
426 .map(|mut glyph| {
427 glyph.pos.x -= row_start_x;
428 glyph
429 })
430 .collect();
431
432 let section_index_at_start = glyphs[0].section_index;
433 let paragraph_min_x = glyphs[0].pos.x;
434 let paragraph_max_x = glyphs.last().unwrap().max_x();
435
436 out_rows.push(PlacedRow {
437 pos: pos2(paragraph_min_x, 0.0),
438 row: Arc::new(Row {
439 section_index_at_start,
440 glyphs,
441 visuals: Default::default(),
442 size: vec2(paragraph_max_x - paragraph_min_x, 0.0),
443 }),
444 ends_with_newline: false,
445 });
446 }
447 }
448}
449
450fn replace_last_glyph_with_overflow_character(
454 fonts: &mut FontsImpl,
455 pixels_per_point: f32,
456 job: &LayoutJob,
457 row: &mut Row,
458) {
459 let Some(overflow_character) = job.wrap.overflow_character else {
460 return;
461 };
462
463 let mut section_index = row
464 .glyphs
465 .last()
466 .map(|g| g.section_index)
467 .unwrap_or(row.section_index_at_start);
468 loop {
469 let section = &job.sections[section_index as usize];
470 let extra_letter_spacing = section.format.extra_letter_spacing;
471 let mut font = fonts.font(§ion.format.font_id.family);
472 let font_size = section.format.font_id.size;
473
474 let (font_id, glyph_info) = font.glyph_info(overflow_character);
475 let mut font_face = font.fonts_by_id.get_mut(&font_id);
476 let font_face_metrics = font_face
477 .as_mut()
478 .map(|f| f.styled_metrics(pixels_per_point, font_size, §ion.format.coords))
479 .unwrap_or_default();
480
481 let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() {
482 let pair_kerning = font_face
484 .as_mut()
485 .map(|font_face| {
486 if let (Some(prev_glyph_id), Some(overflow_glyph_id)) = (
487 font_face.glyph_info(prev_glyph.chr).and_then(|g| g.id),
488 font_face.glyph_info(overflow_character).and_then(|g| g.id),
489 ) {
490 font_face.pair_kerning(&font_face_metrics, prev_glyph_id, overflow_glyph_id)
491 } else {
492 0.0
493 }
494 })
495 .unwrap_or_default();
496
497 prev_glyph.max_x() + extra_letter_spacing + pair_kerning
498 } else {
499 0.0 };
501
502 let replacement_glyph_width = font_face
503 .as_mut()
504 .and_then(|f| f.glyph_info(overflow_character))
505 .map(|i| {
506 i.advance_width_unscaled.0 * font_face_metrics.px_scale_factor / pixels_per_point
507 })
508 .unwrap_or_default();
509
510 if overflow_glyph_x + replacement_glyph_width <= job.effective_wrap_width()
512 || row.glyphs.is_empty()
513 {
514 let (replacement_glyph_alloc, physical_x) = font_face
517 .as_mut()
518 .map(|f| {
519 f.allocate_glyph(
520 font.atlas,
521 &font_face_metrics,
522 glyph_info,
523 overflow_character,
524 overflow_glyph_x * pixels_per_point,
525 )
526 })
527 .unwrap_or_default();
528
529 let font_metrics =
530 font.styled_metrics(pixels_per_point, font_size, §ion.format.coords);
531 let line_height = section
532 .format
533 .line_height
534 .unwrap_or(font_metrics.row_height);
535
536 row.glyphs.push(Glyph {
537 chr: overflow_character,
538 pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN),
539 advance_width: replacement_glyph_alloc.advance_width_px / pixels_per_point,
540 line_height,
541 font_face_height: font_face_metrics.row_height,
542 font_face_ascent: font_face_metrics.ascent,
543 font_height: font_metrics.row_height,
544 font_ascent: font_metrics.ascent,
545 uv_rect: replacement_glyph_alloc.uv_rect,
546 section_index,
547 first_vertex: 0, });
549 return;
550 }
551
552 if let Some(last_glyph) = row.glyphs.pop() {
554 section_index = last_glyph.section_index;
555 } else {
556 section_index = row.section_index_at_start;
557 }
558 }
559}
560
561fn halign_and_justify_row(
565 point_scale: PointScale,
566 placed_row: &mut PlacedRow,
567 halign: Align,
568 wrap_width: f32,
569 justify: bool,
570) {
571 #![expect(clippy::useless_let_if_seq)] let row = Arc::make_mut(&mut placed_row.row);
574
575 if row.glyphs.is_empty() {
576 return;
577 }
578
579 let num_leading_spaces = row
580 .glyphs
581 .iter()
582 .take_while(|glyph| glyph.chr.is_whitespace())
583 .count();
584
585 let glyph_range = if num_leading_spaces == row.glyphs.len() {
586 (0, row.glyphs.len())
588 } else {
589 let num_trailing_spaces = row
590 .glyphs
591 .iter()
592 .rev()
593 .take_while(|glyph| glyph.chr.is_whitespace())
594 .count();
595
596 (num_leading_spaces, row.glyphs.len() - num_trailing_spaces)
597 };
598 let num_glyphs_in_range = glyph_range.1 - glyph_range.0;
599 assert!(num_glyphs_in_range > 0, "Should have at least one glyph");
600
601 let original_min_x = row.glyphs[glyph_range.0].logical_rect().min.x;
602 let original_max_x = row.glyphs[glyph_range.1 - 1].logical_rect().max.x;
603 let original_width = original_max_x - original_min_x;
604
605 let target_width = if justify && num_glyphs_in_range > 1 {
606 wrap_width
607 } else {
608 original_width
609 };
610
611 let (target_min_x, target_max_x) = match halign {
612 Align::LEFT => (0.0, target_width),
613 Align::Center => (-target_width / 2.0, target_width / 2.0),
614 Align::RIGHT => (-target_width, 0.0),
615 };
616
617 let num_spaces_in_range = row.glyphs[glyph_range.0..glyph_range.1]
618 .iter()
619 .filter(|glyph| glyph.chr.is_whitespace())
620 .count();
621
622 let mut extra_x_per_glyph = if num_glyphs_in_range == 1 {
623 0.0
624 } else {
625 (target_width - original_width) / (num_glyphs_in_range as f32 - 1.0)
626 };
627 extra_x_per_glyph = extra_x_per_glyph.at_least(0.0); let mut extra_x_per_space = 0.0;
630 if 0 < num_spaces_in_range && num_spaces_in_range < num_glyphs_in_range {
631 extra_x_per_glyph = point_scale.floor_to_pixel(extra_x_per_glyph);
635
636 extra_x_per_space = (target_width
637 - original_width
638 - extra_x_per_glyph * (num_glyphs_in_range as f32 - 1.0))
639 / (num_spaces_in_range as f32);
640 }
641
642 placed_row.pos.x = point_scale.round_to_pixel(target_min_x);
643 let mut translate_x = -original_min_x - extra_x_per_glyph * glyph_range.0 as f32;
644
645 for glyph in &mut row.glyphs {
646 glyph.pos.x += translate_x;
647 glyph.pos.x = point_scale.round_to_pixel(glyph.pos.x);
648 translate_x += extra_x_per_glyph;
649 if glyph.chr.is_whitespace() {
650 translate_x += extra_x_per_space;
651 }
652 }
653
654 row.size.x = target_max_x - target_min_x;
656}
657
658fn galley_from_rows(
660 point_scale: PointScale,
661 job: Arc<LayoutJob>,
662 mut rows: Vec<PlacedRow>,
663 elided: bool,
664 intrinsic_size: Vec2,
665) -> Galley {
666 let mut first_row_min_height = job.first_row_min_height;
667 let mut cursor_y = 0.0;
668
669 for placed_row in &mut rows {
670 let mut max_row_height = first_row_min_height.at_least(placed_row.height());
671 let row = Arc::make_mut(&mut placed_row.row);
672
673 first_row_min_height = 0.0;
674 for glyph in &row.glyphs {
675 max_row_height = max_row_height.at_least(glyph.line_height);
676 }
677 max_row_height = point_scale.round_to_pixel(max_row_height);
678
679 for glyph in &mut row.glyphs {
681 let format = &job.sections[glyph.section_index as usize].format;
682
683 glyph.pos.y = glyph.font_face_ascent
684
685 + format.valign.to_factor() * (max_row_height - glyph.line_height)
687
688 + 0.5 * (glyph.font_height - glyph.font_face_height);
691
692 glyph.pos.y = point_scale.round_to_pixel(glyph.pos.y);
693 }
694
695 placed_row.pos.y = cursor_y;
696 row.size.y = max_row_height;
697
698 cursor_y += max_row_height;
699 cursor_y = point_scale.round_to_pixel(cursor_y); }
701
702 let format_summary = format_summary(&job);
703
704 let mut rect = Rect::ZERO;
705 let mut mesh_bounds = Rect::NOTHING;
706 let mut num_vertices = 0;
707 let mut num_indices = 0;
708
709 for placed_row in &mut rows {
710 rect |= placed_row.rect();
711
712 let row = Arc::make_mut(&mut placed_row.row);
713 row.visuals = tessellate_row(point_scale, &job, &format_summary, row);
714
715 mesh_bounds |= row.visuals.mesh_bounds.translate(placed_row.pos.to_vec2());
716 num_vertices += row.visuals.mesh.vertices.len();
717 num_indices += row.visuals.mesh.indices.len();
718
719 row.section_index_at_start = u32::MAX; for glyph in &mut row.glyphs {
721 glyph.section_index = u32::MAX; }
723 }
724
725 let mut galley = Galley {
726 job,
727 rows,
728 elided,
729 rect,
730 mesh_bounds,
731 num_vertices,
732 num_indices,
733 pixels_per_point: point_scale.pixels_per_point,
734 intrinsic_size,
735 };
736
737 if galley.job.round_output_to_gui {
738 galley.round_output_to_gui();
739 }
740
741 galley
742}
743
744#[derive(Default)]
745struct FormatSummary {
746 any_background: bool,
747 any_underline: bool,
748 any_strikethrough: bool,
749}
750
751fn format_summary(job: &LayoutJob) -> FormatSummary {
752 let mut format_summary = FormatSummary::default();
753 for section in &job.sections {
754 format_summary.any_background |= section.format.background != Color32::TRANSPARENT;
755 format_summary.any_underline |= section.format.underline != Stroke::NONE;
756 format_summary.any_strikethrough |= section.format.strikethrough != Stroke::NONE;
757 }
758 format_summary
759}
760
761fn tessellate_row(
762 point_scale: PointScale,
763 job: &LayoutJob,
764 format_summary: &FormatSummary,
765 row: &mut Row,
766) -> RowVisuals {
767 if row.glyphs.is_empty() {
768 return Default::default();
769 }
770
771 let mut mesh = Mesh::default();
772
773 mesh.reserve_triangles(row.glyphs.len() * 2);
774 mesh.reserve_vertices(row.glyphs.len() * 4);
775
776 if format_summary.any_background {
777 add_row_backgrounds(point_scale, job, row, &mut mesh);
778 }
779
780 let glyph_index_start = mesh.indices.len();
781 let glyph_vertex_start = mesh.vertices.len();
782 tessellate_glyphs(point_scale, job, row, &mut mesh);
783 let glyph_vertex_end = mesh.vertices.len();
784
785 if format_summary.any_underline {
786 add_row_hline(point_scale, row, &mut mesh, |glyph| {
787 let format = &job.sections[glyph.section_index as usize].format;
788 let stroke = format.underline;
789 let y = glyph.logical_rect().bottom();
790 (stroke, y)
791 });
792 }
793
794 if format_summary.any_strikethrough {
795 add_row_hline(point_scale, row, &mut mesh, |glyph| {
796 let format = &job.sections[glyph.section_index as usize].format;
797 let stroke = format.strikethrough;
798 let y = glyph.logical_rect().center().y;
799 (stroke, y)
800 });
801 }
802
803 let mesh_bounds = mesh.calc_bounds();
804
805 RowVisuals {
806 mesh,
807 mesh_bounds,
808 glyph_index_start,
809 glyph_vertex_range: glyph_vertex_start..glyph_vertex_end,
810 }
811}
812
813fn add_row_backgrounds(point_scale: PointScale, job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
816 if row.glyphs.is_empty() {
817 return;
818 }
819
820 let mut end_run = |start: Option<(Color32, Rect, f32)>, stop_x: f32| {
821 if let Some((color, start_rect, expand)) = start {
822 let rect = Rect::from_min_max(start_rect.left_top(), pos2(stop_x, start_rect.bottom()));
823 let rect = rect.expand(expand);
824 let rect = rect.round_to_pixels(point_scale.pixels_per_point());
825 mesh.add_colored_rect(rect, color);
826 }
827 };
828
829 let mut run_start = None;
830 let mut last_rect = Rect::NAN;
831
832 for glyph in &row.glyphs {
833 let format = &job.sections[glyph.section_index as usize].format;
834 let color = format.background;
835 let rect = glyph.logical_rect();
836
837 if color == Color32::TRANSPARENT {
838 end_run(run_start.take(), last_rect.right());
839 } else if let Some((existing_color, start, expand)) = run_start {
840 if existing_color == color
841 && start.top() == rect.top()
842 && start.bottom() == rect.bottom()
843 && format.expand_bg == expand
844 {
845 } else {
847 end_run(run_start.take(), last_rect.right());
848 run_start = Some((color, rect, format.expand_bg));
849 }
850 } else {
851 run_start = Some((color, rect, format.expand_bg));
852 }
853
854 last_rect = rect;
855 }
856
857 end_run(run_start.take(), last_rect.right());
858}
859
860fn tessellate_glyphs(point_scale: PointScale, job: &LayoutJob, row: &mut Row, mesh: &mut Mesh) {
861 for glyph in &mut row.glyphs {
862 glyph.first_vertex = mesh.vertices.len() as u32;
863 let uv_rect = glyph.uv_rect;
864 if !uv_rect.is_nothing() {
865 let mut left_top = glyph.pos + uv_rect.offset;
866 left_top.x = point_scale.round_to_pixel(left_top.x);
867 left_top.y = point_scale.round_to_pixel(left_top.y);
868
869 let rect = Rect::from_min_max(left_top, left_top + uv_rect.size);
870 let uv = Rect::from_min_max(
871 pos2(uv_rect.min[0] as f32, uv_rect.min[1] as f32),
872 pos2(uv_rect.max[0] as f32, uv_rect.max[1] as f32),
873 );
874
875 let format = &job.sections[glyph.section_index as usize].format;
876
877 let color = format.color;
878
879 if format.italics {
880 let idx = mesh.vertices.len() as u32;
881 mesh.add_triangle(idx, idx + 1, idx + 2);
882 mesh.add_triangle(idx + 2, idx + 1, idx + 3);
883
884 let top_offset = rect.height() * 0.25 * Vec2::X;
885
886 mesh.vertices.push(Vertex {
887 pos: rect.left_top() + top_offset,
888 uv: uv.left_top(),
889 color,
890 });
891 mesh.vertices.push(Vertex {
892 pos: rect.right_top() + top_offset,
893 uv: uv.right_top(),
894 color,
895 });
896 mesh.vertices.push(Vertex {
897 pos: rect.left_bottom(),
898 uv: uv.left_bottom(),
899 color,
900 });
901 mesh.vertices.push(Vertex {
902 pos: rect.right_bottom(),
903 uv: uv.right_bottom(),
904 color,
905 });
906 } else {
907 mesh.add_rect_with_uv(rect, uv, color);
908 }
909 }
910 }
911}
912
913fn add_row_hline(
915 point_scale: PointScale,
916 row: &Row,
917 mesh: &mut Mesh,
918 stroke_and_y: impl Fn(&Glyph) -> (Stroke, f32),
919) {
920 let mut path = crate::tessellator::Path::default(); let mut end_line = |start: Option<(Stroke, Pos2)>, stop_x: f32| {
923 if let Some((stroke, start)) = start {
924 let stop = pos2(stop_x, start.y);
925 path.clear();
926 path.add_line_segment([start, stop]);
927 let feathering = 1.0 / point_scale.pixels_per_point();
928 path.stroke_open(feathering, &PathStroke::from(stroke), mesh);
929 }
930 };
931
932 let mut line_start = None;
933 let mut last_right_x = f32::NAN;
934
935 for glyph in &row.glyphs {
936 let (stroke, mut y) = stroke_and_y(glyph);
937 stroke.round_center_to_pixel(point_scale.pixels_per_point, &mut y);
938
939 if stroke.is_empty() {
940 end_line(line_start.take(), last_right_x);
941 } else if let Some((existing_stroke, start)) = line_start {
942 if existing_stroke == stroke && start.y == y {
943 } else {
945 end_line(line_start.take(), last_right_x);
946 line_start = Some((stroke, pos2(glyph.pos.x, y)));
947 }
948 } else {
949 line_start = Some((stroke, pos2(glyph.pos.x, y)));
950 }
951
952 last_right_x = glyph.max_x();
953 }
954
955 end_line(line_start.take(), last_right_x);
956}
957
958#[derive(Clone, Copy, Default)]
963struct RowBreakCandidates {
964 space: Option<usize>,
967
968 cjk: Option<usize>,
970
971 pre_cjk: Option<usize>,
973
974 dash: Option<usize>,
977
978 punctuation: Option<usize>,
981
982 any: Option<usize>,
985}
986
987impl RowBreakCandidates {
988 fn add(&mut self, index: usize, glyphs: &[Glyph]) {
989 let chr = glyphs[0].chr;
990 const NON_BREAKING_SPACE: char = '\u{A0}';
991 if chr.is_whitespace() && chr != NON_BREAKING_SPACE {
992 self.space = Some(index);
993 } else if is_cjk(chr) && (glyphs.len() == 1 || is_cjk_break_allowed(glyphs[1].chr)) {
994 self.cjk = Some(index);
995 } else if chr == '-' {
996 self.dash = Some(index);
997 } else if chr.is_ascii_punctuation() {
998 self.punctuation = Some(index);
999 } else if glyphs.len() > 1 && is_cjk(glyphs[1].chr) {
1000 self.pre_cjk = Some(index);
1001 }
1002 self.any = Some(index);
1003 }
1004
1005 fn word_boundary(&self) -> Option<usize> {
1006 [self.space, self.cjk, self.pre_cjk]
1007 .into_iter()
1008 .max()
1009 .flatten()
1010 }
1011
1012 fn has_good_candidate(&self, break_anywhere: bool) -> bool {
1013 if break_anywhere {
1014 self.any.is_some()
1015 } else {
1016 self.word_boundary().is_some()
1017 }
1018 }
1019
1020 fn get(&self, break_anywhere: bool) -> Option<usize> {
1021 if break_anywhere {
1022 self.any
1023 } else {
1024 self.word_boundary()
1025 .or(self.dash)
1026 .or(self.punctuation)
1027 .or(self.any)
1028 }
1029 }
1030
1031 fn forget_before_idx(&mut self, index: usize) {
1032 let Self {
1033 space,
1034 cjk,
1035 pre_cjk,
1036 dash,
1037 punctuation,
1038 any,
1039 } = self;
1040 if space.is_some_and(|s| s < index) {
1041 *space = None;
1042 }
1043 if cjk.is_some_and(|s| s < index) {
1044 *cjk = None;
1045 }
1046 if pre_cjk.is_some_and(|s| s < index) {
1047 *pre_cjk = None;
1048 }
1049 if dash.is_some_and(|s| s < index) {
1050 *dash = None;
1051 }
1052 if punctuation.is_some_and(|s| s < index) {
1053 *punctuation = None;
1054 }
1055 if any.is_some_and(|s| s < index) {
1056 *any = None;
1057 }
1058 }
1059}
1060
1061#[cfg(test)]
1064mod tests {
1065
1066 use super::{super::*, *};
1067
1068 #[test]
1069 fn test_zero_max_width() {
1070 let pixels_per_point = 1.0;
1071 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1072 let mut layout_job = LayoutJob::single_section("W".into(), TextFormat::default());
1073 layout_job.wrap.max_width = 0.0;
1074 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1075 assert_eq!(galley.rows.len(), 1);
1076 }
1077
1078 #[test]
1079 fn test_truncate_with_newline() {
1080 let pixels_per_point = 1.0;
1083
1084 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1085 let text_format = TextFormat {
1086 font_id: FontId::monospace(12.0),
1087 ..Default::default()
1088 };
1089
1090 for text in ["Hello\nworld", "\nfoo"] {
1091 for break_anywhere in [false, true] {
1092 for max_width in [0.0, 5.0, 10.0, 20.0, f32::INFINITY] {
1093 let mut layout_job =
1094 LayoutJob::single_section(text.into(), text_format.clone());
1095 layout_job.wrap.max_width = max_width;
1096 layout_job.wrap.max_rows = 1;
1097 layout_job.wrap.break_anywhere = break_anywhere;
1098
1099 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1100
1101 assert!(galley.elided);
1102 assert_eq!(galley.rows.len(), 1);
1103 let row_text = galley.rows[0].text();
1104 assert!(
1105 row_text.ends_with('…'),
1106 "Expected row to end with `…`, got {row_text:?} when line-breaking the text {text:?} with max_width {max_width} and break_anywhere {break_anywhere}.",
1107 );
1108 }
1109 }
1110 }
1111
1112 {
1113 let mut layout_job = LayoutJob::single_section("Hello\nworld".into(), text_format);
1114 layout_job.wrap.max_width = 50.0;
1115 layout_job.wrap.max_rows = 1;
1116 layout_job.wrap.break_anywhere = false;
1117
1118 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1119
1120 assert!(galley.elided);
1121 assert_eq!(galley.rows.len(), 1);
1122 let row_text = galley.rows[0].text();
1123 assert_eq!(row_text, "Hello…");
1124 }
1125 }
1126
1127 #[test]
1128 fn test_cjk() {
1129 let pixels_per_point = 1.0;
1130 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1131 let mut layout_job = LayoutJob::single_section(
1132 "日本語とEnglishの混在した文章".into(),
1133 TextFormat::default(),
1134 );
1135 layout_job.wrap.max_width = 90.0;
1136 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1137 assert_eq!(
1138 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1139 vec!["日本語と", "Englishの混在", "した文章"]
1140 );
1141 }
1142
1143 #[test]
1144 fn test_pre_cjk() {
1145 let pixels_per_point = 1.0;
1146 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1147 let mut layout_job = LayoutJob::single_section(
1148 "日本語とEnglishの混在した文章".into(),
1149 TextFormat::default(),
1150 );
1151 layout_job.wrap.max_width = 110.0;
1152 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1153 assert_eq!(
1154 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1155 vec!["日本語とEnglish", "の混在した文章"]
1156 );
1157 }
1158
1159 #[test]
1160 fn test_truncate_width() {
1161 let pixels_per_point = 1.0;
1162 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1163 let mut layout_job =
1164 LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default());
1165 layout_job.wrap.max_width = f32::INFINITY;
1166 layout_job.wrap.max_rows = 1;
1167 layout_job.round_output_to_gui = false;
1168 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1169 assert!(galley.elided);
1170 assert_eq!(
1171 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1172 vec!["# DNA…"]
1173 );
1174 let row = &galley.rows[0];
1175 assert_eq!(row.pos, Pos2::ZERO);
1176 assert_eq!(row.rect().max.x, row.glyphs.last().unwrap().max_x());
1177 }
1178
1179 #[test]
1180 fn test_truncate_with_pixels_per_point() {
1181 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1182
1183 for pixels_per_point in [
1184 0.33, 0.5, 0.67, 1.0, 1.25, 1.33, 1.5, 1.75, 2.0, 3.0, 4.0, 5.0,
1185 ] {
1186 for ch in ['W', 'A', 'n', 't', 'i'] {
1187 let target_width = 50.0;
1188 let text = (0..20).map(|_| ch).collect::<String>();
1189
1190 let mut job = LayoutJob::single_section(text, TextFormat::default());
1191 job.wrap.max_width = target_width;
1192 job.wrap.max_rows = 1;
1193 let elided_galley = layout(&mut fonts, pixels_per_point, job.into());
1194 assert!(elided_galley.elided);
1195
1196 let test_galley = layout(
1197 &mut fonts,
1198 pixels_per_point,
1199 Arc::new(LayoutJob::single_section(
1200 (0..elided_galley.rows[0].char_count_excluding_newline())
1201 .map(|_| ch)
1202 .chain(std::iter::once('…'))
1203 .collect::<String>(),
1204 TextFormat::default(),
1205 )),
1206 );
1207
1208 assert!(elided_galley.size().x >= 0.0);
1209 assert!(elided_galley.size().x <= target_width);
1210 assert!(test_galley.size().x > target_width);
1211 }
1212 }
1213 }
1214
1215 #[test]
1216 fn test_empty_row() {
1217 let pixels_per_point = 1.0;
1218 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1219
1220 let font_id = FontId::default();
1221 let font_height = fonts
1222 .font(&font_id.family)
1223 .styled_metrics(pixels_per_point, font_id.size, &VariationCoords::default())
1224 .row_height;
1225
1226 let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY);
1227
1228 let galley = layout(&mut fonts, pixels_per_point, job.into());
1229
1230 assert_eq!(galley.rows.len(), 1, "Expected one row");
1231 assert_eq!(
1232 galley.rows[0].row.glyphs.len(),
1233 0,
1234 "Expected no glyphs in the empty row"
1235 );
1236 assert_eq!(
1237 galley.size(),
1238 Vec2::new(0.0, font_height.round()),
1239 "Unexpected galley size"
1240 );
1241 assert_eq!(
1242 galley.intrinsic_size(),
1243 Vec2::new(0.0, font_height.round()),
1244 "Unexpected intrinsic size"
1245 );
1246 }
1247
1248 #[test]
1249 fn test_end_with_newline() {
1250 let pixels_per_point = 1.0;
1251 let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
1252
1253 let font_id = FontId::default();
1254 let font_height = fonts
1255 .font(&font_id.family)
1256 .styled_metrics(pixels_per_point, font_id.size, &VariationCoords::default())
1257 .row_height;
1258
1259 let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY);
1260
1261 let galley = layout(&mut fonts, pixels_per_point, job.into());
1262
1263 assert_eq!(galley.rows.len(), 2, "Expected two rows");
1264 assert_eq!(
1265 galley.rows[1].row.glyphs.len(),
1266 0,
1267 "Expected no glyphs in the empty row"
1268 );
1269 assert_eq!(
1270 galley.size().round(),
1271 Vec2::new(17.0, font_height.round() * 2.0),
1272 "Unexpected galley size"
1273 );
1274 assert_eq!(
1275 galley.intrinsic_size().round(),
1276 Vec2::new(17.0, font_height.round() * 2.0),
1277 "Unexpected intrinsic size"
1278 );
1279 }
1280}