mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 10:02:38 +00:00
Merge pull request #5786 from dotCooCoo/fix/ai-gradient-name-serial
.ai save: new gradient names continue from the last one given
This commit is contained in:
@@ -2259,6 +2259,58 @@ fn gradient_definitions_skip_the_names_of_swatches_and_patterns() {
|
||||
same_lines(&gradient_looks(&read_back(&doc)), &gradient_looks(&doc));
|
||||
}
|
||||
|
||||
/// Each search for a new gradient name starts after the last name given, so naming many gradients
|
||||
/// tries each name once.
|
||||
#[test]
|
||||
fn gradient_names_are_searched_from_the_last_one_given() {
|
||||
let mut taken: std::collections::HashSet<String> = ["Gradient 2", "Gradient 5"].map(String::from).into();
|
||||
let (mut next, mut tries) = (1, 0);
|
||||
let mut names = vec![];
|
||||
for _ in 0..1000 {
|
||||
let name = writer::free_gradient_name(&mut next, |name| {
|
||||
tries += 1;
|
||||
taken.contains(name)
|
||||
});
|
||||
taken.insert(name.clone());
|
||||
names.push(name);
|
||||
}
|
||||
assert_eq!(names[..4], ["Gradient", "Gradient 3", "Gradient 4", "Gradient 6"]);
|
||||
assert_eq!(names.last().map(String::as_str), Some("Gradient 1002"));
|
||||
assert_eq!(tries, 1002);
|
||||
assert_eq!(next, 1003);
|
||||
}
|
||||
|
||||
/// Many gradients that no swatch names each get their own name, in order, skipping the names of
|
||||
/// swatches and patterns, and the same names when the document is written again.
|
||||
#[test]
|
||||
fn many_gradient_definitions_take_distinct_names_in_order() {
|
||||
let mut doc = one_object(shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), vec![]);
|
||||
let check = square(&mut doc, Rect::new(0.0, 0.0, 5.0, 5.0), Color::BLACK);
|
||||
let mut taken = vectorcraft_doc::PatternDef::new("Gradient 7", vec![Arc::new(check)]);
|
||||
taken.tile = Rect::new(0.0, 0.0, 10.0, 10.0);
|
||||
doc.patterns.push(taken);
|
||||
swatches_plus(&mut doc, vec![Swatch { name: "Gradient 3".into(), paint: solid(Color::rgb(0.0, 0.5, 0.0)), global: false, spot: false }]);
|
||||
let layer = doc.layers[0].id;
|
||||
for i in 0..300u16 {
|
||||
let shade = f32::from(i) / 300.0;
|
||||
let g = ramp(GradientKind::Linear, &[(0.0, Color::rgb(shade, 0.0, 0.0), 1.0, 0.5), (1.0, Color::rgb(0.0, 0.0, 1.0), 1.0, 0.5)]);
|
||||
let x = f64::from(i % 20) * 10.0;
|
||||
let y = f64::from(i / 20) * 6.0;
|
||||
let n = painted(
|
||||
&mut doc,
|
||||
shapes::rectangle(Rect::new(x, y, x + 8.0, y + 5.0)),
|
||||
vec![AppearanceItem::Fill(FillLayer::new(gradient_paint(g, None, None)))],
|
||||
);
|
||||
doc.insert(Some(layer), usize::MAX, n).unwrap();
|
||||
}
|
||||
let text = program(&doc);
|
||||
let names: Vec<&str> = text.lines().filter_map(|l| l.strip_prefix("%AI5_BeginGradient: ")).collect();
|
||||
let expected: Vec<String> =
|
||||
std::iter::once("(Gradient)".to_string()).chain((2..=302).filter(|&i| i != 3 && i != 7).map(|i| format!("(Gradient {i})"))).collect();
|
||||
assert_eq!(names, expected);
|
||||
assert_eq!(text, program(&doc));
|
||||
}
|
||||
|
||||
/// A hard edge: a linear gradient whose stops all lie at one place is written from its last stop,
|
||||
/// and reads back with each stop on its side.
|
||||
#[test]
|
||||
|
||||
@@ -22,7 +22,7 @@ pub(super) use images::PAYLOAD;
|
||||
#[cfg(test)]
|
||||
pub(super) use images::{IMAGE_DEPTH, IMAGES, LINKS_EMBEDDED};
|
||||
#[cfg(test)]
|
||||
pub(super) use paints::{GRADIENT_MIX, GRADIENT_SHAPES, GRADIENTS, STOP_LINKS, SWATCH_PLACEMENT};
|
||||
pub(super) use paints::{GRADIENT_MIX, GRADIENT_SHAPES, GRADIENTS, STOP_LINKS, SWATCH_PLACEMENT, free_gradient_name};
|
||||
|
||||
/// How deep symbols, live objects and pattern tiles may nest in one another (deeper art is left
|
||||
/// out). Groups and layers need no limit of their own: a document is never deeper than
|
||||
@@ -141,6 +141,8 @@ pub(super) struct Writer<'a> {
|
||||
canvas: Affine,
|
||||
/// The gradient definitions, in the order they are first used.
|
||||
gradients: Vec<paints::GradientDef>,
|
||||
/// The number the next new gradient definition's name tries first (1 is `Gradient`).
|
||||
next_gradient: u64,
|
||||
/// The pattern definitions written, each after those its art uses.
|
||||
patterns: Vec<paints::PatternOut>,
|
||||
/// The patterns whose definitions are being written.
|
||||
@@ -167,6 +169,7 @@ impl<'a> Writer<'a> {
|
||||
tile_grad_space: Affine::translate((-origin.0, origin.1)),
|
||||
canvas: Affine::new([1.0, 0.0, 0.0, -1.0, origin.0, origin.1]) * to_art,
|
||||
gradients: vec![],
|
||||
next_gradient: 1,
|
||||
patterns: vec![],
|
||||
defining: vec![],
|
||||
in_palette: false,
|
||||
|
||||
@@ -49,6 +49,18 @@ fn writable_gradient(g: &Gradient) -> bool {
|
||||
g.kind != GradientKind::Freeform && !g.stops.is_empty() && g.stops.len() <= crate::import::MAX_GRADIENT_STOPS
|
||||
}
|
||||
|
||||
/// The first name of `Gradient`, `Gradient 2`, `Gradient 3`, … numbered `next` or higher for
|
||||
/// which `taken` returns false (`Gradient` is number 1); `next` is set past it.
|
||||
pub(in crate::ai_editing) fn free_gradient_name(next: &mut u64, mut taken: impl FnMut(&str) -> bool) -> String {
|
||||
loop {
|
||||
let name = if *next <= 1 { String::from("Gradient") } else { format!("Gradient {next}") };
|
||||
*next = next.saturating_add(1);
|
||||
if !taken(&name) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The stops of `g` in order of their place on the ramp.
|
||||
fn sorted_stops(g: &Gradient) -> Vec<&GradientStop> {
|
||||
let mut stops: Vec<&GradientStop> = g.stops.iter().collect();
|
||||
@@ -72,7 +84,8 @@ impl Writer<'_> {
|
||||
|
||||
/// The name of the definition of `g`: the swatch `swatch` names when it is a gradient swatch
|
||||
/// of the same gradient, else a definition of the same gradient that no swatch names, else a
|
||||
/// new one (`Gradient`, numbered when taken by another definition, a swatch or a pattern).
|
||||
/// new one ([`free_gradient_name`]: `Gradient`, numbered when taken by another definition, a
|
||||
/// swatch or a pattern).
|
||||
fn define_gradient(&mut self, g: &Gradient, swatch: Option<&str>) -> String {
|
||||
let used = !self.in_palette;
|
||||
let found = swatch
|
||||
@@ -82,16 +95,10 @@ impl Writer<'_> {
|
||||
d.used |= used;
|
||||
return d.name.clone();
|
||||
}
|
||||
let doc = self.doc;
|
||||
let taken = |gradients: &[GradientDef], name: &str| {
|
||||
let (doc, gradients) = (self.doc, &self.gradients);
|
||||
let name = free_gradient_name(&mut self.next_gradient, |name| {
|
||||
gradients.iter().any(|d| d.name == name) || doc.swatch(name).is_some() || doc.pattern(name).is_some()
|
||||
};
|
||||
let mut name = String::from("Gradient");
|
||||
let mut i = 1;
|
||||
while taken(&self.gradients, &name) {
|
||||
i += 1;
|
||||
name = format!("Gradient {i}");
|
||||
}
|
||||
});
|
||||
self.gradients.push(GradientDef { name: name.clone(), gradient: g.clone(), swatch: false, used });
|
||||
name
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user