mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 09:33:36 +00:00
Batch Trace Folder: move to Object › Plug-ins as an add-on
Batch Trace Folder replaces a script, so it is now a Document Tools
add-on command, addon.document.batchTraceFolder, under Object ›
Plug-ins › Document Tools (as Export All Open Documents is), and no
longer in Object › Image Trace. Behaviour, caps and the refusal of a
folder too big to list in full are unchanged; the tests move with it.
The status messages have @msgplural rows in every catalog, as does the
menu label. When some images trace and some don't, the status is one
counted message ("Traced {n} images; not traced: {failed}") so it
translates as a whole.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
//! 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; 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.
|
||||
//! copy of every open document to one folder as Save a Copy does (#4920); Batch Trace Folder traces
|
||||
//! each image of a folder and saves the results to another ([`batch_trace`], #4921). Its submenu
|
||||
//! (Object › Plug-ins › Document Tools) is the UI's.
|
||||
|
||||
mod batch_trace;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -14,7 +16,7 @@ use vectorcraft_doc::{Node, NodeId, NodeKind, SymbolRegistration};
|
||||
|
||||
use super::super::brushsym::{instance_in_place, normalize, place_xf, store_symbol};
|
||||
use super::super::fileio::{self, SaveMode};
|
||||
use super::super::{CommandSpec, bad, checked_ids_param, clock_date, cmd, date_param, has_doc, str_param, switch_or};
|
||||
use super::super::{CommandSpec, always, bad, checked_ids_param, clock_date, cmd, date_param, has_doc, str_param, switch_or};
|
||||
use crate::{Result, Session};
|
||||
|
||||
/// The most numbers (`-2`, `-3`…) tried after a timestamped name that is taken.
|
||||
@@ -40,6 +42,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
export_all_open
|
||||
),
|
||||
cmd!(
|
||||
"addon.document.batchTraceFolder",
|
||||
"Batch Trace Folder",
|
||||
["Object", "Plug-ins", "Document Tools"],
|
||||
None,
|
||||
"{folder, destinationFolder, preset?: name (imageTrace.presets; default: Default), expand?: false (true: trace and expand, as Make and Expand), format?: vectorcraft (default)|svg|pdf|ai|eps, includeSubdirs?: false} open each image in folder (png, jpg, tif, bmp, gif, webp, psd), select it, trace it with the preset, save it into destinationFolder as <name>.<ext> (-2, -3… added when that name is taken, so no file is replaced) and close it again, whether it traced or not; the document that was active stays active. Caps: 500 images, 8 folder levels; a folder with more than 1,000 entries is refused. Not on the web (no file system) → {traced: [{path, output}], failed: [{path, error}], format} (#4921)",
|
||||
always,
|
||||
batch_trace::batch_trace_folder
|
||||
),
|
||||
cmd!(
|
||||
"addon.document.symbolsFromLayers",
|
||||
"New Symbols from Layers",
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
//! Batch Trace Folder (`addon.document.batchTraceFolder`, #4921): each image in a folder is opened,
|
||||
//! traced with a preset (and expanded when asked), saved into a destination folder under its own
|
||||
//! name, and closed again, whether it traced or not.
|
||||
|
||||
use serde_json::Value;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use serde_json::json;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use super::super::super::fileio;
|
||||
use super::super::super::{Result, Session, bad};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use super::super::super::{bool_or, str_param};
|
||||
|
||||
/// The command id.
|
||||
pub(super) const ID: &str = "addon.document.batchTraceFolder";
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) fn batch_trace_folder(_: &mut Session, _: &Value) -> Result<Value> {
|
||||
Err(bad(ID, "no file system here"))
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
/// The image files a batch traces, by extension.
|
||||
const IMAGE_EXTS: [&str; 10] = ["png", "jpg", "jpeg", "tif", "tiff", "bmp", "gif", "webp", "psd", "psb"];
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
/// Caps on a batch (folders and MCP payloads are untrusted).
|
||||
pub(super) const MAX_FILES: usize = 500;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const MAX_DEPTH: usize = 8;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const FORMATS: [&str; 5] = ["vectorcraft", "svg", "pdf", "ai", "eps"];
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
/// The image files in `folder` (and its subfolders with `deep`), sorted.
|
||||
fn images(folder: &str, deep: bool, depth: usize, out: &mut Vec<String>) -> Result<()> {
|
||||
if depth > MAX_DEPTH {
|
||||
return Err(bad(ID, format!("folders deeper than {MAX_DEPTH} levels")));
|
||||
}
|
||||
let max = MAX_FILES.saturating_mul(2);
|
||||
let (entries, truncated) = fileio::list_dir(folder, max)?;
|
||||
// The listing is cut before it is sorted, so a cut one could skip any image: refuse it.
|
||||
if truncated {
|
||||
return Err(bad(ID, format!("{folder}: more than {max} files and folders")));
|
||||
}
|
||||
let mut subdirs = vec![];
|
||||
for e in entries {
|
||||
let name = e.get("name").and_then(Value::as_str).unwrap_or("");
|
||||
if name.is_empty() || name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
let path = std::path::Path::new(folder).join(name).to_string_lossy().into_owned();
|
||||
match e.get("kind").and_then(Value::as_str) {
|
||||
Some("file") if IMAGE_EXTS.contains(&fileio::extension(&path).as_str()) => out.push(path),
|
||||
Some("folder") if deep => subdirs.push(path),
|
||||
_ => {}
|
||||
}
|
||||
if out.len() > MAX_FILES {
|
||||
return Err(bad(ID, format!("more than {MAX_FILES} images")));
|
||||
}
|
||||
}
|
||||
for d in subdirs {
|
||||
images(&d, deep, depth + 1, out)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) fn batch_trace_folder(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let folder = str_param(p, "folder").map(fileio::absolute_path).ok_or_else(|| bad(ID, "missing `folder`"))?;
|
||||
let dest = str_param(p, "destinationFolder").map(fileio::absolute_path).ok_or_else(|| bad(ID, "missing `destinationFolder`"))?;
|
||||
let format = str_param(p, "format").unwrap_or("vectorcraft");
|
||||
if !FORMATS.contains(&format) {
|
||||
return Err(bad(ID, format!("`format` must be one of {}", FORMATS.join(", "))));
|
||||
}
|
||||
let preset = str_param(p, "preset").unwrap_or("Default").to_string();
|
||||
let presets = s.execute("imageTrace.presets", &json!({}))?;
|
||||
if !presets["presets"].as_array().into_iter().flatten().any(|q| q["name"].as_str().is_some_and(|n| n.eq_ignore_ascii_case(&preset))) {
|
||||
return Err(bad(ID, format!("no tracing preset `{preset}` (see imageTrace.presets)")));
|
||||
}
|
||||
let trace = if bool_or(p, "expand", false) { "imageTrace.makeAndExpand" } else { "imageTrace.make" };
|
||||
fileio::may_read(&folder)?;
|
||||
fileio::may_write(&dest)?;
|
||||
let mut files = vec![];
|
||||
images(&folder, bool_or(p, "includeSubdirs", false), 0, &mut files)?;
|
||||
files.sort();
|
||||
if files.is_empty() {
|
||||
return Err(bad(ID, "no images in the folder"));
|
||||
}
|
||||
std::fs::create_dir_all(&dest).map_err(|e| bad(ID, format!("{dest}: {e}")))?;
|
||||
let ext = fileio::save_format(Some(format), None).map_err(|e| bad(ID, e))?.extensions.first().copied().unwrap_or(format);
|
||||
let active = s.active_index();
|
||||
let (mut traced, mut failed, mut taken) = (vec![], vec![], Vec::<String>::new());
|
||||
for path in &files {
|
||||
let opened_before = s.documents().len();
|
||||
let one = |s: &mut Session| -> Result<String> {
|
||||
s.execute("document.open", &json!({ "path": path }))?;
|
||||
s.execute("select.all", &json!({}))?;
|
||||
s.execute(trace, &json!({ "preset": preset }))?;
|
||||
let stem = fileio::file_stem(path);
|
||||
// Names this batch already took count as taken, as do files on disk.
|
||||
let free = super::free_name(&dest, &stem, ext, |o| taken.iter().any(|t| t == o) || std::path::Path::new(o).exists())
|
||||
.ok_or_else(|| bad(ID, "every numbered name for the result is taken"))?;
|
||||
s.execute("document.save", &json!({ "path": free, "format": format }))?;
|
||||
Ok(free)
|
||||
};
|
||||
let result = one(s);
|
||||
// The image's document closes whatever happened, so a failure leaves no tab behind.
|
||||
if s.documents().len() > opened_before
|
||||
&& let Some(i) = s.active_index()
|
||||
{
|
||||
// A just-opened image has no work to lose; if closing fails, one extra tab stays open.
|
||||
let _ = s.execute("file.close", &json!({ "index": i }));
|
||||
}
|
||||
match result {
|
||||
Ok(out) => {
|
||||
taken.push(out.clone());
|
||||
traced.push(json!({ "path": path, "output": out }));
|
||||
}
|
||||
Err(e) => failed.push(json!({ "path": path, "error": e.to_string() })),
|
||||
}
|
||||
}
|
||||
if let Some(i) = active.filter(|i| *i < s.documents().len()) {
|
||||
s.set_active(i);
|
||||
}
|
||||
Ok(json!({ "traced": traced, "failed": failed, "format": format }))
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Document Tools' commands: the timestamped copy's name and place with the document left as it
|
||||
//! was, symbols made of layers, and bad params refused.
|
||||
//! was, symbols made of layers, a folder of images traced, and bad params refused.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -276,3 +276,77 @@ fn export_all_open_documents_keeps_every_page_and_artboard() {
|
||||
assert!(w > 19.0 && w < 30.0, "the SVG shows the art (the square on the second artboard), not the first artboard: viewBox {view}");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// A 24×24 white PNG with a dark (or red) square in it.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn png(path: &Path, dark: bool) {
|
||||
let mut img = image::RgbaImage::from_pixel(24, 24, image::Rgba([255, 255, 255, 255]));
|
||||
for y in 6..18 {
|
||||
for x in 6..18 {
|
||||
img.put_pixel(x, y, if dark { image::Rgba([0, 0, 0, 255]) } else { image::Rgba([200, 30, 30, 255]) });
|
||||
}
|
||||
}
|
||||
img.save(path).unwrap();
|
||||
}
|
||||
|
||||
/// #4921: each image of the folder is traced and saved under its own name; a file that isn't an
|
||||
/// image is left alone, one that can't be read is reported, and every document the batch opened
|
||||
/// is closed again, the active one staying active.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[test]
|
||||
fn batch_trace_folder_traces_and_saves_each_image() {
|
||||
const C: &str = "addon.document.batchTraceFolder";
|
||||
let dir = folder("batch-trace");
|
||||
let (src, out) = (dir.join("in"), dir.join("out"));
|
||||
std::fs::create_dir_all(&src).unwrap();
|
||||
png(&src.join("logo.png"), true);
|
||||
png(&src.join("mark.png"), false);
|
||||
std::fs::write(src.join("notes.txt"), "not an image").unwrap();
|
||||
std::fs::write(src.join("broken.png"), b"not a png").unwrap();
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
let docs = s.documents().len();
|
||||
let r = s.execute(C, &json!({"folder": src, "destinationFolder": out, "format": "svg", "expand": true})).unwrap();
|
||||
assert_eq!(r["traced"].as_array().unwrap().len(), 2, "{r}");
|
||||
assert_eq!(r["failed"].as_array().unwrap().len(), 1, "{r}");
|
||||
assert!(r["failed"][0]["path"].as_str().unwrap().ends_with("broken.png"));
|
||||
let mut names: Vec<String> = std::fs::read_dir(&out).unwrap().map(|e| e.unwrap().file_name().to_string_lossy().into_owned()).collect();
|
||||
names.sort();
|
||||
assert_eq!(names, ["logo.svg", "mark.svg"]);
|
||||
let svg = std::fs::read_to_string(out.join("logo.svg")).unwrap();
|
||||
assert!(svg.contains("<path") && !svg.contains("<image"), "traced and expanded, not the image");
|
||||
assert_eq!(s.documents().len(), docs, "the opened images are closed");
|
||||
assert_eq!(s.active_index(), Some(0));
|
||||
// Again: the taken names are numbered, nothing is replaced.
|
||||
s.execute(C, &json!({"folder": src, "destinationFolder": out, "format": "svg"})).unwrap();
|
||||
assert!(out.join("logo-2.svg").exists() && out.join("mark-2.svg").exists());
|
||||
// Refused: no folders, an unknown preset or format, a folder without images.
|
||||
for bad in [
|
||||
json!({"destinationFolder": out}),
|
||||
json!({"folder": src}),
|
||||
json!({"folder": src, "destinationFolder": out, "preset": "No Such Preset"}),
|
||||
json!({"folder": src, "destinationFolder": out, "format": "docx"}),
|
||||
json!({"folder": out.join("empty"), "destinationFolder": out}),
|
||||
] {
|
||||
assert!(s.execute(C, &bad).is_err(), "{bad}");
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// A folder too big to list in full is refused rather than traced in part.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[test]
|
||||
fn batch_trace_folder_refuses_a_folder_it_cannot_list_in_full() {
|
||||
let dir = folder("batch-trace-big");
|
||||
let src = dir.join("in");
|
||||
std::fs::create_dir_all(&src).unwrap();
|
||||
for i in 0..=super::batch_trace::MAX_FILES * 2 {
|
||||
std::fs::write(src.join(format!("note-{i}.txt")), "").unwrap();
|
||||
}
|
||||
png(&src.join("logo.png"), true);
|
||||
let mut s = Session::new();
|
||||
let e = s.execute("addon.document.batchTraceFolder", &json!({"folder": src, "destinationFolder": dir.join("out")})).unwrap_err();
|
||||
assert!(e.to_string().contains("more than"), "{e}");
|
||||
assert!(!dir.join("out").exists(), "nothing traced");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ mod export;
|
||||
pub(crate) mod imagemap;
|
||||
mod legacy_ai;
|
||||
mod load;
|
||||
mod trace_batch;
|
||||
pub(crate) use load::{MAX_RASTER_ALLOC, size_note};
|
||||
mod jp2read;
|
||||
mod metafile;
|
||||
@@ -109,7 +108,7 @@ mod export_names;
|
||||
mod formats;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use access::list_dir;
|
||||
pub(crate) use access::list_dir;
|
||||
pub use access::write_file_with;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) use access::{Placed, copy_new, file_metadata};
|
||||
@@ -121,7 +120,7 @@ pub(crate) use access::{drive_share_of, share_of};
|
||||
#[cfg(test)]
|
||||
pub(crate) use access::{drive_share_path, share_path, touched};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use access::{may_read, may_write};
|
||||
pub(crate) use access::{may_read, may_write};
|
||||
pub use commands::specs;
|
||||
pub use export_names::artboard_file_names;
|
||||
pub(crate) use export_names::{default_name, merge, unique_file_names, with_black_output, write_or_return};
|
||||
|
||||
@@ -10,7 +10,7 @@ use super::{EngineError, Result, Session, Value, bad, json};
|
||||
|
||||
/// `path` checked for reading against the automation roots in force.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) fn may_read(path: &str) -> Result<()> {
|
||||
pub(crate) fn may_read(path: &str) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
touch(path);
|
||||
crate::file_access::check_read(path).map_err(EngineError::Other)
|
||||
@@ -44,7 +44,7 @@ pub(crate) fn touched<T>(f: impl FnOnce() -> T) -> (T, Vec<String>) {
|
||||
|
||||
/// `path` checked for writing against the automation roots in force.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) fn may_write(path: &str) -> Result<()> {
|
||||
pub(crate) fn may_write(path: &str) -> Result<()> {
|
||||
crate::file_access::check_write(path).map_err(EngineError::Other)
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ pub(super) fn file_list(_: &mut Session, p: &Value) -> Result<Value> {
|
||||
/// The entries of folder `path`, sorted by name: at most `max`, and whether there were more. A
|
||||
/// link isn't followed: its target may lie outside the automation roots.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) fn list_dir(path: &str, max: usize) -> Result<(Vec<Value>, bool)> {
|
||||
pub(crate) fn list_dir(path: &str, max: usize) -> Result<(Vec<Value>, bool)> {
|
||||
may_read(path)?;
|
||||
let dir = std::fs::read_dir(path).map_err(|e| EngineError::Other(format!("{path}: {e}")))?;
|
||||
let mut entries: Vec<(String, Value)> = vec![];
|
||||
@@ -195,7 +195,7 @@ pub(super) fn list_dir(path: &str, max: usize) -> Result<(Vec<Value>, bool)> {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) fn list_dir(_: &str, _: usize) -> Result<(Vec<Value>, bool)> {
|
||||
pub(crate) fn list_dir(_: &str, _: usize) -> Result<(Vec<Value>, bool)> {
|
||||
Err(EngineError::Other("no file system here".into()))
|
||||
}
|
||||
|
||||
|
||||
@@ -112,15 +112,6 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
batch::batch
|
||||
),
|
||||
cmd!(
|
||||
"imageTrace.batchFolder",
|
||||
"Batch Trace Folder",
|
||||
["Object", "Image Trace"],
|
||||
None,
|
||||
"{folder, destinationFolder, preset?: name (imageTrace.presets; default: Default), expand?: false (true: trace and expand, as Make and Expand), format?: vectorcraft (default)|svg|pdf|ai|eps, includeSubdirs?: false} open each image in folder (png, jpg, tif, bmp, gif, webp, psd), select it, trace it with the preset, save it into destinationFolder as <name>.<ext> (-2, -3… added when that name is taken, so no file is replaced) and close it again, whether it traced or not; the document that was active stays active. Caps: 500 images, 8 folder levels. Not on the web (no file system) → {traced: [{path, output}], failed: [{path, error}], format} (#4921)",
|
||||
always,
|
||||
super::trace_batch::batch_folder
|
||||
),
|
||||
cmd!(
|
||||
"action.batch",
|
||||
"Batch…",
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
//! `imageTrace.batchFolder` (#4921): Image Trace › Batch Trace Folder. Each image in a folder is
|
||||
//! opened, traced with a preset (and expanded when asked), saved into a destination folder under
|
||||
//! its own name, and closed again, whether it traced or not.
|
||||
|
||||
use serde_json::Value;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use serde_json::json;
|
||||
|
||||
use super::super::{Result, Session, bad};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use super::super::{bool_or, str_param};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) fn batch_folder(_: &mut Session, _: &Value) -> Result<Value> {
|
||||
Err(bad("imageTrace.batchFolder", "no file system here"))
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
/// The image files a batch traces, by extension.
|
||||
const IMAGE_EXTS: [&str; 10] = ["png", "jpg", "jpeg", "tif", "tiff", "bmp", "gif", "webp", "psd", "psb"];
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
/// Caps on a batch (folders and MCP payloads are untrusted).
|
||||
const MAX_FILES: usize = 500;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const MAX_DEPTH: usize = 8;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const FORMATS: [&str; 5] = ["vectorcraft", "svg", "pdf", "ai", "eps"];
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
/// The image files in `folder` (and its subfolders with `deep`), sorted.
|
||||
fn images(folder: &str, deep: bool, depth: usize, out: &mut Vec<String>) -> Result<()> {
|
||||
if depth > MAX_DEPTH {
|
||||
return Err(bad("imageTrace.batchFolder", format!("folders deeper than {MAX_DEPTH} levels")));
|
||||
}
|
||||
let max = MAX_FILES.saturating_mul(2);
|
||||
let (entries, truncated) = super::list_dir(folder, max)?;
|
||||
// The listing is cut before it is sorted, so a cut one could skip any image: refuse it.
|
||||
if truncated {
|
||||
return Err(bad("imageTrace.batchFolder", format!("{folder}: more than {max} files and folders")));
|
||||
}
|
||||
let mut subdirs = vec![];
|
||||
for e in entries {
|
||||
let name = e.get("name").and_then(Value::as_str).unwrap_or("");
|
||||
if name.is_empty() || name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
let path = std::path::Path::new(folder).join(name).to_string_lossy().into_owned();
|
||||
match e.get("kind").and_then(Value::as_str) {
|
||||
Some("file") if IMAGE_EXTS.contains(&super::extension(&path).as_str()) => out.push(path),
|
||||
Some("folder") if deep => subdirs.push(path),
|
||||
_ => {}
|
||||
}
|
||||
if out.len() > MAX_FILES {
|
||||
return Err(bad("imageTrace.batchFolder", format!("more than {MAX_FILES} images")));
|
||||
}
|
||||
}
|
||||
for d in subdirs {
|
||||
images(&d, deep, depth + 1, out)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) fn batch_folder(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "imageTrace.batchFolder";
|
||||
let folder = str_param(p, "folder").map(super::absolute_path).ok_or_else(|| bad(C, "missing `folder`"))?;
|
||||
let dest = str_param(p, "destinationFolder").map(super::absolute_path).ok_or_else(|| bad(C, "missing `destinationFolder`"))?;
|
||||
let format = str_param(p, "format").unwrap_or("vectorcraft");
|
||||
if !FORMATS.contains(&format) {
|
||||
return Err(bad(C, format!("`format` must be one of {}", FORMATS.join(", "))));
|
||||
}
|
||||
let preset = str_param(p, "preset").unwrap_or("Default").to_string();
|
||||
let presets = s.execute("imageTrace.presets", &json!({}))?;
|
||||
if !presets["presets"].as_array().into_iter().flatten().any(|q| q["name"].as_str().is_some_and(|n| n.eq_ignore_ascii_case(&preset))) {
|
||||
return Err(bad(C, format!("no tracing preset `{preset}` (see imageTrace.presets)")));
|
||||
}
|
||||
let trace = if bool_or(p, "expand", false) { "imageTrace.makeAndExpand" } else { "imageTrace.make" };
|
||||
super::may_read(&folder)?;
|
||||
super::may_write(&dest)?;
|
||||
let mut files = vec![];
|
||||
images(&folder, bool_or(p, "includeSubdirs", false), 0, &mut files)?;
|
||||
files.sort();
|
||||
if files.is_empty() {
|
||||
return Err(bad(C, "no images in the folder"));
|
||||
}
|
||||
std::fs::create_dir_all(&dest).map_err(|e| bad(C, format!("{dest}: {e}")))?;
|
||||
let ext = super::save_format(Some(format), None).map_err(|e| bad(C, e))?.extensions.first().copied().unwrap_or(format);
|
||||
let active = s.active_index();
|
||||
let (mut traced, mut failed, mut taken) = (vec![], vec![], Vec::<String>::new());
|
||||
for path in &files {
|
||||
let opened_before = s.documents().len();
|
||||
let one = |s: &mut Session| -> Result<String> {
|
||||
s.execute("document.open", &json!({ "path": path }))?;
|
||||
s.execute("select.all", &json!({}))?;
|
||||
s.execute(trace, &json!({ "preset": preset }))?;
|
||||
let stem = super::file_stem(path);
|
||||
let free = (1..=999u32)
|
||||
.map(|i| if i == 1 { format!("{stem}.{ext}") } else { format!("{stem}-{i}.{ext}") })
|
||||
.map(|n| std::path::Path::new(&dest).join(n).to_string_lossy().into_owned())
|
||||
.find(|o| !taken.contains(o) && !std::path::Path::new(o).exists())
|
||||
.ok_or_else(|| bad(C, "every numbered name for the result is taken"))?;
|
||||
s.execute("document.save", &json!({ "path": free, "format": format }))?;
|
||||
Ok(free)
|
||||
};
|
||||
let result = one(s);
|
||||
// The image's document closes whatever happened, so a failure leaves no tab behind.
|
||||
if s.documents().len() > opened_before
|
||||
&& let Some(i) = s.active_index()
|
||||
{
|
||||
let _ = s.execute("file.close", &json!({ "index": i }));
|
||||
}
|
||||
match result {
|
||||
Ok(out) => {
|
||||
taken.push(out.clone());
|
||||
traced.push(json!({ "path": path, "output": out }));
|
||||
}
|
||||
Err(e) => failed.push(json!({ "path": path, "error": e.to_string() })),
|
||||
}
|
||||
}
|
||||
if let Some(i) = active.filter(|i| *i < s.documents().len()) {
|
||||
s.set_active(i);
|
||||
}
|
||||
Ok(json!({ "traced": traced, "failed": failed, "format": format }))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Session;
|
||||
|
||||
fn png(path: &std::path::Path, dark: bool) {
|
||||
let mut img = image::RgbaImage::from_pixel(24, 24, image::Rgba([255, 255, 255, 255]));
|
||||
for y in 6..18 {
|
||||
for x in 6..18 {
|
||||
img.put_pixel(x, y, if dark { image::Rgba([0, 0, 0, 255]) } else { image::Rgba([200, 30, 30, 255]) });
|
||||
}
|
||||
}
|
||||
img.save(path).unwrap();
|
||||
}
|
||||
|
||||
/// #4921: each image of the folder is traced and saved under its own name; a file that isn't
|
||||
/// an image is left alone, one that can't be read is reported, and every document the batch
|
||||
/// opened is closed again, the active one staying active.
|
||||
#[test]
|
||||
fn batch_trace_folder_traces_and_saves_each_image() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-batch-trace-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let (src, out) = (dir.join("in"), dir.join("out"));
|
||||
std::fs::create_dir_all(&src).unwrap();
|
||||
png(&src.join("logo.png"), true);
|
||||
png(&src.join("mark.png"), false);
|
||||
std::fs::write(src.join("notes.txt"), "not an image").unwrap();
|
||||
std::fs::write(src.join("broken.png"), b"not a png").unwrap();
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
let docs = s.documents().len();
|
||||
let r = s.execute("imageTrace.batchFolder", &json!({"folder": src, "destinationFolder": out, "format": "svg", "expand": true})).unwrap();
|
||||
assert_eq!(r["traced"].as_array().unwrap().len(), 2, "{r}");
|
||||
assert_eq!(r["failed"].as_array().unwrap().len(), 1, "{r}");
|
||||
assert!(r["failed"][0]["path"].as_str().unwrap().ends_with("broken.png"));
|
||||
let mut names: Vec<String> = std::fs::read_dir(&out).unwrap().map(|e| e.unwrap().file_name().to_string_lossy().into_owned()).collect();
|
||||
names.sort();
|
||||
assert_eq!(names, ["logo.svg", "mark.svg"]);
|
||||
let svg = std::fs::read_to_string(out.join("logo.svg")).unwrap();
|
||||
assert!(svg.contains("<path") && !svg.contains("<image"), "traced and expanded, not the image");
|
||||
assert_eq!(s.documents().len(), docs, "the opened images are closed");
|
||||
assert_eq!(s.active_index(), Some(0));
|
||||
// Again: the taken names are numbered, nothing is replaced.
|
||||
s.execute("imageTrace.batchFolder", &json!({"folder": src, "destinationFolder": out, "format": "svg"})).unwrap();
|
||||
assert!(out.join("logo-2.svg").exists() && out.join("mark-2.svg").exists());
|
||||
// Refused: no folders, an unknown preset or format, a folder without images.
|
||||
for bad in [
|
||||
json!({"destinationFolder": out}),
|
||||
json!({"folder": src}),
|
||||
json!({"folder": src, "destinationFolder": out, "preset": "No Such Preset"}),
|
||||
json!({"folder": src, "destinationFolder": out, "format": "docx"}),
|
||||
json!({"folder": out.join("empty"), "destinationFolder": out}),
|
||||
] {
|
||||
assert!(s.execute("imageTrace.batchFolder", &bad).is_err(), "{bad}");
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// A folder too big to list in full is refused rather than traced in part.
|
||||
#[test]
|
||||
fn batch_trace_folder_refuses_a_folder_it_cannot_list_in_full() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-batch-trace-big-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let src = dir.join("in");
|
||||
std::fs::create_dir_all(&src).unwrap();
|
||||
for i in 0..=super::MAX_FILES * 2 {
|
||||
std::fs::write(src.join(format!("note-{i}.txt")), "").unwrap();
|
||||
}
|
||||
png(&src.join("logo.png"), true);
|
||||
let mut s = Session::new();
|
||||
let e = s.execute("imageTrace.batchFolder", &json!({"folder": src, "destinationFolder": dir.join("out")})).unwrap_err();
|
||||
assert!(e.to_string().contains("more than"), "{e}");
|
||||
assert!(!dir.join("out").exists(), "nothing traced");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
//! 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`]). Export All Open Documents as PDF or SVG asks
|
||||
//! for the folder the copies go in ([`export_all`], #4920).
|
||||
//! for the folder the copies go in ([`export_all`], #4920). Batch Trace Folder asks for the folder of
|
||||
//! images and then the folder the results go in ([`batch_trace`], #4921).
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -13,6 +14,8 @@ use crate::menus::Item;
|
||||
pub const SAVE_COPY: &str = "addon.document.saveTimestampedCopy";
|
||||
/// Export All Open Documents.
|
||||
pub const EXPORT_ALL: &str = "addon.document.exportAllOpen";
|
||||
/// Batch Trace Folder.
|
||||
pub const BATCH_TRACE: &str = "addon.document.batchTraceFolder";
|
||||
/// New Symbols from Layers (Convert Layers to Symbols: with their art replaced by instances).
|
||||
const SYMBOLS: &str = "addon.document.symbolsFromLayers";
|
||||
|
||||
@@ -22,6 +25,7 @@ pub fn menu() -> Vec<Item> {
|
||||
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::Cmd("Batch Trace Folder", BATCH_TRACE, json!({})),
|
||||
Item::Sep,
|
||||
Item::Cmd("New Symbols from Layers", SYMBOLS, json!({})),
|
||||
Item::Cmd("Convert Layers to Symbols", SYMBOLS, json!({ "replace": true })),
|
||||
@@ -62,5 +66,32 @@ pub(crate) fn export_all(app: &mut VectorcraftApp, p: &Value) -> Result<Value, S
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// Batch Trace Folder with params `p`: the folder of images and then the folder the results go in
|
||||
/// are asked for when not given. The status bar says how many images were traced and how many
|
||||
/// couldn't be.
|
||||
pub(crate) fn batch_trace(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
|
||||
let mut p = if p.is_object() { p.clone() } else { json!({}) };
|
||||
for key in ["folder", "destinationFolder"] {
|
||||
if p.get(key).is_none() {
|
||||
p[key] = json!(crate::picks::folder(app).ok_or("cancelled")?);
|
||||
}
|
||||
}
|
||||
let r = app.session.execute(BATCH_TRACE, &p).map_err(|e| e.to_string())?;
|
||||
let traced = r["traced"].as_array().map_or(0, Vec::len) as u64;
|
||||
let failed = r["failed"].as_array().map_or(0, Vec::len) as u64;
|
||||
// One count per message: a count inside another counted message would read as the wrong
|
||||
// template when the status is translated.
|
||||
let status = if failed == 0 {
|
||||
crate::i18n::msg_n(traced, "Traced {n} image", "Traced {n} images", &[])
|
||||
} else if traced == 0 {
|
||||
crate::i18n::msg_n(failed, "{n} image couldn't be traced", "{n} images couldn't be traced", &[])
|
||||
} else {
|
||||
let failed = failed.to_string();
|
||||
crate::i18n::msg_n(traced, "Traced {n} image; not traced: {failed}", "Traced {n} images; not traced: {failed}", &[("failed", &failed)])
|
||||
};
|
||||
app.status(status);
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -123,3 +123,29 @@ fn export_all_open_documents_writes_each_to_the_folder_picked() {
|
||||
assert!(app.session.execute(super::EXPORT_ALL, &json!({"folder": out, "format": "doc"})).is_err());
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// #4921: Batch Trace Folder from Object › Plug-ins › Document Tools asks for the folder of images,
|
||||
/// then the destination, traces each image there and reports the count (a status the catalogs
|
||||
/// translate); cancelling either does nothing.
|
||||
#[test]
|
||||
fn batch_trace_folder_asks_for_both_folders() {
|
||||
let dir = folder("batch-trace");
|
||||
let (src, out) = (dir.join("in"), dir.join("out"));
|
||||
std::fs::create_dir_all(&src).unwrap();
|
||||
image::RgbaImage::from_pixel(16, 16, image::Rgba([0, 0, 0, 255])).save(src.join("a.png")).unwrap();
|
||||
std::fs::write(src.join("b.png"), b"not a png").unwrap();
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
let mut picks = vec![out.to_string_lossy().into_owned(), src.to_string_lossy().into_owned()];
|
||||
app.services.pick_folder = Some(Box::new(move || picks.pop()));
|
||||
click(&mut app, "Batch Trace Folder");
|
||||
assert!(out.join("a.vectorcraft").exists(), "{}", app.ui.status);
|
||||
assert_eq!(app.ui.status, "Traced 1 image; not traced: 1");
|
||||
let de = crate::i18n::Lang::from_code("de").unwrap();
|
||||
assert_eq!(crate::i18n::message(de, &app.ui.status), "1 Bild nachgezeichnet; nicht nachgezeichnet: 1");
|
||||
assert_eq!(crate::i18n::message(de, "Traced 2 images"), "2 Bilder nachgezeichnet");
|
||||
assert_eq!(crate::i18n::message(de, "2 images couldn't be traced"), "2 Bilder konnten nicht nachgezeichnet werden");
|
||||
assert!(app.session.documents().is_empty(), "the images' documents are closed");
|
||||
app.services.pick_folder = Some(Box::new(|| None));
|
||||
assert_eq!(app.run(super::BATCH_TRACE, json!({})).unwrap_err(), "cancelled");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -6111,6 +6111,11 @@ breakText Glyphs Глифи
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Пакетно трасиране на папка
|
||||
@msgplural Traced {n} image|Traced {n} images Трасирано е {n} изображение|Трасирани са {n} изображения
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} изображение не можа да бъде трасирано|{n} изображения не можаха да бъдат трасирани
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Трасирано е {n} изображение; нетрасирани: {failed}|Трасирани са {n} изображения; нетрасирани: {failed}
|
||||
# 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.
|
@@ -6145,6 +6145,11 @@ shape Open Otvoren
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Paketno praćenje foldera
|
||||
@msgplural Traced {n} image|Traced {n} images Praćena je {n} slika|Praćene su {n} slike|Praćeno je {n} slika
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} slika nije mogla biti praćena|{n} slike nisu mogle biti praćene|{n} slika nije moglo biti praćeno
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Praćena je {n} slika; nije praćeno: {failed}|Praćene su {n} slike; nije praćeno: {failed}|Praćeno je {n} slika; nije praćeno: {failed}
|
||||
# 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.
|
@@ -6245,6 +6245,11 @@ breakText Glyphs Glifs
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Traçat per lots d’una carpeta
|
||||
@msgplural Traced {n} image|Traced {n} images S’ha traçat {n} imatge|S’han traçat {n} imatges
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced No s’ha pogut traçar {n} imatge|No s’han pogut traçar {n} imatges
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} S’ha traçat {n} imatge; no traçades: {failed}|S’han traçat {n} imatges; no traçades: {failed}
|
||||
# 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.
|
@@ -6259,6 +6259,11 @@ breakText Glyphs Glyfy
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Dávková vektorizace složky
|
||||
@msgplural Traced {n} image|Traced {n} images Vektorizován {n} obraz|Vektorizovány {n} obrazy|Vektorizováno {n} obrazů
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} obraz nebylo možné vektorizovat|{n} obrazy nebylo možné vektorizovat|{n} obrazů nebylo možné vektorizovat
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Vektorizován {n} obraz; nevektorizováno: {failed}|Vektorizovány {n} obrazy; nevektorizováno: {failed}|Vektorizováno {n} obrazů; nevektorizováno: {failed}
|
||||
# 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.
|
@@ -6127,6 +6127,11 @@ breakText Glyphs Glyffer
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Batchvektorisér mappe
|
||||
@msgplural Traced {n} image|Traced {n} images Vektoriserede {n} billede|Vektoriserede {n} billeder
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} billede kunne ikke vektoriseres|{n} billeder kunne ikke vektoriseres
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Vektoriserede {n} billede; ikke vektoriseret: {failed}|Vektoriserede {n} billeder; ikke vektoriseret: {failed}
|
||||
# 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.
|
@@ -5845,6 +5845,11 @@ breakText Glyphs Glyphen
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Ordner stapelweise nachzeichnen
|
||||
@msgplural Traced {n} image|Traced {n} images {n} Bild nachgezeichnet|{n} Bilder nachgezeichnet
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} Bild konnte nicht nachgezeichnet werden|{n} Bilder konnten nicht nachgezeichnet werden
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} {n} Bild nachgezeichnet; nicht nachgezeichnet: {failed}|{n} Bilder nachgezeichnet; nicht nachgezeichnet: {failed}
|
||||
# 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.
|
@@ -6211,6 +6211,11 @@ breakText Glyphs Γλύφοι
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Μαζική διανυσματοποίηση φακέλου
|
||||
@msgplural Traced {n} image|Traced {n} images Διανυσματοποιήθηκε {n} εικόνα|Διανυσματοποιήθηκαν {n} εικόνες
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced Δεν ήταν δυνατή η διανυσματοποίηση {n} εικόνας|Δεν ήταν δυνατή η διανυσματοποίηση {n} εικόνων
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Διανυσματοποιήθηκε {n} εικόνα; δεν διανυσματοποιήθηκαν: {failed}|Διανυσματοποιήθηκαν {n} εικόνες; δεν διανυσματοποιήθηκαν: {failed}
|
||||
# Symbol Options › Symbol Type (dynamic symbols)
|
||||
Symbol Type: Τύπος συμβόλου:
|
||||
Dynamic Symbol Δυναμικό σύμβολο
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 163 and column 5.
|
@@ -6201,6 +6201,11 @@ breakText Glyphs Glifos
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Calcar carpeta por lotes
|
||||
@msgplural Traced {n} image|Traced {n} images Se calcó {n} imagen|Se calcaron {n} imágenes
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced No se pudo calcar {n} imagen|No se pudieron calcar {n} imágenes
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Se calcó {n} imagen; sin calcar: {failed}|Se calcaron {n} imágenes; sin calcar: {failed}
|
||||
# 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.
|
@@ -6289,6 +6289,11 @@ breakText Glyphs Merkit
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Jäljitä kansio erässä
|
||||
@msgplural Traced {n} image|Traced {n} images Jäljitettiin {n} kuva|Jäljitettiin {n} kuvaa
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} kuvaa ei voitu jäljittää|{n} kuvaa ei voitu jäljittää
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Jäljitettiin {n} kuva; jäljittämättä: {failed}|Jäljitettiin {n} kuvaa; jäljittämättä: {failed}
|
||||
# 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.
|
@@ -6254,6 +6254,11 @@ breakText Glyphs Glyphes
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Vectoriser un dossier par lots
|
||||
@msgplural Traced {n} image|Traced {n} images {n} image vectorisée|{n} images vectorisées
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} image n’a pas pu être vectorisée|{n} images n’ont pas pu être vectorisées
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} {n} image vectorisée; non vectorisées : {failed}|{n} images vectorisées; non vectorisées : {failed}
|
||||
# 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.
|
@@ -6243,6 +6243,11 @@ breakText Glyphs Glifák
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Mappa kötegelt vektorizálása
|
||||
@msgplural Traced {n} image|Traced {n} images Vektorizált képek: {n}
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced Nem vektorizálható képek: {n}
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Vektorizált képek: {n}; nem vektorizálható: {failed}
|
||||
# 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.
|
@@ -6127,6 +6127,11 @@ breakText Glyphs Glif
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Telusuri Folder secara Batch
|
||||
@msgplural Traced {n} image|Traced {n} images Berhasil menelusuri {n} gambar
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} gambar tidak dapat ditelusuri
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Berhasil menelusuri {n} gambar; tidak ditelusuri: {failed}
|
||||
# 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 2230 and column 5.
|
@@ -5879,6 +5879,11 @@ breakText Glyphs Glifi
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Ricalca cartella in batch
|
||||
@msgplural Traced {n} image|Traced {n} images Ricalcata {n} immagine|Ricalcate {n} immagini
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced Impossibile ricalcare {n} immagine|Impossibile ricalcare {n} immagini
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Ricalcata {n} immagine; non ricalcate: {failed}|Ricalcate {n} immagini; non ricalcate: {failed}
|
||||
# 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.
|
@@ -5944,6 +5944,11 @@ breakText Glyphs 字形
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder フォルダーを一括トレース
|
||||
@msgplural Traced {n} image|Traced {n} images {n} 個の画像をトレースしました
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} 個の画像をトレースできませんでした
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} {n} 個の画像をトレースしました; トレースできなかった画像: {failed}
|
||||
# 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.
|
@@ -6255,6 +6255,11 @@ breakText Glyphs გლიფები
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder საქაღალდის პაკეტური ვექტორიზაცია
|
||||
@msgplural Traced {n} image|Traced {n} images ვექტორიზებულია გამოსახულება ({n})
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced ვერ მოხერხდა გამოსახულების ვექტორიზაცია ({n})
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} ვექტორიზებულია გამოსახულება ({n}); ვერ მოხერხდა: {failed}
|
||||
# Symbol Options › Symbol Type (dynamic symbols)
|
||||
Symbol Type: სიმბოლოს ტიპი:
|
||||
Dynamic Symbol დინამიკური სიმბოლო
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -5840,6 +5840,11 @@ breakText Glyphs 글리프
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder 폴더 일괄 추적
|
||||
@msgplural Traced {n} image|Traced {n} images 이미지 {n}개를 추적했습니다
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced 이미지 {n}개를 추적하지 못했습니다
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} 이미지 {n}개를 추적했습니다; 추적하지 못한 이미지: {failed}
|
||||
# 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.
|
@@ -6171,6 +6171,11 @@ breakText Glyphs Glyphs
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Map in batch traceren
|
||||
@msgplural Traced {n} image|Traced {n} images {n} afbeelding getraceerd|{n} afbeeldingen getraceerd
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} afbeelding kon niet worden getraceerd|{n} afbeeldingen konden niet worden getraceerd
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} {n} afbeelding getraceerd; niet getraceerd: {failed}|{n} afbeeldingen getraceerd; niet getraceerd: {failed}
|
||||
# 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.
|
@@ -6114,6 +6114,11 @@ breakText Glyphs Glify
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Obrysuj folder wsadowo
|
||||
@msgplural Traced {n} image|Traced {n} images Obrysowano {n} obraz|Obrysowano {n} obrazy|Obrysowano {n} obrazów
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced Nie udało się obrysować {n} obrazu|Nie udało się obrysować {n} obrazów|Nie udało się obrysować {n} obrazów
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Obrysowano {n} obraz; nieobrysowane: {failed}|Obrysowano {n} obrazy; nieobrysowane: {failed}|Obrysowano {n} obrazów; nieobrysowane: {failed}
|
||||
# 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.
|
@@ -6177,6 +6177,11 @@ breakText Glyphs Glifos
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Rastrear Pasta em Lote
|
||||
@msgplural Traced {n} image|Traced {n} images {n} imagem rastreada|{n} imagens rastreadas
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced Não foi possível rastrear {n} imagem|Não foi possível rastrear {n} imagens
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} {n} imagem rastreada; não rastreadas: {failed}|{n} imagens rastreadas; não rastreadas: {failed}
|
||||
# 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.
|
@@ -6236,6 +6236,11 @@ breakText Glyphs Глифы
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Пакетная трассировка папки
|
||||
@msgplural Traced {n} image|Traced {n} images Трассировано {n} изображение|Трассировано {n} изображения|Трассировано {n} изображений
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced Не удалось трассировать {n} изображение|Не удалось трассировать {n} изображения|Не удалось трассировать {n} изображений
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Трассировано {n} изображение; не трассировано: {failed}|Трассировано {n} изображения; не трассировано: {failed}|Трассировано {n} изображений; не трассировано: {failed}
|
||||
# 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.
|
@@ -6311,6 +6311,11 @@ breakText Glyphs Glyfer
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Batchvektorisera mapp
|
||||
@msgplural Traced {n} image|Traced {n} images Vektoriserade {n} bild|Vektoriserade {n} bilder
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} bild kunde inte vektoriseras|{n} bilder kunde inte vektoriseras
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Vektoriserade {n} bild; ej vektoriserade: {failed}|Vektoriserade {n} bilder; ej vektoriserade: {failed}
|
||||
# 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.
|
@@ -6216,6 +6216,11 @@ breakText Glyphs Glifler
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Klasörü Toplu İzle
|
||||
@msgplural Traced {n} image|Traced {n} images {n} resim izlendi
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} resim izlenemedi
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} {n} resim izlendi; izlenemeyen: {failed}
|
||||
# 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.
|
@@ -5859,6 +5859,11 @@ breakText Glyphs Гліфи
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Пакетне трасування папки
|
||||
@msgplural Traced {n} image|Traced {n} images Трасовано {n} зображення|Трасовано {n} зображення|Трасовано {n} зображень
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced Не вдалося трасувати {n} зображення|Не вдалося трасувати {n} зображення|Не вдалося трасувати {n} зображень
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Трасовано {n} зображення; не трасовано: {failed}|Трасовано {n} зображення; не трасовано: {failed}|Трасовано {n} зображень; не трасовано: {failed}
|
||||
# Symbol Options › Symbol Type (dynamic symbols)
|
||||
Symbol Type: Тип символу:
|
||||
Dynamic Symbol Динамічний символ
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 177 and column 5.
|
@@ -5860,6 +5860,11 @@ breakText Glyphs Glyph
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder Vector hóa thư mục hàng loạt
|
||||
@msgplural Traced {n} image|Traced {n} images Đã vector hóa {n} hình ảnh
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced Không thể vector hóa {n} hình ảnh
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} Đã vector hóa {n} hình ảnh; không vector hóa được: {failed}
|
||||
# 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 199 and column 5.
|
@@ -6100,6 +6100,11 @@ breakText Glyphs 字形
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder 批量描摹文件夹
|
||||
@msgplural Traced {n} image|Traced {n} images 已描摹 {n} 个图像
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} 个图像无法描摹
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} 已描摹 {n} 个图像; 无法描摹:{failed}
|
||||
# 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.
|
@@ -5897,6 +5897,11 @@ breakText Glyphs 字符
|
||||
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}
|
||||
# Document Tools add-on: Batch Trace Folder (#4921).
|
||||
Batch Trace Folder 批次描圖資料夾
|
||||
@msgplural Traced {n} image|Traced {n} images 已描圖 {n} 個影像
|
||||
@msgplural {n} image couldn't be traced|{n} images couldn't be traced {n} 個影像無法描圖
|
||||
@msgplural Traced {n} image; not traced: {failed}|Traced {n} images; not traced: {failed} 已描圖 {n} 個影像; 無法描圖:{failed}
|
||||
# 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.
|
@@ -288,52 +288,3 @@ mod tests {
|
||||
assert_eq!(app.session.prefs.screen_export_presets.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(), ["Pack", "Pack 2"]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Object › Image Trace › Batch Trace Folder (#4921) with params `p`: the source folder and then
|
||||
/// the destination folder are asked for when not given; the status bar says how many images were
|
||||
/// traced and how many weren't.
|
||||
pub(crate) fn batch_trace(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
|
||||
let mut p = if p.is_object() { p.clone() } else { json!({}) };
|
||||
for key in ["folder", "destinationFolder"] {
|
||||
if p.get(key).is_none() {
|
||||
p[key] = json!(crate::picks::folder(app).ok_or("cancelled")?);
|
||||
}
|
||||
}
|
||||
let r = app.session.execute("imageTrace.batchFolder", &p).map_err(|e| e.to_string())?;
|
||||
let traced = r["traced"].as_array().map_or(0, Vec::len) as u64;
|
||||
let failed = r["failed"].as_array().map_or(0, Vec::len) as u64;
|
||||
let mut status = crate::i18n::msg_n(traced, "Traced {n} image", "Traced {n} images", &[]);
|
||||
if failed > 0 {
|
||||
status = format!("{status}; {}", crate::i18n::msg_n(failed, "{n} image couldn't be traced", "{n} images couldn't be traced", &[]));
|
||||
}
|
||||
app.status(status);
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod batch_trace_tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::VectorcraftApp;
|
||||
|
||||
/// #4921: Batch Trace Folder from the menu asks for the source folder, then the destination,
|
||||
/// traces each image there and reports the count; cancelling either does nothing.
|
||||
#[test]
|
||||
fn batch_trace_folder_asks_for_both_folders() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-ui-batch-trace-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let (src, out) = (dir.join("in"), dir.join("out"));
|
||||
std::fs::create_dir_all(&src).unwrap();
|
||||
image::RgbaImage::from_pixel(16, 16, image::Rgba([0, 0, 0, 255])).save(src.join("a.png")).unwrap();
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
|
||||
let mut picks = vec![out.to_string_lossy().into_owned(), src.to_string_lossy().into_owned()];
|
||||
app.services.pick_folder = Some(Box::new(move || picks.pop()));
|
||||
crate::menus::invoke(&mut app, "imageTrace.batchFolder", json!({}));
|
||||
assert!(out.join("a.vectorcraft").exists(), "{}", app.ui.status);
|
||||
assert!(app.ui.status.starts_with("Traced 1 image"), "{}", app.ui.status);
|
||||
assert!(app.session.documents().is_empty(), "the image's document is closed");
|
||||
app.services.pick_folder = Some(Box::new(|| None));
|
||||
assert_eq!(app.run("imageTrace.batchFolder", json!({})).unwrap_err(), "cancelled");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,8 +565,8 @@ 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),
|
||||
"imageTrace.batchFolder" => crate::io::batch_trace(app, p),
|
||||
crate::addons::document::EXPORT_ALL => crate::addons::document::export_all(app, p),
|
||||
crate::addons::document::BATCH_TRACE => crate::addons::document::batch_trace(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),
|
||||
|
||||
+13
-7
@@ -1880,13 +1880,6 @@ already walks Image Trace. Short agent entry points:
|
||||
Its journal entry records the saved preset's settings as `savedPreset`, so a replay traces with them after the preset
|
||||
is changed, renamed or deleted. In the app,
|
||||
`ui.imageTracePresetDialog {params?, rename?}` opens the Image Trace panel's Save as New Preset… or Rename… dialog.
|
||||
`imageTrace.batchFolder {folder, destinationFolder, preset?, expand?, format?, includeSubdirs?}` (Object › Image
|
||||
Trace › Batch Trace Folder) opens each image in `folder` (PNG, JPEG, TIFF, BMP, GIF, WebP, PSD), traces it with
|
||||
`preset` (Default by default; with `expand`, as Make and Expand), saves it into `destinationFolder` as
|
||||
`<name>.<ext>` in `format` (`vectorcraft` by default; `svg`, `pdf`, `ai`, `eps`), with `-2`, `-3`… added when the
|
||||
name is taken so no file is replaced, and closes it again whether it traced or not; the active document stays
|
||||
active. Up to 500 images and 8 folder levels (a folder with more than 1,000 entries is refused); not on the web. The app asks for both folders (#4921).
|
||||
→ `{traced: [{path, output}], failed: [{path, error}], format}`
|
||||
- Live Paint: `livePaint.make`, `livePaint.merge`, `livePaint.fill {group?, point}`, `livePaint.strokeEdge`,
|
||||
`livePaint.info {group}`, `livePaint.release`, `livePaint.expand`, and `livePaint.gapOptions {ids?, detect?, size?,
|
||||
color?, closeGaps?}` (Object › Live Paint › Gap Options). Make gives each covered face the fill of its front-most
|
||||
@@ -6918,6 +6911,19 @@ Documents as PDF and as SVG ask for the folder. → `{files: [{title, path, warn
|
||||
{"name":"run_command","arguments":{"command":"addon.document.exportAllOpen","params":{"folder":"/Users/me/Out","format":"svg"}}}
|
||||
```
|
||||
|
||||
`addon.document.batchTraceFolder {folder, destinationFolder, preset?, expand?, format?, includeSubdirs?}` opens each
|
||||
image in `folder` (PNG, JPEG, TIFF, BMP, GIF, WebP, PSD; with `includeSubdirs`, in its subfolders too), traces it with
|
||||
`preset` (an `imageTrace.presets` name; Default by default; with `expand`, as Make and Expand), saves it into
|
||||
`destinationFolder` as `<name>.<ext>` in `format` (`vectorcraft` by default; `svg`, `pdf`, `ai`, `eps`), with `-2`,
|
||||
`-3`… added when the name is taken so no file is replaced, and closes it again whether it traced or not; the active
|
||||
document stays active. Images that can't be traced are listed in `failed` and the others are still traced. Up to 500
|
||||
images and 8 folder levels (a folder with more than 1,000 entries is refused); not on the web. The desktop app's Batch
|
||||
Trace Folder asks for both folders (#4921). → `{traced: [{path, output}], failed: [{path, error}], format}`
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"addon.document.batchTraceFolder","params":{"folder":"/Users/me/Scans","destinationFolder":"/Users/me/Traced","preset":"Black and White Logo","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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Parity with Adobe Illustrator
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (area type vertical alignment in the Properties and Control panels, #3759; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (Document Tools add-on: Batch Trace Folder, #4921; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -23,6 +23,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-04 | minor | Document Tools add-on (a VectorCraft addition): Batch Trace Folder under Object › Plug-ins (`addon.document.batchTraceFolder`, #4921); evidence added; scores unchanged. |
|
||||
| 2026-11-04 | minor | Object › Expand turns selected symbol instances into their art as Break Link places it, one level deep, with the Expand dialog's Fill and Stroke reaching that art. Scores and estimates unchanged. |
|
||||
| 2026-11-03 | minor | The OpenType panel offers the selected font's stylistic sets by custom or numbered name, and allows several to be active. Scores and estimates unchanged. |
|
||||
| 2026-11-03 | minor | Actions panel: an expanded action's steps can be excluded from playback and Batch… by clearing their checkmarks; action and set checkmarks include or exclude all their steps, and exclusions survive restart, duplication and `.vcactions` save/load. Scores and estimates unchanged. |
|
||||
@@ -1571,9 +1572,9 @@ Symbols area (#4938), from the public symbol documentation ([create and place sy
|
||||
|
||||
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).
|
||||
|
||||
## Batch Trace Folder (2026-11-01)
|
||||
## Document Tools add-on: Batch Trace Folder (2026-11-04)
|
||||
|
||||
A VectorCraft addition (it replaces a script some users ran), so no area score moves. Object › Image Trace › Batch Trace Folder (`imageTrace.batchFolder`) asks for a folder of images and a destination folder, traces each image with a preset (optionally expanded) and saves it there under its own name as `.vectorcraft`, `.svg`, `.pdf`, `.ai` or `.eps`, without replacing files, closing each image's document again and reporting the images it couldn't trace (#4921). Tests: `cmd::fileio::trace_batch::tests::batch_trace_folder_traces_and_saves_each_image` (engine), `io::batch_trace_tests::batch_trace_folder_asks_for_both_folders` (ui-egui).
|
||||
A VectorCraft addition (it replaces a script some users ran), so no area score moves. Object › Plug-ins › Document Tools › Batch Trace Folder (`addon.document.batchTraceFolder`) asks for a folder of images and a destination folder, traces each image with a preset (optionally expanded) and saves it there under its own name as `.vectorcraft`, `.svg`, `.pdf`, `.ai` or `.eps`, without replacing files, closing each image's document again and reporting the images it couldn't trace (#4921). A folder with more than 1,000 entries is refused rather than traced in part. Tests: `cmd::addons::document::tests::batch_trace_folder_traces_and_saves_each_image` and `batch_trace_folder_refuses_a_folder_it_cannot_list_in_full` (engine), `addons::document::tests::batch_trace_folder_asks_for_both_folders` (ui-egui).
|
||||
|
||||
## Color Effects add-on: Find Nearest Swatch (2026-11-01)
|
||||
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (area type vertical alignment buttons in the Properties and Control panels, turned off for rows, columns and threads, #3759; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (Document Tools add-on: Batch Trace Folder under Object › Plug-ins, #4921; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -30,6 +30,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-04 | minor | Document Tools add-on (a VectorCraft addition): Object › Plug-ins › Document Tools › Batch Trace Folder asks for a folder of images and a destination folder and traces each image into it (#4921); scores and estimates unchanged |
|
||||
| 2026-11-03 | minor | The Action Options row cites the public Actions panel page and describes its controls. Scores and estimates remain unchanged. |
|
||||
| 2026-11-03 | minor | Enclosing Lasso provides whole-object enclosure selection as an add-on with no default shortcut or Basic toolbar slot. Scores and estimates remain unchanged. |
|
||||
| 2026-11-03 | minor | Transform previews valid typed values while numeric fields remain focused. Effect dialogs use the object, group, or layer targeted in Appearance. |
|
||||
@@ -364,6 +365,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 › 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 › Batch Trace Folder asks for a folder of images, then a destination folder, traces each image with a preset (optionally expanded) and saves it there as `.vectorcraft`, `.svg`, `.pdf`, `.ai` or `.eps` under its own name (taken names numbered, nothing replaced), closing each image again; the status bar counts the images traced and those that couldn't be; off on the web | done | #4921; `addon.document.batchTraceFolder`, `crates/engine/src/cmd/addons/document/batch_trace.rs`, `crates/ui-egui/src/addons/document.rs` and their tests; not a feature of the reference app |
|
||||
| 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 |
|
||||
|
||||
Reference in New Issue
Block a user