Merge pull request #5022 from mockydevs/feat/export-all-open-4920

Fix #4920: Document Tools exports every open document
This commit is contained in:
BFlat
2026-11-01 09:38:50 +07:00
committed by GitHub
34 changed files with 284 additions and 2 deletions
+42 -1
View File
@@ -1,6 +1,7 @@
//! Document Tools (`addon.document.*`), a built-in add-on: Save a Timestamped Copy writes a copy of
//! the document named with the date and time through File › Save a Copy's save; New Symbols from
//! Layers makes a symbol of each layer's art as New Symbol does. Its submenu (Object › Plug-ins ›
//! Layers makes a symbol of each layer's art as New Symbol does; Export All Open Documents writes a
//! copy of every open document to one folder as Save a Copy does (#4920). Its submenu (Object › Plug-ins ›
//! Document Tools) is the UI's.
#[cfg(test)]
@@ -30,6 +31,15 @@ pub fn specs() -> Vec<CommandSpec> {
has_clock,
save_timestamped_copy
),
cmd!(
"addon.document.exportAllOpen",
"Export All Open Documents",
["Object", "Plug-ins", "Document Tools"],
None,
"{folder, format?: vectorcraft|pdf|svg|svgz|ai|eps (default: pdf)} write a copy of every open document, in tab order, to `folder` as File › Save a Copy writes it, each named after its document (<name>.<ext>; -2, -3… added when that name is taken, so no file is replaced): every document keeps its path, title and modified state, and no undo step is made. A document that can't be written is listed in `failed` and the others are still written; the active document stays active → {files: [{title, path, warnings}], failed: [{title, error}], format}",
has_doc,
export_all_open
),
cmd!(
"addon.document.symbolsFromLayers",
"New Symbols from Layers",
@@ -88,6 +98,37 @@ fn save_timestamped_copy(s: &mut Session, p: &Value) -> Result<Value> {
fileio::save_document(s, SaveMode::Copy, &json!({ "path": path, "format": format.id }))
}
fn export_all_open(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "addon.document.exportAllOpen";
let format = fileio::save_format(Some(str_param(p, "format").unwrap_or("pdf")), None).map_err(|e| bad(C, e))?;
let folder = str_param(p, "folder").map(str::trim).filter(|f| !f.is_empty()).ok_or_else(|| bad(C, "give `folder`"))?.to_string();
let ext = format.extensions.first().copied().ok_or_else(|| bad(C, "the format has no file extension"))?;
let (mut files, mut failed, mut written) = (vec![], vec![], Vec::<String>::new());
let active = s.active;
for i in 0..s.docs.len() {
// Each document in turn is the one Save a Copy writes; switching tabs this way leaves every
// document's tool and interaction alone, and the active one comes back below.
s.active = Some(i);
let Ok(st) = s.doc() else { continue };
let title = st.doc.title.clone();
let stem = fileio::file_stem(st.path.as_deref().unwrap_or(&title));
// Names the copies already took count as taken, as do files on disk.
let Some(path) = free_name(&folder, &stem, ext, |p| written.iter().any(|w| w == p) || std::path::Path::new(p).exists()) else {
failed.push(json!({ "title": title, "error": "every numbered name for the copy is taken" }));
continue;
};
match fileio::save_document(s, SaveMode::Copy, &json!({ "path": path, "format": format.id })) {
Ok(r) => {
written.push(path.clone());
files.push(json!({ "title": title, "path": r.get("path").cloned().unwrap_or(json!(path)), "warnings": r.get("warnings").cloned().unwrap_or(json!([])) }));
}
Err(e) => failed.push(json!({ "title": title, "error": e.to_string() })),
}
}
s.active = active;
Ok(json!({ "files": files, "failed": failed, "format": format.id }))
}
/// `n` with each layer in it, and itself, made a group of the same art and clipping (a symbol holds
/// no layers).
fn without_layers(n: &Node) -> Node {
@@ -194,3 +194,26 @@ fn the_layers_highlighted_in_the_layers_panel_are_the_default() {
let r = s.execute("addon.document.symbolsFromLayers", &json!({})).unwrap();
assert_eq!((r["symbols"].clone(), r["skipped"].as_array().unwrap().len()), (json!(["Icons"]), 1));
}
/// #4920: Export All Open Documents writes an SVG of each open document; a folder that can't be
/// written lists every document in `failed`, and either way the active document stays active.
#[test]
fn export_all_open_documents_writes_each_and_reports_failures() {
let dir = std::env::temp_dir().join(format!("vc-export-all-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let mut s = Session::new();
s.execute("file.new", &json!({"width": 100, "height": 100, "name": "one"})).unwrap();
s.execute("file.new", &json!({"width": 100, "height": 100, "name": "two"})).unwrap();
s.execute("document.activate", &json!({"index": 0})).unwrap();
let r = s.execute("addon.document.exportAllOpen", &json!({"folder": dir, "format": "svg"})).unwrap();
assert_eq!(r["files"].as_array().unwrap().len(), 2, "{r}");
assert!(dir.join("one.svg").exists() && dir.join("two.svg").exists());
assert_eq!(s.active_index(), Some(0));
assert!(s.documents().iter().all(|d| d.path.is_none()), "the documents stay unsaved");
let missing = dir.join("missing").join("deeper");
let r = s.execute("addon.document.exportAllOpen", &json!({"folder": missing, "format": "svg"})).unwrap();
assert_eq!((r["files"].as_array().unwrap().len(), r["failed"].as_array().unwrap().len()), (0, 2), "{r}");
assert_eq!(s.active_index(), Some(0));
let _ = std::fs::remove_dir_all(&dir);
}
+28 -1
View File
@@ -1,7 +1,8 @@
//! Document Tools in the interface: its submenu (Object › Plug-ins › Document Tools), which runs
//! Save a Timestamped Copy, New Symbols from Layers and Convert Layers to Symbols (the same, with
//! each layer's art replaced by an instance). Save a Timestamped Copy of a document never saved
//! asks for the folder to put it in ([`save_copy`]).
//! asks for the folder to put it in ([`save_copy`]). Export All Open Documents as PDF or SVG asks
//! for the folder the copies go in ([`export_all`], #4920).
use serde_json::{Value, json};
@@ -10,6 +11,8 @@ use crate::menus::Item;
/// Save a Timestamped Copy.
pub const SAVE_COPY: &str = "addon.document.saveTimestampedCopy";
/// Export All Open Documents.
pub const EXPORT_ALL: &str = "addon.document.exportAllOpen";
/// New Symbols from Layers (Convert Layers to Symbols: with their art replaced by instances).
const SYMBOLS: &str = "addon.document.symbolsFromLayers";
@@ -17,6 +20,8 @@ const SYMBOLS: &str = "addon.document.symbolsFromLayers";
pub fn menu() -> Vec<Item> {
vec![
Item::Cmd("Save a Timestamped Copy", SAVE_COPY, json!({})),
Item::Cmd("Export All Open Documents as PDF", EXPORT_ALL, json!({ "format": "pdf" })),
Item::Cmd("Export All Open Documents as SVG", EXPORT_ALL, json!({ "format": "svg" })),
Item::Sep,
Item::Cmd("New Symbols from Layers", SYMBOLS, json!({})),
Item::Cmd("Convert Layers to Symbols", SYMBOLS, json!({ "replace": true })),
@@ -35,5 +40,27 @@ pub(crate) fn save_copy(app: &mut VectorcraftApp, p: &Value) -> Result<Value, St
Ok(r)
}
/// Export All Open Documents with params `p`: without a `folder`, the copies go in the folder
/// picked. The status bar says how many were written and names the first that failed.
pub(crate) fn export_all(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
let mut p = if p.is_object() { p.clone() } else { json!({}) };
if p.get("folder").is_none() {
p["folder"] = json!(crate::picks::folder(app).ok_or("cancelled")?);
}
let r = app.session.execute(EXPORT_ALL, &p).map_err(|e| e.to_string())?;
let written = r["files"].as_array().map_or(0, Vec::len);
let failed = r["failed"].as_array().cloned().unwrap_or_default();
let status = match failed.first() {
Some(f) => {
let args = [("title", f["title"].as_str().unwrap_or_default()), ("error", f["error"].as_str().unwrap_or_default())];
let (one, other) = ("Exported {n} document; {title} wasn't written: {error}", "Exported {n} documents; {title} wasn't written: {error}");
crate::i18n::msg_n(written as u64, one, other, &args)
}
None => crate::i18n::msg_n(written as u64, "Exported {n} document", "Exported {n} documents", &[]),
};
app.status(status);
Ok(r)
}
#[cfg(test)]
mod tests;
@@ -82,3 +82,44 @@ fn new_symbols_from_layers_and_convert_layers_to_symbols() {
.all(|l| matches!(l.children().unwrap()[..], [ref one] if matches!(one.kind, vectorcraft_doc::NodeKind::SymbolInstance { .. })))
);
}
/// #4920: Export All Open Documents as PDF asks for a folder and writes one PDF per open document,
/// named after each, without replacing a file there, and leaves every document as it was.
#[test]
fn export_all_open_documents_writes_each_to_the_folder_picked() {
let dir = folder("export-all");
let mut app = app();
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 40})).unwrap();
app.run("document.save", json!({"path": dir.join("card.vectorcraft")})).unwrap();
app.run("file.new", json!({"width": 200, "height": 100, "name": "card"})).unwrap();
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
let untitled = app.session.doc().unwrap().doc.title.clone();
let out = dir.join("out");
std::fs::create_dir_all(&out).unwrap();
std::fs::write(out.join(format!("{untitled}.pdf")), b"keep").unwrap();
let picked = out.to_string_lossy().into_owned();
app.services.pick_folder = Some(Box::new(move || Some(picked.clone())));
let active = app.session.active_index();
click(&mut app, "Export All Open Documents as PDF");
let mut names = files(&out);
names.sort();
let mut want = vec!["card.pdf".to_string(), "card-2.pdf".into(), format!("{untitled}.pdf"), format!("{untitled}-2.pdf")];
want.sort();
assert_eq!(names, want, "one per document, the taken names numbered");
assert_eq!(std::fs::read(out.join(format!("{untitled}.pdf"))).unwrap(), b"keep", "no file is replaced");
assert!(std::fs::read(out.join("card.pdf")).unwrap().starts_with(b"%PDF"));
assert_eq!(app.session.active_index(), active, "the active document stays active");
assert!(
app.session.documents().iter().all(|d| d.path.as_ref().is_none_or(|p| p.ends_with("card.vectorcraft"))),
"no document takes a copy's path"
);
assert!(app.ui.status.starts_with("Exported 3 documents"), "{}", app.ui.status);
// Cancelled: nothing written.
app.services.pick_folder = Some(Box::new(|| None));
assert_eq!(app.run(super::EXPORT_ALL, json!({})).unwrap_err(), "cancelled");
assert_eq!(files(&out).len(), 4);
// The engine command refuses a missing folder and an unknown format.
assert!(app.session.execute(super::EXPORT_ALL, &json!({})).is_err());
assert!(app.session.execute(super::EXPORT_ALL, &json!({"folder": out, "format": "doc"})).is_err());
let _ = std::fs::remove_dir_all(&dir);
}
+5
View File
@@ -5993,6 +5993,11 @@ breakText Glyphs Глифи
Odia Ория
Urdu Урду
VectorCraft {release} and later (version {n}) VectorCraft {release} и по-нови (версия {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Експортиране на всички отворени документи като PDF
Export All Open Documents as SVG Експортиране на всички отворени документи като SVG
@msgplural Exported {n} document|Exported {n} documents Експортиран е {n} документ|Експортирани са {n} документа
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Експортиран е {n} документ; {title} не беше записан: {error}|Експортирани са {n} документа; {title} не беше записан: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Тип на символа:
Dynamic Symbol Динамичен символ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6025,6 +6025,11 @@ shape Open Otvoren
Odia Odija
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} i novije (verzija {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Izvezi sve otvorene dokumente kao PDF
Export All Open Documents as SVG Izvezi sve otvorene dokumente kao SVG
@msgplural Exported {n} document|Exported {n} documents Izvezen je {n} dokument|Izvezena su {n} dokumenta|Izvezeno je {n} dokumenata
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Izvezen je {n} dokument; {title} nije zapisan: {error}|Izvezena su {n} dokumenta; {title} nije zapisan: {error}|Izvezeno je {n} dokumenata; {title} nije zapisan: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Vrsta simbola:
Dynamic Symbol Dinamički simbol
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6099,6 +6099,11 @@ breakText Glyphs Glifs
Odia Oriya
Urdu Urdú
VectorCraft {release} and later (version {n}) VectorCraft {release} i posteriors (versió {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Exporta tots els documents oberts com a PDF
Export All Open Documents as SVG Exporta tots els documents oberts com a SVG
@msgplural Exported {n} document|Exported {n} documents S’ha exportat {n} document|S’han exportat {n} documents
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} S’ha exportat {n} document; no s’ha escrit {title}: {error}|S’han exportat {n} documents; no s’ha escrit {title}: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Tipus de símbol:
Dynamic Symbol Símbol dinàmic
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6109,6 +6109,11 @@ breakText Glyphs Glyfy
Odia Urijština
Urdu Urdština
VectorCraft {release} and later (version {n}) VectorCraft {release} a novější (verze {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Exportovat všechny otevřené dokumenty jako PDF
Export All Open Documents as SVG Exportovat všechny otevřené dokumenty jako SVG
@msgplural Exported {n} document|Exported {n} documents Exportován {n} dokument|Exportovány {n} dokumenty|Exportováno {n} dokumentů
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Exportován {n} dokument; {title} nebyl zapsán: {error}|Exportovány {n} dokumenty; {title} nebyl zapsán: {error}|Exportováno {n} dokumentů; {title} nebyl zapsán: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Typ symbolu:
Dynamic Symbol Dynamický symbol
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6008,6 +6008,11 @@ breakText Glyphs Glyffer
Odia Oriya
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} og nyere (version {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Eksportér alle åbne dokumenter som PDF
Export All Open Documents as SVG Eksportér alle åbne dokumenter som SVG
@msgplural Exported {n} document|Exported {n} documents Eksporterede {n} dokument|Eksporterede {n} dokumenter
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Eksporterede {n} dokument; {title} blev ikke skrevet: {error}|Eksporterede {n} dokumenter; {title} blev ikke skrevet: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Symboltype:
Dynamic Symbol Dynamisk symbol
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -5694,6 +5694,11 @@ breakText Glyphs Glyphen
Odia Oriya
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} und neuer (Version {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Alle geöffneten Dokumente als PDF exportieren
Export All Open Documents as SVG Alle geöffneten Dokumente als SVG exportieren
@msgplural Exported {n} document|Exported {n} documents {n} Dokument exportiert|{n} Dokumente exportiert
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} {n} Dokument exportiert; {title} wurde nicht geschrieben: {error}|{n} Dokumente exportiert; {title} wurde nicht geschrieben: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Symboltyp:
Dynamic Symbol Dynamisches Symbol
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6061,6 +6061,11 @@ breakText Glyphs Γλύφοι
Odia Όντια
Urdu Ούρντου
VectorCraft {release} and later (version {n}) VectorCraft {release} και νεότερες (έκδοση {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Εξαγωγή όλων των ανοιχτών εγγράφων ως PDF
Export All Open Documents as SVG Εξαγωγή όλων των ανοιχτών εγγράφων ως SVG
@msgplural Exported {n} document|Exported {n} documents Εξήχθη {n} έγγραφο|Εξήχθησαν {n} έγγραφα
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Εξήχθη {n} έγγραφο; το {title} δεν γράφτηκε: {error}|Εξήχθησαν {n} έγγραφα; το {title} δεν γράφτηκε: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Τύπος συμβόλου:
Dynamic Symbol Δυναμικό σύμβολο
Can't render this file because it contains an unexpected character in line 164 and column 5.
+5
View File
@@ -6051,6 +6051,11 @@ breakText Glyphs Glifos
Odia Oriya
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} y posteriores (versión {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Exportar todos los documentos abiertos como PDF
Export All Open Documents as SVG Exportar todos los documentos abiertos como SVG
@msgplural Exported {n} document|Exported {n} documents Se exportó {n} documento|Se exportaron {n} documentos
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Se exportó {n} documento; no se escribió {title}: {error}|Se exportaron {n} documentos; no se escribió {title}: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Tipo de símbolo:
Dynamic Symbol Símbolo dinámico
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6139,6 +6139,11 @@ breakText Glyphs Merkit
Odia Orija
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} ja uudemmat (versio {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Vie kaikki avoimet asiakirjat PDF-muodossa
Export All Open Documents as SVG Vie kaikki avoimet asiakirjat SVG-muodossa
@msgplural Exported {n} document|Exported {n} documents Vietiin {n} asiakirja|Vietiin {n} asiakirjaa
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Vietiin {n} asiakirja; {title} jäi kirjoittamatta: {error}|Vietiin {n} asiakirjaa; {title} jäi kirjoittamatta: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Symbolin tyyppi:
Dynamic Symbol Dynaaminen symboli
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6106,6 +6106,11 @@ breakText Glyphs Glyphes
Odia Odia
Urdu Ourdou
VectorCraft {release} and later (version {n}) VectorCraft {release} et ultérieures (version {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Exporter tous les documents ouverts en PDF
Export All Open Documents as SVG Exporter tous les documents ouverts en SVG
@msgplural Exported {n} document|Exported {n} documents {n} document exporté|{n} documents exportés
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} {n} document exporté; {title} n’a pas été écrit : {error}|{n} documents exportés; {title} n’a pas été écrit : {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Type de symbole :
Dynamic Symbol Symbole dynamique
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6095,6 +6095,11 @@ breakText Glyphs Glifák
Odia Odia
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} és újabb (verzió: {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Az összes megnyitott dokumentum exportálása PDF-ként
Export All Open Documents as SVG Az összes megnyitott dokumentum exportálása SVG-ként
@msgplural Exported {n} document|Exported {n} documents Exportált dokumentumok: {n}
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Exportált dokumentumok: {n}; {title} nem lett kiírva: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Szimbólum típusa:
Dynamic Symbol Dinamikus szimbólum
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6008,6 +6008,11 @@ breakText Glyphs Glif
Odia Oriya
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} dan yang lebih baru (versi {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Ekspor Semua Dokumen Terbuka sebagai PDF
Export All Open Documents as SVG Ekspor Semua Dokumen Terbuka sebagai SVG
@msgplural Exported {n} document|Exported {n} documents Berhasil mengekspor {n} dokumen
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Berhasil mengekspor {n} dokumen; {title} tidak ditulis: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Jenis simbol:
Dynamic Symbol Simbol dinamis
Can't render this file because it contains an unexpected character in line 2190 and column 5.
+5
View File
@@ -5728,6 +5728,11 @@ breakText Glyphs Glifi
Odia Odia
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} e successive (versione {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Esporta tutti i documenti aperti come PDF
Export All Open Documents as SVG Esporta tutti i documenti aperti come SVG
@msgplural Exported {n} document|Exported {n} documents Esportato {n} documento|Esportati {n} documenti
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Esportato {n} documento; {title} non è stato scritto: {error}|Esportati {n} documenti; {title} non è stato scritto: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Tipo di simbolo:
Dynamic Symbol Simbolo dinamico
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -5795,6 +5795,11 @@ breakText Glyphs 字形
Odia オディア語
Urdu ウルドゥー語
VectorCraft {release} and later (version {n}) VectorCraft {release} 以降 (バージョン {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF 開いているすべてのドキュメントを PDF として書き出し
Export All Open Documents as SVG 開いているすべてのドキュメントを SVG として書き出し
@msgplural Exported {n} document|Exported {n} documents {n} 個のドキュメントを書き出しました
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} {n} 個のドキュメントを書き出しました; {title} は書き出されませんでした: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: シンボルの種類:
Dynamic Symbol ダイナミックシンボル
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6098,6 +6098,11 @@ breakText Glyphs გლიფები
Odia ორია
Urdu ურდუ
VectorCraft {release} and later (version {n}) VectorCraft {release} და უფრო ახალი (ვერსია {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF ყველა ღია დოკუმენტის ექსპორტი PDF-ად
Export All Open Documents as SVG ყველა ღია დოკუმენტის ექსპორტი SVG-ად
@msgplural Exported {n} document|Exported {n} documents ექსპორტირებულია დოკუმენტი ({n})
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} ექსპორტირებულია დოკუმენტი ({n}); {title} არ ჩაიწერა: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: სიმბოლოს ტიპი:
Dynamic Symbol დინამიკური სიმბოლო
Can't render this file because it is too large.
+5
View File
@@ -5691,6 +5691,11 @@ breakText Glyphs 글리프
Odia 오리야어
Urdu 우르두어
VectorCraft {release} and later (version {n}) VectorCraft {release} 이상 (버전 {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF 열린 모든 문서를 PDF로 내보내기
Export All Open Documents as SVG 열린 모든 문서를 SVG로 내보내기
@msgplural Exported {n} document|Exported {n} documents 문서 {n}개를 내보냈습니다
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} 문서 {n}개를 내보냈습니다; {title}을(를) 쓰지 못했습니다: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: 심볼 유형:
Dynamic Symbol 동적 심볼
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6052,6 +6052,11 @@ breakText Glyphs Glyphs
Odia Odia
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} en nieuwer (versie {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Alle geopende documenten exporteren als PDF
Export All Open Documents as SVG Alle geopende documenten exporteren als SVG
@msgplural Exported {n} document|Exported {n} documents {n} document geëxporteerd|{n} documenten geëxporteerd
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} {n} document geëxporteerd; {title} is niet geschreven: {error}|{n} documenten geëxporteerd; {title} is niet geschreven: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Symbooltype:
Dynamic Symbol Dynamisch symbool
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -5964,6 +5964,11 @@ breakText Glyphs Glify
Odia Orija
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} i nowsze (wersja {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Eksportuj wszystkie otwarte dokumenty jako PDF
Export All Open Documents as SVG Eksportuj wszystkie otwarte dokumenty jako SVG
@msgplural Exported {n} document|Exported {n} documents Wyeksportowano {n} dokument|Wyeksportowano {n} dokumenty|Wyeksportowano {n} dokumentów
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Wyeksportowano {n} dokument; nie zapisano {title}: {error}|Wyeksportowano {n} dokumenty; nie zapisano {title}: {error}|Wyeksportowano {n} dokumentów; nie zapisano {title}: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Typ symbolu:
Dynamic Symbol Symbol dynamiczny
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6028,6 +6028,11 @@ breakText Glyphs Glifos
Odia Oriá
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} e posteriores (versão {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Exportar todos os documentos abertos como PDF
Export All Open Documents as SVG Exportar todos os documentos abertos como SVG
@msgplural Exported {n} document|Exported {n} documents {n} documento exportado|{n} documentos exportados
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} {n} documento exportado; {title} não foi gravado: {error}|{n} documentos exportados; {title} não foi gravado: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Tipo de símbolo:
Dynamic Symbol Símbolo dinâmico
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -6086,6 +6086,11 @@ breakText Glyphs Глифы
Odia Ория
Urdu Урду
VectorCraft {release} and later (version {n}) VectorCraft {release} и новее (версия {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Экспортировать все открытые документы в PDF
Export All Open Documents as SVG Экспортировать все открытые документы в SVG
@msgplural Exported {n} document|Exported {n} documents Экспортирован {n} документ|Экспортировано {n} документа|Экспортировано {n} документов
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Экспортирован {n} документ; {title} не записан: {error}|Экспортировано {n} документа; {title} не записан: {error}|Экспортировано {n} документов; {title} не записан: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Тип символа:
Dynamic Symbol Динамический символ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -6161,6 +6161,11 @@ breakText Glyphs Glyfer
Odia Oriya
Urdu Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} och senare (version {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Exportera alla öppna dokument som PDF
Export All Open Documents as SVG Exportera alla öppna dokument som SVG
@msgplural Exported {n} document|Exported {n} documents Exporterade {n} dokument|Exporterade {n} dokument
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Exporterade {n} dokument; {title} skrevs inte: {error}|Exporterade {n} dokument; {title} skrevs inte: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Symboltyp:
Dynamic Symbol Dynamisk symbol
Can't render this file because it contains an unexpected character in line 5 and column 25.
+5
View File
@@ -6066,6 +6066,11 @@ breakText Glyphs Glifler
Odia Oriya dili
Urdu Urduca
VectorCraft {release} and later (version {n}) VectorCraft {release} ve sonrası (sürüm {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Tüm Açık Belgeleri PDF Olarak Dışa Aktar
Export All Open Documents as SVG Tüm Açık Belgeleri SVG Olarak Dışa Aktar
@msgplural Exported {n} document|Exported {n} documents {n} belge dışa aktarıldı
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} {n} belge dışa aktarıldı; {title} yazılamadı: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Sembol Türü:
Dynamic Symbol Dinamik Sembol
Can't render this file because it contains an unexpected character in line 5 and column 25.
+5
View File
@@ -5708,6 +5708,11 @@ breakText Glyphs Гліфи
Odia Одія
Urdu Урду
VectorCraft {release} and later (version {n}) VectorCraft {release} і новіші (версія {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Експортувати всі відкриті документи як PDF
Export All Open Documents as SVG Експортувати всі відкриті документи як SVG
@msgplural Exported {n} document|Exported {n} documents Експортовано {n} документ|Експортовано {n} документи|Експортовано {n} документів
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Експортовано {n} документ; {title} не записано: {error}|Експортовано {n} документи; {title} не записано: {error}|Експортовано {n} документів; {title} не записано: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Тип символу:
Dynamic Symbol Динамічний символ
Can't render this file because it contains an unexpected character in line 178 and column 5.
+5
View File
@@ -5711,6 +5711,11 @@ breakText Glyphs Glyph
Odia Tiếng Odia
Urdu Tiếng Urdu
VectorCraft {release} and later (version {n}) VectorCraft {release} trở lên (phiên bản {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF Xuất tất cả tài liệu đang mở dưới dạng PDF
Export All Open Documents as SVG Xuất tất cả tài liệu đang mở dưới dạng SVG
@msgplural Exported {n} document|Exported {n} documents Đã xuất {n} tài liệu
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} Đã xuất {n} tài liệu; không ghi được {title}: {error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: Kiểu ký hiệu:
Dynamic Symbol Ký hiệu động
Can't render this file because it contains an unexpected character in line 200 and column 5.
+5
View File
@@ -5951,6 +5951,11 @@ breakText Glyphs 字形
Odia 奥里亚语
Urdu 乌尔都语
VectorCraft {release} and later (version {n}) VectorCraft {release} 及更高版本 (版本 {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF 将所有打开的文档导出为 PDF
Export All Open Documents as SVG 将所有打开的文档导出为 SVG
@msgplural Exported {n} document|Exported {n} documents 已导出 {n} 个文档
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} 已导出 {n} 个文档; 未写入 {title}:{error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: 符号类型:
Dynamic Symbol 动态符号
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -5748,6 +5748,11 @@ breakText Glyphs 字符
Odia 歐利亞文
Urdu 烏都文
VectorCraft {release} and later (version {n}) VectorCraft {release} 及更新版本 (版本 {n})
# Document Tools add-on: Export All Open Documents (#4920).
Export All Open Documents as PDF 將所有開啟的文件匯出為 PDF
Export All Open Documents as SVG 將所有開啟的文件匯出為 SVG
@msgplural Exported {n} document|Exported {n} documents 已匯出 {n} 份文件
@msgplural Exported {n} document; {title} wasn't written: {error}|Exported {n} documents; {title} wasn't written: {error} 已匯出 {n} 份文件; 未寫入 {title}:{error}
# Symbol Options › Symbol Type (dynamic symbols)
Symbol Type: 符號類型:
Dynamic Symbol 動態符號
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -531,6 +531,7 @@ fn run_ui_part6(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<
crate::addons::pathtools::shared_edge::COMMAND => crate::addons::pathtools::shared_edge::open(app),
crate::addons::arrange::ZOOM => crate::addons::arrange::zoom(app),
crate::addons::document::SAVE_COPY => crate::addons::document::save_copy(app, p),
crate::addons::document::EXPORT_ALL => crate::addons::document::export_all(app, p),
"tool.setOption" => app.session.set_tool_option_cmd(p),
"effect.dialog" => crate::dialogs::open_effect_dialog(app, p),
"ui.recolorDialog" => crate::dialogs::recolor::open(app, p),
+12
View File
@@ -6401,6 +6401,18 @@ command is off where there is no clock (the web). → `{path, format, bytes, war
{"name":"run_command","arguments":{"command":"addon.document.saveTimestampedCopy","params":{"folder":"/Users/me/Versions","format":"pdf"}}}
```
`addon.document.exportAllOpen {folder, format?}` writes a copy of every open document, in tab order, to `folder`,
each named after its document (`<name>.<ext>`, with `-2`, `-3`… added when that name is taken there or by another
copy, so no file is replaced). `format` is a save format (`pdf` by default; `svg`, `svgz`, `vectorcraft`, `ai`,
`eps`). Each copy is written as `file.saveCopy` writes it, inside the automation roots in force: every document keeps
its path, title and modified state, no undo step is made, and the active document stays active. A document that
can't be written is listed in `failed` and the others are still written (#4920). The desktop app's Export All Open
Documents as PDF and as SVG ask for the folder. → `{files: [{title, path, warnings}], failed: [{title, error}], format}`
```json
{"name":"run_command","arguments":{"command":"addon.document.exportAllOpen","params":{"folder":"/Users/me/Out","format":"svg"}}}
```
`addon.document.symbolsFromLayers {layers?, replace?}` makes one symbol of each top-level layer's art, as `symbol.new`
makes one (registration in the centre), named after the layer with a number added when a symbol has that name, as
one undo step. `layers` lists layer ids (top-level layers only; anything else is refused); by default it takes the
+5
View File
@@ -1448,10 +1448,15 @@ A VectorCraft addition, not a feature of the reference app, so no area score mov
Symbols area (#4938), from the public symbol documentation ([create and place symbols](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/create-and-place-symbols.html), [edit symbols](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/edit-symbols.html)). Symbol Options has Symbol Type (Dynamic Symbol / Static Symbol; the dialog's default is Dynamic, `symbol.new`'s `type` defaults to static so scripts behave as before). An instance of a dynamic symbol keeps its own fill, stroke, opacity and blend for any object of the symbol's art, named by a child-index path (`symbol.instance.setAppearance`), and stays an instance: the canvas, SVG, PDF, EPS, legacy `.ai`, EMF/WMF and DXF writers, effects and Expand draw the art with those changes. Redefining the symbol reshapes every instance and keeps each change whose object the new art still has; Break Link bakes the changes into the art; Edit Symbol expands the symbol's own art; Reset (`symbol.instance.reset`: the Properties panel's Quick Actions and the Symbols panel menu) and making the symbol static drop them. The type is stored on the symbol and the changes on the instance in the native format, both left out when unset, so documents without them save byte for byte as before. Remaining: Direct Selection of an object inside a dynamic instance on the canvas (with the Fill/Stroke controls then setting its change), and reading and writing the type and changes in `.ai` files. Evidence: `crates/engine/src/tests/dynamic_symbols.rs`, `crates/doc/src/symbol_overrides.rs` tests. Score unchanged at 87% (estimated); hours 5–9 → 4–8.
## Document Tools add-on: Export All Open Documents (2026-11-01)
A VectorCraft addition, not a feature of the reference app, so no area score moves. Object › Plug-ins › Document Tools › Export All Open Documents as PDF (or as SVG) asks for a folder and writes a copy of every open document there, each named after its document with `-2`, `-3`… added when a name is taken, so no file is replaced (`addon.document.exportAllOpen {folder, format?}`, #4920). Each copy is written as Save a Copy writes it: the documents keep their paths, titles and modified state, and the active one stays active. A document that can't be written is named in the status bar and the others are still written. Tests: `addons::document::tests::export_all_open_documents_writes_each_to_the_folder_picked` (ui-egui), `cmd::addons::document::tests::export_all_open_documents_writes_each_and_reports_failures` (engine).
## Revision history
| Date | Change | Summary |
|---|---|---|
| 2026-11-01 | minor | Document Tools add-on (a VectorCraft addition): Export All Open Documents as PDF or SVG (#4920); evidence added; scores unchanged. |
| 2026-11-01 | minor | Path Precision Tools add-on (a VectorCraft addition): Expand Dashes into Paths (#4922); evidence added; scores unchanged. |
| 2026-11-01 | minor | Type core: the Glyphs panel highlights the character selected with the Type tool and scrolls it into view (#4908); evidence added; scores unchanged. |
| 2026-11-01 | minor | Arrange Tools add-on (a VectorCraft addition): Randomize Opacity (#4925); evidence added; scores unchanged. |
+2
View File
@@ -337,6 +337,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
| Arrange Tools add-on (VectorCraft addition): Object › Plug-ins › Arrange Tools › Randomize Opacity… gives each selected object its own opacity at random between Minimum and Maximum (%), with a Seed that repeats it and Preview, one undo step | done | #4925; `addon.arrange.randomizeOpacity`, `crates/engine/src/cmd/addons/arrange.rs`, `crates/ui-egui/src/addons/arrange.rs` and their tests; not a feature of the reference app, so it is not scored |
| Arrange Tools add-on (VectorCraft addition): Object › Plug-ins › Arrange Tools › Sort Objects… sorts the selected objects by Sort By (width, height, area, X or Y position, opacity, stacking order) in an Order (Small to Large, Large to Small, Random) and gives them Apply To in that order, trading the values or with Distribute Evenly; Scale Strokes & Effects; Preview; one undo step | done | #4917; `addon.arrange.sort`, `crates/engine/src/cmd/addons/arrange.rs`, `crates/ui-egui/src/addons/arrange.rs` and their tests; not a feature of the reference app, so it is not scored |
| Document Tools add-on (VectorCraft addition): Object › Plug-ins › Document Tools › Save a Timestamped Copy writes `<name>_YYYY-MM-DD_HH-MM.<ext>` next to the document (a folder picked for one never saved), which keeps its path, title and modified state; off on the web | done | #4926; `addon.document.saveTimestampedCopy`, `crates/engine/src/cmd/addons/document.rs`, `crates/ui-egui/src/addons/document.rs` and their tests; not a feature of the reference app, so it is not scored |
| Document Tools add-on (VectorCraft addition): Export All Open Documents as PDF / as SVG (#4920) | done | asks for a folder, writes a copy of every open document named after it (taken names numbered, nothing replaced), documents and active tab unchanged; failures named in the status bar |
| Document Tools add-on (VectorCraft addition): Object › Plug-ins › Document Tools › New Symbols from Layers makes a symbol of each layer's art named after the layer (the highlighted layers when two or more are, else all); Convert Layers to Symbols also replaces each layer's art with an instance in place; empty layers skipped, one undo step | done | #4927; `addon.document.symbolsFromLayers`, `crates/engine/src/cmd/addons/document.rs`, `crates/ui-egui/src/addons/document.rs` and their tests; not a feature of the reference app, so it is not scored |
| Smart Pen add-on (VectorCraft addition): Object › Plug-ins › Smart Pen selects the Smart Pen Tool, which the All Tools drawer lists under its own heading but neither toolbar shows as shipped (no shortcut), and opens the Smart Pen panel (next segment straight or curved; Remove Points Keep Shape, Make Smooth, Make Corner, Retract Handles, Close or Join). The tool draws (click: straight or curved segment, a curve after a straight one tangent; drag: smooth anchor, Alt breaks the handle, Shift 45°, Space moves it; Tab switches a segment; rubber band; close, join, continue) and edits without switching tools (click selects a point, drag moves it, Alt-click removes it keeping the shape, Shift+Alt-click deletes it, double-click toggles smooth/corner; handle drags with Shift and Alt; segment drags reshape, Alt-click adds a point) | done | `addon.pen.*`, `crates/tools/src/addons/smart_pen.rs`, `crates/ui-egui/src/addons/pen.rs` and their tests; not a feature of the reference app, so it is not scored |
| Path Precision Tools add-on (VectorCraft addition): Object › Plug-ins › Path Precision Tools selects its seven tools (Anchor Eraser Brush, Extend Path, Measure & Inspect, Parametric Shape, Corner Tool, Slide Anchor Along Path, Shared Edge Merge Tool), which the All Tools drawer lists under their own heading but neither toolbar shows as shipped, opens the Anchor Inspector panel (the selected anchor's X/Y and handle lengths and angles as fields; Make Smooth, Make Corner, Equalize Handles, Remove Points Keep Shape, Add Points, the three ways to close) and the Parametric Shape panel (kind, count, W/H, rotation, the kind's own values and corner radius of the selected parametric shape, or of the tool's next one), and runs Add Points at / Split at Crossings and Convert to Parametric Shape; the Parametric Shape Tool draws ten re-editable kinds from the centre (↑/↓ and ←/→ change their numbers while dragging) and edits a selected one with handles; the Corner Tool cuts editable corners on any path (hover shows the anchor, a click uses its radius and kind, a drag sizes the corner, Shift whole values; a corner's marker selects it and loads its values, drags to resize, to 0 or double-clicked removes it; never past what fits; Alt-marquee cuts every anchor inside) with a Corners section in the Anchor Inspector (radius, kind, selected count, Apply to Selected Anchors); Slide Anchor Along Path drags an anchor along its curve with the segments beside it refitted, Shift snapping where the tangent is a multiple of 45°; the Shared Edge Merge Tool shows, over two paths touching at two coincident anchors, the two stretches and the edge merging them, a click merges them (half way; Shift the upper path, Alt the lower), and near anchors meet or an anchor one path lacks is added to match (Shift+Alt asks for this inside a stretch), with a Shared Edges section in the Anchor Inspector (brush diameter, coincidence 0 to 3 pt, Keep Shared Points Together, which takes coincident anchors along with the add-on's own anchor moves); a double-click on the brush or Extend Path tool opens its options | done | `addon.pathtools.*`, `crates/tools/src/addons/`, `crates/ui-egui/src/addons/pathtools.rs`, `crates/ui-egui/src/addons/pathtools/` and their tests; not a feature of the reference app, so it is not scored |
@@ -392,6 +393,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
| Date | Change | Summary |
|---|---|---|
| 2026-11-01 | minor | Document Tools add-on (a VectorCraft addition): Export All Open Documents as PDF or SVG (#4920); scores and estimates unchanged |
| 2026-11-01 | minor | Path Precision Tools add-on (a VectorCraft addition): Expand Dashes into Paths (#4922); scores and estimates unchanged |
| 2026-11-01 | minor | Arrange Tools add-on (a VectorCraft addition): Randomize Opacity (#4925); scores and estimates unchanged |
| 2026-11-01 | minor | Arrange Tools add-on (a VectorCraft addition): Sort Objects (#4917); scores and estimates unchanged |