mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Document Info: a Repeat Objects category lists the document's repeats
Document Info had no Repeat Objects category, and document.info refused category "repeats". The reference app's Document Info panel menu lists Repeat Objects after Font Details. Document Info now 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. The count comes from the new RepeatSpec::instances, which gives the number of instances a repeat draws. That number is smaller than its options give when repeats nested in its source would pass the instance budget. With Selection Only the category lists the Repeat objects in the selection. document.info gives them as repeatObjects, the panel's flyout offers the category, and the text report that Document Info › Save… and File › Package write has a REPEAT OBJECTS section. The title has a row in every catalog. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -572,6 +572,11 @@ impl RepeatSpec {
|
||||
fn kept(&self, nested: usize) -> usize {
|
||||
self.kind.count().min(MAX_REPEAT_INSTANCES / nested.max(1)).max(1)
|
||||
}
|
||||
/// How many instances this Repeat draws ([`Self::transforms`]): as many as its options make,
|
||||
/// fewer when the Repeats in its source would take it past [`MAX_REPEAT_INSTANCES`].
|
||||
pub fn instances(&self) -> usize {
|
||||
self.kept(nested_instances(&self.source))
|
||||
}
|
||||
/// Instances made in all, those of the Repeats nested in the source included.
|
||||
fn instances_in_all(&self) -> usize {
|
||||
let nested = nested_instances(&self.source);
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_color::Paint;
|
||||
use vectorcraft_doc::{ColorMode, Document, Node, NodeId, NodeKind};
|
||||
use vectorcraft_doc::{ColorMode, Document, Node, NodeId, NodeKind, RepeatKind, RepeatSpec};
|
||||
use vectorcraft_geom::{Affine, Rect};
|
||||
use vectorcraft_text::embed::Embedding;
|
||||
|
||||
@@ -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 (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)}",
|
||||
"{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), …, 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,7 +35,7 @@ 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); 12] = [
|
||||
pub const CATEGORIES: [(&str, &str); 13] = [
|
||||
("document", "Document"),
|
||||
("objects", "Objects"),
|
||||
("graphicStyles", "Graphic Styles"),
|
||||
@@ -48,6 +48,7 @@ pub const CATEGORIES: [(&str, &str); 12] = [
|
||||
("fontDetails", "Font Details"),
|
||||
("linkedImages", "Linked Images"),
|
||||
("embeddedImages", "Embedded Images"),
|
||||
("repeats", "Repeat Objects"),
|
||||
];
|
||||
|
||||
/// The object counts of `document.info` and their labels.
|
||||
@@ -97,9 +98,47 @@ pub fn embedding_label(fs_type: u16) -> &'static str {
|
||||
/// An image object as Document Info lists it: (name, width, height, linked file).
|
||||
type ImageRow = (String, u32, u32, Option<String>);
|
||||
|
||||
/// A Repeat object as Document Info lists it: its `repeatObjects` entry, and its row, which gives
|
||||
/// its name and then its kind with the number of instances it draws (for a grid, its rows and
|
||||
/// columns, followed by the number it draws when that is fewer).
|
||||
fn repeat_entry(n: &Node, spec: &RepeatSpec) -> (Value, (String, String)) {
|
||||
let name = n.display_name();
|
||||
let count = |n: usize, one: &str, many: &str| if n == 1 { format!("1 {one}") } else { format!("{n} {many}") };
|
||||
let instances = spec.instances();
|
||||
let mut entry = json!({ "id": n.id.0, "name": name, "instances": instances });
|
||||
let kind = &spec.kind;
|
||||
let detail = match kind {
|
||||
RepeatKind::Radial { .. } => {
|
||||
entry["type"] = json!("radial");
|
||||
count(instances, "instance", "instances")
|
||||
}
|
||||
RepeatKind::Mirror { .. } => {
|
||||
entry["type"] = json!("mirror");
|
||||
count(instances, "instance", "instances")
|
||||
}
|
||||
RepeatKind::Grid { rows, cols, .. } => {
|
||||
// `repeat_transforms` clamps the rows and columns the same way.
|
||||
let (rows, cols) = ((*rows).clamp(1, 500), (*cols).clamp(1, 500));
|
||||
entry["type"] = json!("grid");
|
||||
entry["rows"] = json!(rows);
|
||||
entry["cols"] = json!(cols);
|
||||
let grid = format!("{} × {}", count(rows as usize, "row", "rows"), count(cols as usize, "column", "columns"));
|
||||
if instances < (rows * cols) as usize { format!("{grid}, {}", count(instances, "instance", "instances")) } else { grid }
|
||||
}
|
||||
};
|
||||
(entry, (name, format!("{}, {detail}", kind.label())))
|
||||
}
|
||||
|
||||
/// The Document Info categories of `d` (only `category` when given) from `info` (what
|
||||
/// `document.info` reports), its fonts `(family, style)`, image objects and used patterns.
|
||||
fn sections(d: &Document, info: &Value, fonts: &BTreeSet<super::fonts::UsedFont>, images: &[ImageRow], category: Option<&str>) -> Vec<Section> {
|
||||
/// `document.info` reports), its fonts, its image objects and the rows of its Repeat objects.
|
||||
fn sections(
|
||||
d: &Document,
|
||||
info: &Value,
|
||||
fonts: &BTreeSet<super::fonts::UsedFont>,
|
||||
images: &[ImageRow],
|
||||
repeats: &[(String, String)],
|
||||
category: Option<&str>,
|
||||
) -> Vec<Section> {
|
||||
let names = |k: &str| -> Vec<(String, String)> {
|
||||
info[k].as_array().into_iter().flatten().filter_map(Value::as_str).map(|n| (n.to_string(), String::new())).collect()
|
||||
};
|
||||
@@ -176,6 +215,7 @@ fn sections(d: &Document, info: &Value, fonts: &BTreeSet<super::fonts::UsedFont>
|
||||
"linkedImages" => {
|
||||
images.iter().filter_map(|(name, w, h, link)| Some((name.clone(), format!("{w} × {h} px — {}", link.as_ref()?)))).collect()
|
||||
}
|
||||
"repeats" => repeats.to_vec(),
|
||||
_ => images.iter().filter(|i| i.3.is_none()).map(|(name, w, h, _)| (name.clone(), format!("{w} × {h} px"))).collect(),
|
||||
};
|
||||
Section { id, title, rows }
|
||||
@@ -250,6 +290,9 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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() };
|
||||
// `repeats` and `repeat_rows` hold the `repeatObjects` entries and the rows of the Repeat objects that the walk visits, in walk order.
|
||||
let mut repeats: Vec<Value> = vec![];
|
||||
let mut repeat_rows: Vec<(String, String)> = vec![];
|
||||
for r in roots {
|
||||
r.walk(&mut |n| {
|
||||
let k = match &n.kind {
|
||||
@@ -333,6 +376,11 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let NodeKind::Repeat(spec) = &n.kind {
|
||||
let (entry, row) = repeat_entry(n, spec);
|
||||
repeats.push(entry);
|
||||
repeat_rows.push(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
let font_names: Vec<String> = fonts.iter().map(super::fonts::font_label).collect();
|
||||
@@ -348,6 +396,7 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
"graphicStyles": d.graphic_styles.len(),
|
||||
"graphicStyleNames": styles,
|
||||
"brushNames": brushes,
|
||||
"repeatObjects": repeats,
|
||||
"characterStyles": d.char_styles.len(),
|
||||
"paragraphStyles": d.para_styles.len(),
|
||||
"patterns": d.patterns.len(),
|
||||
@@ -355,7 +404,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, category);
|
||||
let sections = sections(d, &out, &fonts, &image_rows, &repeat_rows, category);
|
||||
if text {
|
||||
return Ok(json!({ "text": report_text(&st.title(), selection_only, §ions) }));
|
||||
}
|
||||
@@ -497,6 +546,75 @@ mod tests {
|
||||
assert!(text.contains("\nCharcoal\n") && text.contains("\nArrow\n") && !text.contains("OBJECTS"), "{text}");
|
||||
}
|
||||
|
||||
/// Document Info › Repeat Objects lists each Repeat object by name with its kind and the number
|
||||
/// of instances it makes (for a grid, its rows and columns, followed by the number it draws when
|
||||
/// that is fewer), one in a group included. With Selection Only it lists the Repeat objects in
|
||||
/// the selection.
|
||||
#[test]
|
||||
fn info_lists_the_repeat_objects() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 400, "height": 400})).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 (a, b, c, d) = (square(&mut s, 0.0), square(&mut s, 100.0), square(&mut s, 200.0), square(&mut s, 300.0));
|
||||
let radial = s.execute("object.repeat.radial", &json!({"ids": [a], "instances": 6})).unwrap()["id"].clone();
|
||||
let grid = s.execute("object.repeat.grid", &json!({"ids": [b], "rows": 2, "cols": 1})).unwrap()["id"].clone();
|
||||
s.execute("layer.setProps", &json!({"id": grid, "name": "Tiles"})).unwrap();
|
||||
let mirror = s.execute("object.repeat.mirror", &json!({"ids": [c]})).unwrap()["id"].clone();
|
||||
s.execute("select.set", &json!({"ids": [mirror, d]})).unwrap();
|
||||
let group = s.execute("object.group", &json!({})).unwrap()["id"].clone();
|
||||
let i = s.execute("document.info", &json!({"category": "repeats"})).unwrap();
|
||||
assert_eq!(
|
||||
i["repeatObjects"],
|
||||
json!([
|
||||
{"id": radial, "name": "<Radial Repeat>", "type": "radial", "instances": 6},
|
||||
{"id": grid, "name": "Tiles", "type": "grid", "rows": 2, "cols": 1, "instances": 2},
|
||||
{"id": mirror, "name": "<Mirror Repeat>", "type": "mirror", "instances": 2},
|
||||
])
|
||||
);
|
||||
assert_eq!((i["sections"][0]["id"].as_str(), i["sections"][0]["title"].as_str()), (Some("repeats"), Some("Repeat Objects")));
|
||||
assert_eq!(
|
||||
i["sections"][0]["rows"],
|
||||
json!([
|
||||
["<Radial Repeat>", "Radial Repeat, 6 instances"],
|
||||
["Tiles", "Grid Repeat, 2 rows × 1 column"],
|
||||
["<Mirror Repeat>", "Mirror Repeat, 2 instances"],
|
||||
])
|
||||
);
|
||||
let names = |s: &mut Session| -> Vec<Value> {
|
||||
let i = s.execute("document.info", &json!({"selectionOnly": true, "category": "repeats"})).unwrap();
|
||||
i["repeatObjects"].as_array().unwrap().iter().map(|r| r["name"].clone()).collect()
|
||||
};
|
||||
s.execute("select.set", &json!({"ids": [grid, group]})).unwrap();
|
||||
assert_eq!(names(&mut s), ["Tiles", "<Mirror Repeat>"]);
|
||||
s.execute("select.set", &json!({"ids": [radial]})).unwrap();
|
||||
assert_eq!(names(&mut s), ["<Radial Repeat>"]);
|
||||
s.execute("select.set", &json!({"ids": [d]})).unwrap();
|
||||
assert!(names(&mut s).is_empty());
|
||||
let text = s.execute("document.info", &json!({"category": "repeats", "format": "text"})).unwrap()["text"].as_str().unwrap().to_string();
|
||||
assert!(text.contains("\nREPEAT OBJECTS\n<Radial Repeat>: Radial Repeat, 6 instances\nTiles: Grid Repeat, 2 rows × 1 column\n"), "{text}");
|
||||
assert!(!text.contains("\nOBJECTS\n"), "{text}");
|
||||
// A radial of 1,000 copies of a 500 × 500 grid draws one copy within the instance budget,
|
||||
// and Document Info reports 1 instance.
|
||||
let e = square(&mut s, 350.0);
|
||||
let big = s.execute("object.repeat.grid", &json!({"ids": [e], "rows": 500, "cols": 500})).unwrap()["id"].clone();
|
||||
let outer = s.execute("object.repeat.radial", &json!({"ids": [big], "instances": 1000})).unwrap()["id"].clone();
|
||||
s.execute("select.set", &json!({"ids": [outer]})).unwrap();
|
||||
let i = s.execute("document.info", &json!({"selectionOnly": true, "category": "repeats"})).unwrap();
|
||||
assert_eq!((i["repeatObjects"][0]["id"].clone(), i["repeatObjects"][0]["instances"].clone()), (outer, json!(1)));
|
||||
assert_eq!((i["repeatObjects"][1]["id"].clone(), i["repeatObjects"][1]["instances"].clone()), (big, json!(250_000)));
|
||||
assert_eq!(i["sections"][0]["rows"][0], json!(["<Radial Repeat>", "Radial Repeat, 1 instance"]));
|
||||
// A 500 × 500 grid of a mirror draws 125,000 copies within the budget, and its row gives
|
||||
// that number after its rows and columns.
|
||||
let f = square(&mut s, 380.0);
|
||||
let pair = s.execute("object.repeat.mirror", &json!({"ids": [f]})).unwrap()["id"].clone();
|
||||
let tiles = s.execute("object.repeat.grid", &json!({"ids": [pair], "rows": 500, "cols": 500})).unwrap()["id"].clone();
|
||||
s.execute("select.set", &json!({"ids": [tiles]})).unwrap();
|
||||
let i = s.execute("document.info", &json!({"selectionOnly": true, "category": "repeats"})).unwrap();
|
||||
assert_eq!((i["repeatObjects"][0]["id"].clone(), i["repeatObjects"][0]["instances"].clone()), (tiles, json!(125_000)));
|
||||
assert_eq!(i["sections"][0]["rows"][0], json!(["<Grid Repeat>", "Grid Repeat, 500 rows × 500 columns, 125000 instances"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedding_permissions_read_from_fs_type() {
|
||||
assert_eq!(embedding_label(0), "embedding allowed");
|
||||
|
||||
@@ -228,6 +228,7 @@ fn the_document_info_report_has_every_category() {
|
||||
"FONT DETAILS",
|
||||
"LINKED IMAGES",
|
||||
"EMBEDDED IMAGES",
|
||||
"REPEAT OBJECTS",
|
||||
] {
|
||||
assert!(text.contains(&format!("\n{h}\n")), "{h}: {text}");
|
||||
}
|
||||
@@ -239,9 +240,12 @@ 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(), 12);
|
||||
assert_eq!(all["sections"].as_array().unwrap().len(), 13);
|
||||
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")));
|
||||
let i = run(&mut s, "document.info", json!({"category": "repeats"}));
|
||||
assert_eq!((i["sections"].as_array().unwrap().len(), i["sections"][0]["id"].as_str()), (1, Some("repeats")));
|
||||
assert!(text.contains("\nREPEAT OBJECTS\nNone\n"), "{text}");
|
||||
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.
|
||||
|
||||
@@ -2528,6 +2528,7 @@
|
||||
Reorder Пренареждане
|
||||
Reorder Appearance Item Пренареждане на елемент от външния вид
|
||||
Repeat Повтаряне
|
||||
Repeat Objects Обекти с повтаряне
|
||||
Repeat Options Опции за повтаряне
|
||||
Repeat Pathfinder Повтаряне на Pathfinder
|
||||
Repeat Tab Повтаряне на табулатора
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1461,6 +1461,7 @@ lighting Light Světlo
|
||||
{count} more rules: Export writes every one. Další pravidla ({count}): Export zapíše všechna.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. Vynecháno nepojmenovaných objektů: {count}. Pojmenujte je, nebo v nabídce panelu zapněte Nepojmenované objekty.
|
||||
All Categories Všechny kategorie
|
||||
Repeat Objects Objekty opakování
|
||||
Save… Uložit…
|
||||
Selection Only Pouze výběr
|
||||
{title} (selection) {title} (výběr)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1378,6 +1378,7 @@ lighting Light Lys
|
||||
{count} more rules: Export writes every one. {count} flere regler: Eksport skriver hver enkelt.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. {count} unavngivne objekter udeladt: giv dem navne, eller aktivér Unavngivne objekter i panelmenuen.
|
||||
All Categories Alle kategorier
|
||||
Repeat Objects Gentagelsesobjekter
|
||||
Save… Gem…
|
||||
Selection Only Kun markering
|
||||
{title} (selection) {title} (markering)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2554,6 +2554,7 @@ mark-style Japanese Japanisch
|
||||
Reorder Neu anordnen
|
||||
Reorder Appearance Item Aussehenselement neu anordnen
|
||||
Repeat Wiederholung
|
||||
Repeat Objects Wiederholungsobjekte
|
||||
Repeat Options Wiederholungsoptionen
|
||||
Repeat Pathfinder Pathfinder wiederholen
|
||||
Repeat Tab Tabulator wiederholen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3096,6 +3096,7 @@ theme Appearance Εμφάνιση
|
||||
Rendering intent: Σκοπός απόδοσης:
|
||||
Reorder Αναδιάταξη
|
||||
Reorder Appearance Item Αναδιάταξη στοιχείου εμφάνισης
|
||||
Repeat Objects Αντικείμενα επανάληψης
|
||||
Repeat Options Επιλογές επανάληψης
|
||||
Repeat Pathfinder Επανάληψη Pathfinder
|
||||
Repeat Tab Επανάληψη στηλοθέτη
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2031 and column 19.
|
@@ -2581,6 +2581,7 @@
|
||||
Reorder Reordenar
|
||||
Reorder Appearance Item Reordenar elemento de apariencia
|
||||
Repeat Repetir
|
||||
Repeat Objects Objetos de repetición
|
||||
Repeat Options Opciones de repetición
|
||||
Repeat Pathfinder Repetir Buscatrazos
|
||||
Repeat Tab Repetir tabulación
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1435,6 +1435,7 @@ lighting Light Valo
|
||||
{count} more rules: Export writes every one. {count} sääntöä lisää: vienti kirjoittaa ne kaikki.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. {count} nimetöntä objektia jätetty pois: nimeä ne tai ota Nimettömät objektit käyttöön paneelin valikosta.
|
||||
All Categories Kaikki luokat
|
||||
Repeat Objects Toisto-objektit
|
||||
Save… Tallenna…
|
||||
Selection Only Vain valinta
|
||||
{title} (selection) {title} (valinta)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2578,6 +2578,7 @@
|
||||
Reorder Réorganiser
|
||||
Reorder Appearance Item Réorganiser l’élément d’aspect
|
||||
Repeat Répétition
|
||||
Repeat Objects Objets de répétition
|
||||
Repeat Options Options de répétition
|
||||
Repeat Pathfinder Répéter Pathfinder
|
||||
Repeat Tab Répéter la tabulation
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3104,6 +3104,7 @@ mark-style Japanese Japán
|
||||
Reorder Átrendezés
|
||||
Reorder Appearance Item Megjelenési elem átrendezése
|
||||
Repeat Ismétlés
|
||||
Repeat Objects Ismétlésobjektumok
|
||||
Repeat Options Ismétlés beállításai
|
||||
Repeat Pathfinder Útvonalkezelő-művelet ismétlése
|
||||
Repeat Tab Tabulátor ismétlése
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1378,6 +1378,7 @@ lighting Light Cahaya
|
||||
{count} more rules: Export writes every one. {count} aturan lagi: Ekspor menuliskan semuanya.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. {count} objek tanpa nama tidak disertakan: beri nama, atau aktifkan Objek Tanpa Nama di menu panel.
|
||||
All Categories Semua kategori
|
||||
Repeat Objects Objek pengulangan
|
||||
Save… Simpan…
|
||||
Selection Only Hanya seleksi
|
||||
{title} (selection) {title} (seleksi)
|
||||
|
||||
|
@@ -2612,6 +2612,7 @@ mark-style Japanese Giapponese
|
||||
Reorder Riordina
|
||||
Reorder Appearance Item Riordina elemento aspetto
|
||||
Repeat Ripetizione
|
||||
Repeat Objects Oggetti di ripetizione
|
||||
Repeat Options Opzioni di ripetizione
|
||||
Repeat Pathfinder Ripeti Elaborazione tracciati
|
||||
Repeat Tab Ripeti tabulazione
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3511,6 +3511,7 @@ file-filter Templates テンプレート
|
||||
Relinked {n} image(s) {n} 個の画像を再リンクしました
|
||||
Relinked {n} image(s); not in that folder: {} {n} 個の画像を再リンクしました。そのフォルダーに無いもの:{}
|
||||
Relinked {} image(s) {} 個の画像を再リンクしました
|
||||
Repeat Objects リピートオブジェクト
|
||||
Repeat Options リピートオプション
|
||||
Reset with Mesh メッシュでリセット
|
||||
Reset with Warp ワープでリセット
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1293,6 +1293,7 @@ lighting Light სინათლე
|
||||
{count} more rules: Export writes every one. კიდევ წესები: {count}. ექსპორტი ყველას ჩაწერს.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. გამოტოვებულია უსახელო ობიექტები: {count}. დაარქვით მათ სახელი ან პანელის მენიუში ჩართეთ „უსახელო ობიექტები“.
|
||||
All Categories ყველა კატეგორია
|
||||
Repeat Objects გამეორების ობიექტები
|
||||
Save… შენახვა…
|
||||
Selection Only მხოლოდ მონიშნული
|
||||
{title} (selection) {title} (მონიშნული)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3504,6 +3504,7 @@ file-filter Templates 템플릿
|
||||
Relinked {n} image(s) 이미지 {n}개를 다시 연결했습니다
|
||||
Relinked {n} image(s); not in that folder: {} 이미지 {n}개를 다시 연결했습니다. 해당 폴더에 없음: {}
|
||||
Relinked {} image(s) 이미지 {}개를 다시 연결했습니다
|
||||
Repeat Objects 반복 개체
|
||||
Repeat Options 반복 옵션
|
||||
Reset with Mesh 메시로 재설정
|
||||
Reset with Warp 왜곡으로 재설정
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1392,6 +1392,7 @@ lighting Light Licht
|
||||
{count} more rules: Export writes every one. {count} regels meer: Export schrijft ze allemaal.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. {count} naamloze objecten weggelaten: geef ze een naam, of zet Naamloze objecten aan in het paneelmenu.
|
||||
All Categories Alle categorieën
|
||||
Repeat Objects Herhalingsobjecten
|
||||
Save… Opslaan…
|
||||
Selection Only Alleen selectie
|
||||
{title} (selection) {title} (selectie)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1375,6 +1375,7 @@ lighting Light Światło
|
||||
{count} more rules: Export writes every one. Więcej reguł: {count}. Eksport zapisze wszystkie.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. Pominięto obiekty bez nazwy: {count}. Nadaj im nazwy lub włącz Obiekty bez nazwy w menu panelu.
|
||||
All Categories Wszystkie kategorie
|
||||
Repeat Objects Obiekty powtórzenia
|
||||
Save… Zapisz…
|
||||
Selection Only Tylko zaznaczenie
|
||||
{title} (selection) {title} (zaznaczenie)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2139,6 +2139,7 @@
|
||||
Reorder Reordenar
|
||||
Reorder Appearance Item Reordenar Item de Aparência
|
||||
Repeat Repetir
|
||||
Repeat Objects Objetos de Repetição
|
||||
Repeat Pathfinder Repetir Localizador de Caminhos
|
||||
Repeat Tab Repetir Parada de Tabulação
|
||||
Repeat {name} Repetir {name}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2608,6 +2608,7 @@ mark-style Japanese Японский
|
||||
Reorder Изменить порядок
|
||||
Reorder Appearance Item Изменить порядок элемента оформления
|
||||
Repeat Повторить
|
||||
Repeat Objects Объекты повтора
|
||||
Repeat Options Параметры повтора
|
||||
Repeat Pathfinder Повторить обработку контуров
|
||||
Repeat Tab Повторить табуляцию
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2528,6 +2528,7 @@
|
||||
Reorder Ändra ordning
|
||||
Reorder Appearance Item Ändra ordning på utseendeobjekt
|
||||
Repeat Upprepa
|
||||
Repeat Objects Upprepningsobjekt
|
||||
Repeat Options Upprepningsalternativ
|
||||
Repeat Pathfinder Upprepa banöverlappning
|
||||
Repeat Tab Upprepa tabb
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -2604,6 +2604,7 @@ mark-style Japanese Японський
|
||||
Reorder Змінити порядок
|
||||
Reorder Appearance Item Змінити порядок елемента оформлення
|
||||
Repeat Повторити
|
||||
Repeat Objects Об’єкти повтору
|
||||
Repeat Options Параметри повтору
|
||||
Repeat Pathfinder Повторити обробку контурів
|
||||
Repeat Tab Повторити табуляцію
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 726 and column 40.
|
@@ -2871,6 +2871,7 @@ mark-style Japanese Kiểu Nhật
|
||||
Reorder Sắp xếp lại
|
||||
Reorder Appearance Item Sắp xếp lại mục diện mạo
|
||||
Repeat Lặp lại
|
||||
Repeat Objects Đối tượng lặp lại
|
||||
Repeat Options Tùy chọn lặp lại
|
||||
Repeat Pathfinder Lặp lại Pathfinder
|
||||
Repeat Tab Tab lặp lại
|
||||
|
||||
|
@@ -2490,6 +2490,7 @@ font-script Japanese 汉字
|
||||
Reorder 重新排序
|
||||
Reorder Appearance Item 重新排序外观项目
|
||||
Repeat 重复
|
||||
Repeat Objects 重复对象
|
||||
Repeat Options 重复选项
|
||||
Repeat Pathfinder 重复路径查找器
|
||||
Repeat Tab 重复制表位
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3360,6 +3360,7 @@ font-script Japanese 漢字
|
||||
Reorder 重新排序
|
||||
Reorder Appearance Item 重新排序外觀項目
|
||||
Repeat 重複
|
||||
Repeat Objects 重複物件
|
||||
Repeat Options 重複選項
|
||||
Repeat Pathfinder 重複路徑管理員
|
||||
Repeat Tab 重複定位點
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1,6 +1,6 @@
|
||||
//! Document Info panel: the document's setup and what it contains (objects by kind, graphic
|
||||
//! 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)
|
||||
//! 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).
|
||||
|
||||
use egui::Ui;
|
||||
@@ -87,7 +87,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
|
||||
/// The rows of Document Info section `id` labelled by a name (an artboard, a style, a swatch, a
|
||||
/// font, an image) rather than an interface label: every row of the lists, and in Document the
|
||||
/// font, an image, a repeat object) rather than an interface label: every row of the lists, and in Document the
|
||||
/// artboards, which follow the Artboards count. Names are shown as they are.
|
||||
fn named_rows(id: &str, rows: &[Value]) -> std::ops::Range<usize> {
|
||||
match id {
|
||||
@@ -183,7 +183,13 @@ 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("Brushes") && menu_text.contains("Save…"), "{menu_text}");
|
||||
assert!(
|
||||
menu_text.contains("Font Details")
|
||||
&& menu_text.contains("Brushes")
|
||||
&& menu_text.contains("Repeat Objects")
|
||||
&& 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);
|
||||
@@ -199,6 +205,6 @@ mod tests {
|
||||
let r = app.run("docInfo.save", json!({"path": "/tmp/info.txt"})).unwrap();
|
||||
assert_eq!(r["path"], "/tmp/info.txt");
|
||||
let report = String::from_utf8(written.borrow().clone()).unwrap();
|
||||
assert!(report.starts_with("Document Info: ") && report.contains("\nFONT DETAILS\n"), "{report}");
|
||||
assert!(report.starts_with("Document Info: ") && report.contains("\nFONT DETAILS\n") && report.contains("\nREPEAT OBJECTS\n"), "{report}");
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -3196,8 +3196,11 @@ result carries the same files as a zip (`{name: "<name>.zip", dataBase64}`, entr
|
||||
|
||||
`document.info {selectionOnly?, category?, format?: "text"}` adds `sections` (`[{id, title, rows: [[label, value]]}]`:
|
||||
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
|
||||
embeddedImages, repeats), `brushNames` (the Brushes panel's brushes; with `selectionOnly`, the brushes the selected
|
||||
objects' strokes use), `repeatObjects` (`[{id, name, type, instances, rows?, cols?}]`: every Repeat object, those in
|
||||
groups and in other repeats included, with its type `radial`, `grid` or `mirror` and the number of instances it makes,
|
||||
and a grid's `rows` and `cols`; with `selectionOnly`, the Repeat objects in the selection)
|
||||
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.
|
||||
|
||||
@@ -733,6 +733,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
Bristle Brush Options and `brush.new` take a bristle brush name of at most 31 characters, as the Brushes page states for the Bristle Brush Options. The Name field stops taking characters at 31, and `brush.new` and `brush.options` refuse a longer new name with an error that names the limit. The page does not say how the limit counts characters or what happens to longer names already saved; counting characters and the number added to a taken name, keeping longer saved names, and leaving `brush.duplicate` unlimited are VectorCraft's own rules. A bristle brush that a document or a saved file already holds under a longer name keeps it, and the other brush types take a name of any length. Test: `bristle_brush_names_have_at_most_31_characters` in `tests_brushsym.rs`. Public reference: [Brushes](https://web.archive.org/web/20241230205225/https://helpx.adobe.com/illustrator/using/brushes.html) (archived copy).
|
||||
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`).
|
||||
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`).
|
||||
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.
|
||||
- **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.
|
||||
|
||||
Reference in New Issue
Block a user