From 96b370e0aaafb9943eda4ae3f588262565e49a6d Mon Sep 17 00:00:00 2001 From: gcailly <109429289+gcailly@users.noreply.github.com> Date: Thu, 26 Mar 2026 12:27:11 +0100 Subject: [PATCH] Fix NOTDEF fallback using wrong font face and reset cluster tracking between runs Two bugs in layout_shaped_run: 1. When the shaper returned NOTDEF, glyph_info was resolved via font fallback but the returned FontFaceKey was discarded. The glyph was then allocated with run.font_key (the face that couldn't render it), causing the glyph to silently render as invisible. Now uses the correct fallback font face and its metrics for both allocation and Glyph font_face_height/font_face_ascent. 2. prev_cluster was not reset between runs. Since harfrust cluster values are byte offsets within each run's text, comparing clusters across runs is semantically wrong and could skip extra_letter_spacing at run boundaries (e.g. when both runs start at cluster 0). --- crates/epaint/src/text/text_layout.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 2dfdabe99..bf3026200 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -162,6 +162,7 @@ pub fn layout(fonts: &mut FontsImpl, pixels_per_point: f32, job: Arc) /// Shared context for emitting shaped glyphs into a [`Paragraph`]. struct ShapingContext { pixels_per_point: f32, + font_size: f32, line_height: f32, extra_letter_spacing: f32, section_index: u32, @@ -182,6 +183,10 @@ fn layout_shaped_run( ) { let px_scale = face_metrics.px_scale_factor; + // Reset cluster tracking — cluster values are byte offsets within run_text, + // so they are not comparable across runs. + ctx.prev_cluster = None; + for (info, pos) in glyph_buffer .glyph_infos() .iter() @@ -217,10 +222,16 @@ fn layout_shaped_run( continue; } - let (_, glyph_info) = font.glyph_info(chr); + // Use the fallback font face (not run.font_key which returned NOTDEF). + let (fallback_key, glyph_info) = font.glyph_info(chr); + let fallback_metrics = font + .fonts_by_id + .get(&fallback_key) + .map(|ff| ff.styled_metrics(ctx.pixels_per_point, ctx.font_size, &Default::default())) + .unwrap_or_default(); let (glyph_alloc, physical_x) = - if let Some(ff) = font.fonts_by_id.get_mut(&run.font_key) { - ff.allocate_glyph(font.atlas, face_metrics, glyph_info, chr, paragraph.cursor_x_px) + if let Some(ff) = font.fonts_by_id.get_mut(&fallback_key) { + ff.allocate_glyph(font.atlas, &fallback_metrics, glyph_info, chr, paragraph.cursor_x_px) } else { Default::default() }; @@ -230,8 +241,8 @@ fn layout_shaped_run( pos: pos2(physical_x as f32 / ctx.pixels_per_point, f32::NAN), advance_width: glyph_alloc.advance_width_px / ctx.pixels_per_point, line_height: ctx.line_height, - font_face_height: face_metrics.row_height, - font_face_ascent: face_metrics.ascent, + font_face_height: fallback_metrics.row_height, + font_face_ascent: fallback_metrics.ascent, font_height: ctx.font_metrics.row_height, font_ascent: ctx.font_metrics.ascent, uv_rect: glyph_alloc.uv_rect, @@ -305,6 +316,7 @@ fn layout_section( let section_text = &job.text[byte_range.clone()]; let mut ctx = ShapingContext { pixels_per_point, + font_size, line_height, extra_letter_spacing, section_index,