mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 21:11:52 +00:00
Trim marks at the trim box's corners, dashed bleed marks at the bleed box, registration targets, a colour bar and a page-information line, drawn outside the trim box as a removable page mark (MarkKind::PrinterMarks); the page is widened to make room when asked and the trim box stays, so the printed page cuts the same. Edit::AddPrinterMarks (scope Full, undoable) and Remove printer marks through Edit::RemoveMarks; Dialog::PrinterMarks reachable from the catalog, the palette and --view printer-marks; tools doc_printer_marks and doc_remove_marks kind printer_marks; rows in every language; parity print.printer-marks shipped. Carries the review fixes: - ATTRIBUTION.toml: the language files' checksums after the new rows - Printer marks: drop the unreachable AddPrinterMarks pattern in scope_of - Tests: printer marks read the document's marks through upstream's marks() (#1434) - Printer marks: make room inside the crop box and keep the trim box on rerun - Format with rustfmt - Printer marks: keep accented labels and make room for the bleed marks - Printer marks test: the page is letter size, so the bleed marks (33 pt beyond the trim box) fit - i18n(ui): carry this PR's new wordings in the catalogues that must cover them - Parity: evidence paths follow the tests moved into tests/it - Fix the build after merging upstream/main - fix(printer-marks): refuse numbers out of range, print on every plate Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
5.3 KiB
Rust
109 lines
5.3 KiB
Rust
//! Printer marks and the page boxes: the marks must show inside the crop box, and running
|
|
//! again must not move the trim box or grow the page.
|
|
|
|
use pdfcraft_cos::{Dict, Document, Object};
|
|
use pdfcraft_edit::{PrinterMarks, add_printer_marks};
|
|
|
|
fn rect(r: [f64; 4]) -> Object {
|
|
Object::Array(r.iter().map(|v| Object::Real(*v)).collect())
|
|
}
|
|
|
|
/// One empty page with these boxes set on it.
|
|
fn page_with(boxes: &[(&[u8], [f64; 4])]) -> Document {
|
|
let mut doc = Document::new_empty();
|
|
let pages = doc.get(doc.root().unwrap()).as_dict().unwrap().reference(b"Pages").unwrap();
|
|
let mut page = Dict::new();
|
|
page.set(b"Type".to_vec(), Object::name("Page"));
|
|
page.set(b"Parent".to_vec(), Object::Ref(pages));
|
|
for (key, r) in boxes {
|
|
page.set(key.to_vec(), rect(*r));
|
|
}
|
|
let page = doc.add(page);
|
|
doc.update_dict(pages, |d| {
|
|
d.set(b"Kids".to_vec(), Object::Array(vec![Object::Ref(page)]));
|
|
d.set(b"Count".to_vec(), Object::Int(1));
|
|
})
|
|
.unwrap();
|
|
doc
|
|
}
|
|
|
|
fn page_box(doc: &Document, key: &[u8]) -> Option<Vec<f64>> {
|
|
let page = pdfcraft_model::pages(doc).first().cloned()?;
|
|
let a = page.dict.get(key).map(|o| doc.resolve(o))?;
|
|
Some(a.as_array()?.iter().filter_map(|v| doc.resolve(v).as_f64()).collect())
|
|
}
|
|
|
|
/// A crop box cut to the trim inside a large media box: the marks fit the media box but
|
|
/// not the crop box, so the crop box grows to show them and the trim box stays.
|
|
#[test]
|
|
fn printer_marks_widen_crop_inside_media() {
|
|
let mut doc = page_with(&[(b"MediaBox", [0.0, 0.0, 1000.0, 1000.0]), (b"CropBox", [200.0, 200.0, 800.0, 800.0])]);
|
|
let marks = PrinterMarks::default();
|
|
let reach = marks.reach();
|
|
let widened = add_printer_marks(&mut doc, &[0], &marks, false).unwrap();
|
|
assert_eq!(widened, vec![0]);
|
|
assert_eq!(page_box(&doc, b"MediaBox"), Some(vec![0.0, 0.0, 1000.0, 1000.0]), "the media box already had room");
|
|
let crop = page_box(&doc, b"CropBox").unwrap();
|
|
assert_eq!(crop, vec![200.0 - reach, 200.0 - reach, 800.0 + reach, 800.0 + reach]);
|
|
assert_eq!(page_box(&doc, b"TrimBox"), Some(vec![200.0, 200.0, 800.0, 800.0]));
|
|
}
|
|
|
|
/// No trim box given: it defaults to the crop box, which widening moves. Running again with
|
|
/// replace must keep the trim box and the page size from the first run.
|
|
#[test]
|
|
fn printer_marks_rerun_keeps_trim() {
|
|
let mut doc = page_with(&[(b"MediaBox", [0.0, 0.0, 612.0, 792.0])]);
|
|
let marks = PrinterMarks::default();
|
|
add_printer_marks(&mut doc, &[0], &marks, false).unwrap();
|
|
let media = page_box(&doc, b"MediaBox");
|
|
let crop = page_box(&doc, b"CropBox");
|
|
assert_eq!(page_box(&doc, b"TrimBox"), Some(vec![0.0, 0.0, 612.0, 792.0]));
|
|
assert_eq!(page_box(&doc, b"BleedBox"), Some(vec![0.0, 0.0, 612.0, 792.0]));
|
|
let widened = add_printer_marks(&mut doc, &[0], &marks, true).unwrap();
|
|
assert!(widened.is_empty(), "the page already has room");
|
|
assert_eq!(page_box(&doc, b"MediaBox"), media, "the page did not grow again");
|
|
assert_eq!(page_box(&doc, b"CropBox"), crop);
|
|
assert_eq!(page_box(&doc, b"TrimBox"), Some(vec![0.0, 0.0, 612.0, 792.0]));
|
|
}
|
|
|
|
/// A trim box past the media box is clipped to it. Widening the media box must not let it
|
|
/// grow to what was written.
|
|
#[test]
|
|
fn printer_marks_keep_the_trim_box_in_effect() {
|
|
let mut doc = page_with(&[(b"MediaBox", [0.0, 0.0, 100.0, 100.0]), (b"TrimBox", [-20.0, -20.0, 120.0, 120.0])]);
|
|
add_printer_marks(&mut doc, &[0], &PrinterMarks::default(), false).unwrap();
|
|
assert_eq!(page_box(&doc, b"TrimBox"), Some(vec![0.0, 0.0, 100.0, 100.0]));
|
|
}
|
|
|
|
/// Line weights and room out of range are refused, not clamped, and nothing past the largest
|
|
/// page a PDF can have is written.
|
|
#[test]
|
|
fn printer_marks_refuse_numbers_out_of_range() {
|
|
let mut doc = page_with(&[(b"MediaBox", [0.0, 0.0, 612.0, 792.0])]);
|
|
for line_weight in [-1.0, 0.0, 0.05, 5.0, 10_000.0, f64::NAN, f64::INFINITY] {
|
|
assert!(add_printer_marks(&mut doc, &[0], &PrinterMarks { line_weight, ..PrinterMarks::default() }, false).is_err(), "{line_weight}");
|
|
}
|
|
for room in [-1.0, 289.0, 1e9, f64::NAN, f64::INFINITY] {
|
|
assert!(add_printer_marks(&mut doc, &[0], &PrinterMarks { make_room: Some(room), ..PrinterMarks::default() }, false).is_err(), "{room}");
|
|
}
|
|
let mut big = page_with(&[(b"MediaBox", [0.0, 0.0, 14_400.0, 1000.0])]);
|
|
assert!(add_printer_marks(&mut big, &[0], &PrinterMarks::default(), false).is_err());
|
|
assert!(add_printer_marks(&mut big, &[0], &PrinterMarks { make_room: None, ..PrinterMarks::default() }, false).is_ok(), "left as it is");
|
|
}
|
|
|
|
/// Registration colour is the /All Separation, in the page's resources beside what it had.
|
|
#[test]
|
|
fn printer_marks_use_the_all_separation() {
|
|
let mut doc = page_with(&[(b"MediaBox", [0.0, 0.0, 612.0, 792.0])]);
|
|
add_printer_marks(&mut doc, &[0], &PrinterMarks::default(), false).unwrap();
|
|
let page = pdfcraft_model::pages(&doc).swap_remove(0);
|
|
let res = doc.resolve(page.dict.get(b"Resources").unwrap());
|
|
let res = res.as_dict().unwrap();
|
|
assert!(res.get(b"Font").is_some());
|
|
let spaces = doc.resolve(res.get(b"ColorSpace").unwrap());
|
|
let cs = doc.resolve(spaces.as_dict().unwrap().get(b"PCReg").unwrap());
|
|
let cs = cs.as_array().unwrap();
|
|
assert_eq!(cs[..3], [Object::name("Separation"), Object::name("All"), Object::name("DeviceCMYK")]);
|
|
assert!(cs[3].as_dict().unwrap().int(b"FunctionType") == Some(2));
|
|
}
|