From 5b09cf9c09f1737932125cf8380060a4efd4f719 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 13:45:55 -0800 Subject: [PATCH 1/2] Document Info: Spot Color Objects leaves out fills and strokes at opacity 0 A spot-colored fill or stroke whose opacity is 0 prints no ink, but Spot Color Objects still listed its object and objects.spots counted it. Such fills and strokes are now left out; an object stays listed while another of its fills or strokes prints the spot color. Pattern Objects and Gradient Objects are unchanged. --- crates/engine/src/cmd/docinfo.rs | 50 ++++++++++++++++++++++++++++---- docs/mcp.md | 3 +- docs/target-app-parity.md | 2 +- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/crates/engine/src/cmd/docinfo.rs b/crates/engine/src/cmd/docinfo.rs index 4ed63e7ec1..b21bf8178e 100644 --- a/crates/engine/src/cmd/docinfo.rs +++ b/crates/engine/src/cmd/docinfo.rs @@ -18,7 +18,7 @@ pub fn specs() -> Vec { "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), spotObjects: [{id, name, spotColors}] (the objects filled or stroked with a spot color swatch, as a solid color or in the stops of a gradient with a stop above opacity 0, at a tint above 0, and grayscale images colorized with one, 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", + "{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 the stops of a gradient with a stop above opacity 0, at a tint above 0, fills and strokes at an opacity of 0 left out, and grayscale images colorized with one, 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 ), @@ -344,12 +344,14 @@ type PaintNames<'a> = dyn Fn(&Paint) -> Vec + 'a; /// 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. +/// With `ink` (the spot colors), a fill or stroke at an opacity of 0 prints no ink and is left out. fn own_paints( d: &Document, n: &Node, members: &HashSet<*const Node>, painted_runs: Option<&(&vectorcraft_doc::TextObject, BTreeSet)>, label: &PaintNames<'_>, + ink: bool, ) -> Vec { if matches!(n.kind, NodeKind::Path { guide: true, .. }) || members.contains(&std::ptr::from_ref(n)) @@ -364,7 +366,8 @@ fn own_paints( .then(|| vectorcraft_render::effects::image_paint(Some(d), n).into_iter().flat_map(|(_, _, item)| label(item.paint())).collect()); let mut out: Vec = vec![]; for item in &n.appearance.items { - if !item.visible() || matches!(item, vectorcraft_doc::AppearanceItem::Stroke(s) if s.width <= 0.0) { + let prints = item.opacity() > 0.0; + if !item.visible() || matches!(item, vectorcraft_doc::AppearanceItem::Stroke(s) if s.width <= 0.0) || (ink && !prints) { continue; } for name in label(item.paint()) { @@ -501,9 +504,9 @@ fn info(s: &mut Session, p: &Value) -> Result { } // 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); + let mut used_spots = own_paints(d, n, &members, painted_runs.as_ref(), &spot_label, true); + let mut used_patterns = own_paints(d, n, &members, painted_runs.as_ref(), &pattern, false); + let mut used_gradients = own_paints(d, n, &members, painted_runs.as_ref(), &gradient, false); // 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. @@ -1228,6 +1231,43 @@ mod tests { assert_eq!(i["objects"]["spots"].as_u64(), Some(1), "{}", i["objects"]); } + /// Spot Color Objects leaves out a fill or stroke at an opacity of 0, which prints no ink, and + /// keeps the object while another of its fills or strokes prints the spot color. Gradient + /// Objects still lists a clear gradient fill. + #[test] + fn spot_color_objects_skip_clear_fills_and_strokes() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap(); + s.execute("swatch.new", &json!({"name": "Ink", "color": {"model": "cmyk", "c": 0, "m": 1, "y": 1, "k": 0}, "spot": true})).unwrap(); + let rect = s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone(); + s.execute("paint.setFill", &json!({"ids": [rect], "swatch": "Ink"})).unwrap(); + s.execute("paint.setStroke", &json!({"ids": [rect], "swatch": "Ink"})).unwrap(); + let ramp = s.execute("shape.rectangle", &json!({"x": 50, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone(); + let stops = json!([{"offset": 0, "color": "#000", "swatch": "Ink"}, {"offset": 1, "color": "#fff", "swatch": "Ink"}]); + s.execute("paint.setFill", &json!({"ids": [ramp], "gradient": {"kind": "linear", "stops": stops}})).unwrap(); + s.execute("paint.setStroke", &json!({"ids": [ramp], "none": true})).unwrap(); + let items = |s: &Session, id: &Value| s.doc().unwrap().doc.node(NodeId(id.as_u64().unwrap())).unwrap().appearance.items.clone(); + let spots = |s: &mut Session| { + let i = s.execute("document.info", &json!({})).unwrap(); + let mut ids: Vec = i["spotObjects"].as_array().unwrap().iter().map(|o| o["id"].as_u64().unwrap()).collect(); + ids.sort_unstable(); + (ids, i["objects"]["spots"].as_u64(), i["objects"]["gradients"].as_u64()) + }; + // The rectangle's fill at an opacity of 0: its stroke still prints the spot color. + s.execute("select.set", &json!({"ids": [rect]})).unwrap(); + let fill = items(&s, &rect).iter().position(|i| matches!(i, vectorcraft_doc::AppearanceItem::Fill(_))).unwrap(); + s.execute("appearance.setItem", &json!({"index": fill, "opacity": 0})).unwrap(); + assert_eq!(spots(&mut s), (vec![rect.as_u64().unwrap(), ramp.as_u64().unwrap()], Some(2), Some(1))); + // Every fill and stroke of both objects at an opacity of 0. + for id in [&rect, &ramp] { + s.execute("select.set", &json!({"ids": [id]})).unwrap(); + for index in 0..items(&s, id).len() { + s.execute("appearance.setItem", &json!({"index": index, "opacity": 0})).unwrap(); + } + } + assert_eq!(spots(&mut s), (vec![], None, Some(1))); + } + /// 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. diff --git a/docs/mcp.md b/docs/mcp.md index 84ce94f85a..c4c5ec1303 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -6483,7 +6483,8 @@ swatch a gradient is linked to, else `Linear gradient`, `Radial gradient` or `Fr 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), `spotObjects` (`[{id, name, spotColors}]`: every object filled or stroked with a spot color swatch, as a solid color or in the -stops of a gradient with a stop above opacity 0, at a tint above 0, and every grayscale image colorized with one, by +stops of a gradient with a stop above opacity 0, at a tint above 0, leaving out fills and strokes at an opacity of 0, +and every grayscale image colorized with one, 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 diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 8e501889db..e9293a52a0 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1394,7 +1394,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 and a grayscale image colorized with one, leaving out process colors that look the same, Registration, gradients whose stops are all at an opacity of 0, 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. Tests: `cmd::docinfo::tests::info_lists_the_spot_color_objects` and `spot_color_objects_take_colorized_images_and_skip_clear_gradients` (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 and a grayscale image colorized with one, leaving out process colors that look the same, Registration, gradients whose stops are all at an opacity of 0, fills and strokes at an opacity of 0, 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. Tests: `cmd::docinfo::tests::info_lists_the_spot_color_objects`, `spot_color_objects_take_colorized_images_and_skip_clear_gradients` and `spot_color_objects_skip_clear_fills_and_strokes` (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. From fcec08e1bbe3044b1fa11d6d6c8603504f9129be Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 15:41:23 -0800 Subject: [PATCH 2/2] Document Info: the clear-paint test expects Gradient Objects to skip clear gradients Gradient Objects now leaves out fills and strokes at an opacity of 0, so the test expects no gradient count once every fill and stroke of the spot gradient object is clear. --- crates/engine/src/cmd/docinfo.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/docinfo.rs b/crates/engine/src/cmd/docinfo.rs index a5a0261610..c548c3d85a 100644 --- a/crates/engine/src/cmd/docinfo.rs +++ b/crates/engine/src/cmd/docinfo.rs @@ -1380,7 +1380,7 @@ mod tests { /// Spot Color Objects leaves out a fill or stroke at an opacity of 0, which prints no ink, and /// keeps the object while another of its fills or strokes prints the spot color. Gradient - /// Objects still lists a clear gradient fill. + /// Objects leaves out a clear gradient fill as well. #[test] fn spot_color_objects_skip_clear_fills_and_strokes() { let mut s = Session::new(); @@ -1412,7 +1412,7 @@ mod tests { s.execute("appearance.setItem", &json!({"index": index, "opacity": 0})).unwrap(); } } - assert_eq!(spots(&mut s), (vec![], None, Some(1))); + assert_eq!(spots(&mut s), (vec![], None, None)); } /// Document Info › Objects counts an object with a gradient or a pattern on both its fill and