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