use serde::{Deserialize, Serialize};
use crate::importer::SongFile;
use crate::song::Song;
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct PresentationChapter {
pub slides: Vec<Slide>,
pub linked_entity: LinkedEntity,
}
impl PresentationChapter {
pub fn new(slides: Vec<Slide>, linked_entity: LinkedEntity) -> Self {
PresentationChapter {
slides,
linked_entity,
}
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub enum LinkedEntity {
Song(Song),
Title(String),
SongFile(SongFile),
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub enum SlideContent {
SingleLanguageMainContent(SingleLanguageMainContentSlide),
Title(TitleSlide),
MultiLanguageMainContent(MultiLanguageMainContentSlide),
Complex(ComplexSlide),
SimplePicture(SimplePictureSlide),
Empty(EmptySlide),
PdfPage(PdfPageSlide),
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct Slide {
pub slide_content: SlideContent,
pub linked_file: Option<SongFile>,
}
impl Slide {
pub fn new_empty_slide(black_background: bool) -> Self {
Slide {
slide_content: SlideContent::Empty(EmptySlide { black_background }),
linked_file: None,
}
}
pub fn new_content_slide(
main_text: String,
spoiler_text: Option<String>,
meta_text: Option<String>,
) -> Self {
Slide {
slide_content: SlideContent::SingleLanguageMainContent(
SingleLanguageMainContentSlide::new(
main_text.trim().to_string(),
spoiler_text.map(|string| string.trim().to_string()),
meta_text.map(|string| string.trim().to_string()),
),
),
linked_file: None,
}
}
pub fn new_title_slide(title_text: String, meta_text: Option<String>) -> Self {
Slide {
slide_content: SlideContent::Title(TitleSlide {
title_text: title_text.trim().to_string(),
meta_text: meta_text.map(|string| string.trim().to_string()),
}),
linked_file: None,
}
}
pub fn new_pdf_page_slide(pdf_path: String, page_number: u32) -> Self {
Slide {
slide_content: SlideContent::PdfPage(PdfPageSlide {
pdf_path,
page_number,
}),
linked_file: None,
}
}
pub fn new_multi_language_content_slide(
main_text_list: Vec<String>,
spoiler_text_vector: Vec<String>,
meta_text: Option<String>,
) -> Self {
let trimmed_main: Vec<String> = main_text_list
.into_iter()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
let trimmed_spoiler: Vec<String> = spoiler_text_vector
.into_iter()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
let parsed_meta: Option<String> = match meta_text {
Some(s) if !s.trim().is_empty() => Some(s.trim().to_string()),
_ => None,
};
Slide {
slide_content: SlideContent::MultiLanguageMainContent(
MultiLanguageMainContentSlide {
main_text_list: trimmed_main,
spoiler_text_vector: trimmed_spoiler,
meta_text: parsed_meta,
},
),
linked_file: None,
}
}
pub fn new_complex_slide(
rows: Vec<SlideRow>,
spoiler: Vec<SlideRow>,
meta_text: Option<String>,
line_count: usize,
) -> Self {
Slide {
slide_content: SlideContent::Complex(ComplexSlide {
rows,
spoiler,
meta_text: match meta_text {
Some(text) if !text.trim().is_empty() => Some(text.trim().to_string()),
_ => None,
},
line_count,
}),
linked_file: None,
}
}
pub fn with_song_file(self, linked_file: SongFile) -> Self {
let mut cloned_self = self.clone();
cloned_self.linked_file = Some(linked_file);
cloned_self
}
pub fn has_spoiler(&self) -> bool {
match &self.slide_content {
SlideContent::SingleLanguageMainContent(single_language_main_content_slide) => {
single_language_main_content_slide.spoiler_text.is_some()
}
SlideContent::Title(_) => false,
SlideContent::MultiLanguageMainContent(multi_language_main_content_slide) => {
!multi_language_main_content_slide
.spoiler_text_vector
.is_empty()
}
SlideContent::Complex(complex) => !complex.spoiler.is_empty(),
SlideContent::SimplePicture(_) => false,
SlideContent::Empty(_) => false,
SlideContent::PdfPage(_) => false,
}
}
pub fn has_meta_text(&self) -> bool {
match &self.slide_content {
SlideContent::SingleLanguageMainContent(single_language_main_content_slide) => {
single_language_main_content_slide.meta_text.is_some()
}
SlideContent::Title(title_slide) => title_slide.meta_text.is_some(),
SlideContent::MultiLanguageMainContent(multi_language_main_content_slide) => {
multi_language_main_content_slide.meta_text.is_some()
}
SlideContent::Complex(complex) => complex.meta_text.is_some(),
SlideContent::SimplePicture(_) => false,
SlideContent::Empty(_) => false,
SlideContent::PdfPage(_) => false,
}
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct SingleLanguageMainContentSlide {
main_text: String,
spoiler_text: Option<String>,
meta_text: Option<String>,
}
impl SingleLanguageMainContentSlide {
fn new(main_text: String, spoiler_text: Option<String>, meta_text: Option<String>) -> Self {
let parsed_spoiler_text: Option<String> = match spoiler_text {
Some(str) => match str.trim() {
"" => None,
_ => Some(str),
},
None => None,
};
let parsed_meta_text: Option<String> = match meta_text {
Some(str) => match str.trim() {
"" => None,
_ => Some(str),
},
None => None,
};
SingleLanguageMainContentSlide {
main_text,
spoiler_text: parsed_spoiler_text,
meta_text: parsed_meta_text,
}
}
pub fn spoiler_text(self) -> Option<String> {
self.spoiler_text
}
pub fn main_text(self) -> String {
self.main_text
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct MultiLanguageMainContentSlide {
pub main_text_list: Vec<String>,
pub spoiler_text_vector: Vec<String>,
pub meta_text: Option<String>,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct EmptySlide {
pub black_background: bool,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct TitleSlide {
pub title_text: String,
pub meta_text: Option<String>,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct SimplePictureSlide {
picture_path: String,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct PdfPageSlide {
pub pdf_path: String,
pub page_number: u32,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct ComplexSlide {
pub rows: Vec<SlideRow>,
pub spoiler: Vec<SlideRow>,
pub meta_text: Option<String>,
pub line_count: usize,
}
impl ComplexSlide {
pub fn rows_without_repetition(&self) -> impl Iterator<Item = &SlideRow> {
self.rows.iter().filter(|row| !row.redundant)
}
pub fn notation(&self) -> Option<&SlideRow> {
self.rows.iter().find(|row| row.is_notation())
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct SlideRow {
pub kind: SlideRowKind,
pub content: String,
#[serde(default)]
pub redundant: bool,
}
impl SlideRow {
pub fn notation(abc: impl Into<String>, syllables: usize) -> SlideRow {
SlideRow {
kind: SlideRowKind::Notation { syllables },
content: abc.into(),
redundant: false,
}
}
pub fn lyrics(language: Option<String>, text: impl Into<String>) -> SlideRow {
SlideRow {
kind: SlideRowKind::Lyrics { language },
content: text.into(),
redundant: false,
}
}
pub fn also_shown_in_notation(mut self) -> SlideRow {
self.redundant = true;
self
}
pub fn is_notation(&self) -> bool {
matches!(self.kind, SlideRowKind::Notation { .. })
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub enum SlideRowKind {
Notation {
syllables: usize,
},
Lyrics {
language: Option<String>,
},
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
pub enum SlideElement {
Notation,
Lyrics(String),
}
impl SlideElement {
pub fn parse(text: &str) -> SlideElement {
match text.trim().to_lowercase().as_str() {
"notation" | "notes" | "noten" | "abc" | "score" | "music" => SlideElement::Notation,
language => SlideElement::Lyrics(language.to_string()),
}
}
}
impl std::str::FromStr for SlideElement {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(SlideElement::parse(s))
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub enum LanguageConfiguration {
SingleLanguage(Option<String>),
MultiLanguage(Vec<String>),
Complex(Vec<SlideElement>),
}
impl Default for LanguageConfiguration {
fn default() -> Self {
LanguageConfiguration::SingleLanguage(None)
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct SlideSettings {
pub title_slide: bool,
pub show_spoiler: bool,
pub show_meta_information: ShowMetaInformation,
pub meta_syntax: String,
pub empty_last_slide: bool,
pub max_lines: Option<usize>,
pub language: LanguageConfiguration,
}
impl Default for SlideSettings {
fn default() -> Self {
SlideSettings {
title_slide: true,
meta_syntax: "".to_string(),
show_meta_information: ShowMetaInformation::all(),
empty_last_slide: true,
show_spoiler: true,
max_lines: None,
language: LanguageConfiguration::default(),
}
}
}
#[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Debug, Default)]
pub struct ShowMetaInformation {
pub title_slide: bool,
pub first_slide: bool,
pub last_slide: bool,
}
impl ShowMetaInformation {
pub fn none() -> Self {
ShowMetaInformation::default()
}
pub fn title_slide() -> Self {
ShowMetaInformation {
title_slide: true,
..Self::none()
}
}
pub fn first_slide() -> Self {
ShowMetaInformation {
first_slide: true,
..Self::none()
}
}
pub fn last_slide() -> Self {
ShowMetaInformation {
last_slide: true,
..Self::none()
}
}
pub fn first_and_last_slide() -> Self {
ShowMetaInformation {
title_slide: false,
first_slide: true,
last_slide: true,
}
}
pub fn all() -> Self {
ShowMetaInformation {
title_slide: true,
first_slide: true,
last_slide: true,
}
}
pub fn is_none(&self) -> bool {
!self.title_slide && !self.first_slide && !self.last_slide
}
pub fn on_title_slide(&self) -> bool {
self.title_slide
}
pub fn on_first_slide(&self) -> bool {
self.first_slide
}
pub fn on_last_slide(&self) -> bool {
self.last_slide
}
pub fn on_content_slide(&self, index: usize, count: usize) -> bool {
if count == 0 {
return false;
}
(self.first_slide && index == 0) || (self.last_slide && index + 1 == count)
}
pub fn from_bits(bits: u8) -> Self {
ShowMetaInformation {
first_slide: bits & 0b001 != 0,
last_slide: bits & 0b010 != 0,
title_slide: bits & 0b100 != 0,
}
}
pub fn to_bits(&self) -> u8 {
(self.first_slide as u8) | ((self.last_slide as u8) << 1) | ((self.title_slide as u8) << 2)
}
}
pub fn wrap_blocks(
blocks: &[Vec<Vec<String>>],
maximum_lines: usize,
persistence: bool,
) -> Vec<Vec<Vec<String>>> {
if blocks.is_empty() {
return blocks.to_vec();
}
let first_block_length = blocks[0].len();
for group in blocks.iter().skip(1) {
if group.len() != first_block_length {
panic!("The length of every block has to be equal.")
}
}
let mut wrapped_blocks = blocks.to_vec();
let mut block_index: usize = 0;
let mut skip_next: bool = false;
while block_index < wrapped_blocks[0].len() {
if skip_next {
skip_next = false;
block_index += 1;
continue;
}
if wrapped_blocks[0][block_index].len() > maximum_lines {
let target_first_len = maximum_lines;
let has_next = wrapped_blocks[0].get(block_index + 1).is_some();
wrapped_blocks
.iter_mut()
.for_each(|block| block.insert(block_index + 1, vec![]));
let destination_index = block_index + 1;
let mut moved_line_count = 0;
while wrapped_blocks[0][block_index].len() > target_first_len {
let primary_line = wrapped_blocks[0][block_index].remove(target_first_len);
wrapped_blocks[0][destination_index].insert(moved_line_count, primary_line);
for block in wrapped_blocks.iter_mut().skip(1) {
if target_first_len < block[block_index].len() {
let primary_line = block[block_index].remove(target_first_len);
block[destination_index].insert(moved_line_count, primary_line);
}
}
moved_line_count += 1;
}
if !persistence && has_next {
for block in wrapped_blocks.iter_mut() {
if block.len() > block_index + 2 {
let original_next_content = std::mem::take(&mut block[block_index + 2]);
block[destination_index].extend(original_next_content);
}
}
return wrapped_blocks;
}
}
block_index += 1;
}
wrapped_blocks
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_empty_slide() {
let slide = Slide::new_empty_slide(false);
assert!(matches!(slide.slide_content, SlideContent::Empty(_)));
}
#[test]
fn create_pdf_page_slide() {
let slide = Slide::new_pdf_page_slide("/path/to/document.pdf".to_string(), 3);
assert!(matches!(slide.slide_content, SlideContent::PdfPage(_)));
if let SlideContent::PdfPage(pdf_slide) = &slide.slide_content {
assert_eq!(pdf_slide.pdf_path, "/path/to/document.pdf");
assert_eq!(pdf_slide.page_number, 3);
}
assert!(!slide.has_spoiler());
assert!(!slide.has_meta_text());
}
#[test]
fn pdf_page_slide_is_serde_ready() {
let slide = Slide::new_pdf_page_slide("/path/to/file.pdf".to_string(), 1);
let json = serde_json::to_string(&slide).expect("Serialization failed");
let deserialized: Slide = serde_json::from_str(&json).expect("Deserialization failed");
assert_eq!(slide, deserialized);
}
#[test]
fn check_has_spoiler_function() {
let slide_1 = Slide::new_content_slide("Test".to_string(), Some("Hallo".to_string()), None);
assert!(slide_1.has_spoiler());
let slide_2 = Slide::new_content_slide(
"Test".to_string(),
Some("".to_string()),
Some("".to_string()),
);
assert!(!slide_2.has_spoiler());
}
#[test]
fn test_wrap_blocks_function() {
let example_blocks = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
"A4".to_string(),
"A5".to_string(),
],
vec![
"A6".to_string(),
"A7".to_string(),
"A8".to_string(),
"A9".to_string(),
"A10".to_string(),
],
],
vec![
vec![
"B1".to_string(),
"B2".to_string(),
"B3".to_string(),
"B4".to_string(),
],
vec![
"B5".to_string(),
"B6".to_string(),
"B7".to_string(),
"B8".to_string(),
"B9".to_string(),
],
],
];
let wrapped_blocks = wrap_blocks(&example_blocks, 3, true);
dbg!(&wrapped_blocks);
}
#[test]
fn test_wrap_blocks_with_odd_lines() {
let blocks_with_odd_lines = vec![
vec![
vec![
"L1".to_string(),
"L2".to_string(),
"L3".to_string(),
"L4".to_string(),
"L5".to_string(),
],
],
];
let wrapped_blocks = wrap_blocks(&blocks_with_odd_lines, 3, true);
assert_eq!(wrapped_blocks[0][0].len(), 3);
assert_eq!(wrapped_blocks[0][1].len(), 2);
assert_eq!(wrapped_blocks[0][0], vec!["L1".to_string(), "L2".to_string(), "L3".to_string()]);
assert_eq!(wrapped_blocks[0][1], vec!["L4".to_string(), "L5".to_string()]);
}
#[test]
fn test_wrap_blocks_empty() {
let empty_blocks: Vec<Vec<Vec<String>>> = vec![];
let wrapped_empty = wrap_blocks(&empty_blocks, 3, true);
assert_eq!(wrapped_empty.len(), 0);
let blocks_with_empty = vec![vec![vec![]]];
let wrapped_with_empty = wrap_blocks(&blocks_with_empty, 3, true);
assert_eq!(wrapped_with_empty, blocks_with_empty);
}
#[test]
fn test_wrap_blocks_exact_maximum() {
let blocks_exact = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
],
],
];
let wrapped_exact = wrap_blocks(&blocks_exact, 3, true);
assert_eq!(wrapped_exact, blocks_exact);
assert_eq!(wrapped_exact[0][0].len(), 3);
assert_eq!(wrapped_exact[0].len(), 1); }
#[test]
fn test_wrap_blocks_persistence() {
let test_blocks = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
"A4".to_string(),
],
vec![
"B1".to_string(),
"B2".to_string(),
],
],
];
let wrapped_persistent = wrap_blocks(&test_blocks, 2, true);
assert_eq!(wrapped_persistent[0].len(), 3);
assert_eq!(wrapped_persistent[0][0], vec!["A1".to_string(), "A2".to_string()]);
assert_eq!(wrapped_persistent[0][1], vec!["A3".to_string(), "A4".to_string()]);
assert_eq!(wrapped_persistent[0][2], vec!["B1".to_string(), "B2".to_string()]);
let test_blocks_with_next = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
"A4".to_string(),
],
vec![
"B1".to_string(),
"B2".to_string(),
],
],
];
let wrapped_non_persistent = wrap_blocks(&test_blocks_with_next, 2, false);
assert_eq!(wrapped_non_persistent[0].len(), 3);
assert_eq!(wrapped_non_persistent[0][0], vec!["A1".to_string(), "A2".to_string()]);
assert_eq!(wrapped_non_persistent[0][1], vec!["A3".to_string(), "A4".to_string(), "B1".to_string(), "B2".to_string()]);
}
#[test]
fn test_wrap_blocks_multiple_blocks() {
let multiple_blocks = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
"A4".to_string(),
],
],
vec![
vec![
"B1".to_string(),
"B2".to_string(),
"B3".to_string(),
"B4".to_string(),
],
],
];
let wrapped_multiple = wrap_blocks(&multiple_blocks, 2, true);
assert_eq!(wrapped_multiple.len(), 2);
assert_eq!(wrapped_multiple[0].len(), 2);
assert_eq!(wrapped_multiple[1].len(), 2);
assert_eq!(wrapped_multiple[0][0], vec!["A1".to_string(), "A2".to_string()]);
assert_eq!(wrapped_multiple[0][1], vec!["A3".to_string(), "A4".to_string()]);
assert_eq!(wrapped_multiple[1][0], vec!["B1".to_string(), "B2".to_string()]);
assert_eq!(wrapped_multiple[1][1], vec!["B3".to_string(), "B4".to_string()]);
}
#[test]
fn test_wrap_blocks_edge_cases() {
let blocks_for_extreme = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
],
],
];
let wrapped_extreme = wrap_blocks(&blocks_for_extreme, 1, true);
assert_eq!(wrapped_extreme[0].len(), 3);
assert_eq!(wrapped_extreme[0][0], vec!["A1".to_string()]);
assert_eq!(wrapped_extreme[0][1], vec!["A2".to_string()]);
assert_eq!(wrapped_extreme[0][2], vec!["A3".to_string()]);
}
}