Print color: File > Print prints the PDF Export PDF writes

file.print builds its PDF with the same pipeline as file.exportPdf
(flattener, placed PDFs with hidden layers and soft effects as images,
print color, view options), accepts Export PDF's settings and returns its
warnings, instead of calling designcraft_pdf::export_pdf with the
hard-edged vector drop shadow.
This commit is contained in:
aiongg committed 2026-10-20 13:12:15 +00:00
1 parent c51ff78d2b
commit 30c0664142
4 files changed
+56 -21

No files matched your search

+2 -1
View File
@@ -144,7 +144,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
- 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).
- File › Print (⌘P): printer, copies, page range, spreads, marks, bleed; printed through the system spooler as the PDF File › Export PDF writes with the same settings (effects, flattener, print color, warnings) (`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).
- Nested styles (through/up to N sentences, words, characters, letters, digits, tabs, breaks, spaces or given characters) and GREP styles (regex → character style), edited in Paragraph Style Options and composed under local formatting; `ui.dialog.open` for agents. Nested line styles are still to come; nested and GREP styles round-trip through IDML.
- Table and cell styles (round-trip through IDML): cell styles (fill, insets, vertical justification, strokes, paragraph style) and table styles (region cell styles, border, alternating rows, spacing); apply from the Table panel, edits re-apply to every user; `style.cell.*`, `style.table.*`.
- Style groups: paragraph and character styles shown in group folders, Move to Group (new, existing, none); `style.group` renames every use (styles, stories, table cells, footnotes, object styles).
@@ -348,6 +348,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
Newest first.
- **2026-10-20** · Print color (craft-libs#190): CMYK print PDFs blend in DeviceCMYK and carry the CMYK working space as their output intent through craft-pdf's print settings instead of byte rewrites; overprint (Overprint Fill, Stroke and Gap, overprinting text, 100% [Black]) is written as `/op` `/OP` `/OPM 1`; Knockout Group writes `/K true` instead of becoming an image; File › Print prints the PDF Export PDF writes. No readiness percentage change.
- **2026-10-16** · Type › Reload Fonts (`font.reload`): fonts installed, edited or removed while DesignCraft runs are read again without a restart, and the open documents recompose with them (#328, #547). An edited font file is noticed by its length and modification time; bundled, added and document fonts stay as they are. InDesign does this by itself, so noticing changes automatically is still open (gap 6). No readiness percentage change.
- **2026-10-16** · Parents can be deleted from their Pages-panel context menu or `layout.parents.delete {id}`. Applied-page and based-on references are cleared, local override copies survive, and Undo restores the parent. Addresses the deletion portion of #956; no readiness percentage change.
- **2026-10-17** · File › Package writes the document's and the IDML's links relative to the package (`Links/logo.png`, `file:Links/logo.png`), so a package holds no path from the machine that made it; opening a document resolves them to the files beside it. Links › Copy Link(s) To still links by full path. (#340). No readiness percentage change.
+15 -7
View File
@@ -706,6 +706,20 @@ fn first_page_ref(pdf: &[u8]) -> Option<usize> {
}
fn export_pdf(s: &mut Session, p: &Value) -> Result<Value> {
let r = pdf_export(s, p)?;
match str_param(p, "path") {
Some(path) => {
write_file(path, &r.bytes)?;
Ok(json!({"path": path, "bytes": r.bytes.len(), "pages": r.pages, "warnings": r.warnings}))
}
None => Ok(json!({"base64": super::base64_encode(&r.bytes), "bytes": r.bytes.len(), "pages": r.pages, "warnings": r.warnings})),
}
}
/// The PDF that File › Export PDF writes for `p`, with its warnings: the flattener, placed PDFs
/// with hidden layers and soft effects as images, the PDF itself and its view options, timings
/// and transitions. File › Print prints it.
pub(crate) fn pdf_export(s: &Session, p: &Value) -> Result<designcraft_pdf::ExportReport> {
let st = s.doc()?;
let opts = options(p, st.doc.page_count())?;
// Transparency Flattener presets: High / Medium / Low Resolution, or a ppi.
@@ -803,13 +817,7 @@ fn export_pdf(s: &mut Session, p: &Value) -> Result<Value> {
{
r.warnings.push(format!("transparency flattened: {n} region(s) rasterised"));
}
match str_param(p, "path") {
Some(path) => {
write_file(path, &r.bytes)?;
Ok(json!({"path": path, "bytes": r.bytes.len(), "pages": r.pages, "warnings": r.warnings}))
}
None => Ok(json!({"base64": super::base64_encode(&r.bytes), "bytes": r.bytes.len(), "pages": r.pages, "warnings": r.warnings})),
}
Ok(r)
}
#[cfg(not(target_arch = "wasm32"))]
+38 -12
View File
@@ -5,8 +5,6 @@ use serde_json::{Value, json};
#[cfg(not(target_arch = "wasm32"))]
use super::str_param;
use super::{CommandSpec, bad, cmd, has_doc};
#[cfg(not(target_arch = "wasm32"))]
use crate::EngineError;
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
@@ -17,7 +15,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Print…",
["File"],
Some("Cmd+P"),
"{printer?, copies?: 1, pages?: \"1-3,5\", spreads?, marks?, bleed?, dryRun?: bool (export and preview submission without printing)} → {printer, copies, pages, command, bytes}",
"{printer?, copies?: 1, pages?: \"1-3,5\", dryRun?: bool (export and preview submission without printing), …the PDF settings of file.exportPdf (the PDF it writes is what prints)} → {printer, copies, pages, command, bytes, warnings}",
has_doc,
print
),
@@ -58,12 +56,14 @@ fn print_with(
..craft_print::Job::default()
};
let dry = p.get("dryRun").and_then(Value::as_bool).unwrap_or(false);
let pdf = designcraft_pdf::export_pdf(&super::interchange::with_pdf_boxes(d), &s.cache, &opts).map_err(|e| EngineError::Other(e.to_string()))?;
let command = craft_print::command_preview(&job).map_err(|e| bad("file.print", e.to_string()))?;
let pages = opts.pages.as_ref().map_or(d.page_count(), |v| v.len());
let out = json!({"printer": job.printer, "copies": copies, "pages": pages, "command": command, "bytes": pdf.len()});
// The PDF that Export PDF writes with these settings.
let pdf = super::export::pdf_export(s, p)?;
let command = craft_print::command_preview(&job).map_err(|e| bad("file.print", e.to_string()))?;
let out =
json!({"printer": job.printer, "copies": copies, "pages": pages, "command": command, "bytes": pdf.bytes.len(), "warnings": pdf.warnings});
if !dry {
submit(&pdf, &job).map_err(|e| bad("file.print", e.to_string()))?;
submit(&pdf.bytes, &job).map_err(|e| bad("file.print", e.to_string()))?;
}
Ok(out)
}
@@ -96,11 +96,7 @@ mod tests {
let mut s = Session::new();
s.execute("file.new", &json!({"pages": 4})).unwrap();
let params = json!({"printer": "Office (east)", "copies": 2, "pages": "2-3", "marks": true, "bleed": true});
let expected = {
let d = &s.doc().unwrap().doc;
let options = super::super::export::options(&params, d.page_count()).unwrap();
designcraft_pdf::export_pdf(&super::super::interchange::with_pdf_boxes(d), &s.cache, &options).unwrap()
};
let expected = super::super::export::pdf_export(&s, &params).unwrap().bytes;
let r = print_with(&mut s, &params, |pdf, job| {
// The production export options and full bytes reach the shared spooler boundary.
assert_eq!(pdf, expected);
@@ -119,4 +115,34 @@ mod tests {
print_with(&mut s, &json!({"dryRun": true}), |_, _| panic!("dry runs must not spool")).unwrap();
assert!(print_with(&mut s, &json!({"pages": "9"}), |_, _| panic!("invalid ranges must not spool")).is_err());
}
/// File › Print spools the PDF that File › Export PDF writes with the same settings: soft
/// effects as images, CMYK print color, the same warnings.
#[test]
fn print_spools_what_export_pdf_writes() {
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
let id = s.execute("frame.create", &json!({"rect": [72, 72, 200, 200]})).unwrap()["id"].clone();
s.execute("object.fill", &json!({"ids": [id], "swatch": "C=100 M=0 Y=0 K=0"})).unwrap();
s.execute("object.dropShadow", &json!({"ids": [id], "on": true, "distance": 8, "size": 6})).unwrap();
let params = json!({"standard": "x4"});
// The creation date is in whole seconds: an attempt can straddle a second boundary.
let mut matched = false;
for _ in 0..3 {
let exported = s.execute("file.exportPdf", &params).unwrap();
let mut printed = Vec::new();
let r = print_with(&mut s, &params, |pdf, _| {
printed = pdf.to_vec();
Ok("request id is Office-1".into())
})
.unwrap();
assert_eq!(r["warnings"], exported["warnings"]);
assert!(r["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("soft effects")), "{r}");
if printed == super::super::file::base64_decode(exported["base64"].as_str().unwrap()) {
matched = true;
break;
}
}
assert!(matched, "File › Print spools Export PDF's bytes");
}
}
+1 -1
View File
@@ -407,7 +407,7 @@ It is evidence of breadth: each item exists. It is not a claim of InDesign-level
- Preferences › Appearance of Black: 100% K on screen as rich black (InDesign's default) or accurately as dark grey (`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. A click selects a page (handles, its liquid page rule tagged above it); dragging a handle previews the liquid rule and springs back, Alt-drag resizes, Shift keeps proportions, with a W/H readout; `anchor` keeps a reference point in place; double-click for exact sizes. With the Page tool the Control panel shows the page (reference point, W/H, preset, orientation, Liquid Page Rule including Controlled by Parent, Show Parent Page Overlay); X/Y and Objects Move with Page are shown, but pages don't move within their spread yet. On an object-based page it selects objects and shows their pins and width/height locks on the canvas (click to toggle, with tooltips).
- 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).
- File › Print (⌘P): printer, copies, page range, spreads, marks, bleed; printed through the system spooler as the PDF File › Export PDF writes with the same settings (effects, flattener, print color, warnings) (`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).
- Nested styles (through/up to N sentences, words, characters, letters, digits, tabs, breaks, spaces or given characters) and GREP styles (regex → character style), edited in Paragraph Style Options and composed under local formatting; `ui.dialog.open` for agents. Nested line styles are still to come; nested and GREP styles round-trip through IDML.
- Table and cell styles (round-trip through IDML): cell styles (fill, insets, vertical justification, strokes, paragraph style) and table styles (region cell styles, border, alternating rows, spacing); apply from the Table panel, edits re-apply to every user; `style.cell.*`, `style.table.*`.
- Style groups: paragraph and character styles shown in group folders, Move to Group (new, existing, none); `style.group` renames every use (styles, stories, table cells, footnotes, object styles).