C: Color Settings, Proof Setup and Proof Colors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cf5188a272
commit
fb747c5fa5
|
|
@ -99,6 +99,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
|||
- PDF/X-4 export (`file.exportPdf {standard: "x4"}`): an output intent with our own Generic CMYK ICC profile (lut16 tables generated from the parametric press model), GTS_PDFXVersion in Info and XMP, and built-in PDF/X-4 checks reported as warnings. qpdf finds no errors in the result.
|
||||
- EPS place: placed at its (high-resolution) bounding box and shown, printed and exported through its TIFF preview, or a placeholder when it has none. PostScript itself isn't interpreted.
|
||||
- Preferences: Dictionary (user dictionary; `spelling.setWords/words`), Spelling (Dynamic Spelling: red squiggles on the canvas, also Edit ▸ Spelling), Autocorrect (word list, applied as you type), Notes, Track Changes, Story Editor Display and File Handling (recovery interval) sections.
|
||||
- Colour management exposed: Edit ▸ Color Settings (`color.settings`: RGB/CMYK working spaces, intent, black-point compensation; `color.loadProfile` for ICC files; `color.convert` through the working spaces, with gamut checks) and View ▸ Proof Setup / Proof Colors (press, sRGB, legacy Mac RGB, colour-blindness simulations, simulate paper) on the canvas.
|
||||
- Drag and drop text editing: drag selected text to move it (Alt copies) with its formatting (`text.release`).
|
||||
- Image Import Options: place any page of a multi-page PDF (`file.place {pdfPage}`; the app asks which page), shown on screen and embedded as that page in PDF export.
|
||||
- Define Lists: named numbered lists that continue across stories in page order (`list.define`, paragraph `listName` / `startAt`), round-tripped through IDML; `null` in `type.para` / `type.char` now removes an override.
|
||||
|
|
@ -157,7 +158,7 @@ four agents in parallel on separate crates:
|
|||
| Work | Estimate |
|
||||
|---|---|
|
||||
| Open P0 (2: PDF/X-4 validation against a certified checker, PostScript interpretation for EPS) | 8 h |
|
||||
| Open P1 (5: New Window, colour management, language support, UI keyboard navigation, …) | 15 h |
|
||||
| Open P1 (4 partial: New Window as a separate OS window, language support, UI keyboard navigation, on-screen blending in the blend space) | 12 h |
|
||||
| Open P2 (37: 12 missing — spread rotation, page transitions, vertical/RTL type, math, video/audio, split window, localization, flattener, placed-file layers — and 25 partial) | 110 h |
|
||||
| Depth and pixel fidelity of every dialog, panel and menu against InDesign 2026 | 90 h |
|
||||
| Performance (incremental composition, GPU raster) and interchange hardening (IDML/PDF corpus) | 55 h |
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ fn services() -> Services {
|
|||
let d = rfd::FileDialog::new();
|
||||
let d = if purpose == "swatches" {
|
||||
d.add_filter("Swatch Exchange (ASE)", &["ase"])
|
||||
} else if purpose == "icc" {
|
||||
d.add_filter("ICC profile", &["icc", "icm"])
|
||||
} else if purpose == "book" {
|
||||
d.add_filter("Book", &["dcbook"])
|
||||
} else if purpose == "xml" {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
//! Edit › Color Settings: the RGB and CMYK working spaces, conversion intent and black-point
|
||||
//! compensation (process-wide), and loading ICC profiles.
|
||||
|
||||
use designcraft_color::cms::{self, ColorSettings, Intent};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::{CommandSpec, always, bad, cmd, str_param};
|
||||
use crate::Result;
|
||||
|
||||
fn state() -> Value {
|
||||
let st = cms::active_settings();
|
||||
json!({"rgb": st.rgb, "cmyk": st.cmyk, "intent": st.intent.id(), "bpc": st.bpc, "profiles": cms::profiles()})
|
||||
}
|
||||
|
||||
fn set(p: &Value) -> Result<Value> {
|
||||
let mut st: ColorSettings = cms::active_settings();
|
||||
if let Some(v) = str_param(p, "rgb") {
|
||||
st.rgb = v.to_string();
|
||||
}
|
||||
if let Some(v) = str_param(p, "cmyk") {
|
||||
st.cmyk = v.to_string();
|
||||
}
|
||||
if let Some(v) = str_param(p, "intent") {
|
||||
st.intent = Intent::parse(v).ok_or_else(|| bad("color.settings", format!("unknown intent `{v}`")))?;
|
||||
}
|
||||
if let Some(v) = p.get("bpc").and_then(Value::as_bool) {
|
||||
st.bpc = v;
|
||||
}
|
||||
cms::set_active(&st).map_err(|e| bad("color.settings", e.to_string()))?;
|
||||
Ok(state())
|
||||
}
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
cmd!(
|
||||
noundo "color.settings",
|
||||
"Color Settings",
|
||||
[],
|
||||
None,
|
||||
"{rgb?: working RGB profile, cmyk?: working CMYK profile, intent?: perceptual|relative|saturation|absolute, bpc?: bool} → {rgb, cmyk, intent, bpc, profiles}",
|
||||
always,
|
||||
|_s, p| set(p)
|
||||
),
|
||||
cmd!(noundo "color.loadProfile", "Load Profile…", [], None, "{path} — an ICC profile (.icc/.icm) to use as a working space or proof target", always, |_s, p| {
|
||||
let path = str_param(p, "path").ok_or_else(|| bad("color.loadProfile", "`path` required"))?;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let info = cms::load_icc_file(std::path::Path::new(path)).map_err(|e| bad("color.loadProfile", e.to_string()))?;
|
||||
Ok(json!(info))
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let _ = path;
|
||||
Err(bad("color.loadProfile", "no file system on the web"))
|
||||
}
|
||||
}),
|
||||
cmd!(query "color.convert", "Convert Color", [], None, "{color: {model: rgb, r, g, b} | {model: cmyk, c, m, y, k} | {model: gray, k}, to: rgb|cmyk|gray|lab} — through the working spaces", always, |_s, p| {
|
||||
let c: designcraft_color::Color = serde_json::from_value(p.get("color").cloned().unwrap_or_default()).map_err(|e| bad("color.convert", e.to_string()))?;
|
||||
let a = cms::active();
|
||||
let intent = a.settings().intent;
|
||||
Ok(match str_param(p, "to").unwrap_or("rgb") {
|
||||
"lab" => {
|
||||
let l = a.lab(&c);
|
||||
json!({"l": l.l, "a": l.a, "b": l.b, "outOfGamut": a.out_of_gamut(&c)})
|
||||
}
|
||||
"cmyk" => json!(a.convert(&c, cms::Model::Cmyk, intent)),
|
||||
"gray" => json!(a.convert(&c, cms::Model::Gray, intent)),
|
||||
_ => json!(a.convert(&c, cms::Model::Rgb, intent)),
|
||||
})
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Session;
|
||||
|
||||
#[test]
|
||||
fn color_settings_report_convert_and_validate() {
|
||||
// The settings are process-wide: this test only re-applies the current ones, so tests
|
||||
// rendering in parallel aren't affected.
|
||||
let mut s = Session::new();
|
||||
let before = s.execute("color.settings", &json!({})).unwrap();
|
||||
assert!(before["profiles"].as_array().unwrap().len() >= 6);
|
||||
let cyan = json!({"color": {"model": "cmyk", "c": 1.0, "m": 0.0, "y": 0.0, "k": 0.0}, "to": "rgb"});
|
||||
let rgb = s.execute("color.convert", &cyan).unwrap();
|
||||
assert_eq!(rgb["model"], "rgb");
|
||||
assert!(rgb["r"].as_f64().unwrap() < 0.3 && rgb["b"].as_f64().unwrap() > 0.7, "{rgb}");
|
||||
let lab = s.execute("color.convert", &json!({"color": {"model": "rgb", "r": 0.0, "g": 1.0, "b": 0.0}, "to": "lab"})).unwrap();
|
||||
assert_eq!(lab["outOfGamut"], true, "pure RGB green is outside the press gamut");
|
||||
let again = s
|
||||
.execute("color.settings", &json!({"rgb": before["rgb"], "cmyk": before["cmyk"], "intent": before["intent"], "bpc": before["bpc"]}))
|
||||
.unwrap();
|
||||
assert_eq!(again["cmyk"], before["cmyk"]);
|
||||
assert!(s.execute("color.settings", &json!({"cmyk": "Nope"})).is_err());
|
||||
assert!(s.execute("color.settings", &json!({"intent": "sideways"})).is_err());
|
||||
assert_eq!(s.execute("color.settings", &json!({})).unwrap()["cmyk"], before["cmyk"], "a failed change leaves the settings");
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ pub mod book;
|
|||
mod buttons;
|
||||
mod captions;
|
||||
mod changes;
|
||||
mod colorsettings;
|
||||
mod conditions;
|
||||
mod datamerge;
|
||||
mod edit;
|
||||
|
|
@ -156,6 +157,7 @@ pub fn command_specs() -> &'static [CommandSpec] {
|
|||
v.extend(book::specs());
|
||||
v.extend(buttons::specs());
|
||||
v.extend(captions::specs());
|
||||
v.extend(colorsettings::specs());
|
||||
v.extend(changes::specs());
|
||||
v.extend(conditions::specs());
|
||||
v.extend(library::specs());
|
||||
|
|
|
|||
|
|
@ -906,6 +906,25 @@ pub fn pdf_page_box(bytes: &[u8], page: usize, kind: &str) -> Option<(f64, f64,
|
|||
/// Separations Preview on a rendered image: `plate` 0–3 shows that process plate (C, M, Y, K) as
|
||||
/// ink density in grey; with an `ink_limit` (0–4, total ink) areas over it are shown in red.
|
||||
/// Pixels are separated with a plain GCR (black = 1 − max(r, g, b)), so it's a preview.
|
||||
/// View › Proof Colors: simulate the proof target (a press, another RGB space, colour-vision
|
||||
/// deficiencies) through the active colour settings' proof table.
|
||||
pub fn proof_view(img: &mut Rendered, setup: &designcraft_color::cms::ProofSetup) {
|
||||
let lut = designcraft_color::cms::active().proof_lut(setup);
|
||||
for px in img.pixels.chunks_exact_mut(4) {
|
||||
let a = px[3];
|
||||
if a == 0 {
|
||||
continue;
|
||||
}
|
||||
// Premultiplied → straight, proof, back.
|
||||
let k = 255.0 / a as f32;
|
||||
let rgb = [px[0], px[1], px[2]].map(|v| ((v as f32 * k).round()).min(255.0) as u8);
|
||||
let out = lut.apply8(rgb);
|
||||
for i in 0..3 {
|
||||
px[i] = ((out[i] as u32 * a as u32 + 127) / 255) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separation_view(img: &mut Rendered, plate: Option<u8>, ink_limit: Option<f32>) {
|
||||
for px in img.pixels.chunks_exact_mut(4) {
|
||||
let a = px[3] as f32 / 255.0;
|
||||
|
|
@ -941,6 +960,23 @@ mod tests {
|
|||
use designcraft_doc::build::NewDocument;
|
||||
use designcraft_doc::{Fill, ParaFormat};
|
||||
|
||||
#[test]
|
||||
fn proof_view_simulates_the_target() {
|
||||
use designcraft_color::cms::{ProofSetup, ProofTarget};
|
||||
let red = |a: u8| Rendered { width: 1, height: 1, pixels: vec![a, 0, 0, a] };
|
||||
let mut img = red(255);
|
||||
proof_view(&mut img, &ProofSetup { target: ProofTarget::Protanopia, ..Default::default() });
|
||||
assert!(img.pixels[1] > 40, "protanopia turns pure red olive: {:?}", img.pixels);
|
||||
let mut img = red(255);
|
||||
proof_view(&mut img, &ProofSetup { target: ProofTarget::MonitorRgb, ..Default::default() });
|
||||
assert!(img.pixels[0] > 245 && img.pixels[1] < 10, "monitor RGB is no simulation: {:?}", img.pixels);
|
||||
// Transparent pixels stay transparent; premultiplication is kept.
|
||||
let mut img = Rendered { width: 2, height: 1, pixels: vec![0, 0, 0, 0, 100, 0, 0, 128] };
|
||||
proof_view(&mut img, &ProofSetup { target: ProofTarget::WorkingCmyk, ..Default::default() });
|
||||
assert_eq!(&img.pixels[..4], &[0, 0, 0, 0]);
|
||||
assert!(img.pixels[4] <= 128 && img.pixels[7] == 128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_page_with_text_and_fill() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
|
|
|
|||
|
|
@ -499,6 +499,9 @@ fn render_worker_spawn(ctx: &egui::Context) -> Option<crate::render_worker::Work
|
|||
}
|
||||
|
||||
fn upload(app: &mut DesignApp, ctx: &egui::Context, mut img: designcraft_render::Rendered) {
|
||||
if app.ui.proof_colors {
|
||||
designcraft_render::proof_view(&mut img, &app.ui.proof_setup);
|
||||
}
|
||||
if app.ui.separation.is_some() || app.ui.ink_limit.is_some() {
|
||||
designcraft_render::separation_view(&mut img, app.ui.separation, app.ui.ink_limit);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -729,6 +729,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) {
|
|||
"preferences" => "Preferences",
|
||||
"print" => "Print",
|
||||
"pdfImport" => "Place PDF",
|
||||
"colorSettings" => "Color Settings",
|
||||
"keyboardShortcuts" => "Keyboard Shortcuts",
|
||||
"colorPicker" => "Color Picker",
|
||||
id => match id.strip_prefix("cmd:").and_then(designcraft_engine::find_command) {
|
||||
|
|
@ -949,6 +950,48 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) {
|
|||
});
|
||||
});
|
||||
}
|
||||
"colorSettings" => {
|
||||
let profiles = d.fields.get("profiles").and_then(Value::as_array).cloned().unwrap_or_default();
|
||||
egui::Grid::new("color_settings").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| {
|
||||
for (key, label, kind) in [("rgb", "RGB:", "rgb"), ("cmyk", "CMYK:", "cmyk")] {
|
||||
ui.label(label);
|
||||
let cur = d.s(key);
|
||||
egui::ComboBox::from_id_salt(("ws", key)).selected_text(&cur).width(260.0).show_ui(ui, |ui| {
|
||||
for pr in profiles.iter().filter(|p| p["kind"] == kind) {
|
||||
let name = pr["name"].as_str().unwrap_or("");
|
||||
if ui.selectable_label(name == cur, name).clicked() {
|
||||
d.fields.insert(key.into(), json!(name));
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
ui.label("Intent:");
|
||||
let cur = d.s("intent");
|
||||
egui::ComboBox::from_id_salt("ws_intent")
|
||||
.selected_text(designcraft_color::cms::Intent::parse(&cur).map_or("", |i| i.label()))
|
||||
.show_ui(ui, |ui| {
|
||||
for i in designcraft_color::cms::Intent::ALL {
|
||||
if ui.selectable_label(i.id() == cur, i.label()).clicked() {
|
||||
d.fields.insert("intent".into(), json!(i.id()));
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
});
|
||||
check(ui, &mut d, "bpc", "Use Black Point Compensation");
|
||||
if ui.button("Load Profile…").clicked()
|
||||
&& let Some(path) = app.services.pick_open.as_mut().and_then(|f| f("icc"))
|
||||
{
|
||||
match app.session.execute("color.loadProfile", &json!({"path": path})) {
|
||||
Ok(_) => {
|
||||
let f = app.session.execute("color.settings", &json!({})).unwrap_or_default();
|
||||
d.fields.insert("profiles".into(), f["profiles"].clone());
|
||||
}
|
||||
Err(e) => app.status(format!("Color Settings: {e}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
"keyboardShortcuts" => keyboard_shortcuts(app, ui, &mut d),
|
||||
"userDictionary" => user_dictionary(app, ui, &mut d),
|
||||
"menus" => {
|
||||
|
|
@ -1281,6 +1324,12 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> {
|
|||
let hex = d.s("hex");
|
||||
app.run("object.color", json!({"color": hex, "target": d.s("target")}))
|
||||
}
|
||||
"colorSettings" => {
|
||||
let r = app.run("color.settings", json!({"rgb": d.s("rgb"), "cmyk": d.s("cmyk"), "intent": d.s("intent"), "bpc": d.b("bpc")}))?;
|
||||
app.ui.color_settings = Some(designcraft_color::cms::active_settings());
|
||||
app.canvas.shown = None;
|
||||
Ok(r)
|
||||
}
|
||||
"pdfImport" => {
|
||||
let crop = d.s("crop");
|
||||
app.run("file.place", json!({"path": d.s("path"), "pdfPage": d.n("page").unwrap_or(1.0).max(1.0) as u64, "pdfCrop": if crop.is_empty() { "crop".to_string() } else { crop }}))
|
||||
|
|
|
|||
|
|
@ -103,6 +103,11 @@ pub struct UiState {
|
|||
/// Edit › Menus: hidden menu items (`menu/label`), and Show Full Menus.
|
||||
pub hidden_menu_items: Vec<String>,
|
||||
pub show_full_menus: bool,
|
||||
/// Edit › Color Settings (re-applied at start).
|
||||
pub color_settings: Option<designcraft_color::cms::ColorSettings>,
|
||||
/// View › Proof Colors and View › Proof Setup.
|
||||
pub proof_colors: bool,
|
||||
pub proof_setup: designcraft_color::cms::ProofSetup,
|
||||
/// Edit › Spelling › Dynamic Spelling: misspelled words underlined on the canvas.
|
||||
pub dynamic_spelling: bool,
|
||||
/// Preferences › Story Editor Display: text size (points).
|
||||
|
|
@ -179,6 +184,9 @@ impl Default for UiState {
|
|||
hidden_menu_items: Vec::new(),
|
||||
show_full_menus: false,
|
||||
story_editor_size: 14.0,
|
||||
color_settings: None,
|
||||
proof_colors: false,
|
||||
proof_setup: Default::default(),
|
||||
dynamic_spelling: false,
|
||||
language: String::new(),
|
||||
flattener: String::new(),
|
||||
|
|
@ -313,6 +321,8 @@ pub struct DesignApp {
|
|||
pub other_pane: Option<(CanvasCache, Option<egui::Rect>)>,
|
||||
/// Power Zoom in progress: the zoom to return to and the canvas point it will centre on.
|
||||
pub power_zoom: Option<(f64, designcraft_geom::Point)>,
|
||||
/// The persisted colour settings have been applied this session.
|
||||
pub color_applied: bool,
|
||||
pub perf: Perf,
|
||||
pub synthetic: Vec<egui::Event>,
|
||||
/// Story open in the Story Editor.
|
||||
|
|
@ -348,6 +358,7 @@ impl DesignApp {
|
|||
focus_pane: 0,
|
||||
other_pane: None,
|
||||
power_zoom: None,
|
||||
color_applied: false,
|
||||
perf: Perf::default(),
|
||||
synthetic: vec![],
|
||||
story_editor: None,
|
||||
|
|
@ -489,6 +500,12 @@ impl DesignApp {
|
|||
|
||||
/// Per-frame logic before layout.
|
||||
pub fn logic(&mut self, ctx: &egui::Context) {
|
||||
if !self.color_applied {
|
||||
self.color_applied = true;
|
||||
if let Some(cs) = self.ui.color_settings.clone() {
|
||||
let _ = designcraft_color::cms::set_active(&cs);
|
||||
}
|
||||
}
|
||||
let scale = self.ui.ui_scale.clamp(0.5, 3.0);
|
||||
if (ctx.zoom_factor() - scale).abs() > 1e-3 {
|
||||
ctx.set_zoom_factor(scale);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,14 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
|
|||
("view.textThreads", "Show/Hide Text Threads", Some("Cmd+Alt+Y"), "{}"),
|
||||
("view.hiddenCharacters", "Show/Hide Hidden Characters", Some("Cmd+Alt+I"), "{}"),
|
||||
("view.taggedFrames", "Show/Hide Tagged Frames", None, "{} — XML-tagged frames outlined in their tag colour"),
|
||||
("view.proofColors", "Proof Colors", None, "{on?: bool} — simulate the proof target on screen"),
|
||||
(
|
||||
"view.proofSetup",
|
||||
"Proof Setup",
|
||||
None,
|
||||
"{target?: workingCmyk|srgb|legacyMacRgb|monitorRgb|protanopia|deuteranopia|cmyk:<profile>, intent?, simulatePaper?: bool|\"toggle\"}",
|
||||
),
|
||||
("app.colorSettings", "Color Settings…", None, "{} — the working spaces, intent and black-point compensation"),
|
||||
("view.separations", "Separations Preview", None, "{plate?: cyan|magenta|yellow|black|null, inkLimit?: percent|null}"),
|
||||
("view.overprintPreview", "Overprint Preview", Some("Cmd+Alt+Shift+Y"), "{} — show how overprinting inks mix"),
|
||||
("view.fastDisplay", "Fast Display", Some("Cmd+Alt+Shift+Z"), "{} — placed graphics as grey boxes, no effects"),
|
||||
|
|
@ -179,6 +187,7 @@ pub const MENUS: &[(&str, &[&str])] = &[
|
|||
"cmd:edit.transparencyBlendSpace|Document RGB|{\"space\": \"rgb\"}",
|
||||
"cmd:edit.transparencyBlendSpace|Document CMYK|{\"space\": \"cmyk\"}",
|
||||
"<",
|
||||
"ui:app.colorSettings",
|
||||
">Interface Language",
|
||||
"ui:app.language|English|{\"lang\": \"\"}",
|
||||
"ui:app.language|Deutsch|{\"lang\": \"de\"}",
|
||||
|
|
@ -320,6 +329,17 @@ pub const MENUS: &[(&str, &[&str])] = &[
|
|||
"-",
|
||||
"ui:view.hiddenCharacters",
|
||||
"ui:view.taggedFrames",
|
||||
">Proof Setup",
|
||||
"ui:view.proofSetup|Working CMYK|{\"target\": \"workingCmyk\"}",
|
||||
"ui:view.proofSetup|Internet Standard RGB (sRGB)|{\"target\": \"srgb\"}",
|
||||
"ui:view.proofSetup|Legacy Macintosh RGB|{\"target\": \"legacyMacRgb\"}",
|
||||
"ui:view.proofSetup|Monitor RGB|{\"target\": \"monitorRgb\"}",
|
||||
"ui:view.proofSetup|Color Blindness – Protanopia|{\"target\": \"protanopia\"}",
|
||||
"ui:view.proofSetup|Color Blindness – Deuteranopia|{\"target\": \"deuteranopia\"}",
|
||||
"-",
|
||||
"ui:view.proofSetup|Simulate Paper Color|{\"simulatePaper\": \"toggle\"}",
|
||||
"<",
|
||||
"ui:view.proofColors",
|
||||
">Separations Preview",
|
||||
"ui:view.separations|Off|{\"plate\": null, \"inkLimit\": null}",
|
||||
"ui:view.separations|Cyan|{\"plate\": \"cyan\"}",
|
||||
|
|
@ -956,6 +976,34 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
|
|||
"view.textThreads" => flag(&mut app.ui.text_threads),
|
||||
"view.hiddenCharacters" => flag(&mut app.ui.hidden_characters),
|
||||
"view.taggedFrames" => flag(&mut app.ui.tagged_frames),
|
||||
"view.proofColors" => {
|
||||
app.ui.proof_colors = p.get("on").and_then(Value::as_bool).unwrap_or(!app.ui.proof_colors);
|
||||
app.canvas.shown = None;
|
||||
Ok(json!(app.ui.proof_colors))
|
||||
}
|
||||
"view.proofSetup" => {
|
||||
if let Some(t) = p.get("target").and_then(Value::as_str) {
|
||||
let Some(t) = designcraft_color::cms::ProofTarget::parse(t) else { return Some(Err(format!("unknown proof target `{t}`"))) };
|
||||
app.ui.proof_setup.target = t;
|
||||
app.ui.proof_colors = true;
|
||||
}
|
||||
if let Some(i) = p.get("intent").and_then(Value::as_str) {
|
||||
let Some(i) = designcraft_color::cms::Intent::parse(i) else { return Some(Err(format!("unknown intent `{i}`"))) };
|
||||
app.ui.proof_setup.intent = i;
|
||||
}
|
||||
match p.get("simulatePaper") {
|
||||
Some(Value::Bool(b)) => app.ui.proof_setup.simulate_paper = *b,
|
||||
Some(Value::String(s)) if s == "toggle" => app.ui.proof_setup.simulate_paper = !app.ui.proof_setup.simulate_paper,
|
||||
_ => {}
|
||||
}
|
||||
app.canvas.shown = None;
|
||||
Ok(json!({"target": app.ui.proof_setup.target.id(), "simulatePaper": app.ui.proof_setup.simulate_paper, "on": app.ui.proof_colors}))
|
||||
}
|
||||
"app.colorSettings" => {
|
||||
let f = app.session.execute("color.settings", &json!({})).unwrap_or_default();
|
||||
app.ui.dialog = Some(crate::dialogs::Dialog::new("colorSettings", f));
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"view.separations" => {
|
||||
if let Some(v) = p.get("plate") {
|
||||
app.ui.separation = v.as_str().and_then(|s| ["cyan", "magenta", "yellow", "black"].iter().position(|x| *x == s)).map(|i| i as u8);
|
||||
|
|
@ -1440,6 +1488,12 @@ pub fn checked(app: &DesignApp, id: &str, params: &Value) -> Option<bool> {
|
|||
"changes.track" => app.session.active().is_some_and(|d| d.doc.settings.track_changes),
|
||||
"window.controlBar" => app.ui.control_bar,
|
||||
"window.split" => app.split,
|
||||
"view.proofColors" => app.ui.proof_colors,
|
||||
"view.proofSetup" => match (params.get("target").and_then(Value::as_str), params.get("simulatePaper")) {
|
||||
(Some(t), _) => app.ui.proof_setup.target.id() == t,
|
||||
(None, Some(_)) => app.ui.proof_setup.simulate_paper,
|
||||
_ => return None,
|
||||
},
|
||||
"edit.dynamicSpelling" => app.ui.dynamic_spelling,
|
||||
"app.language" => app.ui.language == params.get("lang").and_then(Value::as_str).unwrap_or(""),
|
||||
"app.flattener" => app.ui.flattener == params.get("preset").and_then(Value::as_str).unwrap_or(""),
|
||||
|
|
@ -1823,6 +1877,20 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_setup_and_colors() {
|
||||
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
assert_eq!(checked(&app, "view.proofColors", &Value::Null), Some(false));
|
||||
let r = run_ui(&mut app, "view.proofSetup", &json!({"target": "deuteranopia"})).unwrap().unwrap();
|
||||
assert_eq!(r["on"], true);
|
||||
assert_eq!(checked(&app, "view.proofSetup", &json!({"target": "deuteranopia"})), Some(true));
|
||||
run_ui(&mut app, "view.proofSetup", &json!({"simulatePaper": "toggle"})).unwrap().unwrap();
|
||||
assert!(app.ui.proof_setup.simulate_paper);
|
||||
run_ui(&mut app, "view.proofColors", &json!({})).unwrap().unwrap();
|
||||
assert!(!app.ui.proof_colors);
|
||||
assert!(run_ui(&mut app, "view.proofSetup", &json!({"target": "nope"})).unwrap().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panels_float_and_dock() {
|
||||
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
|||
| Priority | Features | Done | Partial | Missing | Parity |
|
||||
|---|---|---|---|---|---|
|
||||
| P0 | 102 | 100 | 2 | 0 | 99% |
|
||||
| P1 | 97 | 92 | 5 | 0 | 97% |
|
||||
| P1 | 97 | 93 | 4 | 0 | 98% |
|
||||
| P2 | 76 | 41 | 29 | 6 | 73% |
|
||||
|
||||
| Area | Parity |
|
||||
|
|
@ -20,7 +20,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
|||
| Layers | 95% |
|
||||
| Frames, shapes & paths | 100% |
|
||||
| Transform | 100% |
|
||||
| Fill, stroke, colour | 96% |
|
||||
| Fill, stroke, colour | 99% |
|
||||
| Effects & transparency | 75% |
|
||||
| Placing & links | 91% |
|
||||
| Type & text frames | 95% |
|
||||
|
|
@ -153,7 +153,7 @@ Open P0 items: Vector/page formats (PDF/AI/SVG/EPS place), Export to PDF (print)
|
|||
| Fill, stroke, colour | Unnamed colour to swatch | P1 | D |
|
||||
| Fill, stroke, colour | Stroke panel | P0 | D |
|
||||
| Fill, stroke, colour | Stroke styles | P1 | D |
|
||||
| Fill, stroke, colour | Color management | P1 | P |
|
||||
| Fill, stroke, colour | Color management | P1 | D |
|
||||
| Fill, stroke, colour | Overprint Preview | P1 | D |
|
||||
| Fill, stroke, colour | Separations Preview | P2 | P |
|
||||
| Fill, stroke, colour | Eyedropper | P1 | D |
|
||||
|
|
|
|||
Loading…
Reference in New Issue