the beginnings of the text_svg

This commit is contained in:
Chris Cochrun 2025-03-27 15:20:32 -05:00
parent 46cdf53c6e
commit ed80ccaa55
4 changed files with 177 additions and 24 deletions

7
Cargo.lock generated
View file

@ -1156,6 +1156,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "colors-transform"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9226dbc05df4fb986f48d730b001532580883c4c06c5d1c213f4b34c1c157178"
[[package]] [[package]]
name = "com" name = "com"
version = "0.6.0" version = "0.6.0"
@ -4165,6 +4171,7 @@ name = "lumina"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"colors-transform",
"cosmic-time", "cosmic-time",
"crisp", "crisp",
"dirs", "dirs",

View file

@ -28,6 +28,7 @@ gstreamer = "0.23.3"
gstreamer-app = "0.23.3" gstreamer-app = "0.23.3"
cosmic-time = "0.2.0" cosmic-time = "0.2.0"
url = "2" url = "2"
colors-transform = "0.2.11"
# rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false } # rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false }
[dependencies.iced_video_player] [dependencies.iced_video_player]

View file

@ -305,15 +305,19 @@ impl SongEditor {
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(index, slide)| { .map(|(index, slide)| {
let svg = Handle::from_memory(r#"<svg viewBox="0 0 240 100" xmlns="http://www.w3.org/2000/svg"> let svg = Handle::from_memory(r#"<svg viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<filter id="shadow2"> <filter id="shadow">
<feDropShadow dx="0" dy="0" stdDeviation="0" flood-color="cyan" /> <feDropShadow dx="10" dy="10" stdDeviation="5" flood-color='#000' />
</filter> </filter>
</defs> </defs>
<text x="0" y="50" font-weight="bold" font-family="Quicksand" font-size="40" fill="white" stroke="black" stroke-width="2" style="filter:url(#shadow2);"> <text x="0" y="300" font-weight="bold" font-family="Quicksand" font-size="80" fill="white" stroke="black" stroke-width="2" style="filter:url(#shadow);">
Hello World Hello World
</text></svg>"#.as_bytes()); </text>
<text x="0" y="350" font-weight="bold" font-family="Quicksand" font-size="40" fill="white" stroke="black" stroke-width="2" style="filter:url(#shadow);">
Hello World
</text>
</svg>"#.as_bytes());
stack!( stack!(
container( container(
slide_view( slide_view(

View file

@ -1,38 +1,125 @@
use cosmic::iced::Font as IcedFont; use std::fmt::Display;
#[derive(Clone, Debug, Default, PartialEq, Eq)] use colors_transform::Rgb;
use cosmic::{
iced::font::{Style, Weight},
prelude::*,
widget::{svg::Handle, Svg},
};
use tracing::error;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TextSvg { pub struct TextSvg {
text: String, text: String,
font: Font, font: Font,
shadow: Shadow, shadow: Option<Shadow>,
stroke: Stroke, stroke: Option<Stroke>,
fill: Color, fill: Color,
} }
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Font(IcedFont); pub struct Font {
name: String,
#[derive(Clone, Debug, Default, PartialEq, Eq)] weight: Weight,
pub struct Color { style: Style,
red: u8, size: u8,
green: u8,
blue: u8,
} }
#[derive(Clone, Debug, Default, PartialEq, Eq)] impl From<&str> for Font {
fn from(value: &str) -> Self {
Self {
name: value.to_owned(),
..Default::default()
}
}
}
impl Font {
fn get_name(&self) -> String {
self.name.clone()
}
fn get_weight(&self) -> Weight {
self.weight.clone()
}
fn get_style(&self) -> Style {
self.style.clone()
}
fn weight(mut self, weight: impl Into<Weight>) -> Self {
self.weight = weight.into();
self
}
fn style(mut self, style: impl Into<Style>) -> Self {
self.style = style.into();
self
}
fn name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Color(Rgb);
impl Color {
pub fn from_hex_str(color: impl AsRef<str>) -> Color {
match Rgb::from_hex_str(color.as_ref()) {
Ok(rgb) => Color(rgb),
Err(e) => {
error!("error in making color from hex_str: {:?}", e);
Color::default()
}
}
}
}
impl From<&str> for Color {
fn from(value: &str) -> Self {
Self::from_hex_str(value)
}
}
impl Default for Color {
fn default() -> Self {
Self(
Rgb::from_hex_str("#000")
.expect("This is not a hex color"),
)
}
}
impl Display for Color {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
write!(f, "{}", self.0.to_css_hex_string())
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Shadow { pub struct Shadow {
offset_x: i16, pub offset_x: i16,
offset_y: i16, pub offset_y: i16,
spread: u16, pub spread: u16,
color: Color, pub color: Color,
} }
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq)]
pub struct Stroke { pub struct Stroke {
size: u16, size: u16,
color: Color, color: Color,
} }
pub enum Message {
None,
}
impl TextSvg { impl TextSvg {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -46,12 +133,12 @@ impl TextSvg {
} }
pub fn shadow(mut self, shadow: impl Into<Shadow>) -> Self { pub fn shadow(mut self, shadow: impl Into<Shadow>) -> Self {
self.shadow = shadow.into(); self.shadow = Some(shadow.into());
self self
} }
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self { pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
self.stroke = stroke.into(); self.stroke = Some(stroke.into());
self self
} }
@ -59,4 +146,58 @@ impl TextSvg {
self.font = font.into(); self.font = font.into();
self self
} }
pub fn view(&self) -> Element<Message> {
let shadow = if let Some(shadow) = &self.shadow {
format!("<filter id=\"shadow\"><feDropShadow dx=\"{}\" dy=\"{}\" stdDeviation=\"{}\" flood-color=\"{}\"/></filter>",
shadow.offset_x,
shadow.offset_y,
shadow.spread,
shadow.color)
} else {
"".into()
};
let stroke = if let Some(stroke) = &self.stroke {
format!(
"stroke=\"{}\" stroke-width=\"{}\"",
stroke.color, stroke.size
)
} else {
"".into()
};
// text y coords are based on bottom left corner so we need to take height into consideration
let base = format!("<svg viewBox=\"0 0 240 100\" xmlns=\"http://www.w3.org/2000/svg\"><defs>{}</defs>
<text x=\"0\" y=\"50\" font-weight=\"bold\" font-family=\"{}\" font-size=\"{}\" fill=\"{}\" {} style=\"filter:url(#shadow);\">
{}
</text></svg>", shadow, self.font.name, self.font.size, self.fill, stroke, self.text);
Svg::new(Handle::from_memory(
Box::leak(base.into_boxed_str()).as_bytes(),
))
.into()
}
}
pub fn shadow(
offset_x: i16,
offset_y: i16,
spread: u16,
color: impl Into<Color>,
) -> Shadow {
Shadow {
offset_x,
offset_y,
spread,
color: color.into(),
}
}
pub fn stroke(size: u16, color: impl Into<Color>) -> Stroke {
Stroke {
size,
color: color.into(),
}
}
pub fn color(color: impl AsRef<str>) -> Color {
Color::from_hex_str(color)
} }