mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-01 05:58:12 -06:00
Issue #248: pdfcraft-cli render writes the format its --out name says (.png, .jpg, .tif or .pam) (#255)
`render --out p1.png` wrote a netpbm PAM stream (P7) whatever the name, so a `.png` could not be opened by any PNG reader. The CLI already links the engine, whose export module encodes PNG, JPEG and TIFF, so the output format now follows the extension: `.png`, `.jpg`/`.jpeg` (quality 90), `.tif`/`.tiff`, and `.pam` for the raw stream as before. Any other name is refused up front, before the page is rendered, with the accepted list. `export::encode_image` wraps the three encoders behind `ImageFormat` so the CLI and `Exporter::image` share one match. Tests (apps/pdfcraft-cli/tests/render.rs): a `.png` gets the PNG signature and an IHDR with the rendered size; `.JPG` and `.tif` get JPEG/TIFF magic; `.pam` still gets the P7 header and the right number of bytes; `.bmp` fails with the message and writes nothing. Co-authored-by: wstock <1394687+wstock@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
wstock
Claude Fable 5.1
Brandon Thomas
parent
776c8f0742
commit
505c4a2fa3
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! ```text
|
||||
//! pdfcraft-cli info <file.pdf> [--password PW] document summary as JSON
|
||||
//! pdfcraft-cli render <file.pdf> --page N [--dpi 96] --out x.pam
|
||||
//! pdfcraft-cli render <file.pdf> --page N [--dpi 96] --out x.png (.png, .jpg, .tif or .pam)
|
||||
//! pdfcraft-cli text <file.pdf> [--page N] extracted text (pages separated by form feeds)
|
||||
//! pdfcraft-cli edit <in.pdf> --out out.pdf [--rotate 1,3:90] [--delete 2,4] [--move 5:1]
|
||||
//! [--insert-blank 1] [--title T] [--author A] [--full]
|
||||
@@ -39,6 +39,7 @@ use std::process::{Command, ExitCode, Stdio};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use pdfcraft_engine::export::ImageFormat;
|
||||
use pdfcraft_render::{PageRenderer, RenderConfig, RenderRequest, RequestKind, inspect};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
@@ -333,16 +334,30 @@ fn render(args: &[String]) -> Result<(), CliError> {
|
||||
let path = *positional(args).first().ok_or("render: missing file")?;
|
||||
let page: usize = flag(args, "--page").unwrap_or("1").parse().map_err(|_| "bad --page")?;
|
||||
let dpi: f32 = flag(args, "--dpi").unwrap_or("96").parse().map_err(|_| "bad --dpi")?;
|
||||
let out = flag(args, "--out").ok_or("render: missing --out (.pam)")?;
|
||||
let out = flag(args, "--out").ok_or("render: missing --out (.png, .jpg, .tif or .pam)")?;
|
||||
// The file is what its name says (#248): a `.png` used to get a netpbm PAM stream.
|
||||
let format = match Path::new(out).extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase).as_deref() {
|
||||
Some("png") => Some(ImageFormat::Png),
|
||||
Some("jpg" | "jpeg") => Some(ImageFormat::Jpeg { quality: 90 }),
|
||||
Some("tif" | "tiff") => Some(ImageFormat::Tiff),
|
||||
Some("pam") => None,
|
||||
_ => return Err(format!("render: --out {out}: use a .png, .jpg, .tif or .pam name").into()),
|
||||
};
|
||||
let mut r = PageRenderer::new(read(path)?, RenderConfig { password: flag(args, "--password").map(Arc::from), ..Default::default() });
|
||||
let p = r.render(RenderRequest { page: page.saturating_sub(1), kind: RequestKind::Pixels, tile: None, scale: dpi / 72.0, tag: 0 });
|
||||
if let Some(e) = p.error {
|
||||
return Err(e.into());
|
||||
}
|
||||
// PAM (netpbm RGB_ALPHA) keeps this tool dependency-free; convert with any image tool.
|
||||
let mut f = std::fs::File::create(out).map_err(|e| e.to_string())?;
|
||||
write!(f, "P7\nWIDTH {}\nHEIGHT {}\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n", p.width, p.height).map_err(|e| e.to_string())?;
|
||||
f.write_all(&p.rgba).map_err(|e| e.to_string())?;
|
||||
let bytes = match format {
|
||||
Some(f) => pdfcraft_engine::export::encode_image(p.width, p.height, &p.rgba, f)?,
|
||||
None => {
|
||||
// PAM (netpbm RGB_ALPHA): the raw premultiplied pixels, for tools that read it.
|
||||
let mut pam = format!("P7\nWIDTH {}\nHEIGHT {}\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n", p.width, p.height).into_bytes();
|
||||
pam.extend_from_slice(&p.rgba);
|
||||
pam
|
||||
}
|
||||
};
|
||||
std::fs::write(out, bytes).map_err(|e| format!("{out}: {e}"))?;
|
||||
eprintln!("rendered page {page} at {dpi} dpi: {}×{} px in {} ms", p.width, p.height, p.millis);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user