C: Appearance of Black — output rich black, Overprint [Black] at 100%

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-02 23:51:51 -04:00
co-authored by Claude Opus 5.5
parent efb563673f
commit eb2e8d8b4f
10 changed files with 65 additions and 8 deletions
+1
View File
@@ -54,6 +54,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
- Place Excel workbooks (.xlsx): the first worksheet's used range becomes a table (shared/inline strings, numbers, booleans).
- Object Library (`library.new/open/add/place/remove/list/json/close`, Library panel): `.dclib` files of snippets that keep stories, styles, swatches and images.
- Ink Manager (`ink.list`, `ink.options`; Swatches panel menu): All Spots to Process, per-ink conversion and ink aliases in PDF output; IDML ConvertToProcess/AliasInkName.
- Appearance of Black complete: Printing/Exporting rich black for RGB output (PNG), Overprint [Black] Swatch at 100% (document, IDML OverprintBlack).
- 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.
+3
View File
@@ -253,6 +253,8 @@ pub struct DocSettings {
pub advanced_type: AdvancedType,
/// Edit › Transparency Blend Space.
pub blend_space: BlendSpace,
/// Preferences › Appearance of Black: [Black] at 100% overprints (Overprint Preview, output).
pub overprint_black: bool,
}
impl Default for DocSettings {
@@ -281,6 +283,7 @@ impl Default for DocSettings {
global_light: 120.0,
advanced_type: AdvancedType::default(),
blend_space: BlendSpace::Cmyk,
overprint_black: true,
}
}
}
+2 -1
View File
@@ -19,6 +19,7 @@ const DOC_KEYS: &[&str] = &[
"bleedColor",
"slugColor",
"advancedType",
"overprintBlack",
];
pub fn specs() -> Vec<CommandSpec> {
@@ -46,7 +47,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Preferences",
[],
None,
"{showHiddenCharacters?, typographersQuotes?, polygonSides?, starInset?, scaleStrokes?, dimensionsIncludeStroke?, transformationsAreTotals?, absolutePageNumbers?, highlightHj?, highlightKeeps?, highlightCustomTracking?, highlightSubstitutedFonts?} → all application preferences",
"{showHiddenCharacters?, typographersQuotes?, polygonSides?, starInset?, scaleStrokes?, dimensionsIncludeStroke?, transformationsAreTotals?, absolutePageNumbers?, highlightHj?, highlightKeeps?, highlightCustomTracking?, highlightSubstitutedFonts?, richBlackOutput?} → all application preferences",
super::always,
|s, p| {
let cur = serde_json::to_value(&s.prefs).map_err(|e| bad("prefs.set", e.to_string()))?;
+4
View File
@@ -144,6 +144,9 @@ pub struct Prefs {
pub highlight_keeps: bool,
pub highlight_custom_tracking: bool,
pub highlight_substituted_fonts: bool,
/// Preferences › Appearance of Black › Printing / Exporting: 100% K as rich (pure) black in
/// RGB output (PNG) instead of the accurate dark grey.
pub rich_black_output: bool,
/// Preferences › Type › Smart Text Reflow: pages follow the primary text frame's story
/// (added while it oversets, empty ones at the end removed).
pub smart_text_reflow: bool,
@@ -164,6 +167,7 @@ impl Default for Prefs {
highlight_keeps: false,
highlight_custom_tracking: false,
highlight_substituted_fonts: true,
rich_black_output: false,
smart_text_reflow: true,
}
}
+1 -1
View File
@@ -659,7 +659,7 @@ impl<'a> Ex<'a> {
.attr("DocumentSlugUniformSize", bool_s(s.slug.iter().all(|b| *b == s.slug[0])))
.attr("PreserveLayoutWhenShuffling", "true")
.attr("AllowPageShuffle", "true")
.attr("OverprintBlack", "true")
.attr("OverprintBlack", bool_s(s.overprint_black))
.attr("PageBinding", "LeftToRight")
.attr("ColumnDirection", "Horizontal")
.attr(
+3
View File
@@ -394,6 +394,9 @@ impl<'r> Importer<'r> {
fn doc_prefs(&mut self, e: &El) {
let s = &mut self.settings;
if let Some(v) = e.get("OverprintBlack") {
s.overprint_black = v != "false";
}
if let Some(v) = e.num("PageWidth") {
s.page_width = v;
}
+25 -1
View File
@@ -708,7 +708,7 @@ fn set_fill_paint(ctx: &mut RenderContext, doc: &Document, fill: &designcraft_do
/// Overprint Preview: does this paint overprint (its own flag, or 100% [Black], which
/// overprints by default)? It's then multiplied over what's beneath.
pub(crate) fn overprints(f: &Frame, swatch: &str, tint: f32, flag: bool) -> bool {
f.opts.overprint_preview && (flag || (swatch == designcraft_color::swatch::BLACK && tint >= 0.999))
f.opts.overprint_preview && (flag || (f.doc.settings.overprint_black && swatch == designcraft_color::swatch::BLACK && tint >= 0.999))
}
pub fn blend_mode(b: DcBlend) -> BlendMode {
@@ -977,6 +977,30 @@ mod tests {
}
}
#[test]
fn black_overprints_only_with_the_preference() {
let mut d = Document::new(&NewDocument::default());
let lid = d.default_layer();
for (sw, r) in
[("C=100 M=0 Y=0 K=0", Rect::new(100.0, 100.0, 200.0, 200.0)), (designcraft_color::swatch::BLACK, Rect::new(150.0, 100.0, 250.0, 200.0))]
{
let id = designcraft_doc::ItemId(d.alloc());
let mut it = Item::new(id, lid, designcraft_doc::Shape::Rectangle, designcraft_geom::shapes::rectangle(r));
it.fill = Fill::swatch(sw);
d.insert_item(SpreadRef::Doc(0), it, None).unwrap();
}
let cache = Cache::new();
let mut r = Renderer::new();
r.threads = 0;
let o = RenderOptions { overprint_preview: true, ..Default::default() };
let on = r.render_page(&d, &cache, 0, 1.0, false, &o).unwrap().pixel(175, 150);
let alone = r.render_page(&d, &cache, 0, 1.0, false, &o).unwrap().pixel(225, 150);
d.settings.overprint_black = false;
let off = r.render_page(&d, &cache, 0, 1.0, false, &o).unwrap().pixel(175, 150);
assert_eq!(off, alone, "knocks out: black alone");
assert_ne!(on, alone, "overprints: the cyan shows through");
}
#[test]
fn appearance_of_black_on_screen() {
let mut d = Document::new(&NewDocument::default());
+21 -1
View File
@@ -417,6 +417,25 @@ fn preferences(ui: &mut egui::Ui, d: &mut Dialog) {
}
});
});
ui.horizontal(|ui| {
ui.label("Printing / Exporting:");
let rich = d.b("richBlackOutput");
egui::ComboBox::from_id_salt("pref_black_out")
.selected_text(if rich { "Output All Blacks as Rich Black" } else { "Output All Blacks Accurately" })
.width(240.0)
.show_ui(ui, |ui| {
for (v, l) in [(false, "Output All Blacks Accurately"), (true, "Output All Blacks as Rich Black")] {
if ui.selectable_label(v == rich, l).clicked() {
d.fields.insert("richBlackOutput".into(), json!(v));
}
}
});
});
if d.fields.contains_key("overprintBlack") {
ui.add_space(6.0);
ui.label(egui::RichText::new("[Black] Overprint").font(semibold(12.0)));
check(ui, d, "overprintBlack", "Overprint [Black] Swatch at 100%");
}
}
"interface" => {
ui.label(egui::RichText::new("UI Scaling").font(semibold(12.0)));
@@ -963,7 +982,7 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> {
"preferences" => {
app.run(
"prefs.set",
json!({"scaleStrokes": d.b("scaleStrokes"), "dimensionsIncludeStroke": d.b("dimensionsIncludeStroke"), "transformationsAreTotals": d.b("transformationsAreTotals"), "absolutePageNumbers": d.b("absolutePageNumbers"), "highlightHj": d.b("highlightHj"), "highlightKeeps": d.b("highlightKeeps"), "highlightCustomTracking": d.b("highlightCustomTracking"), "highlightSubstitutedFonts": d.b("highlightSubstitutedFonts"), "typographersQuotes": d.b("typographersQuotes"), "smartTextReflow": d.b("smartTextReflow")}),
json!({"scaleStrokes": d.b("scaleStrokes"), "dimensionsIncludeStroke": d.b("dimensionsIncludeStroke"), "transformationsAreTotals": d.b("transformationsAreTotals"), "absolutePageNumbers": d.b("absolutePageNumbers"), "highlightHj": d.b("highlightHj"), "highlightKeeps": d.b("highlightKeeps"), "highlightCustomTracking": d.b("highlightCustomTracking"), "highlightSubstitutedFonts": d.b("highlightSubstitutedFonts"), "richBlackOutput": d.b("richBlackOutput"), "typographersQuotes": d.b("typographersQuotes"), "smartTextReflow": d.b("smartTextReflow")}),
)?;
// Highlight options change the screen view only.
app.canvas.shown = None;
@@ -1020,6 +1039,7 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> {
}
}
doc["advancedType"] = adv;
doc["overprintBlack"] = json!(d.b("overprintBlack"));
app.run("document.preferences", doc)
}
"qrCode" => {
+2 -1
View File
@@ -671,6 +671,7 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
if app.session.active().is_some() {
let doc = app.session.execute("document.preferences", &json!({})).unwrap_or_default();
f["horizontalUnits"] = doc["horizontalUnits"].clone();
f["overprintBlack"] = doc["overprintBlack"].clone();
for k in ["superscriptSize", "superscriptPosition", "subscriptSize", "subscriptPosition"] {
f[format!("adv.{k}")] = json!(format!("{}", doc["advancedType"][k].as_f64().unwrap_or(0.0)));
}
@@ -920,7 +921,7 @@ fn export_png(app: &mut DesignApp, p: &Value) -> Result<Value, String> {
page,
scale,
false,
&designcraft_render::RenderOptions { printing_only: true, ..Default::default() },
&designcraft_render::RenderOptions { printing_only: true, rich_black: app.session.prefs.rich_black_output, ..Default::default() },
)
.ok_or("no such page")?;
let png = img.to_png();
+3 -3
View File
@@ -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 | 75 | 21 | 1 | 88% |
| P1 | 97 | 76 | 20 | 1 | 89% |
| P2 | 76 | 23 | 13 | 40 | 39% |
| Area | Parity |
@@ -20,7 +20,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
| Layers | 95% |
| Frames, shapes & paths | 93% |
| Transform | 100% |
| Fill, stroke, colour | 90% |
| Fill, stroke, colour | 93% |
| Effects & transparency | 71% |
| Placing & links | 82% |
| Type & text frames | 92% |
@@ -157,7 +157,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t
| Fill, stroke, colour | Overprint Preview | P1 | D |
| Fill, stroke, colour | Separations Preview | P2 | M |
| Fill, stroke, colour | Eyedropper | P1 | D |
| Fill, stroke, colour | Appearance of Black prefs | P1 | P |
| Fill, stroke, colour | Appearance of Black prefs | P1 | D |
| Effects & transparency | Opacity & blend modes | P0 | D |
| Effects & transparency | Isolate Blending / Knockout Group | P2 | P |
| Effects & transparency | Drop Shadow | P1 | D |