Add font family selector for watermark text
This commit is contained in:
@@ -313,6 +313,7 @@ fn cmd_process(args: CmdProcessArgs) {
|
|||||||
font_size: 24.0,
|
font_size: 24.0,
|
||||||
opacity: args.watermark_opacity,
|
opacity: args.watermark_opacity,
|
||||||
color: [255, 255, 255, 255],
|
color: [255, 255, 255, 255],
|
||||||
|
font_family: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if args.rename_prefix.is_some() || args.rename_suffix.is_some() || args.rename_template.is_some() {
|
if args.rename_prefix.is_some() || args.rename_suffix.is_some() || args.rename_template.is_some() {
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ pub enum WatermarkConfig {
|
|||||||
font_size: f32,
|
font_size: f32,
|
||||||
opacity: f32,
|
opacity: f32,
|
||||||
color: [u8; 4],
|
color: [u8; 4],
|
||||||
|
font_family: Option<String>,
|
||||||
},
|
},
|
||||||
Image {
|
Image {
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ pub fn apply_watermark(
|
|||||||
font_size,
|
font_size,
|
||||||
opacity,
|
opacity,
|
||||||
color,
|
color,
|
||||||
} => apply_text_watermark(img, text, *position, *font_size, *opacity, *color),
|
font_family,
|
||||||
|
} => apply_text_watermark(img, text, *position, *font_size, *opacity, *color, font_family.as_deref()),
|
||||||
WatermarkConfig::Image {
|
WatermarkConfig::Image {
|
||||||
path,
|
path,
|
||||||
position,
|
position,
|
||||||
@@ -56,7 +57,38 @@ pub fn apply_watermark(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_system_font() -> Result<Vec<u8>> {
|
fn find_system_font(family: Option<&str>) -> Result<Vec<u8>> {
|
||||||
|
// If a specific font family was requested, try to find it via fontconfig
|
||||||
|
if let Some(name) = family {
|
||||||
|
if !name.is_empty() {
|
||||||
|
// Try common font paths with the family name
|
||||||
|
let search_dirs = [
|
||||||
|
"/usr/share/fonts",
|
||||||
|
"/usr/local/share/fonts",
|
||||||
|
];
|
||||||
|
let name_lower = name.to_lowercase();
|
||||||
|
for dir in &search_dirs {
|
||||||
|
if let Ok(entries) = walkdir(std::path::Path::new(dir)) {
|
||||||
|
for path in entries {
|
||||||
|
let file_name = path.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_lowercase();
|
||||||
|
if file_name.contains(&name_lower)
|
||||||
|
&& (file_name.ends_with(".ttf") || file_name.ends_with(".otf"))
|
||||||
|
&& (file_name.contains("regular") || !file_name.contains("bold") && !file_name.contains("italic"))
|
||||||
|
{
|
||||||
|
if let Ok(data) = std::fs::read(&path) {
|
||||||
|
return Ok(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to default system fonts
|
||||||
let candidates = [
|
let candidates = [
|
||||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||||
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
||||||
@@ -78,6 +110,25 @@ fn find_system_font() -> Result<Vec<u8>> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively walk a directory and collect file paths
|
||||||
|
fn walkdir(dir: &std::path::Path) -> std::io::Result<Vec<std::path::PathBuf>> {
|
||||||
|
let mut results = Vec::new();
|
||||||
|
if dir.is_dir() {
|
||||||
|
for entry in std::fs::read_dir(dir)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
if let Ok(sub) = walkdir(&path) {
|
||||||
|
results.extend(sub);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_text_watermark(
|
fn apply_text_watermark(
|
||||||
img: DynamicImage,
|
img: DynamicImage,
|
||||||
text: &str,
|
text: &str,
|
||||||
@@ -85,8 +136,9 @@ fn apply_text_watermark(
|
|||||||
font_size: f32,
|
font_size: f32,
|
||||||
opacity: f32,
|
opacity: f32,
|
||||||
color: [u8; 4],
|
color: [u8; 4],
|
||||||
|
font_family: Option<&str>,
|
||||||
) -> Result<DynamicImage> {
|
) -> Result<DynamicImage> {
|
||||||
let font_data = find_system_font()?;
|
let font_data = find_system_font(font_family)?;
|
||||||
let font = ab_glyph::FontArc::try_from_vec(font_data).map_err(|_| {
|
let font = ab_glyph::FontArc::try_from_vec(font_data).map_err(|_| {
|
||||||
PixstripError::Processing {
|
PixstripError::Processing {
|
||||||
operation: "watermark".into(),
|
operation: "watermark".into(),
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ pub struct JobConfig {
|
|||||||
pub watermark_opacity: f32,
|
pub watermark_opacity: f32,
|
||||||
pub watermark_font_size: f32,
|
pub watermark_font_size: f32,
|
||||||
pub watermark_color: [u8; 4],
|
pub watermark_color: [u8; 4],
|
||||||
|
pub watermark_font_family: String,
|
||||||
pub watermark_use_image: bool,
|
pub watermark_use_image: bool,
|
||||||
// Rename
|
// Rename
|
||||||
pub rename_enabled: bool,
|
pub rename_enabled: bool,
|
||||||
@@ -360,6 +361,7 @@ fn build_ui(app: &adw::Application) {
|
|||||||
watermark_opacity: 0.5,
|
watermark_opacity: 0.5,
|
||||||
watermark_font_size: 24.0,
|
watermark_font_size: 24.0,
|
||||||
watermark_color: [255, 255, 255, 255],
|
watermark_color: [255, 255, 255, 255],
|
||||||
|
watermark_font_family: String::new(),
|
||||||
watermark_use_image: false,
|
watermark_use_image: false,
|
||||||
rename_enabled: if remember { sess_state.rename_enabled.unwrap_or(false) } else { false },
|
rename_enabled: if remember { sess_state.rename_enabled.unwrap_or(false) } else { false },
|
||||||
rename_prefix: String::new(),
|
rename_prefix: String::new(),
|
||||||
@@ -1666,6 +1668,7 @@ fn run_processing(_window: &adw::ApplicationWindow, ui: &WizardUi) {
|
|||||||
font_size: cfg.watermark_font_size,
|
font_size: cfg.watermark_font_size,
|
||||||
opacity: cfg.watermark_opacity,
|
opacity: cfg.watermark_opacity,
|
||||||
color: cfg.watermark_color,
|
color: cfg.watermark_color,
|
||||||
|
font_family: if cfg.watermark_font_family.is_empty() { None } else { Some(cfg.watermark_font_family.clone()) },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2595,6 +2598,7 @@ fn build_preset_from_config(cfg: &JobConfig, name: &str) -> pixstrip_core::prese
|
|||||||
font_size: cfg.watermark_font_size,
|
font_size: cfg.watermark_font_size,
|
||||||
opacity: cfg.watermark_opacity,
|
opacity: cfg.watermark_opacity,
|
||||||
color: cfg.watermark_color,
|
color: cfg.watermark_color,
|
||||||
|
font_family: if cfg.watermark_font_family.is_empty() { None } else { Some(cfg.watermark_font_family.clone()) },
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -61,7 +61,31 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
.adjustment(>k::Adjustment::new(cfg.watermark_font_size as f64, 8.0, 200.0, 1.0, 10.0, 0.0))
|
.adjustment(>k::Adjustment::new(cfg.watermark_font_size as f64, 8.0, 200.0, 1.0, 10.0, 0.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Font family picker
|
||||||
|
let font_row = adw::ActionRow::builder()
|
||||||
|
.title("Font Family")
|
||||||
|
.subtitle("Choose a typeface for the watermark text")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let font_dialog = gtk::FontDialog::builder()
|
||||||
|
.title("Choose Watermark Font")
|
||||||
|
.modal(true)
|
||||||
|
.build();
|
||||||
|
let font_button = gtk::FontDialogButton::builder()
|
||||||
|
.dialog(&font_dialog)
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Set initial font if one was previously selected
|
||||||
|
if !cfg.watermark_font_family.is_empty() {
|
||||||
|
let desc = gtk::pango::FontDescription::from_string(&cfg.watermark_font_family);
|
||||||
|
font_button.set_font_desc(&desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
font_row.add_suffix(&font_button);
|
||||||
|
|
||||||
text_group.add(&text_row);
|
text_group.add(&text_row);
|
||||||
|
text_group.add(&font_row);
|
||||||
text_group.add(&font_size_row);
|
text_group.add(&font_size_row);
|
||||||
content.append(&text_group);
|
content.append(&text_group);
|
||||||
|
|
||||||
@@ -415,6 +439,16 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
jc.borrow_mut().watermark_font_size = row.value() as f32;
|
jc.borrow_mut().watermark_font_size = row.value() as f32;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Wire font family picker
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
font_button.connect_font_desc_notify(move |btn| {
|
||||||
|
let desc = btn.font_desc();
|
||||||
|
if let Some(family) = desc.family() {
|
||||||
|
jc.borrow_mut().watermark_font_family = family.to_string();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
// Wire position grid buttons
|
// Wire position grid buttons
|
||||||
for (i, btn) in buttons.iter().enumerate() {
|
for (i, btn) in buttons.iter().enumerate() {
|
||||||
let jc = state.job_config.clone();
|
let jc = state.job_config.clone();
|
||||||
|
|||||||
Reference in New Issue
Block a user