//! Independent complex-script formatting through style inheritance and DOCX round trips. //! Synthetic packages, written here; no external documents or installed fonts are needed. use std::io::Write; use wordcraft_doc::{Document, Paragraph}; fn package(run: &str, defaults: &str, styles: &str, paragraph_style: &str) -> Vec { let ns = r#"xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main""#; let root = r#""#; let rels = r#""#; let document = format!( r#"{paragraph_style}{run}שָׁלוֹם ABC 123"# ); let styles = format!("{defaults}{styles}"); let mut z = zip::ZipWriter::new(std::io::Cursor::new(Vec::new())); for (name, data) in [ ("_rels/.rels", root), ("word/_rels/document.xml.rels", rels), ("word/document.xml", document.as_str()), ("word/styles.xml", styles.as_str()), ] { z.start_file(name, zip::write::SimpleFileOptions::default()).unwrap(); z.write_all(data.as_bytes()).unwrap(); } z.finish().unwrap().into_inner() } fn paragraph(d: &Document) -> &Paragraph { d.body.iter().find_map(|b| b.as_para()).unwrap() } fn check_generations(bytes: &[u8], check: impl Fn(&Document)) { let mut d = wordcraft_docx::read(bytes).unwrap(); for _ in 0..3 { check(&d); let next = wordcraft_docx::read(&wordcraft_docx::write(&d).unwrap()).unwrap(); assert_eq!(paragraph(&next).text, paragraph(&d).text); assert_eq!(paragraph(&next).runs, paragraph(&d).runs); assert_eq!(paragraph(&next).props.bidi, Some(true)); d = next; } } #[test] fn separate_hebrew_font_size_language_and_flags_survive() { let run = r#""#; check_generations(&package(run, "", "", ""), |d| { let c = ¶graph(d).runs[0].props; assert_eq!((c.font.as_deref(), c.font_cs.as_deref()), (Some("Source Serif 4"), Some("Arial Hebrew"))); assert_eq!((c.size, c.size_cs), (Some(12.0), Some(24.0))); assert_eq!((c.bold, c.bold_cs, c.italic, c.italic_cs), (Some(false), Some(true), Some(false), Some(true))); assert_eq!((c.rtl, c.cs), (Some(true), Some(true))); assert_eq!((c.lang.as_deref(), c.lang_bidi.as_deref()), (Some("en-US"), Some("he-IL"))); }); } #[test] fn latin_direct_properties_leave_complex_script_defaults_inherited() { let defaults = r#""#; let run = r#""#; check_generations(&package(run, defaults, "", ""), |d| { let p = paragraph(d); let c = &p.runs[0].props; assert_eq!((c.size_cs, c.bold_cs, c.italic_cs), (None, None, None), "Absent direct values must remain absent when saved"); let resolved = d.styles.resolve_char(p.props.style.as_deref(), c); assert_eq!((resolved.size, resolved.bold, resolved.italic), (10.0, false, false)); let cs = resolved.complex(); assert_eq!((cs.size, cs.bold, cs.italic), (18.0, true, true)); }); } #[test] fn latin_style_properties_leave_complex_script_style_chain_inherited() { let styles = r#" "#; let run = r#""#; check_generations(&package(run, "", styles, r#""#), |d| { let p = paragraph(d); let c = d.styles.resolve_char(p.props.style.as_deref(), &p.runs[0].props); assert_eq!((c.font.as_str(), c.size, c.bold, c.italic), ("Source Serif 4", 10.0, false, false)); let cs = c.complex(); assert_eq!((cs.font.as_str(), cs.size, cs.bold, cs.italic), ("Arial Hebrew", 18.0, true, true)); assert_eq!(d.styles.get("HebrewBase").unwrap().chr.lang_bidi.as_deref(), Some("he-IL")); }); } #[test] fn equal_explicit_complex_script_values_are_not_discarded() { let run = r#""#; let defaults = r#""#; check_generations(&package(run, defaults, "", ""), |d| { let p = paragraph(d); let c = &p.runs[0].props; assert_eq!((c.size_cs, c.bold_cs, c.italic_cs), (Some(10.0), Some(false), Some(false))); let cs = d.styles.resolve_char(p.props.style.as_deref(), c).complex(); assert_eq!((cs.size, cs.bold, cs.italic), (10.0, false, false)); }); } #[test] fn complex_script_direct_properties_leave_latin_defaults_inherited() { let run = r#""#; let defaults = r#""#; check_generations(&package(run, defaults, "", ""), |d| { let p = paragraph(d); let c = &p.runs[0].props; assert_eq!((c.size, c.bold, c.italic), (None, None, None)); assert_eq!((c.rtl, c.cs), (Some(false), Some(false))); let resolved = d.styles.resolve_char(p.props.style.as_deref(), c); assert_eq!((resolved.size, resolved.bold, resolved.italic), (18.0, true, true)); let cs = resolved.complex(); assert_eq!((cs.size, cs.bold, cs.italic), (10.0, false, false)); }); } #[test] fn tracked_formatting_keeps_independent_complex_script_values() { let defaults = r#""#; let run = r#""#; check_generations(&package(run, defaults, "", ""), |d| { let p = paragraph(d); let c = &p.runs[0].props; let current = d.styles.resolve_char(None, c).complex(); assert_eq!((current.size, current.bold, current.italic), (18.0, true, true)); let change = c.fmt_change.as_ref().unwrap(); assert_eq!((change.old.size_cs, change.old.bold_cs, change.old.italic_cs), (Some(10.0), Some(false), Some(false))); let old = d.styles.resolve_char(None, &change.old).complex(); assert_eq!((old.size, old.bold, old.italic), (10.0, false, false)); let mut overlaid = wordcraft_doc::props::CharProps::default(); overlaid.overlay(c); assert_eq!(overlaid.fmt_change, c.fmt_change); }); }