mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-26 11:27:10 -06:00
Merge PR #2244: fix(docx, doc): support VML wrap side and case-insensitive float anchors
crates/docx/tests/fixtures.rs: both sides' appended tests kept.
This commit is contained in:
+11
-6
@@ -77,12 +77,17 @@ pub enum WrapText {
|
||||
impl WrapText {
|
||||
/// From the OOXML value (`bothSides`, `left`, `right`, `largest`).
|
||||
pub fn from_ooxml(v: &str) -> Option<WrapText> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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##"<w:p>
|
||||
<w:r>
|
||||
<w:pict xmlns:w10="urn:schemas-microsoft-com:office:word">
|
||||
<v:rect style="position:absolute;width:100pt;height:50pt;mso-position-horizontal-relative:Page;mso-position-vertical-relative:Margin" filled="False" stroked="t">
|
||||
<w10:wrap type="Square" side="left"/>
|
||||
</v:rect>
|
||||
</w:pict>
|
||||
</w:r>
|
||||
</w:p>"##;
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user