mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
V: Separations Preview and ink limit view
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
43ba0bf086
commit
adb054df46
6 files changed
+78
-6
No files matched your search
@@ -80,6 +80,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
||||
- Content Collector and Placer tools (B; `conveyor.collect/place/list/clear`): copies of objects (with stories, styles, images) ride a conveyor between pages and documents; Alt-click keeps them on it.
|
||||
- Linked stories (Edit ▸ Place and Link, `story.placeAndLink/links/updateLink/unlink`): child stories copy a parent, show out of date in the Links panel and update.
|
||||
- Edit ▸ Menus (`window.hideMenuItem`): hide menu items; menus with hidden items end in Show All Menu Items.
|
||||
- View ▸ Separations Preview (`view.separations`): one process plate as ink density, or areas over an ink limit in red (a GCR preview of the rendered view; spot plates and the flattener preview aren't there).
|
||||
- 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.
|
||||
|
||||
@@ -884,6 +884,34 @@ pub fn pdf_page_box(bytes: &[u8], page: usize, kind: &str) -> Option<(f64, f64,
|
||||
Some(rel(visible, b))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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;
|
||||
// Composite over paper white, then separate.
|
||||
let ch = |v: u8| (v as f32 / 255.0) + (1.0 - a);
|
||||
let (r, g, b) = (ch(px[0]).min(1.0), ch(px[1]).min(1.0), ch(px[2]).min(1.0));
|
||||
let k = 1.0 - r.max(g).max(b);
|
||||
let inks =
|
||||
if k >= 0.999 { [0.0, 0.0, 0.0, 1.0] } else { [(1.0 - r - k) / (1.0 - k), (1.0 - g - k) / (1.0 - k), (1.0 - b - k) / (1.0 - k), k] };
|
||||
let mut out = match plate {
|
||||
Some(p) => {
|
||||
let v = ((1.0 - inks[p.min(3) as usize].clamp(0.0, 1.0)) * 255.0).round() as u8;
|
||||
[v, v, v, 255]
|
||||
}
|
||||
None => [(r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8, 255],
|
||||
};
|
||||
if let Some(limit) = ink_limit
|
||||
&& inks.iter().sum::<f32>() > limit
|
||||
{
|
||||
out = [230, 40, 40, 255];
|
||||
}
|
||||
px.copy_from_slice(&out);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_mime(bytes: &[u8]) -> &'static str {
|
||||
designcraft_images::mime(bytes)
|
||||
}
|
||||
@@ -1014,6 +1042,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn separations_split_plates_and_flag_ink_limits() {
|
||||
// Pure cyan (0, 255, 255) and rich black-ish (20, 20, 20).
|
||||
let mut img = Rendered { width: 2, height: 1, pixels: vec![0, 255, 255, 255, 20, 20, 20, 255] };
|
||||
let mut c = img.clone();
|
||||
separation_view(&mut c, Some(0), None);
|
||||
assert_eq!(c.pixel(0, 0)[0], 0, "cyan plate solid under cyan");
|
||||
let mut m = img.clone();
|
||||
separation_view(&mut m, Some(1), None);
|
||||
assert_eq!(m.pixel(0, 0)[0], 255, "no magenta");
|
||||
separation_view(&mut img, None, Some(0.5));
|
||||
assert_eq!(img.pixel(0, 0), [230, 40, 40, 255], "100% over a 50% limit");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn black_overprints_only_with_the_preference() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
|
||||
@@ -465,7 +465,10 @@ fn render_worker_spawn(ctx: &egui::Context) -> Option<crate::render_worker::Work
|
||||
crate::render_worker::Worker::spawn(ctx.clone())
|
||||
}
|
||||
|
||||
fn upload(app: &mut DesignApp, ctx: &egui::Context, img: designcraft_render::Rendered) {
|
||||
fn upload(app: &mut DesignApp, ctx: &egui::Context, mut img: designcraft_render::Rendered) {
|
||||
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);
|
||||
}
|
||||
let ci = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
match &mut app.canvas.texture {
|
||||
Some(tex) => tex.set(ci, egui::TextureOptions::LINEAR),
|
||||
|
||||
@@ -102,6 +102,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,
|
||||
/// View › Separations Preview: a process plate (0–3) and/or an ink limit (total, 0–4).
|
||||
#[serde(skip)]
|
||||
pub separation: Option<u8>,
|
||||
#[serde(skip)]
|
||||
pub ink_limit: Option<f32>,
|
||||
/// View › Display Performance.
|
||||
pub display_quality: designcraft_render::DisplayQuality,
|
||||
/// Preferences › Interface › UI scaling (1 = 100%).
|
||||
@@ -164,6 +169,8 @@ impl Default for UiState {
|
||||
tagged_frames: false,
|
||||
hidden_menu_items: Vec::new(),
|
||||
show_full_menus: false,
|
||||
separation: None,
|
||||
ink_limit: None,
|
||||
display_quality: designcraft_render::DisplayQuality::High,
|
||||
ui_scale: 1.0,
|
||||
rich_black: false,
|
||||
|
||||
@@ -64,6 +64,7 @@ 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.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"),
|
||||
("view.typicalDisplay", "Typical Display", Some("Cmd+Alt+Z"), "{} — low-resolution image proxies"),
|
||||
@@ -294,6 +295,14 @@ pub const MENUS: &[(&str, &[&str])] = &[
|
||||
"-",
|
||||
"ui:view.hiddenCharacters",
|
||||
"ui:view.taggedFrames",
|
||||
">Separations Preview",
|
||||
"ui:view.separations|Off|{\"plate\": null, \"inkLimit\": null}",
|
||||
"ui:view.separations|Cyan|{\"plate\": \"cyan\"}",
|
||||
"ui:view.separations|Magenta|{\"plate\": \"magenta\"}",
|
||||
"ui:view.separations|Yellow|{\"plate\": \"yellow\"}",
|
||||
"ui:view.separations|Black|{\"plate\": \"black\"}",
|
||||
"ui:view.separations|Ink Limit 300%|{\"inkLimit\": 300}",
|
||||
"<",
|
||||
"-",
|
||||
">Hyperlinks & Cross-References",
|
||||
"cmd:hyperlink.create",
|
||||
@@ -872,6 +881,16 @@ 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.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);
|
||||
}
|
||||
if let Some(v) = p.get("inkLimit") {
|
||||
app.ui.ink_limit = v.as_f64().map(|l| (l / 100.0) as f32);
|
||||
}
|
||||
app.canvas.shown = None;
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"view.goToPage" => {
|
||||
match p.get("page") {
|
||||
Some(Value::Number(n)) => crate::canvas::go_to_page(app, (n.as_u64().unwrap_or(1) as usize).saturating_sub(1)),
|
||||
|
||||
+5
-5
@@ -11,7 +11,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
|---|---|---|---|---|---|
|
||||
| P0 | 102 | 99 | 3 | 0 | 99% |
|
||||
| P1 | 97 | 92 | 4 | 1 | 97% |
|
||||
| P2 | 76 | 33 | 18 | 25 | 55% |
|
||||
| P2 | 76 | 33 | 20 | 23 | 57% |
|
||||
|
||||
| Area | Parity |
|
||||
|---|---|
|
||||
@@ -20,7 +20,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
| Layers | 95% |
|
||||
| Frames, shapes & paths | 99% |
|
||||
| Transform | 100% |
|
||||
| Fill, stroke, colour | 95% |
|
||||
| Fill, stroke, colour | 96% |
|
||||
| Effects & transparency | 71% |
|
||||
| Placing & links | 90% |
|
||||
| Type & text frames | 94% |
|
||||
@@ -29,7 +29,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
| Tables | 92% |
|
||||
| Long documents | 88% |
|
||||
| Interactivity & digital | 75% |
|
||||
| Output & production | 78% |
|
||||
| Output & production | 80% |
|
||||
| XML & automation | 83% |
|
||||
| View & navigation | 88% |
|
||||
| Undo, history, saving | 100% |
|
||||
@@ -155,7 +155,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t
|
||||
| Fill, stroke, colour | Stroke styles | P1 | D |
|
||||
| Fill, stroke, colour | Color management | P1 | P |
|
||||
| Fill, stroke, colour | Overprint Preview | P1 | D |
|
||||
| Fill, stroke, colour | Separations Preview | P2 | M |
|
||||
| Fill, stroke, colour | Separations Preview | P2 | P |
|
||||
| Fill, stroke, colour | Eyedropper | P1 | D |
|
||||
| Fill, stroke, colour | Appearance of Black prefs | P1 | D |
|
||||
| Effects & transparency | Opacity & blend modes | P0 | D |
|
||||
@@ -287,7 +287,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t
|
||||
| Output & production | Export EPUB/HTML | P2 | P |
|
||||
| Output & production | Export RTF/TXT/Tagged text | P2 | P |
|
||||
| Output & production | Export XML | P2 | P |
|
||||
| Output & production | Separations / Flattener / Ink limit preview | P2 | M |
|
||||
| Output & production | Separations / Flattener / Ink limit preview | P2 | P |
|
||||
| XML & automation | Tags panel, Structure | P2 | P |
|
||||
| XML & automation | Import/Export XML | P2 | P |
|
||||
| XML & automation | Data Merge | P2 | D |
|
||||
|
||||
Reference in new issue
Block a user