pptx_rs/shape/
autoshape.rs1use crate::oxml::color::Color;
35use crate::oxml::shape::Sp as OxmlSp;
36use crate::oxml::simpletypes::PresetGeometry;
37use crate::oxml::sppr::{Fill, Geometry, ShapeProperties};
38use crate::oxml::txbody::TextBody;
39use crate::shape::base::Shape;
40use crate::units::Emu;
41
42#[derive(Clone, Debug, Default)]
44pub struct AutoShape {
45 pub(crate) sp: OxmlSp,
47}
48
49impl AutoShape {
50 #[allow(clippy::field_reassign_with_default)]
52 pub fn new(name: impl Into<String>, geometry: PresetGeometry) -> Self {
53 let mut sp = OxmlSp::default();
54 sp.id = 0;
55 sp.name = name.into();
56 sp.properties.geometry = Some(Geometry::preset(geometry));
57 sp.text = TextBody::new();
58 AutoShape { sp }
59 }
60
61 pub fn from_sp(sp: OxmlSp) -> Self {
63 AutoShape { sp }
64 }
65
66 pub fn sp(&self) -> &OxmlSp {
68 &self.sp
69 }
70 pub fn sp_mut(&mut self) -> &mut OxmlSp {
72 &mut self.sp
73 }
74
75 pub fn text_frame(&self) -> &TextBody {
77 &self.sp.text
78 }
79 pub fn text_frame_mut(&mut self) -> &mut TextBody {
81 &mut self.sp.text
82 }
83
84 pub fn properties(&self) -> &ShapeProperties {
86 &self.sp.properties
87 }
88 pub fn properties_mut(&mut self) -> &mut ShapeProperties {
90 &mut self.sp.properties
91 }
92
93 pub fn set_fill(&mut self, fill: Fill) {
95 self.sp.properties.fill = fill;
96 }
97
98 pub fn set_fill_color(&mut self, c: impl Into<Color>) {
103 self.sp.properties.fill = Fill::Solid(c.into());
104 }
105
106 pub fn set_stroke_color(&mut self, c: impl Into<Color>) {
108 let mut line = self.sp.properties.line.clone().unwrap_or_default();
109 line.color = c.into();
110 self.sp.properties.line = Some(line);
111 }
112
113 pub fn set_stroke_width(&mut self, w: Emu) {
115 let mut line = self.sp.properties.line.clone().unwrap_or_default();
116 line.width = Some(w);
117 self.sp.properties.line = Some(line);
118 }
119
120 pub fn set_outer_shadow(&mut self, shadow: crate::oxml::sppr::ShadowEffect) {
143 self.sp.properties.set_outer_shadow(shadow);
144 }
145
146 pub fn set_inner_shadow(&mut self, shadow: crate::oxml::sppr::ShadowEffect) {
148 self.sp.properties.set_inner_shadow(shadow);
149 }
150
151 pub fn set_glow(&mut self, glow: crate::oxml::sppr::GlowEffect) {
153 self.sp.properties.set_glow(glow);
154 }
155
156 pub fn set_soft_edge(&mut self, rad: i64) {
158 self.sp.properties.set_soft_edge(rad);
159 }
160
161 pub fn set_reflection(&mut self, reflection: crate::oxml::sppr::ReflectionEffect) {
163 self.sp.properties.set_reflection(reflection);
164 }
165
166 pub fn clear_effects(&mut self) {
168 self.sp.properties.clear_effects();
169 }
170
171 pub fn set_3d_rotation(&mut self, lat_deg: f64, lon_deg: f64, rev_deg: f64) {
198 let scene = self
199 .sp
200 .properties
201 .scene3d
202 .get_or_insert_with(crate::oxml::sppr::Scene3d::default);
203 scene.camera.rotation = Some(crate::oxml::sppr::Rotation3d {
204 lat: (lat_deg * 60_000.0) as i32,
205 lon: (lon_deg * 60_000.0) as i32,
206 rev: (rev_deg * 60_000.0) as i32,
207 });
208 }
209
210 pub fn set_3d_extrusion(&mut self, height_emu: i32, color: Option<crate::oxml::color::Color>) {
216 let sp3d = self
217 .sp
218 .properties
219 .sp3d
220 .get_or_insert_with(crate::oxml::sppr::Sp3d::default);
221 sp3d.extrusion_h = height_emu;
222 sp3d.extrusion_color = color;
223 }
224
225 pub fn set_3d_bevel(&mut self, top_w: i32, top_h: i32, bottom_w: i32, bottom_h: i32) {
231 let sp3d = self
232 .sp
233 .properties
234 .sp3d
235 .get_or_insert_with(crate::oxml::sppr::Sp3d::default);
236 sp3d.bevel_top = Some(crate::oxml::sppr::Bevel { w: top_w, h: top_h });
237 sp3d.bevel_bottom = Some(crate::oxml::sppr::Bevel {
238 w: bottom_w,
239 h: bottom_h,
240 });
241 }
242
243 pub fn set_3d_material(&mut self, material: crate::oxml::sppr::MaterialPreset) {
245 let sp3d = self
246 .sp
247 .properties
248 .sp3d
249 .get_or_insert_with(crate::oxml::sppr::Sp3d::default);
250 sp3d.prst_material = material;
251 }
252
253 pub fn clear_3d(&mut self) {
255 self.sp.properties.scene3d = None;
256 self.sp.properties.sp3d = None;
257 }
258
259 pub fn scene_3d(&self) -> Option<&crate::oxml::sppr::Scene3d> {
261 self.sp.properties.scene3d.as_ref()
262 }
263
264 pub fn scene_3d_mut(&mut self) -> &mut Option<crate::oxml::sppr::Scene3d> {
266 &mut self.sp.properties.scene3d
267 }
268
269 pub fn sp_3d(&self) -> Option<&crate::oxml::sppr::Sp3d> {
271 self.sp.properties.sp3d.as_ref()
272 }
273
274 pub fn sp_3d_mut(&mut self) -> &mut Option<crate::oxml::sppr::Sp3d> {
276 &mut self.sp.properties.sp3d
277 }
278
279 pub fn locks(&self) -> Option<&crate::oxml::shape::ShapeLocks> {
283 self.sp.locks.as_ref()
284 }
285
286 pub fn locks_mut(&mut self) -> &mut crate::oxml::shape::ShapeLocks {
288 self.sp.locks.get_or_insert_with(Default::default)
289 }
290
291 pub fn lock_select(&mut self, locked: bool) {
293 self.locks_mut().no_select = locked;
294 }
295
296 pub fn lock_move(&mut self, locked: bool) {
298 self.locks_mut().no_move = locked;
299 }
300
301 pub fn lock_resize(&mut self, locked: bool) {
303 self.locks_mut().no_resize = locked;
304 }
305
306 pub fn lock_rotate(&mut self, locked: bool) {
308 self.locks_mut().no_rot = locked;
309 }
310
311 pub fn lock_group(&mut self, locked: bool) {
313 self.locks_mut().no_grp = locked;
314 }
315
316 pub fn clear_locks(&mut self) {
318 self.sp.locks = None;
319 }
320
321 pub fn set_lock(&mut self, lock_type: crate::oxml::shape::LockType, locked: bool) {
343 self.locks_mut().set_lock(lock_type, locked);
344 }
345
346 pub fn style(&self) -> Option<&crate::oxml::shape::ShapeStyle> {
350 self.sp.style.as_ref()
351 }
352
353 pub fn set_style(&mut self, style: crate::oxml::shape::ShapeStyle) {
360 self.sp.style = Some(style);
361 }
362
363 pub fn clear_style(&mut self) {
365 self.sp.style = None;
366 }
367
368 pub fn set_text(&mut self, t: impl Into<String>) {
370 let s: String = t.into();
371 self.sp.text.set_text(&s);
372 }
373
374 pub fn text(&self) -> String {
376 self.sp.text.text()
377 }
378
379 pub fn adjustments(&self) -> &[crate::oxml::sppr::AdjustmentValue] {
385 match &self.sp.properties.geometry {
386 Some(Geometry::Preset(_, adj)) => adj,
387 _ => &[],
388 }
389 }
390
391 pub fn adjustments_mut(&mut self) -> &mut Vec<crate::oxml::sppr::AdjustmentValue> {
396 if !matches!(self.sp.properties.geometry, Some(Geometry::Preset(..))) {
398 self.sp.properties.geometry = Some(Geometry::default());
399 }
400 match &mut self.sp.properties.geometry {
401 Some(Geometry::Preset(_, adj)) => adj,
402 _ => unreachable!(),
403 }
404 }
405
406 pub fn set_adjustment(&mut self, idx: usize, value: f64) {
418 let adj = self.adjustments_mut();
419 while adj.len() <= idx {
420 let name = if adj.is_empty() {
421 "adj".to_string()
422 } else {
423 format!("adj{}", adj.len() + 1)
424 };
425 adj.push(crate::oxml::sppr::AdjustmentValue::from_normalized(
426 &name, 0.0,
427 ));
428 }
429 adj[idx].raw_value = (value * 100000.0).round() as i64;
430 }
431
432 pub fn adjustment_value(&self, idx: usize) -> Option<f64> {
436 self.adjustments().get(idx).map(|a| a.effective_value())
437 }
438}
439
440impl Shape for AutoShape {
441 fn id(&self) -> u32 {
442 self.sp.id
443 }
444 fn set_id(&mut self, id: u32) {
445 self.sp.id = id;
446 }
447 fn name(&self) -> &str {
448 &self.sp.name
449 }
450 fn set_name(&mut self, name: String) {
451 self.sp.name = name;
452 }
453 fn shape_type(&self) -> &'static str {
454 "auto_shape"
455 }
456
457 fn left(&self) -> Emu {
458 self.sp.properties.xfrm.off_x.unwrap_or_default()
459 }
460 fn set_left(&mut self, emu: Emu) {
461 self.sp.properties.xfrm.off_x = Some(emu);
462 }
463 fn top(&self) -> Emu {
464 self.sp.properties.xfrm.off_y.unwrap_or_default()
465 }
466 fn set_top(&mut self, emu: Emu) {
467 self.sp.properties.xfrm.off_y = Some(emu);
468 }
469 fn width(&self) -> Emu {
470 self.sp.properties.xfrm.ext_cx.unwrap_or_default()
471 }
472 fn set_width(&mut self, emu: Emu) {
473 self.sp.properties.xfrm.ext_cx = Some(emu);
474 }
475 fn height(&self) -> Emu {
476 self.sp.properties.xfrm.ext_cy.unwrap_or_default()
477 }
478 fn set_height(&mut self, emu: Emu) {
479 self.sp.properties.xfrm.ext_cy = Some(emu);
480 }
481
482 fn rotation(&self) -> f64 {
484 self.sp.properties.rot_deg.unwrap_or(0.0)
485 }
486 fn set_rotation(&mut self, deg: f64) {
489 self.sp.properties.rot_deg = Some(deg);
490 let rot = (deg * 60_000.0) as i32;
492 self.sp.properties.xfrm.rot = Some(rot);
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499 use crate::oxml::writer::XmlWriter;
500
501 #[test]
503 fn set_adjustment_first_value() {
504 let mut s = AutoShape::new("RoundRect", PresetGeometry::RoundRectangle);
505 s.set_adjustment(0, 0.25);
506 assert_eq!(s.adjustments().len(), 1);
507 assert!((s.adjustment_value(0).unwrap() - 0.25).abs() < 1e-6);
508 }
509
510 #[test]
512 fn set_adjustment_multiple_values() {
513 let mut s = AutoShape::new("Shape", PresetGeometry::Rectangle);
514 s.set_adjustment(0, 0.5);
515 s.set_adjustment(1, 0.3);
516 assert_eq!(s.adjustments().len(), 2);
517 assert!((s.adjustment_value(0).unwrap() - 0.5).abs() < 1e-6);
518 assert!((s.adjustment_value(1).unwrap() - 0.3).abs() < 1e-6);
519 }
520
521 #[test]
523 fn adjustment_value_out_of_bounds_returns_none() {
524 let s = AutoShape::new("Rect", PresetGeometry::Rectangle);
525 assert!(s.adjustment_value(0).is_none());
526 }
527
528 #[test]
530 fn adjustment_serializes_to_xml() {
531 let mut s = AutoShape::new("RoundRect", PresetGeometry::RoundRectangle);
532 s.set_adjustment(0, 0.16667);
533 let mut w = XmlWriter::new();
534 s.sp.properties.geometry.as_ref().unwrap().write_xml(&mut w);
535 let xml = &w.buf;
536 assert!(
537 xml.contains("<a:prstGeom prst=\"roundRect\">"),
538 "xml: {}",
539 xml
540 );
541 assert!(
542 xml.contains("<a:gd name=\"adj\" fmla=\"val 16667\"/>"),
543 "xml: {}",
544 xml
545 );
546 }
547}