mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Merge pull request #3720 from KaboomFox/p5-document-data-v2
Plug-ins: filters keep their settings in the document's foreign data (G17)
This commit is contained in:
@@ -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