M10: comments and fields without an appearance print as the page shows them (#6367)

This commit is contained in:
mtlcl410 authored and GitHub committed 2026-11-09 08:56:16 +08:00
1 parent 61ce0cb010
commit 3e437b7bd4
3 files changed
+55 -1

No files matched your search

+9 -1
View File
@@ -728,6 +728,14 @@ pub(crate) fn write_display_copy(
}
}
/// `doc` as the reader shows it: comments without an appearance stream drawn
/// (`pdfcraft_annot::with_missing_appearances`) and form fields drawn from their values where the
/// file leaves that to the viewer. `None` when `doc` needs neither. The vector print draws from
/// this copy too, so what prints is what the page shows (#371, #3475 draw them for display only).
pub(crate) fn as_displayed(doc: &pdfcraft_cos::Document) -> Option<pdfcraft_cos::Document> {
with_field_appearances(pdfcraft_annot::with_missing_appearances(doc), doc)
}
/// Carry the marks across a revalidation that leaves the display copy alone, when it may: only
/// when every verdict is the one the copy was drawn with. Returns whether it did; otherwise the
/// copy has to be drawn again.
@@ -779,7 +787,7 @@ pub fn display_bytes_for(bytes: Arc<Vec<u8>>, password: Option<&str>) -> Arc<Vec
let cos = pdfcraft_cos::Document::open_with_password(bytes.clone(), password).ok()?;
let mut signatures = signatures_of(&cos, &bytes, &TrustStore::default(), &pdfcraft_sign::DigestCache::default());
let layered = pdfcraft_sign::has_status_layers(&cos, &signatures);
let (mut display, added) = match with_field_appearances(pdfcraft_annot::with_missing_appearances(&cos), &cos) {
let (mut display, added) = match as_displayed(&cos) {
Some(display) => (display, true),
None if layered => (cos, false),
None => return None,
+7
View File
@@ -164,6 +164,13 @@ impl Session {
pdfcraft_cos::Document::open_with_password(doc.bytes.clone(), doc.password.as_deref()).map_err(|e| EditError::Write(e.to_string()))?
}
};
// Comments and fields without an appearance print as the page shows them (and as Print
// as Image already printed them): the vector print drew only appearance streams, so a
// text box without one was on screen but not on paper. ISO 32000-2 §12.5.3: an
// annotation with the Print flag is printed.
if let Some(displayed) = guard(|| crate::document::as_displayed(&cos)).ok().flatten() {
cos = displayed;
}
// #3046: the layers print as they are shown. Once the Layers panel (or a set-layer
// action) has changed one, the print copy's default configuration says which are on, so a
// hidden layer stays off on paper too; untouched, the document's own configuration prints.
+39
View File
@@ -1779,6 +1779,45 @@ fn low_resolution_printing_prints_only_the_chosen_content() {
}
}
/// A comment and a field the file leaves without an appearance print as the page shows them:
/// a text box (`/FreeText` with only `/Contents` and `/DA`) and a check box set on with
/// `/NeedAppearances`. The vector print drew only appearance streams, so both were on screen and
/// in Print as Image but not on paper (pdf.js `bug1865341`, `freetext_print_no_appearance`).
/// ISO 32000-2 §12.5.3: an annotation with the Print flag is printed.
#[test]
fn comments_and_fields_without_an_appearance_print_as_shown() {
let content = "";
let pdf = format!(
"%PDF-1.7
1 0 obj << /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [6 0 R] /NeedAppearances true /DA (/Helv 0 Tf 0 g) >> >> endobj
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Contents 4 0 R /Annots [5 0 R 6 0 R] >> endobj
4 0 obj << /Length {} >> stream
{content}
endstream endobj
5 0 obj << /Type /Annot /Subtype /FreeText /F 4 /P 3 0 R /Rect [10 120 190 190] /DA (/Helv 40 Tf 0 g) /Contents (WWWW) >> endobj
6 0 obj << /Type /Annot /Subtype /Widget /FT /Btn /T (Box) /V /Yes /AS /Yes /F 4 /P 3 0 R /Rect [10 10 110 110] /MK << /CA (8) >> >> endobj
trailer << /Root 1 0 R >>
%%EOF",
content.len(),
);
let mut s = Session::new();
let id = s.open("bare.pdf", None, Arc::new(pdf.into_bytes()), None).unwrap();
let settings =
print::Settings { pages: vec![0], paper: (200.0, 200.0), layout: print::Layout::Size(print::SizeMode::Actual), ..Default::default() };
let (w, h, rgba) = printed_pixels(s.print_pdf(id, &settings).unwrap());
assert_eq!((w, h), (200, 200));
// Dark pixels in the text box (top half) and in the check box (bottom half).
let dark = |y0: usize, y1: usize| {
(y0..y1)
.flat_map(|y| (0..w as usize).map(move |x| (x, y)))
.filter(|&(x, y)| rgba.get((y * w as usize + x) * 4).is_some_and(|&r| r < 128))
.count()
};
assert!(dark(10, 80) > 50, "the text box prints");
assert!(dark(90, 190) > 50, "the check box prints");
}
#[test]
fn the_permissions_password_unlocks_an_open_document() {
// Only a permissions password (#995, #1077): opens without asking. Changes are allowed,