fix(engine): print as low-resolution pictures when only that is allowed

Printing checked the print permission but not the separate high-quality one
(Table 22, bit 12), so a document limited to low-resolution printing came out
as its original vector pages, text included. Such a document now always
prints as pictures of at most 150 dpi, whatever the settings ask for.
This commit is contained in:
aiongg
2026-10-22 13:18:26 -04:00
committed by Dylan M. Taylor
parent 30300ae71b
commit 28ab616eb6
2 changed files with 43 additions and 1 deletions
+11 -1
View File
@@ -232,6 +232,10 @@ pub struct DocId(pub u64);
/// Undo/redo depth. Snapshots share unchanged data, so this is memory-cheap.
const MAX_UNDO: usize = 100;
/// The highest resolution a document that allows only low-resolution printing (Table 22, bit 12
/// clear) is printed at.
const LOW_RESOLUTION_PRINT_DPI: f64 = 150.0;
/// The passwords that go with one state of a document (protection can change them).
#[derive(Clone, Debug, Default, PartialEq)]
struct Keys {
@@ -4522,9 +4526,15 @@ impl Session {
if !doc.allows_printing() {
return Err(EditError::NotPermitted("printing"));
}
// Table 22, bit 12 clear: only a low-level representation may be printed, so the pages
// always go as pictures, at no more than the low resolution.
let low_res = doc.permissions().is_some_and(|p| !p.print_high_quality()).then(|| print::Raster {
dpi: settings.raster.map_or(LOW_RESOLUTION_PRINT_DPI, |r| r.dpi.min(LOW_RESOLUTION_PRINT_DPI)),
grayscale: settings.raster.is_some_and(|r| r.grayscale),
});
// Print as image: the pages used become pictures first, then the picture document is
// imposed in their place (its page k is the k-th distinct source page used).
if let Some(raster) = &settings.raster {
if let Some(raster) = low_res.as_ref().or(settings.raster.as_ref()) {
let mut distinct: Vec<usize> = Vec::new();
for &p in &settings.pages {
if !distinct.contains(&p) {
+32
View File
@@ -1340,6 +1340,38 @@ fn print_restricted_documents_disable_print_unless_opened_by_the_owner() {
assert!(enabled(&s, low, "print.dialog"), "low-resolution printing is printing");
}
/// Table 22, bit 12 clear: printing is limited to a low-level representation. Whatever the
/// settings ask for, the print-ready PDF carries each page as a picture of at most 150 dpi, with
/// no text or vector artwork left; the owner password lifts the limit.
#[test]
fn low_resolution_printing_prints_pictures_of_at_most_150_dpi() {
let bytes = protected("", "owner", -1 ^ (1 << 11));
let mut s = Session::new();
let low = s.open("low.pdf", None, bytes.clone(), None).unwrap();
let width = f64::from(s.get(low).unwrap().info.pages[0].width);
let actual = print::Settings { pages: vec![0], layout: print::Layout::Size(print::SizeMode::Actual), ..Default::default() };
for raster in [None, Some(print::Raster { dpi: 600.0, grayscale: false })] {
let printed = s.print_pdf(low, &print::Settings { raster, ..actual.clone() }).unwrap();
let mut s2 = Session::new();
let id2 = s2.open("print.pdf", None, Arc::new(printed.clone()), None).unwrap();
assert!(page_texts(&s2, id2).iter().all(|t| t.trim().is_empty()), "{raster:?}: no text is left");
let doc = pdfcraft_cos::Document::open(Arc::new(printed)).unwrap();
let widths: Vec<i64> = doc
.scan_objects()
.filter_map(|(_, o)| match &*o {
pdfcraft_cos::Object::Stream(st) if st.dict.name(b"Subtype") == Some(b"Image") => st.dict.int(b"Width"),
_ => None,
})
.collect();
assert!(!widths.is_empty() && widths.iter().all(|&w| w as f64 <= width * 150.0 / 72.0 + 1.0), "{raster:?}: {widths:?} px for {width} pt");
}
let owner = s.open("low.pdf", None, bytes, Some("owner")).unwrap();
let printed = s.print_pdf(owner, &actual).unwrap();
let mut s2 = Session::new();
let id2 = s2.open("print.pdf", None, Arc::new(printed), None).unwrap();
assert!(page_texts(&s2, id2).iter().any(|t| !t.trim().is_empty()), "the owner prints the pages themselves");
}
#[test]
fn the_permissions_password_unlocks_an_open_document() {
// Only a permissions password (#995, #1077): opens without asking. Changes are allowed,