From 8806a223fa5bdaffe3e4e2470a06775e136559c2 Mon Sep 17 00:00:00 2001 From: aiongg Date: Sat, 10 Oct 2026 23:46:28 +0000 Subject: [PATCH] Integration: shatai glyphs in right-to-left lines draw inside the line span (#392) next's shatai path draws a slanted glyph on its own through tagged_run, which opens a Span; inside #392's right-to-left line span that nested marked content and krilla panicked ("can't start marked content twice") in tagged exports. While a line span is open, the glyph is drawn with run like the line's other runs. Test: shatai_glyphs_in_right_to_left_lines_stay_in_the_line_span (panicked before). --- crates/pdf/src/rtl_tests.rs | 16 ++++++++++++++++ crates/pdf/src/text.rs | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/pdf/src/rtl_tests.rs b/crates/pdf/src/rtl_tests.rs index fc38747a7..82dd97542 100644 --- a/crates/pdf/src/rtl_tests.rs +++ b/crates/pdf/src/rtl_tests.rs @@ -188,3 +188,19 @@ fn arabic_ligatures_extract_their_letters() { assert_eq!(extract_text(&bytes)[0], text); assert_eq!(line_actual_texts(&bytes), [text]); } + +/// A slanted (shatai) glyph in a right-to-left line is drawn inside the line's span, not in a span +/// of its own (marked content can't nest). +#[test] +fn shatai_glyphs_in_right_to_left_lines_stay_in_the_line_span() { + hebrew_font(); + let text = "דוד & שרה"; + let mut d = doc(text, HEBREW, Rect::new(36.0, 36.0, 576.0, 300.0), rtl()); + let sid = *d.stories.keys().next().unwrap(); + d.story_mut(sid).unwrap().format_chars(0.."דוד".len(), |f| f.over.shatai_magnification = Some(30.0)); + for tagged in [false, true] { + let bytes = export_pdf(&d, &Cache::new(), &PdfOptions { tagged, ..PdfOptions::default() }).unwrap(); + assert_eq!(line_actual_texts(&bytes), [text], "tagged: {tagged}"); + assert_eq!(extract_text(&bytes)[0], text, "tagged: {tagged}"); + } +} diff --git a/crates/pdf/src/text.rs b/crates/pdf/src/text.rs index 1146ba455..c3c4f4be6 100644 --- a/crates/pdf/src/text.rs +++ b/crates/pdf/src/text.rs @@ -241,7 +241,11 @@ impl Exporter<'_> { if turn.is_some() || shatai.is_some() { let m = turn.unwrap_or(Affine::IDENTITY) * shatai.unwrap_or(Affine::IDENTITY); s.push_transform(&crate::export::tf(m)); - self.tagged_run(s, cs, &gs[i..i + 1], l.baseline, story); + if line_span { + self.run(s, cs, &gs[i..i + 1], l.baseline, story); + } else { + self.tagged_run(s, cs, &gs[i..i + 1], l.baseline, story); + } s.pop(); i += 1; continue;