mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-10 03:02:19 +00:00
fix(print): refuse fitting that would discard annotations
This commit is contained in:
@@ -61,6 +61,10 @@ a driver paper-size mismatch. On Windows `command_preview` describes the in-proc
|
||||
(`xps-print`, then the printer and options as `key=value`) instead of a command line. The crate
|
||||
does not provide PostScript output, separations, trapping or colour-managed device rendering.
|
||||
|
||||
Windows shrink-to-fit refuses pages with annotations when their geometry needs fitting.
|
||||
Callers must flatten annotation appearances into page content first. Exact-size printing and
|
||||
rotation retain the annotations. This avoids losing printed comments, forms or other marks.
|
||||
|
||||
`cargo test -p craft-print` tests CUPS transport (including pipe-sized jobs and refusal),
|
||||
printer parsing, PPD options and argument mapping, and on every platform the Windows pieces that
|
||||
need no printer: the PrintTicket, the checks on the driver's validated ticket and capabilities,
|
||||
|
||||
@@ -1068,6 +1068,15 @@ impl Sheets<'_> {
|
||||
if get(&self.trailer, b"Encrypt").is_some() {
|
||||
return Err(refuse("it is encrypted"));
|
||||
}
|
||||
// Annotation appearances live outside /Contents. Transforming only content
|
||||
// would leave them at the old coordinates; deleting them loses printed marks.
|
||||
// Require callers to flatten before fitting until appearances can be reframed.
|
||||
if let Some(annotations) = get(&dict, b"Annots")
|
||||
&& !matches!(annotations, Obj::Null)
|
||||
&& !matches!(annotations, Obj::Array(items) if items.is_empty())
|
||||
{
|
||||
return Err(refuse("flatten page annotations before using shrink-to-fit"));
|
||||
}
|
||||
let [a, b, c, d, e, f] = frame.matrix;
|
||||
let [x, y, w, h] = frame.clip;
|
||||
let prefix = format!(
|
||||
|
||||
@@ -49,6 +49,10 @@ const TABLOID: TestPage = TestPage { media: Some([0.0, 0.0, 792.0, 1224.0]), cro
|
||||
/// cross-reference stream, as modern writers save. The root page node sets a Letter MediaBox
|
||||
/// for pages that inherit it.
|
||||
fn test_pdf(pages: &[TestPage], compressed: bool) -> Vec<u8> {
|
||||
test_pdf_with_annotations(pages, compressed, None)
|
||||
}
|
||||
|
||||
fn test_pdf_with_annotations(pages: &[TestPage], compressed: bool, annotations: Option<&str>) -> Vec<u8> {
|
||||
let n = pages.len();
|
||||
// 1 catalog, 2 page tree, 3 content, then the pages.
|
||||
let kids: Vec<String> = (0..n).map(|i| format!("{} 0 R", 4 + i)).collect();
|
||||
@@ -69,6 +73,9 @@ fn test_pdf(pages: &[TestPage], compressed: bool) -> Vec<u8> {
|
||||
if let Some(r) = p.rotate {
|
||||
page += &format!(" /Rotate {r}");
|
||||
}
|
||||
if let Some(annotations) = annotations {
|
||||
page += &format!(" /Annots {annotations}");
|
||||
}
|
||||
objects.push(page + " >>");
|
||||
}
|
||||
let mut pdf = b"%PDF-1.7\n%\xe2\xe3\xcf\xd3\n".to_vec();
|
||||
@@ -264,6 +271,27 @@ fn shrink_to_fit_reframes_sheets_on_the_paper() {
|
||||
assert_eq!(shapes(&sheets.update(&edits).unwrap()), [((612, 792), 0), ((612, 792), 270), ((612, 792), 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shrink_to_fit_refuses_to_drop_annotation_appearances() {
|
||||
use crate::sheets::{Fit, plan, read};
|
||||
for annotations in ["[<< /Subtype /Text /Rect [36 36 72 72] /F 4 >>]", "42 0 R"] {
|
||||
let pdf = test_pdf_with_annotations(&[LETTER, LETTER_LANDSCAPE], false, Some(annotations));
|
||||
let sheets = read(&pdf).unwrap();
|
||||
let fit = plan(&sheets.sheets, (612.0, 792.0), Fit::Shrink { margin: 18.0 }).unwrap();
|
||||
assert!(matches!(sheets.update(&fit), Err(Error::Spool(ref m)) if m.contains("flatten page annotations")));
|
||||
let exact = plan(&sheets.sheets, (612.0, 792.0), Fit::Exact).unwrap();
|
||||
let rotated = sheets.update(&exact).unwrap();
|
||||
assert!(rotated.starts_with(&pdf));
|
||||
assert!(String::from_utf8_lossy(&rotated[pdf.len()..]).contains("/Annots"), "rotation retains annotations on the updated page");
|
||||
}
|
||||
for annotations in ["[]", "null"] {
|
||||
let pdf = test_pdf_with_annotations(&[LETTER, LETTER_LANDSCAPE], false, Some(annotations));
|
||||
let sheets = read(&pdf).unwrap();
|
||||
let fit = plan(&sheets.sheets, (612.0, 792.0), Fit::Shrink { margin: 18.0 }).unwrap();
|
||||
assert!(sheets.update(&fit).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_encrypted_file_is_turned_but_not_reframed() {
|
||||
// An /Encrypt entry is all it takes: new content streams would have to be encrypted too.
|
||||
|
||||
Reference in New Issue
Block a user