1use anyhow::Result;
4
5use crate::html::render_html;
6use crate::snapshot::ProcessSnapshot;
7
8pub fn export_pdf(snapshot: &ProcessSnapshot) -> Result<String> {
14 let html = render_html(snapshot);
15 let info = &snapshot.process;
16
17 let renderer = ["wkhtmltopdf", "weasyprint", "chromium", "google-chrome"]
19 .iter()
20 .find(|cmd| which::which(cmd).is_ok())
21 .copied();
22
23 let Some(renderer) = renderer else {
24 anyhow::bail!(
25 "PDF export requires wkhtmltopdf, weasyprint, or Chromium. \
26 Install one and try again, or use --export html."
27 );
28 };
29
30 let filename = format!("peek-{}-{}.pdf", info.name, info.pid);
31 let out_path = std::env::current_dir()
32 .unwrap_or_else(|_| std::path::PathBuf::from("."))
33 .join(&filename);
34 let out_path_str = out_path
35 .to_str()
36 .ok_or_else(|| anyhow::anyhow!("output path is not valid UTF-8"))?
37 .to_string();
38
39 let tmp = std::env::temp_dir().join(format!("peek-{}.html", info.pid));
41 std::fs::write(&tmp, &html)?;
42 let tmp_str = tmp.to_string_lossy();
43
44 let status = match renderer {
45 "wkhtmltopdf" => std::process::Command::new("wkhtmltopdf")
46 .args([tmp_str.as_ref(), out_path_str.as_str()])
47 .status()?,
48 "weasyprint" => std::process::Command::new("weasyprint")
49 .args([tmp_str.as_ref(), out_path_str.as_str()])
50 .status()?,
51 _ => std::process::Command::new(renderer)
52 .args([
53 "--headless",
54 "--disable-gpu",
55 "--print-to-pdf",
56 &out_path_str,
57 &format!("file://{}", tmp.display()),
58 ])
59 .status()?,
60 };
61
62 let _ = std::fs::remove_file(&tmp);
63
64 if !status.success() {
65 anyhow::bail!("{} exited with status {:?}", renderer, status.code());
66 }
67
68 Ok(out_path_str)
69}