1use ppt_rs::generator::{
14 create_pptx_with_content, SlideContent, SlideLayout,
15 Shape, ShapeType, ShapeFill, ShapeLine,
16};
17use ppt_rs::core::{Dimension, FlexPosition, FlexSize, SLIDE_WIDTH_EMU, SLIDE_HEIGHT_EMU};
18use ppt_rs::prelude::shapes;
19use std::fs;
20
21fn main() -> Result<(), Box<dyn std::error::Error>> {
22 println!("╔══════════════════════════════════════════════════════════════╗");
23 println!("║ Dimension API Demo — Flexible Positioning & Sizing ║");
24 println!("╚══════════════════════════════════════════════════════════════╝\n");
25
26 let mut slides = Vec::new();
27
28 slides.push(
32 SlideContent::new("Dimension API — Flexible Positioning & Sizing")
33 .layout(SlideLayout::CenteredTitle)
34 .title_size(44)
35 .title_bold(true)
36 .title_color("1F497D")
37 );
38
39 println!("📏 Slide 2: All Unit Types");
43
44 let emu_shape = Shape::from_dimensions(ShapeType::Rectangle,
46 Dimension::Emu(457200), Dimension::Inches(1.5),
47 Dimension::Emu(1371600), Dimension::Inches(0.8),
48 ).with_fill(ShapeFill::new("1565C0")).with_text("EMU");
49
50 let inch_shape = Shape::from_dimensions(ShapeType::Rectangle,
51 Dimension::Inches(2.0), Dimension::Inches(1.5),
52 Dimension::Inches(1.5), Dimension::Inches(0.8),
53 ).with_fill(ShapeFill::new("2E7D32")).with_text("Inches");
54
55 let cm_shape = Shape::from_dimensions(ShapeType::Rectangle,
56 Dimension::Cm(9.0), Dimension::Inches(1.5),
57 Dimension::Cm(3.81), Dimension::Inches(0.8),
58 ).with_fill(ShapeFill::new("C62828")).with_text("Cm");
59
60 let pt_shape = Shape::from_dimensions(ShapeType::Rectangle,
61 Dimension::Pt(324.0), Dimension::Inches(1.5),
62 Dimension::Pt(108.0), Dimension::Inches(0.8),
63 ).with_fill(ShapeFill::new("7B1FA2")).with_text("Pt");
64
65 let ratio_shape = Shape::from_dimensions(ShapeType::Rectangle,
66 Dimension::Ratio(0.52), Dimension::Inches(1.5),
67 Dimension::Ratio(0.15), Dimension::Inches(0.8),
68 ).with_fill(ShapeFill::new("EF6C00")).with_text("Ratio");
69
70 let pct_shape = Shape::from_dimensions(ShapeType::Rectangle,
71 Dimension::percent(69.0), Dimension::Inches(1.5),
72 Dimension::percent(15.0), Dimension::Inches(0.8),
73 ).with_fill(ShapeFill::new("00838F")).with_text("Percent");
74
75 let label = Shape::from_dimensions(ShapeType::Rectangle,
77 Dimension::Inches(0.5), Dimension::Inches(0.8),
78 Dimension::Inches(9.0), Dimension::Inches(0.5),
79 ).with_text("Each shape below uses a different unit type for X position:");
80
81 slides.push(
82 SlideContent::new("All Dimension Unit Types")
83 .layout(SlideLayout::TitleOnly)
84 .title_color("1F497D").title_bold(true)
85 .add_shape(label)
86 .add_shape(emu_shape)
87 .add_shape(inch_shape)
88 .add_shape(cm_shape)
89 .add_shape(pt_shape)
90 .add_shape(ratio_shape)
91 .add_shape(pct_shape)
92 );
93
94 println!("📐 Slide 3: Ratio-Based Grid (auto-adapts to slide size)");
98
99 let margin = 0.03; let gap = 0.02; let cell_w = (1.0 - 2.0 * margin - 2.0 * gap) / 3.0;
102 let cell_h = (0.7 - 2.0 * gap) / 3.0; let y_start = 0.22; let colors = [
106 "1565C0", "2E7D32", "C62828",
107 "7B1FA2", "EF6C00", "00838F",
108 "AD1457", "4E342E", "37474F",
109 ];
110 let labels = [
111 "Top-Left", "Top-Center", "Top-Right",
112 "Mid-Left", "Mid-Center", "Mid-Right",
113 "Bot-Left", "Bot-Center", "Bot-Right",
114 ];
115
116 let mut grid_slide = SlideContent::new("Ratio-Based 3x3 Grid Layout")
117 .layout(SlideLayout::TitleOnly)
118 .title_color("1F497D").title_bold(true);
119
120 for row in 0..3 {
121 for col in 0..3 {
122 let idx = row * 3 + col;
123 let x = margin + col as f64 * (cell_w + gap);
124 let y = y_start + row as f64 * (cell_h + gap);
125 let shape = Shape::from_dimensions(ShapeType::RoundedRectangle,
126 Dimension::Ratio(x), Dimension::Ratio(y),
127 Dimension::Ratio(cell_w), Dimension::Ratio(cell_h),
128 ).with_fill(ShapeFill::new(colors[idx])).with_text(labels[idx]);
129 grid_slide = grid_slide.add_shape(shape);
130 }
131 }
132
133 slides.push(grid_slide);
134
135 println!("🔀 Slide 4: Mixed-Unit Positioning");
139
140 let title_box = Shape::from_dimensions(ShapeType::RoundedRectangle,
142 Dimension::Inches(0.5), Dimension::Inches(1.5),
143 Dimension::Ratio(0.9), Dimension::Cm(2.0),
144 ).with_fill(ShapeFill::new("1F497D")).with_text("Inches X + Ratio Width + Cm Height");
145
146 let content_box = Shape::from_dimensions(ShapeType::Rectangle,
148 Dimension::Cm(2.0), Dimension::Cm(6.0),
149 Dimension::Pt(432.0), Dimension::Pt(108.0), ).with_fill(ShapeFill::new("2E7D32")).with_text("Cm position + Pt size");
151
152 let footer_box = Shape::from_dimensions(ShapeType::Rectangle,
154 Dimension::percent(5.0), Dimension::percent(75.0),
155 Dimension::percent(90.0), Dimension::percent(10.0),
156 ).with_fill(ShapeFill::new("C62828")).with_text("100% percent-based");
157
158 let sidebar = Shape::from_dimensions(ShapeType::Rectangle,
160 Dimension::Emu(8000000), Dimension::Inches(1.5),
161 Dimension::Inches(1.0), Dimension::Ratio(0.6),
162 ).with_fill(ShapeFill::new("7B1FA2")).with_text("EMU + Inches + Ratio");
163
164 slides.push(
165 SlideContent::new("Mixed-Unit Positioning")
166 .layout(SlideLayout::TitleOnly)
167 .title_color("1F497D").title_bold(true)
168 .add_shape(title_box)
169 .add_shape(content_box)
170 .add_shape(footer_box)
171 .add_shape(sidebar)
172 );
173
174 println!("🔗 Slide 5: Fluent Chaining API");
178
179 let shape1 = Shape::new(ShapeType::Ellipse, 0, 0, 0, 0)
181 .at(Dimension::percent(10.0), Dimension::percent(25.0))
182 .with_dimensions(Dimension::Inches(2.5), Dimension::Inches(2.5))
183 .with_fill(ShapeFill::new("1565C0"))
184 .with_text(".at() + .with_dimensions()");
185
186 let shape2 = Shape::new(ShapeType::RoundedRectangle, 0, 0, 0, 0)
187 .at(Dimension::Inches(4.0), Dimension::Cm(5.0))
188 .with_dimensions(Dimension::Ratio(0.3), Dimension::Inches(2.0))
189 .with_fill(ShapeFill::new("2E7D32"))
190 .with_line(ShapeLine::new("1B5E20", 25400))
191 .with_text("Chained with fill + line");
192
193 let shape3 = Shape::new(ShapeType::Star5, 0, 0, 0, 0)
194 .at(Dimension::percent(70.0), Dimension::percent(55.0))
195 .with_dimensions(Dimension::Inches(2.0), Dimension::Inches(2.0))
196 .with_fill(ShapeFill::new("FFC107"))
197 .with_rotation(15)
198 .with_text("+ rotation");
199
200 slides.push(
201 SlideContent::new("Fluent .at() and .with_dimensions() Chaining")
202 .layout(SlideLayout::TitleOnly)
203 .title_color("1F497D").title_bold(true)
204 .add_shape(shape1)
205 .add_shape(shape2)
206 .add_shape(shape3)
207 );
208
209 println!("🧰 Slide 6: Prelude Shape Builders");
213
214 let dim_shape = shapes::dim(ShapeType::Diamond,
216 Dimension::percent(5.0), Dimension::percent(25.0),
217 Dimension::percent(25.0), Dimension::percent(35.0),
218 ).with_fill(ShapeFill::new("7B1FA2")).with_text("shapes::dim()");
219
220 let ratio_rect = shapes::rect_ratio(0.35, 0.25, 0.28, 0.35)
222 .with_fill(ShapeFill::new("EF6C00")).with_text("shapes::rect_ratio()");
223
224 let ratio_text = shapes::text_box_ratio(0.68, 0.25, 0.28, 0.35, "shapes::text_box_ratio()")
226 .with_fill(ShapeFill::new("00838F"));
227
228 let inch_rect = shapes::rect(1.0, 5.0, 3.0, 1.0)
230 .with_fill(ShapeFill::new("A5A5A5")).with_text("shapes::rect() (inches)");
231
232 slides.push(
233 SlideContent::new("Prelude Shape Builders")
234 .layout(SlideLayout::TitleOnly)
235 .title_color("1F497D").title_bold(true)
236 .add_shape(dim_shape)
237 .add_shape(ratio_rect)
238 .add_shape(ratio_text)
239 .add_shape(inch_rect)
240 );
241
242 println!("📦 Slide 7: FlexPosition & FlexSize");
246
247 let header_pos = FlexPosition::new(Dimension::percent(5.0), Dimension::percent(20.0));
249 let header_size = FlexSize::new(Dimension::percent(90.0), Dimension::percent(12.0));
250 let (hx, hy) = header_pos.to_emu();
251 let (hw, hh) = header_size.to_emu();
252 let header = Shape::new(ShapeType::RoundedRectangle, hx, hy, hw, hh)
253 .with_fill(ShapeFill::new("1F497D"))
254 .with_text("FlexPosition + FlexSize → header");
255
256 let body_pos = FlexPosition::new(Dimension::percent(5.0), Dimension::percent(35.0));
257 let body_size = FlexSize::new(Dimension::percent(60.0), Dimension::percent(50.0));
258 let (bx, by) = body_pos.to_emu();
259 let (bw, bh) = body_size.to_emu();
260 let body = Shape::new(ShapeType::Rectangle, bx, by, bw, bh)
261 .with_fill(ShapeFill::new("E8EAF6"))
262 .with_line(ShapeLine::new("3F51B5", 12700))
263 .with_text("Body area (60% x 50%)");
264
265 let sidebar_pos = FlexPosition::new(Dimension::percent(68.0), Dimension::percent(35.0));
266 let sidebar_size = FlexSize::new(Dimension::percent(27.0), Dimension::percent(50.0));
267 let (sx, sy) = sidebar_pos.to_emu();
268 let (sw, sh) = sidebar_size.to_emu();
269 let sidebar_shape = Shape::new(ShapeType::Rectangle, sx, sy, sw, sh)
270 .with_fill(ShapeFill::new("FFF3E0"))
271 .with_line(ShapeLine::new("EF6C00", 12700))
272 .with_text("Sidebar (27% x 50%)");
273
274 slides.push(
275 SlideContent::new("FlexPosition & FlexSize — Reusable Layouts")
276 .layout(SlideLayout::TitleOnly)
277 .title_color("1F497D").title_bold(true)
278 .add_shape(header)
279 .add_shape(body)
280 .add_shape(sidebar_shape)
281 );
282
283 println!("📊 Slide 8: Real-World Dashboard");
287
288 let kpi_colors = ["1565C0", "2E7D32", "EF6C00", "7B1FA2"];
290 let kpi_labels = [
291 "Revenue\n$2.14M\n+15%",
292 "Users\n12,450\n+22%",
293 "NPS\n72\n+8 pts",
294 "Uptime\n99.9%\n+0.1%",
295 ];
296
297 let mut dashboard = SlideContent::new("KPI Dashboard — Dimension API")
298 .layout(SlideLayout::TitleOnly)
299 .title_color("1F497D").title_bold(true);
300
301 for i in 0..4 {
302 let x_pct = 3.0 + i as f64 * 24.5;
303 let card = Shape::from_dimensions(ShapeType::RoundedRectangle,
304 Dimension::percent(x_pct), Dimension::percent(22.0),
305 Dimension::percent(22.0), Dimension::percent(30.0),
306 ).with_fill(ShapeFill::new(kpi_colors[i])).with_text(kpi_labels[i]);
307 dashboard = dashboard.add_shape(card);
308 }
309
310 let chart_area = Shape::from_dimensions(ShapeType::Rectangle,
312 Dimension::percent(3.0), Dimension::percent(58.0),
313 Dimension::percent(94.0), Dimension::percent(35.0),
314 ).with_fill(ShapeFill::new("ECEFF1"))
315 .with_line(ShapeLine::new("B0BEC5", 12700))
316 .with_text("Chart Area (94% x 35%)");
317 dashboard = dashboard.add_shape(chart_area);
318
319 slides.push(dashboard);
320
321 println!("📖 Slide 9: Unit Equivalence Reference");
325
326 slides.push(
327 SlideContent::new("Dimension Unit Reference")
328 .layout(SlideLayout::TitleAndContent)
329 .title_color("1F497D").title_bold(true)
330 .add_bullet(&format!("1 inch = {} EMU = Dimension::Inches(1.0)", 914400))
331 .add_bullet(&format!("1 cm = {} EMU = Dimension::Cm(1.0)", 360000))
332 .add_bullet(&format!("1 pt = {} EMU = Dimension::Pt(1.0)", 12700))
333 .add_bullet(&format!("Slide width = {} EMU = 10 inches", SLIDE_WIDTH_EMU))
334 .add_bullet(&format!("Slide height = {} EMU = 7.5 inches", SLIDE_HEIGHT_EMU))
335 .add_bullet("Ratio(0.1) on X = 10% of slide width = 1 inch")
336 .add_bullet("Ratio(0.5) on Y = 50% of slide height = 3.75 inches")
337 .add_bullet("percent(50.0) = Ratio(0.5)")
338 .content_size(22)
339 );
340
341 fs::create_dir_all("examples/output")?;
345 let num_slides = slides.len();
346 let pptx_data = create_pptx_with_content("Dimension API Demo", slides)?;
347 fs::write("examples/output/dimension_demo.pptx", &pptx_data)?;
348
349 println!("\n╔══════════════════════════════════════════════════════════════╗");
350 println!("║ Dimension API Demo Complete ║");
351 println!("╠══════════════════════════════════════════════════════════════╣");
352 println!("║ Output: examples/output/dimension_demo.pptx ║");
353 println!("║ Slides: {} ║", num_slides);
354 println!("║ Size: {} KB ║", pptx_data.len() / 1024);
355 println!("╠══════════════════════════════════════════════════════════════╣");
356 println!("║ Showcased: ║");
357 println!("║ ✓ All 6 unit types: EMU, Inches, Cm, Pt, Ratio, Percent ║");
358 println!("║ ✓ Shape::from_dimensions() constructor ║");
359 println!("║ ✓ Fluent .at() and .with_dimensions() chaining ║");
360 println!("║ ✓ Mixed-unit positioning ║");
361 println!("║ ✓ Prelude helpers: dim(), rect_ratio(), text_box_ratio() ║");
362 println!("║ ✓ FlexPosition & FlexSize structs ║");
363 println!("║ ✓ Ratio-based grid layout (auto-adapts) ║");
364 println!("║ ✓ Real-world KPI dashboard ║");
365 println!("╚══════════════════════════════════════════════════════════════╝");
366
367 Ok(())
368}