mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-29 12:46:53 -06:00
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:
co-authored by
Claude Opus 5.5
parent
efb563673f
commit
eb2e8d8b4f
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()))?;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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" => {
|
||||
|
||||
@@ -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
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user