Skip to main content

ppt_rs/generator/text/
format.rs

1//! Text formatting options
2
3/// Text formatting options
4#[derive(Clone, Debug, Default)]
5pub struct TextFormat {
6    pub bold: bool,
7    pub italic: bool,
8    pub underline: bool,
9    pub strikethrough: bool,
10    pub color: Option<String>,      // RGB hex color (e.g., "FF0000" for red)
11    pub highlight: Option<String>,  // Highlight/background color
12    pub font_size: Option<u32>,     // in points
13    pub font_family: Option<String>, // Font family name (e.g., "Arial")
14    pub subscript: bool,
15    pub superscript: bool,
16}
17
18impl TextFormat {
19    /// Create a new text format with default settings
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Set bold formatting
25    pub fn bold(mut self) -> Self {
26        self.bold = true;
27        self
28    }
29
30    /// Set italic formatting
31    pub fn italic(mut self) -> Self {
32        self.italic = true;
33        self
34    }
35
36    /// Set underline formatting
37    pub fn underline(mut self) -> Self {
38        self.underline = true;
39        self
40    }
41    
42    /// Set strikethrough formatting
43    pub fn strikethrough(mut self) -> Self {
44        self.strikethrough = true;
45        self
46    }
47
48    /// Set text color (RGB hex format)
49    pub fn color(mut self, hex_color: &str) -> Self {
50        self.color = Some(hex_color.trim_start_matches('#').to_uppercase());
51        self
52    }
53    
54    /// Set highlight/background color (RGB hex format)
55    pub fn highlight(mut self, hex_color: &str) -> Self {
56        self.highlight = Some(hex_color.trim_start_matches('#').to_uppercase());
57        self
58    }
59
60    /// Set font size in points
61    pub fn font_size(mut self, size: u32) -> Self {
62        self.font_size = Some(size);
63        self
64    }
65
66    /// Set font family
67    pub fn font_family(mut self, family: &str) -> Self {
68        self.font_family = Some(family.to_string());
69        self
70    }
71    
72    /// Set subscript formatting
73    pub fn subscript(mut self) -> Self {
74        self.subscript = true;
75        self.superscript = false; // Can't be both
76        self
77    }
78    
79    /// Set superscript formatting
80    pub fn superscript(mut self) -> Self {
81        self.superscript = true;
82        self.subscript = false; // Can't be both
83        self
84    }
85
86    /// Generate XML attributes for text formatting
87    pub fn to_xml_attrs(&self) -> String {
88        let mut attrs = String::new();
89
90        if self.bold {
91            attrs.push_str(" b=\"1\"");
92        }
93
94        if self.italic {
95            attrs.push_str(" i=\"1\"");
96        }
97
98        if self.underline {
99            attrs.push_str(" u=\"sng\"");
100        }
101        
102        if self.strikethrough {
103            attrs.push_str(" strike=\"sngStrike\"");
104        }
105        
106        if self.subscript {
107            attrs.push_str(" baseline=\"-25000\""); // 25% below baseline
108        } else if self.superscript {
109            attrs.push_str(" baseline=\"30000\""); // 30% above baseline
110        }
111
112        if let Some(size) = self.font_size {
113            attrs.push_str(&format!(" sz=\"{}\"", size * 100));
114        }
115
116        attrs
117    }
118    
119    /// Generate highlight element if set
120    pub fn to_highlight_xml(&self) -> String {
121        if let Some(ref color) = self.highlight {
122            format!(r#"<a:highlight><a:srgbClr val="{}"/></a:highlight>"#, color)
123        } else {
124            String::new()
125        }
126    }
127}
128
129/// Formatted text with styling
130#[derive(Clone, Debug)]
131pub struct FormattedText {
132    pub text: String,
133    pub format: TextFormat,
134}
135
136impl FormattedText {
137    /// Create new formatted text
138    pub fn new(text: &str) -> Self {
139        FormattedText {
140            text: text.to_string(),
141            format: TextFormat::default(),
142        }
143    }
144
145    /// Apply formatting
146    pub fn with_format(mut self, format: TextFormat) -> Self {
147        self.format = format;
148        self
149    }
150
151    /// Builder method for bold
152    pub fn bold(mut self) -> Self {
153        self.format = self.format.bold();
154        self
155    }
156
157    /// Builder method for italic
158    pub fn italic(mut self) -> Self {
159        self.format = self.format.italic();
160        self
161    }
162
163    /// Builder method for underline
164    pub fn underline(mut self) -> Self {
165        self.format = self.format.underline();
166        self
167    }
168    
169    /// Builder method for strikethrough
170    pub fn strikethrough(mut self) -> Self {
171        self.format = self.format.strikethrough();
172        self
173    }
174
175    /// Builder method for color
176    pub fn color(mut self, hex_color: &str) -> Self {
177        self.format = self.format.color(hex_color);
178        self
179    }
180    
181    /// Builder method for highlight
182    pub fn highlight(mut self, hex_color: &str) -> Self {
183        self.format = self.format.highlight(hex_color);
184        self
185    }
186
187    /// Builder method for font size
188    pub fn font_size(mut self, size: u32) -> Self {
189        self.format = self.format.font_size(size);
190        self
191    }
192    
193    /// Builder method for subscript
194    pub fn subscript(mut self) -> Self {
195        self.format = self.format.subscript();
196        self
197    }
198    
199    /// Builder method for superscript
200    pub fn superscript(mut self) -> Self {
201        self.format = self.format.superscript();
202        self
203    }
204}
205
206/// Generate XML color element
207pub fn color_to_xml(hex_color: &str) -> String {
208    let clean_color = hex_color.trim_start_matches('#').to_uppercase();
209    format!("<a:solidFill><a:srgbClr val=\"{}\"/></a:solidFill>", clean_color)
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_text_format_builder() {
218        let format = TextFormat::new()
219            .bold()
220            .italic()
221            .color("FF0000")
222            .font_size(24);
223
224        assert!(format.bold);
225        assert!(format.italic);
226        assert_eq!(format.color, Some("FF0000".to_string()));
227        assert_eq!(format.font_size, Some(24));
228    }
229
230    #[test]
231    fn test_formatted_text_builder() {
232        let text = FormattedText::new("Hello")
233            .bold()
234            .italic()
235            .color("0000FF");
236
237        assert_eq!(text.text, "Hello");
238        assert!(text.format.bold);
239        assert!(text.format.italic);
240        assert_eq!(text.format.color, Some("0000FF".to_string()));
241    }
242
243    #[test]
244    fn test_format_to_xml_attrs() {
245        let format = TextFormat::new().bold().italic().font_size(24);
246        let attrs = format.to_xml_attrs();
247        assert!(attrs.contains("b=\"1\""));
248        assert!(attrs.contains("i=\"1\""));
249        assert!(attrs.contains("sz=\"2400\""));
250    }
251
252    #[test]
253    fn test_color_to_xml() {
254        let xml = color_to_xml("FF0000");
255        assert!(xml.contains("FF0000"));
256        assert!(xml.contains("srgbClr"));
257    }
258    
259    #[test]
260    fn test_strikethrough() {
261        let format = TextFormat::new().strikethrough();
262        let attrs = format.to_xml_attrs();
263        assert!(attrs.contains("strike=\"sngStrike\""));
264    }
265    
266    #[test]
267    fn test_highlight() {
268        let format = TextFormat::new().highlight("FFFF00");
269        let xml = format.to_highlight_xml();
270        assert!(xml.contains("highlight"));
271        assert!(xml.contains("FFFF00"));
272    }
273    
274    #[test]
275    fn test_subscript_superscript() {
276        let sub = TextFormat::new().subscript();
277        let attrs = sub.to_xml_attrs();
278        assert!(attrs.contains("baseline=\"-25000\""));
279        
280        let sup = TextFormat::new().superscript();
281        let attrs = sup.to_xml_attrs();
282        assert!(attrs.contains("baseline=\"30000\""));
283    }
284    
285    #[test]
286    fn test_formatted_text_strikethrough() {
287        let text = FormattedText::new("Deleted")
288            .strikethrough();
289        assert!(text.format.strikethrough);
290    }
291    
292    #[test]
293    fn test_formatted_text_highlight() {
294        let text = FormattedText::new("Important")
295            .highlight("FFFF00");
296        assert_eq!(text.format.highlight, Some("FFFF00".to_string()));
297    }
298    
299    #[test]
300    fn test_formatted_text_subscript_superscript() {
301        let sub = FormattedText::new("2").subscript();
302        assert!(sub.format.subscript);
303        
304        let sup = FormattedText::new("2").superscript();
305        assert!(sup.format.superscript);
306    }
307}