Skip to main content

ppt_rs/generator/text/
frame.rs

1//! TextFrame - container for paragraphs
2
3use super::paragraph::Paragraph;
4use super::TextAnchor;
5use crate::core::ToXml;
6
7/// A text frame containing paragraphs
8#[derive(Clone, Debug)]
9pub struct TextFrame {
10    pub paragraphs: Vec<Paragraph>,
11    pub anchor: TextAnchor,
12    pub wrap: bool,
13    pub margin_left: u32,
14    pub margin_right: u32,
15    pub margin_top: u32,
16    pub margin_bottom: u32,
17}
18
19impl TextFrame {
20    /// Create a new empty text frame
21    pub fn new() -> Self {
22        TextFrame {
23            paragraphs: Vec::new(),
24            anchor: TextAnchor::Top,
25            wrap: true,
26            margin_left: 91440,   // 0.1 inch
27            margin_right: 91440,
28            margin_top: 45720,    // 0.05 inch
29            margin_bottom: 45720,
30        }
31    }
32
33    /// Create with a single paragraph
34    pub fn with_text(text: &str) -> Self {
35        let mut tf = Self::new();
36        tf.paragraphs.push(Paragraph::with_text(text));
37        tf
38    }
39
40    /// Add a paragraph
41    pub fn add_paragraph(mut self, para: Paragraph) -> Self {
42        self.paragraphs.push(para);
43        self
44    }
45
46    /// Add plain text as a paragraph
47    pub fn add_text(mut self, text: &str) -> Self {
48        self.paragraphs.push(Paragraph::with_text(text));
49        self
50    }
51
52    /// Set vertical anchor
53    pub fn anchor(mut self, anchor: TextAnchor) -> Self {
54        self.anchor = anchor;
55        self
56    }
57
58    /// Set margins (in EMU)
59    pub fn margins(mut self, left: u32, right: u32, top: u32, bottom: u32) -> Self {
60        self.margin_left = left;
61        self.margin_right = right;
62        self.margin_top = top;
63        self.margin_bottom = bottom;
64        self
65    }
66
67    /// Generate XML for this text frame
68    pub fn to_xml(&self) -> String {
69        let wrap = if self.wrap { "square" } else { "none" };
70        
71        let mut xml = format!(
72            r#"<p:txBody><a:bodyPr wrap="{}" lIns="{}" rIns="{}" tIns="{}" bIns="{}" anchor="{}"/><a:lstStyle/>"#,
73            wrap, self.margin_left, self.margin_right, self.margin_top, self.margin_bottom, self.anchor.to_xml()
74        );
75        
76        for para in &self.paragraphs {
77            xml.push_str(&para.to_xml());
78        }
79        
80        // Add empty paragraph if none
81        if self.paragraphs.is_empty() {
82            xml.push_str("<a:p/>");
83        }
84        
85        xml.push_str("</p:txBody>");
86        xml
87    }
88}
89
90impl ToXml for TextFrame {
91    fn to_xml(&self) -> String {
92        TextFrame::to_xml(self)
93    }
94}
95
96impl Default for TextFrame {
97    fn default() -> Self {
98        Self::new()
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::generator::text::TextAlign;
106
107    #[test]
108    fn test_text_frame_to_xml() {
109        let tf = TextFrame::new()
110            .add_paragraph(Paragraph::with_text("Title").align(TextAlign::Center))
111            .add_paragraph(Paragraph::with_text("Content"))
112            .anchor(TextAnchor::Middle);
113        
114        let xml = tf.to_xml();
115        
116        assert!(xml.contains("<p:txBody>"));
117        assert!(xml.contains("anchor=\"ctr\""));
118        assert!(xml.contains("Title"));
119        assert!(xml.contains("Content"));
120    }
121}