closer to using these text_svgs
Some checks failed
/ test (push) Has been cancelled

This commit is contained in:
Chris Cochrun 2025-08-29 16:41:24 -05:00
parent 1446e35c58
commit 4ccb186189
6 changed files with 112 additions and 75 deletions

View file

@ -1,7 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::{Arc, Mutex};
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
use crisp::types::{Keyword, Symbol, Value}; use crisp::types::{Keyword, Symbol, Value};
@ -24,7 +24,7 @@ pub struct ServiceItem {
pub title: String, pub title: String,
pub database_id: i32, pub database_id: i32,
pub kind: ServiceItemKind, pub kind: ServiceItemKind,
pub slides: Arc<[Slide]>, pub slides: Vec<Slide>,
// pub item: Box<dyn ServiceTrait>, // pub item: Box<dyn ServiceTrait>,
} }
@ -122,7 +122,7 @@ impl Default for ServiceItem {
title: String::default(), title: String::default(),
database_id: 0, database_id: 0,
kind: ServiceItemKind::Content(Slide::default()), kind: ServiceItemKind::Content(Slide::default()),
slides: Arc::new([]), slides: vec![],
// item: Box::new(Image::default()), // item: Box::new(Image::default()),
} }
} }
@ -172,7 +172,7 @@ impl From<&Value> for ServiceItem {
kind: ServiceItemKind::Content( kind: ServiceItemKind::Content(
slide.clone(), slide.clone(),
), ),
slides: Arc::new([slide]), slides: vec![slide],
} }
} else if let Some(background) = } else if let Some(background) =
list.get(background_pos) list.get(background_pos)

View file

@ -30,7 +30,7 @@ pub struct Slide {
video_start_time: f32, video_start_time: f32,
video_end_time: f32, video_end_time: f32,
#[serde(skip)] #[serde(skip)]
pub text_svg: TextSvg, pub text_svg: Option<TextSvg>,
} }
#[derive( #[derive(
@ -261,6 +261,11 @@ impl Slide {
self 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<str>) -> Self { pub fn set_font(mut self, font: impl AsRef<str>) -> Self {
self.font = font.as_ref().into(); self.font = font.as_ref().into();
self self
@ -284,6 +289,10 @@ impl Slide {
self.text.clone() self.text.clone()
} }
pub fn text_alignment(&self) -> TextAlignment {
self.text_alignment.clone()
}
pub fn font_size(&self) -> i32 { pub fn font_size(&self) -> i32 {
self.font_size self.font_size
} }
@ -623,7 +632,6 @@ impl SlideBuilder {
let Some(video_end_time) = self.video_end_time else { let Some(video_end_time) = self.video_end_time else {
return Err(miette!("No video_end_time")); return Err(miette!("No video_end_time"));
}; };
if let Some(text_svg) = self.text_svg {
Ok(Slide { Ok(Slide {
background, background,
text, text,
@ -634,34 +642,9 @@ impl SlideBuilder {
video_loop, video_loop,
video_start_time, video_start_time,
video_end_time, video_end_time,
text_svg, text_svg: self.text_svg,
..Default::default() ..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()
})
}
} }
} }

View file

@ -28,8 +28,10 @@ use crisp::types::Value;
use lisp::parse_lisp; use lisp::parse_lisp;
use miette::{miette, Result}; use miette::{miette, Result};
use rayon::prelude::*; use rayon::prelude::*;
use resvg::usvg::fontdb;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
use tracing::{debug, level_filters::LevelFilter}; use tracing::{debug, level_filters::LevelFilter};
use tracing::{error, warn}; use tracing::{error, warn};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
@ -38,6 +40,8 @@ use ui::presenter::{self, Presenter};
use ui::song_editor::{self, SongEditor}; use ui::song_editor::{self, SongEditor};
use ui::EditorMode; use ui::EditorMode;
use crate::ui::text_svg;
pub mod core; pub mod core;
pub mod lisp; pub mod lisp;
pub mod ui; pub mod ui;
@ -111,6 +115,7 @@ struct App {
song_editor: SongEditor, song_editor: SongEditor,
searching: bool, searching: bool,
library_dragged_item: Option<ServiceItem>, library_dragged_item: Option<ServiceItem>,
fontdb: Arc<fontdb::Database>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -159,15 +164,19 @@ impl cosmic::Application for App {
debug!("init"); debug!("init");
let nav_model = nav_bar::Model::default(); 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![]; let mut windows = vec![];
if input.ui { if input.ui {
windows.push(core.main_window_id().unwrap()); 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) => { Ok(lisp) => {
let mut slide_vector = vec![]; let mut service_items = vec![];
let lisp = crisp::reader::read(&lisp); let lisp = crisp::reader::read(&lisp);
match lisp { match lisp {
Value::List(vec) => { Value::List(vec) => {
@ -178,12 +187,12 @@ impl cosmic::Application for App {
// slide_vector.append(items); // slide_vector.append(items);
for value in vec { for value in vec {
let mut inner_vector = parse_lisp(value); let mut inner_vector = parse_lisp(value);
slide_vector.append(&mut inner_vector); service_items.append(&mut inner_vector);
} }
} }
_ => todo!(), _ => todo!(),
} }
slide_vector service_items
} }
Err(e) => { Err(e) => {
warn!("Missing file or could not read: {e}"); warn!("Missing file or could not read: {e}");
@ -191,15 +200,32 @@ impl cosmic::Application for App {
} }
}; };
let items: Vec<ServiceItem> = 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 presenter = Presenter::with_items(items.clone());
let song_editor = SongEditor::new(); let song_editor = SongEditor::new(Arc::clone(&fontdb));
// for item in items.iter() { // for item in items.iter() {
// nav_model.insert().text(item.title()).data(item.clone()); // nav_model.insert().text(item.title()).data(item.clone());
// } // }
// nav_model.activate_position(0); // nav_model.activate_position(0);
let mut app = App { let mut app = App {
presenter, presenter,
core, core,
@ -217,6 +243,7 @@ impl cosmic::Application for App {
searching: false, searching: false,
current_item: (0, 0), current_item: (0, 0),
library_dragged_item: None, library_dragged_item: None,
fontdb,
}; };
let mut batch = vec![]; let mut batch = vec![];
@ -1289,11 +1316,10 @@ where
vec!["application/service-item".into()] vec!["application/service-item".into()]
) )
.data_received_for::<ServiceItem>(|item| { .data_received_for::<ServiceItem>(|item| {
if let Some(item) = item { item.map_or_else(
Message::AppendServiceItem(item) || Message::None,
} else { |item| Message::AppendServiceItem(item),
Message::None )
}
}) })
.on_finish( .on_finish(
move |mime, data, action, x, y| { move |mime, data, action, x, y| {

View file

@ -1,4 +1,5 @@
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use resvg::usvg::fontdb;
use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc}; use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc};
use cosmic::{ use cosmic::{
@ -30,7 +31,7 @@ use url::Url;
use crate::{ use crate::{
core::{service_items::ServiceItem, slide::Slide}, core::{service_items::ServiceItem, slide::Slide},
// ui::widgets::slide_text, ui::text_svg,
BackgroundKind, BackgroundKind,
}; };
@ -148,6 +149,7 @@ impl Presenter {
}; };
let total_slides: usize = let total_slides: usize =
items.iter().fold(0, |a, item| a + item.slides.len()); items.iter().fold(0, |a, item| a + item.slides.len());
Self { Self {
current_slide: items[0].slides[0].clone(), current_slide: items[0].slides[0].clone(),
current_item: 0, current_item: 0,
@ -741,7 +743,12 @@ pub(crate) fn slide_view(
.align_x(Horizontal::Left) .align_x(Horizontal::Left)
} else { } else {
// SVG based // SVG based
let text = slide.text_svg.view().map(|m| Message::None); let text: Element<Message> =
if let Some(text) = &slide.text_svg {
text.view().map(|_| Message::None).into()
} else {
Space::with_width(0).into()
};
Container::new(text) Container::new(text)
.center(Length::Fill) .center(Length::Fill)
.align_x(Horizontal::Left) .align_x(Horizontal::Left)

View file

@ -1,4 +1,4 @@
use std::{io, path::PathBuf}; use std::{io, path::PathBuf, sync::Arc};
use cosmic::{ use cosmic::{
dialog::file_chooser::open::Dialog, dialog::file_chooser::open::Dialog,
@ -31,7 +31,7 @@ use super::presenter::slide_view;
pub struct SongEditor { pub struct SongEditor {
pub song: Option<Song>, pub song: Option<Song>,
title: String, title: String,
font_db: fontdb::Database, font_db: Arc<fontdb::Database>,
fonts: Vec<(fontdb::ID, String)>, fonts: Vec<(fontdb::ID, String)>,
fonts_combo: combo_box::State<String>, fonts_combo: combo_box::State<String>,
font_sizes: combo_box::State<String>, font_sizes: combo_box::State<String>,
@ -72,11 +72,9 @@ pub enum Message {
} }
impl SongEditor { impl SongEditor {
pub fn new() -> Self { pub fn new(font_db: Arc<fontdb::Database>) -> Self {
let fonts = font_dir(); let fonts = font_dir();
debug!(?fonts); debug!(?fonts);
let mut font_db = fontdb::Database::new();
font_db.load_system_fonts();
let mut fonts: Vec<(fontdb::ID, String)> = font_db let mut fonts: Vec<(fontdb::ID, String)> = font_db
.faces() .faces()
.map(|f| { .map(|f| {
@ -447,7 +445,9 @@ order",
impl Default for SongEditor { impl Default for SongEditor {
fn default() -> Self { fn default() -> Self {
Self::new() let mut fontdb = fontdb::Database::new();
fontdb.load_system_fonts();
Self::new(Arc::new(fontdb))
} }
} }

View file

@ -16,7 +16,7 @@ use cosmic::{
}; };
use resvg::{ use resvg::{
tiny_skia::{self, Pixmap}, tiny_skia::{self, Pixmap},
usvg::Tree, usvg::{fontdb, Tree},
}; };
use tracing::{debug, error}; use tracing::{debug, error};
@ -230,6 +230,11 @@ impl TextSvg {
self self
} }
pub fn fontdb(mut self, fontdb: Arc<fontdb::Database>) -> Self {
self.fontdb = fontdb;
self
}
pub fn alignment(mut self, alignment: TextAlignment) -> Self { pub fn alignment(mut self, alignment: TextAlignment) -> Self {
self.alignment = alignment; self.alignment = alignment;
self self
@ -285,7 +290,7 @@ impl TextSvg {
self.font.name, self.font.name,
self.font.size, self.font.size,
self.fill, stroke, text); self.fill, stroke, text);
debug!(?final_svg); // debug!(?final_svg);
let resvg_tree = Tree::from_str( let resvg_tree = Tree::from_str(
&final_svg, &final_svg,
&resvg::usvg::Options { &resvg::usvg::Options {
@ -301,19 +306,15 @@ impl TextSvg {
.expect("opops"); .expect("opops");
resvg::render(&resvg_tree, transform, &mut pixmap.as_mut()); resvg::render(&resvg_tree, transform, &mut pixmap.as_mut());
// debug!(?pixmap); // debug!(?pixmap);
let handle = Handle::from_bytes(pixmap.data().to_owned()); let handle = Handle::from_bytes(pixmap.take());
self.handle = Some(handle); self.handle = Some(handle);
self self
} }
pub fn view<'a>(&self) -> Element<'a, Message> { pub fn view<'a>(&self) -> Element<'a, Message> {
container(
Image::new(self.handle.clone().unwrap()) Image::new(self.handle.clone().unwrap())
.content_fit(ContentFit::Contain) .content_fit(ContentFit::Contain)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill),
)
.width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.into() .into()
} }
@ -352,6 +353,26 @@ pub fn color(color: impl AsRef<str>) -> Color {
Color::from_hex_str(color) Color::from_hex_str(color)
} }
pub fn text_svg_generator(
slide: &mut crate::core::slide::Slide,
fontdb: Arc<fontdb::Database>,
) {
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)] #[cfg(test)]
mod test { mod test {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;