mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 04:37:29 -06:00
Character and Paragraph Styles: a new style's name is trimmed as a rename trims it
charStyle.new and paraStyle.new kept the name they were given as it was, while charStyle.rename and paraStyle.rename trim the white space around the new name. So a script could make a style " Body" beside "Body", and a blank name was refused with the message for a name that is taken. New Character Style and New Paragraph Style now trim the name as rename does, compare the trimmed name with the styles there are, and refuse a name that is then empty with rename's message. Duplicate names the copy from the trimmed name. symbol.new and symbol.duplicate trim the name as symbol.options trims newName, and a blank newName gives "<name> copy" instead of a symbol named with the blank. library.use adds a library's text style under its trimmed name, so a library file whose style is named with spaces around it still applies. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -679,7 +679,7 @@ fn symbol_new(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let ids = roots(s, p)?;
|
||||
let art = selection_art(s, p)?.ok_or_else(|| EngineError::Other("nothing selected".into()))?;
|
||||
let (art, size, c) = normalize(art)?;
|
||||
let name = s.doc()?.doc.unique_symbol_name(str_param(p, "name").filter(|n| !n.trim().is_empty()).unwrap_or("New Symbol"));
|
||||
let name = s.doc()?.doc.unique_symbol_name(str_param(p, "name").map(str::trim).filter(|n| !n.is_empty()).unwrap_or("New Symbol"));
|
||||
let reg = registration_param(p, C)?.unwrap_or(SymbolRegistration::CENTER);
|
||||
let export = export_type_param(p, C)?.unwrap_or_default();
|
||||
let nine = nine_slice_param(p, C, None)?.flatten();
|
||||
@@ -928,7 +928,8 @@ fn symbol_duplicate(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let name = name_param(p, C)?.to_string();
|
||||
let d = &s.doc()?.doc;
|
||||
let sym = d.symbols.iter().find(|x| x.name == name).cloned().ok_or_else(|| bad(C, format!("no symbol named `{name}`")))?;
|
||||
let new = d.unique_symbol_name(str_param(p, "newName").unwrap_or(&format!("{name} copy")));
|
||||
let given = str_param(p, "newName").map(str::trim).filter(|n| !n.is_empty());
|
||||
let new = d.unique_symbol_name(&given.map_or_else(|| format!("{} copy", name.trim()), str::to_string));
|
||||
let size = natural_size(d, &name);
|
||||
let reg = d.symbol_registration(&name);
|
||||
let export = d.symbol_export_type(&name);
|
||||
|
||||
@@ -885,7 +885,7 @@ fn use_item(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
Kind::CharStyle | Kind::ParaStyle => {
|
||||
let para = kind == Kind::ParaStyle;
|
||||
let name = data.get("name").and_then(Value::as_str).unwrap_or("Style").to_string();
|
||||
let name = data.get("name").and_then(Value::as_str).map(str::trim).filter(|n| !n.is_empty()).unwrap_or("Style").to_string();
|
||||
let attrs = data.get("attrs").and_then(Value::as_object).cloned().unwrap_or_default();
|
||||
let doc = &s.doc()?.doc;
|
||||
// The document's style of that name when it is the same; else a new one.
|
||||
|
||||
@@ -58,7 +58,7 @@ macro_rules! style_cmds {
|
||||
concat!($p, ".new"),
|
||||
concat!("New ", $what, " Style"),
|
||||
&["Window", "Type", $menu],
|
||||
"{name?, attrs?: {…} (default: selected text), id?, start?: byte, end?: byte} → {name}; a nonempty character range samples its first selected character; start alone keeps insertion affinity; paragraph styles sample start",
|
||||
"{name? (trimmed, as rename trims `to`), attrs?: {…} (default: selected text), id?, start?: byte, end?: byte} → {name}; a nonempty character range samples its first selected character; start alone keeps insertion affinity; paragraph styles sample start",
|
||||
|s, p| new(s, p, $kind),
|
||||
true,
|
||||
),
|
||||
@@ -432,10 +432,13 @@ fn new(s: &mut Session, p: &Value, kind: Kind) -> Result<Value> {
|
||||
let d = &s.doc()?.doc;
|
||||
let base = if kind == Kind::Char { "Character Style" } else { "Paragraph Style" };
|
||||
let name = match str_param(p, "name") {
|
||||
Some(n) => n.to_string(),
|
||||
Some(n) => n.trim().to_string(),
|
||||
None => (1..).map(|i| format!("{base} {i}")).find(|n| kind.attrs(d, n).is_none()).unwrap_or_else(|| base.to_string()),
|
||||
};
|
||||
if name.trim().is_empty() || kind.attrs(d, &name).is_some() {
|
||||
if name.is_empty() {
|
||||
return Err(bad(&c, format!("`{name}` is empty or already used")));
|
||||
}
|
||||
if kind.attrs(d, &name).is_some() {
|
||||
return Err(bad(&c, format!("a style named `{name}` already exists")));
|
||||
}
|
||||
let n2 = name.clone();
|
||||
@@ -552,10 +555,12 @@ fn duplicate(s: &mut Session, p: &Value, kind: Kind) -> Result<Value> {
|
||||
let name = name_param(p, kind, "duplicate")?.to_string();
|
||||
let d = &s.doc()?.doc;
|
||||
let attrs = kind.attrs(d, &name).ok_or_else(|| bad(&kind.cmd("duplicate"), format!("no style `{name}`")))?;
|
||||
// `new` trims the name, so the copy is named from the trimmed one.
|
||||
let stem = name.trim();
|
||||
let copy = (1..)
|
||||
.map(|i| if i == 1 { format!("{name} copy") } else { format!("{name} copy {i}") })
|
||||
.map(|i| if i == 1 { format!("{stem} copy") } else { format!("{stem} copy {i}") })
|
||||
.find(|n| kind.attrs(d, n).is_none())
|
||||
.unwrap_or_else(|| format!("{name} copy"));
|
||||
.unwrap_or_else(|| format!("{stem} copy"));
|
||||
new(s, &json!({ "name": copy, "attrs": attrs }), kind)
|
||||
}
|
||||
|
||||
@@ -688,6 +693,29 @@ mod tests {
|
||||
assert_eq!(s.doc().unwrap().doc.char_styles.iter().find(|d| d.name == "Pink").unwrap().attrs["fill"], fill);
|
||||
}
|
||||
|
||||
/// New Character Style and New Paragraph Style trim the name they are given, as a rename
|
||||
/// trims `to`; the trimmed name is the one compared with the styles there are.
|
||||
#[test]
|
||||
fn new_styles_trim_their_names_as_a_rename_does() {
|
||||
let (mut s, _) = session_with_text("Body text");
|
||||
for p in ["charStyle", "paraStyle"] {
|
||||
assert_eq!(s.execute(&format!("{p}.new"), &json!({"name": " Body \t"})).unwrap()["name"], "Body", "{p}");
|
||||
let list = s.execute(&format!("{p}.list"), &json!({})).unwrap();
|
||||
let names: Vec<&str> = list["styles"].as_array().unwrap().iter().filter_map(|x| x["name"].as_str()).collect();
|
||||
assert_eq!(names[1..], ["Body"], "{p}");
|
||||
let e = s.execute(&format!("{p}.new"), &json!({"name": " Body"})).unwrap_err().to_string();
|
||||
assert!(e.contains("a style named `Body` already exists"), "{p}: {e}");
|
||||
let e = s.execute(&format!("{p}.new"), &json!({"name": " \t "})).unwrap_err().to_string();
|
||||
assert!(e.contains("`` is empty or already used"), "{p}: {e}");
|
||||
assert_eq!(s.execute(&format!("{p}.duplicate"), &json!({"name": "Body"})).unwrap()["name"], "Body copy", "{p}");
|
||||
}
|
||||
// A style whose name has spaces around it, as a saved file can hold, is copied under the
|
||||
// trimmed name, numbered past the copies there are.
|
||||
std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc).char_styles.push(TextStyleDef { name: " Old ".into(), attrs: Map::new() });
|
||||
s.execute("charStyle.new", &json!({"name": "Old copy"})).unwrap();
|
||||
assert_eq!(s.execute("charStyle.duplicate", &json!({"name": " Old "})).unwrap()["name"], "Old copy 2");
|
||||
}
|
||||
|
||||
/// #1001: attributes their JSON leaves out at the defaults (Auto leading and kerning, no
|
||||
/// mojikumi) are still attributes a style can set, and a style captured with one applies to
|
||||
/// text at the default, is duplicated and comes back out of a library.
|
||||
|
||||
Reference in New Issue
Block a user