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 first_vertex: 0, });
235
236 paragraph.cursor_x_px += glyph_alloc.advance_width_px;
237 last_glyph_id = Some(glyph_alloc.id);
238 }
239 }
240}
241
242fn calculate_intrinsic_size(
247 point_scale: PointScale,
248 job: &LayoutJob,
249 paragraphs: &[Paragraph],
250) -> Vec2 {
251 let mut intrinsic_size = Vec2::ZERO;
252 for (idx, paragraph) in paragraphs.iter().enumerate() {
253 let width = paragraph
254 .glyphs
255 .last()
256 .map(|l| l.max_x())
257 .unwrap_or_default();
258 intrinsic_size.x = f32::max(intrinsic_size.x, width);
259
260 let mut height = paragraph
261 .glyphs
262 .iter()
263 .map(|g| g.line_height)
264 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
265 .unwrap_or(paragraph.empty_paragraph_height);
266 if idx == 0 {
267 height = f32::max(height, job.first_row_min_height);
268 }
269 intrinsic_size.y += point_scale.round_to_pixel(height);
270 }
271 intrinsic_size
272}
273
274fn rows_from_paragraphs(
276 paragraphs: Vec<Paragraph>,
277 job: &LayoutJob,
278 elided: &mut bool,
279) -> Vec<PlacedRow> {
280 let num_paragraphs = paragraphs.len();
281
282 let mut rows = vec![];
283
284 for (i, paragraph) in paragraphs.into_iter().enumerate() {
285 if job.wrap.max_rows <= rows.len() {
286 *elided = true;
287 break;
288 }
289
290 let is_last_paragraph = (i + 1) == num_paragraphs;
291
292 if paragraph.glyphs.is_empty() {
293 rows.push(PlacedRow {
294 pos: pos2(0.0, f32::NAN),
295 row: Arc::new(Row {
296 section_index_at_start: paragraph.section_index_at_start,
297 glyphs: vec![],
298 visuals: Default::default(),
299 size: vec2(0.0, paragraph.empty_paragraph_height),
300 ends_with_newline: !is_last_paragraph,
301 }),
302 });
303 } else {
304 let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x();
305 if paragraph_max_x <= job.effective_wrap_width() {
306 rows.push(PlacedRow {
308 pos: pos2(0.0, f32::NAN),
309 row: Arc::new(Row {
310 section_index_at_start: paragraph.section_index_at_start,
311 glyphs: paragraph.glyphs,
312 visuals: Default::default(),
313 size: vec2(paragraph_max_x, 0.0),
314 ends_with_newline: !is_last_paragraph,
315 }),
316 });
317 } else {
318 line_break(¶graph, job, &mut rows, elided);
319 let placed_row = rows.last_mut().unwrap();
320 let row = Arc::make_mut(&mut placed_row.row);
321 row.ends_with_newline = !is_last_paragraph;
322 }
323 }
324 }
325
326 rows
327}
328
329fn line_break(
330 paragraph: &Paragraph,
331 job: &LayoutJob,
332 out_rows: &mut Vec<PlacedRow>,
333 elided: &mut bool,
334) {
335 let wrap_width = job.effective_wrap_width();
336
337 let mut row_break_candidates = RowBreakCandidates::default();
339
340 let mut first_row_indentation = paragraph.glyphs[0].pos.x;
341 let mut row_start_x = 0.0;
342 let mut row_start_idx = 0;
343
344 for i in 0..paragraph.glyphs.len() {
345 if job.wrap.max_rows <= out_rows.len() {
346 *elided = true;
347 break;
348 }
349
350 let potential_row_width = paragraph.glyphs[i].max_x() - row_start_x;
351
352 if wrap_width < potential_row_width {
353 if first_row_indentation > 0.0
356 && !row_break_candidates.has_good_candidate(job.wrap.break_anywhere)
357 {
358 out_rows.push(PlacedRow {
361 pos: pos2(0.0, f32::NAN),
362 row: Arc::new(Row {
363 section_index_at_start: paragraph.section_index_at_start,
364 glyphs: vec![],
365 visuals: Default::default(),
366 size: Vec2::ZERO,
367 ends_with_newline: false,
368 }),
369 });
370 row_start_x += first_row_indentation;
371 first_row_indentation = 0.0;
372 } else if let Some(last_kept_index) = row_break_candidates.get(job.wrap.break_anywhere)
373 {
374 let glyphs: Vec<Glyph> = paragraph.glyphs[row_start_idx..=last_kept_index]
375 .iter()
376 .copied()
377 .map(|mut glyph| {
378 glyph.pos.x -= row_start_x;
379 glyph
380 })
381 .collect();
382
383 let section_index_at_start = glyphs[0].section_index;
384 let paragraph_max_x = glyphs.last().unwrap().max_x();
385
386 out_rows.push(PlacedRow {
387 pos: pos2(0.0, f32::NAN),
388 row: Arc::new(Row {
389 section_index_at_start,
390 glyphs,
391 visuals: Default::default(),
392 size: vec2(paragraph_max_x, 0.0),
393 ends_with_newline: false,
394 }),
395 });
396
397 row_start_idx = last_kept_index + 1;
399 row_start_x = paragraph.glyphs[row_start_idx].pos.x;
400 row_break_candidates.forget_before_idx(row_start_idx);
401 } else {
402 }
404 }
405
406 row_break_candidates.add(i, ¶graph.glyphs[i..]);
407 }
408
409 if row_start_idx < paragraph.glyphs.len() {
410 if job.wrap.max_rows <= out_rows.len() {
413 *elided = true; } else {
415 let glyphs: Vec<Glyph> = paragraph.glyphs[row_start_idx..]
416 .iter()
417 .copied()
418 .map(|mut glyph| {
419 glyph.pos.x -= row_start_x;
420 glyph
421 })
422 .collect();
423
424 let section_index_at_start = glyphs[0].section_index;
425 let paragraph_min_x = glyphs[0].pos.x;
426 let paragraph_max_x = glyphs.last().unwrap().max_x();
427
428 out_rows.push(PlacedRow {
429 pos: pos2(paragraph_min_x, 0.0),
430 row: Arc::new(Row {
431 section_index_at_start,
432 glyphs,
433 visuals: Default::default(),
434 size: vec2(paragraph_max_x - paragraph_min_x, 0.0),
435 ends_with_newline: false,
436 }),
437 });
438 }
439 }
440}
441
442fn replace_last_glyph_with_overflow_character(
446 fonts: &mut FontsImpl,
447 pixels_per_point: f32,
448 job: &LayoutJob,
449 row: &mut Row,
450) {
451 let Some(overflow_character) = job.wrap.overflow_character else {
452 return;
453 };
454
455 let mut section_index = row
456 .glyphs
457 .last()
458 .map(|g| g.section_index)
459 .unwrap_or(row.section_index_at_start);
460 loop {
461 let section = &job.sections[section_index as usize];
462 let extra_letter_spacing = section.format.extra_letter_spacing;
463 let mut font = fonts.font(§ion.format.font_id.family);
464 let font_size = section.format.font_id.size;
465
466 let (font_id, glyph_info) = font.glyph_info(overflow_character);
467 let mut font_impl = font.fonts_by_id.get_mut(&font_id);
468 let font_impl_metrics = font_impl
469 .as_mut()
470 .map(|f| f.scaled_metrics(pixels_per_point, font_size))
471 .unwrap_or_default();
472
473 let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() {
474 let pair_kerning = font_impl
476 .as_mut()
477 .map(|font_impl| {
478 if let (Some(prev_glyph_id), Some(overflow_glyph_id)) = (
479 font_impl.glyph_info(prev_glyph.chr).and_then(|g| g.id),
480 font_impl.glyph_info(overflow_character).and_then(|g| g.id),
481 ) {
482 font_impl.pair_kerning(&font_impl_metrics, prev_glyph_id, overflow_glyph_id)
483 } else {
484 0.0
485 }
486 })
487 .unwrap_or_default();
488
489 prev_glyph.max_x() + extra_letter_spacing + pair_kerning
490 } else {
491 0.0 };
493
494 let replacement_glyph_width = font_impl
495 .as_mut()
496 .and_then(|f| f.glyph_info(overflow_character))
497 .map(|i| i.advance_width_unscaled.0 * font_impl_metrics.px_scale_factor)
498 .unwrap_or_default();
499
500 if overflow_glyph_x + replacement_glyph_width <= job.effective_wrap_width()
502 || row.glyphs.is_empty()
503 {
504 let (replacement_glyph_alloc, physical_x) = font_impl
507 .as_mut()
508 .map(|f| {
509 f.allocate_glyph(
510 font.atlas,
511 &font_impl_metrics,
512 glyph_info,
513 overflow_character,
514 overflow_glyph_x * pixels_per_point,
515 )
516 })
517 .unwrap_or_default();
518
519 let font_metrics = font.scaled_metrics(pixels_per_point, font_size);
520 let line_height = section
521 .format
522 .line_height
523 .unwrap_or(font_metrics.row_height);
524
525 row.glyphs.push(Glyph {
526 chr: overflow_character,
527 pos: pos2(physical_x as f32 / pixels_per_point, f32::NAN),
528 advance_width: replacement_glyph_alloc.advance_width_px / pixels_per_point,
529 line_height,
530 font_impl_height: font_impl_metrics.row_height,
531 font_impl_ascent: font_impl_metrics.ascent,
532 font_height: font_metrics.row_height,
533 font_ascent: font_metrics.ascent,
534 uv_rect: replacement_glyph_alloc.uv_rect,
535 section_index,
536 first_vertex: 0, });
538 return;
539 }
540
541 if let Some(last_glyph) = row.glyphs.pop() {
543 section_index = last_glyph.section_index;
544 } else {
545 section_index = row.section_index_at_start;
546 }
547 }
548}
549
550fn halign_and_justify_row(
554 point_scale: PointScale,
555 placed_row: &mut PlacedRow,
556 halign: Align,
557 wrap_width: f32,
558 justify: bool,
559) {
560 #![expect(clippy::useless_let_if_seq)] let row = Arc::make_mut(&mut placed_row.row);
563
564 if row.glyphs.is_empty() {
565 return;
566 }
567
568 let num_leading_spaces = row
569 .glyphs
570 .iter()
571 .take_while(|glyph| glyph.chr.is_whitespace())
572 .count();
573
574 let glyph_range = if num_leading_spaces == row.glyphs.len() {
575 (0, row.glyphs.len())
577 } else {
578 let num_trailing_spaces = row
579 .glyphs
580 .iter()
581 .rev()
582 .take_while(|glyph| glyph.chr.is_whitespace())
583 .count();
584
585 (num_leading_spaces, row.glyphs.len() - num_trailing_spaces)
586 };
587 let num_glyphs_in_range = glyph_range.1 - glyph_range.0;
588 assert!(num_glyphs_in_range > 0, "Should have at least one glyph");
589
590 let original_min_x = row.glyphs[glyph_range.0].logical_rect().min.x;
591 let original_max_x = row.glyphs[glyph_range.1 - 1].logical_rect().max.x;
592 let original_width = original_max_x - original_min_x;
593
594 let target_width = if justify && num_glyphs_in_range > 1 {
595 wrap_width
596 } else {
597 original_width
598 };
599
600 let (target_min_x, target_max_x) = match halign {
601 Align::LEFT => (0.0, target_width),
602 Align::Center => (-target_width / 2.0, target_width / 2.0),
603 Align::RIGHT => (-target_width, 0.0),
604 };
605
606 let num_spaces_in_range = row.glyphs[glyph_range.0..glyph_range.1]
607 .iter()
608 .filter(|glyph| glyph.chr.is_whitespace())
609 .count();
610
611 let mut extra_x_per_glyph = if num_glyphs_in_range == 1 {
612 0.0
613 } else {
614 (target_width - original_width) / (num_glyphs_in_range as f32 - 1.0)
615 };
616 extra_x_per_glyph = extra_x_per_glyph.at_least(0.0); let mut extra_x_per_space = 0.0;
619 if 0 < num_spaces_in_range && num_spaces_in_range < num_glyphs_in_range {
620 extra_x_per_glyph = point_scale.floor_to_pixel(extra_x_per_glyph);
624
625 extra_x_per_space = (target_width
626 - original_width
627 - extra_x_per_glyph * (num_glyphs_in_range as f32 - 1.0))
628 / (num_spaces_in_range as f32);
629 }
630
631 placed_row.pos.x = point_scale.round_to_pixel(target_min_x);
632 let mut translate_x = -original_min_x - extra_x_per_glyph * glyph_range.0 as f32;
633
634 for glyph in &mut row.glyphs {
635 glyph.pos.x += translate_x;
636 glyph.pos.x = point_scale.round_to_pixel(glyph.pos.x);
637 translate_x += extra_x_per_glyph;
638 if glyph.chr.is_whitespace() {
639 translate_x += extra_x_per_space;
640 }
641 }
642
643 row.size.x = target_max_x - target_min_x;
645}
646
647fn galley_from_rows(
649 point_scale: PointScale,
650 job: Arc<LayoutJob>,
651 mut rows: Vec<PlacedRow>,
652 elided: bool,
653 intrinsic_size: Vec2,
654) -> Galley {
655 let mut first_row_min_height = job.first_row_min_height;
656 let mut cursor_y = 0.0;
657
658 for placed_row in &mut rows {
659 let mut max_row_height = first_row_min_height.at_least(placed_row.height());
660 let row = Arc::make_mut(&mut placed_row.row);
661
662 first_row_min_height = 0.0;
663 for glyph in &row.glyphs {
664 max_row_height = max_row_height.at_least(glyph.line_height);
665 }
666 max_row_height = point_scale.round_to_pixel(max_row_height);
667
668 for glyph in &mut row.glyphs {
670 let format = &job.sections[glyph.section_index as usize].format;
671
672 glyph.pos.y = glyph.font_impl_ascent
673
674 + format.valign.to_factor() * (max_row_height - glyph.line_height)
676
677 + 0.5 * (glyph.font_height - glyph.font_impl_height);
680
681 glyph.pos.y = point_scale.round_to_pixel(glyph.pos.y);
682 }
683
684 placed_row.pos.y = cursor_y;
685 row.size.y = max_row_height;
686
687 cursor_y += max_row_height;
688 cursor_y = point_scale.round_to_pixel(cursor_y); }
690
691 let format_summary = format_summary(&job);
692
693 let mut rect = Rect::ZERO;
694 let mut mesh_bounds = Rect::NOTHING;
695 let mut num_vertices = 0;
696 let mut num_indices = 0;
697
698 for placed_row in &mut rows {
699 rect |= placed_row.rect();
700
701 let row = Arc::make_mut(&mut placed_row.row);
702 row.visuals = tessellate_row(point_scale, &job, &format_summary, row);
703
704 mesh_bounds |= row.visuals.mesh_bounds.translate(placed_row.pos.to_vec2());
705 num_vertices += row.visuals.mesh.vertices.len();
706 num_indices += row.visuals.mesh.indices.len();
707
708 row.section_index_at_start = u32::MAX; for glyph in &mut row.glyphs {
710 glyph.section_index = u32::MAX; }
712 }
713
714 let mut galley = Galley {
715 job,
716 rows,
717 elided,
718 rect,
719 mesh_bounds,
720 num_vertices,
721 num_indices,
722 pixels_per_point: point_scale.pixels_per_point,
723 intrinsic_size,
724 };
725
726 if galley.job.round_output_to_gui {
727 galley.round_output_to_gui();
728 }
729
730 galley
731}
732
733#[derive(Default)]
734struct FormatSummary {
735 any_background: bool,
736 any_underline: bool,
737 any_strikethrough: bool,
738}
739
740fn format_summary(job: &LayoutJob) -> FormatSummary {
741 let mut format_summary = FormatSummary::default();
742 for section in &job.sections {
743 format_summary.any_background |= section.format.background != Color32::TRANSPARENT;
744 format_summary.any_underline |= section.format.underline != Stroke::NONE;
745 format_summary.any_strikethrough |= section.format.strikethrough != Stroke::NONE;
746 }
747 format_summary
748}
749
750fn tessellate_row(
751 point_scale: PointScale,
752 job: &LayoutJob,
753 format_summary: &FormatSummary,
754 row: &mut Row,
755) -> RowVisuals {
756 if row.glyphs.is_empty() {
757 return Default::default();
758 }
759
760 let mut mesh = Mesh::default();
761
762 mesh.reserve_triangles(row.glyphs.len() * 2);
763 mesh.reserve_vertices(row.glyphs.len() * 4);
764
765 if format_summary.any_background {
766 add_row_backgrounds(point_scale, job, row, &mut mesh);
767 }
768
769 let glyph_index_start = mesh.indices.len();
770 let glyph_vertex_start = mesh.vertices.len();
771 tessellate_glyphs(point_scale, job, row, &mut mesh);
772 let glyph_vertex_end = mesh.vertices.len();
773
774 if format_summary.any_underline {
775 add_row_hline(point_scale, row, &mut mesh, |glyph| {
776 let format = &job.sections[glyph.section_index as usize].format;
777 let stroke = format.underline;
778 let y = glyph.logical_rect().bottom();
779 (stroke, y)
780 });
781 }
782
783 if format_summary.any_strikethrough {
784 add_row_hline(point_scale, row, &mut mesh, |glyph| {
785 let format = &job.sections[glyph.section_index as usize].format;
786 let stroke = format.strikethrough;
787 let y = glyph.logical_rect().center().y;
788 (stroke, y)
789 });
790 }
791
792 let mesh_bounds = mesh.calc_bounds();
793
794 RowVisuals {
795 mesh,
796 mesh_bounds,
797 glyph_index_start,
798 glyph_vertex_range: glyph_vertex_start..glyph_vertex_end,
799 }
800}
801
802fn add_row_backgrounds(point_scale: PointScale, job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
805 if row.glyphs.is_empty() {
806 return;
807 }
808
809 let mut end_run = |start: Option<(Color32, Rect, f32)>, stop_x: f32| {
810 if let Some((color, start_rect, expand)) = start {
811 let rect = Rect::from_min_max(start_rect.left_top(), pos2(stop_x, start_rect.bottom()));
812 let rect = rect.expand(expand);
813 let rect = rect.round_to_pixels(point_scale.pixels_per_point());
814 mesh.add_colored_rect(rect, color);
815 }
816 };
817
818 let mut run_start = None;
819 let mut last_rect = Rect::NAN;
820
821 for glyph in &row.glyphs {
822 let format = &job.sections[glyph.section_index as usize].format;
823 let color = format.background;
824 let rect = glyph.logical_rect();
825
826 if color == Color32::TRANSPARENT {
827 end_run(run_start.take(), last_rect.right());
828 } else if let Some((existing_color, start, expand)) = run_start {
829 if existing_color == color
830 && start.top() == rect.top()
831 && start.bottom() == rect.bottom()
832 && format.expand_bg == expand
833 {
834 } else {
836 end_run(run_start.take(), last_rect.right());
837 run_start = Some((color, rect, format.expand_bg));
838 }
839 } else {
840 run_start = Some((color, rect, format.expand_bg));
841 }
842
843 last_rect = rect;
844 }
845
846 end_run(run_start.take(), last_rect.right());
847}
848
849fn tessellate_glyphs(point_scale: PointScale, job: &LayoutJob, row: &mut Row, mesh: &mut Mesh) {
850 for glyph in &mut row.glyphs {
851 glyph.first_vertex = mesh.vertices.len() as u32;
852 let uv_rect = glyph.uv_rect;
853 if !uv_rect.is_nothing() {
854 let mut left_top = glyph.pos + uv_rect.offset;
855 left_top.x = point_scale.round_to_pixel(left_top.x);
856 left_top.y = point_scale.round_to_pixel(left_top.y);
857
858 let rect = Rect::from_min_max(left_top, left_top + uv_rect.size);
859 let uv = Rect::from_min_max(
860 pos2(uv_rect.min[0] as f32, uv_rect.min[1] as f32),
861 pos2(uv_rect.max[0] as f32, uv_rect.max[1] as f32),
862 );
863
864 let format = &job.sections[glyph.section_index as usize].format;
865
866 let color = format.color;
867
868 if format.italics {
869 let idx = mesh.vertices.len() as u32;
870 mesh.add_triangle(idx, idx + 1, idx + 2);
871 mesh.add_triangle(idx + 2, idx + 1, idx + 3);
872
873 let top_offset = rect.height() * 0.25 * Vec2::X;
874
875 mesh.vertices.push(Vertex {
876 pos: rect.left_top() + top_offset,
877 uv: uv.left_top(),
878 color,
879 });
880 mesh.vertices.push(Vertex {
881 pos: rect.right_top() + top_offset,
882 uv: uv.right_top(),
883 color,
884 });
885 mesh.vertices.push(Vertex {
886 pos: rect.left_bottom(),
887 uv: uv.left_bottom(),
888 color,
889 });
890 mesh.vertices.push(Vertex {
891 pos: rect.right_bottom(),
892 uv: uv.right_bottom(),
893 color,
894 });
895 } else {
896 mesh.add_rect_with_uv(rect, uv, color);
897 }
898 }
899 }
900}
901
902fn add_row_hline(
904 point_scale: PointScale,
905 row: &Row,
906 mesh: &mut Mesh,
907 stroke_and_y: impl Fn(&Glyph) -> (Stroke, f32),
908) {
909 let mut path = crate::tessellator::Path::default(); let mut end_line = |start: Option<(Stroke, Pos2)>, stop_x: f32| {
912 if let Some((stroke, start)) = start {
913 let stop = pos2(stop_x, start.y);
914 path.clear();
915 path.add_line_segment([start, stop]);
916 let feathering = 1.0 / point_scale.pixels_per_point();
917 path.stroke_open(feathering, &PathStroke::from(stroke), mesh);
918 }
919 };
920
921 let mut line_start = None;
922 let mut last_right_x = f32::NAN;
923
924 for glyph in &row.glyphs {
925 let (stroke, mut y) = stroke_and_y(glyph);
926 stroke.round_center_to_pixel(point_scale.pixels_per_point, &mut y);
927
928 if stroke.is_empty() {
929 end_line(line_start.take(), last_right_x);
930 } else if let Some((existing_stroke, start)) = line_start {
931 if existing_stroke == stroke && start.y == y {
932 } else {
934 end_line(line_start.take(), last_right_x);
935 line_start = Some((stroke, pos2(glyph.pos.x, y)));
936 }
937 } else {
938 line_start = Some((stroke, pos2(glyph.pos.x, y)));
939 }
940
941 last_right_x = glyph.max_x();
942 }
943
944 end_line(line_start.take(), last_right_x);
945}
946
947#[derive(Clone, Copy, Default)]
952struct RowBreakCandidates {
953 space: Option<usize>,
956
957 cjk: Option<usize>,
959
960 pre_cjk: Option<usize>,
962
963 dash: Option<usize>,
966
967 punctuation: Option<usize>,
970
971 any: Option<usize>,
974}
975
976impl RowBreakCandidates {
977 fn add(&mut self, index: usize, glyphs: &[Glyph]) {
978 let chr = glyphs[0].chr;
979 const NON_BREAKING_SPACE: char = '\u{A0}';
980 if chr.is_whitespace() && chr != NON_BREAKING_SPACE {
981 self.space = Some(index);
982 } else if is_cjk(chr) && (glyphs.len() == 1 || is_cjk_break_allowed(glyphs[1].chr)) {
983 self.cjk = Some(index);
984 } else if chr == '-' {
985 self.dash = Some(index);
986 } else if chr.is_ascii_punctuation() {
987 self.punctuation = Some(index);
988 } else if glyphs.len() > 1 && is_cjk(glyphs[1].chr) {
989 self.pre_cjk = Some(index);
990 }
991 self.any = Some(index);
992 }
993
994 fn word_boundary(&self) -> Option<usize> {
995 [self.space, self.cjk, self.pre_cjk]
996 .into_iter()
997 .max()
998 .flatten()
999 }
1000
1001 fn has_good_candidate(&self, break_anywhere: bool) -> bool {
1002 if break_anywhere {
1003 self.any.is_some()
1004 } else {
1005 self.word_boundary().is_some()
1006 }
1007 }
1008
1009 fn get(&self, break_anywhere: bool) -> Option<usize> {
1010 if break_anywhere {
1011 self.any
1012 } else {
1013 self.word_boundary()
1014 .or(self.dash)
1015 .or(self.punctuation)
1016 .or(self.any)
1017 }
1018 }
1019
1020 fn forget_before_idx(&mut self, index: usize) {
1021 let Self {
1022 space,
1023 cjk,
1024 pre_cjk,
1025 dash,
1026 punctuation,
1027 any,
1028 } = self;
1029 if space.is_some_and(|s| s < index) {
1030 *space = None;
1031 }
1032 if cjk.is_some_and(|s| s < index) {
1033 *cjk = None;
1034 }
1035 if pre_cjk.is_some_and(|s| s < index) {
1036 *pre_cjk = None;
1037 }
1038 if dash.is_some_and(|s| s < index) {
1039 *dash = None;
1040 }
1041 if punctuation.is_some_and(|s| s < index) {
1042 *punctuation = None;
1043 }
1044 if any.is_some_and(|s| s < index) {
1045 *any = None;
1046 }
1047 }
1048}
1049
1050#[cfg(test)]
1053mod tests {
1054 use crate::AlphaFromCoverage;
1055
1056 use super::{super::*, *};
1057
1058 #[test]
1059 fn test_zero_max_width() {
1060 let pixels_per_point = 1.0;
1061 let mut fonts = FontsImpl::new(
1062 1024,
1063 AlphaFromCoverage::default(),
1064 FontDefinitions::default(),
1065 );
1066 let mut layout_job = LayoutJob::single_section("W".into(), TextFormat::default());
1067 layout_job.wrap.max_width = 0.0;
1068 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1069 assert_eq!(galley.rows.len(), 1);
1070 }
1071
1072 #[test]
1073 fn test_truncate_with_newline() {
1074 let pixels_per_point = 1.0;
1077
1078 let mut fonts = FontsImpl::new(
1079 1024,
1080 AlphaFromCoverage::default(),
1081 FontDefinitions::default(),
1082 );
1083 let text_format = TextFormat {
1084 font_id: FontId::monospace(12.0),
1085 ..Default::default()
1086 };
1087
1088 for text in ["Hello\nworld", "\nfoo"] {
1089 for break_anywhere in [false, true] {
1090 for max_width in [0.0, 5.0, 10.0, 20.0, f32::INFINITY] {
1091 let mut layout_job =
1092 LayoutJob::single_section(text.into(), text_format.clone());
1093 layout_job.wrap.max_width = max_width;
1094 layout_job.wrap.max_rows = 1;
1095 layout_job.wrap.break_anywhere = break_anywhere;
1096
1097 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1098
1099 assert!(galley.elided);
1100 assert_eq!(galley.rows.len(), 1);
1101 let row_text = galley.rows[0].text();
1102 assert!(
1103 row_text.ends_with('…'),
1104 "Expected row to end with `…`, got {row_text:?} when line-breaking the text {text:?} with max_width {max_width} and break_anywhere {break_anywhere}.",
1105 );
1106 }
1107 }
1108 }
1109
1110 {
1111 let mut layout_job = LayoutJob::single_section("Hello\nworld".into(), text_format);
1112 layout_job.wrap.max_width = 50.0;
1113 layout_job.wrap.max_rows = 1;
1114 layout_job.wrap.break_anywhere = false;
1115
1116 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1117
1118 assert!(galley.elided);
1119 assert_eq!(galley.rows.len(), 1);
1120 let row_text = galley.rows[0].text();
1121 assert_eq!(row_text, "Hello…");
1122 }
1123 }
1124
1125 #[test]
1126 fn test_cjk() {
1127 let pixels_per_point = 1.0;
1128 let mut fonts = FontsImpl::new(
1129 1024,
1130 AlphaFromCoverage::default(),
1131 FontDefinitions::default(),
1132 );
1133 let mut layout_job = LayoutJob::single_section(
1134 "日本語とEnglishの混在した文章".into(),
1135 TextFormat::default(),
1136 );
1137 layout_job.wrap.max_width = 90.0;
1138 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1139 assert_eq!(
1140 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1141 vec!["日本語と", "Englishの混在", "した文章"]
1142 );
1143 }
1144
1145 #[test]
1146 fn test_pre_cjk() {
1147 let pixels_per_point = 1.0;
1148 let mut fonts = FontsImpl::new(
1149 1024,
1150 AlphaFromCoverage::default(),
1151 FontDefinitions::default(),
1152 );
1153 let mut layout_job = LayoutJob::single_section(
1154 "日本語とEnglishの混在した文章".into(),
1155 TextFormat::default(),
1156 );
1157 layout_job.wrap.max_width = 110.0;
1158 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1159 assert_eq!(
1160 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1161 vec!["日本語とEnglish", "の混在した文章"]
1162 );
1163 }
1164
1165 #[test]
1166 fn test_truncate_width() {
1167 let pixels_per_point = 1.0;
1168 let mut fonts = FontsImpl::new(
1169 1024,
1170 AlphaFromCoverage::default(),
1171 FontDefinitions::default(),
1172 );
1173 let mut layout_job =
1174 LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default());
1175 layout_job.wrap.max_width = f32::INFINITY;
1176 layout_job.wrap.max_rows = 1;
1177 layout_job.round_output_to_gui = false;
1178 let galley = layout(&mut fonts, pixels_per_point, layout_job.into());
1179 assert!(galley.elided);
1180 assert_eq!(
1181 galley.rows.iter().map(|row| row.text()).collect::<Vec<_>>(),
1182 vec!["# DNA…"]
1183 );
1184 let row = &galley.rows[0];
1185 assert_eq!(row.pos, Pos2::ZERO);
1186 assert_eq!(row.rect().max.x, row.glyphs.last().unwrap().max_x());
1187 }
1188
1189 #[test]
1190 fn test_empty_row() {
1191 let pixels_per_point = 1.0;
1192 let mut fonts = FontsImpl::new(
1193 1024,
1194 AlphaFromCoverage::default(),
1195 FontDefinitions::default(),
1196 );
1197
1198 let font_id = FontId::default();
1199 let font_height = fonts
1200 .font(&font_id.family)
1201 .scaled_metrics(pixels_per_point, font_id.size)
1202 .row_height;
1203
1204 let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY);
1205
1206 let galley = layout(&mut fonts, pixels_per_point, job.into());
1207
1208 assert_eq!(galley.rows.len(), 1, "Expected one row");
1209 assert_eq!(
1210 galley.rows[0].row.glyphs.len(),
1211 0,
1212 "Expected no glyphs in the empty row"
1213 );
1214 assert_eq!(
1215 galley.size(),
1216 Vec2::new(0.0, font_height.round()),
1217 "Unexpected galley size"
1218 );
1219 assert_eq!(
1220 galley.intrinsic_size(),
1221 Vec2::new(0.0, font_height.round()),
1222 "Unexpected intrinsic size"
1223 );
1224 }
1225
1226 #[test]
1227 fn test_end_with_newline() {
1228 let pixels_per_point = 1.0;
1229 let mut fonts = FontsImpl::new(
1230 1024,
1231 AlphaFromCoverage::default(),
1232 FontDefinitions::default(),
1233 );
1234
1235 let font_id = FontId::default();
1236 let font_height = fonts
1237 .font(&font_id.family)
1238 .scaled_metrics(pixels_per_point, font_id.size)
1239 .row_height;
1240
1241 let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY);
1242
1243 let galley = layout(&mut fonts, pixels_per_point, job.into());
1244
1245 assert_eq!(galley.rows.len(), 2, "Expected two rows");
1246 assert_eq!(
1247 galley.rows[1].row.glyphs.len(),
1248 0,
1249 "Expected no glyphs in the empty row"
1250 );
1251 assert_eq!(
1252 galley.size().round(),
1253 Vec2::new(17.0, font_height.round() * 2.0),
1254 "Unexpected galley size"
1255 );
1256 assert_eq!(
1257 galley.intrinsic_size().round(),
1258 Vec2::new(17.0, font_height.round() * 2.0),
1259 "Unexpected intrinsic size"
1260 );
1261 }
1262}