diff --git a/crates/svg/src/import/text.rs b/crates/svg/src/import/text.rs index 1a221a69f1..fad8841e99 100644 --- a/crates/svg/src/import/text.rs +++ b/crates/svg/src/import/text.rs @@ -965,6 +965,12 @@ impl Ctx { "normal".into() } + /// Whether the computed `direction` of element `n` is `rtl`, in any letter case (CSS keywords + /// are ASCII case-insensitive, https://www.w3.org/TR/css-values-3/#keywords). + fn is_rtl(&self, n: XNode) -> bool { + self.prop(n, "direction").is_some_and(|d| d.trim().eq_ignore_ascii_case("rtl")) + } + /// The Character Direction of the characters of element `n` inside a ``, and what it /// leaves out (SVG 1.1 ยง10.7.4, /// https://www.w3.org/TR/SVG11/text.html#RelationshipWithBi-directionality, and CSS Writing @@ -987,7 +993,7 @@ impl Ctx { let inherited = parent.as_ref().and_then(|p| p.style.char_direction); let mut notes = parent.map_or_else(Vec::new, |p| p.bidi_notes.clone()); let rtl = |rtl: bool| Some(if rtl { ParaDirection::RightToLeft } else { ParaDirection::LeftToRight }); - let own = || rtl(self.prop(n, "direction").is_some_and(|d| d.trim() == "rtl")); + let own = || rtl(self.is_rtl(n)); let (dir, note) = match bidi.as_str() { "embed" => (own(), None), "bidi-override" => (own(), Some(BIDI_OVERRIDE)), @@ -1359,7 +1365,7 @@ impl Ctx { // With `unicode-bidi: plaintext`, the text runs in the direction of its first strong // character, and each paragraph in that of its own ([`pin_first_strong`]). let plaintext = self.unicode_bidi(t) == "plaintext"; - let rtl = if plaintext { self.first_strong_rtl(t) == Some(true) } else { self.prop(t, "direction").is_some_and(|d| d.trim() == "rtl") }; + let rtl = if plaintext { self.first_strong_rtl(t) == Some(true) } else { self.is_rtl(t) }; let pin = |obj: &mut TextObject| { pin_direction(obj, rtl); if plaintext { @@ -1386,7 +1392,7 @@ impl Ctx { let chunk: String = cs.chars[i..end].iter().map(|c| c.0).collect(); text_first_strong_rtl(&chunk) == Some(true) }; - let rtl = if plaintext { chunk_rtl() } else { self.prop(el, "direction").is_some_and(|d| d.trim() == "rtl") }; + let rtl = if plaintext { chunk_rtl() } else { self.is_rtl(el) }; if !vertical && rtl { 1.0 - a } else { a } }; let anchor = anchor_of(0); diff --git a/crates/svg/tests/it/import_text.rs b/crates/svg/tests/it/import_text.rs index 472d7517e4..27399ec4ac 100644 --- a/crates/svg/tests/it/import_text.rs +++ b/crates/svg/tests/it/import_text.rs @@ -391,6 +391,9 @@ fn rtl_point_text_start_and_end_use_directional_edges() { (r#"Wave"#, 0.0), (r#"Wave"#, 0.0), (r#"Wave"#, 1.0), + // CSS keywords are case-insensitive. + (r#"Wave"#, 1.0), + (r#"Wave"#, 0.0), // With `unicode-bidi: plaintext`, the direction of the chunk's first strong character. (r#"אבג"#, 1.0), (r#"12 אב"#, 0.0), @@ -494,6 +497,8 @@ fn a_tspan_bidi_embedding_becomes_its_character_direction() { ("ltr", "rtl", "normal", None, false), ("rtl", "ltr", "normal", None, false), ("ltr", "rtl", "EMBED", Some(Rtl), false), + ("ltr", "RTL", "embed", Some(Rtl), false), + ("RTL", "ltr", "embed", Some(Ltr), false), ("ltr", "rtl", "sideways", None, false), ] { let (t, w) = read(&format!( @@ -503,7 +508,7 @@ fn a_tspan_bidi_embedding_becomes_its_character_direction() { assert_eq!(t.plain_text(), "ab 1 2 cd", "{case}: the characters stay in logical order"); assert_eq!(char_direction_of(&t, "1 2"), want, "{case}: {:?}", t.runs); assert_eq!(char_direction_of(&t, "ab"), None, "{case}"); - assert_eq!(t.para_at(0).direction == Some(Rtl), outer == "rtl", "{case}: the paragraph keeps the text's direction"); + assert_eq!(t.para_at(0).direction == Some(Rtl), outer.eq_ignore_ascii_case("rtl"), "{case}: the paragraph keeps the text's direction"); assert_eq!(w.iter().any(|w| w.contains("bidi-override") && w.contains("not supported")), warns, "{case}: {w:?}"); if !warns { assert!(w.is_empty(), "{case}: {w:?}");