diff --git a/crates/svg/src/export.rs b/crates/svg/src/export.rs index bfd01003..616b4771 100644 --- a/crates/svg/src/export.rs +++ b/crates/svg/src/export.rs @@ -50,6 +50,7 @@ fn write(doc: &Document, opts: &ExportOptions, native: Option<&[u8]>, id_prefix: body: String::new(), defs: String::new(), classes: Vec::new(), + class_ids: HashMap::new(), used_ids: HashSet::new(), names: HashMap::new(), depth: 1, @@ -259,8 +260,10 @@ struct Writer<'a> { xf: Affine, body: String, defs: String, - /// Unique CSS declaration blocks; class name is `cls-{index + 1}`. + /// Unique CSS declaration blocks, in first-use order (used for deterministic output). classes: Vec, + /// 1-based class/entity number by declaration; avoids a linear scan for each style. + class_ids: HashMap, used_ids: HashSet, names: HashMap, depth: usize, @@ -657,13 +660,13 @@ impl Writer<'_> { /// The 1-based number of a distinct declaration block (a CSS class or a style entity). fn class(&mut self, decl: String) -> usize { - match self.classes.iter().position(|c| *c == decl) { - Some(i) => i + 1, - None => { - self.classes.push(decl); - self.classes.len() - } + if let Some(&i) = self.class_ids.get(decl.as_str()) { + return i; } + let i = self.classes.len() + 1; + self.class_ids.insert(decl.clone(), i); + self.classes.push(decl); + i } /// ``: the Dublin Core terms of File Info ([`ExportOptions::metadata`]) and the diff --git a/crates/svg/tests/options.rs b/crates/svg/tests/options.rs index d2c8cfda..0a57a103 100644 --- a/crates/svg/tests/options.rs +++ b/crates/svg/tests/options.rs @@ -29,6 +29,33 @@ fn doc() -> Document { d } +/// A large palette should create one class per distinct declaration, preserving first-use +/// numbers even when earlier styles recur after hundreds of others. +#[test] +fn many_css_styles_preserve_first_use_numbers() { + let mut d = Document::new(300.0, 10.0); + let layer = d.layers[0].id; + let colors = (0u16..256).chain([0, 128, 255]); + for (x, color) in colors.enumerate() { + let x = x as f64; + let appearance = Appearance::basic(Paint::solid(Color::rgb8(color as u8, 31, 63)), Paint::None, 0.0); + let n = Node::path(d.alloc_id(), shapes::rectangle(Rect::new(x, 0.0, x + 0.5, 1.0)), appearance); + d.insert(Some(layer), usize::MAX, n).unwrap(); + } + + for (styling, definition_marker) in [(Styling::InternalCss, ".cls-"), (Styling::StyleEntities, " = svg.lines().filter(|line| line.contains("