C: Appearance of Black on screen (rich black / accurate)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
53fc678978
commit
bfe4220840
|
|
@ -30,6 +30,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
|||
- Edit › Paste Into (copied objects become a frame's content, clipped by it; rendered, PDF and IDML both ways — verified in InDesign 2026); Polygon Settings (sides, star inset; double-click the Polygon tool).
|
||||
- Color panel (fill/stroke proxy, CMYK/RGB/Lab sliders with channel ramps, tint slider for swatches, spectrum ramp, Add to Swatches) and Color Picker (double-click the proxy); mixed colours are unnamed (not listed in Swatches until added); `object.color`, `swatch.addToSwatches`, `swatch.addUnnamed`.
|
||||
- Place text files: Word (.docx: styles by name with their attributes, bold/italic/underline/size/font, footnotes, tables, tabs, breaks), RTF and plain text, into the insertion point, the selected frame or a new frame; autoflow adds pages and threaded frames until the text fits; Remove Styles option (`designcraft-textimport`).
|
||||
- Preferences › Appearance of Black: 100% K on screen accurately (dark grey) or as rich black (`window.richBlack`); the printing/export side is still to come.
|
||||
- Spreads of up to 10 pages: Add to Previous Spread and Allow Spread to Shuffle (Pages panel; `layout.pages.toSpread`, `layout.spreadShuffle`); inserted pages go around kept spreads.
|
||||
- Page tool (⇧P) and `layout.pageSize`: pages of their own size (or a preset); the spine stays put and objects keep their place on their page.
|
||||
- File › Print (⌘P): printer, copies, page range, spreads, marks, bleed; printed as PDF through the system spooler (`file.print`, `file.printers`, `dryRun` for agents). Shortcuts now act like choosing the menu item (dialogs open), and UI commands win shortcut ties (⌘N opens New Document).
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@ pub struct RenderOptions {
|
|||
pub highlight_missing_fonts: bool,
|
||||
/// View › Display Performance.
|
||||
pub quality: DisplayQuality,
|
||||
/// Preferences › Appearance of Black: show 100% K as rich (pure) black instead of the
|
||||
/// accurate dark grey.
|
||||
pub rich_black: bool,
|
||||
}
|
||||
|
||||
/// View › Display Performance: how placed graphics and effects are drawn on screen.
|
||||
|
|
@ -128,6 +131,7 @@ impl Default for RenderOptions {
|
|||
page_shadow: false,
|
||||
highlight_missing_fonts: false,
|
||||
quality: DisplayQuality::High,
|
||||
rich_black: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -237,6 +241,7 @@ impl Renderer {
|
|||
_ => RenderContext::new_with(w, h, vello_cpu::RenderSettings { num_threads: self.threads, ..Default::default() }),
|
||||
};
|
||||
let mt = ctx.render_settings().num_threads > 0;
|
||||
RICH_BLACK.with(|r| r.set(opts.rich_black));
|
||||
self.stats = FrameStats::default();
|
||||
self.stories.clear();
|
||||
self.glyphs.tick();
|
||||
|
|
@ -582,7 +587,21 @@ fn rect_overlaps(a: Rect, b: Rect) -> bool {
|
|||
a.x0 < b.x1 && a.x1 > b.x0 && a.y0 < b.y1 && a.y1 > b.y0
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Appearance of Black for the render on this thread: 100% K as pure black.
|
||||
static RICH_BLACK: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
|
||||
}
|
||||
|
||||
pub fn color_of(c: &designcraft_color::Color, alpha: f32) -> peniko::Color {
|
||||
if RICH_BLACK.with(|r| r.get())
|
||||
&& let designcraft_color::Color::Cmyk { c: cc, m, y, k } = *c
|
||||
&& cc <= 1e-3
|
||||
&& m <= 1e-3
|
||||
&& y <= 1e-3
|
||||
&& k >= 0.999
|
||||
{
|
||||
return peniko::Color::from_rgba8(0, 0, 0, (alpha.clamp(0.0, 1.0) * 255.0).round() as u8);
|
||||
}
|
||||
let [r, g, b, a] = c.to_rgba8(alpha);
|
||||
peniko::Color::from_rgba8(r, g, b, a)
|
||||
}
|
||||
|
|
@ -832,6 +851,25 @@ mod tests {
|
|||
assert!(gap[0] < 80 && gap[2] > 150, "gap {gap:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appearance_of_black_on_screen() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
let lid = d.default_layer();
|
||||
let id = designcraft_doc::ItemId(d.alloc());
|
||||
let mut it =
|
||||
Item::new(id, lid, designcraft_doc::Shape::Rectangle, designcraft_geom::shapes::rectangle(Rect::new(100.0, 100.0, 200.0, 200.0)));
|
||||
it.fill = Fill::swatch(designcraft_color::swatch::BLACK);
|
||||
d.insert_item(SpreadRef::Doc(0), it, None).unwrap();
|
||||
let cache = Cache::new();
|
||||
let mut r = Renderer::new();
|
||||
r.threads = 0;
|
||||
let px = |rich: bool, r: &mut Renderer| {
|
||||
r.render_page(&d, &cache, 0, 1.0, false, &RenderOptions { rich_black: rich, ..Default::default() }).unwrap().pixel(150, 150)
|
||||
};
|
||||
assert!(px(false, &mut r)[0] > 20, "accurate: a dark grey");
|
||||
assert_eq!(px(true, &mut r)[..3], [0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_performance_fast_draws_grey_boxes() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
|
|
|
|||
|
|
@ -312,6 +312,7 @@ fn render_texture(app: &mut DesignApp, ctx: &egui::Context, rect: Rect, xf: &Xf,
|
|||
greek_below_px: 3.0 * ppp,
|
||||
highlight_missing_fonts: !preview,
|
||||
quality: app.ui.display_quality,
|
||||
rich_black: app.ui.rich_black,
|
||||
..Default::default()
|
||||
};
|
||||
(st.doc.clone(), placed, w, h, view, opts)
|
||||
|
|
@ -415,6 +416,7 @@ fn patch_texture(app: &mut DesignApp, layout: &CanvasLayout, ppp: f64, doc_key:
|
|||
greek_below_px: 3.0 * ppp,
|
||||
highlight_missing_fonts: !preview,
|
||||
quality: app.ui.display_quality,
|
||||
rich_black: app.ui.rich_black,
|
||||
..Default::default()
|
||||
};
|
||||
let img = app.canvas.patcher.render(&new, &app.session.cache, &placed, (x1 - x0) as u32, (y1 - y0) as u32, view, &opts);
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ fn preferences(ui: &mut egui::Ui, d: &mut Dialog) {
|
|||
("grids", "Grids"),
|
||||
("guides", "Guides & Pasteboard"),
|
||||
("display", "Display Performance"),
|
||||
("black", "Appearance of Black"),
|
||||
]
|
||||
} else {
|
||||
&[("general", "General"), ("interface", "Interface"), ("type", "Type"), ("display", "Display Performance")]
|
||||
|
|
@ -371,6 +372,23 @@ fn preferences(ui: &mut egui::Ui, d: &mut Dialog) {
|
|||
ui.label(egui::RichText::new("Smart Text Reflow").font(semibold(12.0)));
|
||||
check(ui, d, "smartTextReflow", "Add and remove pages as the primary text frame's story grows and shrinks");
|
||||
}
|
||||
"black" => {
|
||||
ui.label(egui::RichText::new("Options for Black on RGB and Grayscale Devices").font(semibold(12.0)));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("On Screen:");
|
||||
let rich = d.b("richBlack");
|
||||
egui::ComboBox::from_id_salt("pref_black")
|
||||
.selected_text(if rich { "Display All Blacks as Rich Black" } else { "Display All Blacks Accurately" })
|
||||
.width(240.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for (v, l) in [(false, "Display All Blacks Accurately"), (true, "Display All Blacks as Rich Black")] {
|
||||
if ui.selectable_label(v == rich, l).clicked() {
|
||||
d.fields.insert("richBlack".into(), json!(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
"interface" => {
|
||||
ui.label(egui::RichText::new("UI Scaling").font(semibold(12.0)));
|
||||
ui.horizontal(|ui| {
|
||||
|
|
@ -862,6 +880,7 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> {
|
|||
_ => "view.highQualityDisplay",
|
||||
};
|
||||
app.run(view, json!({}))?;
|
||||
app.run("window.richBlack", json!({"on": d.b("richBlack")}))?;
|
||||
if let Some(v) = d.n("uiScale") {
|
||||
app.run("window.uiScale", json!({"scale": v / 100.0}))?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ pub struct UiState {
|
|||
pub display_quality: designcraft_render::DisplayQuality,
|
||||
/// Preferences › Interface › UI scaling (1 = 100%).
|
||||
pub ui_scale: f32,
|
||||
/// Preferences › Appearance of Black: 100% K on screen as rich black.
|
||||
pub rich_black: bool,
|
||||
/// Edit › Keyboard Shortcuts: command id → shortcut ("" = none), over the defaults.
|
||||
pub shortcuts: std::collections::BTreeMap<String, String>,
|
||||
pub control_bar: bool,
|
||||
|
|
@ -138,6 +140,7 @@ impl Default for UiState {
|
|||
hidden_characters: false,
|
||||
display_quality: designcraft_render::DisplayQuality::High,
|
||||
ui_scale: 1.0,
|
||||
rich_black: false,
|
||||
shortcuts: Default::default(),
|
||||
control_bar: false,
|
||||
tools_double_column: false,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
|
|||
("app.preferences", "Preferences…", Some("Cmd+K"), "{}"),
|
||||
("window.uiScale", "UI Scaling", None, "{scale: 0.5–3 (1 = 100%)} — the size of the whole interface"),
|
||||
("app.keyboardShortcuts", "Keyboard Shortcuts…", None, "{} — view and change the shortcut of any command"),
|
||||
("window.richBlack", "Appearance of Black", None, "{on: bool} — show 100% black as rich black on screen (off: accurately)"),
|
||||
("window.setShortcut", "Set Shortcut", None, "{id, shortcut: \"Cmd+Alt+J\" | \"\" (none) | null (default)} → {shortcut, conflicts: [ids]}"),
|
||||
("window.resetShortcuts", "Reset Shortcuts", None, "{} — every command back to its default shortcut"),
|
||||
("app.findChange", "Find/Change…", Some("Cmd+F"), "{}"),
|
||||
|
|
@ -630,6 +631,7 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
|
|||
designcraft_render::DisplayQuality::High => "high",
|
||||
});
|
||||
f["uiScale"] = json!((app.ui.ui_scale * 100.0).round());
|
||||
f["richBlack"] = json!(app.ui.rich_black);
|
||||
f["section"] = json!("general");
|
||||
app.ui.dialog = Some(crate::dialogs::Dialog::new("preferences", f));
|
||||
Ok(Value::Null)
|
||||
|
|
@ -746,6 +748,11 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
|
|||
app.ui.shortcuts.clear();
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"window.richBlack" => {
|
||||
app.ui.rich_black = p.get("on").and_then(Value::as_bool).unwrap_or(!app.ui.rich_black);
|
||||
app.canvas.shown = None;
|
||||
Ok(json!({"on": app.ui.rich_black}))
|
||||
}
|
||||
"window.uiScale" => {
|
||||
let v = p.get("scale").and_then(Value::as_f64).unwrap_or(1.0) as f32;
|
||||
app.ui.ui_scale = v.clamp(0.5, 3.0);
|
||||
|
|
|
|||
|
|
@ -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 | 99 | 3 | 0 | 99% |
|
||||
| P1 | 97 | 60 | 25 | 12 | 75% |
|
||||
| P1 | 97 | 60 | 26 | 11 | 75% |
|
||||
| P2 | 76 | 8 | 7 | 61 | 15% |
|
||||
|
||||
| Area | Parity |
|
||||
|
|
@ -20,7 +20,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
|||
| Layers | 95% |
|
||||
| Frames, shapes & paths | 87% |
|
||||
| Transform | 93% |
|
||||
| Fill, stroke, colour | 74% |
|
||||
| Fill, stroke, colour | 76% |
|
||||
| Effects & transparency | 50% |
|
||||
| Placing & links | 78% |
|
||||
| Type & text frames | 82% |
|
||||
|
|
@ -157,7 +157,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t
|
|||
| Fill, stroke, colour | Overprint Preview | P1 | M |
|
||||
| Fill, stroke, colour | Separations Preview | P2 | M |
|
||||
| Fill, stroke, colour | Eyedropper | P1 | D |
|
||||
| Fill, stroke, colour | Appearance of Black prefs | P1 | M |
|
||||
| Fill, stroke, colour | Appearance of Black prefs | P1 | P |
|
||||
| Effects & transparency | Opacity & blend modes | P0 | D |
|
||||
| Effects & transparency | Isolate Blending / Knockout Group | P2 | M |
|
||||
| Effects & transparency | Drop Shadow | P1 | D |
|
||||
|
|
|
|||
Loading…
Reference in New Issue