Document Info: Spot Color Objects lists the objects that use spot colors

The spotColors category is now titled Spot Color Objects and lists each
object filled or stroked with a spot color, by name with the spot colors
it uses, instead of the spot swatch names. A solid color or a gradient
stop linked to a spot swatch at a tint above 0 counts; process colors
that look the same and Registration do not. Paint that draws nothing is
left out by the rules Pattern Objects and Gradient Objects follow, and
type whose characters use a spot color is listed. Objects counts the
listed objects as Spot Color Objects.

document.info returns spotObjects, spotColorNames and objects.spots;
spotColors now holds the spot colors those objects use. The report that
Document Info > Save... and Package write lists the same rows.

The Spot Color Objects, Pattern Objects and Gradient Objects titles have
a row in each of the 27 complete catalogs, and a test checks that every
complete catalog has the three.

Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-11-12 00:59:50 -08:00
parent eb608bb8b5
commit 1d7690029f
33 changed files with 352 additions and 53 deletions
+227 -50
View File
@@ -18,7 +18,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Document Info",
["Window", "Document Info"],
None,
"{selectionOnly?, category?: document|objects|graphicStyles|brushes|spotColors|patterns|gradients|symbols|fonts|fontDetails|linkedImages|embeddedImages|repeats (only that one in sections and the text), format?: \"text\" (→ {text}: the report Document Info › Save… and Package write)} → {document, objects: {paths, compoundPaths, groups, …}, pathLength: pt (the paths' total length, compound paths' included, guides left out), fonts, images, swatches, graphicStyleNames (with selectionOnly: the styles the selected objects are linked to), brushNames (the Brushes panel's brushes; with selectionOnly: the brushes the selected objects' strokes use), repeatObjects: [{id, name, type: radial|grid|mirror, instances, rows?, cols?}] (the Repeat objects, those in groups and other repeats included; with selectionOnly: those in the selection), patternObjects: [{id, name, patterns}] (the objects filled or stroked with a pattern, each once, those in groups and type whose drawn characters use one included, hidden fills and strokes, strokes with no width, guides, compound path members' own fills and strokes, images' fills and strokes without a shape and characters that draw nothing left out, with the patterns they use, which objects.patterns counts; with selectionOnly: those in the selection), patternNames (the patterns those objects use), gradientObjects: [{id, name, gradients}] (the objects filled or stroked with a gradient, by the same rules, with the gradients they use: a linked gradient swatch's name, else Linear gradient, Radial gradient or Freeform gradient; objects.gradients counts them), gradientNames (the gradients those objects use), …, sections: [{id, title, rows: [[label, value]]}] (the panel's categories; fontDetails: each font's file and whether its license lets it be embedded)}. An image linked to a file outside the automation read roots has link null, and its Linked Images row names the file alone",
"{selectionOnly?, category?: document|objects|graphicStyles|brushes|spotColors|patterns|gradients|symbols|fonts|fontDetails|linkedImages|embeddedImages|repeats (only that one in sections and the text), format?: \"text\" (→ {text}: the report Document Info › Save… and Package write)} → {document, objects: {paths, compoundPaths, groups, …}, pathLength: pt (the paths' total length, compound paths' included, guides left out), fonts, images, swatches, graphicStyleNames (with selectionOnly: the styles the selected objects are linked to), brushNames (the Brushes panel's brushes; with selectionOnly: the brushes the selected objects' strokes use), repeatObjects: [{id, name, type: radial|grid|mirror, instances, rows?, cols?}] (the Repeat objects, those in groups and other repeats included; with selectionOnly: those in the selection), spotObjects: [{id, name, spotColors}] (the objects filled or stroked with a spot color swatch, as a solid color or in a gradient's stops, at a tint above 0, each once, by the rules patternObjects follows, with the spot colors they use; Registration and process colors are not spot colors; objects.spots counts them), spotColorNames and spotColors (the spot colors those objects use), patternObjects: [{id, name, patterns}] (the objects filled or stroked with a pattern, each once, those in groups and type whose drawn characters use one included, hidden fills and strokes, strokes with no width, guides, compound path members' own fills and strokes, images' fills and strokes without a shape and characters that draw nothing left out, with the patterns they use, which objects.patterns counts; with selectionOnly: those in the selection), patternNames (the patterns those objects use), gradientObjects: [{id, name, gradients}] (the objects filled or stroked with a gradient, by the same rules, with the gradients they use: a linked gradient swatch's name, else Linear gradient, Radial gradient or Freeform gradient; objects.gradients counts them), gradientNames (the gradients those objects use), …, sections: [{id, title, rows: [[label, value]]}] (the panel's categories; fontDetails: each font's file and whether its license lets it be embedded)}. An image linked to a file outside the automation read roots has link null, and its Linked Images row names the file alone",
has_doc,
info
),
@@ -40,7 +40,7 @@ pub const CATEGORIES: [(&str, &str); 13] = [
("objects", "Objects"),
("graphicStyles", "Graphic Styles"),
("brushes", "Brushes"),
("spotColors", "Spot Colors"),
("spotColors", "Spot Color Objects"),
("patterns", "Pattern Objects"),
("gradients", "Gradient Objects"),
("symbols", "Symbols"),
@@ -52,7 +52,7 @@ pub const CATEGORIES: [(&str, &str); 13] = [
];
/// The object counts of `document.info` and their labels.
const OBJECT_LABELS: [(&str, &str); 18] = [
const OBJECT_LABELS: [(&str, &str); 19] = [
("paths", "Paths"),
("compoundPaths", "Compound Paths"),
("groups", "Groups"),
@@ -63,6 +63,7 @@ const OBJECT_LABELS: [(&str, &str); 18] = [
("symbolInstances", "Symbol Instances"),
("gradients", "Gradient Objects"),
("patterns", "Pattern Objects"),
("spots", "Spot Color Objects"),
("meshes", "Gradient Meshes"),
("blends", "Blends"),
("envelopes", "Envelopes"),
@@ -131,7 +132,7 @@ fn repeat_entry(d: &Document, n: &Node, spec: &RepeatSpec) -> (Value, (String, S
/// The Document Info categories of `d` (only `category` when given) from `info` (what
/// `document.info` reports), its fonts, its image objects, the rows of its Repeat objects and the
/// rows of its objects filled or stroked with a pattern and with a gradient.
/// rows of its objects filled or stroked with a spot color, with a pattern and with a gradient.
#[allow(clippy::too_many_arguments)]
fn sections(
d: &Document,
@@ -139,6 +140,7 @@ fn sections(
fonts: &BTreeSet<super::fonts::UsedFont>,
images: &[ImageRow],
repeats: &[(String, String)],
spot_objects: &[(String, String)],
pattern_objects: &[(String, String)],
gradient_objects: &[(String, String)],
category: Option<&str>,
@@ -183,7 +185,7 @@ fn sections(
}
"graphicStyles" => names("graphicStyleNames"),
"brushes" => names("brushNames"),
"spotColors" => names("spotColors"),
"spotColors" => spot_objects.to_vec(),
"patterns" => pattern_objects.to_vec(),
"gradients" => gradient_objects.to_vec(),
"symbols" => names("symbols"),
@@ -273,15 +275,50 @@ fn gradient_label(p: &Paint) -> Option<String> {
}
}
/// The type object `n` holds and the runs its layout draws, when it uses a pattern or a gradient:
/// the runs with a glyph outline (list markers and ruby included) or an underline or strikethrough
/// bar. Spaces and line breaks draw no outline. A type object's characters carry their own fill and
/// stroke, and the type object's own fills and strokes paint on its characters, so only those runs
/// paint. The type is laid out with its inline graphics' bounds resolved from `d`'s symbols, as the
/// renderer lays it out.
fn painted_runs<'a>(d: &Document, n: &'a Node) -> Option<(&'a vectorcraft_doc::TextObject, BTreeSet<usize>)> {
/// The names of `d`'s spot color swatches: the swatches whose prints go to a plate of their own
/// ([`vectorcraft_doc::inks::inks`]). A name held by several swatches is the first one's, as
/// [`Document::swatch`] finds it, and the Registration swatch prints on every plate, so it is not
/// one.
fn spot_swatches(d: &Document) -> HashSet<&str> {
let mut first: BTreeMap<&str, bool> = BTreeMap::new();
for s in d.swatches_iter() {
first.entry(s.name.as_str()).or_insert(s.spot && s.paint.color().is_some());
}
first.into_iter().filter(|&(name, spot)| spot && name != vectorcraft_color::swatch::REGISTRATION).map(|(name, _)| name).collect()
}
/// The spot colors paint `p` prints (`spots`: [`spot_swatches`]): the spot swatch a solid color is
/// linked to, or those a gradient's stops are linked to, each once, in the order of the stops. A
/// link at a tint of 0 prints no ink, and a color that only looks like a spot color is not linked
/// to one.
fn spot_labels(spots: &HashSet<&str>, p: &Paint) -> Vec<String> {
let mut out: Vec<String> = vec![];
let mut add = |swatch: &Option<String>, tint: f32| {
if let Some(name) = swatch
&& tint > 0.0
&& spots.contains(name.as_str())
&& !out.contains(name)
{
out.push(name.clone());
}
};
match p {
Paint::Solid { swatch, tint, .. } => add(swatch, *tint),
Paint::Gradient(g) => g.gradient.stops.iter().for_each(|s| add(&s.swatch, s.tint)),
_ => {}
}
out
}
/// The type object `n` holds and the runs its layout draws, when it uses a spot color, a pattern or
/// a gradient (`spots`: [`spot_swatches`]): the runs with a glyph outline (list markers and ruby
/// included) or an underline or strikethrough bar. Spaces and line breaks draw no outline. A type
/// object's characters carry their own fill and stroke, and the type object's own fills and strokes
/// paint on its characters, so only those runs paint. The type is laid out with its inline
/// graphics' bounds resolved from `d`'s symbols, as the renderer lays it out.
fn painted_runs<'a>(d: &Document, spots: &HashSet<&str>, n: &'a Node) -> Option<(&'a vectorcraft_doc::TextObject, BTreeSet<usize>)> {
let NodeKind::Text(t) = &n.kind else { return None };
let is_listed = |p: &Paint| matches!(p, Paint::Pattern { .. } | Paint::Gradient(_));
let is_listed = |p: &Paint| matches!(p, Paint::Pattern { .. } | Paint::Gradient(_)) || !spot_labels(spots, p).is_empty();
let own = n.appearance.items.iter().any(|i| match i {
vectorcraft_doc::AppearanceItem::Fill(f) => is_listed(&f.paint),
vectorcraft_doc::AppearanceItem::Stroke(s) => is_listed(&s.paint),
@@ -297,43 +334,53 @@ fn painted_runs<'a>(d: &Document, n: &'a Node) -> Option<(&'a vectorcraft_doc::T
Some((t, painted))
}
/// The patterns or gradients (`label`: [`pattern_label`] or [`gradient_label`]) that `n`'s own
/// fills and strokes paint, each once. A hidden fill or stroke and a stroke with no width paint
/// nothing, and neither does any fill or stroke of a guide (drawn in the guide color), of a compound
/// path's member (`members`: the compound path's own fills and strokes paint it) or of type whose
/// layout draws nothing (`painted_runs`). An image's fills and strokes paint only where an effect
/// gives them a shape.
/// The names a paint is listed by in a Document Info category: its spot colors, its pattern or its
/// gradient.
type PaintNames<'a> = dyn Fn(&Paint) -> Vec<String> + 'a;
/// The spot colors, patterns or gradients (`label`: [`spot_labels`], [`pattern_label`] or
/// [`gradient_label`], each paint's names) that `n`'s own fills and strokes paint, each once. A
/// hidden fill or stroke and a stroke with no width paint nothing, and neither does any fill or
/// stroke of a guide (drawn in the guide color), of a compound path's member (`members`: the
/// compound path's own fills and strokes paint it) or of type whose layout draws nothing
/// (`painted_runs`). An image's fills and strokes paint only where an effect gives them a shape.
fn own_paints(
d: &Document,
n: &Node,
members: &HashSet<*const Node>,
painted_runs: Option<&(&vectorcraft_doc::TextObject, BTreeSet<usize>)>,
label: fn(&Paint) -> Option<String>,
label: &PaintNames<'_>,
) -> Vec<String> {
if matches!(n.kind, NodeKind::Path { guide: true, .. })
|| members.contains(&std::ptr::from_ref(n))
|| painted_runs.is_some_and(|(_, r)| r.is_empty())
|| !n.appearance.items.iter().any(|i| label(i.paint()).is_some())
|| n.appearance.items.iter().all(|i| label(i.paint()).is_empty())
{
return vec![];
}
// The patterns or gradients an image's shaped fills and strokes paint (their effects can
// transform the paint, so they are matched by label).
// The names an image's shaped fills and strokes paint (their effects can transform the paint,
// so they are matched by name).
let shaped: Option<Vec<String>> = matches!(n.kind, NodeKind::Image(_))
.then(|| vectorcraft_render::effects::image_paint(Some(d), n).into_iter().filter_map(|(_, _, item)| label(item.paint())).collect());
.then(|| vectorcraft_render::effects::image_paint(Some(d), n).into_iter().flat_map(|(_, _, item)| label(item.paint())).collect());
let mut out: Vec<String> = vec![];
for item in &n.appearance.items {
let Some(name) = label(item.paint()) else { continue };
let paints = item.visible()
&& !matches!(item, vectorcraft_doc::AppearanceItem::Stroke(s) if s.width <= 0.0)
&& shaped.as_ref().is_none_or(|s| s.contains(&name));
if paints && !out.contains(&name) {
out.push(name);
if !item.visible() || matches!(item, vectorcraft_doc::AppearanceItem::Stroke(s) if s.width <= 0.0) {
continue;
}
for name in label(item.paint()) {
if shaped.as_ref().is_none_or(|s| s.contains(&name)) && !out.contains(&name) {
out.push(name);
}
}
}
out
}
/// [`pattern_label`] or [`gradient_label`] as the names [`own_paints`] takes.
fn one_label(label: fn(&Paint) -> Option<String>) -> impl Fn(&Paint) -> Vec<String> {
move |p| label(p).into_iter().collect()
}
fn info(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "document.info";
let category = str_param(p, "category");
@@ -370,7 +417,13 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
};
let mut patterns = BTreeSet::new();
let mut symbols = BTreeSet::new();
// `spot_objects`, `spot_rows` and `spot` hold the `spotObjects` entries, the rows of the objects filled or stroked with a spot
// color, in walk order, and the spot colors they use. `spot_swatches` names the document's spot colors once, for every paint.
let spot_swatches = spot_swatches(d);
let mut spot_objects: Vec<Value> = vec![];
let mut spot_rows: Vec<(String, String)> = vec![];
let mut spot = BTreeSet::new();
let (spot_label, pattern, gradient) = (|p: &Paint| spot_labels(&spot_swatches, p), one_label(pattern_label), one_label(gradient_label));
let mut styles: Vec<&str> = if selection_only { vec![] } else { d.graphic_styles.iter().map(|g| g.name.as_str()).collect() };
// `brushes` holds the Brushes panel's brushes, or with selectionOnly it holds the brushes that the selected objects' strokes use.
let mut brushes: Vec<String> = if selection_only { vec![] } else { vectorcraft_brush::library(d).into_iter().map(|b| b.name).collect() };
@@ -446,17 +499,11 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(k) = k {
*counts.entry(k).or_default() += 1;
}
for item in &n.appearance.items {
if let Paint::Solid { swatch: Some(name), .. } = item.paint()
&& d.swatch(name).is_some_and(|s| s.spot)
{
spot.insert(name.clone());
}
}
// The patterns and the gradients this object's fills and strokes paint, each once.
let painted_runs = painted_runs(d, n);
let mut used_patterns = own_paints(d, n, &members, painted_runs.as_ref(), pattern_label);
let mut used_gradients = own_paints(d, n, &members, painted_runs.as_ref(), gradient_label);
// The spot colors, the patterns and the gradients this object's fills and strokes paint, each once.
let painted_runs = painted_runs(d, &spot_swatches, n);
let mut used_spots = own_paints(d, n, &members, painted_runs.as_ref(), &spot_label);
let mut used_patterns = own_paints(d, n, &members, painted_runs.as_ref(), &pattern);
let mut used_gradients = own_paints(d, n, &members, painted_runs.as_ref(), &gradient);
// The characters' own paints: an inline graphic draws no outline and does not take its
// run's paint (a list marker set in its run does), and a stroke with no width paints
// nothing.
@@ -464,19 +511,26 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
for run in painted.iter().filter_map(|i| t.runs.get(*i)) {
let stroke = (run.style.stroke_width > 0.0).then_some(&run.style.stroke);
for paint in std::iter::once(&run.style.fill).chain(stroke) {
for (used, label) in
[(&mut used_patterns, pattern_label as fn(&Paint) -> Option<String>), (&mut used_gradients, gradient_label)]
{
if let Some(name) = label(paint)
&& !used.contains(&name)
{
used.push(name);
let labels: [(&mut Vec<String>, &PaintNames<'_>); 3] =
[(&mut used_spots, &spot_label), (&mut used_patterns, &pattern), (&mut used_gradients, &gradient)];
for (used, label) in labels {
for name in label(paint) {
if !used.contains(&name) {
used.push(name);
}
}
}
}
}
}
// Objects counts the objects that Pattern Objects and Gradient Objects list.
// Objects counts the objects that Spot Color Objects, Pattern Objects and Gradient Objects list.
if !used_spots.is_empty() {
let name = n.display_name();
spot.extend(used_spots.iter().cloned());
spot_rows.push((name.clone(), used_spots.join(", ")));
spot_objects.push(json!({ "id": n.id.0, "name": name, "spotColors": used_spots }));
*counts.entry("spots").or_default() += 1;
}
if !used_patterns.is_empty() {
let name = n.display_name();
patterns.extend(used_patterns.iter().cloned());
@@ -529,6 +583,8 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
"images": images,
"symbols": symbols,
"spotColors": spot,
"spotColorNames": spot,
"spotObjects": spot_objects,
"swatches": d.swatches_iter().count(),
"graphicStyles": d.graphic_styles.len(),
"graphicStyleNames": styles,
@@ -544,7 +600,7 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
});
// The document's name: its file's, else its title.
out["document"]["name"] = json!(st.title());
let sections = sections(d, &out, &fonts, &image_rows, &repeat_rows, &pattern_rows, &gradient_rows, category);
let sections = sections(d, &out, &fonts, &image_rows, &repeat_rows, &spot_rows, &pattern_rows, &gradient_rows, category);
if text {
return Ok(json!({ "text": report_text(&st.title(), selection_only, &sections) }));
}
@@ -997,6 +1053,127 @@ mod tests {
assert!(!text.contains("Spare"), "{text}");
}
/// Document Info › Spot Color Objects lists each object filled or stroked with a spot color once,
/// with the spot colors it uses, as a solid color or in a gradient's stops, one in a group and a
/// type object whose characters use one included. A spot swatch no art uses adds no row; a
/// process color that looks the same, Registration and a tint of 0 are not spot colors; paint
/// that draws nothing is left out by the rules Pattern Objects follows; and Objects counts the
/// listed objects. With Selection Only it lists the objects in the selection, each once, and the
/// report writes the same rows.
#[test]
fn info_lists_the_spot_color_objects() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
// An empty document lists none.
let i = s.execute("document.info", &json!({"category": "spotColors"})).unwrap();
assert_eq!((i["spotObjects"].clone(), i["spotColorNames"].clone(), i["spotColors"].clone()), (json!([]), json!([]), json!([])));
assert!(i["objects"]["spots"].is_null() && i["sections"][0]["rows"] == json!([]), "{i}");
let text = s.execute("document.info", &json!({"category": "spotColors", "format": "text"})).unwrap()["text"].as_str().unwrap().to_string();
assert!(text.contains("\nSPOT COLOR OBJECTS\nNone\n"), "{text}");
let ink = |c: f64| json!({"model": "cmyk", "c": c, "m": 0.5, "y": 0.0, "k": 0.0});
s.execute("swatch.new", &json!({"name": "Ink", "color": ink(1.0), "spot": true})).unwrap();
s.execute("swatch.new", &json!({"name": "Gold", "color": ink(0.2), "spot": true})).unwrap();
s.execute("swatch.new", &json!({"name": "Spare", "color": ink(0.4), "spot": true})).unwrap();
// A global process color of the same color as Ink.
s.execute("swatch.new", &json!({"name": "Look", "color": ink(1.0), "global": true})).unwrap();
let square =
|s: &mut Session, x: f64| s.execute("shape.rectangle", &json!({"x": x, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone();
let ramp = |a: &str, b: &str| json!({"kind": "linear", "stops": [{"offset": 0, "color": "#000", "swatch": a}, {"offset": 1, "color": "#fff", "swatch": b}]});
let (a, b, c, f, g, h) =
(square(&mut s, 0.0), square(&mut s, 20.0), square(&mut s, 40.0), square(&mut s, 60.0), square(&mut s, 80.0), square(&mut s, 100.0));
s.execute("paint.setFill", &json!({"ids": [a], "swatch": "Ink"})).unwrap();
s.execute("paint.setStroke", &json!({"ids": [a], "swatch": "Gold", "tint": 40})).unwrap();
s.execute("layer.setProps", &json!({"id": a, "name": "Both"})).unwrap();
s.execute("paint.setFill", &json!({"ids": [b], "swatch": "Look"})).unwrap();
s.execute("paint.setStroke", &json!({"ids": [b], "swatch": "Ink", "tint": 50})).unwrap();
s.execute("paint.setFill", &json!({"ids": [c], "swatch": "Ink", "tint": 0})).unwrap();
s.execute("paint.setFill", &json!({"ids": [f], "gradient": ramp("Gold", "Look")})).unwrap();
s.execute("paint.setFill", &json!({"ids": [g], "gradient": ramp("Look", "Look")})).unwrap();
s.execute("paint.setFill", &json!({"ids": [h], "swatch": "[Registration]"})).unwrap();
for id in [&c, &g, &h] {
s.execute("paint.setStroke", &json!({"ids": [id], "none": true})).unwrap();
}
s.execute("select.set", &json!({"ids": [b, c]})).unwrap();
let group = s.execute("object.group", &json!({})).unwrap()["id"].clone();
// A type object whose characters are filled with a spot color.
let t = s.execute("text.create", &json!({"x": 0, "y": 200, "text": "Hi"})).unwrap()["id"].clone();
s.execute("paint.setFill", &json!({"ids": [t], "swatch": "Ink"})).unwrap();
s.execute("paint.setStroke", &json!({"ids": [t], "none": true})).unwrap();
s.execute("layer.setProps", &json!({"id": t, "name": "Type"})).unwrap();
// A hidden spot fill and a spot stroke with no width paint nothing, so neither object is listed.
let (d, e) = (square(&mut s, 120.0), square(&mut s, 140.0));
s.execute("paint.setStroke", &json!({"ids": [d], "none": true})).unwrap();
s.execute("paint.setFill", &json!({"ids": [d], "swatch": "Ink"})).unwrap();
s.execute("appearance.setItem", &json!({"ids": [d], "index": 0, "visible": false})).unwrap();
s.execute("paint.setFill", &json!({"ids": [e], "none": true})).unwrap();
s.execute("paint.setStroke", &json!({"ids": [e], "swatch": "Ink"})).unwrap();
s.execute("appearance.setItem", &json!({"ids": [e], "index": 1, "weight": 0})).unwrap();
// Type with no characters, or of only spaces and a line break, paints nothing, and neither
// does a guide made from a path with a spot color.
let empty = s.execute("text.create", &json!({"x": 0, "y": 300, "text": ""})).unwrap()["id"].clone();
s.execute("paint.setFill", &json!({"ids": [empty], "swatch": "Ink"})).unwrap();
s.execute("appearance.addFill", &json!({"ids": [empty]})).unwrap();
let blank = s.execute("text.create", &json!({"x": 0, "y": 320, "text": " \n"})).unwrap()["id"].clone();
s.execute("paint.setFill", &json!({"ids": [blank], "swatch": "Ink"})).unwrap();
let guide = square(&mut s, 300.0);
s.execute("paint.setFill", &json!({"ids": [guide], "swatch": "Ink"})).unwrap();
s.execute("select.set", &json!({"ids": [guide]})).unwrap();
s.execute("view.guides.make", &json!({})).unwrap();
// Nor does a compound path's member's own spot fill, or an image's spot fill without an
// effect that gives it a shape.
let (m1, m2) = (square(&mut s, 320.0), square(&mut s, 340.0));
s.execute("select.set", &json!({"ids": [m1, m2]})).unwrap();
let compound = s.execute("object.compoundPath.make", &json!({})).unwrap()["id"].clone();
s.execute("paint.setFill", &json!({"ids": [compound], "color": "#000000"})).unwrap();
s.execute("paint.setStroke", &json!({"ids": [compound], "none": true})).unwrap();
let node = |s: &Session, id: &Value| s.doc().unwrap().doc.node(vectorcraft_doc::NodeId(id.as_u64().unwrap())).unwrap().clone();
let (member, paint) = (node(&s, &compound).children().unwrap()[0].id, node(&s, &a).appearance);
let pic = square(&mut s, 360.0);
s.execute("select.set", &json!({"ids": [pic]})).unwrap();
s.execute("object.rasterize", &json!({"ppi": 72})).unwrap();
let image = s.doc().unwrap().selection.objects[0];
s.edit("own paint", |d, _| {
d.node_mut(member).unwrap().appearance = paint.clone();
d.node_mut(image).unwrap().appearance = paint;
Ok(())
})
.unwrap();
let i = s.execute("document.info", &json!({})).unwrap();
assert_eq!(
i["spotObjects"],
json!([
{"id": a, "name": "Both", "spotColors": ["Ink", "Gold"]},
{"id": b, "name": "<Rectangle>", "spotColors": ["Ink"]},
{"id": f, "name": "<Rectangle>", "spotColors": ["Gold"]},
{"id": t, "name": "Type", "spotColors": ["Ink"]},
])
);
assert_eq!((i["spotColorNames"].clone(), i["spotColors"].clone()), (json!(["Gold", "Ink"]), json!(["Gold", "Ink"])));
// Objects counts the objects that Spot Color Objects lists.
assert_eq!(i["objects"]["spots"].as_u64(), Some(4), "{}", i["objects"]);
let objects = s.execute("document.info", &json!({"category": "objects"})).unwrap()["sections"][0]["rows"].clone();
assert!(objects.as_array().unwrap().contains(&json!(["Spot Color Objects", "4"])), "{objects}");
s.execute("select.set", &json!({"ids": [c, g, h, d, e, empty, blank, guide, compound, image.0]})).unwrap();
let i = s.execute("document.info", &json!({"selectionOnly": true})).unwrap();
assert!(i["objects"]["spots"].is_null() && i["spotObjects"] == json!([]) && i["spotColors"] == json!([]), "{i}");
let i = s.execute("document.info", &json!({"category": "spotColors"})).unwrap();
assert_eq!((i["sections"][0]["id"].as_str(), i["sections"][0]["title"].as_str()), (Some("spotColors"), Some("Spot Color Objects")));
assert_eq!(i["sections"][0]["rows"], json!([["Both", "Ink, Gold"], ["<Rectangle>", "Ink"], ["<Rectangle>", "Gold"], ["Type", "Ink"]]));
// A selection that holds the group and the object inside it lists that object once.
for ids in [json!([group, b]), json!([b, group])] {
s.execute("select.set", &json!({"ids": ids})).unwrap();
let i = s.execute("document.info", &json!({"selectionOnly": true, "category": "spotColors"})).unwrap();
assert_eq!(i["sections"][0]["rows"], json!([["<Rectangle>", "Ink"]]), "{ids}");
assert_eq!((i["objects"]["spots"].as_u64(), i["spotColorNames"].clone()), (Some(1), json!(["Ink"])), "{ids}");
}
s.execute("select.set", &json!({"ids": [t]})).unwrap();
let i = s.execute("document.info", &json!({"selectionOnly": true, "category": "spotColors"})).unwrap();
assert_eq!((i["sections"][0]["rows"].clone(), i["spotColors"].clone()), (json!([["Type", "Ink"]]), json!(["Ink"])));
let text = s.execute("document.info", &json!({"category": "spotColors", "format": "text"})).unwrap()["text"].as_str().unwrap().to_string();
assert!(text.contains("\nSPOT COLOR OBJECTS\nBoth: Ink, Gold\n<Rectangle>: Ink\n<Rectangle>: Gold\nType: Ink\n"), "{text}");
assert!(!text.contains("Spare") && !text.contains("Look"), "{text}");
}
/// Document Info › Objects counts an object with a gradient or a pattern on both its fill and
/// its stroke once, and with Selection Only a selection that holds a group and an object inside
/// it counts that object once.
+1 -1
View File
@@ -515,7 +515,7 @@ fn the_document_info_report_has_every_category() {
"OBJECTS",
"GRAPHIC STYLES",
"BRUSHES",
"SPOT COLORS",
"SPOT COLOR OBJECTS",
"PATTERN OBJECTS",
"GRADIENT OBJECTS",
"SYMBOLS",
+3
View File
@@ -3319,6 +3319,9 @@ digits Malayalam Малаялам
Split Path Разделяне на път
spot петно
Spot Color Спот цвят
Spot Color Objects Обекти със спот цветове
Pattern Objects Обекти с мостри
Gradient Objects Обекти с градиенти
Spot Colors Спот цветове
Spot Colors… Спот цветове…
Spread: Разпространение:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3902,6 +3902,9 @@ theme Appearance Izgled
Select All Unused Odaberi sve nekorišteno
Sort by Kind Sortiraj po vrsti
Sort by Name Sortiraj po nazivu
Spot Color Objects Objekti sa spot bojama
Pattern Objects Objekti s uzorcima
Gradient Objects Objekti s gradijentima
Spot Colors Spot boje
Spot Colors (0) Spot boje (0)
Swatch Options Opcije uzorka boje
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4287,6 +4287,9 @@ digits Malayalam Malaiàlam
Sponge… Esponja…
spot directe
Spot Color Color directe
Spot Color Objects Objectes amb colors directes
Pattern Objects Objectes amb motius
Gradient Objects Objectes amb degradats
Spot Colors Colors directes
Spot Colors (0) Colors directes (0)
Spot Colors… Colors directes…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3954,6 +3954,9 @@ theme Appearance Motiv
Select All Unused Vybrat všechny nepoužité
Sort by Kind Seřadit podle druhu
Sort by Name Seřadit podle názvu
Spot Color Objects Objekty s přímými barvami
Pattern Objects Objekty se vzorky
Gradient Objects Objekty s přechody
Spot Colors Přímé barvy
Spot Colors (0) Přímé barvy (0)
Swatch Options Možnosti položky vzorníku
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3677,6 +3677,9 @@ theme Appearance Udseende
Select All Unused Markér alle ubrugte
Sort by Kind Sortér efter type
Sort by Name Sortér efter navn
Spot Color Objects Objekter med spotfarver
Pattern Objects Objekter med mønstre
Gradient Objects Objekter med gradienter
Spot Colors Spotfarver
Spot Colors (0) Spotfarver (0)
Swatch Options Indstillinger for farveprøve
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3378,6 +3378,9 @@ effect Points Punkte
Split Path Pfad teilen
spot Vollton
Spot Color Volltonfarbe
Spot Color Objects Objekte mit Volltonfarben
Pattern Objects Objekte mit Mustern
Gradient Objects Objekte mit Verläufen
Spot Colors Volltonfarben
Spot Colors… Volltonfarben…
Spread: Ausbreitung:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3832,6 +3832,9 @@ digits Malayalam Μαλαγιάλαμ
Split Into Grid… Διαίρεση σε πλέγμα…
Split Path Διαίρεση διαδρομής
spot πλακάτο
Spot Color Objects Αντικείμενα με χρώματα spot
Pattern Objects Αντικείμενα με μοτίβα
Gradient Objects Αντικείμενα με ντεγκραντέ
Spot Colors Πλακάτα χρώματα
Spot Colors… Πλακάτα χρώματα…
Stacked Bar Graph Tool Στοιβαγμένο ραβδόγραμμα (Stacked Bar Graph Tool)
Can't render this file because it is too large.
+3
View File
@@ -3408,6 +3408,9 @@ digits Malayalam Malayálam
Split Path Dividir trazado
spot plana
Spot Color Tinta plana
Spot Color Objects Objetos con tintas planas
Pattern Objects Objetos con motivos
Gradient Objects Objetos con degradados
Spot Colors Tintas planas
Spot Colors… Tintas planas…
Spread: Extensión:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3878,6 +3878,9 @@ theme Appearance Ulkoasu
Select All Unused Valitse kaikki käyttämättömät
Sort by Kind Lajittele lajin mukaan
Sort by Name Lajittele nimen mukaan
Spot Color Objects Objektit, joissa on spottivärejä
Pattern Objects Objektit, joissa on kuvioita
Gradient Objects Objektit, joissa on liukuvärejä
Spot Colors Spottivärit
Spot Colors (0) Spottivärit (0)
Swatch Options Värimallin asetukset
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3400,6 +3400,9 @@ digits Malayalam Malayalam
Split Path Scinder le tracé
spot ton direct
Spot Color Ton direct
Spot Color Objects Objets avec tons directs
Pattern Objects Objets avec motifs
Gradient Objects Objets avec dégradés
Spot Colors Tons directs
Spot Colors… Tons directs…
Spread: Étendue :
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4645,6 +4645,9 @@ antiAlias Sharp शार्प
Sponge… स्पंज…
spot स्पॉट
Spot Color स्पॉट रंग
Spot Color Objects स्पॉट कलर्स के साथ ऑब्जेक्ट्स
Pattern Objects पैटर्न्स के साथ ऑब्जेक्ट्स
Gradient Objects ग्रेडिएंट्स के साथ ऑब्जेक्ट्स
Spot Colors स्पॉट रंग
Spot Colors (0) स्पॉट कलर्स (0)
Spot Colors… स्पॉट कलर्स…
Can't render this file because it is too large.
+3
View File
@@ -4060,6 +4060,9 @@ mark-style Japanese Japán
Split Path Útvonal kettévágása
spot direkt
Spot Color Direktszín
Spot Color Objects Direkt színeket használó objektumok
Pattern Objects Mintázatokat használó objektumok
Gradient Objects Színátmeneteket használó objektumok
Spot Colors Direktszínek
Spot Colors (0) Direktszínek (0)
Spot Colors… Direktszínek…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3677,6 +3677,9 @@ theme Appearance Tampilan
Select All Unused Pilih semua yang tidak digunakan
Sort by Kind Urutkan berdasarkan jenis
Sort by Name Urutkan berdasarkan nama
Spot Color Objects Objek dengan Warna Spot
Pattern Objects Objek dengan Pola
Gradient Objects Objek dengan Gradien
Spot Colors Warna spot
Spot Colors (0) Warna spot (0)
Swatch Options Opsi sampel
Can't render this file because it contains an unexpected character in line 2380 and column 5.
+3
View File
@@ -3453,6 +3453,9 @@ digits Malayalam Malayalam
Split Path Dividi tracciato
spot spot
Spot Color Colore spot
Spot Color Objects Oggetti con colori spot
Pattern Objects Oggetti con motivi
Gradient Objects Oggetti con sfumature
Spot Colors Colori spot
Spot Colors… Colori spot…
Spread: Estensione:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3228,6 +3228,9 @@ mark-style Japanese 日本式
Add Guides ガイドを追加
Split Path パスを分割
Spot Color 特色
Spot Color Objects 特色を使用したオブジェクト
Pattern Objects パターンを使用したオブジェクト
Gradient Objects グラデーションを使用したオブジェクト
Spot Colors 特色
Spot Colors… 特色…
Spread: 拡散:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3513,6 +3513,9 @@ theme Appearance იერსახე
Select All Unused ყველა გამოუყენებლის მონიშვნა
Sort by Kind ტიპის მიხედვით დალაგება
Sort by Name სახელის მიხედვით დალაგება
Spot Color Objects ობიექტები სპოტ-ფერებით
Pattern Objects ობიექტები პატერნებით
Gradient Objects ობიექტები გრადიენტებით
Spot Colors სპოტ-ფერები
Swatch Options ფერის ნიმუშის პარამეტრები
Ungroup Color Group ფერების ჯგუფის დაშლა
Can't render this file because it is too large.
+3
View File
@@ -3217,6 +3217,9 @@ mark-style Japanese 일본식
Add Guides 안내선 추가
Split Path 패스 분할
Spot Color 별색
Spot Color Objects 별색을 사용한 개체
Pattern Objects 패턴을 사용한 개체
Gradient Objects 그레이디언트를 사용한 개체
Spot Colors 별색
Spot Colors… 별색…
Spread: 확산:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3691,6 +3691,9 @@ theme Appearance Vormgeving
Select All Unused Alle ongebruikte selecteren
Sort by Kind Sorteren op soort
Sort by Name Sorteren op naam
Spot Color Objects Objecten met spotkleuren
Pattern Objects Objecten met patronen
Gradient Objects Objecten met verlopen
Spot Colors Spotkleuren
Spot Colors (0) Spotkleuren (0)
Swatch Options Kleurstaalopties
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3813,6 +3813,9 @@ theme Appearance Wygląd
Select All Unused Zaznacz wszystkie nieużywane
Sort by Kind Sortuj według rodzaju
Sort by Name Sortuj według nazwy
Spot Color Objects Obiekty z kolorami dodatkowymi
Pattern Objects Obiekty ze wzorkami
Gradient Objects Obiekty z gradientami
Spot Colors Kolory dodatkowe
Swatch Options Opcje próbki
Ungroup Color Group Rozgrupuj grupę kolorów
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -2860,6 +2860,9 @@ digits Malayalam Malaiala
Split Into Grid… Dividir em Grade…
Split Path Dividir Caminho
Spot Color Cor Spot
Spot Color Objects Objetos com cores spot
Pattern Objects Objetos com padrões
Gradient Objects Objetos com gradientes
Spot Colors Cores Spot
Spot Colors… Cores Spot…
Spread: Propagação:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+3
View File
@@ -3440,6 +3440,9 @@ digits Malayalam Малаялам
Split Path Разделить контур
spot плашечный
Spot Color Плашечный цвет
Spot Color Objects Объекты с плашечными цветами
Pattern Objects Объекты с узорами
Gradient Objects Объекты с градиентами
Spot Colors Плашечные цвета
Spot Colors… Плашечные цвета…
Spread: Разворот:
Can't render this file because it is too large.
+3
View File
@@ -3330,6 +3330,9 @@ digits Malayalam Malayalam
Split Path Dela bana
spot dekor
Spot Color Dekorfärg
Spot Color Objects Objekt med dekorfärger
Pattern Objects Objekt med mönster
Gradient Objects Objekt med gradienter
Spot Colors Dekorfärger
Spot Colors… Dekorfärger…
Spread: Spridning:
Can't render this file because it contains an unexpected character in line 5 and column 25.
+22
View File
@@ -225,6 +225,28 @@ fn control_bar_panel_menu_labels_are_in_complete_catalogs() {
}
}
/// The titles of Document Info's Spot Color Objects, Pattern Objects and Gradient Objects
/// categories, which the panel, its menu and the Objects category's counts show.
#[test]
fn document_info_object_category_titles_are_in_complete_catalogs() {
let mut session = vectorcraft_engine::Session::new();
session.execute("file.new", &serde_json::json!({"width": 100, "height": 100})).unwrap();
let info = session.execute("document.info", &serde_json::json!({})).unwrap();
let titles: Vec<&str> = info["sections"]
.as_array()
.unwrap()
.iter()
.filter(|s| matches!(s["id"].as_str(), Some("spotColors" | "patterns" | "gradients")))
.filter_map(|s| s["title"].as_str())
.collect();
assert_eq!(titles, ["Spot Color Objects", "Pattern Objects", "Gradient Objects"]);
for lang in LANGUAGES.iter().filter(|lang| lang.complete_menus) {
for title in &titles {
assert!(lang.catalog().plain(title).is_some(), "{} is missing {title}", lang.code);
}
}
}
/// The Character panel's Anti-aliasing Method label and its choices, with the Sharp method's
/// own `antiAlias` row apart from the plain "Sharp" corner row.
#[test]
+3
View File
@@ -3231,6 +3231,9 @@ digits Malayalam Malayalam
Split Path Bölünmüş Yol
spot nokta
Spot Color Nokta Rengi
Spot Color Objects Spot Renkli Nesneler
Pattern Objects Desenli Nesneler
Gradient Objects Degradeli Nesneler
Spot Colors Nokta Renkleri
Spot Colors… Nokta Renkleri…
Spread: Yayılma:
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -3446,6 +3446,9 @@ effect Points Точки
Split Path Розділити контур
spot плашковий
Spot Color Плашковий колір
Spot Color Objects Об’єкти з плашковими кольорами
Pattern Objects Об’єкти з візерунками
Gradient Objects Об’єкти з градієнтами
Spot Colors Плашкові кольори
Spot Colors… Плашкові кольори…
Spread: Розворот:
Can't render this file because it contains an unexpected character in line 182 and column 5.
+3
View File
@@ -3792,6 +3792,9 @@ mark-style Japanese Kiểu Nhật
Split Path Tách đường dẫn
spot đặc biệt
Spot Color Màu đặc biệt
Spot Color Objects Đối tượng có màu đặc biệt
Pattern Objects Đối tượng có họa tiết
Gradient Objects Đối tượng có chuyển sắc
Spot Colors Màu đặc biệt
Spot Colors… Màu đặc biệt…
Spray Radius Bán kính phun
Can't render this file because it contains an unexpected character in line 204 and column 5.
+3
View File
@@ -3273,6 +3273,9 @@ digits Malayalam 马拉雅拉姆数字
Split Path 分割路径
spot 专色
Spot Color 专色
Spot Color Objects 使用专色的对象
Pattern Objects 使用图案的对象
Gradient Objects 使用渐变的对象
Spot Colors 专色
Spot Colors… 专色…
Spread: 扩散:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4415,6 +4415,9 @@ digits Malayalam 馬來亞拉姆數字
Split Path 分割路徑
spot 特別色
Spot Color 特別色
Spot Color Objects 使用特別色的物件
Pattern Objects 使用圖樣的物件
Gradient Objects 使用漸層的物件
Spot Colors 特別色
Spot Colors… 特別色…
Spread: 擴散:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+14 -1
View File
@@ -1,5 +1,5 @@
//! Document Info panel: the document's setup and what it contains (objects by kind, graphic
//! styles, brushes, spot colors, objects painted with patterns and with gradients, symbols, fonts and their files,
//! styles, brushes, objects painted with spot colors, with patterns and with gradients, symbols, fonts and their files,
//! linked and embedded images, repeat objects), for the whole document or the selection only. The flyout picks one category (or all)
//! and saves the text report (`docInfo.save`, the report File → Package writes too).
@@ -192,6 +192,7 @@ mod tests {
&& menu_text.contains("Brushes")
&& menu_text.contains("Repeat Objects")
&& menu_text.contains("Gradient Objects")
&& menu_text.contains("Spot Color Objects")
&& menu_text.contains("Save…"),
"{menu_text}"
);
@@ -205,6 +206,17 @@ mod tests {
show(app, ui);
});
assert!(gradients.contains("Gradient Objects (1)") && gradients.contains("Glow") && gradients.contains("Radial gradient"), "{gradients}");
// Spot Color Objects lists the objects painted with a spot color by name.
let spot = json!({"model": "cmyk", "c": 0.0, "m": 0.8, "y": 0.6, "k": 0.0});
app.session.execute("swatch.new", &json!({"name": "Signal Red", "color": spot, "spot": true})).unwrap();
let badge = app.session.execute("shape.ellipse", &json!({"x": 50, "y": 50, "width": 20, "height": 20})).unwrap()["id"].clone();
app.session.execute("paint.setFill", &json!({"ids": [badge], "swatch": "Signal Red"})).unwrap();
app.session.execute("layer.setProps", &json!({"id": badge, "name": "Badge"})).unwrap();
let spots = crate::tests_labels::painted_text(&mut app, |app, ui| {
set_pstate(ui.ctx(), "docinfo-category", "spotColors".to_string());
show(app, ui);
});
assert!(spots.contains("Spot Color Objects (1)") && spots.contains("Badge") && spots.contains("Signal Red"), "{spots}");
let filtered = crate::tests_labels::painted_text(&mut app, |app, ui| {
set_pstate(ui.ctx(), "docinfo-category", "fonts".to_string());
show(app, ui);
@@ -222,6 +234,7 @@ mod tests {
let report = String::from_utf8(written.borrow().clone()).unwrap();
assert!(report.starts_with("Document Info: ") && report.contains("\nFONT DETAILS\n") && report.contains("\nREPEAT OBJECTS\n"), "{report}");
assert!(report.contains("\nGRADIENT OBJECTS\nGlow: Radial gradient\n"), "{report}");
assert!(report.contains("\nSPOT COLOR OBJECTS\nBadge: Signal Red\n"), "{report}");
}
/// The report the panel menu's Save… writes with category `category` shown ("" for All
+6 -1
View File
@@ -6094,7 +6094,12 @@ the patterns it uses, which the patterns category lists as Pattern Objects and `
stroked with a gradient, by the same rules as `patternObjects`, with the gradients it uses: the name of the gradient
swatch a gradient is linked to, else `Linear gradient`, `Radial gradient` or `Freeform gradient`; the gradients category
lists them as Gradient Objects and `objects.gradients` counts them; gradient swatches no art uses are not listed; with
`selectionOnly`, those in the selection), `gradientNames` (the gradients those objects use) and `pathLength` (pt: the total length of the paths, compound paths' pieces included and guides left
`selectionOnly`, those in the selection), `gradientNames` (the gradients those objects use), `spotObjects`
(`[{id, name, spotColors}]`: every object filled or stroked with a spot color swatch, as a solid color or in a
gradient's stops, at a tint above 0, by the same rules as `patternObjects`, with the spot colors it uses; a process
color, even one of the same color, and Registration are not spot colors; the spotColors category lists them as Spot
Color Objects and `objects.spots` counts them; spot swatches no art uses are not listed; with `selectionOnly`, those
in the selection), `spotColorNames` and `spotColors` (the spot colors those objects use) and `pathLength` (pt: the total length of the paths, compound paths' pieces included and guides left
out, which objects lists as Path Length in the document's units, say for a cut line or an LED strip along the letters);
`format: "text"` returns the plain-text report Document Info › Save… (`docInfo.save {path?, selectionOnly?, category?}`:
only `category` when given, else every category) and the package report write.
+1
View File
@@ -1352,6 +1352,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
Document Info has a Repeat Objects category after Embedded Images. It lists each radial, grid and mirror Repeat object by name with its kind and the number of instances it makes. For a grid it gives the rows and columns instead, followed by the number of instances it draws when that is fewer. With Selection Only it lists the Repeat objects in the selection. The Document Info report and Package's report list them too (`document.info {category: "repeats"}`, `repeatObjects`).
Document Info's Pattern Objects category lists the objects whose fill or stroke is a pattern, the category the [Document Info panel](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/document-info-panel.html) page describes, each by name with the patterns it uses. With Selection Only it lists those in the selection, and the Document Info report and Package's report list them too (`document.info {category: "patterns"}`, `patternObjects`). Listing an object with a pattern fill and a pattern stroke once, listing objects inside groups and type objects whose characters use a pattern, and leaving out hidden fills and strokes, strokes with no width, guides, a compound path member's own fills and strokes, an image's fills and strokes without a shape, characters the type does not draw and patterns no art uses, and counting the listed objects in Objects, are VectorCraft's own rules. Test: `cmd::docinfo::tests::info_lists_the_pattern_objects` (vectorcraft-engine).
Document Info's Gradient Objects category lists the objects whose fill or stroke is a gradient, the category the [Document Info panel](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/document-info-panel.html) page describes as the count of objects with gradient fills or strokes. Each object is listed by name with the gradients it uses. With Selection Only it lists those in the selection, and the Document Info report and Package's report list them too (`document.info {category: "gradients"}`, `gradientObjects`). Naming a linked gradient by its swatch and any other gradient by its kind, and leaving out paint that draws nothing by the rules Pattern Objects follows, gradient swatches no art uses, gradient meshes and gradients drawn by effects, are VectorCraft's own rules. Objects counts the listed objects as Gradient Objects. Test: `cmd::docinfo::tests::info_lists_the_gradient_objects` (vectorcraft-engine).
Document Info's Spot Color Objects category lists the objects that use spot colors, the category the [Document Info panel](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/document-info-panel.html) page describes as objects using spot color separations. Each object is listed by name with the spot colors it uses. With Selection Only it lists those in the selection, and the Document Info report and Package's report list them too (`document.info {category: "spotColors"}`, `spotObjects`). Taking a solid color or a gradient stop linked to a spot swatch at a tint above 0, leaving out process colors that look the same, Registration, paint that draws nothing by the rules Pattern Objects follows, spot swatches no art uses and spot colors inside patterns, symbols, opacity masks, effects and placed files, are VectorCraft's own rules. Objects counts the listed objects as Spot Color Objects. Test: `cmd::docinfo::tests::info_lists_the_spot_color_objects` (vectorcraft-engine).
Document Info's Save… writes the category the panel shows, with Selection Only applied, as the [Document Info panel](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/document-info-panel.html) page describes ("Exports the selected category's visible information to a text file"). With All Categories shown it writes every category, which is VectorCraft's own rule (`docInfo.save {category}`). Test: `panels::doc_info::tests::save_writes_the_category_the_panel_shows`.
The Brushes panel menu's Select All Unused selects the brushes that are not used in the document, as the [Brushes](https://web.archive.org/web/20241230205225/https://helpx.adobe.com/illustrator/using/brushes.html) page (archived copy) describes. A brush counts as used when a stroke paints with it in the art (opacity masks included), in symbols, graph designs, pattern tiles or graphic styles, or in the art of a used brush (`brush.unused`).
A rename in Brush Options and Delete Brush change every stroke that paints with the brush: in the layers and opacity masks, symbols, graph designs, pattern tiles, graphic styles and the art of other brushes. A rename gives those strokes the new name, and Delete Brush makes them plain strokes, in one undo step.