mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3330 from dotCooCoo/feat/docinfo-brushes
Document Info: a Brushes category lists the document's brushes
This commit is contained in:
@@ -18,7 +18,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Document Info",
|
||||
["Window", "Document Info"],
|
||||
None,
|
||||
"{selectionOnly?, category?: document|objects|graphicStyles|spotColors|patterns|gradients|symbols|fonts|fontDetails|linkedImages|embeddedImages (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), …, sections: [{id, title, rows: [[label, value]]}] (the panel's categories; fontDetails: each font's file and whether its licence lets it be embedded)}",
|
||||
"{selectionOnly?, category?: document|objects|graphicStyles|brushes|spotColors|patterns|gradients|symbols|fonts|fontDetails|linkedImages|embeddedImages (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), …, sections: [{id, title, rows: [[label, value]]}] (the panel's categories; fontDetails: each font's file and whether its license lets it be embedded)}",
|
||||
has_doc,
|
||||
info
|
||||
),
|
||||
@@ -35,10 +35,11 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
}
|
||||
|
||||
/// Document Info's categories: (id, title), in the order the panel and the report show them.
|
||||
pub const CATEGORIES: [(&str, &str); 11] = [
|
||||
pub const CATEGORIES: [(&str, &str); 12] = [
|
||||
("document", "Document"),
|
||||
("objects", "Objects"),
|
||||
("graphicStyles", "Graphic Styles"),
|
||||
("brushes", "Brushes"),
|
||||
("spotColors", "Spot Colors"),
|
||||
("patterns", "Pattern Objects"),
|
||||
("gradients", "Gradient Swatches"),
|
||||
@@ -138,6 +139,7 @@ fn sections(d: &Document, info: &Value, fonts: &BTreeSet<super::fonts::UsedFont>
|
||||
rows
|
||||
}
|
||||
"graphicStyles" => names("graphicStyleNames"),
|
||||
"brushes" => names("brushNames"),
|
||||
"spotColors" => names("spotColors"),
|
||||
"patterns" => names("patternNames"),
|
||||
"gradients" => d.swatches_iter().filter(|s| matches!(s.paint, Paint::Gradient(_))).map(|s| (s.name.clone(), String::new())).collect(),
|
||||
@@ -246,6 +248,8 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let mut symbols = BTreeSet::new();
|
||||
let mut spot = BTreeSet::new();
|
||||
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() };
|
||||
for r in roots {
|
||||
r.walk(&mut |n| {
|
||||
let k = match &n.kind {
|
||||
@@ -319,6 +323,16 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
{
|
||||
styles.push(&g.name);
|
||||
}
|
||||
if selection_only {
|
||||
for item in &n.appearance.items {
|
||||
if let vectorcraft_doc::AppearanceItem::Stroke(st) = item
|
||||
&& let Some(b) = &st.brush
|
||||
&& !brushes.contains(b)
|
||||
{
|
||||
brushes.push(b.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
let font_names: Vec<String> = fonts.iter().map(super::fonts::font_label).collect();
|
||||
@@ -333,6 +347,7 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
"swatches": d.swatches_iter().count(),
|
||||
"graphicStyles": d.graphic_styles.len(),
|
||||
"graphicStyleNames": styles,
|
||||
"brushNames": brushes,
|
||||
"characterStyles": d.char_styles.len(),
|
||||
"paragraphStyles": d.para_styles.len(),
|
||||
"patterns": d.patterns.len(),
|
||||
@@ -448,6 +463,40 @@ mod tests {
|
||||
assert_eq!(length(&mut s, json!({"selectionOnly": true})), 0.0);
|
||||
}
|
||||
|
||||
/// Document Info › Brushes lists the Brushes panel's brushes, and with Selection Only it lists
|
||||
/// the brushes the selected objects' strokes use, each once.
|
||||
#[test]
|
||||
fn info_lists_the_brushes() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 200, "height": 100})).unwrap();
|
||||
let panel: Vec<Value> =
|
||||
s.execute("brush.list", &json!({})).unwrap()["brushes"].as_array().unwrap().iter().map(|b| b["name"].clone()).collect();
|
||||
let i = s.execute("document.info", &json!({"category": "brushes"})).unwrap();
|
||||
assert_eq!(i["brushNames"], json!(panel));
|
||||
assert_eq!((i["sections"][0]["id"].as_str(), i["sections"][0]["title"].as_str()), (Some("brushes"), Some("Brushes")));
|
||||
let rows: Vec<Value> = panel.iter().map(|n| json!([n, ""])).collect();
|
||||
assert_eq!(i["sections"][0]["rows"], json!(rows));
|
||||
let a = s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone();
|
||||
let b = s.execute("shape.rectangle", &json!({"x": 20, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone();
|
||||
let c = s.execute("shape.rectangle", &json!({"x": 40, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone();
|
||||
s.execute("brush.apply", &json!({"ids": [a], "name": "Charcoal"})).unwrap();
|
||||
s.execute("brush.apply", &json!({"ids": [b], "name": "Arrow"})).unwrap();
|
||||
s.execute("brush.apply", &json!({"ids": [c], "name": "Charcoal"})).unwrap();
|
||||
let names =
|
||||
|s: &mut Session| s.execute("document.info", &json!({"selectionOnly": true, "category": "brushes"})).unwrap()["brushNames"].clone();
|
||||
s.execute("select.set", &json!({"ids": [a, b, c]})).unwrap();
|
||||
assert_eq!(names(&mut s), json!(["Charcoal", "Arrow"]));
|
||||
s.execute("select.set", &json!({"ids": [b]})).unwrap();
|
||||
assert_eq!(names(&mut s), json!(["Arrow"]));
|
||||
// A stroke without a brush lists none.
|
||||
let plain = s.execute("shape.rectangle", &json!({"x": 60, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone();
|
||||
s.execute("select.set", &json!({"ids": [plain]})).unwrap();
|
||||
assert_eq!(names(&mut s), json!([]));
|
||||
let text = s.execute("document.info", &json!({"category": "brushes", "format": "text"})).unwrap()["text"].as_str().unwrap().to_string();
|
||||
assert!(text.contains(&format!("\nBRUSHES\n{}\n", panel[0].as_str().unwrap())), "{text}");
|
||||
assert!(text.contains("\nCharcoal\n") && text.contains("\nArrow\n") && !text.contains("OBJECTS"), "{text}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedding_permissions_read_from_fs_type() {
|
||||
assert_eq!(embedding_label(0), "embedding allowed");
|
||||
|
||||
@@ -219,6 +219,7 @@ fn the_document_info_report_has_every_category() {
|
||||
"DOCUMENT",
|
||||
"OBJECTS",
|
||||
"GRAPHIC STYLES",
|
||||
"BRUSHES",
|
||||
"SPOT COLORS",
|
||||
"PATTERN OBJECTS",
|
||||
"GRADIENT SWATCHES",
|
||||
@@ -238,8 +239,10 @@ fn the_document_info_report_has_every_category() {
|
||||
assert_eq!((i["sections"].as_array().unwrap().len(), i["sections"][0]["id"].as_str()), (1, Some("linkedImages")));
|
||||
assert_eq!(i["sections"][0]["rows"].as_array().unwrap().len(), 2);
|
||||
let all = run(&mut s, "document.info", json!({}));
|
||||
assert_eq!(all["sections"].as_array().unwrap().len(), 11);
|
||||
assert!(s.execute("document.info", &json!({"category": "brushes"})).is_err());
|
||||
assert_eq!(all["sections"].as_array().unwrap().len(), 12);
|
||||
let i = run(&mut s, "document.info", json!({"category": "brushes"}));
|
||||
assert_eq!((i["sections"].as_array().unwrap().len(), i["sections"][0]["id"].as_str()), (1, Some("brushes")));
|
||||
assert!(s.execute("document.info", &json!({"category": "strokes"})).is_err());
|
||||
assert!(s.execute("document.info", &json!({"format": "xml"})).is_err());
|
||||
// The bundled fonts allow embedding.
|
||||
let face = vectorcraft_text::FontDb::global().face("Source Sans 3", "Regular").unwrap();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Document Info panel: the document's setup and what it contains (objects by kind, graphic
|
||||
//! styles, spot colours, patterns, gradients, symbols, fonts and their files, linked and embedded
|
||||
//! styles, brushes, spot colors, patterns, gradients, symbols, fonts and their files, linked and embedded
|
||||
//! images), 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).
|
||||
|
||||
@@ -183,7 +183,7 @@ mod tests {
|
||||
let text = crate::tests_labels::painted_text(&mut app, show);
|
||||
assert!(text.contains("Color Mode") && text.contains("Artboard 1"), "{text}");
|
||||
let menu_text = crate::tests_labels::painted_text(&mut app, menu);
|
||||
assert!(menu_text.contains("Font Details") && menu_text.contains("Save…"), "{menu_text}");
|
||||
assert!(menu_text.contains("Font Details") && menu_text.contains("Brushes") && menu_text.contains("Save…"), "{menu_text}");
|
||||
let filtered = crate::tests_labels::painted_text(&mut app, |app, ui| {
|
||||
set_pstate(ui.ctx(), "docinfo-category", "fonts".to_string());
|
||||
show(app, ui);
|
||||
|
||||
+3
-2
@@ -3067,8 +3067,9 @@ written. Every option defaults to true. Without `folder` (the web, or an agent t
|
||||
result carries the same files as a zip (`{name: "<name>.zip", dataBase64}`, entries under `<name>/`).
|
||||
|
||||
`document.info {selectionOnly?, category?, format?: "text"}` adds `sections` (`[{id, title, rows: [[label, value]]}]`:
|
||||
document, objects, graphicStyles, spotColors, patterns, gradients, symbols, fonts, fontDetails, linkedImages,
|
||||
embeddedImages) and `pathLength` (pt: the total length of the paths, compound paths' pieces included and guides left
|
||||
document, objects, graphicStyles, brushes, spotColors, patterns, gradients, symbols, fonts, fontDetails, linkedImages,
|
||||
embeddedImages), `brushNames` (the Brushes panel's brushes; with `selectionOnly`, the brushes the selected objects'
|
||||
strokes 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?}`) and the package report
|
||||
write.
|
||||
|
||||
@@ -690,6 +690,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Strokes:** one stroke-geometry module shared by the canvas, SVG, PDF and Outline Stroke; dotted lines (zero-length dashes draw dots or squares); arrowheads with hollow outlines, tip-on-end or extend-past-end alignment and one opacity for line and head. SVG and PDF export arrowheads, width profiles, brushes and aligned strokes exactly as the canvas draws them; Outline Stroke and the live Outline Stroke effect outline every stroke item as drawn (dashes, heads, profiles, alignment); dashes can be fitted to corners and path ends; width profiles carry through dashes, and their corners take the join. The Stroke panel edits type's character strokes, follows the stroke units (its weight presets are round values in that unit: `0.25 mm`, `0.0625 in`, `1 px`, #426; fields show four decimals in mm, cm and inches, three in points), shows mixed values blank and opens from the Control bar. A width profile library, thirty more arrowheads, and Width Point Edit with multi-select and discontinuous points. Visual bounds and clicks cover the whole painted stroke. Scale Strokes & Effects, Scale Corners and Use Preview Bounds work everywhere, and new art inherits stroke options, a graphic style or the whole last appearance.
|
||||
- **Brushes (part of #852):** double-clicking a brush in the Brushes panel (or the panel menu's Brush Options…) opens Calligraphic Brush Options (Name; Angle, Roundness and Size, each Fixed, Random or Pressure with its Variation; live previews of the nib and of a light–heavy–light stroke) or Bristle Brush Options (Shape and the bristle sliders); OK edits the brush and every stroke painted with it. The Paintbrush records the pen pressure along each stroke (kept with the path, saved, and joined when a stroke continues a path); a Calligraphic value on Pressure goes from value − variation at the lightest pressure to value + variation at the heaviest, on the canvas and in SVG, PDF and the other exports. Brushes saved before keep their look. Scatter Brush Options (Size, Spacing, Scatter and Rotation, each Fixed, Random between two values or Pressure from the first at the lightest pressure to the second at the heaviest; Rotation relative to Page or Path), Art Brush Options (Width, Scale Proportionately, Stretch to Fit Stroke Length or Stretch Between Guides with the guides' start and end, the four Directions, Flip Along and Across) and Pattern Brush Options (Scale, Spacing, the five tiles shown, Flip Along and Across, Stretch to fit, Add space to fit or Approximate path) each have Colorization (None, Tints, Tints and Shades, Hue Shift with its Key Color picked from the art's colours) and a live stroke preview; values from files and commands are clamped to their ranges. Still to come: the Art brush's Overlap, auto-generated Pattern brush corners, new tile art picked in Pattern Brush Options, Stylus Wheel, Tilt, Bearing and Rotation, and pen pressure on macOS.
|
||||
In the Brushes panel, Shift-click selects a range of brushes and Ctrl/Cmd-click adds or removes one. Delete Brush and Duplicate Brush then act on all of them (`brush.delete {names}`, `brush.duplicate {names}`).
|
||||
Document Info has a Brushes category after Graphic Styles. It lists the Brushes panel's brushes, or with Selection Only it lists the brushes that the selected objects' strokes use. The Document Info report and Package's report list them too (`document.info {category: "brushes"}`, `brushNames`).
|
||||
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`).
|
||||
- **Appearance:** the Appearance panel's selected row drives every paint, stroke, gradient and transparency edit (`item` on the commands); effects apply to one fill or stroke; per-item Opacity popups, whose blend-mode list and opacity slider stay open inside them (#430; the Control bar's Stroke popover too); Mixed Appearances, Layers target dots and basic-appearance rules. Effects can be reordered, moved between items and copied by dragging; Show All Hidden Attributes; clicking an effect edits it in place; the object thumbnail drags onto art; effects render on type, images, symbols, blends, envelopes, meshes and repeats, and per-item raster effects export. Groups, layers and type have their own fills, strokes and effects (Contents and Characters rows); the Layers panel's target circles target layers, groups and objects; Expand Appearance turns fills, strokes, effects and brushes into objects.
|
||||
Fill and stroke paint (Fill, Stroke, None, Apply Last Color and Apply Last Gradient, also on characters selected with the Type tool), the Stroke panel's options, gradient and freeform point edits, Swap Fill and Stroke, Default Fill and Stroke, the Eyedropper, and the Appearance panel's item edits, Clear Appearance, Reduce to Basic Appearance, Remove Item, item reordering and Move Appearance record no undo step when they change nothing.
|
||||
|
||||
Reference in New Issue
Block a user