diff --git a/crates/doc/src/para.rs b/crates/doc/src/para.rs index e004b9086..d815bad7a 100644 --- a/crates/doc/src/para.rs +++ b/crates/doc/src/para.rs @@ -77,12 +77,17 @@ pub enum WrapText { impl WrapText { /// From the OOXML value (`bothSides`, `left`, `right`, `largest`). pub fn from_ooxml(v: &str) -> Option { - match v { - "bothSides" => Some(WrapText::BothSides), - "left" => Some(WrapText::Left), - "right" => Some(WrapText::Right), - "largest" => Some(WrapText::Largest), - _ => None, + let v = v.trim(); + if v.eq_ignore_ascii_case("bothSides") || v.eq_ignore_ascii_case("both") { + Some(WrapText::BothSides) + } else if v.eq_ignore_ascii_case("left") { + Some(WrapText::Left) + } else if v.eq_ignore_ascii_case("right") { + Some(WrapText::Right) + } else if v.eq_ignore_ascii_case("largest") { + Some(WrapText::Largest) + } else { + None } } diff --git a/crates/docx/src/read/story.rs b/crates/docx/src/read/story.rs index f5d3d3d78..68faaffc0 100644 --- a/crates/docx/src/read/story.rs +++ b/crates/docx/src/read/story.rs @@ -1635,7 +1635,10 @@ impl Reader<'_> { let type_id = shape.attr("type").map(|t| t.trim().trim_start_matches('#')); let shapetype = type_id.and_then(|id| pict.els().find(|e| e.name == "v:shapetype" && e.attr("id") == Some(id))); let attr = |name: &str| shape.attr(name).or_else(|| shapetype.and_then(|t| t.attr(name))); - let on = |name: &str| attr(name).is_none_or(|v| !matches!(v, "f" | "false" | "0")); + let on = |name: &str| attr(name).is_none_or(|v| { + let t = v.trim(); + !t.eq_ignore_ascii_case("f") && !t.eq_ignore_ascii_case("false") && t != "0" + }); let stroke = on("stroked").then(|| attr("strokecolor").map_or(Some(Rgb::BLACK), vml_color)).flatten(); let stroke_width = attr("strokeweight").and_then(|v| measure(v, 1.0)).unwrap_or(0.75).clamp(0.0, 100.0); let fill = vml_fill(shape, shapetype); @@ -2137,37 +2140,43 @@ fn vml_float(shape: &El, style: &str, in_hf: bool) -> Float { let off = |k: &str| get(k).and_then(|v| measure(v, 1.0)).unwrap_or(0.0); let max = crate::units::MAX_LEN_PT; let behind = get("z-index").and_then(int).is_some_and(|z| z < 0); - let wrap = match shape.child("w10:wrap").and_then(|w| w.attr("type")).unwrap_or("none") { - "square" => Wrap::Square, - "tight" => Wrap::Tight, - "through" => Wrap::Through, - "topAndBottom" => Wrap::TopAndBottom, + let wrap_el = shape.child("w10:wrap"); + let wrap_type = wrap_el.and_then(|w| w.attr("type")).unwrap_or("none"); + let wrap = match wrap_type { + t if t.eq_ignore_ascii_case("square") => Wrap::Square, + t if t.eq_ignore_ascii_case("tight") => Wrap::Tight, + t if t.eq_ignore_ascii_case("through") => Wrap::Through, + t if t.eq_ignore_ascii_case("topAndBottom") || t.eq_ignore_ascii_case("top-and-bottom") => Wrap::TopAndBottom, _ if behind => Wrap::BehindText, _ => Wrap::InFrontOfText, }; - let h_rel = match get("mso-position-horizontal-relative").unwrap_or("text") { - "page" => Anchor::Page, - "margin" => Anchor::Margin, - "char" => Anchor::Character, - "left-margin-area" => Anchor::LeftMargin, - "right-margin-area" => Anchor::RightMargin, - "inner-margin-area" => Anchor::InsideMargin, - "outer-margin-area" => Anchor::OutsideMargin, + let wrap_text = wrap_el.and_then(|w| w.attr("side")).and_then(wordcraft_doc::para::WrapText::from_ooxml).unwrap_or_default(); + let h_rel_str = get("mso-position-horizontal-relative").unwrap_or("text"); + let h_rel = match h_rel_str { + s if s.eq_ignore_ascii_case("page") => Anchor::Page, + s if s.eq_ignore_ascii_case("margin") => Anchor::Margin, + s if s.eq_ignore_ascii_case("char") => Anchor::Character, + s if s.eq_ignore_ascii_case("left-margin-area") => Anchor::LeftMargin, + s if s.eq_ignore_ascii_case("right-margin-area") => Anchor::RightMargin, + s if s.eq_ignore_ascii_case("inner-margin-area") => Anchor::InsideMargin, + s if s.eq_ignore_ascii_case("outer-margin-area") => Anchor::OutsideMargin, _ => Anchor::Column, }; - let v_rel = match get("mso-position-vertical-relative").unwrap_or("text") { - "page" => Anchor::Page, - "margin" => Anchor::Margin, - "line" => Anchor::Line, - "top-margin-area" => Anchor::TopMargin, - "bottom-margin-area" => Anchor::BottomMargin, - "inner-margin-area" => Anchor::InsideMargin, - "outer-margin-area" => Anchor::OutsideMargin, + let v_rel_str = get("mso-position-vertical-relative").unwrap_or("text"); + let v_rel = match v_rel_str { + s if s.eq_ignore_ascii_case("page") => Anchor::Page, + s if s.eq_ignore_ascii_case("margin") => Anchor::Margin, + s if s.eq_ignore_ascii_case("line") => Anchor::Line, + s if s.eq_ignore_ascii_case("top-margin-area") => Anchor::TopMargin, + s if s.eq_ignore_ascii_case("bottom-margin-area") => Anchor::BottomMargin, + s if s.eq_ignore_ascii_case("inner-margin-area") => Anchor::InsideMargin, + s if s.eq_ignore_ascii_case("outer-margin-area") => Anchor::OutsideMargin, _ => Anchor::Paragraph, }; let dist = |k: &str| get(k).and_then(|v| measure(v, 1.0)).unwrap_or(0.0).clamp(0.0, 1584.0); let mut f = Float { wrap, + wrap_text, h_rel, v_rel, x: (off("left") + off("margin-left")).clamp(-max, max), diff --git a/crates/docx/tests/fixtures.rs b/crates/docx/tests/fixtures.rs index 434a22b10..2452434c5 100644 --- a/crates/docx/tests/fixtures.rs +++ b/crates/docx/tests/fixtures.rs @@ -3652,3 +3652,27 @@ fn shape_gradients_and_transparency_are_read_and_survive_saving() { check(&d); check(&wordcraft_docx::read(&wordcraft_docx::write(&d).unwrap()).unwrap()); } + +#[test] +fn vml_shape_wrap_side_and_anchor_case_insensitivity() { + let body = r##" + + + + + + + + "##; + let doc = wordcraft_docx::read(&docx(body, &[], &[])).unwrap(); + let p = doc.body.iter().find_map(|b| b.as_para()).unwrap(); + let shape = match p.objects.first() { + Some(InlineObject::Shape { fill, float, .. }) => (fill, float), + o => panic!("expected shape, got {o:?}"), + }; + assert_eq!(*shape.0, None); + assert_eq!(shape.1.wrap, wordcraft_doc::para::Wrap::Square); + assert_eq!(shape.1.wrap_text, wordcraft_doc::para::WrapText::Left); + assert_eq!(shape.1.h_rel, wordcraft_doc::para::Anchor::Page); + assert_eq!(shape.1.v_rel, wordcraft_doc::para::Anchor::Margin); +}