diff --git a/crates/engine/src/cmd/textstyles.rs b/crates/engine/src/cmd/textstyles.rs index 47677e78b..ffc75acc9 100644 --- a/crates/engine/src/cmd/textstyles.rs +++ b/crates/engine/src/cmd/textstyles.rs @@ -125,7 +125,7 @@ macro_rules! style_cmds { } /// The params of `charStyle.load`, `paraStyle.load` and `textStyle.loadAll`. -const LOAD_PARAMS: &str = "{path? | data?: file text | dataBase64?, name?: file name (default: the path's)} copy the styles of this kind (textStyle.loadAll: character and paragraph styles) from a document VectorCraft opens (see document.formats) into this one, after its own, in one undo step, with the swatches and patterns they paint with and the styles of either kind that type in those patterns is set in, as Paste brings those of pasted type (a global or spot color whose name this document gives another color is this document's). A style that is the same as the style here of its name, or of its name with a number (`Body 2`), isn't added again; any other is added under a free name (`Body 2`). The file's Normal styles aren't loaded, and a style whose attributes don't read, or whose name is empty, is skipped → {added: [names here], existing: [names already here], skipped: [names]} (textStyle.loadAll: {charStyles: {…}, paraStyles: {…}})"; +const LOAD_PARAMS: &str = "{path? | data?: file text | dataBase64?, name?: file name (default: the path's)} copy the styles of this kind (textStyle.loadAll: character and paragraph styles) from a document VectorCraft opens (see document.formats) into this one, after its own, in one undo step, with the swatches and patterns they paint with and the styles of either kind that type in those patterns is set in, as Paste brings those of pasted type (a global or spot color whose name this document gives another color is this document's). A style that is the same as the style here of its name, or of its name with a number (`Body 2`), isn't added again; any other is added under a free name (`Body 2`). The file's Normal styles aren't loaded, and a style whose attributes don't read or are out of the range charStyle.new takes, or whose name is empty, is skipped → {added: [names here], existing: [names already here], skipped: [names]} (textStyle.loadAll: {charStyles: {…}, paraStyles: {…}})"; pub fn specs() -> Vec { let mut v = style_cmds!( @@ -913,16 +913,21 @@ fn unused(s: &mut Session, kind: Kind) -> Result { } /// The styles of `kind` in `src` that load, with their source names so that art in the file keeps -/// referring to them: not the Normal style, and not one whose attributes don't read or whose name is empty, -/// which are skipped. → (the styles, the names in `src` of those skipped) +/// referring to them: not the Normal style, and not one whose attributes don't read, whose sizes, +/// scales, leading, shifts or stroke width are out of the range `charStyle.new` takes, or whose name +/// is empty, which are skipped. → (the styles, the names in `src` of those skipped) fn loadable(src: &Document, kind: Kind) -> (Vec, Vec) { + const RANGED: [&str; 8] = ["size", "h_scale", "v_scale", "leading", "baseline_shift", "tcyUpDown", "tcyLeftRight", "stroke_width"]; let (mut defs, mut skipped) = (vec![], vec![]); for def in kind.defs(src) { let name = def.name.as_str(); if name.trim() == kind.normal() { continue; } - match attrs_param(src, &json!({ "attrs": def.attrs }), kind, "load", Some(&def.attrs)).ok().flatten().filter(|_| !name.trim().is_empty()) { + // Paints, features and language are taken as the file stores them; the ranged values are checked. + let mut kept = def.attrs.clone(); + kept.retain(|k, _| !RANGED.contains(&k.as_str())); + match attrs_param(src, &json!({ "attrs": def.attrs }), kind, "load", Some(&kept)).ok().flatten().filter(|_| !name.trim().is_empty()) { Some(attrs) => defs.push(TextStyleDef { name: name.to_string(), attrs }), None => skipped.push(def.name.clone()), } diff --git a/crates/engine/src/cmd/textstyles/tests.rs b/crates/engine/src/cmd/textstyles/tests.rs index 8d68fedc8..25ae42b10 100644 --- a/crates/engine/src/cmd/textstyles/tests.rs +++ b/crates/engine/src/cmd/textstyles/tests.rs @@ -819,6 +819,24 @@ fn load_all_styles_loads_both_kinds_in_one_step() { assert_eq!(s.doc().unwrap().doc, before); } +/// A loaded character style whose size, scale, leading, shift or stroke width is out of the range +/// `charStyle.new` takes is skipped. +#[test] +fn loaded_styles_out_of_range_are_skipped() { + let (mut s, _) = session_with_text("Title"); + let file = [ + ("Huge", json!({"size": 2000.0})), + ("Flat", json!({"h_scale": 0.5})), + ("Shifted", json!({"baseline_shift": 5000.0})), + ("Thick", json!({"stroke_width": -2.0})), + ("Fine", json!({"size": 12.0, "h_scale": 50.0})), + ]; + let data = styles_file(&file, &[]); + let r = s.execute("charStyle.load", &json!({"dataBase64": data, "name": "Other.vectorcraft"})).unwrap(); + assert_eq!(r, json!({"added": ["Fine"], "existing": [], "skipped": ["Huge", "Flat", "Shifted", "Thick"]})); + assert_eq!(listed(&mut s, "charStyle"), [NORMAL_CHAR, "Fine"]); +} + /// A loaded style brings the pattern it paints with, renamed when the document has another pattern /// of that name, as Paste brings it; a global color whose name the document gives another color /// is the document's. diff --git a/docs/mcp.md b/docs/mcp.md index 495bdae3f..020712101 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5939,8 +5939,8 @@ in, come along as Paste brings those of pasted type; a global or spot color whos is the document's. A loaded style that is the same as the style of its name, or of its name with a number (`Body 2`), is not added again; any other is added under a free name. The document's styles and the type that uses them keep their attributes, and the file's Normal styles are not loaded. The -reply lists the names `added`, `existing` and `skipped` (a style whose attributes do not read, or whose name is -empty); `textStyle.loadAll` replies `{charStyles, paraStyles}`. A Return the Type tool types (or `text.editRange` +reply lists the names `added`, `existing` and `skipped` (a style whose attributes do not read or are out of the range +`charStyle.new` takes, or whose name is empty); `textStyle.loadAll` replies `{charStyles, paraStyles}`. A Return the Type tool types (or `text.editRange` inserting `\n`) continues the style of the paragraph it splits; deleting a break keeps the first paragraph's style; `text.setText` gives every paragraph the first one's attributes. Threaded text keeps each paragraph's attributes as the story re-flows (a paragraph split between frames has them in both).