138 lines
5.2 KiB
Rust
138 lines
5.2 KiB
Rust
// icon-color-tool
|
|
// Copyright (C) 2026 Rootiest
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
use crate::color_scheme::ColorScheme;
|
|
|
|
pub struct CompositeIcon {
|
|
pub bg: ColorScheme,
|
|
pub emblem: Option<ColorScheme>,
|
|
pub emblem_x: f64,
|
|
pub emblem_y: f64,
|
|
pub emblem_size: f64,
|
|
pub is_modified: bool,
|
|
}
|
|
|
|
impl CompositeIcon {
|
|
pub fn new(bg: ColorScheme) -> Self {
|
|
Self {
|
|
bg,
|
|
emblem: None,
|
|
emblem_x: 32.0,
|
|
emblem_y: 32.0,
|
|
emblem_size: 32.0,
|
|
is_modified: false,
|
|
}
|
|
}
|
|
|
|
pub fn set_emblem(&mut self, emblem: ColorScheme) {
|
|
self.emblem = Some(emblem);
|
|
}
|
|
|
|
pub fn render(&self) -> String {
|
|
use regex::Regex;
|
|
|
|
let strip_dimensions = |svg: &str| -> String {
|
|
if let Some(root_svg_end) = svg.find('>') {
|
|
let mut root_tag = svg[..=root_svg_end].to_string();
|
|
let re_w = Regex::new(r#"(?i)\s+width\s*=\s*"[^"]*""#).unwrap();
|
|
root_tag = re_w.replace_all(&root_tag, "").into_owned();
|
|
let re_h = Regex::new(r#"(?i)\s+height\s*=\s*"[^"]*""#).unwrap();
|
|
root_tag = re_h.replace_all(&root_tag, "").into_owned();
|
|
|
|
let mut new_svg = root_tag;
|
|
new_svg.push_str(&svg[root_svg_end + 1..]);
|
|
new_svg
|
|
} else {
|
|
svg.to_string()
|
|
}
|
|
};
|
|
|
|
if let Some(emblem) = &self.emblem {
|
|
|
|
fn parse_dims(svg: &str) -> (f64, f64) {
|
|
if let Ok(re) = Regex::new(r#"(?i)<svg[^>]*viewBox\s*=\s*"([^"]+)""#) {
|
|
if let Some(caps) = re.captures(svg) {
|
|
let parts: Vec<&str> = caps.get(1).unwrap().as_str().split_whitespace().collect();
|
|
if parts.len() == 4 {
|
|
if let (Ok(w), Ok(h)) = (parts[2].parse::<f64>(), parts[3].parse::<f64>()) {
|
|
return (w, h);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let mut w = 64.0;
|
|
let mut h = 64.0;
|
|
if let Ok(re_w) = Regex::new(r#"(?i)<svg[^>]*width\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#) {
|
|
if let Some(caps) = re_w.captures(svg) {
|
|
if let Ok(val) = caps.get(1).unwrap().as_str().trim().parse::<f64>() {
|
|
w = val;
|
|
}
|
|
}
|
|
}
|
|
if let Ok(re_h) = Regex::new(r#"(?i)<svg[^>]*height\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#) {
|
|
if let Some(caps) = re_h.captures(svg) {
|
|
if let Ok(val) = caps.get(1).unwrap().as_str().trim().parse::<f64>() {
|
|
h = val;
|
|
}
|
|
}
|
|
}
|
|
(w, h)
|
|
}
|
|
|
|
fn extract_inner(svg: &str) -> &str {
|
|
let start = if let Some(idx) = svg.find("<svg") {
|
|
if let Some(close_idx) = svg[idx..].find('>') {
|
|
idx + close_idx + 1
|
|
} else { 0 }
|
|
} else { 0 };
|
|
let end = if let Some(idx) = svg.rfind("</svg>") { idx } else { svg.len() };
|
|
if start < end { &svg[start..end] } else { svg }
|
|
}
|
|
|
|
let (bg_w, bg_h) = parse_dims(&self.bg.content);
|
|
let (em_w, em_h) = parse_dims(&emblem.content);
|
|
|
|
let mapped_size = self.emblem_size * bg_w / 64.0; // Assuming uniform coordinate mapping scaling based on width
|
|
let mapped_x = (self.emblem_x * bg_w / 64.0) - (mapped_size / 2.0);
|
|
let mapped_y = (self.emblem_y * bg_h / 64.0) - (mapped_size / 2.0);
|
|
|
|
let scale_x = mapped_size / em_w;
|
|
let scale_y = mapped_size / em_h; // Keep aspect ratio mapping relative to its own viewBox
|
|
|
|
let emblem_inner = extract_inner(&emblem.content);
|
|
|
|
let emblem_group = format!(
|
|
r#"<g transform="translate({}, {}) scale({}, {})">
|
|
{}
|
|
</g>
|
|
</svg>"#,
|
|
mapped_x, mapped_y, scale_x, scale_y, emblem_inner
|
|
);
|
|
|
|
let base_svg = strip_dimensions(&self.bg.content);
|
|
if let Some(idx) = base_svg.rfind("</svg>") {
|
|
let mut new_svg = base_svg[..idx].to_string();
|
|
new_svg.push_str(&emblem_group);
|
|
new_svg
|
|
} else {
|
|
base_svg
|
|
}
|
|
} else {
|
|
strip_dimensions(&self.bg.content)
|
|
}
|
|
}
|
|
}
|