use crate::oxml::color::Color;
use crate::oxml::shape::Sp as OxmlSp;
use crate::oxml::simpletypes::PresetGeometry;
use crate::oxml::sppr::{Fill, Geometry, ShapeProperties};
use crate::oxml::txbody::TextBody;
use crate::shape::base::Shape;
use crate::units::Emu;
#[derive(Clone, Debug, Default)]
pub struct AutoShape {
pub(crate) sp: OxmlSp,
}
impl AutoShape {
#[allow(clippy::field_reassign_with_default)]
pub fn new(name: impl Into<String>, geometry: PresetGeometry) -> Self {
let mut sp = OxmlSp::default();
sp.id = 0;
sp.name = name.into();
sp.properties.geometry = Some(Geometry::preset(geometry));
sp.text = TextBody::new();
AutoShape { sp }
}
pub fn from_sp(sp: OxmlSp) -> Self {
AutoShape { sp }
}
pub fn sp(&self) -> &OxmlSp {
&self.sp
}
pub fn sp_mut(&mut self) -> &mut OxmlSp {
&mut self.sp
}
pub fn text_frame(&self) -> &TextBody {
&self.sp.text
}
pub fn text_frame_mut(&mut self) -> &mut TextBody {
&mut self.sp.text
}
pub fn properties(&self) -> &ShapeProperties {
&self.sp.properties
}
pub fn properties_mut(&mut self) -> &mut ShapeProperties {
&mut self.sp.properties
}
pub fn set_fill(&mut self, fill: Fill) {
self.sp.properties.fill = fill;
}
pub fn set_fill_color(&mut self, c: impl Into<Color>) {
self.sp.properties.fill = Fill::Solid(c.into());
}
pub fn set_stroke_color(&mut self, c: impl Into<Color>) {
let mut line = self.sp.properties.line.clone().unwrap_or_default();
line.color = c.into();
self.sp.properties.line = Some(line);
}
pub fn set_stroke_width(&mut self, w: Emu) {
let mut line = self.sp.properties.line.clone().unwrap_or_default();
line.width = Some(w);
self.sp.properties.line = Some(line);
}
pub fn set_outer_shadow(&mut self, shadow: crate::oxml::sppr::ShadowEffect) {
self.sp.properties.set_outer_shadow(shadow);
}
pub fn set_inner_shadow(&mut self, shadow: crate::oxml::sppr::ShadowEffect) {
self.sp.properties.set_inner_shadow(shadow);
}
pub fn set_glow(&mut self, glow: crate::oxml::sppr::GlowEffect) {
self.sp.properties.set_glow(glow);
}
pub fn set_soft_edge(&mut self, rad: i64) {
self.sp.properties.set_soft_edge(rad);
}
pub fn set_reflection(&mut self, reflection: crate::oxml::sppr::ReflectionEffect) {
self.sp.properties.set_reflection(reflection);
}
pub fn clear_effects(&mut self) {
self.sp.properties.clear_effects();
}
pub fn set_3d_rotation(&mut self, lat_deg: f64, lon_deg: f64, rev_deg: f64) {
let scene = self
.sp
.properties
.scene3d
.get_or_insert_with(crate::oxml::sppr::Scene3d::default);
scene.camera.rotation = Some(crate::oxml::sppr::Rotation3d {
lat: (lat_deg * 60_000.0) as i32,
lon: (lon_deg * 60_000.0) as i32,
rev: (rev_deg * 60_000.0) as i32,
});
}
pub fn set_3d_extrusion(&mut self, height_emu: i32, color: Option<crate::oxml::color::Color>) {
let sp3d = self
.sp
.properties
.sp3d
.get_or_insert_with(crate::oxml::sppr::Sp3d::default);
sp3d.extrusion_h = height_emu;
sp3d.extrusion_color = color;
}
pub fn set_3d_bevel(&mut self, top_w: i32, top_h: i32, bottom_w: i32, bottom_h: i32) {
let sp3d = self
.sp
.properties
.sp3d
.get_or_insert_with(crate::oxml::sppr::Sp3d::default);
sp3d.bevel_top = Some(crate::oxml::sppr::Bevel { w: top_w, h: top_h });
sp3d.bevel_bottom = Some(crate::oxml::sppr::Bevel {
w: bottom_w,
h: bottom_h,
});
}
pub fn set_3d_material(&mut self, material: crate::oxml::sppr::MaterialPreset) {
let sp3d = self
.sp
.properties
.sp3d
.get_or_insert_with(crate::oxml::sppr::Sp3d::default);
sp3d.prst_material = material;
}
pub fn clear_3d(&mut self) {
self.sp.properties.scene3d = None;
self.sp.properties.sp3d = None;
}
pub fn scene_3d(&self) -> Option<&crate::oxml::sppr::Scene3d> {
self.sp.properties.scene3d.as_ref()
}
pub fn scene_3d_mut(&mut self) -> &mut Option<crate::oxml::sppr::Scene3d> {
&mut self.sp.properties.scene3d
}
pub fn sp_3d(&self) -> Option<&crate::oxml::sppr::Sp3d> {
self.sp.properties.sp3d.as_ref()
}
pub fn sp_3d_mut(&mut self) -> &mut Option<crate::oxml::sppr::Sp3d> {
&mut self.sp.properties.sp3d
}
pub fn locks(&self) -> Option<&crate::oxml::shape::ShapeLocks> {
self.sp.locks.as_ref()
}
pub fn locks_mut(&mut self) -> &mut crate::oxml::shape::ShapeLocks {
self.sp.locks.get_or_insert_with(Default::default)
}
pub fn lock_select(&mut self, locked: bool) {
self.locks_mut().no_select = locked;
}
pub fn lock_move(&mut self, locked: bool) {
self.locks_mut().no_move = locked;
}
pub fn lock_resize(&mut self, locked: bool) {
self.locks_mut().no_resize = locked;
}
pub fn lock_rotate(&mut self, locked: bool) {
self.locks_mut().no_rot = locked;
}
pub fn lock_group(&mut self, locked: bool) {
self.locks_mut().no_grp = locked;
}
pub fn clear_locks(&mut self) {
self.sp.locks = None;
}
pub fn set_lock(&mut self, lock_type: crate::oxml::shape::LockType, locked: bool) {
self.locks_mut().set_lock(lock_type, locked);
}
pub fn style(&self) -> Option<&crate::oxml::shape::ShapeStyle> {
self.sp.style.as_ref()
}
pub fn set_style(&mut self, style: crate::oxml::shape::ShapeStyle) {
self.sp.style = Some(style);
}
pub fn clear_style(&mut self) {
self.sp.style = None;
}
pub fn set_text(&mut self, t: impl Into<String>) {
let s: String = t.into();
self.sp.text.set_text(&s);
}
pub fn text(&self) -> String {
self.sp.text.text()
}
pub fn adjustments(&self) -> &[crate::oxml::sppr::AdjustmentValue] {
match &self.sp.properties.geometry {
Some(Geometry::Preset(_, adj)) => adj,
_ => &[],
}
}
pub fn adjustments_mut(&mut self) -> &mut Vec<crate::oxml::sppr::AdjustmentValue> {
if !matches!(self.sp.properties.geometry, Some(Geometry::Preset(..))) {
self.sp.properties.geometry = Some(Geometry::default());
}
match &mut self.sp.properties.geometry {
Some(Geometry::Preset(_, adj)) => adj,
_ => unreachable!(),
}
}
pub fn set_adjustment(&mut self, idx: usize, value: f64) {
let adj = self.adjustments_mut();
while adj.len() <= idx {
let name = if adj.is_empty() {
"adj".to_string()
} else {
format!("adj{}", adj.len() + 1)
};
adj.push(crate::oxml::sppr::AdjustmentValue::from_normalized(
&name, 0.0,
));
}
adj[idx].raw_value = (value * 100000.0).round() as i64;
}
pub fn adjustment_value(&self, idx: usize) -> Option<f64> {
self.adjustments().get(idx).map(|a| a.effective_value())
}
}
impl Shape for AutoShape {
fn id(&self) -> u32 {
self.sp.id
}
fn set_id(&mut self, id: u32) {
self.sp.id = id;
}
fn name(&self) -> &str {
&self.sp.name
}
fn set_name(&mut self, name: String) {
self.sp.name = name;
}
fn shape_type(&self) -> &'static str {
"auto_shape"
}
fn left(&self) -> Emu {
self.sp.properties.xfrm.off_x.unwrap_or_default()
}
fn set_left(&mut self, emu: Emu) {
self.sp.properties.xfrm.off_x = Some(emu);
}
fn top(&self) -> Emu {
self.sp.properties.xfrm.off_y.unwrap_or_default()
}
fn set_top(&mut self, emu: Emu) {
self.sp.properties.xfrm.off_y = Some(emu);
}
fn width(&self) -> Emu {
self.sp.properties.xfrm.ext_cx.unwrap_or_default()
}
fn set_width(&mut self, emu: Emu) {
self.sp.properties.xfrm.ext_cx = Some(emu);
}
fn height(&self) -> Emu {
self.sp.properties.xfrm.ext_cy.unwrap_or_default()
}
fn set_height(&mut self, emu: Emu) {
self.sp.properties.xfrm.ext_cy = Some(emu);
}
fn rotation(&self) -> f64 {
self.sp.properties.rot_deg.unwrap_or(0.0)
}
fn set_rotation(&mut self, deg: f64) {
self.sp.properties.rot_deg = Some(deg);
let rot = (deg * 60_000.0) as i32;
self.sp.properties.xfrm.rot = Some(rot);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oxml::writer::XmlWriter;
#[test]
fn set_adjustment_first_value() {
let mut s = AutoShape::new("RoundRect", PresetGeometry::RoundRectangle);
s.set_adjustment(0, 0.25);
assert_eq!(s.adjustments().len(), 1);
assert!((s.adjustment_value(0).unwrap() - 0.25).abs() < 1e-6);
}
#[test]
fn set_adjustment_multiple_values() {
let mut s = AutoShape::new("Shape", PresetGeometry::Rectangle);
s.set_adjustment(0, 0.5);
s.set_adjustment(1, 0.3);
assert_eq!(s.adjustments().len(), 2);
assert!((s.adjustment_value(0).unwrap() - 0.5).abs() < 1e-6);
assert!((s.adjustment_value(1).unwrap() - 0.3).abs() < 1e-6);
}
#[test]
fn adjustment_value_out_of_bounds_returns_none() {
let s = AutoShape::new("Rect", PresetGeometry::Rectangle);
assert!(s.adjustment_value(0).is_none());
}
#[test]
fn adjustment_serializes_to_xml() {
let mut s = AutoShape::new("RoundRect", PresetGeometry::RoundRectangle);
s.set_adjustment(0, 0.16667);
let mut w = XmlWriter::new();
s.sp.properties.geometry.as_ref().unwrap().write_xml(&mut w);
let xml = &w.buf;
assert!(
xml.contains("<a:prstGeom prst=\"roundRect\">"),
"xml: {}",
xml
);
assert!(
xml.contains("<a:gd name=\"adj\" fmla=\"val 16667\"/>"),
"xml: {}",
xml
);
}
}