From 4ccb186189b45bc5b6f9bb8f3233e689a5aeea81 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Fri, 29 Aug 2025 16:41:24 -0500 Subject: [PATCH] closer to using these text_svgs --- src/core/service_items.rs | 8 ++--- src/core/slide.rs | 63 ++++++++++++++------------------------- src/main.rs | 48 ++++++++++++++++++++++------- src/ui/presenter.rs | 11 +++++-- src/ui/song_editor.rs | 12 ++++---- src/ui/text_svg.rs | 45 ++++++++++++++++++++-------- 6 files changed, 112 insertions(+), 75 deletions(-) diff --git a/src/core/service_items.rs b/src/core/service_items.rs index 2ee9893..76e99cc 100644 --- a/src/core/service_items.rs +++ b/src/core/service_items.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::cmp::Ordering; use std::ops::Deref; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; use crisp::types::{Keyword, Symbol, Value}; @@ -24,7 +24,7 @@ pub struct ServiceItem { pub title: String, pub database_id: i32, pub kind: ServiceItemKind, - pub slides: Arc<[Slide]>, + pub slides: Vec, // pub item: Box, } @@ -122,7 +122,7 @@ impl Default for ServiceItem { title: String::default(), database_id: 0, kind: ServiceItemKind::Content(Slide::default()), - slides: Arc::new([]), + slides: vec![], // item: Box::new(Image::default()), } } @@ -172,7 +172,7 @@ impl From<&Value> for ServiceItem { kind: ServiceItemKind::Content( slide.clone(), ), - slides: Arc::new([slide]), + slides: vec![slide], } } else if let Some(background) = list.get(background_pos) diff --git a/src/core/slide.rs b/src/core/slide.rs index f54df80..d1ef8b6 100644 --- a/src/core/slide.rs +++ b/src/core/slide.rs @@ -30,7 +30,7 @@ pub struct Slide { video_start_time: f32, video_end_time: f32, #[serde(skip)] - pub text_svg: TextSvg, + pub text_svg: Option, } #[derive( @@ -261,6 +261,11 @@ impl Slide { self } + pub fn with_text_svg(mut self, text_svg: TextSvg) -> Self { + self.text_svg = Some(text_svg); + self + } + pub fn set_font(mut self, font: impl AsRef) -> Self { self.font = font.as_ref().into(); self @@ -284,6 +289,10 @@ impl Slide { self.text.clone() } + pub fn text_alignment(&self) -> TextAlignment { + self.text_alignment.clone() + } + pub fn font_size(&self) -> i32 { self.font_size } @@ -623,45 +632,19 @@ impl SlideBuilder { let Some(video_end_time) = self.video_end_time else { return Err(miette!("No video_end_time")); }; - if let Some(text_svg) = self.text_svg { - Ok(Slide { - background, - text, - font, - font_size, - text_alignment, - audio: self.audio, - video_loop, - video_start_time, - video_end_time, - text_svg, - ..Default::default() - }) - } else { - let text_svg = TextSvg::new(text.clone()) - .alignment(text_alignment) - .fill("#fff") - .shadow(text_svg::shadow(2, 2, 5, "#000000")) - .stroke(text_svg::stroke(3, "#000")) - .font( - text_svg::Font::from(font.clone()) - .size(font_size.try_into().unwrap()), - ) - .build(); - Ok(Slide { - background, - text, - font, - font_size, - text_alignment, - audio: self.audio, - video_loop, - video_start_time, - video_end_time, - text_svg, - ..Default::default() - }) - } + Ok(Slide { + background, + text, + font, + font_size, + text_alignment, + audio: self.audio, + video_loop, + video_start_time, + video_end_time, + text_svg: self.text_svg, + ..Default::default() + }) } } diff --git a/src/main.rs b/src/main.rs index 1a9eb64..202205c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,8 +28,10 @@ use crisp::types::Value; use lisp::parse_lisp; use miette::{miette, Result}; use rayon::prelude::*; +use resvg::usvg::fontdb; use std::fs::read_to_string; use std::path::PathBuf; +use std::sync::Arc; use tracing::{debug, level_filters::LevelFilter}; use tracing::{error, warn}; use tracing_subscriber::EnvFilter; @@ -38,6 +40,8 @@ use ui::presenter::{self, Presenter}; use ui::song_editor::{self, SongEditor}; use ui::EditorMode; +use crate::ui::text_svg; + pub mod core; pub mod lisp; pub mod ui; @@ -111,6 +115,7 @@ struct App { song_editor: SongEditor, searching: bool, library_dragged_item: Option, + fontdb: Arc, } #[derive(Debug, Clone)] @@ -159,15 +164,19 @@ impl cosmic::Application for App { debug!("init"); let nav_model = nav_bar::Model::default(); + let mut fontdb = fontdb::Database::new(); + fontdb.load_system_fonts(); + let fontdb = Arc::new(fontdb); + let mut windows = vec![]; if input.ui { windows.push(core.main_window_id().unwrap()); } - let items = match read_to_string(input.file) { + let mut items = match read_to_string(input.file) { Ok(lisp) => { - let mut slide_vector = vec![]; + let mut service_items = vec![]; let lisp = crisp::reader::read(&lisp); match lisp { Value::List(vec) => { @@ -178,12 +187,12 @@ impl cosmic::Application for App { // slide_vector.append(items); for value in vec { let mut inner_vector = parse_lisp(value); - slide_vector.append(&mut inner_vector); + service_items.append(&mut inner_vector); } } _ => todo!(), } - slide_vector + service_items } Err(e) => { warn!("Missing file or could not read: {e}"); @@ -191,15 +200,32 @@ impl cosmic::Application for App { } }; + let items: Vec = items + .into_par_iter() + .map(|mut item| { + item.slides = item + .slides + .into_par_iter() + .map(|mut slide| { + text_svg::text_svg_generator( + &mut slide, + Arc::clone(&fontdb), + ); + slide + }) + .collect(); + item + }) + .collect(); + let presenter = Presenter::with_items(items.clone()); - let song_editor = SongEditor::new(); + let song_editor = SongEditor::new(Arc::clone(&fontdb)); // for item in items.iter() { // nav_model.insert().text(item.title()).data(item.clone()); // } // nav_model.activate_position(0); - let mut app = App { presenter, core, @@ -217,6 +243,7 @@ impl cosmic::Application for App { searching: false, current_item: (0, 0), library_dragged_item: None, + fontdb, }; let mut batch = vec![]; @@ -1289,11 +1316,10 @@ where vec!["application/service-item".into()] ) .data_received_for::(|item| { - if let Some(item) = item { - Message::AppendServiceItem(item) - } else { - Message::None - } + item.map_or_else( + || Message::None, + |item| Message::AppendServiceItem(item), + ) }) .on_finish( move |mime, data, action, x, y| { diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index 779e964..565cead 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -1,4 +1,5 @@ use miette::{IntoDiagnostic, Result}; +use resvg::usvg::fontdb; use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc}; use cosmic::{ @@ -30,7 +31,7 @@ use url::Url; use crate::{ core::{service_items::ServiceItem, slide::Slide}, - // ui::widgets::slide_text, + ui::text_svg, BackgroundKind, }; @@ -148,6 +149,7 @@ impl Presenter { }; let total_slides: usize = items.iter().fold(0, |a, item| a + item.slides.len()); + Self { current_slide: items[0].slides[0].clone(), current_item: 0, @@ -741,7 +743,12 @@ pub(crate) fn slide_view( .align_x(Horizontal::Left) } else { // SVG based - let text = slide.text_svg.view().map(|m| Message::None); + let text: Element = + if let Some(text) = &slide.text_svg { + text.view().map(|_| Message::None).into() + } else { + Space::with_width(0).into() + }; Container::new(text) .center(Length::Fill) .align_x(Horizontal::Left) diff --git a/src/ui/song_editor.rs b/src/ui/song_editor.rs index 89ebf89..943379c 100644 --- a/src/ui/song_editor.rs +++ b/src/ui/song_editor.rs @@ -1,4 +1,4 @@ -use std::{io, path::PathBuf}; +use std::{io, path::PathBuf, sync::Arc}; use cosmic::{ dialog::file_chooser::open::Dialog, @@ -31,7 +31,7 @@ use super::presenter::slide_view; pub struct SongEditor { pub song: Option, title: String, - font_db: fontdb::Database, + font_db: Arc, fonts: Vec<(fontdb::ID, String)>, fonts_combo: combo_box::State, font_sizes: combo_box::State, @@ -72,11 +72,9 @@ pub enum Message { } impl SongEditor { - pub fn new() -> Self { + pub fn new(font_db: Arc) -> Self { let fonts = font_dir(); debug!(?fonts); - let mut font_db = fontdb::Database::new(); - font_db.load_system_fonts(); let mut fonts: Vec<(fontdb::ID, String)> = font_db .faces() .map(|f| { @@ -447,7 +445,9 @@ order", impl Default for SongEditor { fn default() -> Self { - Self::new() + let mut fontdb = fontdb::Database::new(); + fontdb.load_system_fonts(); + Self::new(Arc::new(fontdb)) } } diff --git a/src/ui/text_svg.rs b/src/ui/text_svg.rs index b04ec4c..e8e185a 100644 --- a/src/ui/text_svg.rs +++ b/src/ui/text_svg.rs @@ -16,7 +16,7 @@ use cosmic::{ }; use resvg::{ tiny_skia::{self, Pixmap}, - usvg::Tree, + usvg::{fontdb, Tree}, }; use tracing::{debug, error}; @@ -230,6 +230,11 @@ impl TextSvg { self } + pub fn fontdb(mut self, fontdb: Arc) -> Self { + self.fontdb = fontdb; + self + } + pub fn alignment(mut self, alignment: TextAlignment) -> Self { self.alignment = alignment; self @@ -285,7 +290,7 @@ impl TextSvg { self.font.name, self.font.size, self.fill, stroke, text); - debug!(?final_svg); + // debug!(?final_svg); let resvg_tree = Tree::from_str( &final_svg, &resvg::usvg::Options { @@ -301,21 +306,17 @@ impl TextSvg { .expect("opops"); resvg::render(&resvg_tree, transform, &mut pixmap.as_mut()); // debug!(?pixmap); - let handle = Handle::from_bytes(pixmap.data().to_owned()); + let handle = Handle::from_bytes(pixmap.take()); self.handle = Some(handle); self } pub fn view<'a>(&self) -> Element<'a, Message> { - container( - Image::new(self.handle.clone().unwrap()) - .content_fit(ContentFit::Contain) - .width(Length::Fill) - .height(Length::Fill), - ) - .width(Length::Fill) - .height(Length::Fill) - .into() + Image::new(self.handle.clone().unwrap()) + .content_fit(ContentFit::Contain) + .width(Length::Fill) + .height(Length::Fill) + .into() } fn text_spans(&self) -> Vec { @@ -352,6 +353,26 @@ pub fn color(color: impl AsRef) -> Color { Color::from_hex_str(color) } +pub fn text_svg_generator( + slide: &mut crate::core::slide::Slide, + fontdb: Arc, +) { + if slide.text().len() > 0 { + let text_svg = TextSvg::new(slide.text()) + .alignment(slide.text_alignment()) + .fill("#fff") + .shadow(shadow(2, 2, 5, "#000000")) + .stroke(stroke(3, "#000")) + .font( + Font::from(slide.font().clone()) + .size(slide.font_size().try_into().unwrap()), + ) + .fontdb(Arc::clone(&fontdb)) + .build(); + slide.text_svg = Some(text_svg); + } +} + #[cfg(test)] mod test { use pretty_assertions::assert_eq;