#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)]
pub enum ShowType {
#[default]
Speaker,
Kiosk,
Browsed,
}
impl ShowType {
pub fn to_xml_element(&self) -> &'static str {
match self {
ShowType::Speaker => r#"<p:present/>"#,
ShowType::Kiosk => r#"<p:kiosk restart="300000"/>"#,
ShowType::Browsed => r#"<p:browse showScrollbar="1"/>"#,
}
}
}
#[derive(Clone, Debug)]
pub struct PenColor {
pub color: String,
}
impl PenColor {
pub fn new(color: &str) -> Self {
Self {
color: color.trim_start_matches('#').to_uppercase(),
}
}
pub fn red() -> Self { Self::new("FF0000") }
pub fn blue() -> Self { Self::new("0000FF") }
pub fn black() -> Self { Self::new("000000") }
}
impl Default for PenColor {
fn default() -> Self {
Self::red()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub enum SlideRange {
#[default]
All,
Range { start: u32, end: u32 },
Custom(Vec<u32>),
}
#[derive(Clone, Debug, Default)]
pub struct SlideShowSettings {
pub show_type: ShowType,
pub loop_continuously: bool,
pub show_without_narration: bool,
pub show_without_animation: bool,
pub pen_color: PenColor,
pub slide_range: SlideRange,
pub use_timings: bool,
pub show_media_controls: bool,
}
impl SlideShowSettings {
pub fn new() -> Self {
Self {
use_timings: true,
show_media_controls: true,
..Default::default()
}
}
pub fn show_type(mut self, show_type: ShowType) -> Self {
self.show_type = show_type;
self
}
pub fn loop_continuously(mut self, looping: bool) -> Self {
self.loop_continuously = looping;
self
}
pub fn without_narration(mut self, val: bool) -> Self {
self.show_without_narration = val;
self
}
pub fn without_animation(mut self, val: bool) -> Self {
self.show_without_animation = val;
self
}
pub fn pen_color(mut self, color: PenColor) -> Self {
self.pen_color = color;
self
}
pub fn slide_range(mut self, range: SlideRange) -> Self {
self.slide_range = range;
self
}
pub fn use_timings(mut self, val: bool) -> Self {
self.use_timings = val;
self
}
pub fn show_media_controls(mut self, val: bool) -> Self {
self.show_media_controls = val;
self
}
pub fn kiosk() -> Self {
Self::new()
.show_type(ShowType::Kiosk)
.loop_continuously(true)
.without_narration(true)
}
pub fn to_xml(&self) -> String {
let mut attrs = Vec::new();
if self.loop_continuously {
attrs.push(r#"loop="1""#.to_string());
}
if self.show_without_narration {
attrs.push(r#"showNarration="0""#.to_string());
}
if self.show_without_animation {
attrs.push(r#"showAnimation="0""#.to_string());
}
if self.use_timings {
attrs.push(r#"useTimings="1""#.to_string());
}
let attrs_str = if attrs.is_empty() {
String::new()
} else {
format!(" {}", attrs.join(" "))
};
let mut xml = format!(r#"<p:showPr{}>"#, attrs_str);
xml.push_str(self.show_type.to_xml_element());
match &self.slide_range {
SlideRange::All => xml.push_str(r#"<p:sldAll/>"#),
SlideRange::Range { start, end } => {
xml.push_str(&format!(r#"<p:sldRg st="{}" end="{}"/>"#, start, end));
}
SlideRange::Custom(slides) => {
xml.push_str("<p:custShow>");
for s in slides {
xml.push_str(&format!(r#"<p:sldId id="{}"/>"#, s));
}
xml.push_str("</p:custShow>");
}
}
xml.push_str(&format!(
r#"<p:penClr><a:srgbClr val="{}"/></p:penClr>"#,
self.pen_color.color
));
xml.push_str("</p:showPr>");
xml
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_show_type_default() {
assert_eq!(ShowType::default(), ShowType::Speaker);
}
#[test]
fn test_show_type_xml() {
assert!(ShowType::Speaker.to_xml_element().contains("present"));
assert!(ShowType::Kiosk.to_xml_element().contains("kiosk"));
assert!(ShowType::Browsed.to_xml_element().contains("browse"));
}
#[test]
fn test_pen_color_presets() {
assert_eq!(PenColor::red().color, "FF0000");
assert_eq!(PenColor::blue().color, "0000FF");
assert_eq!(PenColor::black().color, "000000");
}
#[test]
fn test_pen_color_custom() {
let pc = PenColor::new("#00FF00");
assert_eq!(pc.color, "00FF00");
}
#[test]
fn test_slide_range_default() {
assert_eq!(SlideRange::default(), SlideRange::All);
}
#[test]
fn test_slide_show_settings_new() {
let s = SlideShowSettings::new();
assert_eq!(s.show_type, ShowType::Speaker);
assert!(!s.loop_continuously);
assert!(s.use_timings);
assert!(s.show_media_controls);
}
#[test]
fn test_slide_show_settings_builder() {
let s = SlideShowSettings::new()
.show_type(ShowType::Kiosk)
.loop_continuously(true)
.without_narration(true)
.without_animation(true)
.pen_color(PenColor::blue())
.slide_range(SlideRange::Range { start: 1, end: 5 })
.use_timings(false)
.show_media_controls(false);
assert_eq!(s.show_type, ShowType::Kiosk);
assert!(s.loop_continuously);
assert!(s.show_without_narration);
assert!(s.show_without_animation);
assert_eq!(s.pen_color.color, "0000FF");
assert!(!s.use_timings);
}
#[test]
fn test_kiosk_preset() {
let s = SlideShowSettings::kiosk();
assert_eq!(s.show_type, ShowType::Kiosk);
assert!(s.loop_continuously);
assert!(s.show_without_narration);
}
#[test]
fn test_xml_basic() {
let s = SlideShowSettings::new();
let xml = s.to_xml();
assert!(xml.contains("<p:showPr"));
assert!(xml.contains("<p:present/>"));
assert!(xml.contains("<p:sldAll/>"));
assert!(xml.contains("penClr"));
assert!(xml.contains("</p:showPr>"));
}
#[test]
fn test_xml_kiosk() {
let s = SlideShowSettings::kiosk();
let xml = s.to_xml();
assert!(xml.contains("loop=\"1\""));
assert!(xml.contains("showNarration=\"0\""));
assert!(xml.contains("<p:kiosk"));
}
#[test]
fn test_xml_slide_range() {
let s = SlideShowSettings::new()
.slide_range(SlideRange::Range { start: 2, end: 8 });
let xml = s.to_xml();
assert!(xml.contains(r#"st="2""#));
assert!(xml.contains(r#"end="8""#));
}
#[test]
fn test_xml_custom_slides() {
let s = SlideShowSettings::new()
.slide_range(SlideRange::Custom(vec![256, 258, 260]));
let xml = s.to_xml();
assert!(xml.contains("custShow"));
assert!(xml.contains(r#"id="256""#));
assert!(xml.contains(r#"id="260""#));
}
#[test]
fn test_xml_pen_color() {
let s = SlideShowSettings::new().pen_color(PenColor::new("00FF00"));
let xml = s.to_xml();
assert!(xml.contains("00FF00"));
}
}