craft-docx: a field's Thai result takes the complex-script formatting only where Word does

Measured in Word for Windows: Thai text as a field's result follows the rule ordinary text does. mark_runs_as_word_draws_them counts the result of a field kept as one object as the text of its run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pakiatandClaude Opus 5.5 committed 2026-11-14 11:52:27 +00:00
1 parent 24907cbd7a
commit 9a5a35334a
2 files changed
+55 -4

No files matched your search

+37
View File
@@ -290,3 +290,40 @@ fn thai_text_takes_the_complex_script_formatting_only_in_a_complex_script_run()
assert_eq!(drawn, [("Angsana New".to_string(), 22.0), ("Tahoma".to_string(), 11.0), ("Angsana New".to_string(), 22.0)], "{when}");
}
}
/// Measured in Word for Windows: Thai text as a field's result follows the same rule as ordinary
/// text, also when the reader keeps the field as one object with its result.
#[test]
fn a_fields_thai_result_takes_the_complex_script_formatting_only_in_a_complex_script_run() {
let styles = r#"<w:docDefaults><w:rPrDefault><w:rPr><w:rFonts w:ascii="Angsana New" w:hAnsi="Angsana New" w:cs="Tahoma"/><w:sz w:val="20"/><w:szCs w:val="40"/></w:rPr></w:rPrDefault></w:docDefaults>
<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/></w:style>"#;
let field = |rpr: &str| {
let r = |inner: &str| format!("<w:r><w:rPr>{rpr}</w:rPr>{inner}</w:r>");
format!(
"<w:p>{}{}{}{}{}</w:p>",
r(r#"<w:fldChar w:fldCharType="begin"/>"#),
r(r#"<w:instrText xml:space="preserve"> QUOTE "x" </w:instrText>"#),
r(r#"<w:fldChar w:fldCharType="separate"/>"#),
r("<w:t>ทดสอบ</w:t>"),
r(r#"<w:fldChar w:fldCharType="end"/>"#)
)
};
let drawn = |d: &Document| {
let p = paras(d)[0];
let at = p.text.find(craft_wordmodel::para::OBJ).expect("the field is one object");
let run = p.runs.iter().scan(0, |end, r| {
let start = *end;
*end += r.len;
Some((start, r))
});
let (_, run) = run.into_iter().find(|(start, r)| *start <= at && at < start + r.len).unwrap();
let rc = d.styles.resolve_char(p.props.style.as_deref(), &run.props);
if rc.uses_complex('ท') { (rc.font_cs.clone(), rc.size_cs) } else { (rc.font.clone(), rc.size) }
};
for (what, rpr, want) in [("nothing", "", ("Angsana New", 10.0)), ("w:cs", "<w:cs/>", ("Tahoma", 20.0))] {
let d = craft_docx::read(&docx_with_styles(&field(rpr), styles)).unwrap();
assert_eq!(drawn(&d), (want.0.to_string(), want.1), "a run with {what}: read");
let again = craft_docx::read(&craft_docx::write(&d).unwrap()).unwrap();
assert_eq!(drawn(&again), (want.0.to_string(), want.1), "a run with {what}: after a save");
}
}
+18 -4
View File
@@ -841,11 +841,25 @@ impl StyleSheet {
/// right to left.
pub fn mark_runs_as_word_draws_them(&self, para: &mut crate::para::Paragraph, table: Option<&CharProps>) {
use crate::bidi::{is_complex_script, is_rtl_script};
let style = para.props.style.as_deref();
let mut at = 0;
for run in &mut para.runs {
let text = para.text.get(at..at + run.len).unwrap_or("");
use crate::para::{InlineObject, OBJ};
// What each run draws: its text, a field kept as one object standing for its result
// (drawn in the run's formatting, and measured in Word to follow the same rule).
let mut drawn = Vec::with_capacity(para.runs.len());
let (mut at, mut objects) = (0, para.objects.iter());
for run in &para.runs {
let mut text = String::new();
for c in para.text.get(at..at + run.len).unwrap_or("").chars() {
if c != OBJ {
text.push(c);
} else if let Some(InlineObject::Field { result, code: false, .. }) = objects.next() {
text.push_str(result);
}
}
at += run.len;
drawn.push(text);
}
let style = para.props.style.as_deref();
for (run, text) in para.runs.iter_mut().zip(&drawn) {
if text.is_empty() || text.chars().any(is_rtl_script) {
continue;
}