diff --git a/crates/model/src/style.rs b/crates/model/src/style.rs index 83c494bd..f3044419 100644 --- a/crates/model/src/style.rs +++ b/crates/model/src/style.rs @@ -232,6 +232,34 @@ pub enum LineJoin { Miter, } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum LineAlign { + #[default] + Center, + Inside, + Outside, +} + +impl LineAlign { + pub fn xml(self) -> &'static str { + match self { + LineAlign::Center => "ctr", + LineAlign::Inside => "in", + LineAlign::Outside => "out", + } + } + + pub fn from_xml(value: &str) -> Option { + match value { + "ctr" => Some(LineAlign::Center), + "in" => Some(LineAlign::Inside), + "out" => Some(LineAlign::Outside), + _ => None, + } + } +} + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum Compound { @@ -324,6 +352,7 @@ pub struct Line { pub dash: Option, pub cap: Option, pub join: Option, + pub align: Option, pub compound: Option, pub head: Option, pub tail: Option, @@ -347,6 +376,7 @@ impl Line { } self.cap = self.cap.or(parent.cap); self.join = self.join.or(parent.join); + self.align = self.align.or(parent.align); self.compound = self.compound.or(parent.compound); if self.head.is_none() { self.head.clone_from(&parent.head); diff --git a/crates/pptx/src/read/dml.rs b/crates/pptx/src/read/dml.rs index 767b7dc0..61b861df 100644 --- a/crates/pptx/src/read/dml.rs +++ b/crates/pptx/src/read/dml.rs @@ -3,8 +3,8 @@ use deckcraft_color::{ColorTransform, Rgba, SchemeSlot}; use deckcraft_geom::{Xfrm, emu_to_pt}; use deckcraft_model::style::{ - ColorBase, ColorRef, Compound, Dash, Effects, Fill, Glow, Gradient, GradientShape, GradientStop, Line, LineCap, LineEnd, LineJoin, PatternFill, - PictureFill, PictureMode, Reflection, Shadow, + ColorBase, ColorRef, Compound, Dash, Effects, Fill, Glow, Gradient, GradientShape, GradientStop, Line, LineAlign, LineCap, LineEnd, LineJoin, + PatternFill, PictureFill, PictureMode, Reflection, Shadow, }; use deckcraft_model::text::{ Action, Align, Anchor, AutoFit, BodyProps, Bullet, Caps, Hyperlink, LevelStyle, ListStyle, ParaProps, Paragraph, Run, RunKind, RunProps, Spacing, @@ -222,6 +222,7 @@ pub fn line(imp: &mut Imp, part: &Part, ln: &El) -> Line { _ => LineCap::Flat, }); l.compound = ln.attr("cmpd").map(Compound::from_xml); + l.align = ln.attr("algn").and_then(LineAlign::from_xml); l.fill = fill_of(imp, part, ln); for e in ln.elements() { match e.local() { diff --git a/crates/pptx/src/write/dml.rs b/crates/pptx/src/write/dml.rs index 518f8c4a..fbf8857b 100644 --- a/crates/pptx/src/write/dml.rs +++ b/crates/pptx/src/write/dml.rs @@ -1,7 +1,9 @@ //! DrawingML writing: colours, fills, lines, effects, transforms and text. use deckcraft_geom::{Xfrm, pt_to_emu}; -use deckcraft_model::style::{ColorBase, ColorRef, Compound, Dash, Effects, Fill, GradientShape, Line, LineCap, LineJoin, PictureFill, PictureMode}; +use deckcraft_model::style::{ + ColorBase, ColorRef, Compound, Dash, Effects, Fill, GradientShape, Line, LineAlign, LineCap, LineJoin, PictureFill, PictureMode, +}; use deckcraft_model::text::{ Action, AutoFit, BodyProps, Bullet, Caps, Hyperlink, ListStyle, ParaProps, Paragraph, RunKind, RunProps, Spacing, Strike, TextBody, }; @@ -208,6 +210,7 @@ pub fn line(w: &mut W, x: &mut Exp, o: &mut Out, tag: &str, l: &Line) { LineCap::Flat => "flat", }), ) + .o("algn", l.align.map(LineAlign::xml)) .o("cmpd", l.compound.map(Compound::xml)); w.open(tag, a); if let Some(f) = &l.fill { diff --git a/crates/pptx/tests/it/roundtrip.rs b/crates/pptx/tests/it/roundtrip.rs index 370cc9a9..e93807c2 100644 --- a/crates/pptx/tests/it/roundtrip.rs +++ b/crates/pptx/tests/it/roundtrip.rs @@ -488,6 +488,23 @@ fn shapes_fills_lines_effects_round_trip() { assert_eq!(by_name(&q.slides[1], "Group").id, by_name(&p.slides[1], "Group").id); } +#[test] +fn line_alignment_survives_two_pptx_round_trips() { + let mut p = Presentation::default(); + let mut inside = shape(&mut p, "Inset line", 20.0, 20.0, 100.0, 100.0); + inside.line = Some(Line { align: Some(deckcraft_model::style::LineAlign::Inside), ..Default::default() }); + let mut outside = shape(&mut p, "Outset line", 140.0, 20.0, 100.0, 100.0); + outside.line = Some(Line { align: Some(deckcraft_model::style::LineAlign::Outside), ..Default::default() }); + let mut centered = shape(&mut p, "Centered line", 260.0, 20.0, 100.0, 100.0); + centered.line = Some(Line { align: Some(deckcraft_model::style::LineAlign::Center), ..Default::default() }); + Arc::make_mut(&mut p.slides[0]).shapes.extend([inside, outside, centered]); + + let q = round(&round(&p)); + assert_eq!(by_name(&q.slides[0], "Inset line").line.as_ref().and_then(|line| line.align), Some(deckcraft_model::style::LineAlign::Inside)); + assert_eq!(by_name(&q.slides[0], "Outset line").line.as_ref().and_then(|line| line.align), Some(deckcraft_model::style::LineAlign::Outside)); + assert_eq!(by_name(&q.slides[0], "Centered line").line.as_ref().and_then(|line| line.align), Some(deckcraft_model::style::LineAlign::Center)); +} + #[test] fn pictures_media_and_ink_round_trip() { let p = rich_deck();