mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Plug-ins: filters keep data with the document (G17)
A filter plug-in had nowhere to keep settings that belong to the whole document rather than to one object, such as the pens a plotter holds or a machine profile: it could only ask for them again on every run. - A filter's output may hold documentData: any JSON of at most 1 MiB (objects::MAX_DOCUMENT_DATA) that replaces what the plug-in keeps with the document, null removing it, in the same undo step as its objects (objects::decode_output). Too much refuses the output and the document stays as it was. - A filter gets it back in _context.documentData (null until it keeps something). A filter reads and writes only its own. - It is the document's foreign data, Document::unknown["pluginData"] by plug-in id (vectorcraft_doc::plugin_data), where the perspective grid and the brushes keep theirs. So the native file needs no section of its own for it, and hosts from before keep it when they save. An exporter is given the whole document, so it can read every plug-in's. Nothing else lists it, as the reference app's Document Info lists no plug-in data. A live effect isn't given it: its geometry is worked out from the object alone wherever it is drawn. Tests: plugin_data.rs (each plug-in its own entry, forgetting what isn't kept copies nothing, a damaged value holds nothing), objects/tests.rs (kept, forgotten, refused over the limit), tests_plugins.rs (one undo step with the objects, read back, not given to another filter, kept by the native file among its foreign data, refused over the limit with the document unchanged, null removes it) and tests/plugin_export.rs (an exporter finds another plug-in's). The size error has its row in each complete catalog (COMPLETE_MESSAGES). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
This commit is contained in:
committed by
bflatastic
co-authored by
Claude Opus 5.5
parent
e93a73f60b
commit
1505384501
@@ -44,6 +44,7 @@ pub mod perspective;
|
||||
mod pixels;
|
||||
pub use pixels::MAX_RASTER_ALLOC;
|
||||
pub mod placed_document;
|
||||
pub mod plugin_data;
|
||||
pub mod profiles;
|
||||
pub mod puppet;
|
||||
pub mod range;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
//! What plug-ins keep with a document: settings that belong to the whole document rather than to
|
||||
//! one object, such as the pens a plotter holds or a machine's profile.
|
||||
//!
|
||||
//! They are the document's foreign data ([`Document::unknown`], under [`PLUGIN_DATA`]), as the
|
||||
//! perspective grid and the brushes are, so the native file has no section of its own for them
|
||||
//! and a version from before them keeps them when it saves. The host never reads them: a filter
|
||||
//! plug-in writes its own and reads only its own back, and an exporter, which is given the whole
|
||||
//! document, can read them all (`vectorcraft-plugins`, which caps what one plug-in keeps).
|
||||
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::Document;
|
||||
|
||||
/// `Document::unknown` key: `{"<plug-in id>": <what it keeps>}`.
|
||||
pub const PLUGIN_DATA: &str = "pluginData";
|
||||
|
||||
impl Document {
|
||||
/// What plug-in `id` keeps with the document, if anything.
|
||||
pub fn plugin_data(&self, id: &str) -> Option<&Value> {
|
||||
self.unknown.get(PLUGIN_DATA).and_then(|m| m.get(id))
|
||||
}
|
||||
|
||||
/// Keeps `data` for plug-in `id` in place of what it kept; `None` forgets it, and the key goes
|
||||
/// once no plug-in keeps anything. A value under the key that isn't a map of plug-in ids (a
|
||||
/// damaged file) is replaced when a plug-in keeps something.
|
||||
pub fn set_plugin_data(&mut self, id: &str, data: Option<Value>) {
|
||||
let Some(data) = data else {
|
||||
// Nothing to forget: don't copy the foreign data the undo history shares.
|
||||
if self.plugin_data(id).is_none() {
|
||||
return;
|
||||
}
|
||||
let Some(m) = self.unknown.get_mut(PLUGIN_DATA).and_then(Value::as_object_mut) else { return };
|
||||
m.remove(id);
|
||||
if m.is_empty() {
|
||||
self.unknown.remove(PLUGIN_DATA);
|
||||
}
|
||||
return;
|
||||
};
|
||||
let kept = self.unknown.entry(PLUGIN_DATA.to_string()).or_insert_with(|| Value::Object(Map::new()));
|
||||
if !kept.is_object() {
|
||||
*kept = Value::Object(Map::new());
|
||||
}
|
||||
if let Some(m) = kept.as_object_mut() {
|
||||
m.insert(id.to_string(), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Each plug-in has its own entry, replaced when it keeps something else; forgetting the last
|
||||
/// one removes the key, and forgetting what isn't kept copies nothing.
|
||||
#[test]
|
||||
fn each_plug_in_keeps_its_own_data() {
|
||||
let mut d = Document::new(10.0, 10.0);
|
||||
assert_eq!(d.plugin_data("a"), None);
|
||||
d.set_plugin_data("a", Some(json!({"pens": 2})));
|
||||
d.set_plugin_data("b", Some(json!([1, 2])));
|
||||
d.set_plugin_data("a", Some(json!({"pens": 3})));
|
||||
assert_eq!((d.plugin_data("a"), d.plugin_data("b")), (Some(&json!({"pens": 3})), Some(&json!([1, 2]))));
|
||||
d.set_plugin_data("a", None);
|
||||
assert_eq!(d.unknown[PLUGIN_DATA], json!({"b": [1, 2]}));
|
||||
let before = d.clone();
|
||||
d.set_plugin_data("a", None);
|
||||
assert!(crate::Shared::ptr_eq(&before.unknown, &d.unknown), "still shared with the earlier version");
|
||||
d.set_plugin_data("b", None);
|
||||
assert!(!d.unknown.contains_key(PLUGIN_DATA));
|
||||
}
|
||||
|
||||
/// A damaged file's value under the key holds nothing for any plug-in, stays while there is
|
||||
/// nothing to forget, and is replaced when a plug-in keeps something.
|
||||
#[test]
|
||||
fn a_damaged_value_holds_nothing() {
|
||||
let mut d = Document::new(10.0, 10.0);
|
||||
d.unknown.insert(PLUGIN_DATA.into(), json!(5));
|
||||
assert_eq!(d.plugin_data("a"), None);
|
||||
d.set_plugin_data("a", None);
|
||||
assert_eq!(d.unknown[PLUGIN_DATA], json!(5));
|
||||
d.set_plugin_data("a", Some(json!(true)));
|
||||
assert_eq!(d.unknown[PLUGIN_DATA], json!({"a": true}));
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,10 @@
|
||||
//! [`Session::attach_plugin_store`]) or from every `*.wasm` in the Additional Plug-ins Folder
|
||||
//! preference, and listed with `plugin.list` / `plugin.info`. Object filters run
|
||||
//! with `plugin.run` on the selected paths and compound paths (one undo step, transactional: a
|
||||
//! plug-in that fails leaves the document untouched); live-effect plug-ins are applied like any
|
||||
//! effect (`effect.apply {effect: "plugin.<id>"}`) and drawn by `vectorcraft-effects`; exporters
|
||||
//! add a file format that `document.export` writes (`fileio::plugin_formats`).
|
||||
//! plug-in that fails leaves the document untouched), and read and write the data they keep with
|
||||
//! the document ([`vectorcraft_doc::plugin_data`], theirs alone); live-effect plug-ins are applied
|
||||
//! like any effect (`effect.apply {effect: "plugin.<id>"}`) and drawn by `vectorcraft-effects`;
|
||||
//! exporters add a file format that `document.export` writes (`fileio::plugin_formats`).
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -60,8 +61,9 @@ fn plugin_by_id(cmd: &str, p: &Value) -> Result<Arc<Plugin>> {
|
||||
}
|
||||
|
||||
/// `plugin.run`: runs filter `id` on `ids` (else the selected paths and compound paths) with its
|
||||
/// parameters and `_context`, and applies its output as one undo step ([`objects::apply_output`];
|
||||
/// effect records as [`admit_effects`] admits them). → `{plugin, ids}`: what it made or changed,
|
||||
/// parameters and `_context` (what it keeps with the document included), and applies its output as
|
||||
/// one undo step ([`objects::apply_output`]; effect records as [`admit_effects`] admits them),
|
||||
/// with what it keeps with the document from now on. → `{plugin, ids}`: what it made or changed,
|
||||
/// which is then selected.
|
||||
fn run(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "plugin.run";
|
||||
@@ -85,12 +87,17 @@ fn run(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let bounds = st.doc.bounds_of(&inputs, false);
|
||||
let board = bounds.and_then(|b| st.doc.artboard_at(b.center())).and_then(|i| st.doc.artboards.get(i)).or(st.doc.artboards.first());
|
||||
let rect = |r: vectorcraft_geom::Rect| json!([r.x0, r.y0, r.x1, r.y1]);
|
||||
params["_context"] = json!({"mode": "filter", "bounds": bounds.map(rect), "artboard": board.map(|a| rect(a.rect))});
|
||||
// What the plug-in keeps with the document, as it last wrote it.
|
||||
let data = st.doc.plugin_data(&m.id).cloned().unwrap_or(Value::Null);
|
||||
params["_context"] = json!({"mode": "filter", "bounds": bounds.map(rect), "artboard": board.map(|a| rect(a.rect)), "documentData": data});
|
||||
let input = objects::encode_input(&st.doc, &inputs);
|
||||
let params = serde_json::to_vec(¶ms).map_err(|e| bad(C, e.to_string()))?;
|
||||
let out = objects::decode(&plugin.run(&input, ¶ms).map_err(err)?).map_err(err)?;
|
||||
let out = objects::decode_output(&plugin.run(&input, ¶ms).map_err(err)?).map_err(err)?;
|
||||
let ids = s.edit(m.name.trim_end_matches('…'), |d, sel| {
|
||||
let ids = objects::apply_output(d, &inputs, out, parent, &admit_effects).map_err(err)?;
|
||||
let ids = objects::apply_output(d, &inputs, out.objects, parent, &admit_effects).map_err(err)?;
|
||||
if let Some(data) = out.document_data {
|
||||
d.set_plugin_data(&m.id, data);
|
||||
}
|
||||
sel.set(ids.iter().copied());
|
||||
Ok(ids)
|
||||
})?;
|
||||
|
||||
@@ -330,6 +330,53 @@ fn rough_frame_test_filter() {
|
||||
std::fs::write(std::path::Path::new(&dir).join("rough-frame-test-filter.wasm"), wat::parse_str(src).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
/// A filter keeps data with the document: it writes it with `documentData` in the same undo step as
|
||||
/// its objects (`null` removes it) and reads what it last wrote in `_context.documentData`, which
|
||||
/// no other filter is given. It is the document's foreign data, so the native file keeps it with
|
||||
/// no section of its own. More than the limit is refused.
|
||||
#[test]
|
||||
fn filters_keep_their_data_with_the_document() {
|
||||
let mut s = session();
|
||||
let id = rect(&mut s, 10.0, "#ff0000");
|
||||
let pid = "test.engine.data";
|
||||
let returning = |out: Value| tpl::returning(&tpl::manifest(pid, "filter", "{}"), &out.to_string());
|
||||
let keep = |data: Value| json!({"objects": [{"id": id.0}], "documentData": data});
|
||||
let data = |s: &Session| s.doc().unwrap().doc.plugin_data(pid).cloned();
|
||||
let filter = |s: &mut Session, pid: &str| s.execute("plugin.run", &json!({"id": pid, "ids": [id.0]}));
|
||||
install_wat(&mut s, &returning(keep(json!({"pen": 2}))));
|
||||
filter(&mut s, pid).unwrap();
|
||||
assert_eq!(data(&s), Some(json!({"pen": 2})));
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
assert_eq!(data(&s), None, "one undo step with the objects");
|
||||
run(&mut s, "edit.redo", json!({}));
|
||||
// Read back: these name the object only when they are given what they look for.
|
||||
let named = |name: &str| json!({"objects": [{"id": id.0, "name": name}]}).to_string();
|
||||
let when_given =
|
||||
|pid: &str, needle: &str, name: &str| tpl::when_given(&tpl::manifest(pid, "filter", "{}"), tpl::Given::Params, needle, &named(name));
|
||||
install_wat(&mut s, &when_given(pid, r#""documentData":{"pen":2}"#, "Pen 2"));
|
||||
filter(&mut s, pid).unwrap();
|
||||
assert_eq!(node(&s, id).name.as_deref(), Some("Pen 2"));
|
||||
// Another filter is given nothing: what a filter keeps is its own.
|
||||
let other = "test.engine.other";
|
||||
install_wat(&mut s, &when_given(other, r#""documentData":null"#, "Nothing kept"));
|
||||
filter(&mut s, other).unwrap();
|
||||
assert_eq!(node(&s, id).name.as_deref(), Some("Nothing kept"));
|
||||
// Saved with the document, among its foreign data.
|
||||
let saved = vectorcraft_format::load(&vectorcraft_format::save(&s.doc().unwrap().doc, false)).unwrap();
|
||||
assert_eq!(saved.plugin_data(pid), Some(&json!({"pen": 2})));
|
||||
assert_eq!(saved.unknown.get(vectorcraft_doc::plugin_data::PLUGIN_DATA), Some(&json!({pid: {"pen": 2}})));
|
||||
// Too much is refused, and the document stays as it was; null removes the data.
|
||||
let before = s.doc().unwrap().doc.clone();
|
||||
let big = "x".repeat(vectorcraft_plugins::objects::MAX_DOCUMENT_DATA);
|
||||
install_wat(&mut s, &returning(keep(json!(big))));
|
||||
let e = filter(&mut s, pid).unwrap_err().to_string();
|
||||
assert!(e.contains("documentData"), "{e}");
|
||||
assert_eq!(s.doc().unwrap().doc, before);
|
||||
install_wat(&mut s, &returning(keep(Value::Null)));
|
||||
filter(&mut s, pid).unwrap();
|
||||
assert_eq!(data(&s), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_management_commands() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -104,6 +104,23 @@ fn the_exporter_is_given_the_native_file_s_json() {
|
||||
assert!(image.get("blob").is_some() && image.get("data").is_none(), "{image}");
|
||||
}
|
||||
|
||||
/// What filter plug-ins keep with the document reaches an exporter with the rest of the document:
|
||||
/// a plotter's exporter finds the pens a setup filter kept (`document.unknown.pluginData`, by
|
||||
/// plug-in id).
|
||||
#[test]
|
||||
fn an_exporter_finds_what_filters_keep_with_the_document() {
|
||||
let mut s = session();
|
||||
s.execute("select.none", &json!({})).unwrap();
|
||||
let pens = json!({"pens": [{"slot": 1, "width": 0.3}, {"slot": 2, "width": 0.5}]});
|
||||
let setup = json!({"objects": [], "documentData": pens});
|
||||
install(&mut s, &tpl::returning(&tpl::manifest("test.setup", "filter", "{}"), &setup.to_string()));
|
||||
s.execute("plugin.run", &json!({"id": "test.setup"})).unwrap();
|
||||
install(&mut s, &tpl::echo_exporter(&tpl::exporter("test.pens", r#"["tsd"]"#, "{}"), Echo::Input));
|
||||
let r = s.execute("document.export", &json!({"format": "plugin.test.pens"})).unwrap();
|
||||
let doc: Value = serde_json::from_slice(&exported(&r)).unwrap();
|
||||
assert_eq!(doc["document"]["unknown"][vectorcraft_doc::plugin_data::PLUGIN_DATA], json!({"test.setup": pens}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_exporter_is_given_its_parameters_and_the_file_it_writes() {
|
||||
let dir = tmp_dir("params");
|
||||
|
||||
@@ -30,6 +30,8 @@ pub const MAX_STROKE_WIDTH: f64 = 1000.0;
|
||||
pub const MITER_LIMIT: std::ops::RangeInclusive<f64> = 1.0..=500.0;
|
||||
/// Longest effect id a plug-in may return, in bytes.
|
||||
pub const MAX_EFFECT_ID: usize = 128;
|
||||
/// Largest data a plug-in may keep with a document (`documentData`), in bytes of JSON.
|
||||
pub const MAX_DOCUMENT_DATA: usize = 1 << 20;
|
||||
|
||||
/// One object returned by a plug-in. `None` fields keep the object's value (new objects take the
|
||||
/// defaults: a path, non-zero rule, no fills or strokes, full opacity).
|
||||
@@ -373,11 +375,45 @@ fn decode_effect(v: &Value, at: &str) -> Result<Effect> {
|
||||
Ok(Effect { id: id.to_string(), params, visible })
|
||||
}
|
||||
|
||||
/// Parses a plug-in's output: `{"objects": [...]}`, or `{"error": "message"}` when the plug-in
|
||||
/// declined (the message reaches the user).
|
||||
/// What a filter returned: its objects, and what it keeps with the document.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Output {
|
||||
pub objects: Vec<OutObject>,
|
||||
/// `documentData`, as `Document::set_plugin_data` takes it: `None` when the output has none
|
||||
/// (what the plug-in keeps stays as it is), `Some(None)` for `null` (it forgets it), else what
|
||||
/// it keeps from now on, at most [`MAX_DOCUMENT_DATA`] bytes of JSON.
|
||||
pub document_data: Option<Option<Value>>,
|
||||
}
|
||||
|
||||
/// Parses a filter's output: `{"objects": [...], "documentData"?: …}`, or `{"error": "message"}`
|
||||
/// when the plug-in declined (the message reaches the user).
|
||||
pub fn decode_output(bytes: &[u8]) -> Result<Output> {
|
||||
let mut v: Value = serde_json::from_slice(bytes).map_err(|e| Error::Output(format!("not JSON: {e}")))?;
|
||||
let objects = decode_objects(&v)?;
|
||||
let document_data = match v.get_mut("documentData").map(Value::take) {
|
||||
None => None,
|
||||
Some(Value::Null) => Some(None),
|
||||
Some(data) => {
|
||||
let size = serde_json::to_vec(&data).map_or(usize::MAX, |b| b.len());
|
||||
if size > MAX_DOCUMENT_DATA {
|
||||
return Err(Error::Output(format!("`documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA})")));
|
||||
}
|
||||
Some(Some(data))
|
||||
}
|
||||
};
|
||||
Ok(Output { objects, document_data })
|
||||
}
|
||||
|
||||
/// Parses a plug-in's objects: `{"objects": [...]}`, or `{"error": "message"}` when the plug-in
|
||||
/// declined (the message reaches the user). A live effect's other keys don't matter.
|
||||
pub fn decode(bytes: &[u8]) -> Result<Vec<OutObject>> {
|
||||
let v: Value = serde_json::from_slice(bytes).map_err(|e| Error::Output(format!("not JSON: {e}")))?;
|
||||
if let Some(e) = declined(&v) {
|
||||
decode_objects(&v)
|
||||
}
|
||||
|
||||
/// The objects of output `v`, or its decline.
|
||||
fn decode_objects(v: &Value) -> Result<Vec<OutObject>> {
|
||||
if let Some(e) = declined(v) {
|
||||
return Err(e);
|
||||
}
|
||||
let objects = v.get("objects").and_then(Value::as_array).ok_or_else(|| Error::Output("expected {\"objects\": [...]}".into()))?;
|
||||
|
||||
@@ -269,3 +269,25 @@ fn bad_stroke_styles_and_effect_records_are_refused() {
|
||||
assert!(decode_json(json!({"objects": [{"id": a, "effects": bad}]})).is_err(), "{bad}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A filter's `documentData`: absent keeps what the plug-in keeps, `null` forgets it, any other JSON
|
||||
/// of at most [`MAX_DOCUMENT_DATA`] bytes is kept, and more refuses the whole output.
|
||||
#[test]
|
||||
fn document_data_is_kept_forgotten_or_refused() {
|
||||
let out = |data: Option<Value>| {
|
||||
let mut v = json!({"objects": []});
|
||||
if let Some(data) = data {
|
||||
v["documentData"] = data;
|
||||
}
|
||||
decode_output(v.to_string().as_bytes()).map(|o| o.document_data)
|
||||
};
|
||||
assert_eq!(out(None).unwrap(), None);
|
||||
assert_eq!(out(Some(Value::Null)).unwrap(), Some(None));
|
||||
assert_eq!(out(Some(json!({"pens": 2}))).unwrap(), Some(Some(json!({"pens": 2}))));
|
||||
// A string's quotes count: this one is the limit exactly, and one more byte refuses it.
|
||||
let at = json!("x".repeat(MAX_DOCUMENT_DATA - 2));
|
||||
assert_eq!(out(Some(at.clone())).unwrap(), Some(Some(at)));
|
||||
assert!(matches!(out(Some(json!("x".repeat(MAX_DOCUMENT_DATA - 1)))), Err(Error::Output(_))));
|
||||
// A decline is still a decline.
|
||||
assert!(matches!(decode_output(br#"{"error": "No pens.", "documentData": 1}"#), Err(Error::Failed(_))));
|
||||
}
|
||||
|
||||
@@ -4627,6 +4627,7 @@ imageTrace Outline Obrys
|
||||
@msg {_1} font families available Dostupné rodiny písem: {_1}
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} je živý efekt (Efekt › Zásuvné moduly)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} je exportní zásuvný modul (Soubor › Export › Exportovat jako…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` má {size} bajtů (limit {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} už používá {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show propojené soubory ({_1}) zde nelze přečíst: zobrazují se jejich náhledy
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions propojené obrázky ({_1}) se na disku změnily: nové verze zobrazí Aktualizovat propojení
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4250,6 +4250,7 @@ theme Appearance Erscheinungsbild
|
||||
@msg {_1} font families available {_1} Schriftfamilien verfügbar
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} ist ein Live-Effekt (Effekt › Plug-ins)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} ist ein Export-Plug-in (Datei › Exportieren › Exportieren als…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` ist {size} Byte groß (Grenze {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} wird bereits von {_2} verwendet
|
||||
@msg {_1} linked file(s) can't be read here: their previews show {_1} verknüpfte Datei(en) können hier nicht gelesen werden: Ihre Vorschauen werden angezeigt
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions {_1} verknüpfte(s) Bild(er) wurde(n) auf dem Datenträger geändert: „Verknüpfungen aktualisieren“ zeigt die neuen Versionen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4298,6 +4298,7 @@ theme Appearance Εμφάνιση
|
||||
@msg {_1} font families available {_1} διαθέσιμες οικογένειες γραμματοσειρών
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) το {_1} είναι δυναμικό εφέ (Εφέ › Πρόσθετα)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) το {_1} είναι πρόσθετο εξαγωγής (Αρχείο › Εξαγωγή › Εξαγωγή ως…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) το `documentData` έχει {size} byte (όριο {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} το {_1} χρησιμοποιείται ήδη από το {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show {_1} συνδεδεμένα αρχεία δεν μπορούν να διαβαστούν εδώ: εμφανίζονται οι προεπισκοπήσεις τους
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions {_1} συνδεδεμένες εικόνες άλλαξαν στον δίσκο: η Ενημέρωση συνδέσμων εμφανίζει τις νέες εκδόσεις
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2045 and column 19.
|
@@ -4325,6 +4325,7 @@ axis Both Ambos
|
||||
@msg {_1} font families available {_1} familias de fuentes disponibles
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} es un efecto interactivo (Efecto › Plugins)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} es un plugin de exportación (Archivo › Exportar › Exportar como…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` ocupa {size} bytes (límite {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} ya lo usa {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show aquí no se pueden leer {_1} archivo(s) vinculado(s): se muestran sus previsualizaciones
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions {_1} imagen(es) vinculada(s) cambiaron en el disco: Actualizar vínculos muestra las versiones nuevas
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4534,6 +4534,7 @@ imageTrace Outline Ääriviiva
|
||||
@msg {_1} font families available {_1} fonttiperhettä käytettävissä
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} on live-tehoste (Tehoste › Laajennukset)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} on vientilaajennus (Tiedosto › Vie › Vie muodossa…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` on {size} tavua (raja {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} on jo käytössä kohteella {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show {_1} linkitettyä tiedostoa ei voi lukea täällä: niiden esikatselut näytetään
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions {_1} linkitettyä kuvaa muuttui levyllä: Päivitä linkit näyttää uudet versiot
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4322,6 +4322,7 @@ axis Both Les deux
|
||||
@msg {_1} font families available {_1} familles de polices disponibles
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} est un effet dynamique (Effet › Plug-ins)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} est un module externe d’exportation (Fichier › Exporter › Exporter sous…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` fait {size} octets (limite {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} est déjà utilisé par {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show {_1} fichier(s) lié(s) illisible(s) ici : leurs aperçus sont affichés
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions {_1} image(s) liée(s) modifiée(s) sur le disque : Mettre à jour les liens affiche les nouvelles versions
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5083,6 +5083,7 @@ imageTrace Outline Körvonal
|
||||
@msg {_1} has no {_2} style ennek nincs ilyen stílusa: {_1} ({_2})
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) élő effektus: {_1} (Effektus › Bővítmények)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) exportáló bővítmény: {_1} (Fájl › Exportálás › Exportálás másként…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) a `documentData` mérete {size} bájt (korlát: {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} már foglalt: {_1} (használja: {_2})
|
||||
@msg {_1} linked file(s) can't be read here: their previews show Itt nem olvasható csatolt fájlok: {_1}; az előnézetük látszik
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions A lemezen módosult csatolt képek: {_1}; a Csatolások frissítése az új változatokat mutatja
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4365,6 +4365,7 @@ axis Both Entrambi
|
||||
@msg {_1} font families available {_1} famiglie di font disponibili
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} è un effetto dinamico (Effetto › Plug-in)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} è un plug-in di esportazione (File › Esporta › Esporta come…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` occupa {size} byte (limite {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} è già usato da {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show file collegati non leggibili qui: {_1}; vengono mostrate le loro anteprime
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions immagini collegate modificate su disco: {_1}; Aggiorna collegamenti mostra le nuove versioni
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4355,6 +4355,7 @@ swatch Bright Violet ビビッドバイオレット
|
||||
@msg no plug-in {id} is installed プラグイン {id} はインストールされていません
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} はライブ効果です (「効果 › プラグイン」から使用してください)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} は書き出しプラグインです (「ファイル › 書き出し › 形式を指定して書き出し…」から使用してください)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` は {size} バイトです (上限 {MAX_DOCUMENT_DATA})
|
||||
@msg a preset named `{_1}` exists `{_1}` という名前のプリセットはすでに存在します
|
||||
@msg a swatch or color group named `{n}` exists `{n}` という名前のスウォッチまたはカラーグループはすでに存在します
|
||||
@msg no print preset named `{base}` `{base}` という名前のプリントプリセットが見つかりません
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4083,6 +4083,7 @@ effect Regular ჩვეულებრივი
|
||||
@msg {_1} font families available ხელმისაწვდომი შრიფტების ოჯახები: {_1}
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} ცოცხალი ეფექტია (ეფექტი › პლაგინები)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} ექსპორტის პლაგინია (ფაილი › ექსპორტირება › ექსპორტირება ფორმატში…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` {size} ბაიტია (ლიმიტი {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} უკვე გამოიყენება აქ: {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show მიბმული ფაილების წაკითხვა აქ ვერ ხერხდება ({_1}): ჩანს მათი გადახედვა
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions მიბმული გამოსახულებები შეიცვალა დისკზე ({_1}): ბმულების განახლება აჩვენებს ახალ ვერსიებს
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4287,6 +4287,7 @@ theme Appearance 외관
|
||||
@msg no plug-in {id} is installed 플러그인 {id}이(가) 설치되어 있지 않습니다
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1}은(는) 라이브 효과입니다(효과 › 플러그인)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1}은(는) 내보내기 플러그인입니다(파일 › 내보내기 › 다른 이름으로 내보내기…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData`가 {size}바이트입니다(한도 {MAX_DOCUMENT_DATA})
|
||||
@msg a preset named `{_1}` exists `{_1}`이라는 프리셋이 이미 있습니다
|
||||
@msg a swatch or color group named `{n}` exists `{n}`이라는 견본 또는 색상 그룹이 이미 있습니다
|
||||
@msg no print preset named `{base}` `{base}`라는 인쇄 프리셋이 없습니다
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4452,6 +4452,7 @@ imageTrace Outline Kontur
|
||||
@msg {_1} font families available dostępne rodziny czcionek: {_1}
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} to efekt na żywo (Efekt › Wtyczki)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} to wtyczka eksportu (Plik › Eksportuj › Eksportuj jako…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` ma {size} bajtów (limit {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} jest już używany przez {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show połączonych plików nie można tu odczytać ({_1}): wyświetlane są ich podglądy
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions połączone obrazy zmienione na dysku: {_1} — Aktualizuj łącza pokaże nowe wersje
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3898,6 +3898,7 @@ axis Both Ambos
|
||||
@msg {_1} font families available {_1} famílias de fontes disponíveis
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} é um efeito dinâmico (Efeito › Plug-ins)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} é um plug-in de exportação (Arquivo › Exportar › Exportar Como…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` tem {size} bytes (limite {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} já é usado por {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show {_1} arquivo(s) vinculado(s) não podem ser lidos aqui: suas visualizações são exibidas
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions {_1} imagem(ns) vinculada(s) mudou(aram) no disco: Atualizar links mostra as novas versões
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -4361,6 +4361,7 @@ axis Both Оба
|
||||
@msg {_1} font families available доступно семейств шрифтов: {_1}
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} — динамический эффект (Эффект › Модули)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} — модуль экспорта (Файл › Экспорт › Экспортировать как…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` занимает {size} байт (предел {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} уже используется {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show здесь невозможно прочитать связанных файлов: {_1}: отображаются их предварительные просмотры
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions связанных изображений ({_1}) изменились на диске: «Обновить связи» покажет новые версии
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4234,6 +4234,7 @@ axis Both Båda
|
||||
@msg {_1} font families available {_1} teckensnittsfamiljer tillgängliga
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} är en live-effekt (Effekt › Insticksprogram)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} är ett exportinsticksprogram (Fil › Exportera › Exportera som…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` är {size} byte (gräns {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} används redan av {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show {_1} länkade fil(er) kan inte läsas här: deras förhandsgranskningar visas
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions {_1} länkade bild(er) har ändrats på disken: Uppdatera länkar visar de nya versionerna
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5176,6 +5176,7 @@ theme Appearance Görünüm
|
||||
@msg select an Objects on Path group bir Yol Üzerindeki Nesneler grubu seçin
|
||||
@msg the Simplify slider is not open Basitleştirme kaydırıcısı açık değil
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} bir dışa aktarma eklentisidir (Dosya › Dışa Aktar › Farklı Dışa Aktar…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` {size} bayt (sınır {MAX_DOCUMENT_DATA})
|
||||
Alt key toggles to Smooth Tool Alt tuşu Düzleştirme Aracına geçiş yapar
|
||||
Auto-Simplify Otomatik Basitleştir
|
||||
Drag an item here, or right-click one, to put it in this group. Bu gruba eklemek için bir öğeyi buraya sürükleyin veya sağ tıklayın.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -4349,6 +4349,7 @@ axis Both Обидві
|
||||
@msg {_1} font families available доступно сімейства шрифтів: {_1}
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} - динамічний ефект (Ефект › Модулі)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} - модуль експорту (Файл › Експорт › Експортувати як…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` займає {size} байт (межа {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} вже використовується {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show тут неможливо прочитати пов’язані файли: {_1}: відображаються їх попередні перегляди
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions пов’язаних зображень ({_1}) змінилися на диску: «Оновити зв’язки» покаже нові версії
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 733 and column 40.
|
@@ -4742,6 +4742,7 @@ theme Appearance Giao diện
|
||||
@msg {_1} has no {_2} style {_1} không có kiểu {_2}
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} là hiệu ứng trực tiếp (Hiệu ứng › Plug-ins)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} là plug-in xuất (Tệp › Xuất › Xuất thành…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` có {size} byte (giới hạn {MAX_DOCUMENT_DATA})
|
||||
@msg {_1} is already used by {_2} {_1} đã được dùng bởi {_2}
|
||||
@msg {_1} linked file(s) can't be read here: their previews show {_1} tệp liên kết không thể đọc ở đây: bản xem trước được hiển thị
|
||||
@msg {_1} linked image(s) changed on disk: Update Links shows the new versions {_1} ảnh liên kết đã thay đổi trên đĩa: Cập nhật liên kết hiển thị phiên bản mới
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 4030 and column 10.
|
@@ -4082,6 +4082,7 @@ imageTrace Outline 轮廓
|
||||
@msg no plug-in {id} is installed 未安装增效工具 {id}
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} 是实时效果 (效果 › 增效工具)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} 是导出增效工具 (文件 › 导出 › 导出为…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` 为 {size} 字节(上限 {MAX_DOCUMENT_DATA})
|
||||
@msg a preset named `{_1}` exists 名为 `{_1}` 的预设已存在
|
||||
@msg a swatch or color group named `{n}` exists 名为 `{n}` 的色板或颜色组已存在
|
||||
@msg no print preset named `{base}` 没有名为 `{base}` 的打印预设
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2068,6 +2068,7 @@ file-filter Templates 範本
|
||||
@msg no plug-in {id} is installed 未安裝增效模組 {id}
|
||||
@msg {_1} is a live effect (Effect › Plug-ins) {_1} 是即時效果 (效果 › 增效模組)
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} 是匯出增效模組 (檔案 › 匯出 › 匯出為…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` 為 {size} 位元組(上限 {MAX_DOCUMENT_DATA})
|
||||
@msg a preset named `{_1}` exists 名為 `{_1}` 的預設集已存在
|
||||
@msg a swatch or color group named `{n}` exists 名為 `{n}` 的色票或顏色群組已存在
|
||||
@msg no print preset named `{base}` 沒有名為 `{base}` 的列印預設集
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
+6
-1
File diff suppressed because one or more lines are too long
+11
-2
@@ -212,12 +212,13 @@ type and `blob` but without their data, which follows the JSON in a saved file;
|
||||
|
||||
```json
|
||||
{"amount": 50, "mode": "soft",
|
||||
"_context": {"mode": "filter", "bounds": [10, 0, 75, 60], "artboard": [0, 0, 612, 792]}}
|
||||
"_context": {"mode": "filter", "bounds": [10, 0, 75, 60], "artboard": [0, 0, 612, 792], "documentData": {"pens": 4}}}
|
||||
```
|
||||
|
||||
`mode` is `"filter"`, `"effect"` or `"export"`; `bounds` the selection's geometric bounds (a live effect: the reference
|
||||
box of the effect, the object's bounds or those of the previous effect's result); `artboard` (filters) the artboard
|
||||
under the selection's centre, else the first. A live effect's `item` says where it sits: `"object"` (one of the
|
||||
under the selection's centre, else the first; `documentData` (filters) what the plug-in last kept with the document
|
||||
(`null` until it keeps something; see Output). A live effect's `item` says where it sits: `"object"` (one of the
|
||||
object's effects), or `{"fill": i}` / `{"stroke": i}` (in the own effects of the object's `i`th fill or stroke,
|
||||
counted from the bottom as in `fills` and `strokes`). An export's `_context` is `{"mode": "export", "extension": "plt",
|
||||
"name": "Poster"}` instead: the extension of the file being written (one the exporter lists) and its name without the
|
||||
@@ -254,6 +255,14 @@ clamped into range, stroke weights to 0–1,000 pt and miter limits to 1–500;
|
||||
paint returned unchanged keeps its swatch link; any other paint is unlinked. One refused effect record refuses the
|
||||
whole output.
|
||||
|
||||
A filter's output may also hold `documentData`: any JSON of at most 1 MiB, which replaces what the plug-in keeps with
|
||||
the document (`null` removes it), in the same undo step as its objects. Settings that belong to the whole document
|
||||
rather than to one object go there, such as the pens a plotter holds. A filter reads and writes only its own data. It
|
||||
is the document's foreign data, as the perspective grid is: the native file keeps it in `document.unknown.pluginData`
|
||||
under the plug-in's id, and hosts from before `documentData` keep it when they save. An exporter is given the whole
|
||||
document, so it can read what every plug-in keeps there, not only its own. A live effect isn't given it: its geometry
|
||||
is worked out from the object alone.
|
||||
|
||||
For a **live effect**, the geometry of every returned object's `path` (their subpaths, in order) replaces the
|
||||
object's; the rest is ignored (the object keeps its own appearance). An empty list removes the geometry.
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user