Skip to main content

ppt_rs/generator/text/
paragraph.rs

1//! Paragraph - a block of text with alignment and spacing
2
3use super::run::Run;
4use super::TextAlign;
5use crate::core::ToXml;
6
7/// A paragraph containing one or more runs
8#[derive(Clone, Debug)]
9pub struct Paragraph {
10    pub runs: Vec<Run>,
11    pub align: TextAlign,
12    pub level: u32,
13    pub bullet: bool,
14    pub spacing_before: Option<u32>,
15    pub spacing_after: Option<u32>,
16    pub line_spacing: Option<u32>,
17}
18
19impl Paragraph {
20    /// Create a new empty paragraph
21    pub fn new() -> Self {
22        Paragraph {
23            runs: Vec::new(),
24            align: TextAlign::Left,
25            level: 0,
26            bullet: false,
27            spacing_before: None,
28            spacing_after: None,
29            line_spacing: None,
30        }
31    }
32
33    /// Create a paragraph with text
34    pub fn with_text(text: &str) -> Self {
35        let mut p = Self::new();
36        p.runs.push(Run::new(text));
37        p
38    }
39
40    /// Add a run
41    pub fn add_run(mut self, run: Run) -> Self {
42        self.runs.push(run);
43        self
44    }
45
46    /// Add plain text
47    pub fn add_text(mut self, text: &str) -> Self {
48        self.runs.push(Run::new(text));
49        self
50    }
51
52    /// Set alignment
53    pub fn align(mut self, align: TextAlign) -> Self {
54        self.align = align;
55        self
56    }
57
58    /// Set as bullet point
59    pub fn bullet(mut self) -> Self {
60        self.bullet = true;
61        self
62    }
63
64    /// Set indent level (0-8)
65    pub fn level(mut self, level: u32) -> Self {
66        self.level = level.min(8);
67        self
68    }
69
70    /// Set spacing before (in points)
71    pub fn spacing_before(mut self, points: u32) -> Self {
72        self.spacing_before = Some(points * 100);
73        self
74    }
75
76    /// Set spacing after (in points)
77    pub fn spacing_after(mut self, points: u32) -> Self {
78        self.spacing_after = Some(points * 100);
79        self
80    }
81
82    /// Generate XML for this paragraph
83    pub fn to_xml(&self) -> String {
84        let mut xml = String::from("<a:p>");
85        
86        // Paragraph properties
87        let mut ppr = format!(r#"<a:pPr algn="{}" lvl="{}""#, self.align.to_xml(), self.level);
88        
89        if self.spacing_before.is_some() || self.spacing_after.is_some() || self.line_spacing.is_some() {
90            ppr.push('>');
91            if let Some(before) = self.spacing_before {
92                ppr.push_str(&format!(r#"<a:spcBef><a:spcPts val="{}"/></a:spcBef>"#, before));
93            }
94            if let Some(after) = self.spacing_after {
95                ppr.push_str(&format!(r#"<a:spcAft><a:spcPts val="{}"/></a:spcAft>"#, after));
96            }
97            if self.bullet {
98                ppr.push_str("<a:buChar char=\"•\"/>");
99            }
100            ppr.push_str("</a:pPr>");
101        } else if self.bullet {
102            ppr.push_str("><a:buChar char=\"•\"/></a:pPr>");
103        } else {
104            ppr.push_str("/>");
105        }
106        
107        xml.push_str(&ppr);
108        
109        // Runs
110        for run in &self.runs {
111            xml.push_str(&run.to_xml());
112        }
113        
114        xml.push_str("</a:p>");
115        xml
116    }
117}
118
119impl ToXml for Paragraph {
120    fn to_xml(&self) -> String {
121        Paragraph::to_xml(self)
122    }
123}
124
125impl Default for Paragraph {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_paragraph_to_xml() {
137        let para = Paragraph::new()
138            .add_run(Run::new("Bold text").bold())
139            .add_run(Run::new(" normal text"))
140            .align(TextAlign::Center);
141        
142        let xml = para.to_xml();
143        
144        assert!(xml.contains("<a:p>"));
145        assert!(xml.contains("algn=\"ctr\""));
146        assert!(xml.contains("Bold text"));
147        assert!(xml.contains("normal text"));
148    }
149
150    #[test]
151    fn test_paragraph_with_bullet() {
152        let para = Paragraph::with_text("Bullet item").bullet();
153        let xml = para.to_xml();
154        
155        assert!(xml.contains("buChar"));
156    }
157}