From 535206726edaa913e9b0f09d7a8b056dd263fa36 Mon Sep 17 00:00:00 2001 From: Warren Lo Date: Wed, 18 Mar 2026 21:45:24 +0800 Subject: [PATCH] Add PDF export feature with print CSS --- src/main.rs | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 191 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 65e5929..2e574cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,8 @@ enum Commands { file: String, #[arg(short, long, help = "Output file path")] output: Option, + #[arg(short = 'p', long, help = "Export as PDF")] + pdf: bool, }, Batch { #[arg(help = "Directory containing markdown files")] @@ -43,6 +45,14 @@ enum Commands { #[arg(short = 'e', long, default_value = "700")] height: u32, }, + Export { + #[arg(help = "Markdown file to export")] + file: String, + #[arg(short, long, help = "Output file path")] + output: String, + #[arg(short, long, default_value = "pdf", value_parser = ["pdf", "html"])] + format: String, + }, } fn render_markdown(content: &str) -> String { @@ -247,6 +257,163 @@ fn wrap_in_html(title: &str, content: &str) -> String { ) } +fn wrap_for_pdf(title: &str, content: &str) -> String { + format!( + r#" + + + + + {} + + + + +{} + + +"#, + title, content + ) +} + +fn export_pdf(file_path: &str, output: Option) -> Result<()> { + let path = Path::new(file_path); + let content = read_file(path)?; + let html = render_markdown(&content); + let title = path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("Markdown"); + + let pdf_html = wrap_for_pdf(title, &html); + + let out_path = if let Some(o) = output { + o + } else { + let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + let out_dir = home.join("docs/pdf"); + std::fs::create_dir_all(&out_dir)?; + out_dir + .join(format!("{}.html", title)) + .to_string_lossy() + .to_string() + }; + + std::fs::write(&out_path, &pdf_html) + .with_context(|| format!("Failed to write to {}", out_path))?; + + println!("PDF export file: {}", out_path); + println!("Open this file in browser and use File > Print > Save as PDF"); + + open::that(&out_path).context("Failed to open file in browser")?; + + Ok(()) +} + fn read_file(path: &Path) -> Result { std::fs::read_to_string(path) .with_context(|| format!("Failed to read file: {}", path.display())) @@ -467,10 +634,17 @@ fn main() { let cli = Cli::parse(); match cli.command { - Commands::Render { file, output } => { - if let Err(e) = process_file(&file, output) { - eprintln!("Error: {}", e); - std::process::exit(1); + Commands::Render { file, output, pdf } => { + if pdf { + if let Err(e) = export_pdf(&file, output) { + eprintln!("Error: {}", e); + std::process::exit(1); + } + } else { + if let Err(e) = process_file(&file, output) { + eprintln!("Error: {}", e); + std::process::exit(1); + } } } Commands::Batch { directory, output } => { @@ -503,5 +677,18 @@ fn main() { std::process::exit(1); } } + Commands::Export { file, output, format } => { + if format == "pdf" { + if let Err(e) = export_pdf(&file, Some(output)) { + eprintln!("Export error: {}", e); + std::process::exit(1); + } + } else { + if let Err(e) = process_file(&file, Some(output)) { + eprintln!("Export error: {}", e); + std::process::exit(1); + } + } + } } }