diff --git a/src/core/presentations.rs b/src/core/presentations.rs index 106f8c4..19f9166 100644 --- a/src/core/presentations.rs +++ b/src/core/presentations.rs @@ -1,12 +1,13 @@ use crisp::types::{Keyword, Symbol, Value}; use miette::{IntoDiagnostic, Result}; +use mupdf::{Document, Page}; use serde::{Deserialize, Serialize}; use sqlx::{ pool::PoolConnection, prelude::FromRow, query, sqlite::SqliteRow, Row, Sqlite, SqliteConnection, SqlitePool, }; -use std::path::PathBuf; -use tracing::error; +use std::{path::PathBuf, sync::Arc}; +use tracing::{debug, error}; use crate::{Background, Slide, SlideBuilder, TextAlignment}; @@ -27,9 +28,7 @@ pub enum PresKind { Generic, } -#[derive( - Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, -)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Presentation { pub id: i32, pub title: String, @@ -37,6 +36,17 @@ pub struct Presentation { pub kind: PresKind, } +impl Eq for Presentation {} + +impl PartialEq for Presentation { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + && self.title == other.title + && self.path == other.path + && self.kind == other.kind + } +} + impl From<&Presentation> for Value { fn from(value: &Presentation) -> Self { Self::List(vec![Self::Symbol(Symbol("presentation".into()))]) @@ -117,22 +127,36 @@ impl ServiceTrait for Presentation { } fn to_slides(&self) -> Result> { - let slide = SlideBuilder::new() - .background( - Background::try_from(self.path.clone()) - .into_diagnostic()?, - ) - .text("") - .audio("") - .font("") - .font_size(50) - .text_alignment(TextAlignment::MiddleCenter) - .video_loop(false) - .video_start_time(0.0) - .video_end_time(0.0) - .build()?; - - Ok(vec![slide]) + debug!(?self); + let background = Background::try_from(self.path.clone()) + .into_diagnostic()?; + debug!(?background); + let doc = Document::open(background.path.as_path()) + .into_diagnostic()?; + debug!(?doc); + let pages = doc.page_count().into_diagnostic()?; + debug!(?pages); + let mut slides: Vec = vec![]; + for page in 0..pages { + let slide = SlideBuilder::new() + .background( + Background::try_from(self.path.clone()) + .into_diagnostic()?, + ) + .text("") + .audio("") + .font("") + .font_size(50) + .text_alignment(TextAlignment::MiddleCenter) + .video_loop(false) + .video_start_time(0.0) + .video_end_time(0.0) + .pdf_index(page as u32) + .build()?; + slides.push(slide); + } + debug!(?slides); + Ok(slides) } fn box_clone(&self) -> Box { diff --git a/src/core/service_items.rs b/src/core/service_items.rs index e54b07f..da6267d 100644 --- a/src/core/service_items.rs +++ b/src/core/service_items.rs @@ -318,8 +318,8 @@ impl From<&Image> for ServiceItem { impl From<&Presentation> for ServiceItem { fn from(presentation: &Presentation) -> Self { - if let Ok(slides) = presentation.to_slides() { - Self { + match presentation.to_slides() { + Ok(slides) => Self { kind: ServiceItemKind::Presentation( presentation.clone(), ), @@ -327,15 +327,17 @@ impl From<&Presentation> for ServiceItem { title: presentation.title.clone(), slides: slides.into(), ..Default::default() - } - } else { - Self { - kind: ServiceItemKind::Presentation( - presentation.clone(), - ), - database_id: presentation.id, - title: presentation.title.clone(), - ..Default::default() + }, + Err(e) => { + error!(?e); + Self { + kind: ServiceItemKind::Presentation( + presentation.clone(), + ), + database_id: presentation.id, + title: presentation.title.clone(), + ..Default::default() + } } } } diff --git a/src/core/slide.rs b/src/core/slide.rs index d1ef8b6..97ad589 100644 --- a/src/core/slide.rs +++ b/src/core/slide.rs @@ -11,7 +11,10 @@ use std::{ }; use tracing::error; -use crate::ui::text_svg::{self, TextSvg}; +use crate::ui::{ + pdf::PdfViewer, + text_svg::{self, TextSvg}, +}; use super::songs::Song; @@ -29,6 +32,7 @@ pub struct Slide { video_loop: bool, video_start_time: f32, video_end_time: f32, + pdf_index: u32, #[serde(skip)] pub text_svg: Option, } @@ -40,6 +44,8 @@ pub enum BackgroundKind { #[default] Image, Video, + Pdf, + Html, } #[derive(Debug, Clone, Default)] @@ -157,16 +163,22 @@ impl TryFrom for Background { .to_str() .unwrap_or_default(); match extension { - "jpeg" | "jpg" | "png" | "webp" | "html" => { - Ok(Self { - path: value, - kind: BackgroundKind::Image, - }) - } + "jpeg" | "jpg" | "png" | "webp" => Ok(Self { + path: value, + kind: BackgroundKind::Image, + }), "mp4" | "mkv" | "webm" => Ok(Self { path: value, kind: BackgroundKind::Video, }), + "pdf" => Ok(Self { + path: value, + kind: BackgroundKind::Pdf, + }), + "html" => Ok(Self { + path: value, + kind: BackgroundKind::Html, + }), _ => Err(ParseError::NonBackgroundFile), } } @@ -281,6 +293,11 @@ impl Slide { self } + pub fn set_pdf_index(mut self, pdf_index: u32) -> Self { + self.pdf_index = pdf_index; + self + } + pub fn background(&self) -> &Background { &self.background } @@ -309,6 +326,10 @@ impl Slide { self.audio.clone() } + pub fn pdf_index(&self) -> u32 { + self.pdf_index + } + pub fn song_slides(song: &Song) -> Result> { let lyrics = song.get_lyrics()?; let slides: Vec = lyrics @@ -524,6 +545,7 @@ pub struct SlideBuilder { video_loop: Option, video_start_time: Option, video_end_time: Option, + pdf_index: Option, #[serde(skip)] text_svg: Option, } @@ -607,6 +629,14 @@ impl SlideBuilder { self } + pub(crate) fn pdf_index( + mut self, + pdf_index: impl Into, + ) -> Self { + let _ = self.pdf_index.insert(pdf_index.into()); + self + } + pub(crate) fn build(self) -> Result { let Some(background) = self.background else { return Err(miette!("No background")); @@ -643,6 +673,7 @@ impl SlideBuilder { video_start_time, video_end_time, text_svg: self.text_svg, + pdf_index: self.pdf_index.unwrap_or_default(), ..Default::default() }) } diff --git a/src/ui/pdf.rs b/src/ui/pdf.rs index 392caca..60e9fe5 100644 --- a/src/ui/pdf.rs +++ b/src/ui/pdf.rs @@ -11,26 +11,42 @@ use miette::Severity; use mupdf::Colorspace; use mupdf::Document; use mupdf::Matrix; -use mupdf::Page; +use tracing::debug; +use tracing::error; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct PdfViewer { document: Option, - pages: Option>, - current_page: Option, + pages: Option>, + current_page: Option, current_index: usize, - handle: Option, } -enum Message {} +pub enum Message {} impl PdfViewer { pub fn with_pdf(pdf: impl AsRef) -> Result { let pdf_path = pdf.as_ref(); let document = Document::open(pdf_path).into_diagnostic()?; let pages = document.pages().into_diagnostic()?; - let pages: Vec = - pages.filter_map(|page| page.ok()).collect(); + let pages: Vec = pages + .filter_map(|page| { + let Some(page) = page.ok() else { + return None; + }; + let matrix = Matrix::IDENTITY; + let colorspace = Colorspace::device_rgb(); + let Ok(pixmap) = page + .to_pixmap(&matrix, &colorspace, true, true) + .into_diagnostic() + else { + error!("Can't turn this page into pixmap"); + return None; + }; + let handle = pixmap.samples().to_vec(); + Some(Handle::from_bytes(handle)) + }) + .collect(); let Some(page) = document.pages().into_diagnostic()?.next() else { return Err(miette::miette!( @@ -48,9 +64,8 @@ impl PdfViewer { Ok(Self { document: Some(document), pages: Some(pages), - current_page: Some(page), current_index: 0, - handle: Some(Handle::from_bytes(handle)), + current_page: Some(Handle::from_bytes(handle)), }) } @@ -61,8 +76,29 @@ impl PdfViewer { let pdf_path = pdf.as_ref(); let document = Document::open(pdf_path).into_diagnostic()?; let pages = document.pages().into_diagnostic()?; - let pages: Vec = - pages.filter_map(|page| page.ok()).collect(); + + let pages: Vec = pages + .filter_map(|page| { + let Some(page) = page.ok() else { + return None; + }; + let matrix = Matrix::IDENTITY; + let colorspace = Colorspace::device_rgb(); + let Ok(pixmap) = page + .to_pixmap(&matrix, &colorspace, true, true) + .into_diagnostic() + else { + error!("Can't turn this page into pixmap"); + return None; + }; + debug!(?pixmap); + Some(Handle::from_rgba( + pixmap.width(), + pixmap.height(), + pixmap.samples().to_vec(), + )) + }) + .collect(); let Some(page) = document.pages().into_diagnostic()?.next() else { return Err(miette::miette!( @@ -70,18 +106,11 @@ impl PdfViewer { "There isn't a first page here" )); }; - let page = page.into_diagnostic()?; - let matrix = Matrix::IDENTITY; - let colorspace = Colorspace::device_rgb(); - let pixmap = page - .to_pixmap(&matrix, &colorspace, true, true) - .into_diagnostic()?; - let handle = pixmap.samples().to_vec(); + self.current_page = pages.get(0).map(|h| h.to_owned()); self.document = Some(document); self.pages = Some(pages); - self.current_page = Some(page); self.current_index = 0; - self.handle = Some(Handle::from_bytes(handle)); + debug!(?self); Ok(()) } @@ -92,15 +121,8 @@ impl PdfViewer { let Some(page) = pages.get(self.current_index + 1) else { return Err(miette::miette!("There isn't a next page")); }; - let matrix = Matrix::IDENTITY; - let colorspace = Colorspace::device_rgb(); - let pixmap = page - .to_pixmap(&matrix, &colorspace, true, true) - .into_diagnostic()?; - let handle = pixmap.samples().to_vec(); self.current_page = Some(page.to_owned()); self.current_index += 1; - self.handle = Some(Handle::from_bytes(handle)); Ok(()) } @@ -116,20 +138,16 @@ impl PdfViewer { "There isn't a previous page" )); }; - let matrix = Matrix::IDENTITY; - let colorspace = Colorspace::device_rgb(); - let pixmap = page - .to_pixmap(&matrix, &colorspace, true, true) - .into_diagnostic()?; - let handle = pixmap.samples().to_vec(); self.current_page = Some(page.to_owned()); self.current_index -= 1; - self.handle = Some(Handle::from_bytes(handle)); Ok(()) } - pub fn view(&self) -> Option> { - let Some(handle) = self.handle.clone() else { + pub fn view(&self, index: u32) -> Option> { + let Some(pages) = &self.pages else { + return None; + }; + let Some(handle) = pages.get(index as usize) else { return None; }; Some( diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index a521374..ea268cc 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -174,6 +174,7 @@ impl Presenter { }, scroll_id: Id::unique(), current_font: cosmic::font::default(), + pdf_viewer: PdfViewer::default(), } } @@ -206,7 +207,8 @@ impl Presenter { Message::SlideChange(slide) => { let slide_text = slide.text(); debug!(slide_text, "slide changed"); - debug!("comparing background..."); + let bg = slide.background().clone(); + debug!(?bg, "comparing background..."); let backgrounds_match = self.current_slide.background() == slide.background(); @@ -223,6 +225,11 @@ impl Presenter { self.reset_video(); } + if slide.background().kind == BackgroundKind::Pdf { + self.pdf_viewer + .insert_pdf(&slide.background().path); + } + let offset = AbsoluteOffset { x: { if self.current_slide_index > 2 { @@ -404,6 +411,7 @@ impl Presenter { slide_view( &self.current_slide, &self.video, + &self.pdf_viewer, self.current_font, false, true, @@ -414,6 +422,7 @@ impl Presenter { slide_view( &self.current_slide, &self.video, + &self.pdf_viewer, self.current_font, false, false, @@ -450,6 +459,7 @@ impl Presenter { let container = slide_view( &slide, &self.video, + &self.pdf_viewer, font, true, false, @@ -636,7 +646,6 @@ impl Presenter { fn reset_video(&mut self) { match self.current_slide.background().kind { - BackgroundKind::Image => self.video = None, BackgroundKind::Video => { let path = &self.current_slide.background().path; if path.exists() { @@ -660,6 +669,7 @@ impl Presenter { self.video = None; } } + _ => self.video = None, } } @@ -712,6 +722,7 @@ fn scale_font(font_size: f32, width: f32) -> f32 { pub(crate) fn slide_view<'a>( slide: &'a Slide, video: &'a Option