ppt_rs/generator/layouts/
title_content.rs1use super::common::{SlideXmlBuilder, generate_text_props, escape_xml, ShapePosition, TextContent};
4use crate::generator::slide_content::SlideContent;
5use crate::generator::shapes_xml::generate_shape_xml;
6use crate::generator::constants::{
7 TITLE_X, TITLE_Y, TITLE_WIDTH, TITLE_HEIGHT, TITLE_HEIGHT_BIG,
8 CONTENT_X, CONTENT_Y_START, CONTENT_Y_START_BIG,
9 CONTENT_WIDTH, CONTENT_HEIGHT, CONTENT_HEIGHT_BIG,
10 TITLE_FONT_SIZE, CONTENT_FONT_SIZE,
11};
12
13pub struct TitleContentLayout;
15
16impl TitleContentLayout {
17 pub fn generate(content: &SlideContent) -> String {
19 let title_size = content.title_size.unwrap_or(TITLE_FONT_SIZE / 100 * 100);
20 let content_size = content.content_size.unwrap_or(CONTENT_FONT_SIZE / 100 * 100);
21
22 let title_props = generate_text_props(
23 title_size,
24 content.title_bold,
25 content.title_italic,
26 content.title_underline,
27 content.title_color.as_deref(),
28 );
29
30 let content_props = generate_text_props(
31 content_size,
32 content.content_bold,
33 content.content_italic,
34 content.content_underline,
35 content.content_color.as_deref(),
36 );
37
38 let mut builder = SlideXmlBuilder::new()
39 .start_slide_with_bg()
40 .start_sp_tree()
41 .add_title(2, ShapePosition::new(TITLE_X, TITLE_Y, TITLE_WIDTH, TITLE_HEIGHT), TextContent::new(&content.title, &title_props), "title");
42
43 if let Some(ref table) = content.table {
45 builder = builder.raw(&crate::generator::table::generate_table_xml(table, 3));
46 } else if !content.bullets.is_empty() {
47 builder = builder.start_content_body(3, CONTENT_X, CONTENT_Y_START, CONTENT_WIDTH, CONTENT_HEIGHT);
49 for bullet in &content.bullets {
50 builder = builder.add_bullet_with_style(&bullet.text, &content_props, bullet.level, bullet.style);
51 }
52 builder = builder.end_content_body();
53 } else if !content.content.is_empty() {
54 builder = builder.start_content_body(3, CONTENT_X, CONTENT_Y_START, CONTENT_WIDTH, CONTENT_HEIGHT);
56 for bullet in &content.content {
57 builder = builder.add_bullet_with_style(bullet, &content_props, 0, content.bullet_style);
58 }
59 builder = builder.end_content_body();
60 }
61
62 for (i, shape) in content.shapes.iter().enumerate() {
64 builder = builder.raw("\n").raw(&generate_shape_xml(shape, (i + 10) as u32));
65 }
66
67 let image_start_id = 20 + content.shapes.len();
69 for (i, image) in content.images.iter().enumerate() {
70 builder = builder.raw(&format!(
71 r#"
72<p:sp>
73<p:nvSpPr>
74<p:cNvPr id="{}" name="Image Placeholder: {}"/>
75<p:cNvSpPr/>
76<p:nvPr/>
77</p:nvSpPr>
78<p:spPr>
79<a:xfrm>
80<a:off x="{}" y="{}"/>
81<a:ext cx="{}" cy="{}"/>
82</a:xfrm>
83<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
84<a:solidFill><a:srgbClr val="E0E0E0"/></a:solidFill>
85<a:ln w="12700"><a:solidFill><a:srgbClr val="808080"/></a:solidFill></a:ln>
86</p:spPr>
87<p:txBody>
88<a:bodyPr wrap="square" rtlCol="0" anchor="ctr"/>
89<a:lstStyle/>
90<a:p>
91<a:pPr algn="ctr"/>
92<a:r>
93<a:rPr lang="en-US" sz="1400"/>
94<a:t>📷 {}</a:t>
95</a:r>
96</a:p>
97</p:txBody>
98</p:sp>"#,
99 image_start_id + i,
100 escape_xml(&image.filename),
101 image.x,
102 image.y,
103 image.width,
104 image.height,
105 escape_xml(&image.filename)
106 ));
107 }
108
109 builder
110 .end_sp_tree()
111 .end_slide()
112 .build()
113 }
114}
115
116pub struct TitleBigContentLayout;
118
119impl TitleBigContentLayout {
120 pub fn generate(content: &SlideContent) -> String {
122 let title_size = content.title_size.unwrap_or(TITLE_FONT_SIZE / 100 * 100);
123 let content_size = content.content_size.unwrap_or(CONTENT_FONT_SIZE / 100 * 100);
124
125 let title_props = generate_text_props(
126 title_size,
127 content.title_bold,
128 content.title_italic,
129 content.title_underline,
130 content.title_color.as_deref(),
131 );
132
133 let content_props = generate_text_props(
134 content_size,
135 content.content_bold,
136 content.content_italic,
137 content.content_underline,
138 content.content_color.as_deref(),
139 );
140
141 let mut builder = SlideXmlBuilder::new()
142 .start_slide_with_bg()
143 .start_sp_tree()
144 .add_title(2, ShapePosition::new(TITLE_X, TITLE_Y, TITLE_WIDTH, TITLE_HEIGHT_BIG), TextContent::new(&content.title, &title_props), "title");
145
146 if !content.bullets.is_empty() {
147 builder = builder.start_content_body(3, CONTENT_X, CONTENT_Y_START_BIG, CONTENT_WIDTH, CONTENT_HEIGHT_BIG);
148 for bullet in &content.bullets {
149 builder = builder.add_bullet_with_style(&bullet.text, &content_props, bullet.level, bullet.style);
150 }
151 builder = builder.end_content_body();
152 } else if !content.content.is_empty() {
153 builder = builder.start_content_body(3, CONTENT_X, CONTENT_Y_START_BIG, CONTENT_WIDTH, CONTENT_HEIGHT_BIG);
154 for bullet in &content.content {
155 builder = builder.add_bullet_with_style(bullet, &content_props, 0, content.bullet_style);
156 }
157 builder = builder.end_content_body();
158 }
159
160 builder
161 .end_sp_tree()
162 .end_slide()
163 .build()
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_title_content_layout() {
173 let content = SlideContent::new("Test")
174 .add_bullet("Point 1")
175 .add_bullet("Point 2");
176 let xml = TitleContentLayout::generate(&content);
177
178 assert!(xml.contains("Test"));
179 assert!(xml.contains("Point 1"));
180 assert!(xml.contains("Point 2"));
181 }
182
183 #[test]
184 fn test_title_big_content_layout() {
185 let content = SlideContent::new("Big Content")
186 .add_bullet("Item");
187 let xml = TitleBigContentLayout::generate(&content);
188
189 assert!(xml.contains("Big Content"));
190 assert!(xml.contains("5668800")); }
192}