mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 10:27:00 -06:00
Merge #1165: Make SVG unique_id linear for repeated layer names (@Silvertree2010)
This commit is contained in:
+12
-1
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user