From 236208bc0db2586bd6eecdf7a00235efd9db6502 Mon Sep 17 00:00:00 2001 From: LloydNicholson <34131158+LloydNicholson@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:56:45 +0200 Subject: [PATCH] DOCX: a style's East Asian / complex-script font no longer replaces the Latin font (#93) (#111) A Normal style whose rFonts has only w:eastAsia and w:cs (common in Word-made files) set the run font to that face, so text rendered in Times New Roman instead of the docDefaults font. Those attributes now stand in for the Latin font only when the run is hinted as that script (w:hint), and otherwise the Latin font is inherited, as in Word. Co-authored-by: Claude Opus 5.5 Co-authored-by: Brandon Thomas --- crates/docx/src/read/props.rs | 14 +++++++++++++- crates/docx/tests/fixtures.rs | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/docx/src/read/props.rs b/crates/docx/src/read/props.rs index d6d97914e..0ba0132a5 100644 --- a/crates/docx/src/read/props.rs +++ b/crates/docx/src/read/props.rs @@ -42,7 +42,19 @@ impl PropCtx { .filter(|f| !f.is_empty()) .map(str::to_string) .or_else(|| k.attr("w:asciiTheme").or_else(|| k.attr("w:hAnsiTheme")).and_then(|t| self.theme_font(t))) - .or_else(|| k.attr("w:cs").or_else(|| k.attr("w:eastAsia")).filter(|f| !f.is_empty()).map(str::to_string)); + .or_else(|| { + // `w:eastAsia` and `w:cs` name fonts for other scripts; they stand in for the + // Latin font only when the run is hinted as that script. Otherwise the Latin + // font is inherited (a Normal style with only `w:eastAsia`/`w:cs` keeps the + // `docDefaults` font, as in Word). + match k.attr("w:hint") { + Some("eastAsia") => k.attr("w:eastAsia"), + Some("cs") => k.attr("w:cs"), + _ => None, + } + .filter(|f| !f.is_empty()) + .map(str::to_string) + }); } "w:b" => c.bold = Some(on_off(k)), "w:i" => c.italic = Some(on_off(k)), diff --git a/crates/docx/tests/fixtures.rs b/crates/docx/tests/fixtures.rs index 7f1d99b7f..d91d6a8b5 100644 --- a/crates/docx/tests/fixtures.rs +++ b/crates/docx/tests/fixtures.rs @@ -389,6 +389,25 @@ fn header_self_reference_and_escaping_targets() { wordcraft_docx::read(&out).unwrap(); } +#[test] +fn style_rfonts_without_ascii_inherits_doc_defaults_font() { + // Issue #93: Normal names only East Asian / complex-script fonts, so Latin text keeps the + // docDefaults font. A run hinted as East Asian still uses its East Asian font. + let styles = format!( + r#" + + +"# + ); + let body = r#"Plain漢字"#; + let d = wordcraft_docx::read(&docx(body, &[("rId1", "styles", "styles.xml")], &[("word/styles.xml", &styles)])).unwrap(); + assert_eq!(d.styles.get("Normal").unwrap().chr.font, None); + let ps = paras(&d); + let font = |p: &Paragraph| d.styles.resolve_char(p.props.style.as_deref(), &p.runs[0].props).font; + assert_eq!(font(ps[0]), "Calibri"); + assert_eq!(font(ps[1]), "SimSun"); +} + #[test] fn bare_break_directly_in_paragraph() { let body = r#"FirstSecondThird"#;