Merge pull request #6219 from dotCooCoo/fix/ai-gradient-swatch-self-link

.ai import: a palette gradient swatch's paint is not linked to itself
This commit is contained in:
BFlat
2026-11-09 02:06:03 +08:00
committed by GitHub
5 changed files with 73 additions and 5 deletions
+31
View File
@@ -2047,6 +2047,37 @@ fn gradients_and_patterns_are_read_back() {
same_lines(&gradient_looks(&read_back(&doc)), &gradient_looks(&doc));
}
/// A new document's gradient swatches read back with the paint they were written with, not linked
/// to a swatch, and art filled with one of them reads back linked to it.
#[test]
fn gradient_swatches_read_back_unlinked() {
let mut doc = Document::new(200.0, 100.0);
let Some(Paint::Gradient(sunset)) = doc.swatch("Sunset").map(|s| s.paint.clone()) else { panic!("no Sunset swatch") };
let layer = doc.layers[0].id;
let linked = gradient_paint(sunset.gradient.clone(), None, Some("Sunset"));
let rect = painted(&mut doc, shapes::rectangle(Rect::new(10.0, 10.0, 90.0, 50.0)), vec![AppearanceItem::Fill(FillLayer::new(linked))]);
doc.insert(Some(layer), usize::MAX, rect).unwrap();
let back = read_back(&doc);
let gradients = |d: &Document| -> Vec<String> {
d.swatches_iter()
.filter_map(|s| match &s.paint {
Paint::Gradient(g) => {
Some(format!("{} {:?} stops={} geom={:?} swatch={:?}", s.name, g.gradient.kind, g.gradient.stops.len(), g.geom, g.swatch))
}
_ => None,
})
.collect()
};
let want = gradients(&doc);
assert_eq!(want.len(), 4, "{want:?}");
assert_eq!(gradients(&back), want);
let link = back.layers[0].children().and_then(|c| c.first()).map(|n| match n.appearance.items.first().map(AppearanceItem::paint) {
Some(Paint::Gradient(g)) => g.swatch.clone(),
other => panic!("{other:?}"),
});
assert_eq!(link, Some(Some("Sunset".to_string())));
}
#[test]
fn gradients_and_patterns_write_the_same_bytes_again() {
let doc = gradients_and_patterns();
+3 -2
View File
@@ -820,12 +820,13 @@ impl<'a> Reader<'a> {
let (paint, global, spot) = match e.cell {
Cell::Color { color, global, spot } => (Paint::solid(color), global, spot),
// A swatch named by its gradient or pattern takes the name that got in the document.
// A gradient swatch's own paint does not link to a swatch; art painted with the
// gradient links to it.
Cell::Gradient(g) => {
let mut gp = vectorcraft_color::gradient::GradientPaint::new(self.gradients.get(&g)?.clone());
let gp = vectorcraft_color::gradient::GradientPaint::new(self.gradients.get(&g)?.clone());
if e.name == g {
e.name = self.global_names.get(Global::Gradient, &g).to_string();
}
gp.swatch = Some(e.name.clone());
(Paint::Gradient(Box::new(gp)), false, false)
}
Cell::Pattern(p) => {
+20
View File
@@ -906,6 +906,26 @@ fn a_color_named_as_a_palette_gradient_or_pattern_is_numbered() {
assert_eq!(links, [Some("Brand 3".to_string()), Some("Brand 2".to_string())]);
}
/// A palette's gradient swatch holds its gradient without a link to a swatch, whether it is named
/// by its gradient or has a name of its own, and the art filled with the gradient links to the
/// swatch named by the gradient.
#[test]
fn a_palette_gradient_swatch_is_not_linked_to_itself() {
let gradient = "%AI5_BeginGradient: (Sky)\n(Sky) 0 2 Bd\n[\n%_1 0 50 0 Bs\n%_0 0 50 100 Bs\nBD\n%AI5_EndGradient\n";
let palette = "%AI5_BeginPalette\n0 0 Pb\nBb\n2 (Sky) 0 0 0 1 1 0 0 1 0 0 1 Bg\n0 BB\n(Sky)\nPc\n\
Bb\n2 (Sky) 0 0 0 1 1 0 0 1 0 0 1 Bg\n0 BB\n(Brand)\nPc\nPB\n%AI5_EndPalette\n";
let filled = format!("Bb\n1 (Sky) 0 0 0 10 1 0 0 1 0 0 Bg\n{}f\n0 BB\n", rect_path(0.0, 0.0, 10.0, 10.0));
let doc = read_ok(&format!("{gradient}{palette}{}", layer(&filled))).doc;
for name in ["Sky", "Brand"] {
let sw = doc.swatch(name).unwrap_or_else(|| panic!("no {name} swatch"));
let Paint::Gradient(g) = &sw.paint else { panic!("{sw:?}") };
assert_eq!(g.swatch, None, "{name}'s own paint");
assert_eq!(g.geom, None);
assert_eq!(g.gradient.stops.len(), 2);
}
assert!(matches!(fill(&art(&doc)[0]), Paint::Gradient(g) if g.swatch.as_deref() == Some("Sky")));
}
/// A palette's pattern swatch named by its pattern takes the name the pattern is shown with.
#[test]
fn a_palette_pattern_swatch_takes_its_patterns_shown_name() {
+3 -3
View File
@@ -444,7 +444,7 @@ fn a_version_7_palette_keeps_its_swatches_without_names() {
let spot = d.swatch("Spot A").unwrap();
assert!(spot.spot && spot.global, "{spot:?}");
assert!(d.swatches.iter().filter(|s| s.name != "Spot A").all(|s| !s.spot && !s.global));
assert!(matches!(&d.swatch("B&W").unwrap().paint, Paint::Gradient(g) if g.swatch.as_deref() == Some("B&W")));
assert!(matches!(&d.swatch("B&W").unwrap().paint, Paint::Gradient(g) if g.swatch.is_none()));
assert_eq!(d.swatch("Gray K=0").unwrap().paint.color(), Some(Color::gray(0.0)), "1 g is white");
}
@@ -481,8 +481,8 @@ fn gradient_names_are_shown_up_to_their_newline() {
let names: Vec<&str> = d.swatches.iter().map(|s| s.name.as_str()).collect();
let expected: Vec<&str> = ["[None]"].into_iter().chain((!own.is_empty()).then_some("Sky")).chain([first, second]).collect();
assert_eq!(names, expected, "{:?}", r.warnings);
assert_eq!(first_stop(&d.swatch(first).unwrap().paint), (Some(first.into()), Some(Color::gray(0.0))));
assert_eq!(first_stop(&d.swatch(second).unwrap().paint), (Some(second.into()), Some(Color::gray(0.5))));
assert_eq!(first_stop(&d.swatch(first).unwrap().paint), (None, Some(Color::gray(0.0))));
assert_eq!(first_stop(&d.swatch(second).unwrap().paint), (None, Some(Color::gray(0.5))));
let art = d.layers[0].children().unwrap();
let fills: Vec<_> = art.iter().map(|n| first_stop(&n.appearance.fill().unwrap().paint)).collect();
assert_eq!(fills, [(Some(second.into()), Some(Color::gray(0.5))), (Some(first.into()), Some(Color::gray(0.0)))]);
+16
View File
@@ -1331,6 +1331,22 @@ fn an_ai_file_that_comes_in_as_its_pdf_part_keeps_the_swatches_of_its_palette()
assert!(d.swatch("Red ink").is_some_and(|s| s.spot));
}
/// A `.ai` gradient library (a palette of gradient swatches and no layers) gets the palette's
/// gradient swatches, each holding its gradient without a link to a swatch.
#[test]
fn an_ai_file_without_layers_keeps_the_gradients_of_its_palette_unlinked() {
let sky = "%AI5_BeginGradient: (Sky)\n(Sky) 0 2 Bd\n[\n%_1 0 50 0 Bs\n%_0 0 50 100 Bs\nBD\n%AI5_EndGradient\n";
let palette = "%AI5_BeginPalette\n0 0 Pb\nBb\n2 (Sky) 0 0 0 1 1 0 0 1 0 0 1 Bg\n0 BB\n(Sky)\nPc\nPB\n%AI5_EndPalette\n";
let art = native(&format!("{sky}{palette}"));
let (d, warnings, read) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, true, 1.0);
assert!(!read, "{warnings:?}");
let swatches: Vec<&str> = d.swatches.iter().map(|s| s.name.as_str()).collect();
assert_eq!(swatches, ["[None]", "Sky"], "{warnings:?}");
let Some(vectorcraft_color::Paint::Gradient(g)) = d.swatch("Sky").map(|s| &s.paint) else { panic!("{:?}", d.swatch("Sky")) };
assert_eq!(g.swatch, None, "the swatch's own paint");
assert_eq!(g.gradient.stops.len(), 2);
}
/// A `.ai` pattern library (a palette of pattern swatches and no layers) whose PDF part defines no
/// pattern gets the patterns its editing data defines with the palette's pattern swatches.
#[test]