Save As .ai: keep the group fills and strokes of pattern tiles in the editing data

A pattern fill is written as its tiles cut to the shape. A group in the
tile art with fills or strokes of its own was written as its members alone,
without that paint, because only the layers and the symbols' art were baked
before writing. The patterns' tile art is now baked the same way as the
symbols' art, so its group paint and geometry effects are written as plain
paths.
This commit is contained in:
dotCooCoo
2026-10-28 21:52:04 -07:00
parent 9cd5e3df8f
commit 80c43c8133
2 changed files with 39 additions and 9 deletions
+18 -9
View File
@@ -156,26 +156,35 @@ fn crlf(s: &str) -> Vec<u8> {
s.replace('\n', "\r\n").into_bytes()
}
/// `doc` with its geometry effects baked ([`vectorcraft_effects::bake_document`]), in its symbols'
/// art too (which instances are written as); `None` when nothing changes.
/// `doc` with its geometry effects and group paint baked ([`vectorcraft_effects::bake_document`]),
/// in its symbols' art and its patterns' tile art too (which instances and pattern fills are
/// written as); `None` when nothing changes.
fn baked(doc: &Document) -> Option<Document> {
let baked = vectorcraft_effects::bake_document(doc);
let d = baked.as_ref().unwrap_or(doc);
if !d.symbols.iter().any(|s| vectorcraft_effects::needs_bake(&s.art)) {
let needs = vectorcraft_effects::needs_bake;
if !d.symbols.iter().any(|s| needs(&s.art)) && !d.patterns.iter().any(|p| p.art.iter().any(|a| needs(a))) {
return baked;
}
// The symbols' art baked as the layers of a copy of the document.
// The symbols' art, then each pattern's art, baked as the layers of a copy of the document.
let mut art = d.clone();
art.layers = d.symbols.iter().map(|s| Arc::new(Node::group(s.art.id, vec![s.art.clone()]))).collect();
let symbols = d.symbols.iter().map(|s| Arc::new(Node::group(s.art.id, vec![s.art.clone()])));
let patterns = d.patterns.iter().map(|p| Arc::new(Node::group(vectorcraft_doc::NodeId(0), p.art.clone())));
art.layers = symbols.chain(patterns).collect();
// The copy keeps what baking adds to the document (the images color adjustments make).
let Some(mut out) = vectorcraft_effects::bake_document(&art) else { return baked };
let arts: Vec<Option<Arc<Node>>> = out.layers.iter().map(|l| l.children().and_then(|c| c.first()).cloned()).collect();
out.layers = d.layers.clone();
for (s, a) in out.symbols.iter_mut().zip(arts) {
if let Some(a) = a {
let layers = std::mem::replace(&mut out.layers, d.layers.clone());
let mut members = layers.iter().map(|l| l.children().cloned().unwrap_or_default());
for s in out.symbols.iter_mut() {
if let Some(a) = members.next().and_then(|m| m.first().cloned()) {
s.art = a;
}
}
for p in out.patterns.iter_mut() {
if let Some(m) = members.next() {
p.art = m;
}
}
Some(out)
}
+21
View File
@@ -718,6 +718,27 @@ fn an_adjusted_image_in_a_symbol_is_written_in_its_adjusted_color() {
assert_ne!(fill, paint(&solid(Color::rgb(0.0, 0.0, 0.0))), "the image is adjusted");
}
#[test]
fn a_group_fill_in_a_pattern_tile_is_written() {
let mut doc = Document::new(200.0, 100.0);
let layer = doc.layers[0].id;
let dot = Node::path(doc.alloc_id(), shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), Appearance::default());
let mut group = Node::group(doc.alloc_id(), vec![Arc::new(dot)]);
group.appearance = Appearance::basic(solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0).into();
doc.patterns.push(vectorcraft_doc::PatternDef::new("Dots", vec![Arc::new(group)]));
let filled = Node::path(
doc.alloc_id(),
shapes::rectangle(Rect::new(20.0, 20.0, 40.0, 40.0)),
Appearance::basic(Paint::Pattern { pattern: "Dots".into(), xf: Affine::IDENTITY }, Paint::None, 0.0),
);
doc.insert(Some(layer), usize::MAX, filled).unwrap();
let back = read_back(&doc);
let mut all = vec![];
paths(back.layers[0].children().unwrap(), &mut all);
let red = paint(&solid(Color::rgb(1.0, 0.0, 0.0)));
assert!(all.iter().any(|p| p.appearance.items.iter().any(|i| paint(i.paint()) == red)), "the tiles keep their group's fill");
}
#[test]
fn effects_on_a_fill_are_reported_left_out() {
let mut doc = Document::new(200.0, 100.0);