diff --git a/Cargo.lock b/Cargo.lock index 9de55f7..7de30a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1276,7 +1276,7 @@ dependencies = [ "log", "rangemap", "rustc-hash 1.1.0", - "rustybuzz", + "rustybuzz 0.14.1", "self_cell", "smol_str", "swash", @@ -2943,7 +2943,7 @@ dependencies = [ "iced_graphics", "kurbo 0.10.4", "log", - "resvg", + "resvg 0.42.0", "rustc-hash 2.1.1", "softbuffer", "tiny-skia", @@ -2984,7 +2984,7 @@ dependencies = [ "lyon", "once_cell", "raw-window-handle", - "resvg", + "resvg 0.42.0", "rustc-hash 2.1.1", "rustix 0.38.44", "thiserror 1.0.69", @@ -3166,12 +3166,28 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + [[package]] name = "imagesize" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + [[package]] name = "immutable-chunkmap" version = "2.0.6" @@ -3643,6 +3659,7 @@ dependencies = [ "miette", "pretty_assertions", "rayon", + "resvg 0.45.1", "rodio", "ron 0.8.1", "serde", @@ -4927,6 +4944,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" @@ -5200,7 +5223,24 @@ dependencies = [ "rgb", "svgtypes", "tiny-skia", - "usvg", + "usvg 0.42.0", +] + +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "gif", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg 0.45.1", + "zune-jpeg", ] [[package]] @@ -5376,8 +5416,26 @@ dependencies = [ "libm", "smallvec", "ttf-parser 0.21.1", - "unicode-bidi-mirroring", - "unicode-ccc", + "unicode-bidi-mirroring 0.2.0", + "unicode-ccc 0.2.0", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.9.2", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser 0.25.1", + "unicode-bidi-mirroring 0.4.0", + "unicode-ccc 0.4.0", "unicode-properties", "unicode-script", ] @@ -6729,12 +6787,24 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + [[package]] name = "unicode-ccc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -6826,12 +6896,39 @@ dependencies = [ "data-url", "flate2", "fontdb 0.18.0", - "imagesize", + "imagesize 0.12.0", "kurbo 0.11.3", "log", "pico-args", "roxmltree", - "rustybuzz", + "rustybuzz 0.14.1", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb 0.23.0", + "imagesize 0.13.0", + "kurbo 0.11.3", + "log", + "pico-args", + "roxmltree", + "rustybuzz 0.20.1", "simplecss", "siphasher", "strict-num", diff --git a/Cargo.toml b/Cargo.toml index fd26207..acda2ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ gstreamer-app = "0.23" url = "2" colors-transform = "0.2.11" rayon = "1.11.0" -# resvg = "0.45.1" +resvg = "0.45.1" # femtovg = { version = "0.16.0", features = ["wgpu"] } # wgpu = "26.0.1" # mupdf = "0.5.0" diff --git a/src/core/service_items.rs b/src/core/service_items.rs index 19c2301..2ee9893 100644 --- a/src/core/service_items.rs +++ b/src/core/service_items.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; use crisp::types::{Keyword, Symbol, Value}; use miette::Result; +use resvg::usvg::fontdb; use tracing::{debug, error}; use crate::Slide; @@ -17,7 +18,7 @@ use super::videos::Video; use super::kinds::ServiceItemKind; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ServiceItem { pub id: i32, pub title: String, diff --git a/src/core/slide.rs b/src/core/slide.rs index a6cb45a..f54df80 100644 --- a/src/core/slide.rs +++ b/src/core/slide.rs @@ -2,10 +2,12 @@ use crisp::types::{Keyword, Symbol, Value}; use iced_video_player::Video; use miette::{miette, Result}; +use resvg::usvg::fontdb; use serde::{Deserialize, Serialize}; use std::{ fmt::Display, path::{Path, PathBuf}, + sync::Arc, }; use tracing::error; @@ -13,6 +15,40 @@ use crate::ui::text_svg::{self, TextSvg}; use super::songs::Song; +#[derive( + Clone, Debug, Default, PartialEq, Serialize, Deserialize, +)] +pub struct Slide { + id: i32, + background: Background, + text: String, + font: String, + font_size: i32, + text_alignment: TextAlignment, + audio: Option, + video_loop: bool, + video_start_time: f32, + video_end_time: f32, + #[serde(skip)] + pub text_svg: TextSvg, +} + +#[derive( + Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, +)] +pub enum BackgroundKind { + #[default] + Image, + Video, +} + +#[derive(Debug, Clone, Default)] +struct Image { + pub source: String, + pub fit: String, + pub children: Vec, +} + #[derive( Clone, Copy, @@ -203,15 +239,6 @@ impl Display for ParseError { } } -#[derive( - Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, -)] -pub enum BackgroundKind { - #[default] - Image, - Video, -} - impl From for BackgroundKind { fn from(value: String) -> Self { if value == "image" { @@ -222,24 +249,6 @@ impl From for BackgroundKind { } } -#[derive( - Clone, Debug, Default, PartialEq, Serialize, Deserialize, -)] -pub struct Slide { - id: i32, - background: Background, - text: String, - font: String, - font_size: i32, - text_alignment: TextAlignment, - audio: Option, - video_loop: bool, - video_start_time: f32, - video_end_time: f32, - #[serde(skip)] - pub text_svg: TextSvg, -} - impl From<&Slide> for Value { fn from(value: &Slide) -> Self { Self::List(vec![Self::Symbol(Symbol("slide".into()))]) @@ -656,13 +665,6 @@ impl SlideBuilder { } } -#[derive(Debug, Clone, Default)] -struct Image { - pub source: String, - pub fit: String, - pub children: Vec, -} - impl Image { fn new() -> Self { Self { diff --git a/src/ui/text_svg.rs b/src/ui/text_svg.rs index cbfd280..b04ec4c 100644 --- a/src/ui/text_svg.rs +++ b/src/ui/text_svg.rs @@ -1,22 +1,28 @@ use std::{ fmt::Display, hash::{Hash, Hasher}, + io::Read, + sync::Arc, }; use colors_transform::Rgb; use cosmic::{ iced::{ font::{Style, Weight}, - Length, Size, + ContentFit, Length, Size, }, prelude::*, - widget::{container, svg::Handle, Svg}, + widget::{container, image::Handle, Image}, }; -use tracing::error; +use resvg::{ + tiny_skia::{self, Pixmap}, + usvg::Tree, +}; +use tracing::{debug, error}; use crate::TextAlignment; -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default)] pub struct TextSvg { text: String, font: Font, @@ -25,6 +31,19 @@ pub struct TextSvg { fill: Color, alignment: TextAlignment, handle: Option, + fontdb: Arc, +} + +impl PartialEq for TextSvg { + fn eq(&self, other: &Self) -> bool { + self.text == other.text + && self.font == other.font + && self.shadow == other.shadow + && self.stroke == other.stroke + && self.fill == other.fill + && self.alignment == other.alignment + && self.handle == other.handle + } } impl Hash for TextSvg { @@ -46,6 +65,27 @@ pub struct Font { size: u8, } +#[derive(Clone, Debug, Default, PartialEq, Hash)] +pub struct Shadow { + pub offset_x: i16, + pub offset_y: i16, + pub spread: u16, + pub color: Color, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash)] +pub struct Stroke { + size: u16, + color: Color, +} + +pub enum Message { + None, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Color(Rgb); + impl From for Font { fn from(value: cosmic::font::Font) -> Self { Self { @@ -113,9 +153,6 @@ impl Font { } } -#[derive(Clone, Debug, PartialEq)] -pub struct Color(Rgb); - impl Hash for Color { fn hash(&self, state: &mut H) { self.0.to_css_hex_string().hash(state); @@ -158,24 +195,6 @@ impl Display for Color { } } -#[derive(Clone, Debug, Default, PartialEq, Hash)] -pub struct Shadow { - pub offset_x: i16, - pub offset_y: i16, - pub spread: u16, - pub color: Color, -} - -#[derive(Clone, Debug, Default, PartialEq, Hash)] -pub struct Stroke { - size: u16, - color: Color, -} - -pub enum Message { - None, -} - impl TextSvg { pub fn new(text: impl Into) -> Self { Self { @@ -234,7 +253,7 @@ impl TextSvg { } else { "".into() }; - let size = Size::new(640.0, 360.0); + let size = Size::new(1920.0, 1080.0); let total_lines = self.text.lines().count(); let half_lines = (total_lines / 2) as f32; let middle_position = size.height / 2.0; @@ -266,20 +285,31 @@ impl TextSvg { self.font.name, self.font.size, self.fill, stroke, text); - let handle = Handle::from_memory( - Box::leak( - ::clone(&final_svg) - .into_boxed_str(), - ) - .as_bytes(), - ); + debug!(?final_svg); + let resvg_tree = Tree::from_str( + &final_svg, + &resvg::usvg::Options { + fontdb: Arc::clone(&self.fontdb), + ..Default::default() + }, + ) + .expect("Woops mama"); + // debug!(?resvg_tree); + let transform = tiny_skia::Transform::default(); + let mut pixmap = + Pixmap::new(size.width as u32, size.height as u32) + .expect("opops"); + resvg::render(&resvg_tree, transform, &mut pixmap.as_mut()); + // debug!(?pixmap); + let handle = Handle::from_bytes(pixmap.data().to_owned()); self.handle = Some(handle); self } pub fn view<'a>(&self) -> Element<'a, Message> { container( - Svg::new(self.handle.clone().unwrap()) + Image::new(self.handle.clone().unwrap()) + .content_fit(ContentFit::Contain) .width(Length::Fill) .height(Length::Fill), )