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 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
LloydNicholson
2026-10-10 06:56:45 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Brandon Thomas
parent bded38dd23
commit 236208bc0d
2 changed files with 32 additions and 1 deletions
+13 -1
View File
@@ -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)),
+19
View File
@@ -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#"<w:styles {W_NS}>
<w:docDefaults><w:rPrDefault><w:rPr><w:rFonts w:ascii="Calibri" w:eastAsia="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/></w:rPr></w:rPrDefault><w:pPrDefault/></w:docDefaults>
<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/><w:rPr><w:rFonts w:eastAsia="Times New Roman" w:cs="Times New Roman"/></w:rPr></w:style>
</w:styles>"#
);
let body = r#"<w:p><w:r><w:t>Plain</w:t></w:r></w:p><w:p><w:r><w:rPr><w:rFonts w:hint="eastAsia" w:eastAsia="SimSun"/></w:rPr><w:t>漢字</w:t></w:r></w:p>"#;
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#"<w:p><w:r><w:t>First</w:t></w:r><w:br/><w:r><w:t>Second</w:t></w:r><w:cr/><w:br w:type="page"/><w:r><w:t>Third</w:t></w:r></w:p>"#;