Files
pdfcraft/apps/pdfcraft-cli/tests/render.rs
T
Morten Husted Lochmann f280ca8e6d M10: exported TIFF and JPEG pages record the resolution they were drawn at (#3332)
* M10: exported TIFF and JPEG pages record the resolution they were drawn at

A page exported as TIFF said 1 x 1 with no unit, whatever the dpi, and
a JPEG export said nothing, so other programs showed and printed them at
a default (a 300-dpi page shown at 72 dpi is four times too big).
encode_image now takes the dpi: TIFF X/YResolution in inches, the JFIF
density in dots per inch.

PNG stays without pHYs. It counts pixels per metre, so 72 dpi would read
back as 72.009, and a page made from an exported PNG (doc_create from
images, as enhance_camera_image_through_tools does) would come out 0.01%
small.

Exporter records the resolution a page was actually drawn at (dpi_used),
so a page too large for the renderer says the lower one. render records
--dpi, since it refuses oversized pages. The TIFF is written with the
tiff crate, already in the graph, because image's TiffEncoder can't set
a resolution.

* M10: TIFF page exports are LZW-compressed, as documented

encode_tiff's comment said "LZW-compressed by the encoder's default",
but the TIFF was written uncompressed: a Letter page at 300 dpi came to
25 MB. It is now LZW without a predictor, the compression TIFF readers
support most widely after none.

* Contributors: add my name for the About window
2026-10-18 17:06:41 -04:00

91 lines
3.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! `render --out` writes the file format its name says (#248): a `.png` is a PNG, not a netpbm
//! PAM stream with a `.png` extension.
use std::path::PathBuf;
use std::process::{Command, Output};
const BIN: &str = env!("CARGO_BIN_EXE_pdfcraft-cli");
fn tmp(test: &str, name: &str) -> PathBuf {
let d = std::env::temp_dir().join(format!("pdfcraft-cli-render-{test}-{}", std::process::id()));
std::fs::create_dir_all(&d).unwrap();
d.join(name)
}
/// A one-page PDF with some text, as `--out` would be given it.
fn fixture(test: &str) -> PathBuf {
let pdf = tmp(test, "in.pdf");
let bytes = pdfcraft_engine::Session::new().create_from_text("Render test", "Hello from PdfCraft").unwrap();
std::fs::write(&pdf, bytes.as_slice()).unwrap();
pdf
}
fn render(pdf: &std::path::Path, out: &std::path::Path) -> Output {
Command::new(BIN).args(["render", pdf.to_str().unwrap(), "--page", "1", "--dpi", "36", "--out", out.to_str().unwrap()]).output().unwrap()
}
fn be32(b: &[u8]) -> u32 {
u32::from_be_bytes([b[0], b[1], b[2], b[3]])
}
#[test]
fn a_png_name_gets_a_png() {
let pdf = fixture("png");
let out = tmp("png", "p1.png");
let r = render(&pdf, &out);
assert!(r.status.success(), "{}", String::from_utf8_lossy(&r.stderr));
let bytes = std::fs::read(&out).unwrap();
assert_eq!(&bytes[..8], b"\x89PNG\r\n\x1a\n", "not a PNG: {:?}", &bytes[..bytes.len().min(16)]);
// IHDR: the page is 612×792 pt, rendered at 36 dpi
assert_eq!(&bytes[12..16], b"IHDR");
assert_eq!((be32(&bytes[16..20]), be32(&bytes[20..24])), (306, 396));
// the reported size matches the file
let stderr = String::from_utf8_lossy(&r.stderr);
assert!(stderr.contains("306×396"), "{stderr}");
}
#[test]
fn jpeg_and_tiff_names_get_those_formats() {
let pdf = fixture("jpg");
let jpg = tmp("jpg", "p1.JPG");
assert!(render(&pdf, &jpg).status.success());
assert_eq!(&std::fs::read(&jpg).unwrap()[..2], b"\xff\xd8");
let tif = tmp("jpg", "p1.tif");
assert!(render(&pdf, &tif).status.success());
let bytes = std::fs::read(&tif).unwrap();
assert!(bytes.starts_with(b"II*\0") || bytes.starts_with(b"MM\0*"), "not a TIFF: {:?}", &bytes[..4]);
}
#[test]
fn the_file_records_the_dpi_it_was_rendered_at() {
// The engine's tests check each format; this checks that `--dpi` reaches the file.
let pdf = fixture("dpi");
let jpg = tmp("dpi", "p1.jpg");
assert!(render(&pdf, &jpg).status.success());
let bytes = std::fs::read(&jpg).unwrap();
assert_eq!(&bytes[6..11], b"JFIF\0");
assert_eq!(bytes[13..18], [1, 0, 36, 0, 36], "JFIF density in dots per inch");
}
#[test]
fn a_pam_name_still_gets_the_netpbm_stream() {
let pdf = fixture("pam");
let out = tmp("pam", "p1.pam");
assert!(render(&pdf, &out).status.success());
let bytes = std::fs::read(&out).unwrap();
let header = b"P7\nWIDTH 306\nHEIGHT 396\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n";
assert!(bytes.starts_with(header), "{:?}", String::from_utf8_lossy(&bytes[..bytes.len().min(80)]));
assert_eq!(bytes.len(), header.len() + 306 * 396 * 4);
}
#[test]
fn an_unknown_extension_fails_and_writes_nothing() {
let pdf = fixture("bmp");
let out = tmp("bmp", "p1.bmp");
let r = render(&pdf, &out);
assert!(!r.status.success());
let stderr = String::from_utf8_lossy(&r.stderr);
assert!(stderr.contains(".png, .jpg, .tif or .pam"), "{stderr}");
assert!(!out.exists());
}