ppt_rs/generator/table/
format.rs1use super::cell::{CellAlign, CellVAlign, TableCell};
4use crate::core::escape_xml;
5use crate::generator::slide_content::table_merge::CellMergeState;
6use crate::generator::text::{color_to_xml, TextFormat};
7
8impl TableCell {
9 pub fn to_text_format(&self) -> TextFormat {
11 let mut format = TextFormat::new();
12 if self.bold {
13 format = format.bold();
14 }
15 if self.italic {
16 format = format.italic();
17 }
18 if self.underline {
19 format = format.underline();
20 }
21 if let Some(ref color) = self.text_color {
22 format = format.color(color);
23 }
24 if let Some(size) = self.font_size {
25 format = format.font_size(size);
26 }
27 if let Some(ref family) = self.font_family {
28 format = format.font_family(family);
29 }
30 format
31 }
32}
33
34pub fn merge_attrs_from_cell(cell: &TableCell) -> String {
36 if cell.h_merge {
37 return CellMergeState::HMerge.to_xml_attrs();
38 }
39 if cell.v_merge {
40 return CellMergeState::VMerge.to_xml_attrs();
41 }
42
43 let col_span = cell.grid_span.unwrap_or(1) as usize;
44 let row_span = cell.row_span.unwrap_or(1) as usize;
45 CellMergeState::Anchor { row_span, col_span }.to_xml_attrs()
46}
47
48pub fn run_properties_xml(cell: &TableCell) -> String {
50 let format = cell.to_text_format();
51 let attrs = format.to_xml_attrs();
52 let color_xml = cell
53 .text_color
54 .as_ref()
55 .map(|color| color_to_xml(color))
56 .unwrap_or_default();
57 let font_xml = cell
58 .font_family
59 .as_ref()
60 .map(|font| format!(r#"<a:latin typeface="{}"/>"#, escape_xml(font)))
61 .unwrap_or_default();
62
63 if color_xml.is_empty() && font_xml.is_empty() {
64 format!(r#"<a:rPr lang="en-US" dirty="0"{attrs}/>"#)
65 } else {
66 format!(
67 r#"<a:rPr lang="en-US" dirty="0"{attrs}>{color_xml}{font_xml}</a:rPr>"#
68 )
69 }
70}
71
72pub fn paragraph_props_xml(cell: &TableCell) -> String {
74 if cell.align == CellAlign::Center {
75 String::new()
76 } else {
77 format!(r#"<a:pPr algn="{}"/>"#, cell.align.as_str())
78 }
79}
80
81pub fn body_props_xml(_cell: &TableCell) -> String {
83 r#"<a:bodyPr/>"#.to_string()
85}
86
87pub fn tc_properties_xml(cell: &TableCell) -> String {
89 let anchor = if cell.valign == CellVAlign::Middle {
90 String::new()
91 } else {
92 format!(r#" anchor="{}""#, cell.valign.as_str())
93 };
94
95 if let Some(ref background) = cell.background_color {
96 format!(
97 r#"<a:tcPr{anchor}>{}</a:tcPr>"#,
98 color_to_xml(background)
99 )
100 } else if anchor.is_empty() {
101 "<a:tcPr/>".to_string()
102 } else {
103 format!(r#"<a:tcPr{anchor}/>"#)
104 }
105}
106
107pub fn generate_cell_xml(cell: &TableCell) -> String {
110 let merge_attrs = merge_attrs_from_cell(cell);
111
112 if cell.h_merge || cell.v_merge {
113 return format!(
114 r#"<a:tc{merge_attrs}><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr/></a:p></a:txBody><a:tcPr/></a:tc>"#
115 );
116 }
117
118 let body_pr = body_props_xml(cell);
119 let paragraph_props = paragraph_props_xml(cell);
120 let run_props = run_properties_xml(cell);
121 let text = escape_xml(&cell.text);
122 let tc_pr = tc_properties_xml(cell);
123 let paragraph = if cell.text.is_empty() {
124 r#"<a:p><a:endParaRPr lang="en-US"/></a:p>"#.to_string()
125 } else {
126 format!(
127 r#"<a:p>{paragraph_props}<a:r>{run_props}<a:t>{text}</a:t></a:r></a:p>"#
128 )
129 };
130
131 format!(
132 r#"<a:tc{merge_attrs}><a:txBody>{body_pr}<a:lstStyle/>{paragraph}</a:txBody>{tc_pr}</a:tc>"#
133 )
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_run_properties_bold() {
142 let cell = TableCell::new("Bold").bold();
143 let xml = run_properties_xml(&cell);
144 assert!(xml.contains(r#"b="1""#));
145 }
146
147 #[test]
148 fn test_run_properties_uses_color_helper() {
149 let cell = TableCell::new("Red").text_color("FF0000");
150 let xml = run_properties_xml(&cell);
151 assert!(xml.contains("FF0000"));
152 assert!(xml.contains("solidFill"));
153 }
154
155 #[test]
156 fn test_tc_properties_background_and_valign() {
157 let cell = TableCell::new("Top").background_color("0000FF").valign_top();
158 let xml = tc_properties_xml(&cell);
159 assert!(xml.contains("0000FF"));
160 assert!(xml.contains(r#"anchor="t""#));
161 }
162
163 #[test]
164 fn test_generate_cell_alignment() {
165 let cell = TableCell::new("Left").align_left();
166 let xml = generate_cell_xml(&cell);
167 assert!(xml.contains(r#"<a:pPr algn="l"/>"#));
168 }
169
170 #[test]
171 fn test_generate_cell_wrap_disabled() {
172 let cell = TableCell::new("No wrap").wrap(false);
173 let xml = generate_cell_xml(&cell);
174 assert!(xml.contains("<a:bodyPr/>"));
175 assert!(!xml.contains(r#"wrap=""#));
176 }
177
178 #[test]
179 fn test_generate_empty_cell_uses_end_para_rpr() {
180 let cell = TableCell::new("");
181 let xml = generate_cell_xml(&cell);
182 assert!(xml.contains("<a:endParaRPr"));
183 assert!(!xml.contains("<a:t></a:t>"));
184 }
185
186 #[test]
187 fn test_merge_attrs_uses_cell_merge_state() {
188 let cell = TableCell::new("").h_merge();
189 assert!(merge_attrs_from_cell(&cell).contains("hMerge"));
190 }
191}