M4.78: File > Print opens the Print dialog and prints through the system

The Print dialog (Ctrl/Cmd+P) edits the document's print settings in the
sections General, Marks and Bleed, Output, Graphics, Color Management,
Advanced and Summary, with a preview of each printed page under the section
list (drag the art to move its placement). It drives print.setup,
print.preview and file.print; the preview pages now carry their transform,
printed area and trim box. Print sends the job's PDF to the chosen printer
through the new PrintService (lp on macOS/Linux, the shell print verb on
Windows, a hidden frame and the browser's print dialog on the web); without
one, or with PDF File, it saves the PDF. Done only keeps the settings.
UI commands file.print, print.printers and print.printerSetup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-05 16:30:35 +07:00
co-authored by Claude Opus 5.5
parent f1f7b1f945
commit ffce1cf686
20 changed files with 1441 additions and 60 deletions
+1 -1
View File
@@ -239,7 +239,7 @@ fn export_pdf(s: &mut Session, p: &Value) -> Result<Value> {
}
/// `(path, value)` for every leaf of `v` that differs from `default` (`compression.color.ppi`).
fn changed(prefix: &str, v: &Value, default: &Value, out: &mut Vec<Value>) {
pub fn changed(prefix: &str, v: &Value, default: &Value, out: &mut Vec<Value>) {
match (v, default) {
(Value::Object(o), Value::Object(d)) => {
for (k, x) in o {
+2 -2
View File
@@ -28,7 +28,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Print Preview",
[],
None,
"{settings?: {…print.setup settings} (over the document's)} → {pages (copies included), sheets: [{artboard (0-based; null with artboards ignored), tile? (1-based), ink? (separations), width, height (pt, the page), orientation, scale: [width %, height %]}] (one copy, in order), tiles: [{artboard, columns, rows, printed: [1-based tiles], tiles: [[x0, y0, x1, y1]…] (document space)}], inks: [{name, spot, print, frequency, angle}] (separations), warnings (art larger than the imageable area, options not applied yet…), settings} without printing",
"{settings?: {…print.setup settings} (over the document's)} → {pages (copies included), sheets: [{artboard (0-based; null with artboards ignored), tile? (1-based), ink? (separations), width, height (pt, the page), orientation, scale: [width %, height %], transform: [a, b, c, d, e, f] (document space → the page, pt, y down), area: [x0, y0, x1, y1] (the document region the page prints), trim: [x0, y0, x1, y1] (the artboard on the page)}] (one copy, in order), tiles: [{artboard, columns, rows, printed: [1-based tiles], tiles: [[x0, y0, x1, y1]…] (document space)}], inks: [{name, spot, print, frequency, angle}] (separations), warnings (art larger than the imageable area, options not applied yet…), settings} without printing",
has_doc,
preview
),
@@ -36,7 +36,7 @@ pub fn specs() -> Vec<CommandSpec> {
"file.print",
"Print",
[],
None,
Some("Cmd+P"),
"{settings?: {…print.setup settings} (over the document's), path?} print the document as a print-ready PDF, one page per sheet of paper (per tile, per ink, per copy) → {pages, path, bytes, warnings}; no path → {dataBase64, bytes, pages, warnings}. Raster effects print at the document's raster effects resolution; halftone screens and flatness are left to the output device",
has_doc,
print
+2
View File
@@ -1210,6 +1210,8 @@ mod tests_previewbounds;
#[cfg(test)]
mod tests_print;
#[cfg(test)]
mod tests_printpreview;
#[cfg(test)]
mod tests_proxyitems;
#[cfg(test)]
mod tests_rastersettings;
+53
View File
@@ -0,0 +1,53 @@
//! `print.preview`'s pages say where the art lands (what the Print dialog's preview draws): the
//! transform from the document onto the page, the region each page prints and its trim box.
use serde_json::{Value, json};
use super::*;
/// `print.preview {settings}`'s first page.
fn first_sheet(s: &mut Session, settings: Value) -> Value {
s.execute("print.preview", &json!({ "settings": settings })).unwrap()["sheets"][0].clone()
}
/// `[a, b, c, d, e, f]` applied to (x, y).
fn apply(t: &Value, (x, y): (f64, f64)) -> (f64, f64) {
let n = |i: usize| t[i].as_f64().unwrap();
(n(0) * x + n(2) * y + n(4), n(1) * x + n(3) * y + n(5))
}
fn close(a: (f64, f64), b: (f64, f64)) -> bool {
(a.0 - b.0).abs() < 1e-6 && (a.1 - b.1).abs() < 1e-6
}
#[test]
fn pages_place_the_artboard_on_the_trim_box() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
s.execute("document.setup", &json!({"bleed": 9})).unwrap();
// Centred on Letter, turned landscape for the wide artboard.
let p = first_sheet(&mut s, json!({}));
assert_eq!((p["width"].clone(), p["height"].clone()), (json!(792.0), json!(612.0)));
assert_eq!(p["trim"], json!([246.0, 206.0, 546.0, 406.0]));
assert!(close(apply(&p["transform"], (0.0, 0.0)), (246.0, 206.0)) && close(apply(&p["transform"], (300.0, 200.0)), (546.0, 406.0)));
assert_eq!(p["area"], json!([-9.0, -9.0, 309.0, 209.0]), "the artboard with the document's bleed");
// Flipped, the artboard's top-left corner lands on the trim box's bottom-right one.
let p = first_sheet(
&mut s,
json!({"autoRotate": false, "orientation": "landscapeFlipped", "scaling": "custom", "scale": {"width": 50, "height": 50}}),
);
let t = &p["trim"];
assert!(close(apply(&p["transform"], (0.0, 0.0)), (t[2].as_f64().unwrap(), t[3].as_f64().unwrap())), "{p}");
assert_eq!(p["scale"], json!([50.0, 50.0]));
// A tile prints its part of the artboard.
let v = s.execute("print.preview", &json!({"settings": {"scaling": "tileFull", "scale": {"width": 400, "height": 400}}})).unwrap();
let tiles = v["tiles"][0]["tiles"].as_array().unwrap();
for (sheet, tile) in v["sheets"].as_array().unwrap().iter().zip(tiles) {
let (a, t) = (&sheet["area"], tile);
for i in 0..4 {
let inside =
if i < 2 { a[i].as_f64().unwrap() >= t[i].as_f64().unwrap() - 1e-6 } else { a[i].as_f64().unwrap() <= t[i].as_f64().unwrap() + 1e-6 };
assert!(inside, "{a} within {t}");
}
}
}