diff --git a/crates/docx/src/fit.rs b/crates/docx/src/fit.rs index 2af5eadab8..2409f66bbf 100644 --- a/crates/docx/src/fit.rs +++ b/crates/docx/src/fit.rs @@ -20,8 +20,7 @@ pub(crate) const KIND: &str = "wordcraft-fit-v1"; pub(crate) fn write(fit: &FitState) -> Result, DocxError> { let json = serde_json::to_string(fit).map_err(|e| DocxError::Xml(e.to_string()))?; let esc_json = quick_xml::escape::escape(&json); - Ok(format!(r#"<{ROOT} xmlns="{NS}" kind="{KIND}">{}"#, esc_json) - .into_bytes()) + Ok(format!(r#"<{ROOT} xmlns="{NS}" kind="{KIND}">{}"#, esc_json).into_bytes()) } /// Read the state from a custom XML part's bytes, if it is ours. diff --git a/crates/docx/src/lib.rs b/crates/docx/src/lib.rs index ac8f42dbed..41ba32991d 100644 --- a/crates/docx/src/lib.rs +++ b/crates/docx/src/lib.rs @@ -8,10 +8,9 @@ mod fit; mod model_bridge; pub use craft_docx::{ - DEFAULT_SPIN_COUNT, DocxError, EmbeddedFont, Flavor, MAX_PASSWORD_CHARS, MAX_SOURCES, - MAX_XML_FILE, PASSWORD_REQUIRED, PasswordCheck, WRONG_PASSWORD, XmlKind, check_password, - check_protection_password, encrypt, encrypt_standard_for_tests, - encrypt_with_spin_count, package_title, protection_hash, sniff_xml, + DEFAULT_SPIN_COUNT, DocxError, EmbeddedFont, Flavor, MAX_PASSWORD_CHARS, MAX_SOURCES, MAX_XML_FILE, PASSWORD_REQUIRED, PasswordCheck, + WRONG_PASSWORD, XmlKind, check_password, check_protection_password, encrypt, encrypt_standard_for_tests, encrypt_with_spin_count, package_title, + protection_hash, sniff_xml, }; pub fn decrypt(bytes: &[u8], password: Option<&str>) -> Result, DocxError> { @@ -56,19 +55,13 @@ pub fn read_with_password(bytes: &[u8], password: Option<&str>) -> Result Result { let bridge_importer = |shared_chunk: &craft_docx::AltChunk| -> Option { let importer = opts.chunks?; - let chunk = AltChunk { - content_type: shared_chunk.content_type, - part: shared_chunk.part, - bytes: shared_chunk.bytes, - }; + let chunk = AltChunk { content_type: shared_chunk.content_type, part: shared_chunk.part, bytes: shared_chunk.bytes }; let doc = importer(&chunk)?; to_shared_model(&doc).ok() }; - let shared_opts = craft_docx::ReadOptions { - password: opts.password, - chunks: opts.chunks.as_ref().map(|_| &bridge_importer as craft_docx::ChunkImporter), - }; + let shared_opts = + craft_docx::ReadOptions { password: opts.password, chunks: opts.chunks.as_ref().map(|_| &bridge_importer as craft_docx::ChunkImporter) }; let shared = craft_docx::read_with(bytes, &shared_opts)?; from_shared_model(shared) @@ -105,21 +98,22 @@ pub fn write_wordml2003(doc: &wordcraft_doc::Document) -> Result, DocxEr } /// Draw a chart. -pub fn chart_items( - spec: &wordcraft_doc::chart::ChartSpec, - theme: &[wordcraft_doc::Rgb], - w: f32, - h: f32, -) -> Vec { - let shared_spec: craft_wordmodel::chart::ChartSpec = model_bridge::convert_ref(spec).expect("chart spec convert"); - let shared_theme: Vec = model_bridge::convert_ref(theme).expect("theme convert"); +pub fn chart_items(spec: &wordcraft_doc::chart::ChartSpec, theme: &[wordcraft_doc::Rgb], w: f32, h: f32) -> Vec { + let (Ok(shared_spec), Ok(shared_theme)) = ( + model_bridge::convert_ref::<_, craft_wordmodel::chart::ChartSpec>(spec), + model_bridge::convert_ref::<_, Vec>(theme), + ) else { + return Vec::new(); + }; let shared_items = craft_docx::chart_items(&shared_spec, &shared_theme, w, h); - model_bridge::convert(shared_items).expect("chart items convert") + model_bridge::convert(shared_items).unwrap_or_default() } /// Write a chart part. pub fn chart_xml(spec: &wordcraft_doc::chart::ChartSpec) -> Vec { - let shared_spec: craft_wordmodel::chart::ChartSpec = model_bridge::convert_ref(spec).expect("chart spec convert"); + let Ok(shared_spec) = model_bridge::convert_ref(spec) else { + return Vec::new(); + }; craft_docx::chart_xml(&shared_spec).into_bytes() } @@ -130,19 +124,20 @@ pub fn smart_art_items( w: f32, h: f32, ) -> Vec { - let shared_spec: craft_wordmodel::smart_art::SmartArtSpec = model_bridge::convert_ref(spec).expect("smart art spec convert"); - let shared_theme: Vec = model_bridge::convert_ref(theme).expect("theme convert"); + let (Ok(shared_spec), Ok(shared_theme)) = ( + model_bridge::convert_ref::<_, craft_wordmodel::smart_art::SmartArtSpec>(spec), + model_bridge::convert_ref::<_, Vec>(theme), + ) else { + return Vec::new(); + }; let shared_items = craft_docx::smart_art_items(&shared_spec, &shared_theme, w, h); - model_bridge::convert(shared_items).expect("smart art items convert") + model_bridge::convert(shared_items).unwrap_or_default() } /// Read bibliography sources from a `b:Sources` file. pub fn read_sources(bytes: &[u8]) -> Result { let shared = craft_docx::read_sources(bytes)?; - Ok(Sources { - sources: model_bridge::convert(shared.sources).map_err(|e| DocxError::Xml(e.to_string()))?, - style: shared.style, - }) + Ok(Sources { sources: model_bridge::convert(shared.sources).map_err(|e| DocxError::Xml(e.to_string()))?, style: shared.style }) } /// The font files embedded in the document's font table. diff --git a/crates/docx/src/model_bridge.rs b/crates/docx/src/model_bridge.rs index e6fed956f8..20bf0bce80 100644 --- a/crates/docx/src/model_bridge.rs +++ b/crates/docx/src/model_bridge.rs @@ -64,16 +64,7 @@ fn object_value_from_shared(source: craft_wordmodel::para::InlineObject) -> Resu use wordcraft_doc::para::InlineObject as Target; match source { - Source::Image { - media, - w, - h, - alt, - float, - crop, - fx, - ole, - } => Ok(Target::Image { + Source::Image { media, w, h, alt, float, crop, fx, ole } => Ok(Target::Image { media, w, h, @@ -156,13 +147,12 @@ fn missing_pictures_from_shared(source: craft_wordmodel::MissingPictures) -> Res pub fn from_shared_model(mut source: craft_wordmodel::Document) -> Result { let mut fit_state = None; let fit_key = source.passthrough.keys().find(|k| k.eq_ignore_ascii_case(crate::fit::PART)).cloned(); - if let Some(key) = fit_key { - if let Some(xml_bytes) = source.passthrough.get(&key) { - if let Some(state) = crate::fit::read_from_bytes(xml_bytes) { - fit_state = Some(state); - source.passthrough.remove(&key); - } - } + if let Some(key) = fit_key + && let Some(xml_bytes) = source.passthrough.get(&key) + && let Some(state) = crate::fit::read_from_bytes(xml_bytes) + { + fit_state = Some(state); + source.passthrough.remove(&key); } let craft_wordmodel::Document { @@ -235,17 +225,24 @@ fn sanitize_para_props(mut props: wordcraft_doc::ParaProps) -> wordcraft_doc::Pa wordcraft_doc::props::LineSpacing::AtLeast(v) => *v = finite(*v), } } - if let Some(v) = &mut props.indent_left { if !v.is_finite() { *v = 0.0; } } - if let Some(v) = &mut props.indent_right { if !v.is_finite() { *v = 0.0; } } - if let Some(v) = &mut props.indent_first { if !v.is_finite() { *v = 0.0; } } - if let Some(v) = &mut props.space_before { if !v.is_finite() { *v = 0.0; } } - if let Some(v) = &mut props.space_after { if !v.is_finite() { *v = 0.0; } } + for field in [&mut props.indent_left, &mut props.indent_right, &mut props.indent_first, &mut props.space_before, &mut props.space_after] { + if let Some(v) = field + && !v.is_finite() + { + *v = 0.0; + } + } props } fn sanitize_char_props(mut props: wordcraft_doc::CharProps) -> wordcraft_doc::CharProps { - if let Some(v) = &mut props.size { if !v.is_finite() { *v = 0.0; } } - if let Some(v) = &mut props.spacing { if !v.is_finite() { *v = 0.0; } } + for field in [&mut props.size, &mut props.spacing] { + if let Some(v) = field + && !v.is_finite() + { + *v = 0.0; + } + } props } @@ -265,31 +262,17 @@ fn paragraph_value_to_shared(source: &wordcraft_doc::para::Paragraph) -> Result< } fn object_value_to_shared(source: &wordcraft_doc::para::InlineObject) -> Result { - use wordcraft_doc::para::InlineObject as Source; use craft_wordmodel::para::InlineObject as Target; + use wordcraft_doc::para::InlineObject as Source; match source { - Source::Image { - media, - w, - h, - alt, - float, - crop, - fx, - ole, - } => Ok(Target::Image { + Source::Image { media, w, h, alt, float, crop, fx, ole } => Ok(Target::Image { media: media.clone(), w: finite(*w), h: finite(*h), alt: alt.clone(), float: convert_ref(float)?, - crop: [ - finite(crop[0]), - finite(crop[1]), - finite(crop[2]), - finite(crop[3]), - ], + crop: [finite(crop[0]), finite(crop[1]), finite(crop[2]), finite(crop[3])], fx: convert_ref(fx)?, ole: ole.as_ref().map(|o| convert_ref(o.as_ref()).map(Arc::new)).transpose()?, }), @@ -336,11 +319,7 @@ fn cell_value_to_shared(source: &wordcraft_doc::table::Cell) -> Result Result { - Ok(craft_wordmodel::Part { - kind: convert_ref(&source.kind)?, - blocks: blocks_to_shared(&source.blocks)?, - body: convert_ref(&source.body)?, - }) + Ok(craft_wordmodel::Part { kind: convert_ref(&source.kind)?, blocks: blocks_to_shared(&source.blocks)?, body: convert_ref(&source.body)? }) } fn glossary_value_to_shared(source: &wordcraft_doc::BuildingBlock) -> Result { diff --git a/crates/formats/src/rtf.rs b/crates/formats/src/rtf.rs index 02a5de7f1a..6dbdb24492 100644 --- a/crates/formats/src/rtf.rs +++ b/crates/formats/src/rtf.rs @@ -1,7 +1,7 @@ //! Rich Text Format adapter using craft-rtf. -pub use craft_rtf::{dttm_to_iso, iso_to_dttm}; pub use craft_rtf::doc::esc; +pub use craft_rtf::{dttm_to_iso, iso_to_dttm}; /// Import RTF bytes into WordCraft's document model. pub fn import(bytes: &[u8]) -> Result { @@ -11,7 +11,9 @@ pub fn import(bytes: &[u8]) -> Result { /// Export a WordCraft document to RTF. pub fn export(doc: &wordcraft_doc::Document) -> String { - let shared = wordcraft_docx::to_shared_model(doc).expect("model conversion"); + let Ok(shared) = wordcraft_docx::to_shared_model(doc) else { + return String::new(); + }; craft_rtf::doc::export(&shared) }