Effects: effect.apply and effect.setParams refuse a color the effect can't draw

effect.apply and effect.setParams stored any string as the color of
Drop Shadow, Inner Glow, Outer Glow and Shift to Color. When the
effect could not read it, it drew its default color with no message,
so a typo such as #ff00 in the Drop Shadow dialog turned the shadow
black.

The commands now refuse such a color before they edit, with
"`color` must be a #rrggbb color", and the document and its undo
history stay as they were. Hex colors, the five named colors,
[r, g, b] and color objects still pass. The check runs in the
commands, so the app, command.batch steps and replayed journals get
it too. The effect dialog's OK shows the error and keeps the dialog
open, with Preview on or off. Saved documents open as before.

Docs: docs/mcp.md (effect params).
This commit is contained in:
dotCooCoo
2026-10-17 13:52:06 +07:00
committed by bflatastic
parent b9d370aeff
commit b9c3dd4519
24 changed files with 172 additions and 22 deletions
+6 -1
View File
@@ -138,6 +138,7 @@ pub(crate) fn apply(s: &mut Session, p: &Value) -> Result<Value> {
if !params.is_null() && !params.is_object() {
return Err(bad(C, "`params` must be an object"));
}
super::params::check_effect_color(C, id, &params)?;
// Crop Marks take their style from the preferences when applied.
if id == effects::CROP_MARKS && params.get("style").is_none() {
params["style"] = json!(s.crop_mark_style().id());
@@ -313,7 +314,11 @@ fn set_params(s: &mut Session, p: &Value) -> Result<Value> {
.transpose()?;
let visible = p.get("visible").and_then(Value::as_bool);
let item = item_target(s, p, C)?;
if set_params_effects(s, p).iter().any(|e| e == GLASS) {
let edited = set_params_effects(s, p);
for effect in &edited {
super::params::check_effect_color(C, effect, &params)?;
}
if edited.iter().any(|e| e == GLASS) {
load_texture(&mut params, C)?;
}
for id in appearance_targets(s, p)? {
+40 -3
View File
@@ -13,7 +13,9 @@
//! command with its default. So are the `params` of an effect (#1592): `effect.apply` and
//! `effect.setParams` answer a key the effect doesn't take, or a value of another type than its
//! default, with a parameter error ([`check_effect_params`]). [`Session::execute`] itself takes
//! what it is given, so the journal of an older version still replays.
//! what it is given, so the journal of an older version still replays, except for an effect's
//! `color` that the effect can't read, which `effect.apply` and `effect.setParams` refuse
//! themselves ([`check_effect_color`]).
use serde_json::Value;
@@ -212,8 +214,9 @@ fn unknown(id: &str, key: &str, keys: &[&str]) -> crate::EngineError {
/// `params` of built-in effect `effect`, as command `id` was given them (#1592): only keys the
/// effect takes (those of its dialog defaults and of its params description), each of its
/// default's type. Numbers out of range pass: the effect clamps them when it draws. An effect
/// plug-in resolves its own against its manifest, and an unknown effect is the command's to report.
/// default's type, and a `color` the effect can draw ([`check_effect_color`]). Numbers out of
/// range pass: the effect clamps them when it draws. An effect plug-in resolves its own against
/// its manifest, and an unknown effect is the command's to report.
pub fn check_effect_params(id: &str, effect: &str, params: &Value) -> Result<()> {
let Value::Object(given) = params else { return Ok(()) };
if effect.starts_with(vectorcraft_plugins::effect::PREFIX) {
@@ -240,6 +243,40 @@ pub fn check_effect_params(id: &str, effect: &str, params: &Value) -> Result<()>
_ => {}
}
}
check_effect_color(id, effect, params)
}
/// Can an effect read `v` as its `color`: a string [`Color::from_hex`] reads (`#rrggbb`, `#rgb`,
/// `#rrggbbaa`, black, white, red, green, blue), `[r, g, b]` whose first three entries are numbers
/// (read on a 0..1 scale) or a color object (`{"model": "rgb", "r": …}`)? The effect draws its
/// default color for any other value, except that an array of three or more entries draws an
/// entry that isn't a number as 0.
///
/// [`Color::from_hex`]: vectorcraft_color::Color::from_hex
fn is_effect_color(v: &Value) -> bool {
match v {
Value::String(s) => vectorcraft_color::Color::from_hex(s).is_some(),
Value::Array(a) => a.len() >= 3 && a.iter().take(3).all(Value::is_number),
Value::Object(_) => serde_json::from_value::<vectorcraft_color::Color>(v.clone()).is_ok(),
_ => false,
}
}
/// The `color` in `params` of built-in effect `effect`, when the effect has a `color` parameter,
/// as command `id` was given it: a parameter error naming it unless the effect can read it
/// ([`is_effect_color`]). `effect.apply` and `effect.setParams` run this themselves before they
/// edit, so the app's own edits, the steps of a `command.batch` and a replayed journal are held to
/// it too.
pub(crate) fn check_effect_color(id: &str, effect: &str, params: &Value) -> Result<()> {
let key = "color";
let Some(v) = params.get(key) else { return Ok(()) };
if effect.starts_with(vectorcraft_plugins::effect::PREFIX) {
return Ok(());
}
let takes_color = vectorcraft_render::effects::effect_info(effect).is_some_and(|info| info.defaults.get(key).is_some_and(Value::is_string));
if takes_color && !is_effect_color(v) {
return Err(bad(id, format!("`{key}` must be a #rrggbb color")));
}
Ok(())
}
+52
View File
@@ -44,6 +44,58 @@ fn effect_params_no_longer_taken_still_replay_and_open() {
assert_eq!(stored(&back)["blurr"], json!(3));
}
/// An effect's `color` that the effect can't read (a string `Color::from_hex` doesn't read, an
/// `[r, g, b]` without three numbers, an object that isn't a color, `null`) is refused by
/// `effect.apply`, `appearance.addEffect` and `effect.setParams` themselves, not only by the check,
/// and the document and its undo history stay as they were. The forms the effect reads pass. A
/// document saved with an unreadable color still opens with it.
#[test]
fn an_effect_color_it_cannot_draw_is_refused() {
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
let id = s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 50, "height": 40})).unwrap()["id"].as_u64().unwrap();
s.execute("effect.apply", &json!({"effect": "stylize.dropShadow", "params": {"color": "#ff0000"}})).unwrap();
let state = |s: &Session| {
let st = s.active().unwrap();
(st.doc.clone(), st.history.undo.len())
};
let before = state(&s);
let why = "`color` must be a #rrggbb color";
for bad in [json!("not-a-color"), json!("#ff00"), json!([1, 0]), json!(["#ff0000", 0, 0]), json!({"gray": 50}), json!(null)] {
for (cmd, params) in [
("effect.setParams", json!({"index": 0, "params": {"color": bad}})),
("effect.apply", json!({"effect": "stylize.outerGlow", "params": {"color": bad}})),
("appearance.addEffect", json!({"effect": "adjust.shiftToColor", "params": {"color": bad}})),
] {
let e = s.execute(cmd, &params).unwrap_err().to_string();
assert!(e.contains(why), "{cmd} {params}: {e}");
let checked = s.check_params(cmd, &params).unwrap_err().to_string();
assert!(checked.contains(why), "{cmd} {params}: {checked}");
assert!(state(&s) == before, "{cmd} {params} changed the document or its undo history");
}
}
let stored = |s: &Session| s.active().unwrap().doc.layers[0].children().unwrap()[0].appearance.effects[0].params["color"].clone();
for good in [
json!("#00ff00"),
json!("#0f0"),
json!("00ff00"),
json!("#00ff00ff"),
json!("green"),
json!([0, 1, 0]),
json!({"model": "rgb", "r": 0.0, "g": 1.0, "b": 0.0}),
json!({"model": "cmyk", "c": 1.0, "m": 0.0, "y": 1.0, "k": 0.0}),
] {
let params = json!({"index": 0, "params": {"color": good}});
s.check_params("effect.setParams", &params).unwrap_or_else(|e| panic!("{good}: {e}"));
s.execute("effect.setParams", &params).unwrap_or_else(|e| panic!("{good}: {e}"));
assert_eq!(stored(&s), good);
}
let mut doc = (*s.active().unwrap().doc).clone();
doc.node_mut(vectorcraft_doc::NodeId(id)).unwrap().appearance.effects[0].params["color"] = json!("not-a-color");
let back = vectorcraft_format::load(&vectorcraft_format::save(&doc, false)).unwrap();
assert_eq!(back.layers[0].children().unwrap()[0].appearance.effects[0].params["color"], json!("not-a-color"));
}
/// What a command adds to its own journal entry passes the check, so a recorded journal can be
/// sent back through the control channel or MCP.
#[test]
+4 -16
View File
@@ -604,26 +604,14 @@ fn command(d: &Dialog) -> (&'static str, Value) {
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
let (cmd, p) = command(d);
if super::warp_effect::is_warp(&d.str("__effect")) {
let r = form::commit_preview(app, cmd, p.clone())?;
if cmd == "effect.apply" {
app.last_effect = Some((d.str("__effect"), p["params"].clone()));
}
return Ok(r);
}
let r = if app.session.in_interaction() {
// Live preview already applied: keep it (the interaction commits as one undo step).
let _ = app.session.preview(cmd, &p);
app.session.commit_interaction().map(|_| Value::Null).map_err(|e| e.to_string())
} else {
app.run(cmd, p.clone())
};
// A live preview commits as one undo step. When the command refuses the values (a Color the
// effect can't draw), the dialog stays open and the error is returned.
let r = form::commit_preview(app, cmd, p.clone())?;
// Last Effect repeats applied effects, not edits.
if cmd == "effect.apply" {
app.last_effect = Some((d.str("__effect"), p["params"].clone()));
}
app.ui.dialog = None;
r
Ok(r)
}
#[cfg(test)]
+21
View File
@@ -174,6 +174,27 @@ fn effect_preview_is_kept_as_one_undo_step() {
assert_eq!(app.session.doc().unwrap().doc, before);
}
/// Drop Shadow's Color typed as text the effect can't draw: OK returns the error and keeps the
/// dialog open, with Preview on or off, and the document stays as it was.
#[test]
fn effect_dialog_refuses_a_color_it_cannot_draw() {
for preview in [true, false] {
let mut app = app();
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap();
app.run("select.all", json!({})).unwrap();
let before = app.session.doc().unwrap().doc.clone();
app.run("effect.dialog", json!({"effect": "stylize.dropShadow"})).unwrap();
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert("preview".into(), json!(preview));
d.fields.insert("color".into(), json!("not-a-color"));
frame(&mut app, Default::default());
let e = confirm(&mut app).unwrap_err();
assert!(e.contains("`color` must be a #rrggbb color"), "preview {preview}: {e}");
assert!(app.ui.dialog.is_some() && app.last_effect.is_none(), "preview {preview}");
assert_eq!(app.session.doc().unwrap().doc, before, "preview {preview}");
}
}
#[test]
fn raster_filter_dialogs_preview_and_apply() {
for (effect, key, value) in [
+1
View File
@@ -3605,6 +3605,7 @@ axis Both И двете
@msg `{id}` has no envelope dialog `{id}` няма диалогов прозорец за обвивка
@msg `{keep}` is not a side of this axis `{keep}` не е страна на тази ос
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` трябва да бъде `auto` или езиков код като `en` или `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` трябва да бъде цвят във формат #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` трябва да бъде цвят във формат #rrggbb
@msg `{key}` must be a number `{key}` трябва да бъде число
@msg `{key}` must be a string `{key}` трябва да бъде низ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3976,6 +3976,7 @@ imageTrace Outline Obrys
@msg `{id}` has no envelope dialog `{id}` nemá dialogové okno obálky
@msg `{keep}` is not a side of this axis `{keep}` není strana této osy
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` musí být `auto` nebo kód jazyka, například `en` nebo `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` musí být barva #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` musí být barva #rrggbb
@msg `{key}` must be a number `{key}` musí být číslo
@msg `{key}` must be a string `{key}` musí být řetězec
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3579,6 +3579,7 @@ theme Appearance Erscheinungsbild
@msg `{id}` has no envelope dialog `{id}` hat kein Hüllendialogfeld
@msg `{keep}` is not a side of this axis `{keep}` ist keine Seite dieser Achse
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` muss `auto` oder ein Sprachcode wie `en` oder `zh-hant` sein
@msg `{key}` must be a #rrggbb color `{key}` muss eine Farbe der Form #rrggbb sein
@msg `{key}` must be a #rrggbb colour `{key}` muss eine Farbe der Form #rrggbb sein
@msg `{key}` must be a number `{key}` muss eine Zahl sein
@msg `{key}` must be a string `{key}` muss eine Zeichenfolge sein
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1988,6 +1988,7 @@ axis Both Και τα δύο
@msg `{id}` has no envelope dialog Το `{id}` δεν έχει παράθυρο διαλόγου περιβλήματος
@msg `{keep}` is not a side of this axis Το `{keep}` δεν είναι πλευρά αυτού του άξονα
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` Το `{key}` πρέπει να είναι `auto` ή κωδικός γλώσσας όπως `en` ή `zh-hant`
@msg `{key}` must be a #rrggbb color Το `{key}` πρέπει να είναι χρώμα #rrggbb
@msg `{key}` must be a #rrggbb colour Το `{key}` πρέπει να είναι χρώμα #rrggbb
@msg `{key}` must be a number Το `{key}` πρέπει να είναι αριθμός
@msg `{key}` must be a string Το `{key}` πρέπει να είναι συμβολοσειρά
Can't render this file because it contains an unexpected character in line 1902 and column 19.
+1
View File
@@ -3676,6 +3676,7 @@ axis Both Ambos
@msg `{id}` has no envelope dialog `{id}` no tiene cuadro de diálogo de envolvente
@msg `{keep}` is not a side of this axis `{keep}` no es un lado de este eje
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` debe ser `auto` o un código de idioma como `en` o `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` debe ser un color #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` debe ser un color #rrggbb
@msg `{key}` must be a number `{key}` debe ser un número
@msg `{key}` must be a string `{key}` debe ser un texto
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3903,6 +3903,7 @@ imageTrace Outline Ääriviiva
@msg `{id}` has no envelope dialog `{id}`: ei vääristyskuoren valintaikkunaa
@msg `{keep}` is not a side of this axis `{keep}` ei ole tämän akselin puoli
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` on oltava `auto` tai kielikoodi, kuten `en` tai `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` on oltava #rrggbb-väri
@msg `{key}` must be a #rrggbb colour `{key}` on oltava #rrggbb-väri
@msg `{key}` must be a number `{key}` on oltava luku
@msg `{key}` must be a string `{key}` on oltava merkkijono
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3673,6 +3673,7 @@ axis Both Les deux
@msg `{id}` has no envelope dialog `{id}` n’a pas de boîte de dialogue d’enveloppe
@msg `{keep}` is not a side of this axis `{keep}` n’est pas un côté de cet axe
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` doit être `auto` ou un code de langue comme `en` ou `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` doit être une couleur #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` doit être une couleur #rrggbb
@msg `{key}` must be a number `{key}` doit être un nombre
@msg `{key}` must be a string `{key}` doit être une chaîne
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4169,6 +4169,7 @@ imageTrace Outline Körvonal
@msg `{id}` has no envelope dialog ennek nincs burkoló-párbeszédablaka: `{id}`
@msg `{keep}` is not a side of this axis `{keep}` nem ennek a tengelynek az oldala
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}`: `auto` vagy nyelvkód kell, például `en` vagy `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}`: #rrggbb szín kell
@msg `{key}` must be a #rrggbb colour `{key}`: #rrggbb szín kell
@msg `{key}` must be a number `{key}`: szám kell
@msg `{key}` must be a string `{key}`: szöveg kell
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3679,6 +3679,7 @@ axis Both Entrambi
@msg `{id}` has no envelope dialog `{id}` non ha una finestra di dialogo per l'involucro
@msg `{keep}` is not a side of this axis `{keep}` non è un lato di questo asse
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` deve essere `auto` o un codice lingua come `en` o `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` deve essere un colore #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` deve essere un colore #rrggbb
@msg `{key}` must be a number `{key}` deve essere un numero
@msg `{key}` must be a string `{key}` deve essere una stringa
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3857,6 +3857,7 @@ swatch Bright Violet ビビッドバイオレット
@msg no artboard アートボードがありません
@msg installing from a path is not available on the web; pass the module as `dataBase64` Web 版ではファイルパスからはインストールできないため、モジュールは `dataBase64` で渡してください
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` は `auto` か、`en` や `zh-hant` などの言語コードにしてください
@msg `{key}` must be a #rrggbb color `{key}` は #rrggbb 形式のカラーにしてください
@msg `{key}` must be a #rrggbb colour `{key}` は #rrggbb 形式のカラーにしてください
@msg `{key}` must be a string `{key}` は文字列にしてください
@msg `{key}` must be a whole number `{key}` は整数にしてください
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3789,6 +3789,7 @@ theme Appearance 외관
@msg no artboard 아트보드가 없습니다
@msg installing from a path is not available on the web; pass the module as `dataBase64` 웹에서는 경로로 설치할 수 없습니다. 모듈을 `dataBase64`로 전달하세요
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}`은(는) `auto` 또는 `en`, `zh-hant` 같은 언어 코드여야 합니다
@msg `{key}` must be a #rrggbb color `{key}`은(는) #rrggbb 색상이어야 합니다
@msg `{key}` must be a #rrggbb colour `{key}`은(는) #rrggbb 색상이어야 합니다
@msg `{key}` must be a string `{key}`은(는) 문자열이어야 합니다
@msg `{key}` must be a whole number `{key}`은(는) 정수여야 합니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3791,6 +3791,7 @@ imageTrace Outline Kontur
@msg `{id}` has no envelope dialog `{id}` nie ma okna dialogowego koperty
@msg `{keep}` is not a side of this axis `{keep}` nie jest stroną tej osi
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` musi mieć wartość `auto` lub kod języka, np. `en` albo `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` musi być kolorem #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` musi być kolorem #rrggbb
@msg `{key}` must be a number `{key}` musi być liczbą
@msg `{key}` must be a string `{key}` musi być ciągiem znaków
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3275,6 +3275,7 @@ axis Both Ambos
@msg `{id}` has no envelope dialog `{id}` não tem caixa de diálogo de envelope
@msg `{keep}` is not a side of this axis `{keep}` não é um lado deste eixo
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` deve ser `auto` ou um código de idioma como `en` ou `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` deve ser uma cor #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` deve ser uma cor #rrggbb
@msg `{key}` must be a number `{key}` deve ser um número
@msg `{key}` must be a string `{key}` deve ser uma string
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3675,6 +3675,7 @@ axis Both Оба
@msg `{id}` has no envelope dialog `{id}` не имеет диалогового окна оболочки
@msg `{keep}` is not a side of this axis `{keep}` не является стороной этой оси
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` должно быть `auto` или кодом языка, например `en` или `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` должно быть цветом #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` должно быть цветом #rrggbb
@msg `{key}` must be a number `{key}` должно быть числом
@msg `{key}` must be a string `{key}` должно быть строкой
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3605,6 +3605,7 @@ axis Both Båda
@msg `{id}` has no envelope dialog `{id}` har ingen dialogruta för kuvert
@msg `{keep}` is not a side of this axis `{keep}` är inte en sida av den här axeln
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` måste vara `auto` eller en språkkod som `en` eller `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` måste vara en färg i formatet #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` måste vara en färg i formatet #rrggbb
@msg `{key}` must be a number `{key}` måste vara ett tal
@msg `{key}` must be a string `{key}` måste vara en sträng
Can't render this file because it contains an unexpected character in line 5 and column 25.
+14
View File
@@ -1251,6 +1251,20 @@ fn unknown_parameter_errors_are_translated() {
}
}
/// The error for an effect `color` the effect can't draw, whose text the scan above doesn't read,
/// is translated whole in each language of [`COMPLETE_MESSAGES`] and in Bulgarian.
#[test]
fn effect_color_param_error_is_translated() {
use serde_json::json;
let p = json!({"effect": "stylize.dropShadow", "params": {"color": "not-a-color"}});
let e = vectorcraft_engine::check_params("effect.apply", &p).unwrap_err();
let vectorcraft_engine::EngineError::BadParams { msg, .. } = &e else { panic!("{e}") };
for code in COMPLETE_MESSAGES.iter().chain(&["bg"]) {
let shown = message(Lang::from_code(code).unwrap(), &e.to_string());
assert!(!shown.contains(msg.as_str()) && shown.contains("`color`") && shown.contains("effect.apply"), "{code}: {shown}");
}
}
/// The notes of an EPS or `.ai` whose layers weren't read, which the importer pushes as warnings
/// (the scan above doesn't read them), are translated whole, reason included, as the status bar
/// shows them after opening (#2035).
+1
View File
@@ -3663,6 +3663,7 @@ axis Both Обидві
@msg `{id}` has no envelope dialog `{id}` не має діалогового вікна оболонки
@msg `{keep}` is not a side of this axis `{keep}` не є стороною цієї осі
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` має бути `auto` або кодом мови, наприклад `en` або `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` має бути кольором #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` має бути кольором #rrggbb
@msg `{key}` must be a number `{key}` має бути числом
@msg `{key}` must be a string `{key}` має бути рядком
Can't render this file because it contains an unexpected character in line 721 and column 40.
+1
View File
@@ -3800,6 +3800,7 @@ theme Appearance Giao diện
@msg `{id}` has no envelope dialog `{id}` không có hộp thoại Envelope
@msg `{keep}` is not a side of this axis `{keep}` không phải là một cạnh của trục này
@msg `{key}` must be `auto` or a language code such as `en` or `zh-hant` `{key}` phải là `auto` hoặc mã ngôn ngữ như `en` hoặc `zh-hant`
@msg `{key}` must be a #rrggbb color `{key}` phải là màu #rrggbb
@msg `{key}` must be a #rrggbb colour `{key}` phải là màu #rrggbb
@msg `{key}` must be a number `{key}` phải là một số
@msg `{key}` must be a string `{key}` phải là một chuỗi
Can't render this file because it contains an unexpected character in line 3780 and column 10.
+18 -2
View File
@@ -815,13 +815,29 @@ is refused the same way:
→ invalid parameters for `effect.apply`: unknown parameter `blurr`; the command takes: radius
```
The `color` of Drop Shadow, Inner Glow, Outer Glow and Shift to Color must be one the effect can read: a hex color
(`#rrggbb`, `#rgb` or `#rrggbbaa`, with or without the `#`), `black`, `white`, `red`, `green` or `blue`, `[r, g, b]`
(three numbers, each read on a 0 to 1 scale), or a color object (`{"model": "rgb", "r": 1, "g": 0, "b": 0}`).
`effect.apply` and `effect.setParams` refuse any other value themselves (where the parameter check runs first, a number
or a boolean is refused as not a string), so the app's effect dialogs and Appearance panel, a step of
`command.batch` and a replayed journal get the same error, and the document and its undo history stay as they were:
```json
{"id": "effect.setParams", "params": {"index": 0, "params": {"color": "#ff00"}}}
→ invalid parameters for `effect.setParams`: `color` must be a #rrggbb color
```
A document saved with such a color opens as before, and the effect draws its default color (an array of three or more
entries reads an entry that isn't a number as 0).
A number outside the range the effect documents is still stored as given and clamped when the effect draws. A
plug-in effect's parameters are resolved against its manifest instead, and `effect.setParams` as a step of
`command.batch` isn't checked (the effect it edits may only exist once the steps before it ran). A description with a `…` entry takes more keys than it lists: those of the command it names
`command.batch` isn't checked before the batch runs (the effect it edits may only exist once the steps before it ran);
the command still refuses an effect `color` it can't read. A description with a `…` entry takes more keys than it lists: those of the command it names
(`…settings as file.new`), or any key when it names none, as `document.export` and `document.save` do for the options
of the format they write (one object can carry several formats' options). A command that takes its own reply back
(`document.setup`, `text.areaOptions`) says so the same way. The app's own journal isn't checked: an action or
journal recorded by an older version replays as it did.
journal recorded by an older version replays as it did, except for an effect `color` that the effect can't read.
A step can use an earlier step's result, in `run` as in the `command_batch` tool. A parameter string that is exactly
`$N` or `$N.path` takes that value from the result of step `N` (counting from 0, in the order they ran; `run --in`'s