use deckmint::layout::{center_in, GridLayoutBuilder, GridTrack};
use deckmint::{GradientFill, GradientStop};
use deckmint::objects::shape::ShapeOptionsBuilder;
use deckmint::objects::table::{TableCell, TableOptionsBuilder};
use deckmint::objects::text::TextOptionsBuilder;
use deckmint::{BarDir, BarGrouping, ChartOptionsBuilder, ChartSeries, LegendPos};
use deckmint::TextRunBuilder;
use deckmint::types::{AnimationEffect, CheckerboardDir, Direction, HyperlinkProps, ShapeVariant, SlideNumberProps, SplitOrientation, StripDir};
use deckmint::{
AlignH, AlignV, ChartType, GlowProps, Presentation, SchemeColor, ShapeType, SlideMasterDef, TabStop,
TextOutlineProps,
};
use deckmint::{
AnimationTrigger, ConnectorOptions, ConnectorType, FieldType, HyperlinkAction,
PatternFill, PatternType, ThemeColorMod, TransitionDir, TransitionProps,
};
const RED_1PX_PNG: &[u8] = &[
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44,
0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90,
0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, 0xF8,
0xCF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xE2, 0x21, 0xBC, 0x33, 0x00, 0x00, 0x00,
0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
];
fn heading(slide: &mut deckmint::Slide, text: &str) {
slide.add_text(
text,
TextOptionsBuilder::new()
.x(0.3).y(0.1).w(9.4).h(0.7)
.font_size(24.0).bold().color("2E4057")
.build(),
);
slide.add_shape(
ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(0.3).y(0.75).w(9.4).h(0.04)
.fill_color("2E4057")
.build(),
);
}
fn main() {
let mut pres = Presentation::new();
pres.title = "deckmint Feature Demo".to_string();
pres.author = "deckmint".to_string();
{
let mut master = SlideMasterDef::default();
master.title = "Feature Demo Master".to_string();
let mut stripe_opts = ShapeOptionsBuilder::new()
.x(0.0).y(7.3).w(13.33).h(0.2)
.fill_color("2E4057")
.build();
use deckmint::objects::SlideObject;
use deckmint::objects::shape::ShapeObject;
master.objects.push(SlideObject::Shape(ShapeObject {
object_name: "MasterStripe".to_string(),
shape_type: ShapeType::Rect,
options: stripe_opts,
text: None,
}));
pres.define_master(master);
}
pres.define_layout("WIDE_16x9", Some(13.33), Some(7.5));
{
let s = pres.add_slide();
heading(s, "Text: Highlight & Underline Styles");
let runs = vec![
TextRunBuilder::new("Normal · ").font_size(20.0).build(),
TextRunBuilder::new("Yellow highlight").font_size(20.0).highlight("FFFF00").build(),
TextRunBuilder::new(" · ").font_size(20.0).build(),
TextRunBuilder::new("Cyan highlight").font_size(20.0).highlight("00FFFF").build(),
];
s.add_text_runs(runs, TextOptionsBuilder::new().x(0.5).y(1.0).w(9.0).h(0.8).build());
let underlines = [
("Single (sng)", "sng"),
("Double (dbl)", "dbl"),
("Dash (dash)", "dash"),
("Dotted", "dotted"),
("Heavy (heavy)", "heavy"),
("Wavy (wavy)", "wavy"),
("Wavy Double (wavyDbl)", "wavyDbl"),
];
for (i, (label, style)) in underlines.iter().enumerate() {
let col = i % 2;
let row = i / 2;
let x = 0.5 + col as f64 * 4.8;
let y = 1.9 + row as f64 * 0.55;
let runs = vec![TextRunBuilder::new(*label).font_size(18.0).underline(*style).build()];
s.add_text_runs(runs, TextOptionsBuilder::new().x(x).y(y).w(4.5).h(0.5).build());
}
}
{
let s = pres.add_slide();
s.set_background_color("1A1A2E");
heading(s, "Text: Glow & Outline");
let glows = [
("Blue glow", "4472C4", "4472C4", 0.7_f64, 8.0_f64),
("Red glow", "FF0000", "FF6B6B", 0.8, 12.0),
("Green glow", "FFFFFF", "00FF88", 0.6, 6.0),
];
for (i, (label, text_color, glow_color, opacity, size)) in glows.iter().enumerate() {
let runs = vec![
TextRunBuilder::new(*label)
.font_size(36.0).bold().color(*text_color)
.glow(GlowProps { size: *size, color: glow_color.to_string(), opacity: *opacity })
.build(),
];
s.add_text_runs(
runs,
TextOptionsBuilder::new()
.x(0.5).y(1.0 + i as f64 * 1.1).w(9.0).h(0.9)
.align(AlignH::Center)
.build(),
);
}
let runs = vec![
TextRunBuilder::new("Outlined text (white fill, black stroke)")
.font_size(28.0).bold().color("FFFFFF")
.outline(TextOutlineProps { color: "000000".to_string(), size: 1.5 })
.build(),
];
s.add_text_runs(
runs,
TextOptionsBuilder::new()
.x(0.5).y(4.4).w(9.0).h(0.9)
.align(AlignH::Center)
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Text: Soft Break · Direction · Tab Stops");
let runs = vec![
TextRunBuilder::new("Line 1 — first line of paragraph").font_size(16.0).build(),
TextRunBuilder::new("Line 2 — soft break before this (same paragraph)")
.font_size(16.0).color("4472C4").soft_break_before().build(),
TextRunBuilder::new("Line 3 — another soft break")
.font_size(16.0).italic().soft_break_before().build(),
];
s.add_text_runs(
runs,
TextOptionsBuilder::new().x(0.5).y(0.9).w(6.5).h(1.8).build(),
);
s.add_text(
"VERTICAL",
TextOptionsBuilder::new()
.x(7.5).y(0.9).w(0.8).h(3.5)
.font_size(16.0).bold().color("C00000")
.text_direction("vert")
.build(),
);
s.add_text(
"↑ vert270",
TextOptionsBuilder::new()
.x(8.5).y(0.9).w(0.9).h(3.5)
.font_size(14.0).color("4472C4")
.text_direction("vert270")
.build(),
);
s.add_text(
"Name\tRole\tScore",
TextOptionsBuilder::new()
.x(0.5).y(2.9).w(9.0).h(0.5)
.font_size(16.0).bold()
.tab_stops(vec![
TabStop { pos_inches: 3.0, align: "l".to_string() },
TabStop { pos_inches: 6.5, align: "r".to_string() },
])
.build(),
);
for (name, role, score) in &[("Alice", "Engineer", "98"), ("Bob", "Designer", "87"), ("Carol", "PM", "92")] {
let row_y = 3.4 + match *name { "Alice" => 0.0, "Bob" => 0.45, _ => 0.9 };
s.add_text(
&format!("{name}\t{role}\t{score}"),
TextOptionsBuilder::new()
.x(0.5).y(row_y).w(9.0).h(0.4)
.font_size(15.0)
.tab_stops(vec![
TabStop { pos_inches: 3.0, align: "l".to_string() },
TabStop { pos_inches: 6.5, align: "r".to_string() },
])
.build(),
);
}
}
{
let s = pres.add_slide();
heading(s, "Shapes: Hyperlink · Shadow rotateWithShape");
s.add_shape(
ShapeType::RoundRect,
ShapeOptionsBuilder::new()
.x(0.5).y(1.0).w(4.0).h(1.0)
.fill_color("4472C4")
.hyperlink(HyperlinkProps {
r_id: 0,
slide: None,
url: Some("https://github.com/gitbrent/PptxGenJS".to_string()),
tooltip: Some("Open PptxGenJS on GitHub".to_string()),
action: None,
})
.build(),
);
s.add_text(
"▶ Click: GitHub link (shape hyperlink)",
TextOptionsBuilder::new()
.x(0.5).y(1.0).w(4.0).h(1.0)
.font_size(14.0).bold().color("FFFFFF")
.valign(AlignV::Middle)
.align(AlignH::Center)
.build(),
);
s.add_shape(
ShapeType::RoundRect,
ShapeOptionsBuilder::new()
.x(5.0).y(1.0).w(4.0).h(1.0)
.fill_color("ED7D31")
.hyperlink(HyperlinkProps {
r_id: 0,
slide: Some(1),
url: None,
tooltip: Some("Jump to slide 1".to_string()),
action: None,
})
.build(),
);
s.add_text(
"▶ Click: Jump to Slide 1 (slide hyperlink)",
TextOptionsBuilder::new()
.x(5.0).y(1.0).w(4.0).h(1.0)
.font_size(14.0).bold().color("FFFFFF")
.valign(AlignV::Middle)
.align(AlignH::Center)
.build(),
);
use deckmint::{ShadowProps, ShadowType};
let shadow_on = ShadowProps {
shadow_type: ShadowType::Outer,
blur: Some(6.0),
offset: Some(4.0),
angle: Some(45.0),
color: Some("000000".to_string()),
opacity: Some(0.5),
rotate_with_shape: true,
};
let shadow_off = ShadowProps {
rotate_with_shape: false,
..shadow_on.clone()
};
s.add_shape(
ShapeType::Pentagon,
ShapeOptionsBuilder::new()
.x(0.5).y(2.5).w(3.0).h(2.0)
.fill_color("70AD47")
.rotate(30.0)
.shadow(shadow_on)
.build(),
);
s.add_text(
"rotateWithShape=true\n(shadow rotates with shape)",
TextOptionsBuilder::new()
.x(0.5).y(4.6).w(3.5).h(0.8)
.font_size(12.0).align(AlignH::Center)
.build(),
);
s.add_shape(
ShapeType::Pentagon,
ShapeOptionsBuilder::new()
.x(5.5).y(2.5).w(3.0).h(2.0)
.fill_color("ED7D31")
.rotate(30.0)
.shadow(shadow_off)
.build(),
);
s.add_text(
"rotateWithShape=false\n(shadow stays fixed)",
TextOptionsBuilder::new()
.x(5.5).y(4.6).w(3.5).h(0.8)
.font_size(12.0).align(AlignH::Center)
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Tables: Per-Row Heights · Cell Hyperlinks");
let mut header = TableCell::new("Feature");
header.options.bold = Some(true);
header.options.fill = Some("2E4057".to_string());
header.options.color = Some("FFFFFF".to_string());
let mut h2 = TableCell::new("Value");
h2.options.bold = Some(true);
h2.options.fill = Some("2E4057".to_string());
h2.options.color = Some("FFFFFF".to_string());
let mut h3 = TableCell::new("Link");
h3.options.bold = Some(true);
h3.options.fill = Some("2E4057".to_string());
h3.options.color = Some("FFFFFF".to_string());
let mut link_cell = TableCell::new("Click me");
link_cell.options.color = Some("4472C4".to_string());
link_cell.options.underline = Some(true);
link_cell.options.hyperlink = Some(HyperlinkProps {
r_id: 0,
slide: None,
url: Some("https://github.com/gitbrent/PptxGenJS".to_string()),
tooltip: Some("Open PptxGenJS".to_string()),
action: None,
});
let rows = vec![
vec![header, h2, h3],
vec![TableCell::new("Row height 0.6\""), TableCell::new("Tall row"), TableCell::new("—")],
vec![TableCell::new("Row height 0.3\""), TableCell::new("Normal row"), TableCell::new("—")],
vec![TableCell::new("Cell hyperlink"), TableCell::new("Underlined blue text"), link_cell],
vec![TableCell::new("Row height 0.8\""), TableCell::new("Extra tall"), TableCell::new("—")],
];
s.add_table(
rows,
TableOptionsBuilder::new()
.x(0.5).y(0.9).w(9.0).h(5.5)
.col_w(vec![3.0, 3.5, 2.5])
.row_h(vec![0.5, 0.6, 0.3, 0.4, 0.8])
.build(),
);
}
{
let s = pres.add_slide();
s.set_background_image(RED_1PX_PNG.to_vec(), "png");
s.add_text(
"Background Image",
TextOptionsBuilder::new()
.x(0.5).y(2.5).w(9.0).h(1.2)
.font_size(40.0).bold()
.align(AlignH::Center).color("FFFFFF")
.build(),
);
s.add_text(
"Slide background set from raw image bytes via set_background_image()",
TextOptionsBuilder::new()
.x(0.5).y(3.8).w(9.0).h(0.8)
.font_size(16.0)
.align(AlignH::Center).color("FFFFFF")
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Slide Master: Bottom Stripe from Master");
s.add_text(
"The dark stripe at the bottom of every slide in this deck\n\
is defined on the slide master via define_master(). It appears on\n\
all slides without being added individually.",
TextOptionsBuilder::new()
.x(0.5).y(1.0).w(9.0).h(2.5)
.font_size(18.0)
.build(),
);
s.add_text(
"Custom slide layouts can also be added with define_layout(name, w, h)\n\
which supports custom slide dimensions.",
TextOptionsBuilder::new()
.x(0.5).y(3.8).w(9.0).h(1.5)
.font_size(16.0).color("555555")
.build(),
);
}
{
let s = pres.add_slide();
s.add_text("Animations: Entrance & Exit", TextOptionsBuilder::new().x(0.5).y(0.2).w(9.0).h(0.6).font_size(26.0).bold().build());
let items: &[(&str, fn() -> AnimationEffect)] = &[
("Appear (entrance)", || AnimationEffect::appear()),
("Disappear (exit)", || AnimationEffect::disappear()),
("Fade In (entrance)", || AnimationEffect::fade_in()),
("Fade Out (exit)", || AnimationEffect::fade_out()),
("Fly In from Left", || AnimationEffect::fly_in(Direction::Left)),
("Fly Out to Right", || AnimationEffect::fly_out(Direction::Right)),
("Fly In from Bottom", || AnimationEffect::fly_in(Direction::Down)),
("Wipe In from Left", || AnimationEffect::wipe_in(Direction::Left)),
("Zoom In (entrance)", || AnimationEffect::zoom_in()),
("Zoom Out (exit)", || AnimationEffect::zoom_out()),
];
for (idx, (label, make)) in items.iter().enumerate() {
let col = (idx % 2) as f64;
let row = (idx / 2) as f64;
s.add_text(
*label,
TextOptionsBuilder::new()
.x(0.4 + col * 4.8).y(1.0 + row * 0.7).w(4.5).h(0.6)
.font_size(16.0)
.animation(make())
.build(),
);
}
}
{
let s = pres.add_slide();
s.add_text("Animations: Emphasis & Split", TextOptionsBuilder::new().x(0.5).y(0.2).w(9.0).h(0.6).font_size(26.0).bold().build());
s.add_text("Spin 360°", TextOptionsBuilder::new()
.x(0.5).y(1.1).w(4.0).h(0.6).font_size(18.0)
.animation(AnimationEffect::spin(360.0)).build());
s.add_text("Spin 180° (half)", TextOptionsBuilder::new()
.x(5.0).y(1.1).w(4.0).h(0.6).font_size(18.0)
.animation(AnimationEffect::spin(180.0)).build());
s.add_text("Pulse", TextOptionsBuilder::new()
.x(0.5).y(1.9).w(4.0).h(0.6).font_size(18.0)
.animation(AnimationEffect::pulse()).build());
s.add_text("Grow/Shrink ×1.5", TextOptionsBuilder::new()
.x(5.0).y(1.9).w(4.0).h(0.6).font_size(18.0)
.animation(AnimationEffect::grow_shrink(1.5)).build());
s.add_text("Split In (Horizontal)", TextOptionsBuilder::new()
.x(0.5).y(2.7).w(4.0).h(0.6).font_size(18.0)
.animation(AnimationEffect::split_in(SplitOrientation::Horizontal)).build());
s.add_text("Split Out (Vertical)", TextOptionsBuilder::new()
.x(5.0).y(2.7).w(4.0).h(0.6).font_size(18.0)
.animation(AnimationEffect::split_out(SplitOrientation::Vertical)).build());
s.add_text("Wipe In from Top", TextOptionsBuilder::new()
.x(0.5).y(3.5).w(4.0).h(0.6).font_size(18.0)
.animation(AnimationEffect::wipe_in(Direction::Up)).build());
s.add_text("Fly In from Top", TextOptionsBuilder::new()
.x(5.0).y(3.5).w(4.0).h(0.6).font_size(18.0)
.animation(AnimationEffect::fly_in(Direction::Up)).build());
}
{
let s = pres.add_slide();
heading(s, "Layout: GridLayout — 3 Equal Columns");
let grid = GridLayoutBuilder::cols_n(3, 0.2)
.origin(0.3, 0.9)
.container(9.4, 4.6)
.build();
let colors = ["4472C4", "ED7D31", "70AD47"];
let labels = ["Column 1\n(Fr 1)", "Column 2\n(Fr 1)", "Column 3\n(Fr 1)"];
for col in 0..3 {
let r = grid.cell(col, 0);
s.add_shape(ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.fill_color(colors[col]).build());
s.add_text(labels[col],
TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.font_size(20.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
}
{
let s = pres.add_slide();
heading(s, "Layout: GridLayout — Mixed Tracks & Spans");
let outer = GridLayoutBuilder::new()
.cols(vec![GridTrack::Fr(1.0)])
.rows(vec![
GridTrack::Inches(0.55), GridTrack::Fr(1.0), GridTrack::Inches(0.25), ])
.row_gap(0.05)
.origin(0.3, 0.85)
.container(9.4, 4.65)
.build();
let content_cell = outer.cell(0, 1);
let inner = GridLayoutBuilder::new()
.cols(vec![GridTrack::Inches(2.2), GridTrack::Fr(1.0), GridTrack::Fr(1.0)])
.rows(vec![GridTrack::Fr(1.0), GridTrack::Fr(1.0)])
.col_gap(0.15)
.row_gap(0.12)
.origin(content_cell.x, content_cell.y)
.container(content_cell.w, content_cell.h)
.build();
let sidebar = inner.span(0, 0, 1, 2);
s.add_shape(ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(sidebar.x).y(sidebar.y).w(sidebar.w).h(sidebar.h)
.fill_color("2E4057").build());
s.add_text("Sidebar\n(fixed 2.2\")",
TextOptionsBuilder::new()
.x(sidebar.x).y(sidebar.y).w(sidebar.w).h(sidebar.h)
.font_size(14.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
let cell_colors = [("4472C4","Top Left"), ("ED7D31","Top Right"),
("70AD47","Bottom Left"), ("9E480E","Bottom Right")];
for (idx, (color, label)) in cell_colors.iter().enumerate() {
let col = 1 + (idx % 2);
let row = idx / 2;
let r = inner.cell(col, row);
s.add_shape(ShapeType::RoundRect,
ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.fill_color(*color).rect_radius(0.05).build());
s.add_text(*label,
TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.font_size(13.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
let footer = outer.cell(0, 2);
let centered_label = center_in(&footer, 4.0, footer.h);
s.add_text("Footer — auto-positioned with center_in()",
TextOptionsBuilder::new()
.x(centered_label.x).y(centered_label.y)
.w(centered_label.w).h(centered_label.h)
.font_size(10.0).italic().color("888888")
.align(AlignH::Center).build());
}
{
let s = pres.add_slide();
heading(s, "Gradient Fills");
let grid = GridLayoutBuilder::grid_n_m(3, 2, 0.18)
.origin(0.3, 0.95)
.container(9.4, 4.55)
.build();
let r = grid.cell(0, 0);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.gradient_fill(GradientFill::two_color(0.0, "4472C4", "ED7D31"))
.build());
s.add_text("Linear 0°\n(left → right)", TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(13.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
let r = grid.cell(1, 0);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.gradient_fill(GradientFill::two_color(90.0, "4472C4", "ED7D31"))
.build());
s.add_text("Linear 90°\n(top → bottom)", TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(13.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
let r = grid.cell(2, 0);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.gradient_fill(GradientFill::two_color(45.0, "4472C4", "ED7D31"))
.build());
s.add_text("Linear 45°\n(diagonal)", TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(13.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
let r = grid.cell(0, 1);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.gradient_fill(GradientFill::linear(90.0, vec![
GradientStop::new("FF0000", 0.0),
GradientStop::new("FFFF00", 50.0),
GradientStop::new("00B050", 100.0),
]))
.build());
s.add_text("Multi-stop\n(3 colours)", TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(13.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
let r = grid.cell(1, 1);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.gradient_fill(GradientFill::radial_two_color("FFFFFF", "4472C4"))
.build());
s.add_text("Radial\n(white → blue)", TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(13.0).bold().color("1F3864")
.align(AlignH::Center).valign(AlignV::Middle).build());
let r = grid.cell(2, 1);
s.add_text("Gradient\ntext box\nbackground", TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(13.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle)
.gradient_fill(GradientFill::two_color(135.0, "7030A0", "00B0F0"))
.build());
}
{
let s = pres.add_slide();
heading(s, "New Entrance Animations — Basic (Filter-Based)");
let grid = GridLayoutBuilder::new()
.cols(vec![GridTrack::fr(1.0); 4])
.rows(vec![GridTrack::inches(0.6), GridTrack::fr(1.0), GridTrack::fr(1.0), GridTrack::fr(1.0)])
.gap(0.15)
.origin(0.3, 0.85)
.container(9.4, 4.6)
.build();
let anims: &[(&str, AnimationEffect, &str)] = &[
("Blinds H", AnimationEffect::blinds_in(SplitOrientation::Horizontal), "4472C4"),
("Blinds V", AnimationEffect::blinds_in(SplitOrientation::Vertical), "ED7D31"),
("Checker\n(Across)", AnimationEffect::checkerboard_in(CheckerboardDir::Across), "70AD47"),
("Checker\n(Down)", AnimationEffect::checkerboard_in(CheckerboardDir::Down), "FFC000"),
("Dissolve In", AnimationEffect::dissolve_in(), "5B9BD5"),
("Peek In", AnimationEffect::peek_in(Direction::Down), "C55A11"),
("Rand Bars H", AnimationEffect::random_bars_in(SplitOrientation::Horizontal), "7030A0"),
("Rand Bars V", AnimationEffect::random_bars_in(SplitOrientation::Vertical), "00B0F0"),
("Shape Box", AnimationEffect::shape_in(ShapeVariant::Box), "4472C4"),
("Shape Circle",AnimationEffect::shape_in(ShapeVariant::Circle), "ED7D31"),
("Strips LD", AnimationEffect::strips_in(StripDir::LeftDown), "70AD47"),
("Wedge", AnimationEffect::wedge_in(), "FFC000"),
];
let cols = 4;
for (i, (label, anim, color)) in anims.iter().enumerate() {
let col = i % cols;
let row = i / cols + 1;
let r = grid.cell(col, row);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.fill_color(*color)
.animation(anim.clone())
.build());
s.add_text(*label, TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(11.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
let r = grid.cell(3, 3);
s.add_shape(ShapeType::Ellipse, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.fill_color("FF0000")
.animation(AnimationEffect::wheel_in(3))
.build());
s.add_text("Wheel\n(3 spokes)", TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(11.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
{
let s = pres.add_slide();
heading(s, "New Entrance Animations — Subtle & Moderate");
let grid = GridLayoutBuilder::new()
.cols(vec![GridTrack::fr(1.0); 4])
.rows(vec![GridTrack::inches(0.6), GridTrack::fr(1.0), GridTrack::fr(1.0)])
.gap(0.15)
.origin(0.3, 0.85)
.container(9.4, 4.6)
.build();
let anims: &[(&str, AnimationEffect, &str)] = &[
("Expand", AnimationEffect::expand_in(), "4472C4"),
("Swivel", AnimationEffect::swivel_in(), "ED7D31"),
("Basic Zoom", AnimationEffect::basic_zoom_in(), "70AD47"),
("Stretch H", AnimationEffect::stretch_in(Direction::Left), "FFC000"),
("Centre Revolve", AnimationEffect::centre_revolve_in(), "7030A0"),
("Float In ↑", AnimationEffect::float_in(Direction::Up), "00B0F0"),
("Grow Turn", AnimationEffect::grow_turn_in(), "C55A11"),
("Rise Up", AnimationEffect::rise_up_in(), "4472C4"),
];
let cols = 4;
for (i, (label, anim, color)) in anims.iter().enumerate() {
let col = i % cols;
let row = i / cols + 1;
let r = grid.cell(col, row);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.fill_color(*color)
.animation(anim.clone())
.build());
s.add_text(*label, TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(11.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
}
{
let s = pres.add_slide();
heading(s, "New Entrance Animations — Exciting");
let grid = GridLayoutBuilder::new()
.cols(vec![GridTrack::fr(1.0); 4])
.rows(vec![GridTrack::inches(0.6), GridTrack::fr(1.0), GridTrack::fr(1.0), GridTrack::fr(1.0)])
.gap(0.15)
.origin(0.3, 0.85)
.container(9.4, 4.6)
.build();
let anims: &[(&str, AnimationEffect, &str)] = &[
("Boomerang", AnimationEffect::boomerang_in(), "4472C4"),
("Bounce", AnimationEffect::bounce_in(), "ED7D31"),
("Credits", AnimationEffect::credits_in(), "70AD47"),
("Curve Up", AnimationEffect::curve_up_in(), "FFC000"),
("Drop", AnimationEffect::drop_in(), "7030A0"),
("Flip", AnimationEffect::flip_in(), "00B0F0"),
("Pinwheel", AnimationEffect::pinwheel_in(), "C55A11"),
("Spiral In", AnimationEffect::spiral_in(), "4472C4"),
("Basic Swivel", AnimationEffect::basic_swivel_in(), "ED7D31"),
("Whip", AnimationEffect::whip_in(), "70AD47"),
("Spinner", AnimationEffect::spinner_in(), "FFC000"),
];
let cols = 4;
for (i, (label, anim, color)) in anims.iter().enumerate() {
let col = i % cols;
let row = i / cols + 1;
let r = grid.cell(col, row);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.fill_color(*color)
.animation(anim.clone())
.build());
s.add_text(*label, TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(11.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
}
{
let s = pres.add_slide();
heading(s, "Exit Animations — Basic & Subtle");
let grid = GridLayoutBuilder::grid_n_m(4, 4, 0.08)
.origin(0.3, 0.85).container(9.4, 4.7).build();
let anims: &[(&str, AnimationEffect, &str)] = &[
("Blinds H", AnimationEffect::blinds_out(SplitOrientation::Horizontal), "4472C4"),
("Blinds V", AnimationEffect::blinds_out(SplitOrientation::Vertical), "4472C4"),
("Checkerboard", AnimationEffect::checkerboard_out(CheckerboardDir::Across), "ED7D31"),
("Dissolve Out", AnimationEffect::dissolve_out(), "A9D18E"),
("Peek Out", AnimationEffect::peek_out(Direction::Left), "FF0000"),
("Random Bars H", AnimationEffect::random_bars_out(SplitOrientation::Horizontal), "70AD47"),
("Random Bars V", AnimationEffect::random_bars_out(SplitOrientation::Vertical), "70AD47"),
("Shape Box", AnimationEffect::shape_out(ShapeVariant::Box), "FFC000"),
("Strips", AnimationEffect::strips_out(StripDir::LeftDown), "9DC3E6"),
("Wedge", AnimationEffect::wedge_out(), "FF7F7F"),
("Wheel 2", AnimationEffect::wheel_out(2), "C5A5C5"),
("Wipe Left", AnimationEffect::wipe_out(Direction::Left), "4472C4"),
("Contract", AnimationEffect::contract_out(), "ED7D31"),
("Swivel", AnimationEffect::swivel_out(), "70AD47"),
("Split H", AnimationEffect::split_out(SplitOrientation::Horizontal), "FF0000"),
("Zoom Out", AnimationEffect::zoom_out(), "FFC000"),
];
let cols = 4;
for (i, (label, anim, color)) in anims.iter().enumerate() {
let col = i % cols;
let row = i / cols;
let r = grid.cell(col, row);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.fill_color(*color)
.animation(anim.clone())
.build());
s.add_text(*label, TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(11.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
}
{
let s = pres.add_slide();
heading(s, "Exit Animations — Moderate & Exciting");
let grid = GridLayoutBuilder::grid_n_m(4, 4, 0.08)
.origin(0.3, 0.85).container(9.4, 4.7).build();
let anims: &[(&str, AnimationEffect, &str)] = &[
("Centre Revolve", AnimationEffect::centre_revolve_out(), "4472C4"),
("Collapse", AnimationEffect::collapse_out(), "ED7D31"),
("Float Out", AnimationEffect::float_out(Direction::Up), "A9D18E"),
("Shrink Turn", AnimationEffect::shrink_turn_out(), "FF0000"),
("Sink Down", AnimationEffect::sink_down_out(), "70AD47"),
("Spinner", AnimationEffect::spinner_out(), "FFC000"),
("Basic Zoom", AnimationEffect::basic_zoom_out(), "9DC3E6"),
("Stretchy H", AnimationEffect::stretchy_out(Direction::Left), "FF7F7F"),
("Boomerang", AnimationEffect::boomerang_out(), "C5A5C5"),
("Bounce Out", AnimationEffect::bounce_out(), "4472C4"),
("Credits Out", AnimationEffect::credits_out(), "ED7D31"),
("Curve Down", AnimationEffect::curve_down_out(), "70AD47"),
("Drop Out", AnimationEffect::drop_out(), "FF0000"),
("Flip Out", AnimationEffect::flip_out(), "FFC000"),
("Pinwheel Out", AnimationEffect::pinwheel_out(), "9DC3E6"),
("Whip Out", AnimationEffect::whip_out(), "A9D18E"),
];
let cols = 4;
for (i, (label, anim, color)) in anims.iter().enumerate() {
let col = i % cols;
let row = i / cols;
let r = grid.cell(col, row);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.fill_color(*color)
.animation(anim.clone())
.build());
s.add_text(*label, TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(11.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
}
{
let s = pres.add_slide();
heading(s, "Emphasis — Basic & Subtle");
let grid = GridLayoutBuilder::grid_n_m(4, 4, 0.08)
.origin(0.3, 0.85).container(9.4, 4.7).build();
let anims: &[(&str, AnimationEffect, &str, bool)] = &[
("Fill Colour", AnimationEffect::fill_color("FF0000"), "4472C4", false),
("Font Colour", AnimationEffect::font_color("FFFF00"), "ED7D31", true),
("Line Colour", AnimationEffect::line_color("00FF00"), "70AD47", false),
("Transparency", AnimationEffect::transparency(0.2), "FF0000", false),
("Bold Flash", AnimationEffect::bold_flash(), "4472C4", true),
("Brush Colour", AnimationEffect::brush_color("FF8000"), "ED7D31", false),
("Comp. Colour", AnimationEffect::complementary_color(), "70AD47", false),
("Comp. Colour2", AnimationEffect::complementary_color2(), "FFC000", false),
("Contrast Clr", AnimationEffect::contrasting_color(), "9DC3E6", false),
("Darken", AnimationEffect::darken(), "FF7F7F", false),
("Desaturate", AnimationEffect::desaturate(), "C5A5C5", false),
("Lighten", AnimationEffect::lighten(), "4472C4", false),
("Object Colour", AnimationEffect::object_color("70AD47"), "ED7D31", false),
("Underline", AnimationEffect::underline(), "FF0000", true),
("Spin 360°", AnimationEffect::spin(360.0), "70AD47", false),
("Pulse", AnimationEffect::pulse(), "FFC000", false),
];
let cols = 4;
for (i, (label, anim, color, on_text)) in anims.iter().enumerate() {
let col = i % cols;
let row = i / cols;
let r = grid.cell(col, row);
if *on_text {
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).fill_color(*color).build());
s.add_text(*label, TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(10.5).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle)
.animation(anim.clone()).build());
} else {
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).fill_color(*color).animation(anim.clone()).build());
s.add_text(*label, TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(10.5).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
}
}
{
let s = pres.add_slide();
heading(s, "Emphasis — Moderate & Exciting");
let grid = GridLayoutBuilder::grid_n_m(4, 2, 0.08)
.origin(0.3, 0.85).container(9.4, 4.7).build();
let anims: &[(&str, AnimationEffect, &str)] = &[
("Colour Pulse", AnimationEffect::color_pulse("ED7D31"), "4472C4"),
("Grow w/ Colour", AnimationEffect::grow_with_color("FF0000"), "70AD47"),
("Shimmer", AnimationEffect::shimmer(), "FFC000"),
("Teeter", AnimationEffect::teeter(), "9DC3E6"),
("Blink", AnimationEffect::blink(), "FF7F7F"),
("Bold Reveal", AnimationEffect::bold_reveal(), "C5A5C5"),
("Wave", AnimationEffect::wave(), "4472C4"),
("Grow/Shrink", AnimationEffect::grow_shrink(1.5), "ED7D31"),
];
let cols = 4;
for (i, (label, anim, color)) in anims.iter().enumerate() {
let col = i % cols;
let row = i / cols;
let r = grid.cell(col, row);
s.add_shape(ShapeType::Rect, ShapeOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h)
.fill_color(*color)
.animation(anim.clone())
.build());
s.add_text(*label, TextOptionsBuilder::new()
.x(r.x).y(r.y).w(r.w).h(r.h).font_size(11.0).bold().color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
}
}
{
let s = pres.add_slide();
heading(s, "Font Colour — Sub-paragraph targeting");
let demos: &[(&str, u32, u32, &str, &str)] = &[
("The quick brown fox", 4, 8, "\"quick\"", "FFFF00"),
("jumps over the lazy dog", 6, 9, "\"over\"", "FF4444"),
];
for (row_idx, (text, st, end, word, clr)) in demos.iter().enumerate() {
let y = 1.5 + row_idx as f64 * 2.0;
s.add_text(
&format!("Click to colour {} → #{} (chars {}–{})", word, clr, st, end),
TextOptionsBuilder::new()
.x(0.5).y(y - 0.45).w(9.0).h(0.4).font_size(11.0).color("666666").build()
);
s.add_text(
*text,
TextOptionsBuilder::new()
.x(0.5).y(y).w(9.0).h(0.7)
.font_size(28.0).bold().color("222222")
.animation(
AnimationEffect::font_color(clr)
.with_char_range(*st, *end)
)
.build()
);
}
s.add_text(
"Three paragraphs:\nFirst\nSecond\nThird",
TextOptionsBuilder::new()
.x(0.5).y(5.0).w(4.5).h(0.55).font_size(14.0).color("444444").build()
);
s.add_text(
"First\nSecond\nThird",
TextOptionsBuilder::new()
.x(5.2).y(5.0).w(4.3).h(0.55)
.font_size(14.0).color("222222")
.animation(
AnimationEffect::font_color("0070C0")
.with_para_range(1, 1) )
.build()
);
s.add_text(
"← click to colour only para 1 (\"Second\") blue",
TextOptionsBuilder::new()
.x(0.5).y(4.55).w(9.0).h(0.4).font_size(11.0).color("666666").build()
);
}
{
let s = pres.add_slide();
heading(s, "Rich Text — Per-run colors within a paragraph");
s.add_text_runs(
vec![
TextRunBuilder::new("The ").font_size(28.0).color("222222").build(),
TextRunBuilder::new("quick brown").font_size(28.0).color("C45911").bold().build(),
TextRunBuilder::new(" fox ").font_size(28.0).color("222222").build(),
TextRunBuilder::new("jumps").font_size(28.0).color("2E75B6").bold().build(),
TextRunBuilder::new(" over the lazy dog.").font_size(28.0).color("222222").build(),
],
TextOptionsBuilder::new().x(0.5).y(0.9).w(9.0).h(0.7).build(),
);
s.add_text_runs(
vec![
TextRunBuilder::new("Passed: ").font_size(22.0).color("375623").bold().build(),
TextRunBuilder::new("all tests green ").font_size(22.0).color("375623").build(),
TextRunBuilder::new(" Warning: ").font_size(22.0).color("7F6000").bold().build(),
TextRunBuilder::new("deprecated API ").font_size(22.0).color("7F6000").build(),
TextRunBuilder::new(" Error: ").font_size(22.0).color("C00000").bold().build(),
TextRunBuilder::new("build failed").font_size(22.0).color("C00000").build(),
],
TextOptionsBuilder::new().x(0.5).y(1.8).w(9.0).h(0.6).build(),
);
s.add_text_runs(
vec![
TextRunBuilder::new("Call ").font_size(20.0).color("222222").build(),
TextRunBuilder::new("slide.add_text_runs(runs, opts)")
.font_size(18.0).color("7030A0").italic().font_face("Courier New").build(),
TextRunBuilder::new(" for inline rich text.").font_size(20.0).color("222222").build(),
],
TextOptionsBuilder::new().x(0.5).y(2.6).w(9.0).h(0.5).build(),
);
s.add_text_runs(
vec![
TextRunBuilder::new("First sentence stays on line one.")
.font_size(18.0).color("1F3864").break_line().build(),
TextRunBuilder::new("Second sentence is on its own line (")
.font_size(18.0).color("C00000").build(),
TextRunBuilder::new("new paragraph").font_size(18.0).color("C00000").bold().build(),
TextRunBuilder::new(").").font_size(18.0).color("C00000").build(),
],
TextOptionsBuilder::new().x(0.5).y(3.3).w(9.0).h(0.85).build(),
);
s.add_shape(
ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(0.3).y(4.35).w(9.4).h(1.5)
.fill_color("F2F2F2")
.line_color("BFBFBF").line_width(0.5)
.build(),
);
s.add_text(
"API: TextRunBuilder::new(text).color(hex).bold().italic().font_size(pt).break_line().build()\n\
• Runs without .break_line() share a paragraph (same <a:p>)\n\
• .break_line() on a run ends that paragraph and starts a new one",
TextOptionsBuilder::new()
.x(0.5).y(4.4).w(9.0).h(1.4)
.font_size(11.0).color("444444")
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Emphasis — Per-sentence colour animation (one box per sentence)");
s.add_text(
"Each sentence is a separate text box with its own font_color emphasis.",
TextOptionsBuilder::new()
.x(0.5).y(0.85).w(9.0).h(0.38).font_size(11.5).color("666666").italic().build(),
);
let sentences: &[(&str, &str)] = &[
("The sun rose slowly over the hills.", "C45911"),
("Birds began to sing in the distance.", "2E75B6"),
("A gentle breeze stirred the leaves.", "C45911"),
("The day had finally begun.", "2E75B6"),
("All was calm and still.", "C45911"),
];
let x = 0.5_f64;
let y0 = 1.35_f64;
let line_h = 0.52_f64;
for (i, (text, color)) in sentences.iter().enumerate() {
let y = y0 + i as f64 * line_h;
s.add_text(
*text,
TextOptionsBuilder::new()
.x(x).y(y).w(9.0).h(line_h)
.font_size(22.0).color("333333")
.animation(AnimationEffect::font_color(*color))
.build(),
);
}
s.add_text(
"Clicks 1,3,5 → orange Clicks 2,4 → blue (each click = one sentence)",
TextOptionsBuilder::new()
.x(0.5).y(4.1).w(9.0).h(0.4).font_size(11.5).color("666666").italic().build(),
);
s.add_shape(
ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(0.3).y(4.6).w(9.4).h(1.0)
.fill_color("F2F2F2").line_color("BFBFBF").line_width(0.5).build(),
);
s.add_text(
"for (text, color) in sentences {\n\
\x20 slide.add_text(text, TextOptionsBuilder::new()\n\
\x20 .x(0.5).y(y).w(9.0).h(0.52).font_size(22.0)\n\
\x20 .animation(AnimationEffect::font_color(color)).build());\n\
}",
TextOptionsBuilder::new()
.x(0.5).y(4.65).w(9.0).h(0.9)
.font_size(10.0).color("333333").font_face("Courier New").build(),
);
}
{
use deckmint::objects::image::ImageOptionsBuilder;
let s = pres.add_slide();
heading(s, "New: Image & Text Rotate / Flip");
s.add_image(
RED_1PX_PNG.to_vec(), "png",
ImageOptionsBuilder::new().x(0.3).y(1.0).w(2.5).h(1.5).build().0,
);
s.add_text("image (normal)", TextOptionsBuilder::new().x(0.3).y(2.55).w(2.5).h(0.4)
.font_size(11.0).align(AlignH::Center).build());
s.add_image(
RED_1PX_PNG.to_vec(), "png",
ImageOptionsBuilder::new().x(3.2).y(1.0).w(2.5).h(1.5).rotate(30.0).build().0,
);
s.add_text("image rotate=30°", TextOptionsBuilder::new().x(3.2).y(2.55).w(2.5).h(0.4)
.font_size(11.0).align(AlignH::Center).build());
s.add_image(
RED_1PX_PNG.to_vec(), "png",
ImageOptionsBuilder::new().x(6.2).y(1.0).w(2.5).h(1.5).flip_h().flip_v().build().0,
);
s.add_text("image flip_h + flip_v", TextOptionsBuilder::new().x(6.2).y(2.55).w(2.5).h(0.4)
.font_size(11.0).align(AlignH::Center).build());
s.add_text("Normal text box",
TextOptionsBuilder::new().x(0.3).y(3.2).w(2.5).h(0.8)
.font_size(15.0).fill("4472C4").color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle).build());
s.add_text("Rotated 30°",
TextOptionsBuilder::new().x(3.2).y(3.2).w(2.5).h(0.8)
.font_size(15.0).fill("ED7D31").color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle)
.rotate(30.0).build());
s.add_text("Flipped H",
TextOptionsBuilder::new().x(6.2).y(3.2).w(2.5).h(0.8)
.font_size(15.0).fill("70AD47").color("FFFFFF")
.align(AlignH::Center).valign(AlignV::Middle)
.flip_h().build());
}
{
use deckmint::types::ShapeLineProps;
let s = pres.add_slide();
heading(s, "New: Text Border · Transparency · Strike · Underline Color");
s.add_text("Text box with a 2pt blue dashed border",
TextOptionsBuilder::new()
.x(0.4).y(0.95).w(5.8).h(0.6)
.font_size(16.0)
.line(ShapeLineProps {
color: Some("4472C4".to_string()),
width: Some(2.0),
dash_type: Some("dash".to_string()),
..Default::default()
})
.build());
s.add_text("Border via .line_color() + .line_width() builder shorthand",
TextOptionsBuilder::new()
.x(0.4).y(1.65).w(5.8).h(0.6)
.font_size(16.0)
.line_color("C00000").line_width(1.5)
.build());
let y = 2.45_f64;
s.add_text_runs(
vec![
TextRunBuilder::new("Opaque ").font_size(22.0).color("222222").build(),
TextRunBuilder::new("50% trans ").font_size(22.0).color("C45911").transparency(50.0).build(),
TextRunBuilder::new("75% trans ").font_size(22.0).color("C45911").transparency(75.0).build(),
TextRunBuilder::new("90% trans").font_size(22.0).color("C45911").transparency(90.0).build(),
],
TextOptionsBuilder::new().x(0.4).y(y).w(9.0).h(0.6).build(),
);
let y = 3.2_f64;
s.add_text_runs(
vec![
TextRunBuilder::new("Single strike ").font_size(22.0).strike().build(),
TextRunBuilder::new("Double strike").font_size(22.0).strike_double().build(),
],
TextOptionsBuilder::new().x(0.4).y(y).w(9.0).h(0.6).build(),
);
let y = 3.95_f64;
s.add_text_runs(
vec![
TextRunBuilder::new("Red underline ").font_size(22.0)
.underline("sng").underline_color("FF0000").build(),
TextRunBuilder::new("Green heavy ").font_size(22.0)
.underline("heavy").underline_color("00B050").build(),
TextRunBuilder::new("Blue wavy").font_size(22.0)
.underline("wavy").underline_color("4472C4").build(),
],
TextOptionsBuilder::new().x(0.4).y(y).w(9.0).h(0.6).build(),
);
}
{
let s = pres.add_slide();
heading(s, "New: Arc / Angle-Range Shapes (PIE, ARC, BLOCK_ARC)");
let sweeps: &[(f64, f64, &str, &str)] = &[
(0.0, 90.0, "4472C4", "Pie 90°"),
(0.0, 180.0, "ED7D31", "Pie 180°"),
(0.0, 270.0, "70AD47", "Pie 270°"),
];
for (i, (start, swing, color, label)) in sweeps.iter().enumerate() {
let x = 0.4 + i as f64 * 3.1;
s.add_shape(ShapeType::Pie,
ShapeOptionsBuilder::new()
.x(x).y(1.0).w(2.5).h(2.5)
.fill_color(*color)
.angle_range(*start, *swing)
.build());
s.add_text(*label,
TextOptionsBuilder::new().x(x).y(3.6).w(2.5).h(0.4)
.font_size(12.0).align(AlignH::Center).build());
}
let arcs: &[(f64, f64, &str, &str)] = &[
(0.0, 180.0, "C45911", "BlockArc thick"),
(0.0, 270.0, "7030A0", "BlockArc thin"),
];
for (i, (start, swing, color, label)) in arcs.iter().enumerate() {
let x = 0.4 + i as f64 * 4.7;
let thickness = if i == 0 { 0.6 } else { 0.15 };
s.add_shape(ShapeType::BlockArc,
ShapeOptionsBuilder::new()
.x(x).y(4.2).w(3.2).h(2.0)
.fill_color(*color)
.angle_range(*start, *swing)
.arc_thickness(thickness)
.build());
s.add_text(*label,
TextOptionsBuilder::new().x(x).y(6.25).w(3.2).h(0.4)
.font_size(12.0).align(AlignH::Center).build());
}
}
{
let s = pres.add_slide();
heading(s, "Pattern Fill");
let patterns = [
(PatternType::Cross, "Cross", "4472C4", "FFFFFF"),
(PatternType::DotGrid, "DotGrid", "ED7D31", "FFFFFF"),
(PatternType::HorzBrick, "HorzBrick", "70AD47", "FFF2CC"),
(PatternType::Trellis, "Trellis", "9966CC", "E2EFDA"),
(PatternType::Wave, "Wave", "C00000", "FFFFFF"),
(PatternType::ZigZag, "ZigZag", "0070C0", "DEEAF1"),
(PatternType::LgConfetti,"LgConfetti","FF0000", "FFFF00"),
(PatternType::Sphere, "Sphere", "333333", "EEEEEE"),
];
for (i, &(ref pat, label, fg, bg)) in patterns.iter().enumerate() {
let col = i % 4;
let row = i / 4;
let x = 0.3 + col as f64 * 2.45;
let y = 1.0 + row as f64 * 1.6;
s.add_shape(
ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(x).y(y).w(2.2).h(1.2)
.pattern_fill(PatternFill {
pattern: pat.clone(),
fg_color: fg.to_string(),
bg_color: bg.to_string(),
})
.line_color("CCCCCC").line_width(0.5)
.build(),
);
s.add_text(
label,
TextOptionsBuilder::new()
.x(x).y(y + 1.22).w(2.2).h(0.3)
.font_size(10.0).align(AlignH::Center).color("333333")
.build(),
);
}
}
{
use deckmint::CustomGeomPoint as CGP;
let s = pres.add_slide();
heading(s, "New: Custom Shape Geometry (Freeform Points)");
s.add_shape(ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(0.3).y(1.0).w(2.8).h(2.5)
.fill_color("4472C4")
.custom_geometry(vec![
CGP::MoveTo(0.5, 0.0),
CGP::LineTo(1.0, 1.0),
CGP::LineTo(0.0, 1.0),
CGP::Close,
])
.build());
s.add_text("Triangle\n(3 LineTo points)", TextOptionsBuilder::new()
.x(0.3).y(3.6).w(2.8).h(0.6).font_size(12.0).align(AlignH::Center).build());
s.add_shape(ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(3.5).y(1.0).w(2.8).h(2.5)
.fill_color("ED7D31")
.custom_geometry(vec![
CGP::MoveTo(0.0, 0.25),
CGP::LineTo(0.6, 0.25),
CGP::LineTo(0.6, 0.0),
CGP::LineTo(1.0, 0.5),
CGP::LineTo(0.6, 1.0),
CGP::LineTo(0.6, 0.75),
CGP::LineTo(0.0, 0.75),
CGP::Close,
])
.build());
s.add_text("Arrow right\n(LineTo polygon)", TextOptionsBuilder::new()
.x(3.5).y(3.6).w(2.8).h(0.6).font_size(12.0).align(AlignH::Center).build());
s.add_shape(ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(6.7).y(1.0).w(2.8).h(2.5)
.fill_color("70AD47")
.custom_geometry(vec![
CGP::MoveTo(0.15, 0.0),
CGP::LineTo(0.85, 0.0),
CGP::CubicBezTo(1.0, 0.0, 1.0, 0.0, 1.0, 0.15),
CGP::LineTo(1.0, 0.85),
CGP::CubicBezTo(1.0, 1.0, 1.0, 1.0, 0.85, 1.0),
CGP::LineTo(0.15, 1.0),
CGP::CubicBezTo(0.0, 1.0, 0.0, 1.0, 0.0, 0.85),
CGP::LineTo(0.0, 0.15),
CGP::CubicBezTo(0.0, 0.0, 0.0, 0.0, 0.15, 0.0),
CGP::Close,
])
.build());
s.add_text("Rounded rect\n(CubicBezTo corners)", TextOptionsBuilder::new()
.x(6.7).y(3.6).w(2.8).h(0.6).font_size(12.0).align(AlignH::Center).build());
}
{
let s = pres.add_slide();
heading(s, "New: Shape Alt Text (Accessibility)");
s.add_shape(ShapeType::RoundRect,
ShapeOptionsBuilder::new()
.x(0.5).y(1.2).w(4.0).h(2.0)
.fill_color("4472C4")
.alt_text("A blue rounded rectangle used as a decorative accent")
.build());
s.add_text("▲ Has alt text: \"A blue rounded rectangle…\"",
TextOptionsBuilder::new().x(0.5).y(3.3).w(4.0).h(0.5)
.font_size(11.0).color("666666").build());
s.add_shape(ShapeType::Ellipse,
ShapeOptionsBuilder::new()
.x(5.5).y(1.2).w(3.5).h(2.0)
.fill_color("ED7D31")
.alt_text("Orange ellipse — chart placeholder")
.build());
s.add_text("▲ Has alt text: \"Orange ellipse — chart placeholder\"",
TextOptionsBuilder::new().x(5.5).y(3.3).w(4.0).h(0.5)
.font_size(11.0).color("666666").build());
s.add_text(
"Alt text is set via .alt_text(\"...\") on ShapeOptionsBuilder.\n\
It maps to descr=\"...\" on <p:cNvPr> and is read by screen readers.",
TextOptionsBuilder::new().x(0.4).y(4.2).w(9.0).h(1.0)
.font_size(14.0).color("444444").build());
}
{
let s = pres.add_slide();
s.set_background_color_transparency("4472C4", 60.0);
heading(s, "New: Background Color + Transparency");
s.add_text(
"Background: #4472C4 at 60% transparency\n(set via set_background_color_transparency())",
TextOptionsBuilder::new()
.x(0.5).y(2.0).w(9.0).h(1.5)
.font_size(20.0).bold().align(AlignH::Center)
.build(),
);
}
{
use deckmint::Color;
let s = pres.add_slide();
heading(s, "Theme Color Tints & Shades (Color::ThemedWith)");
let mods: &[(&str, ThemeColorMod)] = &[
("Accent1 (base)", ThemeColorMod::default()),
("+ tint 40%", ThemeColorMod::tint(40000)),
("+ tint 70%", ThemeColorMod::tint(70000)),
("+ shade 25%", ThemeColorMod::shade(25000)),
("+ shade 50%", ThemeColorMod::shade(50000)),
("lum 75% / off 15%", ThemeColorMod::lum(75000, 15000)),
];
for (i, &(label, ref m)) in mods.iter().enumerate() {
let x = 0.3 + (i % 3) as f64 * 3.2;
let y = 1.0 + (i / 3) as f64 * 1.6;
let fill_color = Color::ThemedWith(SchemeColor::Accent1, m.clone());
s.add_shape(
ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(x).y(y).w(2.9).h(1.0)
.fill_color_value(fill_color)
.line_color("CCCCCC").line_width(0.5)
.build(),
);
s.add_text(
label,
TextOptionsBuilder::new()
.x(x).y(y + 1.02).w(2.9).h(0.4)
.font_size(10.5).align(AlignH::Center).color("333333")
.build(),
);
}
s.add_text(
"Color::ThemedWith(SchemeColor::Accent1, ThemeColorMod::tint(40000))\n\
emits <a:schemeClr val=\"accent1\"><a:tint val=\"40000\"/></a:schemeClr>",
TextOptionsBuilder::new()
.x(0.3).y(4.5).w(9.4).h(0.9)
.font_size(11.0).color("666666").italic()
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "New: Slide Numbers");
s.set_slide_number(SlideNumberProps {
x: Some(deckmint::Coord::Inches(8.5)),
y: Some(deckmint::Coord::Inches(7.0)),
w: Some(deckmint::Coord::Inches(1.5)),
h: Some(deckmint::Coord::Inches(0.4)),
font_size: Some(14.0),
color: Some("444444".to_string()),
bold: true,
align: Some("r".to_string()),
..Default::default()
});
s.add_text(
"This slide has a slide-number placeholder in the bottom-right corner.\n\
Set via slide.set_slide_number(SlideNumberProps { x, y, w, h, font_size, color, … })\n\
The number is rendered automatically by PowerPoint/LibreOffice.",
TextOptionsBuilder::new()
.x(0.5).y(1.5).w(9.0).h(2.5)
.font_size(16.0)
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Advanced Hyperlink Actions");
let actions: &[(&str, HyperlinkAction, &str)] = &[
("Next Slide →", HyperlinkAction::NextSlide, "4472C4"),
("← Prev Slide", HyperlinkAction::PrevSlide, "ED7D31"),
("⏮ First Slide", HyperlinkAction::FirstSlide, "70AD47"),
("⏭ Last Slide", HyperlinkAction::LastSlide, "9966CC"),
("✕ End Show", HyperlinkAction::EndShow, "C00000"),
];
for (i, &(label, ref action, color)) in actions.iter().enumerate() {
let x = 0.5 + (i % 3) as f64 * 3.1;
let y = 1.1 + (i / 3) as f64 * 1.5;
s.add_shape(
ShapeType::RoundRect,
ShapeOptionsBuilder::new()
.x(x).y(y).w(2.7).h(0.9)
.fill_color(color)
.hyperlink(HyperlinkProps {
r_id: 0,
slide: None,
url: None,
tooltip: Some(format!("Action: {label}")),
action: Some(action.clone()),
})
.build(),
);
s.add_text(
label,
TextOptionsBuilder::new()
.x(x).y(y + 0.15).w(2.7).h(0.6)
.font_size(14.0).bold().align(AlignH::Center).color("FFFFFF")
.build(),
);
}
s.add_text(
"Click any button in Slideshow mode — each fires a navigation action with no URL relationship.",
TextOptionsBuilder::new()
.x(0.3).y(4.3).w(9.4).h(0.5)
.font_size(11.0).color("666666").italic()
.build(),
);
}
{
let s = pres.add_slide();
s.set_transition(TransitionProps {
transition_type: deckmint::TransitionType::Fade,
speed: Some(deckmint::TransitionSpeed::Medium),
advance_on_click: true,
..Default::default()
});
heading(s, "Slide Transitions (this slide: Fade)");
s.add_text(
"This slide has a Fade transition applied via set_transition().\n\
Switch to Slideshow mode and advance to see it.",
TextOptionsBuilder::new()
.x(0.5).y(1.1).w(9.0).h(1.2).font_size(18.0).build(),
);
let types_text = "Supported types: Cut, Fade, Push, Wipe, Split, Cover, Uncover,\n\
Zoom, Flash, Morph, Vortex, Ripple, Glitter, Honeycomb, Shred, Switch,\n\
Flip, Pan, Ferris, Gallery, Conveyor, Doors, Box, Random, RandomBar,\n\
Circle, Diamond, Wheel, Checker, Blinds, Strips, Plus (30 types total)";
s.add_text(
types_text,
TextOptionsBuilder::new()
.x(0.5).y(2.5).w(9.0).h(2.0).font_size(14.0).color("444444").build(),
);
}
{
let s = pres.add_slide();
s.set_transition(TransitionProps::push(TransitionDir::Left));
heading(s, "Slide Transitions (this slide: Push Left)");
s.add_text(
"TransitionProps::push(TransitionDir::Left)\n\n\
Direction variants: Left, Right, Up, Down, LeftDown, LeftUp, RightDown, RightUp\n\
Speed variants: Slow, Medium, Fast\n\
Auto-advance: set advance_after_ms to advance without a click",
TextOptionsBuilder::new()
.x(0.5).y(1.1).w(9.0).h(3.0).font_size(16.0).build(),
);
}
{
use deckmint::ShapeLineProps;
let s = pres.add_slide();
heading(s, "Connectors (<p:cxnSp>)");
let boxes = [(1.0_f64, 1.2_f64), (4.5_f64, 1.2_f64), (8.0_f64, 1.2_f64)];
let labels = ["Box A", "Box B", "Box C"];
for ((bx, by), &lbl) in boxes.iter().zip(labels.iter()) {
s.add_shape(
ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(*bx).y(*by).w(1.5).h(0.8)
.fill_color("4472C4").line_color("1F3864").line_width(1.5)
.build(),
);
s.add_text(
lbl,
TextOptionsBuilder::new()
.x(*bx).y(*by + 0.1).w(1.5).h(0.6)
.font_size(14.0).bold().align(AlignH::Center).color("FFFFFF")
.build(),
);
}
s.add_connector(
ConnectorType::Straight,
ConnectorOptions {
x1: Some(deckmint::Coord::Inches(2.5)),
y1: Some(deckmint::Coord::Inches(1.6)),
x2: Some(deckmint::Coord::Inches(4.5)),
y2: Some(deckmint::Coord::Inches(1.6)),
line: Some(ShapeLineProps {
color: Some("ED7D31".to_string()),
width: Some(2.0),
end_arrow_type: Some("triangle".to_string()),
..Default::default()
}),
..Default::default()
},
);
s.add_connector(
ConnectorType::Elbow,
ConnectorOptions {
x1: Some(deckmint::Coord::Inches(6.0)),
y1: Some(deckmint::Coord::Inches(1.6)),
x2: Some(deckmint::Coord::Inches(8.0)),
y2: Some(deckmint::Coord::Inches(1.6)),
line: Some(ShapeLineProps {
color: Some("70AD47".to_string()),
width: Some(2.0),
end_arrow_type: Some("triangle".to_string()),
..Default::default()
}),
..Default::default()
},
);
s.add_connector(
ConnectorType::Curved,
ConnectorOptions {
x1: Some(deckmint::Coord::Inches(1.75)),
y1: Some(deckmint::Coord::Inches(2.0)),
x2: Some(deckmint::Coord::Inches(8.75)),
y2: Some(deckmint::Coord::Inches(3.5)),
line: Some(ShapeLineProps {
color: Some("9966CC".to_string()),
width: Some(2.0),
dash_type: Some("dash".to_string()),
end_arrow_type: Some("triangle".to_string()),
..Default::default()
}),
..Default::default()
},
);
s.add_text(
"Orange: Straight | Green: Elbow | Purple dashed: Curved\n\
add_connector(ConnectorType::Straight/Elbow/Curved, ConnectorOptions { x1, y1, x2, y2, line, … })",
TextOptionsBuilder::new()
.x(0.3).y(4.1).w(9.4).h(0.9)
.font_size(11.0).color("666666").italic()
.build(),
);
}
{
pres.add_section("Section A — Introduction");
let s = pres.add_slide();
heading(s, "Sections: Slide in Section A");
s.add_text(
"This slide belongs to \"Section A — Introduction\".\n\
Sections are added via pres.add_section(name) before the relevant add_slide() calls.\n\
They appear as collapsible groups in PowerPoint's slide panel.",
TextOptionsBuilder::new()
.x(0.5).y(1.2).w(9.0).h(2.5)
.font_size(16.0)
.build(),
);
pres.add_section("Section B — Content");
let s = pres.add_slide();
heading(s, "Sections: First Slide in Section B");
s.add_text(
"This slide belongs to \"Section B — Content\".",
TextOptionsBuilder::new()
.x(0.5).y(1.5).w(9.0).h(1.0)
.font_size(18.0)
.build(),
);
let s = pres.add_slide();
heading(s, "Sections: Second Slide in Section B");
s.add_text(
"Also in Section B. Multiple slides can share a section.",
TextOptionsBuilder::new()
.x(0.5).y(1.5).w(9.0).h(1.0)
.font_size(18.0)
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Table Style Preset + AlignH::Distribute");
let rows = vec![
vec![
TableCell::new("Quarter"),
TableCell::new("Revenue"),
TableCell::new("Growth"),
],
vec![TableCell::new("Q1"), TableCell::new("$1.2M"), TableCell::new("+8%")],
vec![TableCell::new("Q2"), TableCell::new("$1.5M"), TableCell::new("+25%")],
vec![TableCell::new("Q3"), TableCell::new("$1.4M"), TableCell::new("−7%")],
vec![TableCell::new("Q4"), TableCell::new("$1.9M"), TableCell::new("+36%")],
];
s.add_table(
rows,
TableOptionsBuilder::new()
.x(1.0).y(1.0).w(8.0).h(3.0)
.table_style_id("{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}")
.align(AlignH::Center)
.build(),
);
s.add_text(
"table_style_id(\"{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}\") → OOXML built-in style applied",
TextOptionsBuilder::new()
.x(0.3).y(4.1).w(9.4).h(0.4)
.font_size(11.0).color("666666").italic()
.build(),
);
s.add_text(
"AlignH::Distribute → text distributed evenly across width",
TextOptionsBuilder::new()
.x(0.5).y(4.6).w(9.0).h(0.5)
.font_size(14.0).align(AlignH::Distribute).color("2E4057")
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Animation Sequencing (WithPrevious / AfterPrevious)");
s.add_shape(
ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(1.0).y(1.5).w(2.5).h(1.5)
.fill_color("4472C4")
.build(),
);
s.add_text(
"1 — On Click",
TextOptionsBuilder::new()
.x(1.0).y(1.5).w(2.5).h(1.5)
.font_size(14.0).color("FFFFFF").align(AlignH::Center).valign(AlignV::Middle)
.animation(AnimationEffect::fade_in())
.build(),
);
s.add_text(
"2 — With Previous",
TextOptionsBuilder::new()
.x(4.0).y(1.5).w(2.5).h(1.5)
.font_size(14.0).color("FFFFFF").align(AlignH::Center).valign(AlignV::Middle)
.fill("ED7D31".to_string())
.animation(AnimationEffect::fade_in().with_previous())
.build(),
);
s.add_text(
"3 — After Previous",
TextOptionsBuilder::new()
.x(7.0).y(1.5).w(2.5).h(1.5)
.font_size(14.0).color("FFFFFF").align(AlignH::Center).valign(AlignV::Middle)
.fill("70AD47".to_string())
.animation(AnimationEffect::fly_in(Direction::Down).after_previous())
.build(),
);
s.add_text(
"4 — After + 500ms delay",
TextOptionsBuilder::new()
.x(4.0).y(3.5).w(2.5).h(1.5)
.font_size(14.0).color("FFFFFF").align(AlignH::Center).valign(AlignV::Middle)
.fill("A855C7".to_string())
.animation(AnimationEffect::zoom_in().after_previous().delay(500))
.build(),
);
s.add_text(
"Run slideshow (F5) → single click triggers shapes 1+2 together,\n\
then 3 auto-plays after they finish, then 4 after a 500 ms delay.",
TextOptionsBuilder::new()
.x(0.3).y(5.1).w(9.4).h(0.7)
.font_size(11.0).color("666666").italic()
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Field Codes (FieldType::SlideNumber / DateTime)");
s.add_text_runs(
vec![
TextRunBuilder::new("Slide number: ").font_size(20.0).build(),
TextRunBuilder::new("").font_size(20.0).bold().field(FieldType::SlideNumber).build(),
],
TextOptionsBuilder::new().x(1.0).y(1.5).w(8.0).h(1.0).build(),
);
s.add_text_runs(
vec![
TextRunBuilder::new("Date/time: ").font_size(20.0).build(),
TextRunBuilder::new("").font_size(20.0).bold().field(FieldType::DateTime).build(),
],
TextOptionsBuilder::new().x(1.0).y(2.8).w(8.0).h(1.0).build(),
);
s.add_text(
"The fields above auto-update when opened in PowerPoint.\n\
TextRunBuilder::new(\"\").field(FieldType::SlideNumber).build()",
TextOptionsBuilder::new()
.x(0.3).y(4.5).w(9.4).h(0.9)
.font_size(11.0).color("666666").italic()
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Multiple Text Columns (.columns(N))");
let lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris \
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in \
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla \
pariatur. Excepteur sint occaecat cupidatat non proident.";
s.add_text(
lorem,
TextOptionsBuilder::new()
.x(0.5).y(1.2).w(9.0).h(2.5)
.font_size(14.0).color("333333")
.columns(2).column_spacing(0.4)
.build(),
);
s.add_text(
"Three columns:",
TextOptionsBuilder::new()
.x(0.5).y(3.8).w(9.0).h(0.4)
.font_size(12.0).bold().color("2E4057")
.build(),
);
s.add_text(
lorem,
TextOptionsBuilder::new()
.x(0.5).y(4.2).w(9.0).h(1.8)
.font_size(11.0).color("333333")
.columns(3).column_spacing(0.3)
.build(),
);
}
{
use deckmint::Color;
let s = pres.add_slide();
heading(s, "Gradient Stops with Theme Colors (GradientStop::from_color)");
s.add_shape(
ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(0.5).y(1.5).w(9.0).h(2.0)
.gradient_fill(GradientFill::linear(0.0, vec![
GradientStop::from_color(Color::ThemedWith(SchemeColor::Accent1, ThemeColorMod::tint(40000)), 0.0),
GradientStop::from_color(Color::Theme(SchemeColor::Accent1), 50.0),
GradientStop::from_color(Color::ThemedWith(SchemeColor::Accent1, ThemeColorMod::shade(50000)), 100.0),
]))
.build(),
);
s.add_text(
"Accent1 tint 40% → Accent1 → Accent1 shade 50%",
TextOptionsBuilder::new()
.x(0.5).y(3.6).w(9.0).h(0.5)
.font_size(12.0).color("333333").align(AlignH::Center)
.build(),
);
s.add_shape(
ShapeType::Rect,
ShapeOptionsBuilder::new()
.x(0.5).y(4.2).w(9.0).h(1.2)
.gradient_fill(GradientFill::linear(90.0, vec![
GradientStop::new("FFFFFF", 0.0),
GradientStop::from_color(Color::Theme(SchemeColor::Accent2), 100.0),
]))
.build(),
);
s.add_text(
"White (hex) → Accent2 (theme) at 90°",
TextOptionsBuilder::new()
.x(0.5).y(5.5).w(9.0).h(0.4)
.font_size(12.0).color("333333").align(AlignH::Center)
.build(),
);
}
{
let s = pres.add_slide();
heading(s, "Image Color Adjustments (.brightness / .contrast / .grayscale)");
s.add_text(
"ImageOptionsBuilder::new()\n .brightness(30.0) // brighter\n \
.contrast(-20.0) // less contrast\n .grayscale() // b&w",
TextOptionsBuilder::new()
.x(3.0).y(1.5).w(6.5).h(2.5)
.font_size(14.0).color("2E4057")
.build(),
);
s.add_text(
"Emits <a:lum bright=\"30000\" contrast=\"-20000\"/> and <a:grayscl/> inside <a:blip>",
TextOptionsBuilder::new()
.x(0.3).y(4.5).w(9.4).h(0.9)
.font_size(11.0).color("666666").italic()
.build(),
);
}
{
let labels = vec!["Q1", "Q2", "Q3", "Q4"];
let series = vec![
ChartSeries::new("North", labels.clone(), vec![4.3, 2.5, 3.5, 4.5]),
ChartSeries::new("South", labels.clone(), vec![2.4, 4.4, 1.8, 2.8]),
ChartSeries::new("East", labels.clone(), vec![2.0, 2.0, 3.0, 5.0]),
];
let s = pres.add_slide();
heading(s, "Charts: Column Chart");
s.add_chart(
ChartType::Bar,
series.clone(),
ChartOptionsBuilder::new()
.x(0.5).y(1.2).w(9.0).h(4.3)
.title("Quarterly Sales (Column)")
.show_value()
.build(),
);
let s = pres.add_slide();
heading(s, "Charts: Horizontal Bar Chart");
s.add_chart(
ChartType::Bar,
series.clone(),
ChartOptionsBuilder::new()
.x(0.5).y(1.2).w(9.0).h(4.3)
.title("Quarterly Sales (Bar)")
.bar_dir(BarDir::Bar)
.bar_grouping(BarGrouping::Stacked)
.build(),
);
let s = pres.add_slide();
heading(s, "Charts: Line Chart");
s.add_chart(
ChartType::Line,
series.clone(),
ChartOptionsBuilder::new()
.x(0.5).y(1.2).w(9.0).h(4.3)
.title("Quarterly Trend (Line)")
.line_smooth()
.cat_axis_title("Quarter")
.val_axis_title("Units (M)")
.build(),
);
let pie_labels = vec!["Apples", "Bananas", "Cherries", "Dates"];
let pie_series = vec![ChartSeries::new("Fruit", pie_labels, vec![28.0, 17.0, 40.0, 15.0])];
let s = pres.add_slide();
heading(s, "Charts: Pie Chart");
s.add_chart(
ChartType::Pie,
pie_series.clone(),
ChartOptionsBuilder::new()
.x(1.0).y(1.2).w(8.0).h(4.3)
.title("Fruit Distribution")
.show_value()
.first_slice_angle(45)
.build(),
);
let s = pres.add_slide();
heading(s, "Charts: Doughnut Chart");
s.add_chart(
ChartType::Doughnut,
pie_series,
ChartOptionsBuilder::new()
.x(1.0).y(1.2).w(8.0).h(4.3)
.title("Fruit Distribution (Doughnut)")
.hole_size(60)
.legend_pos(LegendPos::Right)
.build(),
);
let s = pres.add_slide();
heading(s, "Charts: Area Chart");
s.add_chart(
ChartType::Area,
series.clone(),
ChartOptionsBuilder::new()
.x(0.5).y(1.2).w(9.0).h(4.3)
.title("Quarterly Sales (Area)")
.val_axis_min(0.0)
.val_axis_max(6.0)
.build(),
);
let scatter_series = vec![
ChartSeries::new("Dataset A", Vec::<String>::new(), vec![1.0, 4.0, 9.0, 16.0, 25.0]),
ChartSeries::new("Dataset B", Vec::<String>::new(), vec![2.0, 3.0, 5.0, 8.0, 13.0]),
];
let s = pres.add_slide();
heading(s, "Charts: Scatter Chart");
s.add_chart(
ChartType::Scatter,
scatter_series,
ChartOptionsBuilder::new()
.x(0.5).y(1.2).w(9.0).h(4.3)
.title("Scatter Plot")
.no_grid_lines()
.build(),
);
}
let out = "features.pptx";
pres.write_to_file(out).expect("write failed");
println!("Wrote {out}");
println!("Open in LibreOffice Impress / PowerPoint to review all features.");
}