Files
pdfcraft/apps/pdfcraft-cli/tests/render.rs
T
aiongg 4cebd0c771 Merge PR #2935: Fix/cli option value errors
contributors/people.toml: union of both new entries
2026-10-22 01:45:25 +00:00

115 lines
5.0 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());
}
/// `--page` and `--dpi` are checked before the file is opened. A page that is not a positive
/// whole number names the same expectation `text` names; a dpi that parses as an `f32` but
/// cannot be rendered (`0`, a negative value, `NaN`) names the option and the value, instead
/// of reaching the renderer as a scale it refuses with its own internal message.
#[test]
fn a_page_or_dpi_the_renderer_cannot_use_is_refused_before_reading_the_file() {
for (option, value, diagnostic) in [
("--page", "abc", "bad --page: expected a positive page number"),
("--page", "0", "bad --page: expected a positive page number"),
("--dpi", "abc", "bad --dpi: expected a positive number of dots per inch, got \"abc\""),
("--dpi", "0", "bad --dpi: expected a positive number of dots per inch, got \"0\""),
("--dpi", "-3", "bad --dpi: expected a positive number of dots per inch, got \"-3\""),
("--dpi", "NaN", "bad --dpi: expected a positive number of dots per inch, got \"NaN\""),
("--dpi", "inf", "bad --dpi: expected a positive number of dots per inch, got \"inf\""),
] {
let out = tmp("refuse", "out.png");
// `missing.pdf` is never read: the option's own message is the error.
let r = Command::new(BIN).args(["render", "missing.pdf", "--out", out.to_str().unwrap(), option, value]).output().unwrap();
assert_eq!(r.status.code(), Some(1), "{option} {value}");
assert_eq!(r.stderr, format!("pdfcraft-cli: {diagnostic}\n").as_bytes(), "{option} {value}");
assert!(!out.exists(), "{option} {value}: no output may be written");
}
}