diff --git a/crates/text/src/lib.rs b/crates/text/src/lib.rs index cd43630..6354d17 100644 --- a/crates/text/src/lib.rs +++ b/crates/text/src/lib.rs @@ -1684,7 +1684,11 @@ fn layout_scaled(ctx: &Ctx, shape: &Shape, body: &TextBody, bp: &BodyProps, opts for c in p.cells.get(rl.start..rl.end.max(rl.start)).unwrap_or(&[]) { if let (Some(st), Some(face)) = (p.styles.get(c.style), p.faces.get(c.face)) { let sz = st.size; - let (a, d) = line_metrics(face, sz); + // The run's own fonts set the line; a fallback face (emoji, a symbol or a + // script the run's fonts lack) does not move the baseline in PowerPoint. + let own = Arc::ptr_eq(face, &st.face) + || [st.ea.as_deref(), st.cs.as_deref(), st.sym.as_deref()].into_iter().flatten().any(|f| face.family.eq_ignore_ascii_case(f)); + let (a, d) = line_metrics(if own { face } else { &st.face }, sz); asc = asc.max(a); desc = desc.max(d); size = size.max(sz); diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 65f38ae..0b2c6cc 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -1134,3 +1134,19 @@ fn symbols_after_an_emoji_keep_a_text_face() { } } } + +#[test] +fn fallback_faces_do_not_move_the_baseline() { + // PowerPoint keeps a line's baseline and pitch from the run's own font when an emoji, a symbol + // or a script the font lacks is drawn from another face. + let base = |text: &str| { + let (p, sh) = bidi_setup(vec![Run::new(text)], 2000.0, false, Align::Left); + let l = lay(&p, &sh, 2000.0, 300.0); + (l.lines[0].baseline, l.lines[0].bottom) + }; + let plain = base("Hxg Hxg"); + for text in ["Hxg \u{1F600} Hxg", "Hxg \u{0E2A}\u{0E27}\u{0E31}\u{0E2A}", "Hxg \u{222B}\u{2211}"] { + let b = base(text); + assert!((b.0 - plain.0).abs() < 0.01 && (b.1 - plain.1).abs() < 0.01, "{text:?}: {b:?} vs {plain:?}"); + } +}