fix: deduplicate icons in browser by preferring highest resolution version

This commit is contained in:
2026-08-03 22:24:34 -04:00
parent 84e236ea4c
commit 80db9daf0b
2 changed files with 102 additions and 20 deletions
+63 -16
View File
@@ -25,28 +25,75 @@ impl CompositeIcon {
pub fn render(&self) -> String {
if let Some(emblem) = &self.emblem {
fn strip_xml(svg: &str) -> &str {
if let Some(idx) = svg.find("<svg") {
&svg[idx..]
} else {
svg
use regex::Regex;
fn parse_dims(svg: &str) -> (f64, f64) {
if let Some(re) = Regex::new(r#"(?i)<svg[^>]*viewBox\s*=\s*"([^"]+)""#).ok() {
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 Some(re_w) = Regex::new(r#"(?i)<svg[^>]*width\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#).ok() {
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 Some(re_h) = Regex::new(r#"(?i)<svg[^>]*height\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#).ok() {
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)
}
let bg_svg = strip_xml(&self.bg.content);
let emblem_svg = strip_xml(&emblem.content);
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);
format!(
r#"<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<svg x="0" y="0" width="64" height="64">
let mapped_x = self.emblem_x * bg_w / 64.0;
let mapped_y = self.emblem_y * bg_h / 64.0;
let mapped_size = self.emblem_size * bg_w / 64.0; // Assuming uniform coordinate mapping scaling based on width
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({}, {})">
{}
</svg>
<svg x="{}" y="{}" width="{}" height="{}">
{}
</svg>
</g>
</svg>"#,
bg_svg, self.emblem_x, self.emblem_y, self.emblem_size, self.emblem_size, emblem_svg
)
mapped_x, mapped_y, scale_x, scale_y, emblem_inner
);
if let Some(idx) = self.bg.content.rfind("</svg>") {
let mut new_svg = self.bg.content[..idx].to_string();
new_svg.push_str(&emblem_group);
new_svg
} else {
self.bg.content.clone() // fallback
}
} else {
self.bg.content.clone()
}
+39 -4
View File
@@ -2,13 +2,48 @@ use std::path::{Path, PathBuf};
use walkdir::WalkDir;
pub fn list_svg_icons(root: &Path) -> Vec<PathBuf> {
WalkDir::new(root)
use std::collections::HashMap;
let mut best_icons: HashMap<String, (u32, PathBuf)> = HashMap::new();
let walker = WalkDir::new(root)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file())
.filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("svg"))
.map(|e| e.path().to_path_buf())
.collect()
.filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("svg"));
for entry in walker {
let path = entry.path().to_path_buf();
let mut components = path.components().rev();
let filename = if let Some(c) = components.next() {
c.as_os_str().to_string_lossy().into_owned()
} else { continue; };
let size_str = if let Some(c) = components.next() {
c.as_os_str().to_string_lossy().into_owned()
} else { continue; };
let category = if let Some(c) = components.next() {
c.as_os_str().to_string_lossy().into_owned()
} else { continue; };
let score = if size_str == "scalable" {
10000
} else if size_str.ends_with("x2") || size_str.ends_with("@2x") {
size_str.trim_matches(|c: char| !c.is_numeric()).parse::<u32>().unwrap_or(0) * 2
} else {
size_str.parse::<u32>().unwrap_or(0)
};
let key = format!("{}/{}", category, filename);
let entry = best_icons.entry(key).or_insert((score, path.clone()));
if score > entry.0 {
*entry = (score, path);
}
}
best_icons.into_values().map(|(_, p)| p).collect()
}
pub fn fuzzy_match(query: &str, candidate: &str) -> bool {