papr-pdf 0.1.0

A fast, keyboard-first CLI tool to compile Markdown, LaTeX, and plain text files into publication-grade PDFs.
//! Headless browser runner for printing HTML to PDF.

use anyhow::{Context, Result};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use which::which;

/// Finds an installed Chromium-compatible browser binary.
pub fn find_browser_executable() -> Result<PathBuf> {
    let candidates = [
        "chromium",
        "google-chrome",
        "google-chrome-stable",
        "chrome",
        "brave",
        "microsoft-edge",
    ];

    for candidate in candidates {
        if let Ok(path) = which(candidate) {
            return Ok(path);
        }
    }

    // Also check standard user bin locations
    if let Some(home) = dirs::home_dir() {
        let home_bin_chrome = home.join("bin/google-chrome");
        if home_bin_chrome.is_file() {
            return Ok(home_bin_chrome);
        }
    }

    anyhow::bail!(
        "No Chromium-compatible browser found (checked chromium, google-chrome, brave, edge).\n\
         Please install chromium (e.g. 'sudo pacman -S chromium' or 'apt install chromium-browser')."
    )
}

/// Renders a local HTML file to a PDF using headless Chromium.
pub fn render_html_to_pdf(html_path: &Path, output_pdf: &Path) -> Result<()> {
    let browser = find_browser_executable()?;

    let abs_html = fs::canonicalize(html_path)
        .with_context(|| format!("Failed to canonicalize HTML path: {}", html_path.display()))?;
    let abs_pdf = if output_pdf.is_absolute() {
        output_pdf.to_path_buf()
    } else {
        std::env::current_dir()?.join(output_pdf)
    };

    if let Some(parent) = abs_pdf.parent() {
        fs::create_dir_all(parent)?;
    }

    let url = format!("file://{}", abs_html.display());
    let pdf_arg = format!("--print-to-pdf={}", abs_pdf.display());

    let output = Command::new(&browser)
        .arg("--headless")
        .arg("--disable-gpu")
        .arg("--no-pdf-header-footer")
        .arg("--run-all-compositor-stages-before-draw")
        .arg("--virtual-time-budget=3000") // Give KaTeX & syntax highlighting time to render
        .arg(&pdf_arg)
        .arg(&url)
        .output()
        .with_context(|| format!("Failed to execute browser at {}", browser.display()))?;

    if !output.status.success() && !abs_pdf.exists() {
        let err_str = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("Browser PDF rendering failed: {}", err_str);
    }

    if !abs_pdf.exists() {
        anyhow::bail!("PDF output file was not created at {}", abs_pdf.display());
    }

    Ok(())
}