From 3297a2f7badc034101ef7ffe2cecb778df3e4714 Mon Sep 17 00:00:00 2001 From: Sergio Ribera <56278796+SergioRibera@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:48:12 -0400 Subject: [PATCH] refactor: configuration and args structs --- Cargo.lock | 2 + crates/sss_cli/src/config.rs | 161 +++---------------------- crates/sss_cli/src/main.rs | 28 +---- crates/sss_code/src/config.rs | 158 +++--------------------- crates/sss_code/src/img.rs | 10 +- crates/sss_code/src/main.rs | 36 ++---- crates/sss_lib/Cargo.toml | 4 +- crates/sss_lib/src/args.rs | 219 ++++++++++++++++++++++++++++++++++ crates/sss_lib/src/img.rs | 53 ++++---- crates/sss_lib/src/lib.rs | 78 ++++++------ crates/sss_lib/src/shadow.rs | 2 +- 11 files changed, 347 insertions(+), 404 deletions(-) create mode 100644 crates/sss_lib/src/args.rs diff --git a/Cargo.lock b/Cargo.lock index 2feb9b4..7198677 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1847,12 +1847,14 @@ name = "sss_lib" version = "0.1.4" dependencies = [ "arboard", + "clap", "conv", "env_logger", "font-kit", "image", "imageproc", "log", + "merge2", "pathfinder_geometry", "rayon", "serde", diff --git a/crates/sss_cli/src/config.rs b/crates/sss_cli/src/config.rs index 191c93b..9420247 100644 --- a/crates/sss_cli/src/config.rs +++ b/crates/sss_cli/src/config.rs @@ -1,25 +1,22 @@ use clap::Parser; use merge2::{bool::overwrite_false, Merge}; use serde::{Deserialize, Serialize}; -use sss_lib::font::parse_font_str; -use sss_lib::font::FontCollection; -use sss_lib::{Background, GenerationSettings, Shadow, ToRgba}; +use sss_lib::{default_bool, swap_option}; use crate::{str_to_area, Area}; -const fn default_bool() -> bool { - false -} - -#[inline] -fn swap_option(left: &mut Option, right: &mut Option) { - if left.is_none() || right.is_some() { - core::mem::swap(left, right); - } +#[derive(Clone, Debug, Deserialize, Merge, Parser, Serialize)] +#[clap(version, author)] +struct ClapConfig { + #[clap(flatten)] + pub cli: Option, + // lib configs + #[clap(flatten)] + #[serde(rename = "general")] + pub lib_config: sss_lib::GenerationSettingsArgs, } -#[derive(Clone, Debug, Default, Deserialize, Merge, Parser, Serialize)] -#[clap(version, author)] +#[derive(Clone, Debug, Deserialize, Merge, Parser, Serialize)] pub struct CliConfig { #[clap( long, @@ -42,103 +39,9 @@ pub struct CliConfig { #[clap(long, help = "Captures an area of the screen", value_parser = str_to_area)] #[merge(strategy = swap_option)] pub area: Option, - // Screenshot Section - #[clap( - long, - help = "[default: Hack=12.0;] The font used to render, format: Font Name=size;Other Font Name=12.0", - value_parser = parse_font_str - )] - #[merge(strategy = swap_option)] - pub fonts: Option, - #[clap( - long, - short, - help = "[default: #323232] Support: '#RRGGBBAA' 'h;#RRGGBBAA;#RRGGBBAA' 'v;#RRGGBBAA;#RRGGBBAA' or file path" - )] - #[merge(strategy = swap_option)] - pub background: Option, - #[clap(long, short, help = "[default: 15] ")] - #[merge(strategy = swap_option)] - pub radius: Option, - #[clap(long, help = "Author Name of screenshot")] - #[merge(strategy = swap_option)] - pub author: Option, - #[clap(long, help = "[default: #FFFFFF] Title bar text color")] - #[merge(strategy = swap_option)] - pub author_color: Option, - #[clap(long, help = "[default: Hack] Font to render Author")] - #[merge(strategy = swap_option)] - pub author_font: Option, - // Window Bar - #[clap(long, help = "Whether show the window controls")] - #[merge(strategy = overwrite_false)] - #[serde(default = "default_bool")] - pub window_controls: bool, - #[clap(long, help = "Window title")] - #[merge(strategy = swap_option)] - pub window_title: Option, - #[clap(long, help = "[default: #4287f5] Window bar background")] - #[merge(strategy = swap_option)] - pub window_background: Option, - #[clap(long, help = "[default: #FFFFFF] Title bar text color")] - #[merge(strategy = swap_option)] - pub window_title_color: Option, - #[clap(long, help = "[default 120] Width of window controls")] - #[merge(strategy = swap_option)] - pub window_controls_width: Option, - #[clap(long, help = "[default: 40] Height of window title/controls bar")] - #[merge(strategy = swap_option)] - pub window_controls_height: Option, - #[clap(long, help = "[default: 10] Padding of title on window bar")] - #[merge(strategy = swap_option)] - pub titlebar_padding: Option, - // Padding Section - #[clap(long, help = "[default: 80]")] - #[merge(strategy = swap_option)] - pub padding_x: Option, - #[clap(long, help = "[default: 100]")] - #[merge(strategy = swap_option)] - pub padding_y: Option, - // Shadow Section - #[clap(long, help = "Enable shadow")] - #[merge(strategy = overwrite_false)] - #[serde(default = "default_bool")] - pub shadow: bool, - #[clap(long, help = "Generate shadow from inner image")] - #[merge(strategy = overwrite_false)] - #[serde(default = "default_bool")] - pub shadow_image: bool, - #[clap( - long, - help = "[default: #707070] Support: '#RRGGBBAA' 'h;#RRGGBBAA;#RRGGBBAA' 'v;#RRGGBBAA;#RRGGBBAA' or file path" - )] - #[merge(strategy = swap_option)] - pub shadow_color: Option, - #[clap(long, help = "[default: 50] Shadow blur")] - #[merge(strategy = swap_option)] - pub shadow_blur: Option, - // Saving options - #[clap(long, short, help = "Send the result to your clipboard")] - #[merge(strategy = overwrite_false)] - #[serde(default = "default_bool")] - pub copy: bool, - #[clap( - long, - short, - help = "If it is set then the result will be saved here, otherwise it will not be saved." - )] - #[serde(skip)] - pub output: String, - #[clap( - long, - short = 'f', - help = "[default: png] The format in which the image will be saved" - )] - #[merge(strategy = swap_option)] - pub save_format: Option, } -pub fn get_config() -> CliConfig { +pub fn get_config() -> (CliConfig, sss_lib::GenerationSettings) { let config_path = directories::BaseDirs::new() .unwrap() .config_dir() @@ -151,42 +54,12 @@ pub fn get_config() -> CliConfig { if let Ok(cfg_content) = std::fs::read_to_string(config_path) { println!("Merging from config file"); - let mut config: CliConfig = toml::from_str(&cfg_content).unwrap(); - let mut args = CliConfig::parse(); + let mut config: ClapConfig = toml::from_str(&cfg_content).unwrap(); + let mut args = ClapConfig::parse(); config.merge(&mut args); - return config; - } - CliConfig::parse() -} - -impl From for GenerationSettings { - fn from(val: CliConfig) -> Self { - let background = Background::try_from(val.background.unwrap().clone()).unwrap(); - let windows_background = - Background::try_from(val.window_background.unwrap().clone()).unwrap(); - let shadow_color = Background::try_from(val.shadow_color.unwrap().clone()).unwrap(); - - GenerationSettings { - windows_background, - background, - padding: (val.padding_x.unwrap(), val.padding_y.unwrap()), - round_corner: val.radius, - shadow: val.shadow.then_some(Shadow { - shadow_color, - use_inner_image: val.shadow_image, - blur_radius: val.shadow_blur.unwrap(), - }), - fonts: val.fonts.unwrap_or_default(), - author: val.author.clone(), - author_font: val.author_font.clone().unwrap(), - author_color: val.author_color.unwrap().to_rgba().unwrap(), - window_controls: val.window_controls, - windows_title: val.window_title.clone(), - windows_title_color: val.window_title_color.unwrap().to_rgba().unwrap(), - window_controls_width: val.window_controls_width.unwrap(), - window_controls_height: val.window_controls_height.unwrap(), - titlebar_padding: val.titlebar_padding.unwrap(), - } + return (config.cli.unwrap(), config.lib_config.into()); } + let config = ClapConfig::parse(); + (config.cli.unwrap(), config.lib_config.into()) } diff --git a/crates/sss_cli/src/main.rs b/crates/sss_cli/src/main.rs index 896b10c..cf82182 100644 --- a/crates/sss_cli/src/main.rs +++ b/crates/sss_cli/src/main.rs @@ -1,7 +1,5 @@ use config::get_config; use img::Screenshot; -use screenshots::image::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; -use screenshots::image::{ImageError, ImageFormat}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use sss_lib::generate_image; @@ -18,31 +16,9 @@ pub struct Area { } fn main() { - let config = get_config(); + let (config, g_config) = get_config(); - let img = generate_image( - config.copy, - config.clone().into(), - Screenshot { - config: config.clone(), - }, - ); - - img.save_with_format( - &config.output, - str_to_format(config.save_format.unwrap_or("png".to_string())).unwrap(), - ) - .unwrap(); - println!("Saved!"); -} - -fn str_to_format(s: String) -> Result { - ImageFormat::from_extension(s.clone()).ok_or(ImageError::Unsupported( - UnsupportedError::from_format_and_kind( - ImageFormatHint::Name(s.to_string()), - UnsupportedErrorKind::Format(ImageFormatHint::Name(s.to_string())), - ), - )) + generate_image(g_config, Screenshot { config }); } fn str_to_area(s: &str) -> Result { diff --git a/crates/sss_code/src/config.rs b/crates/sss_code/src/config.rs index 85b7b5d..e685ab8 100644 --- a/crates/sss_code/src/config.rs +++ b/crates/sss_code/src/config.rs @@ -4,25 +4,22 @@ use clap::Parser; use clap_stdin::FileOrStdin; use merge2::{bool::overwrite_false, Merge}; use serde::{Deserialize, Serialize}; -use sss_lib::font::parse_font_str; -use sss_lib::font::FontCollection; -use sss_lib::{Background, GenerationSettings, Shadow, ToRgba}; +use sss_lib::default_bool; use crate::error::CodeScreenshotError; -const fn default_bool() -> bool { - false -} - -#[inline] -fn swap_option(left: &mut Option, right: &mut Option) { - if left.is_none() || right.is_some() { - core::mem::swap(left, right); - } -} - #[derive(Clone, Debug, Deserialize, Merge, Parser, Serialize)] #[clap(author, version, about)] +struct ClapConfig { + #[clap(flatten)] + pub code: Option, + // lib configs + #[clap(flatten)] + #[serde(rename = "general")] + pub lib_config: sss_lib::GenerationSettingsArgs, +} + +#[derive(Clone, Debug, Default, Deserialize, Merge, Parser, Serialize)] pub struct CodeConfig { #[clap(help = "Content to take screenshot. It accepts stdin or File")] #[serde(skip)] @@ -34,13 +31,6 @@ pub struct CodeConfig { help = "Theme file to use. May be a path, or an embedded theme. Embedded themes will take precendence." )] pub theme: Option, - #[clap( - long, - default_value = "Hack=12.0;", - help = "The font used to render, format: Font Name=size;Other Font Name=12.0", - value_parser = parse_font_str - )] - pub fonts: Option, #[clap( long, help = "[Not recommended for manual use] Set theme from vim highlights, format: group,bg,fg,style;group,bg,fg,style;" @@ -76,95 +66,13 @@ pub struct CodeConfig { pub line_numbers: bool, #[clap(long, default_value = "4", help = "Tab width")] pub tab_width: Option, - #[clap(long, help = "Author Name of screenshot")] - pub author: Option, - #[clap(long, default_value = "#FFFFFF", help = "Title bar text color")] - pub author_color: Option, - #[clap(long, default_value = "Hack", help = "Font to render Author")] - pub author_font: Option, - // Window Bar - #[clap(long, help = "Whether show the window controls")] - #[merge(strategy = overwrite_false)] - #[serde(default = "default_bool")] - pub window_controls: bool, - #[clap(long, help = "Window title")] - pub window_title: Option, - #[clap(long, default_value = "#4287f5", help = "Window bar background")] - pub window_background: Option, - #[clap(long, default_value = "#FFFFFF", help = "Title bar text color")] - pub window_title_color: Option, - #[clap(long, default_value = "120", help = "Width of window controls")] - pub window_controls_width: Option, - #[clap( - long, - default_value = "40", - help = "Height of window title/controls bar" - )] - pub window_controls_height: Option, - #[clap(long, default_value = "10", help = "Padding of title on window bar")] - pub titlebar_padding: Option, - // Screenshot Section - #[clap( - long, - short, - default_value = "#323232", - help = "Support: '#RRGGBBAA' 'h;#RRGGBBAA;#RRGGBBAA' 'v;#RRGGBBAA;#RRGGBBAA' or file path" - )] - pub background: Option, - #[clap(long, short, default_value = "15")] - pub radius: Option, - // Padding Section - #[clap(long, default_value = "80")] - #[merge(strategy = swap_option)] - pub padding_x: Option, - #[clap(long, default_value = "100")] - #[merge(strategy = swap_option)] - pub padding_y: Option, - // Shadow Section - #[clap(long, help = "Enable shadow")] - #[merge(strategy = overwrite_false)] - #[serde(default = "default_bool")] - pub shadow: bool, - #[clap(long, help = "Generate shadow from inner image")] - #[merge(strategy = overwrite_false)] - #[serde(default = "default_bool")] - pub shadow_image: bool, - #[clap( - long, - default_value = "#707070", - help = "Support: '#RRGGBBAA' 'h;#RRGGBBAA;#RRGGBBAA' 'v;#RRGGBBAA;#RRGGBBAA' or file path" - )] - #[merge(strategy = swap_option)] - pub shadow_color: Option, - #[clap(long, default_value = "50")] - #[merge(strategy = swap_option)] - pub shadow_blur: Option, - // Saving options - #[clap(long, short = 'c', help = "Send the result to your clipboard")] - #[merge(strategy = overwrite_false)] - #[serde(default = "default_bool")] - pub copy: bool, - #[clap( - long, - short, - help = "If it is set then the result will be saved here, otherwise it will not be saved." - )] - #[serde(skip)] - pub output: String, - #[clap( - long, - short = 'f', - default_value = "png", - help = "The format in which the image will be saved" - )] - pub save_format: Option, } -pub fn get_config() -> CodeConfig { +pub fn get_config() -> (CodeConfig, sss_lib::GenerationSettings) { let config_path = directories::BaseDirs::new() .unwrap() .config_dir() - .join("sss_code"); + .join("sss"); let _ = std::fs::create_dir_all(config_path.clone()); @@ -173,44 +81,14 @@ pub fn get_config() -> CodeConfig { if let Ok(cfg_content) = std::fs::read_to_string(config_path) { println!("Merging from config file"); - let mut config: CodeConfig = toml::from_str(&cfg_content).unwrap(); - let mut args = CodeConfig::parse(); + let mut config: ClapConfig = toml::from_str(&cfg_content).unwrap(); + let mut args = ClapConfig::parse(); config.merge(&mut args); - return config; - } - CodeConfig::parse() -} - -impl From for GenerationSettings { - fn from(val: CodeConfig) -> Self { - let background = Background::try_from(val.background.unwrap().clone()).unwrap(); - let windows_background = - Background::try_from(val.window_background.unwrap().clone()).unwrap(); - let shadow_color = Background::try_from(val.shadow_color.unwrap().clone()).unwrap(); - - GenerationSettings { - windows_background, - background, - padding: (val.padding_x.unwrap(), val.padding_y.unwrap()), - round_corner: val.radius, - shadow: val.shadow.then_some(Shadow { - shadow_color, - use_inner_image: val.shadow_image, - blur_radius: val.shadow_blur.unwrap(), - }), - fonts: val.fonts.unwrap_or_default(), - author: val.author.clone(), - author_font: val.author_font.clone().unwrap(), - author_color: val.author_color.unwrap().to_rgba().unwrap(), - window_controls: val.window_controls, - windows_title: val.window_title.clone(), - windows_title_color: val.window_title_color.unwrap().to_rgba().unwrap(), - window_controls_width: val.window_controls_width.unwrap(), - window_controls_height: val.window_controls_height.unwrap(), - titlebar_padding: val.titlebar_padding.unwrap(), - } + return (config.code.unwrap(), config.lib_config.into()); } + let config = ClapConfig::parse(); + (config.code.unwrap(), config.lib_config.into()) } fn parse_range(s: &str) -> Result, CodeScreenshotError> { diff --git a/crates/sss_code/src/img.rs b/crates/sss_code/src/img.rs index c81cacf..94d9f84 100644 --- a/crates/sss_code/src/img.rs +++ b/crates/sss_code/src/img.rs @@ -7,7 +7,7 @@ use std::ops::Range; use sss_lib::font::{FontCollection, FontStyle}; use sss_lib::image::{Rgba, RgbaImage}; -use sss_lib::DynImageContent; +use sss_lib::{DynImageContent, GenerationSettings}; use syntect::easy::HighlightLines; use syntect::highlighting::{Color, Style, Theme}; use syntect::parsing::{SyntaxReference, SyntaxSet}; @@ -24,6 +24,7 @@ const CODE_PADDING: u32 = 25; pub struct ImageCode<'a> { pub font: FontCollection, + pub lib_config: GenerationSettings, pub config: CodeConfig, pub syntax_set: &'a SyntaxSet, pub syntax: &'a SyntaxReference, @@ -41,8 +42,11 @@ impl<'a> ImageCode<'a> { fn get_line_y(&self, lineno: u32) -> u32 { lineno * self.get_line_height() + CODE_PADDING - + if self.config.window_controls || self.config.window_title.is_some() { - self.config.window_controls_height.unwrap() + self.config.titlebar_padding.unwrap() + + if self.lib_config.window_controls.enable + || self.lib_config.window_controls.title.is_some() + { + self.lib_config.window_controls.height + + self.lib_config.window_controls.title_padding } else { 0 } diff --git a/crates/sss_code/src/main.rs b/crates/sss_code/src/main.rs index c6ea709..11da72b 100644 --- a/crates/sss_code/src/main.rs +++ b/crates/sss_code/src/main.rs @@ -1,10 +1,9 @@ +#![allow(clippy::expect_fun_call)] use std::borrow::Cow; use config::get_config; use img::ImageCode; use sss_lib::generate_image; -use sss_lib::image::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; -use sss_lib::image::{ImageError, ImageFormat}; use syntect::highlighting::ThemeSet; use syntect::parsing::SyntaxSet; use theme::{list_themes, load_theme, theme_from_vim}; @@ -17,7 +16,8 @@ mod utils; // wl-paste | cargo run -p sss_code -- -t InspiredGitHub -e rs --lines 2..10 --vim-theme "Normal,#dab997,#262626,,;LineNr,#949494,#262626,,;Visual,,#4e4e4e,,;Cursor,#262626,#dab997;CursorLine,,#3a3a3a,,;Search,#3a3a3a,ffaf00;SpellBad,#d75f5f,,undercurl,;Title,#83adad,,,;MatchParen,,#8a8a8a,,;IdentBlanklineChar,#4e4e4e,,,;Number,#ff8700,,,;Character,#d75f5f,,,;String,#afaf00,,,;Constant,#ff8700,,,;Identifier,#d75f5f,,,;Keyword,#d485ad,,,;Comment,#8a8a8a,,,;Operator,#d485ad,,,;Statement,#d75f5f,,,;Type,#ffaf00,,,;StorageClass,#ffaf00,,,;Function,#83adad,,," - fn main() { - let config = get_config(); + let (config, g_config) = get_config(); + let mut ss = SyntaxSet::load_defaults_newlines(); let themes = ThemeSet::load_defaults(); @@ -39,10 +39,11 @@ fn main() { let content = config.content.clone().unwrap().contents().unwrap(); let syntax = if let Some(ext) = &config.extension { - ss.find_syntax_by_extension(ext).unwrap() + ss.find_syntax_by_extension(ext) + .expect(&format!("Extension not found: {ext}")) } else { ss.find_syntax_by_first_line(content.split('\n').next().unwrap()) - .unwrap() + .expect("Extension not found by code") }; let theme = if let Some(vim_theme) = &config.vim_theme { @@ -59,33 +60,18 @@ fn main() { .unwrap_or_else(|| Cow::Owned(load_theme(&theme, true))) }; - let out = generate_image( - config.copy, - config.clone().into(), + generate_image( + g_config.clone(), ImageCode { - font: config.fonts.clone().unwrap_or_default(), - config: config.clone(), + config, syntax, theme, + lib_config: g_config.clone(), syntax_set: &ss, content: &content, + font: g_config.fonts, }, ); - - out.save_with_format( - &config.output, - str_to_format(config.save_format.unwrap_or("png".to_string())).unwrap(), - ) - .unwrap(); -} - -fn str_to_format(s: String) -> Result { - ImageFormat::from_extension(s.clone()).ok_or(ImageError::Unsupported( - UnsupportedError::from_format_and_kind( - ImageFormatHint::Name(s.to_string()), - UnsupportedErrorKind::Format(ImageFormatHint::Name(s.to_string())), - ), - )) } fn list_file_types(ss: &SyntaxSet) { diff --git a/crates/sss_lib/Cargo.toml b/crates/sss_lib/Cargo.toml index 244d2f9..de113d4 100644 --- a/crates/sss_lib/Cargo.toml +++ b/crates/sss_lib/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true [dependencies] log.workspace = true +clap.workspace = true +serde.workspace = true +merge2.workspace = true env_logger.workspace = true thiserror.workspace = true conv = "0.3.3" @@ -24,4 +27,3 @@ image = { version = "0.24.7", default-features = false, features = [ "webp", ] } rayon = "1.8.0" -serde.workspace = true diff --git a/crates/sss_lib/src/args.rs b/crates/sss_lib/src/args.rs new file mode 100644 index 0000000..0068122 --- /dev/null +++ b/crates/sss_lib/src/args.rs @@ -0,0 +1,219 @@ +use clap::Parser; +use image::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; +use image::{ImageError, ImageFormat, Rgba}; +use merge2::bool::overwrite_false; +use merge2::Merge; +use serde::{Deserialize, Serialize}; + +use crate::font::{parse_font_str, FontCollection}; +use crate::{Background, Colors, GenerationSettings, Shadow, ToRgba, WindowControls}; + +pub fn str_to_format(s: String) -> Result { + ImageFormat::from_extension(s.clone()).ok_or(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormatHint::Name(s.to_string()), + UnsupportedErrorKind::Format(ImageFormatHint::Name(s.to_string())), + ), + )) +} + +pub const fn default_bool() -> bool { + false +} + +#[inline] +pub fn swap_option(left: &mut Option, right: &mut Option) { + if left.is_none() || right.is_some() { + core::mem::swap(left, right); + } +} + +#[derive(Clone, Debug, Parser, Merge, Serialize, Deserialize)] +pub struct GenerationSettingsArgs { + // Screenshot Section + #[clap( + long, + help = "[default: Hack=12.0;] The font used to render, format: Font Name=size;Other Font Name=12.0", + value_parser = parse_font_str + )] + #[merge(strategy = swap_option)] + pub fonts: Option, + #[clap(long, short, help = "[default: 15] ")] + #[merge(strategy = swap_option)] + pub radius: Option, + #[clap(long, help = "Author Name of screenshot")] + #[merge(strategy = swap_option)] + pub author: Option, + #[clap(long, help = "[default: Hack] Font to render Author")] + #[merge(strategy = swap_option)] + pub author_font: Option, + // Padding Section + #[clap(long, help = "[default: 80]")] + #[merge(strategy = swap_option)] + pub padding_x: Option, + #[clap(long, help = "[default: 100]")] + #[merge(strategy = swap_option)] + pub padding_y: Option, + // Shadow Section + #[clap(long, help = "Enable shadow")] + #[merge(strategy = overwrite_false)] + #[serde(default = "default_bool")] + pub shadow: bool, + #[clap(long, help = "Generate shadow from inner image")] + #[merge(strategy = overwrite_false)] + #[serde(default = "default_bool")] + pub shadow_image: bool, + #[clap(long, help = "[default: 50] Shadow blur")] + #[merge(strategy = swap_option)] + pub shadow_blur: Option, + // Saving options + #[clap(long, short, help = "Send the result to your clipboard")] + #[merge(strategy = overwrite_false)] + #[serde(default = "default_bool")] + pub copy: bool, + #[clap( + long, + short, + help = "If it is set then the result will be saved here, otherwise it will not be saved." + )] + #[serde(skip)] + pub output: String, + #[clap( + long, + short = 'f', + help = "[default: png] The format in which the image will be saved" + )] + #[merge(strategy = swap_option)] + pub save_format: Option, + #[clap(flatten)] + pub colors: ColorsArgs, + #[clap(flatten)] + pub window_controls: WindowControlsArgs, +} + +#[derive(Clone, Debug, Parser, Merge, Serialize, Deserialize)] +pub struct ColorsArgs { + #[clap( + long, + short, + help = "[default: #323232] Support: '#RRGGBBAA' 'h;#RRGGBBAA;#RRGGBBAA' 'v;#RRGGBBAA;#RRGGBBAA' or file path" + )] + #[merge(strategy = swap_option)] + pub background: Option, + #[clap(long, help = "[default: #FFFFFF] Title bar text color")] + #[merge(strategy = swap_option)] + #[serde(rename = "author")] + pub author_color: Option, + #[clap(long, help = "[default: #4287f5] Window bar background")] + #[merge(strategy = swap_option)] + pub window_background: Option, + #[clap(long, help = "[default: #FFFFFF] Title bar text color")] + #[merge(strategy = swap_option)] + #[serde(rename = "title")] + pub window_title_color: Option, + #[clap( + long, + help = "[default: #707070] Support: '#RRGGBBAA' 'h;#RRGGBBAA;#RRGGBBAA' 'v;#RRGGBBAA;#RRGGBBAA' or file path" + )] + #[merge(strategy = swap_option)] + #[serde(rename = "shadow")] + pub shadow_color: Option, +} + +#[derive(Clone, Debug, Parser, Merge, Serialize, Deserialize)] +pub struct WindowControlsArgs { + // Window Bar + #[clap(long = "window-controls", help = "Whether show the window controls")] + #[merge(strategy = overwrite_false)] + #[serde(default = "default_bool")] + pub enable: bool, + #[clap(long, help = "Window title")] + #[merge(strategy = swap_option)] + #[serde(rename = "title")] + pub window_title: Option, + #[clap(long, help = "[default 120] Width of window controls")] + #[merge(strategy = swap_option)] + #[serde(rename = "width")] + pub window_controls_width: Option, + #[clap(long, help = "[default: 40] Height of window title/controls bar")] + #[merge(strategy = swap_option)] + #[serde(rename = "height")] + pub window_controls_height: Option, + #[clap(long, help = "[default: 10] Padding of title on window bar")] + #[merge(strategy = swap_option)] + #[serde(rename = "padding")] + pub titlebar_padding: Option, +} + +impl From for GenerationSettings { + fn from(val: GenerationSettingsArgs) -> Self { + let shadow_color = val + .colors + .clone() + .shadow_color + .map(|b| Background::try_from(b).unwrap_or_default()) + .unwrap_or_default(); + + GenerationSettings { + copy: val.copy, + output: val.output.clone(), + save_format: val.save_format.clone(), + colors: val.colors.into(), + padding: (val.padding_x.unwrap_or(80), val.padding_y.unwrap_or(100)), + round_corner: val.radius.or(Some(15)), + shadow: val.shadow.then_some(Shadow { + shadow_color, + use_inner_image: val.shadow_image, + blur_radius: val.shadow_blur.unwrap_or(50.), + }), + fonts: val.fonts.unwrap_or_default(), + author: val.author.clone(), + author_font: val.author_font.clone().unwrap_or("Hack".to_string()), + window_controls: val.window_controls.into(), + } + } +} + +impl From for Colors { + fn from(val: ColorsArgs) -> Self { + let background = val + .background + .map(|b| { + Background::try_from(b) + .unwrap_or(Background::Solid(image::Rgba([0x32, 0x32, 0x32, 255]))) + }) + .unwrap_or(Background::Solid(image::Rgba([0x32, 0x32, 0x32, 255]))); + let windows_background = val + .window_background + .map(|b| { + Background::try_from(b).unwrap_or(Background::Solid(Rgba([0x42, 0x87, 0xf5, 255]))) + }) + .unwrap_or(Background::Solid(Rgba([0x42, 0x87, 0xf5, 255]))); + Colors { + background, + windows_background, + author_color: val + .author_color + .unwrap_or("#FFFFFF".to_string()) + .to_rgba() + .unwrap(), + windows_title: val + .window_title_color + .unwrap_or("#FFFFFF".to_string()) + .to_rgba() + .unwrap(), + } + } +} + +impl From for WindowControls { + fn from(val: WindowControlsArgs) -> Self { + WindowControls { + enable: val.enable, + title: val.window_title.clone(), + width: val.window_controls_width.unwrap_or(120), + height: val.window_controls_height.unwrap_or(40), + title_padding: val.titlebar_padding.unwrap_or(10), + } + } +} diff --git a/crates/sss_lib/src/img.rs b/crates/sss_lib/src/img.rs index ec8ecc8..98d25fa 100644 --- a/crates/sss_lib/src/img.rs +++ b/crates/sss_lib/src/img.rs @@ -5,7 +5,7 @@ use crate::color::ToRgba; use crate::components::{add_window_controls, add_window_title, round_corner}; use crate::error::Background as BackgroundError; use crate::font::FontStyle; -use crate::{DynImageContent, GenerationSettings}; +use crate::{str_to_format, DynImageContent, GenerationSettings}; #[derive(Clone, Debug)] pub enum GradientType { @@ -43,16 +43,12 @@ impl Background { } } -pub fn generate_image( - copy_img: bool, - settings: GenerationSettings, - content: impl DynImageContent, -) -> image::ImageBuffer, Vec> { +pub fn generate_image(settings: GenerationSettings, content: impl DynImageContent) { let mut inner = content.content(); - let show_winbar = settings.window_controls || settings.windows_title.is_some(); + let show_winbar = settings.window_controls.enable || settings.window_controls.title.is_some(); let (p_x, p_y) = settings.padding; let win_bar_h = if show_winbar { - settings.window_controls_height + settings.window_controls.height } else { 0 }; @@ -62,31 +58,32 @@ pub fn generate_image( ); let mut winbar = settings + .colors .windows_background - .to_image(inner.width(), settings.window_controls_height); - let mut img = settings.background.to_image(w, h); + .to_image(inner.width(), settings.window_controls.height); + let mut img = settings.colors.background.to_image(w, h); - if settings.window_controls { + if settings.window_controls.enable { add_window_controls( &mut winbar, - settings.windows_background, - settings.window_controls_width, - settings.window_controls_height, - settings.titlebar_padding, - settings.window_controls_width / 3 / 4, + settings.colors.windows_background, + settings.window_controls.width, + settings.window_controls.height, + settings.window_controls.title_padding, + settings.window_controls.width / 3 / 4, ); } - if let Some(title) = settings.windows_title.as_ref() { + if let Some(title) = settings.window_controls.title.as_ref() { add_window_title( &mut winbar, &settings.fonts, - settings.windows_title_color, + settings.colors.windows_title, title, - settings.titlebar_padding, - settings.window_controls, - settings.window_controls_width, - settings.window_controls_height, + settings.window_controls.title_padding, + settings.window_controls.enable, + settings.window_controls.width, + settings.window_controls.height, ); } @@ -113,7 +110,7 @@ pub fn generate_image( settings.fonts.draw_text_mut( &mut img, - settings.author_color, + settings.colors.author_color, w / 2 - title_w / 2, h - p_y / 2, FontStyle::Bold, @@ -121,7 +118,7 @@ pub fn generate_image( ); } - if copy_img { + if settings.copy { let mut c = arboard::Clipboard::new().unwrap(); c.set_image(arboard::ImageData { @@ -130,9 +127,13 @@ pub fn generate_image( bytes: std::borrow::Cow::Owned(img.to_vec()), }) .unwrap(); + } else { + img.save_with_format( + &settings.output, + str_to_format(settings.save_format.unwrap_or("png".to_string())).unwrap(), + ) + .unwrap(); } - - img } impl TryFrom for Background { diff --git a/crates/sss_lib/src/lib.rs b/crates/sss_lib/src/lib.rs index 7e352fd..f3fd6b2 100644 --- a/crates/sss_lib/src/lib.rs +++ b/crates/sss_lib/src/lib.rs @@ -1,6 +1,7 @@ //! This library is originally inspired from https://github.com/Aloxaf/silicon use font::FontCollection; +mod args; pub mod blur; mod color; pub mod components; @@ -10,6 +11,7 @@ mod img; mod shadow; pub mod utils; +pub use args::*; pub use color::ToRgba; pub use image; use image::{Rgba, RgbaImage}; @@ -17,10 +19,17 @@ pub use imageproc; pub use img::*; pub use shadow::Shadow; +#[derive(Clone, Debug)] pub struct GenerationSettings { - /// Background for image - /// Default: #323232 - pub background: Background, + /// Copy to clipboard + /// Default: false + pub copy: bool, + /// Output img + /// Not Default + pub output: String, + /// Save Format image + /// Default: png + pub save_format: Option, /// pad between inner immage and edge. /// Default: 25 pub padding: (u32, u32), @@ -36,55 +45,48 @@ pub struct GenerationSettings { /// Show author name /// Default: None pub author: Option, - /// Author color - /// Default: #FFFFFF - pub author_color: Rgba, /// Author font to use /// Default: Hack pub author_font: String, - /// Enable Window Controls - /// Default: false - pub window_controls: bool, + /// Set Colors + pub colors: Colors, + /// Set Window Controls + pub window_controls: WindowControls, +} + +#[derive(Clone, Debug)] +pub struct Colors { + /// Author color + /// Default: #FFFFFF + pub author_color: Rgba, + /// Background for image + /// Default: #323232 + pub background: Background, /// Window title bar background color /// Default: #4287f5 pub windows_background: Background, - /// Enable Window Controls - /// Default: None - pub windows_title: Option, /// Title color /// Default: #FFFFFF - pub windows_title_color: Rgba, + pub windows_title: Rgba, +} + +#[derive(Clone, Debug)] +pub struct WindowControls { + /// Enable Window Controls + /// Default: false + pub enable: bool, + /// Enable Window Controls + /// Default: None + pub title: Option, /// Width of window controls /// Default: 120 - pub window_controls_width: u32, + pub width: u32, /// Height of window controls /// Default: 40 - pub window_controls_height: u32, + pub height: u32, /// Title bar padding on horizontal /// Default: 10 - pub titlebar_padding: u32, -} - -impl Default for GenerationSettings { - fn default() -> Self { - Self { - background: Background::Solid(image::Rgba([0x32, 0x32, 0x32, 255])), - fonts: FontCollection::default(), - padding: (80, 100), - round_corner: Some(15), - shadow: None, - author: None, - author_font: "Hack".to_string(), - author_color: Rgba([255, 255, 255, 255]), - window_controls: false, - windows_background: Background::Solid(Rgba([0x42, 0x87, 0xf5, 255])), - windows_title: None, - windows_title_color: Rgba([255, 255, 255, 255]), - window_controls_width: 120, - window_controls_height: 40, - titlebar_padding: 10, - } - } + pub title_padding: u32, } pub trait DynImageContent { diff --git a/crates/sss_lib/src/shadow.rs b/crates/sss_lib/src/shadow.rs index e0e1591..07f1d48 100644 --- a/crates/sss_lib/src/shadow.rs +++ b/crates/sss_lib/src/shadow.rs @@ -5,7 +5,7 @@ use crate::utils::copy_alpha; use crate::Background; /// Add the shadow for image -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Shadow { pub shadow_color: Background, pub use_inner_image: bool,