Merge #1165: Make SVG unique_id linear for repeated layer names (@Silvertree2010)

This commit is contained in:
bflatastic
2026-10-24 09:19:41 +07:00
2 changed files with 41 additions and 1 deletions
+12 -1
View File
@@ -79,6 +79,8 @@ struct W<'a> {
clip: Option<String>,
/// `id` attributes written so far.
used_ids: std::collections::BTreeSet<String>,
/// The next `_k` suffix to try per base id, so repeated names don't rescan from `_2`.
next_suffix: std::collections::HashMap<String, usize>,
}
/// ` clip-path="url(#…)"` for a clip path id (else nothing).
@@ -549,7 +551,15 @@ impl W<'_> {
fn unique_id(&mut self, name: &str) -> String {
let base: String = name.chars().take(200).map(|c| if c.is_whitespace() || c.is_control() { '_' } else { c }).collect();
let base = if base.is_empty() { "layer".to_string() } else { base };
let id = (1..).map(|k| if k == 1 { base.clone() } else { format!("{base}_{k}") }).find(|i| !self.used_ids.contains(i)).unwrap_or(base);
let next = self.next_suffix.entry(base.clone()).or_insert(1);
let id = loop {
let k = *next;
*next += 1;
let candidate = if k == 1 { base.clone() } else { format!("{base}_{k}") };
if !self.used_ids.contains(&candidate) {
break candidate;
}
};
self.used_ids.insert(id.clone());
id
}
@@ -1004,6 +1014,7 @@ pub fn try_export_svg(doc: &Document, ids: &[NodeId], opts: &SvgOptions) -> Resu
mask: None,
clip: None,
used_ids: Default::default(),
next_suffix: Default::default(),
};
for id in ids {
let parent_xf = doc.node(*id).and_then(|n| n.parent).map(|p| doc.world_transform(p)).unwrap_or(Affine::IDENTITY);
+29
View File
@@ -179,3 +179,32 @@ fn point_joins_equal_to_the_layer_join_keep_a_plain_stroke() {
assert!(svg_with(StrokeJoin::Miter).contains("stroke-width=\"2\""), "equal joins stay a stroke");
assert!(!svg_with(StrokeJoin::Round).contains("stroke-width="), "a differing join is drawn as an outline");
}
#[test]
fn unique_id_is_linear_for_repeated_names() {
let d = Document::new("t");
let opts = SvgOptions::default();
let mut w = W {
doc: &d,
roots: &[],
page: Affine::IDENTITY,
defs: String::new(),
body: String::new(),
next: 0,
opts: &opts,
mask: None,
clip: None,
used_ids: Default::default(),
next_suffix: Default::default(),
};
assert_eq!(w.unique_id("a"), "a");
assert_eq!(w.unique_id("a_3"), "a_3");
assert_eq!(w.unique_id("a"), "a_2");
assert_eq!(w.unique_id("a"), "a_4");
let start = std::time::Instant::now();
for _ in 0..30_000 {
w.unique_id("Rectangle");
}
assert_eq!(w.unique_id("Rectangle"), "Rectangle_30001");
assert!(start.elapsed() < std::time::Duration::from_secs(2), "unique_id is quadratic: {:?}", start.elapsed());
}