Files
figcraft/apps/figcraft-cli/tests/pdf_print.rs
BFlatandClaude Opus 5.5 fe550fc14a M6: PDF print options (bleed, marks, CMYK) (#92)
PDF export gains print options, saved on PDF export settings and taken
by file.export, file.exportLayers, file.exportFramesPdf, export.add /
export.update, figcraft-cli export and the MCP export tool:

- a bleed (points or a length like 3mm / 0.125in, up to 2 in) that grows
  each page and shows what lies past the frame: its fills reach out to
  the bleed edge and its children are clipped there instead; a print
  page of one frame is trimmed to the frame's box;
- crop and bleed marks: 0.25 pt hairlines outside the bleed in
  registration colour;
- MediaBox / BleedBox / TrimBox;
- CMYK colour: fills, strokes, text, gradient stops, images and
  rasterised effects converted from sRGB with the naive, deterministic
  K = 1 - max(R, G, B) formula; DeviceCMYK without an output intent (no
  CMYK ICC profile can ship in the repository), soft masks stay RGB.

The Design panel shows a PDF row's print settings as a summary line that
opens them; the Export dialog has print options for its PDF preset.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:50:29 +07:00

40 lines
2.2 KiB
Rust

//! `figcraft-cli export` with PDF print options: bleed, marks, CMYK; bad lengths are usage errors.
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::process::Command;
fn bin() -> Command {
Command::new(env!("CARGO_BIN_EXE_figcraft-cli"))
}
#[test]
fn pdf_export_with_bleed_marks_and_cmyk() {
let d = std::env::temp_dir().join(format!("figcraft-cli-print-{}", std::process::id()));
std::fs::create_dir_all(&d).unwrap();
let script = d.join("s.jsonl");
std::fs::write(&script, r##"{"command":"frame.create","params":{"x":0,"y":0,"width":100,"height":50,"name":"A","props":{"fill":"#ff0000"}}}"##)
.unwrap();
let file = d.join("doc.figcraft");
assert!(bin().arg("run").arg(&script).arg("--out").arg(&file).status().unwrap().success());
let pdf = d.join("print.pdf");
let out = bin().arg("export").arg(&file).arg("-o").arg(&pdf).args(["--bleed", "0.125in", "--crop-marks", "--cmyk"]).output().unwrap();
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
let s = String::from_utf8_lossy(&std::fs::read(&pdf).unwrap()).into_owned();
// 9 pt of bleed, then 3 + 18 + 3 pt for the marks.
assert!(s.contains("/MediaBox[0 0 166 116]") && s.contains("/BleedBox[24 24 142 92]") && s.contains("/TrimBox[33 33 133 83]"), "{s}");
// Without options: the frame's size, no boxes.
let plain = d.join("plain.pdf");
assert!(bin().arg("export").arg(&file).arg("-o").arg(&plain).status().unwrap().success());
let s = String::from_utf8_lossy(&std::fs::read(&plain).unwrap()).into_owned();
assert!(s.contains("/MediaBox[0 0 100 50]") && !s.contains("/TrimBox"), "{s}");
// A directory of PDFs gets them too.
let dir = d.join("out");
assert!(bin().arg("export").arg(&file).arg("-o").arg(&dir).args(["--format", "pdf", "--bleed", "3mm"]).status().unwrap().success());
assert!(String::from_utf8_lossy(&std::fs::read(dir.join("A.pdf")).unwrap()).contains("/TrimBox["));
// A bleed that isn't a length is a usage error.
let out = bin().arg("export").arg(&file).arg("-o").arg(&pdf).args(["--bleed", "lots"]).output().unwrap();
assert_eq!(out.status.code(), Some(2), "{}", String::from_utf8_lossy(&out.stderr));
let _ = std::fs::remove_dir_all(&d);
}