From 72d8e0ab42bb1c2892b58e89da5e3b649148d2db Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 12 Oct 2026 17:36:44 -0400 Subject: [PATCH] Draw only the first character of a multi-character bullet (#307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PowerPoint draws buChar char="••" (seen in a LibreOffice test deck) as a single dot; DeckCraft drew both, pushing the text right. Co-authored-by: Claude Opus 5.5 (1M context) --- crates/text/src/lib.rs | 3 ++- crates/text/src/tests.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/text/src/lib.rs b/crates/text/src/lib.rs index 287c442..4cf1b3d 100644 --- a/crates/text/src/lib.rs +++ b/crates/text/src/lib.rs @@ -686,7 +686,8 @@ fn map_symbol_bullet(c: &str, font: Option<&str>) -> String { }; return m.to_string(); } - c.to_string() + // PowerPoint draws only the first character of a longer `buChar` ("••" draws one dot). + c.graphemes(true).next().unwrap_or("•").to_string() } fn measure_str(face: &FontFace, size: f64, s: &str) -> (f64, Vec<(u32, f64)>) { diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 0f6c647..959d614 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -45,6 +45,20 @@ fn bullets_are_drawn_for_body_text() { assert_eq!(l.para_lines.len(), 2); } +#[test] +fn only_the_first_character_of_a_bullet_is_drawn() { + // "••" (seen in the wild) draws one dot in PowerPoint. + for (char, glyphs) in [("\u{2022}\u{2022}", 1), ("\u{2022}", 1), ("-->", 1)] { + let (p, mut sh) = setup("One", 600.0, 300.0); + sh.text.as_mut().unwrap().paragraphs[0].props.bullet = Some(Bullet::Char { char: char.into() }); + let l = lay(&p, &sh, 600.0, 300.0); + let bullet = l.runs.iter().find(|r| r.chars == (0, 0)).expect("bullet run"); + assert_eq!(bullet.glyphs.len(), glyphs, "{char:?}"); + } + // The first grapheme cluster is kept whole. + assert_eq!(map_symbol_bullet("e\u{301}x", None), "e\u{301}"); +} + #[test] fn shrink_on_overflow() { let text = (0..30).map(|i| format!("Line number {i}")).collect::>().join("\n");