mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-03 06:30:00 -06:00
Merge wp/WP-00: neutral labels, packaging metadata and vendor-name CI gate (M3.7, M4.10, M4.11)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -157,10 +157,10 @@ fn color_settings(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let mut st = cms::active_settings();
|
||||
let before = st.clone();
|
||||
if let Some(v) = str_param(p, "rgb") {
|
||||
st.rgb = v.to_string();
|
||||
st.rgb = cms::canonical_name(v).to_string();
|
||||
}
|
||||
if let Some(v) = str_param(p, "cmyk") {
|
||||
st.cmyk = v.to_string();
|
||||
st.cmyk = cms::canonical_name(v).to_string();
|
||||
}
|
||||
if let Some(i) = intent_param(p, C)? {
|
||||
st.intent = i;
|
||||
@@ -189,24 +189,23 @@ fn load_profile(_: &mut Session, _: &Value) -> Result<Value> {
|
||||
/// The document's assigned profiles (`None` = working space).
|
||||
pub fn doc_profiles(d: &vectorcraft_doc::Document) -> (Option<String>, Option<String>) {
|
||||
let o = d.unknown.get(PROFILES_KEY);
|
||||
let get = |k: &str| o.and_then(|o| o.get(k)).and_then(Value::as_str).map(str::to_string);
|
||||
let get = |k: &str| o.and_then(|o| o.get(k)).and_then(Value::as_str).map(|n| cms::canonical_name(n).to_string());
|
||||
(get("rgb"), get("cmyk"))
|
||||
}
|
||||
|
||||
fn assign_profile(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "edit.assignProfile";
|
||||
let (mut rgb, mut cmyk) = doc_profiles(&s.doc()?.doc);
|
||||
let known = cms::profiles();
|
||||
let pick = |key: &str, cur: &mut Option<String>, kind: cms::ProfileKind| -> Result<bool> {
|
||||
match p.get(key) {
|
||||
None => Ok(false),
|
||||
Some(Value::Null) => Ok(cur.take().is_some()),
|
||||
Some(Value::String(n)) => {
|
||||
if !known.iter().any(|k| &k.name == n && k.kind == kind) {
|
||||
let Some(info) = cms::profile(n).filter(|k| k.kind == kind) else {
|
||||
return Err(bad(C, format!("unknown {key} profile `{n}`")));
|
||||
}
|
||||
let changed = cur.as_deref() != Some(n.as_str());
|
||||
*cur = Some(n.clone());
|
||||
};
|
||||
let changed = cur.as_deref() != Some(info.name.as_str());
|
||||
*cur = Some(info.name);
|
||||
Ok(changed)
|
||||
}
|
||||
Some(_) => Err(bad(C, format!("`{key}` must be a profile name or null"))),
|
||||
@@ -363,7 +362,7 @@ fn proof_setup(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if let Some(t) = str_param(p, "target") {
|
||||
let t = ProofTarget::parse(t).ok_or_else(|| bad(C, format!("unknown proof target `{t}`")))?;
|
||||
if let ProofTarget::Cmyk(name) = &t
|
||||
&& !cms::profiles().iter().any(|k| &k.name == name && k.kind == cms::ProfileKind::Cmyk)
|
||||
&& !cms::profile(name).is_some_and(|k| k.kind == cms::ProfileKind::Cmyk)
|
||||
{
|
||||
return Err(bad(C, format!("unknown CMYK profile `{name}`")));
|
||||
}
|
||||
|
||||
@@ -269,12 +269,12 @@ pub const PREF_SPECS: &[PrefSpec] = &[
|
||||
// Clipboard Handling
|
||||
p!("copyAsSvg", "Clipboard Handling", "On Copy", "Include SVG Code", bool),
|
||||
p!("copyAsPdf", "Clipboard Handling", "On Copy", "PDF", bool),
|
||||
p!("copyAicb", "Clipboard Handling", "On Copy", "AICB (no transparency support)", bool),
|
||||
p!("copyAicb", "Clipboard Handling", "On Copy", "Legacy vector clipboard (no transparency)", bool),
|
||||
p!(
|
||||
"aicbMode",
|
||||
"Clipboard Handling",
|
||||
"On Copy",
|
||||
"AICB",
|
||||
"Legacy vector clipboard",
|
||||
choice(&[("preservePaths", "Preserve Paths"), ("preserveAppearance", "Preserve Appearance and Overprints")])
|
||||
),
|
||||
p!(
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Area / Path Type",
|
||||
[],
|
||||
None,
|
||||
"{path: id, mode: \"area\"|\"onPath\", text?: \"\", at?: [x, y] (on-path start: nearest point), size?, font?} turn a path into an area-type frame or a type-on-a-path baseline (the path's paint is dropped, like Illustrator) → {id}",
|
||||
"{path: id, mode: \"area\"|\"onPath\", text?: \"\", at?: [x, y] (on-path start: nearest point), size?, font?} turn a path into an area-type frame or a type-on-a-path baseline (the path's paint is dropped) → {id}",
|
||||
has_doc,
|
||||
create_in_path
|
||||
),
|
||||
|
||||
@@ -13,7 +13,7 @@ use vectorcraft_render::{RenderOptions, Renderer};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Tests that touch the process-wide proof view or colour settings run one at a time.
|
||||
/// Every test here reads or changes the process-wide proof view or colour settings: one at a time.
|
||||
static GLOBAL: Mutex<()> = Mutex::new(());
|
||||
|
||||
fn session() -> Session {
|
||||
@@ -45,6 +45,7 @@ fn render(doc: &Document, opts: RenderOptions) -> vectorcraft_render::Rendered {
|
||||
|
||||
#[test]
|
||||
fn document_mode_converts_through_cms() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 0.0, 0.0, 10.0, 10.0, json!("#ff0000"));
|
||||
let r = s.execute("object.convertDocumentColorMode", &json!({"mode": "cmyk"})).unwrap();
|
||||
@@ -63,6 +64,7 @@ fn document_mode_converts_through_cms() {
|
||||
|
||||
#[test]
|
||||
fn document_mode_conversion_is_idempotent() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
rect(&mut s, 0.0, 0.0, 10.0, 10.0, json!("#336699"));
|
||||
rect(&mut s, 20.0, 0.0, 10.0, 10.0, json!({"gray": 40}));
|
||||
@@ -79,6 +81,7 @@ fn document_mode_conversion_is_idempotent() {
|
||||
|
||||
#[test]
|
||||
fn rgb_cmyk_rgb_document_roundtrip_within_tolerance() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
let cols = ["#cc9980", "#808080", "#4d7359", "#e6d94d"];
|
||||
let ids: Vec<NodeId> = cols.iter().enumerate().map(|(i, h)| rect(&mut s, i as f64 * 20.0, 0.0, 10.0, 10.0, json!(h))).collect();
|
||||
@@ -95,6 +98,7 @@ fn rgb_cmyk_rgb_document_roundtrip_within_tolerance() {
|
||||
|
||||
#[test]
|
||||
fn swatch_links_survive_mode_conversion() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
s.execute("swatch.new", &json!({"name": "Brand", "color": "#2a6fb0", "global": true})).unwrap();
|
||||
let a = NodeId(s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap()["id"].as_u64().unwrap());
|
||||
@@ -109,6 +113,7 @@ fn swatch_links_survive_mode_conversion() {
|
||||
|
||||
#[test]
|
||||
fn gamut_check_flags_unprintable_colours() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
rect(&mut s, 0.0, 0.0, 10.0, 10.0, json!("#0000ff"));
|
||||
rect(&mut s, 20.0, 0.0, 10.0, 10.0, json!("#cc9980"));
|
||||
@@ -124,6 +129,7 @@ fn gamut_check_flags_unprintable_colours() {
|
||||
|
||||
#[test]
|
||||
fn convert_query_and_intents() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
let q = |s: &mut Session, intent: &str| s.execute("color.convert", &json!({"color": "#0000ff", "to": "cmyk", "intent": intent})).unwrap();
|
||||
let p = q(&mut s, "perceptual");
|
||||
@@ -170,6 +176,7 @@ fn color_settings_lists_profiles_and_validates() {
|
||||
|
||||
#[test]
|
||||
fn proof_colors_simulate_press_on_screen() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
rect(&mut s, 0.0, 0.0, 100.0, 100.0, json!("#0000ff"));
|
||||
let doc = s.doc().unwrap().doc.clone();
|
||||
@@ -186,6 +193,7 @@ fn proof_colors_simulate_press_on_screen() {
|
||||
|
||||
#[test]
|
||||
fn separation_plates_render_ink_coverage() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
rect(&mut s, 0.0, 0.0, 50.0, 100.0, json!({"c": 100, "m": 0, "y": 0, "k": 0}));
|
||||
rect(&mut s, 50.0, 0.0, 50.0, 100.0, json!({"c": 0, "m": 50, "y": 0, "k": 0}));
|
||||
@@ -223,6 +231,7 @@ fn separation_plates_render_ink_coverage() {
|
||||
|
||||
#[test]
|
||||
fn rgb_art_separates_with_gcr() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
rect(&mut s, 0.0, 0.0, 100.0, 100.0, json!("#000000"));
|
||||
let doc = s.doc().unwrap().doc.clone();
|
||||
@@ -274,6 +283,7 @@ fn spot_colours_get_their_own_plate() {
|
||||
|
||||
#[test]
|
||||
fn overprint_black_multiplies_in_preview() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
rect(&mut s, 0.0, 0.0, 100.0, 100.0, json!({"c": 100, "m": 0, "y": 0, "k": 0}));
|
||||
let k = rect(&mut s, 25.0, 25.0, 50.0, 50.0, json!({"c": 0, "m": 0, "y": 0, "k": 100}));
|
||||
@@ -304,6 +314,8 @@ fn view_toggles_redraw_without_dirtying() {
|
||||
assert_eq!(r["target"], "deuteranopia");
|
||||
assert_eq!(vectorcraft_render::proof::active_proof().unwrap().target, ProofTarget::Deuteranopia);
|
||||
assert!(s.execute("view.proofSetup", &json!({"target": "cmyk:Nope"})).is_err());
|
||||
assert!(s.execute("view.proofSetup", &json!({"target": format!("cmyk:{}", cms::WIDE_GAMUT_RGB)})).is_err(), "an RGB profile");
|
||||
s.execute("view.proofSetup", &json!({"target": format!("cmyk:{}", cms::DEVICE_CMYK)})).unwrap();
|
||||
s.execute("view.proofSetup", &json!({"target": "workingCmyk", "intent": "relative"})).unwrap();
|
||||
let r = s.execute("view.proofColors", &json!({"on": false})).unwrap();
|
||||
assert_eq!(r["proofColors"], false);
|
||||
@@ -325,6 +337,7 @@ fn pdf_text(doc: &Document) -> String {
|
||||
|
||||
#[test]
|
||||
fn pdf_cmyk_document_writes_device_cmyk() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut s = session();
|
||||
rect(&mut s, 0.0, 0.0, 10.0, 10.0, json!("#ff0000"));
|
||||
let rgb_pdf = pdf_text(&s.doc().unwrap().doc);
|
||||
@@ -356,6 +369,7 @@ fn pdf_spot_colour_is_a_separation() {
|
||||
|
||||
#[test]
|
||||
fn intents_reach_the_document_conversion() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut a = session();
|
||||
rect(&mut a, 0.0, 0.0, 10.0, 10.0, json!("#00ff00"));
|
||||
let mut b = session();
|
||||
@@ -377,3 +391,24 @@ fn intents_reach_the_document_conversion() {
|
||||
assert_ne!(first(&a), first(&b));
|
||||
let _ = Intent::Perceptual;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_profile_name_resolves_in_commands_and_files() {
|
||||
let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let (old, _) = cms::LEGACY_NAMES[0];
|
||||
let mut s = session();
|
||||
let before = cms::active_settings();
|
||||
let r = s.execute("edit.colorSettings", &json!({"rgb": old})).unwrap();
|
||||
assert_eq!(r["rgb"], cms::WIDE_GAMUT_RGB);
|
||||
assert_eq!(cms::active_settings().rgb, cms::WIDE_GAMUT_RGB);
|
||||
let names: Vec<&str> = r["profiles"].as_array().unwrap().iter().map(|p| p["name"].as_str().unwrap()).collect();
|
||||
assert!(names.contains(&cms::WIDE_GAMUT_RGB) && !names.contains(&old));
|
||||
let r = s.execute("edit.assignProfile", &json!({"rgb": old})).unwrap();
|
||||
assert_eq!(r["rgb"], cms::WIDE_GAMUT_RGB, "stored under the current name");
|
||||
// A document saved by an older version names the profile the old way.
|
||||
let mut d = (*s.doc().unwrap().doc).clone();
|
||||
d.unknown.insert(cmd::colormgmt::PROFILES_KEY.into(), json!({"rgb": old, "cmyk": null}));
|
||||
let back = vectorcraft_format::load(&vectorcraft_format::save(&d, false)).unwrap();
|
||||
assert_eq!(cmd::colormgmt::doc_profiles(&back), (Some(cms::WIDE_GAMUT_RGB.to_string()), None));
|
||||
cms::set_active(&before).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user