mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 09:39:52 +00:00
Color management: Assign Profile leaves the working spaces of Color Settings
Edit > Assign Profile wrote the profiles it assigned into the working settings of Edit > Color Settings. Color Settings then showed the assigned profile as the working space, and the next change in Color Settings saved that profile for the next start. The working settings are now kept apart from the settings in effect. Assign Profile still makes the profiles it assigns the ones colors show and convert through, and assigning none puts the working space back. Color Settings shows and saves only the working settings, and a change there keeps the profiles the document in front is assigned in use. color.removeProfile refuses only a working space: removing an assigned profile in use puts the working space in its place, and loading that profile again puts it back in use. Public reference: "Changing the color profile for a document" and "About color working spaces", https://help.adobe.com/archive/en_US/illustrator/cs3/illustrator_cs3_help.pdf#page=150
This commit is contained in:
+58
-7
@@ -6,8 +6,9 @@
|
||||
//! * **CMYK spaces:** [`GENERIC_CMYK`] is our own documented parametric press model
|
||||
//! ([`generic`]); [`DEVICE_CMYK`] is the old profile-free formula (kept for exact legacy numbers);
|
||||
//! any CMYK `.icc` the user loads goes through `moxcms` (LUT-based profiles).
|
||||
//! * The **active settings** (Edit → Color Settings) are process-wide; [`crate::Color::to_rgb`]
|
||||
//! uses them. Code that needs deterministic numbers builds its own [`Cms`] with [`Cms::new`].
|
||||
//! * The **active settings** are process-wide: the working settings (Edit → Color Settings), with
|
||||
//! the profiles [`use_document_profiles`] last put in place of the working spaces;
|
||||
//! [`crate::Color::to_rgb`] uses them. Code that needs deterministic numbers builds its own [`Cms`] with [`Cms::new`].
|
||||
|
||||
pub mod generic;
|
||||
pub mod icc;
|
||||
@@ -890,10 +891,12 @@ impl CmykLut {
|
||||
// ---------- the active (process-wide) settings ----------
|
||||
|
||||
static ACTIVE: RwLock<Option<Arc<Cms>>> = RwLock::new(None);
|
||||
static WORKING: RwLock<Option<Arc<Cms>>> = RwLock::new(None);
|
||||
static RGB_IS_SRGB: AtomicBool = AtomicBool::new(true);
|
||||
static CMYK_IS_DEVICE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// The active colour-management context (Edit → Color Settings).
|
||||
/// The active color-management context: the working settings, with the profiles
|
||||
/// [`use_document_profiles`] last put in place of the working spaces.
|
||||
pub fn active() -> Arc<Cms> {
|
||||
if let Some(c) = ACTIVE.read().unwrap_or_else(|e| e.into_inner()).as_ref() {
|
||||
return c.clone();
|
||||
@@ -906,13 +909,61 @@ pub fn active_settings() -> ColorSettings {
|
||||
active().settings.clone()
|
||||
}
|
||||
|
||||
/// Replace the active settings (fails, leaving them unchanged, if a profile is unknown).
|
||||
/// The color-management context of Edit → Color Settings (the working spaces, intent and black
|
||||
/// point compensation), as [`set_active`] last set them, whatever profiles a document is assigned.
|
||||
pub fn working() -> Arc<Cms> {
|
||||
if let Some(c) = WORKING.read().unwrap_or_else(|e| e.into_inner()).as_ref() {
|
||||
return c.clone();
|
||||
}
|
||||
let mut w = WORKING.write().unwrap_or_else(|e| e.into_inner());
|
||||
w.get_or_insert_with(|| Arc::new(Cms::default())).clone()
|
||||
}
|
||||
|
||||
/// The settings of [`working`].
|
||||
pub fn working_settings() -> ColorSettings {
|
||||
working().settings.clone()
|
||||
}
|
||||
|
||||
/// Replace the working settings (Edit → Color Settings) and make them the active settings (fails,
|
||||
/// leaving both unchanged, if a profile is unknown).
|
||||
pub fn set_active(settings: &ColorSettings) -> Result<(), CmsError> {
|
||||
let cms = Cms::new(settings)?;
|
||||
let cms = Arc::new(Cms::new(settings)?);
|
||||
*WORKING.write().unwrap_or_else(|e| e.into_inner()) = Some(cms.clone());
|
||||
install(cms);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Make the working settings the active settings, with the profiles a document is assigned
|
||||
/// (`None`: none) in place of the working spaces. An assigned profile that is not available, or
|
||||
/// is not of its model, leaves the working space. The working settings stay as they are. True when
|
||||
/// the active settings changed.
|
||||
pub fn use_document_profiles(rgb: Option<&str>, cmyk: Option<&str>) -> bool {
|
||||
let working = working();
|
||||
let mut st = working.settings.clone();
|
||||
if let Some(p) = rgb.and_then(profile).filter(|p| p.kind == ProfileKind::Rgb) {
|
||||
st.rgb = p.name;
|
||||
}
|
||||
if let Some(p) = cmyk.and_then(profile).filter(|p| p.kind == ProfileKind::Cmyk) {
|
||||
st.cmyk = p.name;
|
||||
}
|
||||
if st == active().settings {
|
||||
return false;
|
||||
}
|
||||
if st == working.settings {
|
||||
install(working);
|
||||
return true;
|
||||
}
|
||||
// The working settings were checked when they were set; a working profile removed since then
|
||||
// leaves the active settings as they are.
|
||||
let Ok(cms) = Cms::new(&st) else { return false };
|
||||
install(Arc::new(cms));
|
||||
true
|
||||
}
|
||||
|
||||
fn install(cms: Arc<Cms>) {
|
||||
RGB_IS_SRGB.store(cms.rgb_is_srgb(), Ordering::Relaxed);
|
||||
CMYK_IS_DEVICE.store(matches!(cms.cmyk, CmykSpace::Device), Ordering::Relaxed);
|
||||
*ACTIVE.write().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(cms));
|
||||
Ok(())
|
||||
*ACTIVE.write().unwrap_or_else(|e| e.into_inner()) = Some(cms);
|
||||
}
|
||||
|
||||
/// Fast path for [`Color::to_rgb`]: RGB colours need no conversion while the working space is sRGB.
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
//! Colour settings and the proof view are process-wide (`vectorcraft_color::cms::active`,
|
||||
//! `vectorcraft_render::proof::view`). A document's assigned profiles are stored in
|
||||
//! `Document::color_profiles` (files before format v3: `Document.unknown["colorProfiles"]`) and
|
||||
//! become active when assigned.
|
||||
//! take the place of the working spaces in the active settings when assigned, and when the working
|
||||
//! settings change while the document is in front ([`use_front_profiles`]); the working spaces stay
|
||||
//! as Edit → Color Settings set them.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
@@ -59,7 +61,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Assign Profile…",
|
||||
["Edit"],
|
||||
None,
|
||||
"{rgb?: profile|null, cmyk?: profile|null} tag the document with profiles (colour numbers are kept) → {rgb, cmyk}",
|
||||
"{rgb?: profile|null, cmyk?: profile|null} tag the document with profiles (color numbers are kept; null: the working space). Colors then show and convert through the profiles assigned; the working spaces stay as edit.colorSettings set them → {rgb, cmyk}",
|
||||
has_doc,
|
||||
assign_profile
|
||||
),
|
||||
@@ -176,9 +178,19 @@ fn touch_all(s: &mut Session) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the active settings the working settings with the profiles the front document is assigned
|
||||
/// in place of the working spaces (none, or no document open: the working spaces). The open
|
||||
/// documents redraw when the active settings change.
|
||||
fn use_front_profiles(s: &mut Session) {
|
||||
let (rgb, cmyk) = s.active().map(|d| doc_profiles(&d.doc)).unwrap_or_default();
|
||||
if cms::use_document_profiles(rgb.as_deref(), cmyk.as_deref()) {
|
||||
touch_all(s);
|
||||
}
|
||||
}
|
||||
|
||||
fn color_settings(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "edit.colorSettings";
|
||||
let mut st = cms::active_settings();
|
||||
let mut st = cms::working_settings();
|
||||
let before = st.clone();
|
||||
if let Some(v) = text_param(p, "rgb", C)? {
|
||||
st.rgb = cms::canonical_name(v).to_string();
|
||||
@@ -194,6 +206,7 @@ fn color_settings(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
cms::set_active(&st).map_err(|e| bad(C, e.to_string()))?;
|
||||
// Saved for the next start when the app attached a settings file (#6335).
|
||||
s.store_color_settings(&st);
|
||||
use_front_profiles(s);
|
||||
touch_all(s);
|
||||
}
|
||||
Ok(settings_json(&st))
|
||||
@@ -208,10 +221,15 @@ fn load_profile(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let info = cms::register_icc(&bytes, None).map_err(|e| bad(C, e.to_string()))?;
|
||||
// The bytes are kept, not the path: the file may move or go away.
|
||||
let remembered = s.store_profile(&info.name, &bytes);
|
||||
// A new file of a working space: the active settings still hold the old one.
|
||||
let working = cms::active_settings();
|
||||
if working.rgb == info.name || working.cmyk == info.name {
|
||||
// A new file of a working space or of an assigned profile in use: the active settings still
|
||||
// hold the old one. A profile the front document is assigned, loaded after it was removed, is
|
||||
// in use again.
|
||||
let (working, active) = (cms::working_settings(), cms::active_settings());
|
||||
let (rgb, cmyk) = s.active().map(|d| doc_profiles(&d.doc)).unwrap_or_default();
|
||||
let front = [rgb, cmyk].iter().any(|n| n.as_deref() == Some(info.name.as_str()));
|
||||
if front || [&working.rgb, &working.cmyk, &active.rgb, &active.cmyk].contains(&&info.name) {
|
||||
cms::set_active(&working).map_err(|e| bad(C, e.to_string()))?;
|
||||
use_front_profiles(s);
|
||||
touch_all(s);
|
||||
}
|
||||
Ok(json!({"name": info.name, "kind": info.kind, "remembered": remembered}))
|
||||
@@ -225,14 +243,20 @@ fn remove_profile(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if info.builtin {
|
||||
return Err(bad(C, format!("`{name}` is a built-in profile and stays")));
|
||||
}
|
||||
let working = cms::active_settings();
|
||||
let working = cms::working_settings();
|
||||
if working.rgb == name || working.cmyk == name {
|
||||
return Err(bad(C, format!("`{name}` is a working space: choose another with edit.colorSettings first")));
|
||||
}
|
||||
let active = cms::active_settings();
|
||||
if !cms::unregister_icc(&info.name) {
|
||||
return Err(bad(C, format!("no profile named `{name}`")));
|
||||
}
|
||||
s.unstore_profile(&info.name);
|
||||
// An assigned profile in use: the working space takes its place, as for any document assigned
|
||||
// a profile that is not available.
|
||||
if active.rgb == name || active.cmyk == name {
|
||||
use_front_profiles(s);
|
||||
}
|
||||
touch_all(s);
|
||||
Ok(json!({"removed": info.name}))
|
||||
}
|
||||
@@ -273,18 +297,9 @@ fn assign_profile(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
d.color_profiles = profiles;
|
||||
Ok(())
|
||||
})?;
|
||||
let mut st = cms::active_settings();
|
||||
let before = st.clone();
|
||||
if let Some(r) = &rgb {
|
||||
st.rgb = r.clone();
|
||||
}
|
||||
if let Some(c) = &cmyk {
|
||||
st.cmyk = c.clone();
|
||||
}
|
||||
if st != before {
|
||||
cms::set_active(&st).map_err(|e| bad(C, e.to_string()))?;
|
||||
touch_all(s);
|
||||
}
|
||||
// The document draws through its profiles now, and a Document Color Mode later in the same
|
||||
// batch converts through them. The working spaces stay as they are.
|
||||
use_front_profiles(s);
|
||||
}
|
||||
Ok(json!({"rgb": rgb, "cmyk": cmyk}))
|
||||
}
|
||||
|
||||
@@ -75,6 +75,111 @@ fn assigning_another_profile_replaces_the_active_color_settings() {
|
||||
cms::set_active(&before).unwrap();
|
||||
}
|
||||
|
||||
/// Assign Profile tags the document and leaves the working spaces: Color Settings shows the
|
||||
/// working spaces, a later change to it saves them and not the assigned profile, and taking the
|
||||
/// profile off the document brings the working spaces back.
|
||||
#[test]
|
||||
fn assign_profile_leaves_the_working_spaces() {
|
||||
let _g = settings();
|
||||
let dir = fresh_dir("assign-profile");
|
||||
let _reset = ColorSettingsReset { profile: None, paths: vec![dir.clone()] };
|
||||
let file = dir.join("Color Settings.json");
|
||||
let mut s = session();
|
||||
s.attach_color_settings(file.clone());
|
||||
let working = cms::working_settings();
|
||||
assert_ne!(working.cmyk, cms::DEVICE_CMYK);
|
||||
exec(&mut s, "edit.assignProfile", json!({"cmyk": cms::DEVICE_CMYK}));
|
||||
assert_eq!(cms::active_settings().cmyk, cms::DEVICE_CMYK, "the document draws through its profile");
|
||||
assert_eq!(cms::working_settings(), working);
|
||||
assert!(!file.exists(), "Assign Profile saves no Color Settings");
|
||||
let r = exec(&mut s, "edit.colorSettings", json!({}));
|
||||
assert_eq!(r["cmyk"], working.cmyk.as_str(), "Color Settings shows the working CMYK space");
|
||||
exec(&mut s, "edit.colorSettings", json!({"intent": "perceptual"}));
|
||||
let saved: cms::ColorSettings = serde_json::from_slice(&std::fs::read(&file).unwrap()).unwrap();
|
||||
assert_eq!((saved.cmyk.as_str(), saved.intent), (working.cmyk.as_str(), cms::Intent::Perceptual));
|
||||
assert_eq!(cms::active_settings().cmyk, cms::DEVICE_CMYK, "the document still draws through its profile");
|
||||
exec(&mut s, "edit.assignProfile", json!({"cmyk": null}));
|
||||
assert_eq!(cms::active_settings(), cms::working_settings(), "no profile: the working spaces");
|
||||
}
|
||||
|
||||
/// A loaded profile that is assigned but is not a working space can be removed: the working space
|
||||
/// takes its place in the active settings, and the working spaces stay as they are.
|
||||
#[test]
|
||||
fn removing_an_assigned_profile_puts_the_working_space_back() {
|
||||
const NAME: &str = "Assigned CMYK";
|
||||
let _g = settings();
|
||||
let _reset = ColorSettingsReset { profile: Some(NAME), paths: vec![] };
|
||||
cms::register_icc(&cms::icc_bytes(cms::GENERIC_CMYK).unwrap(), Some(NAME.into())).unwrap();
|
||||
let mut s = session();
|
||||
let working = cms::working_settings();
|
||||
exec(&mut s, "edit.assignProfile", json!({"cmyk": NAME}));
|
||||
assert_eq!(cms::active_settings().cmyk, NAME);
|
||||
let revision = s.doc().unwrap().revision;
|
||||
let r = exec(&mut s, "color.removeProfile", json!({"name": NAME}));
|
||||
assert_eq!(r["removed"], NAME);
|
||||
assert_eq!(cms::active_settings(), working, "the working CMYK space is in use again");
|
||||
assert_eq!(cms::working_settings(), working);
|
||||
assert!(s.doc().unwrap().revision > revision, "the canvas redraws");
|
||||
}
|
||||
|
||||
/// Loading the file of an assigned profile in use again keeps it in use, loading it after it was
|
||||
/// removed puts it back in use, and the working spaces stay as they are.
|
||||
#[test]
|
||||
fn loading_an_assigned_profile_again_keeps_it_in_use() {
|
||||
const NAME: &str = "Assigned P";
|
||||
let _g = settings();
|
||||
let dir = fresh_dir("reload-assigned");
|
||||
let _reset = ColorSettingsReset { profile: Some(NAME), paths: vec![dir.clone()] };
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("assigned.icc");
|
||||
std::fs::write(&path, icc_named(NAME)).unwrap();
|
||||
let mut s = session();
|
||||
let working = cms::working_settings();
|
||||
let load = |s: &mut Session| assert_eq!(exec(s, "color.loadProfile", json!({"path": path.to_str().unwrap()}))["name"], NAME);
|
||||
load(&mut s);
|
||||
exec(&mut s, "edit.assignProfile", json!({"rgb": NAME}));
|
||||
let before = cms::active();
|
||||
load(&mut s);
|
||||
assert_eq!(cms::active_settings().rgb, NAME, "the assigned profile stays in use");
|
||||
assert!(!std::sync::Arc::ptr_eq(&before, &cms::active()), "the new file is in use");
|
||||
exec(&mut s, "color.removeProfile", json!({"name": NAME}));
|
||||
assert_eq!(cms::active_settings(), working);
|
||||
load(&mut s);
|
||||
assert_eq!(cms::active_settings().rgb, NAME, "loaded again, the assigned profile is in use");
|
||||
assert_eq!(cms::working_settings(), working);
|
||||
}
|
||||
|
||||
/// A batch that assigns a profile and then changes the color mode, as Document Setup's OK does,
|
||||
/// converts the colors through the profile it assigns.
|
||||
#[test]
|
||||
fn a_batch_converts_through_the_profile_it_assigns() {
|
||||
let _g = settings();
|
||||
let _reset = ColorSettingsReset { profile: None, paths: vec![] };
|
||||
let blue = |s: &mut Session| {
|
||||
let id = rect(s, 10.0, 10.0, 50.0, 50.0);
|
||||
exec(s, "paint.setFill", json!({"ids": [id.0], "color": "#3366cc"}));
|
||||
id
|
||||
};
|
||||
let fill = |s: &Session, id| s.doc().unwrap().doc.node(id).unwrap().appearance.fill_paint().color();
|
||||
let mut working = session();
|
||||
let w = blue(&mut working);
|
||||
exec(&mut working, "file.documentColorMode", json!({"mode": "cmyk"}));
|
||||
let mut tagged = session();
|
||||
let a = blue(&mut tagged);
|
||||
let steps = json!([
|
||||
{"command": "edit.assignProfile", "params": {"cmyk": cms::DEVICE_CMYK}},
|
||||
{"command": "file.documentColorMode", "params": {"mode": "cmyk"}},
|
||||
]);
|
||||
exec(&mut tagged, "command.batch", json!({"commands": steps}));
|
||||
let converted = fill(&tagged, a);
|
||||
assert_ne!(converted, fill(&working, w), "converted through the working CMYK space");
|
||||
let mut device = session();
|
||||
let b = blue(&mut device);
|
||||
exec(&mut device, "edit.colorSettings", json!({"cmyk": cms::DEVICE_CMYK}));
|
||||
exec(&mut device, "file.documentColorMode", json!({"mode": "cmyk"}));
|
||||
assert_eq!(converted, fill(&device, b));
|
||||
}
|
||||
|
||||
/// Separations Preview follows the Color Settings and the profiles loaded: a renderer draws a gray
|
||||
/// square on the Black plate through the new working CMYK profile, and the four process plates
|
||||
/// through a proof profile loaded again under the same name, as a new renderer draws them.
|
||||
|
||||
@@ -103,7 +103,7 @@ fn settings_section(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
/// The section's rows: the RGB and CMYK working spaces, intent, black point compensation, Load
|
||||
/// Profile… and Gamut Check, and the profile the active document is assigned.
|
||||
fn settings_rows(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let st = cms::active_settings();
|
||||
let st = cms::working_settings();
|
||||
widgets::section_header(ui, tl!("Color Settings"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("RGB:"));
|
||||
|
||||
@@ -1339,6 +1339,8 @@ preferences (the folder the desktop app uses, so both see the same ones; `VECTOR
|
||||
compensation go into `Color Settings.json` when they change, and are the active settings again at the next
|
||||
start, after the profiles above are registered (#6335). Saved settings that name a profile that is no longer
|
||||
there, or a damaged file, leave the defaults (logged), and the next change writes over the file.
|
||||
`edit.assignProfile` tags the document only: colors show and convert through the profiles it assigns, and the
|
||||
working spaces and the file stay as they are.
|
||||
|
||||
Not remembered: the document, anything else. Nothing is read or written when
|
||||
`VECTORCRAFT_NO_PREFS` is set, and a server confined with `--automation-read-root` or
|
||||
|
||||
@@ -689,6 +689,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
|
||||
- **Document Setup modal handoff:** Document Setup → Edit Artboards applies valid settings, closes the dialog and activates the Artboard tool. Pointer-click tests cover successful handoff, invalid settings, OK, Cancel and Undo in `dialogs/document_setup.rs`.
|
||||
- **Document Setup › Color:** a Color tab shows the document color mode (RGB Color or CMYK Color), a Convert to CMYK or Convert to RGB box for a mode change (cleared: the colors keep their numbers), and the RGB and CMYK profiles assigned to the document, with a working-space entry for none. OK runs `edit.assignProfile` and then `file.documentColorMode` with the dialog's other changes in one `command.batch`, and the document's changes are one undo step. An unchanged Color tab adds no color commands, and Cancel, or OK with nothing changed, records no undo step. A Color tab field with a value of the wrong kind is an error, and the dialog stays open. The tab is VectorCraft's own layout; File › Document Color Mode and Edit › Assign Profile… stay as they are. Tests in `dialogs/document_setup.rs`.
|
||||
- **Assign Profile and the working spaces:** Edit › Assign Profile… tags the document and leaves the working spaces of Edit › Color Settings… as they are, in Color Settings, the Separations panel and the settings saved for the next start. Colors show and convert through the profiles assigned; a change in Color Settings keeps the profiles the document in front is assigned in use, and removing an assigned profile in use puts the working space back in its place. Public reference: [Changing the color profile for a document and About color working spaces](https://help.adobe.com/archive/en_US/illustrator/cs3/illustrator_cs3_help.pdf#page=150). Tests in `engine/tests/color_settings.rs`.
|
||||
- **New Document's More Settings and Save Preset:** More Settings opens a pop-up over New Document with only Number of Artboards, the artboard layout, Change to Right-to-Left Layout, Spacing and Columns; Done, Escape or a click outside closes it and keeps the values. Save Preset turns the top of Preset Details into a Save Document Preset heading with the preset's name, Cancel and Save, and hides the document's name, Close and Create until the preset is saved or canceled; nothing in that mode creates a document. Save Preset's default name is the first "Preset N" no saved preset has. VectorCraft's own layout. Harness tests in `dialogs/new_document.rs` cover both.
|
||||
- **Control bar with the Artboard tool:** while the Artboard tool is in use, the Control bar shows the active artboard's controls in place of the object controls: Preset, Portrait and Landscape, New Artboard, Delete Artboard, the Name field, Artboard Options, Rearrange All Artboards, the Transform link, Move and Scale Artwork with Artboard and, while the bar has room, the reference point with X and Y, and W and H with the Constrain Width and Height Proportions link. Each runs the command its Properties or Transform panel counterpart runs (`artboard.setProps`, `artboard.new`, `artboard.delete`, `tool.options {tool: "artboard"}`), and the reference point is the one the Transform panel uses. In an 800-point window the controls up to the Transform link fit, Move and Scale Artwork with Artboard show as their boxes alone with their names in tooltips, and the link opens the Transform panel with the artboard's position and size. Pointer and key tests in `panels/artboards.rs` cover the controls, W about the reference point, the orientation and the name. Public reference: [Using multiple artboards](https://web.archive.org/web/20210925094818/https://helpx.adobe.com/illustrator/using/using-multiple-artboards.html) (archived copy).
|
||||
- **Names of new and renamed artboards, brushes and patterns:** `artboard.new` and `artboard.setProps` trim the white space around the name they are given, as the Artboards panel's rename does, and a blank name gives the artboard its default name, Artboard N for the Nth artboard. `brush.new`, `brush.duplicate`, `brush.options`, `object.pattern.make`, `object.pattern.saveCopy` and `pattern.options` trim the name as the Brush Options dialog and the Pattern Options panel do, and compare the trimmed name with the names there are; a blank name gives the name each command gives when there is none, and Brush Options and Pattern Options keep the name. Tests in `tests/panelcmds.rs`, `tests/brushsym.rs` and `tests/pattern.rs` cover each command. Public references: [Using multiple artboards](https://web.archive.org/web/20210925094818/https://helpx.adobe.com/illustrator/using/using-multiple-artboards.html), [Brushes](https://web.archive.org/web/20251018101754/https://helpx.adobe.com/illustrator/using/brushes.html) and [Create and edit patterns](https://web.archive.org/web/20211031180358/https://helpx.adobe.com/illustrator/using/create-edit-patterns.html) (archived copies).
|
||||
|
||||
Reference in New Issue
Block a user