ppt_rs/generator/
shapes.rs

1//! Shape creation support for PPTX generation
2//!
3//! Provides shape types, fills, lines, and builders for creating shapes in slides.
4
5/// Shape types available in PPTX
6#[derive(Clone, Debug, Copy, PartialEq)]
7pub enum ShapeType {
8    // Basic shapes
9    Rectangle,
10    RoundedRectangle,
11    Ellipse,
12    Circle, // Alias for Ellipse
13    Triangle,
14    RightTriangle,
15    Diamond,
16    Pentagon,
17    Hexagon,
18    Octagon,
19    
20    // Arrows
21    RightArrow,
22    LeftArrow,
23    UpArrow,
24    DownArrow,
25    LeftRightArrow,
26    UpDownArrow,
27    BentArrow,
28    UTurnArrow,
29    
30    // Stars and banners
31    Star4,
32    Star5,
33    Star6,
34    Star8,
35    Ribbon,
36    Wave,
37    
38    // Callouts
39    WedgeRectCallout,
40    WedgeEllipseCallout,
41    CloudCallout,
42    
43    // Flow chart
44    FlowChartProcess,
45    FlowChartDecision,
46    FlowChartTerminator,
47    FlowChartDocument,
48    FlowChartPredefinedProcess,
49    FlowChartInternalStorage,
50    FlowChartData,
51    FlowChartInputOutput,
52    FlowChartManualInput,
53    FlowChartManualOperation,
54    FlowChartConnector,
55    FlowChartOffPageConnector,
56    FlowChartPunchedCard,
57    FlowChartPunchedTape,
58    FlowChartSummingJunction,
59    FlowChartOr,
60    FlowChartCollate,
61    FlowChartSort,
62    FlowChartExtract,
63    FlowChartMerge,
64    FlowChartOnlineStorage,
65    FlowChartDelay,
66    FlowChartMagneticTape,
67    FlowChartMagneticDisk,
68    FlowChartMagneticDrum,
69    FlowChartDisplay,
70    FlowChartPreparation,
71    
72    // More arrows
73    CurvedRightArrow,
74    CurvedLeftArrow,
75    CurvedUpArrow,
76    CurvedDownArrow,
77    CurvedLeftRightArrow,
78    CurvedUpDownArrow,
79    StripedRightArrow,
80    NotchedRightArrow,
81    PentagonArrow,
82    ChevronArrow,
83    RightArrowCallout,
84    LeftArrowCallout,
85    UpArrowCallout,
86    DownArrowCallout,
87    LeftRightArrowCallout,
88    UpDownArrowCallout,
89    QuadArrow,
90    LeftRightUpArrow,
91    CircularArrow,
92    
93    // More geometric shapes
94    Parallelogram,
95    Trapezoid,
96    NonIsoscelesTrapezoid,
97    IsoscelesTrapezoid,
98    Cube,
99    Can,
100    Cone,
101    Cylinder,
102    Bevel,
103    Donut,
104    NoSmoking,
105    BlockArc,
106    FoldedCorner,
107    SmileyFace,
108    Arc,
109    Chord,
110    Pie,
111    Teardrop,
112    Plaque,
113    MusicNote,
114    PictureFrame,
115    
116    // More decorative
117    Star10,
118    Star12,
119    Star16,
120    Star24,
121    Star32,
122    Seal,
123    Seal4,
124    Seal8,
125    Seal16,
126    Seal32,
127    ActionButtonBlank,
128    ActionButtonHome,
129    ActionButtonHelp,
130    ActionButtonInformation,
131    ActionButtonForwardNext,
132    ActionButtonBackPrevious,
133    ActionButtonBeginning,
134    ActionButtonEnd,
135    ActionButtonReturn,
136    ActionButtonDocument,
137    ActionButtonSound,
138    ActionButtonMovie,
139    
140    // Other (original shapes)
141    Heart,
142    Lightning,
143    Sun,
144    Moon,
145    Cloud,
146    Brace,
147    Bracket,
148    Plus,
149    Minus,
150}
151
152impl ShapeType {
153    /// Get the preset geometry name for the shape (OOXML preset name)
154    pub fn preset_name(&self) -> &'static str {
155        match self {
156            ShapeType::Rectangle => "rect",
157            ShapeType::RoundedRectangle => "roundRect",
158            ShapeType::Ellipse | ShapeType::Circle => "ellipse",
159            ShapeType::Triangle => "triangle",
160            ShapeType::RightTriangle => "rtTriangle",
161            ShapeType::Diamond => "diamond",
162            ShapeType::Pentagon => "pentagon",
163            ShapeType::Hexagon => "hexagon",
164            ShapeType::Octagon => "octagon",
165            
166            ShapeType::RightArrow => "rightArrow",
167            ShapeType::LeftArrow => "leftArrow",
168            ShapeType::UpArrow => "upArrow",
169            ShapeType::DownArrow => "downArrow",
170            ShapeType::LeftRightArrow => "leftRightArrow",
171            ShapeType::UpDownArrow => "upDownArrow",
172            ShapeType::BentArrow => "bentArrow",
173            ShapeType::UTurnArrow => "uturnArrow",
174            
175            ShapeType::Star4 => "star4",
176            ShapeType::Star5 => "star5",
177            ShapeType::Star6 => "star6",
178            ShapeType::Star8 => "star8",
179            ShapeType::Ribbon => "ribbon2",
180            ShapeType::Wave => "wave",
181            
182            ShapeType::WedgeRectCallout => "wedgeRectCallout",
183            ShapeType::WedgeEllipseCallout => "wedgeEllipseCallout",
184            ShapeType::CloudCallout => "cloudCallout",
185            
186            ShapeType::FlowChartProcess => "flowChartProcess",
187            ShapeType::FlowChartDecision => "flowChartDecision",
188            ShapeType::FlowChartTerminator => "flowChartTerminator",
189            ShapeType::FlowChartDocument => "flowChartDocument",
190            ShapeType::FlowChartPredefinedProcess => "flowChartPredefinedProcess",
191            ShapeType::FlowChartInternalStorage => "flowChartInternalStorage",
192            ShapeType::FlowChartData => "flowChartData",
193            ShapeType::FlowChartInputOutput => "flowChartInputOutput",
194            ShapeType::FlowChartManualInput => "flowChartManualInput",
195            ShapeType::FlowChartManualOperation => "flowChartManualOperation",
196            ShapeType::FlowChartConnector => "flowChartConnector",
197            ShapeType::FlowChartOffPageConnector => "flowChartOffPageConnector",
198            ShapeType::FlowChartPunchedCard => "flowChartPunchedCard",
199            ShapeType::FlowChartPunchedTape => "flowChartPunchedTape",
200            ShapeType::FlowChartSummingJunction => "flowChartSummingJunction",
201            ShapeType::FlowChartOr => "flowChartOr",
202            ShapeType::FlowChartCollate => "flowChartCollate",
203            ShapeType::FlowChartSort => "flowChartSort",
204            ShapeType::FlowChartExtract => "flowChartExtract",
205            ShapeType::FlowChartMerge => "flowChartMerge",
206            ShapeType::FlowChartOnlineStorage => "flowChartOnlineStorage",
207            ShapeType::FlowChartDelay => "flowChartDelay",
208            ShapeType::FlowChartMagneticTape => "flowChartMagneticTape",
209            ShapeType::FlowChartMagneticDisk => "flowChartMagneticDisk",
210            ShapeType::FlowChartMagneticDrum => "flowChartMagneticDrum",
211            ShapeType::FlowChartDisplay => "flowChartDisplay",
212            ShapeType::FlowChartPreparation => "flowChartPreparation",
213            
214            ShapeType::CurvedRightArrow => "curvedRightArrow",
215            ShapeType::CurvedLeftArrow => "curvedLeftArrow",
216            ShapeType::CurvedUpArrow => "curvedUpArrow",
217            ShapeType::CurvedDownArrow => "curvedDownArrow",
218            ShapeType::CurvedLeftRightArrow => "curvedLeftRightArrow",
219            ShapeType::CurvedUpDownArrow => "curvedUpDownArrow",
220            ShapeType::StripedRightArrow => "stripedRightArrow",
221            ShapeType::NotchedRightArrow => "notchedRightArrow",
222            ShapeType::PentagonArrow => "pentArrow",
223            ShapeType::ChevronArrow => "chevron",
224            ShapeType::RightArrowCallout => "rightArrowCallout",
225            ShapeType::LeftArrowCallout => "leftArrowCallout",
226            ShapeType::UpArrowCallout => "upArrowCallout",
227            ShapeType::DownArrowCallout => "downArrowCallout",
228            ShapeType::LeftRightArrowCallout => "leftRightArrowCallout",
229            ShapeType::UpDownArrowCallout => "upDownArrowCallout",
230            ShapeType::QuadArrow => "quadArrow",
231            ShapeType::LeftRightUpArrow => "leftRightUpArrow",
232            ShapeType::CircularArrow => "circularArrow",
233            
234            ShapeType::Parallelogram => "parallelogram",
235            ShapeType::Trapezoid => "trapezoid",
236            ShapeType::NonIsoscelesTrapezoid => "nonIsoscelesTrapezoid",
237            ShapeType::IsoscelesTrapezoid => "isoTrapezoid",
238            ShapeType::Cube => "cube",
239            ShapeType::Can => "can",
240            ShapeType::Cone => "cone",
241            ShapeType::Cylinder => "cylinder",
242            ShapeType::Bevel => "bevel",
243            ShapeType::Donut => "donut",
244            ShapeType::NoSmoking => "noSmoking",
245            ShapeType::BlockArc => "blockArc",
246            ShapeType::FoldedCorner => "foldedCorner",
247            ShapeType::SmileyFace => "smileyFace",
248            ShapeType::Arc => "arc",
249            ShapeType::Chord => "chord",
250            ShapeType::Pie => "pie",
251            ShapeType::Teardrop => "teardrop",
252            ShapeType::Plaque => "plaque",
253            ShapeType::MusicNote => "musicNote",
254            ShapeType::PictureFrame => "frame",
255            
256            ShapeType::Star10 => "star10",
257            ShapeType::Star12 => "star12",
258            ShapeType::Star16 => "star16",
259            ShapeType::Star24 => "star24",
260            ShapeType::Star32 => "star32",
261            ShapeType::Seal => "seal",
262            ShapeType::Seal4 => "seal4",
263            ShapeType::Seal8 => "seal8",
264            ShapeType::Seal16 => "seal16",
265            ShapeType::Seal32 => "seal32",
266            ShapeType::ActionButtonBlank => "actionButtonBlank",
267            ShapeType::ActionButtonHome => "actionButtonHome",
268            ShapeType::ActionButtonHelp => "actionButtonHelp",
269            ShapeType::ActionButtonInformation => "actionButtonInformation",
270            ShapeType::ActionButtonForwardNext => "actionButtonForwardNext",
271            ShapeType::ActionButtonBackPrevious => "actionButtonBackPrevious",
272            ShapeType::ActionButtonBeginning => "actionButtonBeginning",
273            ShapeType::ActionButtonEnd => "actionButtonEnd",
274            ShapeType::ActionButtonReturn => "actionButtonReturn",
275            ShapeType::ActionButtonDocument => "actionButtonDocument",
276            ShapeType::ActionButtonSound => "actionButtonSound",
277            ShapeType::ActionButtonMovie => "actionButtonMovie",
278            
279            ShapeType::Heart => "heart",
280            ShapeType::Lightning => "lightningBolt",
281            ShapeType::Sun => "sun",
282            ShapeType::Moon => "moon",
283            ShapeType::Cloud => "cloud",
284            ShapeType::Brace => "leftBrace",
285            ShapeType::Bracket => "leftBracket",
286            ShapeType::Plus => "mathPlus",
287            ShapeType::Minus => "mathMinus",
288        }
289    }
290
291    /// Get a user-friendly name for the shape
292    pub fn display_name(&self) -> &'static str {
293        match self {
294            ShapeType::Rectangle => "Rectangle",
295            ShapeType::RoundedRectangle => "Rounded Rectangle",
296            ShapeType::Ellipse => "Ellipse",
297            ShapeType::Circle => "Circle",
298            ShapeType::Triangle => "Triangle",
299            ShapeType::RightTriangle => "Right Triangle",
300            ShapeType::Diamond => "Diamond",
301            ShapeType::Pentagon => "Pentagon",
302            ShapeType::Hexagon => "Hexagon",
303            ShapeType::Octagon => "Octagon",
304            ShapeType::RightArrow => "Right Arrow",
305            ShapeType::LeftArrow => "Left Arrow",
306            ShapeType::UpArrow => "Up Arrow",
307            ShapeType::DownArrow => "Down Arrow",
308            ShapeType::LeftRightArrow => "Left-Right Arrow",
309            ShapeType::UpDownArrow => "Up-Down Arrow",
310            ShapeType::BentArrow => "Bent Arrow",
311            ShapeType::UTurnArrow => "U-Turn Arrow",
312            ShapeType::Star4 => "4-Point Star",
313            ShapeType::Star5 => "5-Point Star",
314            ShapeType::Star6 => "6-Point Star",
315            ShapeType::Star8 => "8-Point Star",
316            ShapeType::Ribbon => "Ribbon",
317            ShapeType::Wave => "Wave",
318            ShapeType::WedgeRectCallout => "Rectangle Callout",
319            ShapeType::WedgeEllipseCallout => "Oval Callout",
320            ShapeType::CloudCallout => "Cloud Callout",
321            ShapeType::FlowChartProcess => "Process",
322            ShapeType::FlowChartDecision => "Decision",
323            ShapeType::FlowChartTerminator => "Terminator",
324            ShapeType::FlowChartDocument => "Document",
325            ShapeType::FlowChartPredefinedProcess => "Predefined Process",
326            ShapeType::FlowChartInternalStorage => "Internal Storage",
327            ShapeType::FlowChartData => "Data",
328            ShapeType::FlowChartInputOutput => "Input/Output",
329            ShapeType::FlowChartManualInput => "Manual Input",
330            ShapeType::FlowChartManualOperation => "Manual Operation",
331            ShapeType::FlowChartConnector => "Connector",
332            ShapeType::FlowChartOffPageConnector => "Off-page Connector",
333            ShapeType::FlowChartPunchedCard => "Punched Card",
334            ShapeType::FlowChartPunchedTape => "Punched Tape",
335            ShapeType::FlowChartSummingJunction => "Summing Junction",
336            ShapeType::FlowChartOr => "Or",
337            ShapeType::FlowChartCollate => "Collate",
338            ShapeType::FlowChartSort => "Sort",
339            ShapeType::FlowChartExtract => "Extract",
340            ShapeType::FlowChartMerge => "Merge",
341            ShapeType::FlowChartOnlineStorage => "Online Storage",
342            ShapeType::FlowChartDelay => "Delay",
343            ShapeType::FlowChartMagneticTape => "Magnetic Tape",
344            ShapeType::FlowChartMagneticDisk => "Magnetic Disk",
345            ShapeType::FlowChartMagneticDrum => "Magnetic Drum",
346            ShapeType::FlowChartDisplay => "Display",
347            ShapeType::FlowChartPreparation => "Preparation",
348            
349            ShapeType::CurvedRightArrow => "Curved Right Arrow",
350            ShapeType::CurvedLeftArrow => "Curved Left Arrow",
351            ShapeType::CurvedUpArrow => "Curved Up Arrow",
352            ShapeType::CurvedDownArrow => "Curved Down Arrow",
353            ShapeType::CurvedLeftRightArrow => "Curved Left-Right Arrow",
354            ShapeType::CurvedUpDownArrow => "Curved Up-Down Arrow",
355            ShapeType::StripedRightArrow => "Striped Right Arrow",
356            ShapeType::NotchedRightArrow => "Notched Right Arrow",
357            ShapeType::PentagonArrow => "Pentagon Arrow",
358            ShapeType::ChevronArrow => "Chevron Arrow",
359            ShapeType::RightArrowCallout => "Right Arrow Callout",
360            ShapeType::LeftArrowCallout => "Left Arrow Callout",
361            ShapeType::UpArrowCallout => "Up Arrow Callout",
362            ShapeType::DownArrowCallout => "Down Arrow Callout",
363            ShapeType::LeftRightArrowCallout => "Left-Right Arrow Callout",
364            ShapeType::UpDownArrowCallout => "Up-Down Arrow Callout",
365            ShapeType::QuadArrow => "Quad Arrow",
366            ShapeType::LeftRightUpArrow => "Left-Right-Up Arrow",
367            ShapeType::CircularArrow => "Circular Arrow",
368            
369            ShapeType::Parallelogram => "Parallelogram",
370            ShapeType::Trapezoid => "Trapezoid",
371            ShapeType::NonIsoscelesTrapezoid => "Non-Isosceles Trapezoid",
372            ShapeType::IsoscelesTrapezoid => "Isosceles Trapezoid",
373            ShapeType::Cube => "Cube",
374            ShapeType::Can => "Can",
375            ShapeType::Cone => "Cone",
376            ShapeType::Cylinder => "Cylinder",
377            ShapeType::Bevel => "Bevel",
378            ShapeType::Donut => "Donut",
379            ShapeType::NoSmoking => "No Smoking",
380            ShapeType::BlockArc => "Block Arc",
381            ShapeType::FoldedCorner => "Folded Corner",
382            ShapeType::SmileyFace => "Smiley Face",
383            ShapeType::Arc => "Arc",
384            ShapeType::Chord => "Chord",
385            ShapeType::Pie => "Pie",
386            ShapeType::Teardrop => "Teardrop",
387            ShapeType::Plaque => "Plaque",
388            ShapeType::MusicNote => "Music Note",
389            ShapeType::PictureFrame => "Picture Frame",
390            
391            ShapeType::Star10 => "10-Point Star",
392            ShapeType::Star12 => "12-Point Star",
393            ShapeType::Star16 => "16-Point Star",
394            ShapeType::Star24 => "24-Point Star",
395            ShapeType::Star32 => "32-Point Star",
396            ShapeType::Seal => "Seal",
397            ShapeType::Seal4 => "4-Point Seal",
398            ShapeType::Seal8 => "8-Point Seal",
399            ShapeType::Seal16 => "16-Point Seal",
400            ShapeType::Seal32 => "32-Point Seal",
401            ShapeType::ActionButtonBlank => "Action Button (Blank)",
402            ShapeType::ActionButtonHome => "Action Button (Home)",
403            ShapeType::ActionButtonHelp => "Action Button (Help)",
404            ShapeType::ActionButtonInformation => "Action Button (Information)",
405            ShapeType::ActionButtonForwardNext => "Action Button (Forward/Next)",
406            ShapeType::ActionButtonBackPrevious => "Action Button (Back/Previous)",
407            ShapeType::ActionButtonBeginning => "Action Button (Beginning)",
408            ShapeType::ActionButtonEnd => "Action Button (End)",
409            ShapeType::ActionButtonReturn => "Action Button (Return)",
410            ShapeType::ActionButtonDocument => "Action Button (Document)",
411            ShapeType::ActionButtonSound => "Action Button (Sound)",
412            ShapeType::ActionButtonMovie => "Action Button (Movie)",
413            
414            ShapeType::Heart => "Heart",
415            ShapeType::Lightning => "Lightning Bolt",
416            ShapeType::Sun => "Sun",
417            ShapeType::Moon => "Moon",
418            ShapeType::Cloud => "Cloud",
419            ShapeType::Brace => "Brace",
420            ShapeType::Bracket => "Bracket",
421            ShapeType::Plus => "Plus",
422            ShapeType::Minus => "Minus",
423        }
424    }
425}
426
427/// Shape fill/color properties
428#[derive(Clone, Debug)]
429pub struct ShapeFill {
430    pub color: String, // RGB hex color (e.g., "FF0000")
431    pub transparency: Option<u32>, // 0-100000 (100000 = fully transparent)
432}
433
434impl ShapeFill {
435    /// Create new shape fill with color
436    pub fn new(color: &str) -> Self {
437        ShapeFill {
438            color: color.trim_start_matches('#').to_uppercase(),
439            transparency: None,
440        }
441    }
442
443    /// Set transparency (0-100 percent)
444    pub fn transparency(mut self, percent: u32) -> Self {
445        let alpha = ((100 - percent.min(100)) * 1000) as u32;
446        self.transparency = Some(alpha);
447        self
448    }
449}
450
451/// Shape line/border properties
452#[derive(Clone, Debug)]
453pub struct ShapeLine {
454    pub color: String,
455    pub width: u32, // in EMU (English Metric Units)
456}
457
458impl ShapeLine {
459    /// Create new shape line with color and width
460    pub fn new(color: &str, width: u32) -> Self {
461        ShapeLine {
462            color: color.trim_start_matches('#').to_uppercase(),
463            width,
464        }
465    }
466}
467
468/// Shape definition
469#[derive(Clone, Debug)]
470pub struct Shape {
471    pub shape_type: ShapeType,
472    pub x: u32,      // Position X in EMU
473    pub y: u32,      // Position Y in EMU
474    pub width: u32,  // Width in EMU
475    pub height: u32, // Height in EMU
476    pub fill: Option<ShapeFill>,
477    pub line: Option<ShapeLine>,
478    pub text: Option<String>,
479}
480
481impl Shape {
482    /// Create a new shape
483    pub fn new(shape_type: ShapeType, x: u32, y: u32, width: u32, height: u32) -> Self {
484        Shape {
485            shape_type,
486            x,
487            y,
488            width,
489            height,
490            fill: None,
491            line: None,
492            text: None,
493        }
494    }
495
496    /// Set shape fill
497    pub fn with_fill(mut self, fill: ShapeFill) -> Self {
498        self.fill = Some(fill);
499        self
500    }
501
502    /// Set shape line
503    pub fn with_line(mut self, line: ShapeLine) -> Self {
504        self.line = Some(line);
505        self
506    }
507
508    /// Set shape text
509    pub fn with_text(mut self, text: &str) -> Self {
510        self.text = Some(text.to_string());
511        self
512    }
513}
514
515/// Convert EMU (English Metric Units) to inches
516pub fn emu_to_inches(emu: u32) -> f64 {
517    emu as f64 / 914400.0
518}
519
520/// Convert inches to EMU
521pub fn inches_to_emu(inches: f64) -> u32 {
522    (inches * 914400.0) as u32
523}
524
525/// Convert centimeters to EMU
526pub fn cm_to_emu(cm: f64) -> u32 {
527    (cm * 360000.0) as u32
528}
529
530#[cfg(test)]
531mod tests {
532    use super::*;
533
534    #[test]
535    fn test_shape_type_names() {
536        assert_eq!(ShapeType::Rectangle.preset_name(), "rect");
537        assert_eq!(ShapeType::Circle.preset_name(), "ellipse");
538        assert_eq!(ShapeType::RightArrow.preset_name(), "rightArrow");
539        assert_eq!(ShapeType::Star5.preset_name(), "star5");
540        assert_eq!(ShapeType::Heart.preset_name(), "heart");
541    }
542
543    #[test]
544    fn test_shape_fill_builder() {
545        let fill = ShapeFill::new("FF0000").transparency(50);
546        assert_eq!(fill.color, "FF0000");
547        assert_eq!(fill.transparency, Some(50000));
548    }
549
550    #[test]
551    fn test_shape_builder() {
552        let shape = Shape::new(ShapeType::Rectangle, 0, 0, 1000000, 500000)
553            .with_fill(ShapeFill::new("0000FF"))
554            .with_line(ShapeLine::new("000000", 25400))
555            .with_text("Hello");
556
557        assert_eq!(shape.x, 0);
558        assert_eq!(shape.width, 1000000);
559        assert_eq!(shape.text, Some("Hello".to_string()));
560    }
561
562    #[test]
563    fn test_emu_conversions() {
564        let emu = inches_to_emu(1.0);
565        assert_eq!(emu, 914400);
566        assert!((emu_to_inches(emu) - 1.0).abs() < 0.001);
567    }
568
569    #[test]
570    fn test_cm_to_emu() {
571        let emu = cm_to_emu(2.54); // 1 inch
572        assert_eq!(emu, 914400);
573    }
574}