mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 16:36:13 -06:00
Print: print as image (Advanced), with grayscale pictures (#1965)
* Print: print as image (Advanced), with grayscale pictures print::Settings.raster (dpi 72-600, grayscale): Session::print_pdf renders each distinct page used once the way the viewer shows it (comments left out when the print leaves them out), composes it over white, grey by Rec. 601 luma when grayscale is on, makes an image-only document of the pictures and imposes it on the paper like the pages themselves. The Print dialog's new Advanced section (folded unless on) offers it at 150, 300 or 600 dpi; doc_print takes as_image and dpi. Rows in every language; parity print.print-as-image shipped, print.grayscale shipped (the pictures are grey, not only the printer asked). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ATTRIBUTION.toml: the language files' checksums after the new rows cargo xtask assets checks each catalog's sha256; the rows this branch adds changed the files without updating it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Refresh catalog hashes after merging main --------- Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Osvaldo Duran
Brandon Thomas
parent
57256f9a4c
commit
8e803b6290
+21
-21
@@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "348b7dbb18ca419dbdaf05c6701bb96754a5989c178bec1e971164e2e15c6c43"
|
||||
sha256 = "0c9691685fecfc582ae81c748bcbf645044722e3c233d4502cb49b5a98f93af9"
|
||||
|
||||
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
|
||||
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
|
||||
@@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "bea733dbdd4cdcdd2f9c1d7c278a382ee51188ee37f01c8a049c35e953151869"
|
||||
sha256 = "219efe9f342aa269d4ecfef9617fa39064ea63ac7148231493068470f8257700"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
|
||||
@@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "6d5f5923f73d01fa6166a43b7221834ebb39002ce2d826084c10a4d3d89526a9"
|
||||
sha256 = "e730271977b4d89e30f2d822e65f8894b832a1b2ba66f816828a106cc6b20e8e"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/cs.tsv"
|
||||
@@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "9cea4cc5aa0289e609b00779acc1709c33488fe0a0b59d60a124e877c714eedc"
|
||||
sha256 = "a209f68c88cab1c19e095e9e12bc6f9251bbabc0a411d0e5aba7c1399c94159e"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pt-br.tsv"
|
||||
@@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "b6c62e0b859917175c80d7fa0a9174a3d445458b8d16ff85260a224ccc10204d"
|
||||
sha256 = "d174236d0bf87e2c212fa4c4c1b7cd2a2438150a13af0a2ca08194170d192b8d"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/es.tsv"
|
||||
@@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "8ee80cd976b0a43f601628fa48a9b09bcc4a1e66b4df6870597ee0b1027d897e"
|
||||
sha256 = "b588396c7c4972709843ea3bfc42213780a11c92b83d52292926b6eb9ed52167"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/fr.tsv"
|
||||
@@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "656b2e9550c747e44bffd2437443a5dcd946841831e9708bcb93c5dd919221f5"
|
||||
sha256 = "762a8220093ad1a51126f850e6c8692067a626f528e2cd88b40a1f04d9482c9a"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/de.tsv"
|
||||
@@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "f7bb9f7b48c8759ca2e9a4fac8d991b5d656ef0c0563b81b320b4d127bb474a9"
|
||||
sha256 = "61c4bdac7f60d924ec548c84d04b4c9ef137f27096f958f3584207b50c8e91d5"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/te.tsv"
|
||||
@@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "e2ab1fe22706c4121c7e9a0d56da6404e61d7430433b915f22129c4733da3ed1"
|
||||
sha256 = "91808c674033322c15fa4b40f9175551bcd66bdc02769a90df04c3fa33d710ef"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/it.tsv"
|
||||
@@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "657dcdbecc7e105e03a82a0f7903b0e51c4120f8c4a793254216df989fb1f893"
|
||||
sha256 = "70ae72cd45e6084be65cda4adbfbc1fba31dc92ef46ece38064f4a21755d096b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pl.tsv"
|
||||
@@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "75282238445a2873e322059a90795463033ec4dfbc30b83c21d20bcde3680f8f"
|
||||
sha256 = "94b507ad2d09e04339d4307ea44281c0bb27eb5f04f616c4e9e9bb69613fd5cb"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ru.tsv"
|
||||
@@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "653525be13d7fb18b3dcc6eb05656bb9d2c573dea2c488b21aa82b319ed4b67c"
|
||||
sha256 = "b69396f3c0049eaf788fcd742d067857dacb0ae61fb84e22888373b647a62c1c"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hu.tsv"
|
||||
@@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "d49788ad569aee627cd4386799b58334f03115c2e6977af3f02305a6771bc403"
|
||||
sha256 = "778e2c93af6178fa9ec3544d10716174259a8587cf0912b0d5ab4c769b50a514"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/bg.tsv"
|
||||
@@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "5fa60b06689a8c33fb0540dd3cd4c996064629066b2b116a3ded535c73405192"
|
||||
sha256 = "1d8ef2cb8b2010178bf5036c4563dec00bfca8d5ef1ad436dacf6412567e74d7"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/kk.tsv"
|
||||
@@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "2ac70113c4dd3182c566681f301f0fa0da77c57231ddcad514db6f67cd4bb8e9"
|
||||
sha256 = "d722c9d6d0427b9f50119df0cde7b4f3341779bdfaa349460b68f02bc21ca5f8"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ja.tsv"
|
||||
@@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "c61ad71750d8246f09d8e9ee1229d77662b96c7dba886e3ce7b46083e3bac406"
|
||||
sha256 = "79033f5db7d2a5ab1aebaa6cadb77806d45d072fdf250bd2086974a77f4bf7ce"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/uk.tsv"
|
||||
@@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "f675541c61cf5fb70cc63e95ffe1e56c5c63e9c311d1c321852aa2e3313b7833"
|
||||
sha256 = "fe159f37f57f1d49e6a80b6b971f799a19a49b6feaf91d40424d0d5837e8bfb5"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ar.tsv"
|
||||
@@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "cdb9a5d7e51db6b62c1d85a77e42faa73932425791b06fad836333f1ee21b7fe"
|
||||
sha256 = "79332492b077695340b023c6e0b33523ae40f7a77f88eedc739cb8cff4c6b29e"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/tr.tsv"
|
||||
@@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "d4d0591fab5d328a5d34346ffc7abe1dbaeed8241a97f9624763b95126ab78fa"
|
||||
sha256 = "5eaa8ca29239cd92d64e343e5a2809a5e02239ccd06e7ac16774335328f01c80"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/lt.tsv"
|
||||
@@ -228,7 +228,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "d2d0588abbb6ae03347d197c58dc18d97a18bc479580216a14501be292254e8f"
|
||||
sha256 = "122781df7711e41ce2462eea032133c449c5cafbdb528dc7fd096d7ff033e587"
|
||||
|
||||
|
||||
[[asset]]
|
||||
@@ -3363,4 +3363,4 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "2a2d8aa07d2c6de2f55b9d7de1fb299c63843c217013c930c288be4ce68c6b87"
|
||||
sha256 = "c81dcea055935312c77aea40928748a95f179d85a0418968d2893a479a1272bd"
|
||||
|
||||
@@ -125,7 +125,18 @@ impl Automation {
|
||||
bad(format!("unknown paper {p:?}; choose one of {}", PAPERS.iter().map(|(name, _)| *name).collect::<Vec<_>>().join(", ")))
|
||||
})?,
|
||||
};
|
||||
let settings = print::Settings { pages, paper, orientation, layout, content };
|
||||
// Print as image (Acrobat's Advanced Print Setup): pictures at `dpi`, grey when
|
||||
// `grayscale` is also asked for.
|
||||
let raster = if a.opt_bool("as_image")?.unwrap_or(false) {
|
||||
let dpi = a.opt_num("dpi")?.unwrap_or(300.0);
|
||||
if !(72.0..=600.0).contains(&dpi) {
|
||||
return Err(bad("dpi must be between 72 and 600"));
|
||||
}
|
||||
Some(print::Raster { dpi, grayscale: a.opt_bool("grayscale")?.unwrap_or(false) })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let settings = print::Settings { pages, paper, orientation, layout, content, raster };
|
||||
let sizes: Vec<(f64, f64)> = self.doc(a)?.info.pages.iter().map(|p| (p.width as f64, p.height as f64)).collect();
|
||||
let sheets = print::layout(&sizes, &settings).map_err(|e| bad(e.to_string()))?.len();
|
||||
let bytes = self.session.print_pdf(id, &settings).map_err(failed)?;
|
||||
|
||||
@@ -512,6 +512,8 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
"collate": { "type": "boolean" },
|
||||
"duplex": { "type": "string", "enum": ["off", "long-edge", "short-edge"] },
|
||||
"grayscale": { "type": "boolean" },
|
||||
"as_image": { "type": "boolean", "description": "Print as image (Acrobat's Advanced Print Setup): every page rendered to a picture at dpi before it is placed on the paper, for printers that skip text, shading or transparency; the print is no longer searchable text, and with grayscale the pictures are grey." },
|
||||
"dpi": { "type": "number", "minimum": 72, "maximum": 600, "description": "Resolution for as_image (default 300)." },
|
||||
"options": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Printer driver options from printer_options: key → choice value." },
|
||||
}),
|
||||
&["doc"],
|
||||
|
||||
@@ -6094,3 +6094,23 @@ fn a_missing_path_under_the_root_names_where_it_was_looked_for() {
|
||||
ok(&mut a, "doc_open", json!({ "path": "inside.pdf" }));
|
||||
let _ = std::fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
/// `doc_print` prints as image: pictures at `dpi`, grey with `grayscale`; a resolution outside
|
||||
/// 72–600 is refused.
|
||||
#[test]
|
||||
fn doc_print_as_image() {
|
||||
let dir = workdir("print-image");
|
||||
let mut a = auto(&dir);
|
||||
let doc = ok(&mut a, "doc_open", json!({ "path": "a.pdf" }))["doc"].as_u64().unwrap();
|
||||
let r = ok(&mut a, "doc_print", json!({ "doc": doc, "pages": "1", "as_image": true, "dpi": 150, "grayscale": true, "path": "image.pdf" }));
|
||||
assert_eq!(r["sheets"], 1);
|
||||
let bytes = std::fs::read(dir.join("image.pdf")).unwrap();
|
||||
let out = pdfcraft_cos::Document::open(std::sync::Arc::new(bytes)).unwrap();
|
||||
let pictures =
|
||||
out.scan_objects().filter(|(_, o)| matches!(&**o, pdfcraft_cos::Object::Stream(st) if st.dict.name(b"Subtype") == Some(b"Image"))).count();
|
||||
assert_eq!(pictures, 1);
|
||||
match a.call("doc_print", &json!({ "doc": doc, "as_image": true, "dpi": 20, "path": "x.pdf" })) {
|
||||
Err(ToolError::InvalidArgs(m)) => assert!(m.contains("dpi"), "{m}"),
|
||||
other => panic!("{other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ pub mod js;
|
||||
pub mod links;
|
||||
pub mod ocr;
|
||||
pub mod optimizer;
|
||||
pub mod print_raster;
|
||||
pub mod printcheck;
|
||||
mod scan_colours;
|
||||
mod scan_fields;
|
||||
@@ -4104,6 +4105,26 @@ impl Session {
|
||||
if !doc.allows_printing() {
|
||||
return Err(EditError::NotPermitted("printing"));
|
||||
}
|
||||
// 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 {
|
||||
let mut distinct: Vec<usize> = Vec::new();
|
||||
for &p in &settings.pages {
|
||||
if !distinct.contains(&p) {
|
||||
distinct.push(p);
|
||||
}
|
||||
}
|
||||
let hide_comments = matches!(settings.content, print::Content::Document | print::Content::FormFieldsOnly);
|
||||
let bytes = print_raster::rasterize(doc, &distinct, raster, hide_comments).map_err(EditError::Print)?;
|
||||
let pictures = pdfcraft_cos::Document::open(Arc::new(bytes)).map_err(|e| EditError::Print(e.to_string()))?;
|
||||
let remapped = print::Settings {
|
||||
pages: settings.pages.iter().map(|p| distinct.iter().position(|d| d == p).unwrap_or(0)).collect(),
|
||||
content: print::Content::Document,
|
||||
raster: None,
|
||||
..settings.clone()
|
||||
};
|
||||
return pdfcraft_print::impose(&pictures, &remapped).map_err(|e| EditError::Print(e.to_string()));
|
||||
}
|
||||
let cos = match doc.editor.as_ref() {
|
||||
Some(e) => e.cos.clone(),
|
||||
None => {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
//! Print as image (Acrobat's Advanced Print Setup ▸ Print As Image): every page
|
||||
//! sent to the printer as a picture at a chosen resolution, for printers that skip text,
|
||||
//! shading or transparency. The pages are rendered the way the viewer shows them (reading
|
||||
//! colours off, the print's own colours), made into an image-only document, and that document
|
||||
//! is imposed on the paper like any other. The print is no longer searchable text.
|
||||
|
||||
use pdfcraft_create::ImageResolution;
|
||||
use pdfcraft_print::Raster;
|
||||
use pdfcraft_render::{PRINT_TAG, PageRenderer, RenderRequest};
|
||||
|
||||
use crate::Document;
|
||||
use crate::export::encode_png;
|
||||
|
||||
/// `doc`'s pages `pages` (0-based, each once, in the order given) as an image-only PDF, one
|
||||
/// page per entry, the size of the source page as displayed. Pages too large for the renderer
|
||||
/// at `raster.dpi` render at the largest resolution it allows.
|
||||
pub fn rasterize(doc: &Document, pages: &[usize], raster: &Raster, hide_comments: bool) -> Result<Vec<u8>, String> {
|
||||
let dpi = raster.dpi.clamp(72.0, 600.0);
|
||||
let src = doc.export_source();
|
||||
let mut config = src.config.clone();
|
||||
config.hide_comments = hide_comments;
|
||||
let mut renderer = PageRenderer::new(src.bytes, config);
|
||||
let mut images: Vec<(String, Vec<u8>)> = Vec::with_capacity(pages.len());
|
||||
let mut used: Option<f64> = None;
|
||||
for &page in pages {
|
||||
let (w, h) = src.sizes.get(page).copied().ok_or_else(|| format!("page {} does not exist", page + 1))?;
|
||||
let scale = pdfcraft_render::effective_scale(w, h, (dpi / 72.0) as f32);
|
||||
let page_dpi = f64::from(scale) * 72.0;
|
||||
// Every page of the image document shares one resolution, so its pages keep the
|
||||
// source's sizes; the lowest a page needed is used for all.
|
||||
used = Some(used.map_or(page_dpi, |u: f64| u.min(page_dpi)));
|
||||
let r = renderer.render(RenderRequest { page, scale, tag: PRINT_TAG, ..Default::default() });
|
||||
if let Some(e) = r.error {
|
||||
return Err(format!("page {}: {e}", page + 1));
|
||||
}
|
||||
let mut rgba = r.rgba.to_vec();
|
||||
over_white(&mut rgba, raster.grayscale);
|
||||
images.push((format!("page-{}.png", page + 1), encode_png(r.width, r.height, &rgba)?));
|
||||
}
|
||||
let dpi = used.unwrap_or(dpi);
|
||||
let doc = pdfcraft_create::from_images_with_resolution(&images, ImageResolution::Dpi(dpi)).map_err(|e| e.to_string())?;
|
||||
pdfcraft_cos::write_full(&doc, &pdfcraft_cos::SaveOptions::default()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Premultiplied RGBA composed over white paper (opaque), in grey when asked (Rec. 601 luma).
|
||||
fn over_white(rgba: &mut [u8], grayscale: bool) {
|
||||
for px in rgba.as_chunks_mut::<4>().0 {
|
||||
let gap = 255 - u32::from(px[3]);
|
||||
let (r, g, b) = ((u32::from(px[0]) + gap).min(255), (u32::from(px[1]) + gap).min(255), (u32::from(px[2]) + gap).min(255));
|
||||
let (r, g, b) = if grayscale {
|
||||
let y = ((299 * r + 587 * g + 114 * b) / 1000).min(255);
|
||||
(y, y, y)
|
||||
} else {
|
||||
(r, g, b)
|
||||
};
|
||||
px[0] = r as u8;
|
||||
px[1] = g as u8;
|
||||
px[2] = b as u8;
|
||||
px[3] = 255;
|
||||
}
|
||||
}
|
||||
@@ -4975,3 +4975,44 @@ fn copy_image_finds_a_picture_inside_form_artwork_under_text() {
|
||||
assert_eq!(d.page_image_copy_file(0, 0).unwrap(), ("jpg", jpeg));
|
||||
assert!(d.page_image_copy_file(0, 1).is_err());
|
||||
}
|
||||
|
||||
/// Print as image (Acrobat's Advanced Print Setup): the print-ready PDF carries each page as a
|
||||
/// picture, the size of the page, with no text left to search; in grayscale the pictures are
|
||||
/// grey. A page used on several sheets is rendered once.
|
||||
#[test]
|
||||
fn printing_as_image_sends_pictures_instead_of_pages() {
|
||||
let (s, id) = session_with(3);
|
||||
let page_size = (s.get(id).unwrap().info.pages[1].width as f64, s.get(id).unwrap().info.pages[1].height as f64);
|
||||
let settings = print::Settings {
|
||||
pages: vec![1, 2, 1],
|
||||
layout: print::Layout::Size(print::SizeMode::Actual),
|
||||
raster: Some(print::Raster { dpi: 100.0, grayscale: false }),
|
||||
..Default::default()
|
||||
};
|
||||
let bytes = s.print_pdf(id, &settings).unwrap();
|
||||
let mut s2 = Session::new();
|
||||
let id2 = s2.open("print.pdf", None, Arc::new(bytes.clone()), None).unwrap();
|
||||
assert_eq!(s2.get(id2).unwrap().info.pages.len(), 3, "three sheets, one page each");
|
||||
assert!(page_texts(&s2, id2).iter().all(|t| t.trim().is_empty()), "pictures carry no text");
|
||||
let doc = pdfcraft_cos::Document::open(Arc::new(bytes)).unwrap();
|
||||
let images: Vec<pdfcraft_cos::Dict> = doc
|
||||
.scan_objects()
|
||||
.filter_map(|(_, o)| match &*o {
|
||||
pdfcraft_cos::Object::Stream(st) if st.dict.name(b"Subtype") == Some(b"Image") => Some(st.dict.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(images.len(), 2, "two distinct pages, each rendered once: {images:?}");
|
||||
// 100 dpi: the picture's pixels match the page size.
|
||||
let w = images.iter().map(|d| d.int(b"Width").unwrap_or(0)).max().unwrap();
|
||||
assert!((w as f64 - page_size.0 * 100.0 / 72.0).abs() <= 1.0, "{w} px for {} pt", page_size.0);
|
||||
assert!(images.iter().all(|d| d.name(b"ColorSpace") == Some(b"DeviceRGB")), "{images:?}");
|
||||
// Grayscale renders the pictures grey (the printer is not merely asked for monochrome).
|
||||
let grey = s.print_pdf(id, &print::Settings { raster: Some(print::Raster { dpi: 72.0, grayscale: true }), ..settings.clone() }).unwrap();
|
||||
let mut s3 = Session::new();
|
||||
let id3 = s3.open("grey.pdf", None, Arc::new(grey), None).unwrap();
|
||||
let mut ex = crate::export::Exporter::new(s3.get(id3).unwrap());
|
||||
let png = ex.png(0, 36.0).unwrap();
|
||||
let img = image::load_from_memory(&png).unwrap().to_rgba8();
|
||||
assert!(img.pixels().all(|p| p[0] == p[1] && p[1] == p[2]), "every pixel grey");
|
||||
}
|
||||
|
||||
+27
-1
@@ -228,11 +228,37 @@ pub struct Settings {
|
||||
pub orientation: Orientation,
|
||||
pub layout: Layout,
|
||||
pub content: Content,
|
||||
/// Print as image (Acrobat's Advanced Print Setup): every page rendered to a picture at this
|
||||
/// resolution before it is placed on the paper. The engine does the rendering; `impose` is
|
||||
/// then given the image-only document.
|
||||
pub raster: Option<Raster>,
|
||||
}
|
||||
|
||||
/// How pages are rendered when printing as image.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Raster {
|
||||
/// Dots per inch, 72–600 (Acrobat offers 150, 300 and 600).
|
||||
pub dpi: f64,
|
||||
/// Render the pictures in grey, so the print-ready PDF itself is grayscale.
|
||||
pub grayscale: bool,
|
||||
}
|
||||
|
||||
impl Default for Raster {
|
||||
fn default() -> Self {
|
||||
Raster { dpi: 300.0, grayscale: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Settings { pages: Vec::new(), paper: PAPERS[0].1, orientation: Orientation::Auto, layout: Layout::default(), content: Content::default() }
|
||||
Settings {
|
||||
pages: Vec::new(),
|
||||
paper: PAPERS[0].1,
|
||||
orientation: Orientation::Auto,
|
||||
layout: Layout::default(),
|
||||
content: Content::default(),
|
||||
raster: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2408,3 +2408,6 @@ animation effects Off معطّلة
|
||||
New file only الملف الجديد فقط
|
||||
Old file: {name} الملف القديم: {name}
|
||||
New file: {name} الملف الجديد: {name}
|
||||
Print as image الطباعة كصورة
|
||||
Resolution: الدقة:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. استخدمه عندما تتجاهل الطابعة النص أو التظليل أو الشفافية. لا تعود المطبوعة نصًا قابلًا للبحث؛ ومع الطباعة بتدرج الرمادي تكون الصور رمادية.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2350,3 +2350,6 @@ animation effects Off Изключени
|
||||
New file only Само новият файл
|
||||
Old file: {name} Стар файл: {name}
|
||||
New file: {name} Нов файл: {name}
|
||||
Print as image Печат като изображение
|
||||
Resolution: Разделителна способност:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Използвайте го, когато принтерът пропуска текст, сенки или прозрачност. Отпечатаното вече не е текст за търсене; с печат в сиво изображенията са сиви.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -2461,3 +2461,6 @@ animation effects Off Vypnuto
|
||||
New file only Jen nový soubor
|
||||
Old file: {name} Starý soubor: {name}
|
||||
New file: {name} Nový soubor: {name}
|
||||
Print as image Tisknout jako obrázek
|
||||
Resolution: Rozlišení:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Použijte, když tiskárna vynechává text, stínování nebo průhlednost. Výtisk už není prohledávatelný text; s tiskem v odstínech šedi jsou obrázky šedé.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2388,3 +2388,6 @@ animation effects Off Aus
|
||||
New file only Nur neue Datei
|
||||
Old file: {name} Alte Datei: {name}
|
||||
New file: {name} Neue Datei: {name}
|
||||
Print as image Als Bild drucken
|
||||
Resolution: Auflösung:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Verwenden, wenn ein Drucker Text, Schattierungen oder Transparenz auslässt. Der Ausdruck ist kein durchsuchbarer Text mehr; mit Graustufendruck sind die Bilder grau.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
@@ -2397,3 +2397,6 @@ animation effects Off Ανενεργά
|
||||
New file only Μόνο το νέο αρχείο
|
||||
Old file: {name} Παλιό αρχείο: {name}
|
||||
New file: {name} Νέο αρχείο: {name}
|
||||
Print as image Εκτύπωση ως εικόνα
|
||||
Resolution: Ανάλυση:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Χρησιμοποιήστε το όταν ένας εκτυπωτής παραλείπει κείμενο, σκίαση ή διαφάνεια. Η εκτύπωση δεν είναι πλέον κείμενο με δυνατότητα αναζήτησης· με εκτύπωση σε αποχρώσεις του γκρι, οι εικόνες είναι γκρι.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 475 and column 9.
|
@@ -2517,3 +2517,6 @@ animation effects Off Desactivados
|
||||
New file only Solo el archivo nuevo
|
||||
Old file: {name} Archivo antiguo: {name}
|
||||
New file: {name} Archivo nuevo: {name}
|
||||
Print as image Imprimir como imagen
|
||||
Resolution: Resolución:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Úsalo cuando una impresora omite texto, sombreados o transparencias. La impresión deja de ser texto que se puede buscar; con Imprimir en escala de grises, las imágenes son grises.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
@@ -2387,3 +2387,6 @@ animation effects Off Désactivés
|
||||
New file only Nouveau fichier seul
|
||||
Old file: {name} Ancien fichier : {name}
|
||||
New file: {name} Nouveau fichier : {name}
|
||||
Print as image Imprimer comme image
|
||||
Resolution: Résolution :
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. À utiliser quand une imprimante saute du texte, des dégradés ou de la transparence. L'impression n'est plus du texte consultable ; avec l'impression en niveaux de gris, les images sont grises.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
@@ -2386,3 +2386,6 @@ animation effects Off Ki
|
||||
New file only Csak az új fájl
|
||||
Old file: {name} Régi fájl: {name}
|
||||
New file: {name} Új fájl: {name}
|
||||
Print as image Nyomtatás képként
|
||||
Resolution: Felbontás:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Akkor használja, ha a nyomtató kihagy szöveget, árnyékolást vagy átlátszóságot. A nyomat már nem kereshető szöveg; szürkeárnyalatos nyomtatással a képek szürkék.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
@@ -2396,3 +2396,6 @@ animation effects Off Disattivati
|
||||
New file only Solo il nuovo file
|
||||
Old file: {name} File vecchio: {name}
|
||||
New file: {name} File nuovo: {name}
|
||||
Print as image Stampa come immagine
|
||||
Resolution: Risoluzione:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Usalo quando una stampante salta testo, sfumature o trasparenze. La stampa non è più testo ricercabile; con Stampa in scala di grigi le immagini sono grigie.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2387,3 +2387,6 @@ animation effects Off オフ
|
||||
New file only 新しいファイルのみ
|
||||
Old file: {name} 古いファイル: {name}
|
||||
New file: {name} 新しいファイル: {name}
|
||||
Print as image 画像として印刷
|
||||
Resolution: 解像度:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. プリンターが文字、グラデーション、透明部分を印刷しないときに使います。印刷物は検索できる文字ではなくなります。グレースケール印刷と併用すると画像はグレーになります。
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2385,3 +2385,6 @@ animation effects Off Өшірулі
|
||||
New file only Тек жаңа файл
|
||||
Old file: {name} Ескі файл: {name}
|
||||
New file: {name} Жаңа файл: {name}
|
||||
Print as image Сурет ретінде басып шығару
|
||||
Resolution: Ажыратымдылық:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Принтер мәтінді, көлеңкені немесе мөлдірлікті өткізіп жіберсе, қолданыңыз. Басылған құжат енді іздеуге болатын мәтін емес; сұр реңкпен басып шығарғанда суреттер сұр болады.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 822 and column 9.
|
@@ -2631,3 +2631,6 @@ key Space Tarpas
|
||||
New file only Tik naujas failas
|
||||
Old file: {name} Senas failas: {name}
|
||||
New file: {name} Naujas failas: {name}
|
||||
Print as image Spausdinti kaip vaizdą
|
||||
Resolution: Raiška:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Naudokite, kai spausdintuvas praleidžia tekstą, šešėliavimą ar skaidrumą. Atspaudas nebėra ieškomas tekstas; spausdinant pilkais atspalviais vaizdai yra pilki.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 401 and column 9.
|
@@ -2367,3 +2367,6 @@ animation effects Off Wyłączone
|
||||
New file only Tylko nowy plik
|
||||
Old file: {name} Stary plik: {name}
|
||||
New file: {name} Nowy plik: {name}
|
||||
Print as image Drukuj jako obraz
|
||||
Resolution: Rozdzielczość:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Użyj, gdy drukarka pomija tekst, cieniowanie lub przezroczystość. Wydruk nie jest już tekstem do przeszukiwania; przy druku w skali szarości obrazy są szare.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2401,3 +2401,6 @@ animation effects Off Desativados
|
||||
New file only Somente o arquivo novo
|
||||
Old file: {name} Arquivo antigo: {name}
|
||||
New file: {name} Arquivo novo: {name}
|
||||
Print as image Imprimir como imagem
|
||||
Resolution: Resolução:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Use quando uma impressora omite texto, sombreamento ou transparência. A impressão deixa de ser texto pesquisável; com Imprimir em tons de cinza, as imagens ficam cinza.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2300,3 +2300,6 @@ animation effects Off Выкл
|
||||
New file only Только новый файл
|
||||
Old file: {name} Старый файл: {name}
|
||||
New file: {name} Новый файл: {name}
|
||||
Print as image Печать как изображение
|
||||
Resolution: Разрешение:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Используйте, когда принтер пропускает текст, заливки или прозрачность. Отпечаток больше не текст с поиском; при печати в оттенках серого изображения серые.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2392,3 +2392,6 @@ animation effects Off Izklopljeni
|
||||
New file only Samo nova datoteka
|
||||
Old file: {name} Stara datoteka: {name}
|
||||
New file: {name} Nova datoteka: {name}
|
||||
Print as image Natisni kot sliko
|
||||
Resolution: Ločljivost:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Uporabite, ko tiskalnik izpusti besedilo, senčenje ali prosojnost. Natis ni več iskalno besedilo; s tiskanjem v sivinah so slike sive.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2300,3 +2300,6 @@ animation effects Off ఆఫ్
|
||||
New file only కొత్త ఫైల్ మాత్రమే
|
||||
Old file: {name} పాత ఫైల్: {name}
|
||||
New file: {name} కొత్త ఫైల్: {name}
|
||||
Print as image చిత్రంగా ముద్రించు
|
||||
Resolution: రిజల్యూషన్:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. ప్రింటర్ వచనం, షేడింగ్ లేదా పారదర్శకతను వదిలేసినప్పుడు దీన్ని వాడండి. ముద్రణ ఇక శోధించగల వచనం కాదు; బూడిద రంగులో ముద్రించినప్పుడు చిత్రాలు బూడిద రంగులో ఉంటాయి.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 13 and column 16.
|
@@ -2383,3 +2383,6 @@ animation effects Off Kapalı
|
||||
New file only Yalnızca yeni dosya
|
||||
Old file: {name} Eski dosya: {name}
|
||||
New file: {name} Yeni dosya: {name}
|
||||
Print as image Resim olarak yazdır
|
||||
Resolution: Çözünürlük:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Yazıcı metni, gölgelendirmeyi veya saydamlığı atladığında kullanın. Çıktı artık aranabilir metin değildir; gri tonlamalı yazdırmayla resimler gri olur.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 821 and column 9.
|
@@ -2409,3 +2409,6 @@ animation effects Off Вимкнено
|
||||
New file only Лише новий файл
|
||||
Old file: {name} Старий файл: {name}
|
||||
New file: {name} Новий файл: {name}
|
||||
Print as image Друкувати як зображення
|
||||
Resolution: Роздільність:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. Використовуйте, коли принтер пропускає текст, заливки або прозорість. Відбиток більше не текст із пошуком; під час друку у відтінках сірого зображення сірі.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2351,3 +2351,6 @@ animation effects Off 关
|
||||
New file only 仅新文件
|
||||
Old file: {name} 旧文件:{name}
|
||||
New file: {name} 新文件:{name}
|
||||
Print as image 作为图像打印
|
||||
Resolution: 分辨率:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. 当打印机漏印文字、渐变或透明效果时使用。打印结果不再是可搜索的文字;配合灰度打印时图像为灰色。
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2341,3 +2341,6 @@ animation effects Off 關
|
||||
New file only 僅新檔案
|
||||
Old file: {name} 舊檔案:{name}
|
||||
New file: {name} 新檔案:{name}
|
||||
Print as image 以影像列印
|
||||
Resolution: 解析度:
|
||||
Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey. 當印表機漏印文字、漸層或透明效果時使用。列印結果不再是可搜尋的文字;搭配灰階列印時影像為灰色。
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -67,6 +67,9 @@ pub struct PrintDraft {
|
||||
pub cut_marks: bool,
|
||||
pub orientation: Orientation,
|
||||
pub content: Content,
|
||||
/// Print as image (Acrobat's Advanced Print Setup) at `dpi`.
|
||||
pub as_image: bool,
|
||||
pub dpi: f64,
|
||||
pub paper: usize,
|
||||
/// The sheet shown in the preview (0-based).
|
||||
pub sheet: usize,
|
||||
@@ -111,6 +114,8 @@ impl Default for PrintDraft {
|
||||
cut_marks: true,
|
||||
orientation: Orientation::Auto,
|
||||
content: Content::DocumentAndMarkups,
|
||||
as_image: false,
|
||||
dpi: 300.0,
|
||||
paper: 0,
|
||||
sheet: 0,
|
||||
current_page: 0,
|
||||
@@ -156,7 +161,14 @@ impl PrintDraft {
|
||||
Handling::Booklet => Layout::Booklet { subset: self.booklet_subset, binding: self.binding },
|
||||
Handling::Poster => Layout::Poster { scale: self.poster_scale, overlap: self.overlap, cut_marks: self.cut_marks },
|
||||
};
|
||||
Ok(print::Settings { pages, paper: PAPERS[self.paper.min(PAPERS.len() - 1)].1, orientation: self.orientation, layout, content: self.content })
|
||||
Ok(print::Settings {
|
||||
pages,
|
||||
paper: PAPERS[self.paper.min(PAPERS.len() - 1)].1,
|
||||
orientation: self.orientation,
|
||||
layout,
|
||||
content: self.content,
|
||||
raster: self.as_image.then_some(print::Raster { dpi: self.dpi, grayscale: self.grayscale }),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn job(&self, title: &str) -> spool::Job {
|
||||
@@ -909,6 +921,29 @@ pub(crate) fn body(
|
||||
],
|
||||
220.0,
|
||||
);
|
||||
// Advanced (Acrobat's Advanced Print Setup): print as image. Folded unless it is on.
|
||||
ui.add_space(6.0);
|
||||
egui::CollapsingHeader::new(egui::RichText::new(tl!("Advanced").to_uppercase()).font(theme::semibold(10.5)).color(t.text_faint))
|
||||
.id_salt("print-advanced")
|
||||
.default_open(d.as_image)
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(widgets::checkbox(&mut d.as_image, tl!("Print as image")));
|
||||
ui.add_enabled_ui(d.as_image, |ui| {
|
||||
ui.label(tl!("Resolution:"));
|
||||
let mut dpi = d.dpi.round() as u32;
|
||||
combo(ui, "print-dpi", &mut dpi, &[(150, "150 dpi"), (300, "300 dpi"), (600, "600 dpi")], 100.0);
|
||||
d.dpi = f64::from(dpi);
|
||||
});
|
||||
});
|
||||
ui.label(
|
||||
egui::RichText::new(tl!(
|
||||
"Use it when a printer skips text, shading or transparency. The print is no longer searchable text; with Print in grayscale, the pictures are grey."
|
||||
))
|
||||
.small()
|
||||
.color(t.text_muted),
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
// Preview.
|
||||
|
||||
@@ -373,3 +373,31 @@ fn a_printer_without_two_sided_printing_points_to_its_properties() {
|
||||
h.run_steps(2);
|
||||
h.get_by_label_contains(note);
|
||||
}
|
||||
|
||||
/// Print as image (Acrobat's Advanced Print Setup) is a choice in the Print dialog's Advanced
|
||||
/// section: the saved print-ready PDF carries the page as a picture, no text.
|
||||
#[test]
|
||||
fn print_as_image_is_an_advanced_choice_that_saves_pictures() {
|
||||
let mut h = harness();
|
||||
assert!(h.state_mut().execute("print.dialog"));
|
||||
h.run_steps(3);
|
||||
h.get_by_label("Advanced".to_uppercase().as_str()).click();
|
||||
h.run_steps(2);
|
||||
h.get_by_label("Print as image").click();
|
||||
h.run_steps(2);
|
||||
assert!(h.state().print_draft.as_image);
|
||||
let out = std::env::temp_dir().join(format!("pdfcraft-print-image-test-{}.pdf", std::process::id()));
|
||||
h.state_mut().save_override = Some(out.to_string_lossy().into_owned());
|
||||
h.state_mut().print_draft.printer = None;
|
||||
h.run_steps(1);
|
||||
h.get_by_label("Save as PDF").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().dialog, None);
|
||||
let bytes = std::fs::read(&out).expect("saved");
|
||||
let doc = pdfcraft_cos::Document::open(std::sync::Arc::new(bytes)).unwrap();
|
||||
assert_eq!(pdfcraft_model::pages(&doc).len(), 1);
|
||||
let pictures =
|
||||
doc.scan_objects().filter(|(_, o)| matches!(&**o, pdfcraft_cos::Object::Stream(st) if st.dict.name(b"Subtype") == Some(b"Image"))).count();
|
||||
assert_eq!(pictures, 1, "the page is one picture");
|
||||
let _ = std::fs::remove_file(out);
|
||||
}
|
||||
|
||||
@@ -8105,11 +8105,11 @@ title = "Print in grayscale"
|
||||
area = "L"
|
||||
tier = "P1"
|
||||
milestone = "M10"
|
||||
status = "partial"
|
||||
status = "shipped"
|
||||
commands = ["print.dialog"]
|
||||
tools = ["doc_print"]
|
||||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list"]
|
||||
notes = "asks the printer for monochrome (lp -o, and the Windows page settings as the job's); the PDF itself stays in colour"
|
||||
evidence = ["crates/engine/src/tests.rs::printing_as_image_sends_pictures_instead_of_pages", "crates/ui-egui/tests/print.rs::print_as_image_is_an_advanced_choice_that_saves_pictures", "crates/automation/tests/automation.rs::doc_print_as_image"]
|
||||
notes = "asks the printer for monochrome (lp -o, and the Windows page settings as the job's); with Print as image the pictures themselves are rendered grey (Rec. 601 luma), so the print-ready PDF is grayscale too. Still missing against Acrobat: a grey conversion of the vector pages without rendering them."
|
||||
|
||||
[[feature]]
|
||||
id = "print.print-as-image"
|
||||
@@ -8117,11 +8117,11 @@ title = "Print as image"
|
||||
area = "L"
|
||||
tier = "P1"
|
||||
milestone = "M10"
|
||||
status = "planned"
|
||||
commands = []
|
||||
tools = []
|
||||
evidence = []
|
||||
notes = ""
|
||||
status = "shipped"
|
||||
commands = ["print.dialog"]
|
||||
tools = ["doc_print"]
|
||||
evidence = ["crates/engine/src/tests.rs::printing_as_image_sends_pictures_instead_of_pages", "crates/ui-egui/tests/print.rs::print_as_image_is_an_advanced_choice_that_saves_pictures", "crates/automation/tests/automation.rs::doc_print_as_image"]
|
||||
notes = "The Print dialog's Advanced section (folded unless on): Print as image at 150, 300 or 600 dpi; doc_print as_image and dpi (72–600). The pages used are rendered once each the way the viewer shows them (comments left out when the print leaves them out), composed over white, made into an image-only document and imposed on the paper like the pages themselves. The print is no longer searchable text. Still missing against Acrobat: per-printer defaults for it."
|
||||
|
||||
[[feature]]
|
||||
id = "print.page-setup"
|
||||
|
||||
Reference in New Issue
Block a user