diff --git a/crates/textimport/src/rtf.rs b/crates/textimport/src/rtf.rs
--- a/crates/textimport/src/rtf.rs
+++ b/crates/textimport/src/rtf.rs
@@ -120,13 +120,15 @@ pub fn import(bytes: &[u8]) -> Result<Imported, ImportError> {
                 let next = chars.get(i + 1).copied().unwrap_or(' ');
                 if next == '\'' {
                     // \'hh: a Windows-1252 byte.
-                    let hex: String = chars.iter().skip(i + 2).take(2).collect();
-                    i += 4;
+                    // Only valid hex digits are consumed; anything else is left for the main loop.
+                    let hex: String = chars.iter().skip(i + 2).take(2).take_while(|c| c.is_ascii_hexdigit()).collect();
+                    i += 2 + hex.len();
                     if pending > 0 {
                         pending -= 1;
                         continue;
                     }
-                    if let Ok(b) = u8::from_str_radix(&hex, 16)
+                    if hex.len() == 2
+                        && let Ok(b) = u8::from_str_radix(&hex, 16)
                         && !stack.last().is_some_and(|s| s.skip)
                     {
                         units.reset();
@@ -302,6 +304,15 @@ mod tests {
         assert_eq!(import(br"{\rtf1 a\u-4294967231?b}").unwrap().story.text, "ab");
     }
 
+    /// A short or invalid `\'hh` escape consumes only its valid hex digits.
+    #[test]
+    fn short_hex_escape_keeps_closing_brace() {
+        let i = import(br"{\rtf1 {\b A\'4}B}").unwrap();
+        assert_eq!(i.story.text, "AB");
+        assert_eq!(i.story.format_after(1).over.font_style, None);
+        assert_eq!(import(br"{\rtf1 a\'+fb}").unwrap().story.text, "a+fb");
+    }
+
     #[test]
     fn imports_rtf_text_and_formatting() {
         let rtf = br"{\rtf1\ansi{\fonttbl{\f0 Times;}}{\colortbl;\red0\green0\blue0;}{\*\generator Test;}
