mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 17:44:02 +00:00
Effects: a pasted SVG filter at the size limit still draws under its new name
Pasted art whose SVG filter is renamed to a free name ("Soft 2") had
the new name written into the filter's id. For a filter at the 64 KB
limit the longer id made it too long to draw, so the pasted art lost
the effect. Paste now takes the first free name under which the filter
still fits: a shorter start of its name ("Sof"), else a free letter or
digit. The filter's id is always the effect's name.
This commit is contained in:
@@ -1219,7 +1219,9 @@ impl Clipboard {
|
||||
/// How the pasted SVG filter effects relink ([`Relink::filters`]). Each names the document's
|
||||
/// filter of its name; one that draws another definition than that filter takes the document's
|
||||
/// filter when pasted back into the document it came from, and elsewhere keeps its own under a
|
||||
/// free name, so editing or deleting the document's filter leaves it alone.
|
||||
/// free name, so editing or deleting the document's filter leaves it alone. When that name would
|
||||
/// make the filter too long to draw, the free name is a shorter start of its name ("Shif"), else
|
||||
/// a free letter or digit; the filter's id is always its name.
|
||||
fn filter_links(&self, d: &Document, same_doc: bool) -> BTreeMap<(String, String), (String, String)> {
|
||||
let mut found = BTreeSet::new();
|
||||
for n in &self.nodes {
|
||||
@@ -1244,9 +1246,20 @@ impl Clipboard {
|
||||
let to = if same_doc {
|
||||
(name.clone(), held.xml.clone())
|
||||
} else {
|
||||
let new = unique_name(&name, |n| taken.contains(n));
|
||||
// Free names made from the starts of the name, longest first, then free letters and
|
||||
// digits: the first under which the filter still draws.
|
||||
let mut ends: Vec<usize> = name.char_indices().map(|(i, _)| i).skip(1).collect();
|
||||
ends.push(name.len());
|
||||
let starts = ends.iter().rev().filter_map(|&end| name.get(..end).map(str::trim_end)).filter(|b| !b.is_empty());
|
||||
let shorter = starts.map(|b| unique_name(b, |n| taken.contains(n)));
|
||||
let single = ('a'..='z').chain('0'..='9').map(String::from).filter(|n| !taken.contains(n));
|
||||
let renamed = shorter.chain(single).find_map(|new| {
|
||||
let drawn = vectorcraft_render::effects::svg_filter::with_id(&xml, &new)
|
||||
.filter(|x| vectorcraft_render::effects::svg_filter::check(x).is_ok())?;
|
||||
Some((new, drawn))
|
||||
});
|
||||
let Some((new, drawn)) = renamed else { continue };
|
||||
taken.insert(new.clone());
|
||||
let drawn = vectorcraft_render::effects::svg_filter::with_id(&xml, &new).unwrap_or_else(|| xml.clone());
|
||||
(new, drawn)
|
||||
};
|
||||
links.insert((name, xml), to);
|
||||
|
||||
@@ -259,6 +259,31 @@ fn pasted_art_keeps_a_filter_another_of_its_name_would_replace() {
|
||||
assert_eq!(both, [("Shift".to_string(), SHIFT_MORE.to_string()), ("Shift".to_string(), SHIFT_MORE.to_string())]);
|
||||
}
|
||||
|
||||
/// A pasted filter at the size limit, which "Shift 2" would make too long to draw, keeps its own
|
||||
/// definition under a free name no longer than its own (a free letter for a one-letter name), and
|
||||
/// its id is that name.
|
||||
#[test]
|
||||
fn pasted_art_with_a_filter_at_the_size_limit_still_draws_when_renamed() {
|
||||
for (name, held, renamed) in [("Shift", SHIFT, "Shif"), ("S", r#"<filter id="S"><feOffset dx="3" dy="3"/></filter>"#, "a")] {
|
||||
let mut s = session();
|
||||
run(&mut s, "svgFilter.define", json!({"xml": held}));
|
||||
let (open, close) = (format!(r#"<filter id="{name}"><feOffset dx="9" dy="9" result=""#), r#""/></filter>"#);
|
||||
let big = format!("{open}{}{close}", "a".repeat(vectorcraft_doc::SvgFilter::MAX_XML - open.len() - close.len()));
|
||||
assert!(vectorcraft_render::effects::svg_filter::check(&big).is_ok());
|
||||
let svg = format!(
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><defs>{big}</defs><rect width="30" height="30" filter="url(#{name})"/></svg>"#
|
||||
);
|
||||
run(&mut s, "clipboard.importSvg", json!({"svg": svg}));
|
||||
run(&mut s, "edit.pasteInPlace", json!({}));
|
||||
let pasted = filter_effects(&s);
|
||||
assert_eq!(pasted.len(), 1);
|
||||
assert_eq!(pasted[0].0, renamed, "renamed: {}", pasted[0].0);
|
||||
assert_eq!(vectorcraft_render::effects::svg_filter::check(&pasted[0].1), Ok(renamed.to_string()), "the pasted filter draws under its name");
|
||||
assert!(pasted[0].1.contains("dx=\"9\""), "its own filter");
|
||||
assert_eq!(names(&mut s), [name]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Art in a symbol's definition that uses a filter follows its edits too.
|
||||
#[test]
|
||||
fn a_symbols_art_follows_a_filter_edit() {
|
||||
|
||||
Reference in New Issue
Block a user