1pub use crate::generator::{
23 SlideContent, SlideLayout,
24 Shape, ShapeType, ShapeFill, ShapeLine,
25 Image,
26 Connector, ConnectorType, ArrowType,
27 create_pptx, create_pptx_with_content,
28 BulletStyle, BulletPoint,
29 TextFormat, FormattedText,
30};
31
32pub use crate::generator::shapes::{
33 GradientFill, GradientDirection, GradientStop,
34};
35
36pub use crate::elements::{Color, RgbColor, Position, Size};
37pub use crate::exc::Result;
38
39pub mod font_sizes {
53 pub const TITLE: u32 = 44;
55 pub const SUBTITLE: u32 = 32;
57 pub const HEADING: u32 = 28;
59 pub const BODY: u32 = 18;
61 pub const SMALL: u32 = 14;
63 pub const CAPTION: u32 = 12;
65 pub const CODE: u32 = 14;
67 pub const LARGE: u32 = 36;
69 pub const XLARGE: u32 = 48;
71
72 pub fn to_emu(pt: u32) -> u32 {
74 pt * 100
75 }
76}
77
78#[macro_export]
80macro_rules! pptx {
81 ($title:expr) => {
82 $crate::prelude::QuickPptx::new($title)
83 };
84}
85
86#[macro_export]
88macro_rules! shape {
89 (rect $x:expr, $y:expr, $w:expr, $h:expr) => {
91 $crate::prelude::Shape::new(
92 $crate::prelude::ShapeType::Rectangle,
93 $crate::prelude::inches($x),
94 $crate::prelude::inches($y),
95 $crate::prelude::inches($w),
96 $crate::prelude::inches($h),
97 )
98 };
99 (circle $x:expr, $y:expr, $size:expr) => {
101 $crate::prelude::Shape::new(
102 $crate::prelude::ShapeType::Circle,
103 $crate::prelude::inches($x),
104 $crate::prelude::inches($y),
105 $crate::prelude::inches($size),
106 $crate::prelude::inches($size),
107 )
108 };
109}
110
111pub fn inches(val: f64) -> u32 {
113 (val * 914400.0) as u32
114}
115
116pub fn cm(val: f64) -> u32 {
118 (val * 360000.0) as u32
119}
120
121pub fn pt(val: f64) -> u32 {
123 (val * 12700.0) as u32
124}
125
126pub struct QuickPptx {
128 title: String,
129 slides: Vec<SlideContent>,
130}
131
132impl QuickPptx {
133 pub fn new(title: &str) -> Self {
135 QuickPptx {
136 title: title.to_string(),
137 slides: Vec::new(),
138 }
139 }
140
141 pub fn slide(mut self, title: &str, bullets: &[&str]) -> Self {
143 let mut slide = SlideContent::new(title);
144 for bullet in bullets {
145 slide = slide.add_bullet(*bullet);
146 }
147 self.slides.push(slide);
148 self
149 }
150
151 pub fn title_slide(mut self, title: &str) -> Self {
153 self.slides.push(SlideContent::new(title));
154 self
155 }
156
157 pub fn content_slide(mut self, slide: SlideContent) -> Self {
159 self.slides.push(slide);
160 self
161 }
162
163 pub fn shapes_slide(mut self, title: &str, shapes: Vec<Shape>) -> Self {
165 let slide = SlideContent::new(title).with_shapes(shapes);
166 self.slides.push(slide);
167 self
168 }
169
170 pub fn build(self) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>> {
172 if self.slides.is_empty() {
173 create_pptx(&self.title, 1)
175 } else {
176 create_pptx_with_content(&self.title, self.slides)
177 }
178 }
179
180 pub fn save(self, path: &str) -> std::result::Result<(), Box<dyn std::error::Error>> {
182 let data = self.build()?;
183 std::fs::write(path, data)?;
184 Ok(())
185 }
186}
187
188
189pub mod shapes {
191 use super::*;
192
193 pub fn rect(x: f64, y: f64, width: f64, height: f64) -> Shape {
195 Shape::new(ShapeType::Rectangle, inches(x), inches(y), inches(width), inches(height))
196 }
197
198 pub fn rect_emu(x: u32, y: u32, width: u32, height: u32) -> Shape {
200 Shape::new(ShapeType::Rectangle, x, y, width, height)
201 }
202
203 pub fn circle(x: f64, y: f64, diameter: f64) -> Shape {
205 Shape::new(ShapeType::Circle, inches(x), inches(y), inches(diameter), inches(diameter))
206 }
207
208 pub fn circle_emu(x: u32, y: u32, diameter: u32) -> Shape {
210 Shape::new(ShapeType::Circle, x, y, diameter, diameter)
211 }
212
213 pub fn rounded_rect(x: f64, y: f64, width: f64, height: f64) -> Shape {
215 Shape::new(ShapeType::RoundedRectangle, inches(x), inches(y), inches(width), inches(height))
216 }
217
218 pub fn text_box(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
220 Shape::new(ShapeType::Rectangle, inches(x), inches(y), inches(width), inches(height))
221 .with_text(text)
222 }
223
224 pub fn colored(shape: Shape, fill: &str, line: Option<&str>) -> Shape {
226 let mut s = shape.with_fill(ShapeFill::new(fill));
227 if let Some(l) = line {
228 s = s.with_line(ShapeLine::new(l, 12700));
229 }
230 s
231 }
232
233 pub fn gradient(shape: Shape, start: &str, end: &str, direction: GradientDirection) -> Shape {
235 shape.with_gradient(GradientFill::linear(start, end, direction))
236 }
237
238 pub fn arrow_right(x: f64, y: f64, width: f64, height: f64) -> Shape {
240 Shape::new(ShapeType::RightArrow, inches(x), inches(y), inches(width), inches(height))
241 }
242
243 pub fn arrow_left(x: f64, y: f64, width: f64, height: f64) -> Shape {
245 Shape::new(ShapeType::LeftArrow, inches(x), inches(y), inches(width), inches(height))
246 }
247
248 pub fn arrow_up(x: f64, y: f64, width: f64, height: f64) -> Shape {
250 Shape::new(ShapeType::UpArrow, inches(x), inches(y), inches(width), inches(height))
251 }
252
253 pub fn arrow_down(x: f64, y: f64, width: f64, height: f64) -> Shape {
255 Shape::new(ShapeType::DownArrow, inches(x), inches(y), inches(width), inches(height))
256 }
257
258 pub fn diamond(x: f64, y: f64, size: f64) -> Shape {
260 Shape::new(ShapeType::Diamond, inches(x), inches(y), inches(size), inches(size))
261 }
262
263 pub fn triangle(x: f64, y: f64, width: f64, height: f64) -> Shape {
265 Shape::new(ShapeType::Triangle, inches(x), inches(y), inches(width), inches(height))
266 }
267
268 pub fn star(x: f64, y: f64, size: f64) -> Shape {
270 Shape::new(ShapeType::Star5, inches(x), inches(y), inches(size), inches(size))
271 }
272
273 pub fn heart(x: f64, y: f64, size: f64) -> Shape {
275 Shape::new(ShapeType::Heart, inches(x), inches(y), inches(size), inches(size))
276 }
277
278 pub fn cloud(x: f64, y: f64, width: f64, height: f64) -> Shape {
280 Shape::new(ShapeType::Cloud, inches(x), inches(y), inches(width), inches(height))
281 }
282
283 pub fn callout(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
285 Shape::new(ShapeType::WedgeRectCallout, inches(x), inches(y), inches(width), inches(height))
286 .with_text(text)
287 }
288
289 pub fn badge(x: f64, y: f64, text: &str, fill_color: &str) -> Shape {
291 Shape::new(ShapeType::RoundedRectangle, inches(x), inches(y), inches(1.5), inches(0.4))
292 .with_fill(ShapeFill::new(fill_color))
293 .with_text(text)
294 }
295
296 pub fn process(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
298 Shape::new(ShapeType::FlowChartProcess, inches(x), inches(y), inches(width), inches(height))
299 .with_text(text)
300 }
301
302 pub fn decision(x: f64, y: f64, size: f64, text: &str) -> Shape {
304 Shape::new(ShapeType::FlowChartDecision, inches(x), inches(y), inches(size), inches(size))
305 .with_text(text)
306 }
307
308 pub fn document(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
310 Shape::new(ShapeType::FlowChartDocument, inches(x), inches(y), inches(width), inches(height))
311 .with_text(text)
312 }
313
314 pub fn data(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
316 Shape::new(ShapeType::FlowChartData, inches(x), inches(y), inches(width), inches(height))
317 .with_text(text)
318 }
319
320 pub fn terminator(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
322 Shape::new(ShapeType::FlowChartTerminator, inches(x), inches(y), inches(width), inches(height))
323 .with_text(text)
324 }
325}
326
327pub mod colors {
329 pub const RED: &str = "FF0000";
330 pub const GREEN: &str = "00FF00";
331 pub const BLUE: &str = "0000FF";
332 pub const WHITE: &str = "FFFFFF";
333 pub const BLACK: &str = "000000";
334 pub const GRAY: &str = "808080";
335 pub const LIGHT_GRAY: &str = "D3D3D3";
336 pub const DARK_GRAY: &str = "404040";
337 pub const YELLOW: &str = "FFFF00";
338 pub const ORANGE: &str = "FFA500";
339 pub const PURPLE: &str = "800080";
340 pub const CYAN: &str = "00FFFF";
341 pub const MAGENTA: &str = "FF00FF";
342 pub const NAVY: &str = "000080";
343 pub const TEAL: &str = "008080";
344 pub const OLIVE: &str = "808000";
345
346 pub const CORPORATE_BLUE: &str = "1565C0";
348 pub const CORPORATE_GREEN: &str = "2E7D32";
349 pub const CORPORATE_RED: &str = "C62828";
350 pub const CORPORATE_ORANGE: &str = "EF6C00";
351
352 pub const MATERIAL_RED: &str = "F44336";
354 pub const MATERIAL_PINK: &str = "E91E63";
355 pub const MATERIAL_PURPLE: &str = "9C27B0";
356 pub const MATERIAL_INDIGO: &str = "3F51B5";
357 pub const MATERIAL_BLUE: &str = "2196F3";
358 pub const MATERIAL_CYAN: &str = "00BCD4";
359 pub const MATERIAL_TEAL: &str = "009688";
360 pub const MATERIAL_GREEN: &str = "4CAF50";
361 pub const MATERIAL_LIME: &str = "CDDC39";
362 pub const MATERIAL_AMBER: &str = "FFC107";
363 pub const MATERIAL_ORANGE: &str = "FF9800";
364 pub const MATERIAL_BROWN: &str = "795548";
365 pub const MATERIAL_GRAY: &str = "9E9E9E";
366
367 pub const CARBON_BLUE_60: &str = "0043CE";
369 pub const CARBON_BLUE_40: &str = "4589FF";
370 pub const CARBON_GRAY_100: &str = "161616";
371 pub const CARBON_GRAY_80: &str = "393939";
372 pub const CARBON_GRAY_20: &str = "E0E0E0";
373 pub const CARBON_GREEN_50: &str = "24A148";
374 pub const CARBON_RED_60: &str = "DA1E28";
375 pub const CARBON_PURPLE_60: &str = "8A3FFC";
376}
377
378pub mod themes {
380 #[derive(Debug, Clone)]
382 pub struct Theme {
383 pub name: &'static str,
384 pub primary: &'static str,
385 pub secondary: &'static str,
386 pub accent: &'static str,
387 pub background: &'static str,
388 pub text: &'static str,
389 pub light: &'static str,
390 pub dark: &'static str,
391 }
392
393 pub const CORPORATE: Theme = Theme {
395 name: "Corporate",
396 primary: "1565C0",
397 secondary: "1976D2",
398 accent: "FF6F00",
399 background: "FFFFFF",
400 text: "212121",
401 light: "E3F2FD",
402 dark: "0D47A1",
403 };
404
405 pub const MODERN: Theme = Theme {
407 name: "Modern",
408 primary: "212121",
409 secondary: "757575",
410 accent: "00BCD4",
411 background: "FAFAFA",
412 text: "212121",
413 light: "F5F5F5",
414 dark: "424242",
415 };
416
417 pub const VIBRANT: Theme = Theme {
419 name: "Vibrant",
420 primary: "E91E63",
421 secondary: "9C27B0",
422 accent: "FF9800",
423 background: "FFFFFF",
424 text: "212121",
425 light: "FCE4EC",
426 dark: "880E4F",
427 };
428
429 pub const DARK: Theme = Theme {
431 name: "Dark",
432 primary: "BB86FC",
433 secondary: "03DAC6",
434 accent: "CF6679",
435 background: "121212",
436 text: "FFFFFF",
437 light: "1E1E1E",
438 dark: "000000",
439 };
440
441 pub const NATURE: Theme = Theme {
443 name: "Nature",
444 primary: "2E7D32",
445 secondary: "4CAF50",
446 accent: "8BC34A",
447 background: "FFFFFF",
448 text: "1B5E20",
449 light: "E8F5E9",
450 dark: "1B5E20",
451 };
452
453 pub const TECH: Theme = Theme {
455 name: "Tech",
456 primary: "0D47A1",
457 secondary: "1976D2",
458 accent: "00E676",
459 background: "FAFAFA",
460 text: "263238",
461 light: "E3F2FD",
462 dark: "01579B",
463 };
464
465 pub const CARBON: Theme = Theme {
467 name: "Carbon",
468 primary: "0043CE",
469 secondary: "4589FF",
470 accent: "24A148",
471 background: "FFFFFF",
472 text: "161616",
473 light: "E0E0E0",
474 dark: "161616",
475 };
476
477 pub fn all() -> Vec<Theme> {
479 vec![CORPORATE, MODERN, VIBRANT, DARK, NATURE, TECH, CARBON]
480 }
481}
482
483pub mod layouts {
485 pub const SLIDE_WIDTH: u32 = 9144000; pub const SLIDE_HEIGHT: u32 = 6858000; pub const MARGIN: u32 = 457200; pub const MARGIN_SMALL: u32 = 228600; pub const MARGIN_LARGE: u32 = 914400; pub fn center_x(shape_width: u32) -> u32 {
496 (SLIDE_WIDTH - shape_width) / 2
497 }
498
499 pub fn center_y(shape_height: u32) -> u32 {
501 (SLIDE_HEIGHT - shape_height) / 2
502 }
503
504 pub fn center(shape_width: u32, shape_height: u32) -> (u32, u32) {
506 (center_x(shape_width), center_y(shape_height))
507 }
508
509 pub fn grid(rows: usize, cols: usize, cell_width: u32, cell_height: u32) -> Vec<(u32, u32)> {
512 let mut positions = Vec::new();
513 let total_width = cell_width * cols as u32;
514 let total_height = cell_height * rows as u32;
515 let start_x = center_x(total_width);
516 let start_y = center_y(total_height);
517
518 for row in 0..rows {
519 for col in 0..cols {
520 let x = start_x + (col as u32 * cell_width);
521 let y = start_y + (row as u32 * cell_height);
522 positions.push((x, y));
523 }
524 }
525 positions
526 }
527
528 pub fn stack_horizontal(count: usize, shape_width: u32, spacing: u32, y: u32) -> Vec<(u32, u32)> {
530 let total_width = (shape_width * count as u32) + (spacing * (count - 1) as u32);
531 let start_x = center_x(total_width);
532
533 (0..count)
534 .map(|i| (start_x + (i as u32 * (shape_width + spacing)), y))
535 .collect()
536 }
537
538 pub fn stack_vertical(count: usize, shape_height: u32, spacing: u32, x: u32) -> Vec<(u32, u32)> {
540 let total_height = (shape_height * count as u32) + (spacing * (count - 1) as u32);
541 let start_y = center_y(total_height);
542
543 (0..count)
544 .map(|i| (x, start_y + (i as u32 * (shape_height + spacing))))
545 .collect()
546 }
547
548 pub fn distribute_horizontal(count: usize, shape_width: u32, y: u32) -> Vec<(u32, u32)> {
550 if count == 0 {
551 return vec![];
552 }
553 if count == 1 {
554 return vec![(center_x(shape_width), y)];
555 }
556
557 let usable_width = SLIDE_WIDTH - (2 * MARGIN);
558 let spacing = (usable_width - (shape_width * count as u32)) / (count as u32 - 1);
559
560 (0..count)
561 .map(|i| (MARGIN + (i as u32 * (shape_width + spacing)), y))
562 .collect()
563 }
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569
570 #[test]
571 fn test_quick_pptx() {
572 let result = QuickPptx::new("Test")
573 .slide("Slide 1", &["Point 1", "Point 2"])
574 .build();
575 assert!(result.is_ok());
576 }
577
578 #[test]
579 fn test_inches_conversion() {
580 assert_eq!(inches(1.0), 914400);
581 assert_eq!(cm(2.54), 914400); }
583
584 #[test]
585 fn test_shape_builders() {
586 let rect = shapes::rect(1.0, 1.0, 2.0, 1.0);
587 assert_eq!(rect.width, inches(2.0));
588
589 let circle = shapes::circle(1.0, 1.0, 1.0);
590 assert_eq!(circle.width, circle.height);
591 }
592
593 #[test]
594 fn test_arrow_shapes() {
595 let arrow = shapes::arrow_right(1.0, 1.0, 2.0, 1.0);
596 assert_eq!(arrow.width, inches(2.0));
597
598 let up = shapes::arrow_up(1.0, 1.0, 1.0, 2.0);
599 assert_eq!(up.height, inches(2.0));
600 }
601
602 #[test]
603 fn test_flowchart_shapes() {
604 let process = shapes::process(1.0, 1.0, 2.0, 1.0, "Process");
605 assert!(process.text.is_some());
606
607 let decision = shapes::decision(1.0, 1.0, 1.5, "Yes/No");
608 assert!(decision.text.is_some());
609 }
610
611 #[test]
612 fn test_badge_shape() {
613 let badge = shapes::badge(1.0, 1.0, "NEW", colors::MATERIAL_GREEN);
614 assert!(badge.text.is_some());
615 assert!(badge.fill.is_some());
616 }
617
618 #[test]
619 fn test_themes() {
620 let all_themes = crate::prelude::themes::all();
621 assert_eq!(all_themes.len(), 7);
622
623 assert_eq!(crate::prelude::themes::CORPORATE.name, "Corporate");
624 assert_eq!(crate::prelude::themes::DARK.background, "121212");
625 }
626
627 #[test]
628 fn test_layouts_center() {
629 let (x, y) = crate::prelude::layouts::center(1000000, 500000);
630 assert!(x > 0);
631 assert!(y > 0);
632
633 assert_eq!(x, (crate::prelude::layouts::SLIDE_WIDTH - 1000000) / 2);
635 assert_eq!(y, (crate::prelude::layouts::SLIDE_HEIGHT - 500000) / 2);
636 }
637
638 #[test]
639 fn test_layouts_grid() {
640 let positions = crate::prelude::layouts::grid(2, 3, 1000000, 800000);
641 assert_eq!(positions.len(), 6);
642 }
643
644 #[test]
645 fn test_layouts_stack_horizontal() {
646 let positions = crate::prelude::layouts::stack_horizontal(4, 500000, 100000, 2000000);
647 assert_eq!(positions.len(), 4);
648
649 for i in 1..positions.len() {
651 let diff = positions[i].0 - positions[i-1].0;
652 assert_eq!(diff, 600000); }
654 }
655
656 #[test]
657 fn test_layouts_distribute_horizontal() {
658 let positions = crate::prelude::layouts::distribute_horizontal(3, 500000, 2000000);
659 assert_eq!(positions.len(), 3);
660 }
661
662 #[test]
663 fn test_material_colors() {
664 assert_eq!(colors::MATERIAL_RED, "F44336");
665 assert_eq!(colors::MATERIAL_BLUE, "2196F3");
666 assert_eq!(colors::MATERIAL_GREEN, "4CAF50");
667 }
668
669 #[test]
670 fn test_carbon_colors() {
671 assert_eq!(colors::CARBON_BLUE_60, "0043CE");
672 assert_eq!(colors::CARBON_GRAY_100, "161616");
673 }
674}