diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index 6bc584439..2696a0319 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -46,6 +46,10 @@ pub struct CompileCommand { /// Path to output file (PDF, PNG, or SVG) pub output: Option, + /// The format of the output file, inferred from the extension by default + #[arg(long = "format", short = 'f')] + pub format: Option, + /// Opens the output file using the default viewer after compilation #[arg(long = "open")] pub open: Option>, @@ -59,15 +63,6 @@ pub struct CompileCommand { pub flamegraph: Option>, } -impl CompileCommand { - /// The output path. - pub fn output(&self) -> PathBuf { - self.output - .clone() - .unwrap_or_else(|| self.common.input.with_extension("pdf")) - } -} - /// Processes an input file to extract provided metadata #[derive(Debug, Clone, Parser)] pub struct QueryCommand { @@ -158,3 +153,20 @@ impl Display for DiagnosticFormat { .fmt(f) } } + +/// Which format to use for the generated output file. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] +pub enum OutputFormat { + Pdf, + Png, + Svg, +} + +impl Display for OutputFormat { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.to_possible_value() + .expect("no values are skipped") + .get_name() + .fmt(f) + } +} diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 5b51f4223..4c4c24be5 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -1,5 +1,5 @@ use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term::{self, termcolor}; @@ -11,7 +11,7 @@ use typst::geom::Color; use typst::syntax::{FileId, Source}; use typst::World; -use crate::args::{CompileCommand, DiagnosticFormat}; +use crate::args::{CompileCommand, DiagnosticFormat, OutputFormat}; use crate::watch::Status; use crate::world::SystemWorld; use crate::{color_stream, set_failed}; @@ -19,6 +19,33 @@ use crate::{color_stream, set_failed}; type CodespanResult = Result; type CodespanError = codespan_reporting::files::Error; +impl CompileCommand { + /// The output path. + pub fn output(&self) -> PathBuf { + self.output + .clone() + .unwrap_or_else(|| self.common.input.with_extension("pdf")) + } + + /// The format to use for generated output, either specified by the user or inferred from the extension. + /// + /// Will return `Err` if the format was not specified and could not be inferred. + pub fn output_format(&self) -> StrResult { + Ok(if let Some(specified) = self.format { + specified + } else if let Some(output) = &self.output { + match output.extension() { + Some(ext) if ext.eq_ignore_ascii_case("pdf") => OutputFormat::Pdf, + Some(ext) if ext.eq_ignore_ascii_case("png") => OutputFormat::Png, + Some(ext) if ext.eq_ignore_ascii_case("svg") => OutputFormat::Svg, + _ => bail!("could not infer output format for path {}.\nconsider providing the format manually with `--format/-f`", output.display()), + } + } else { + OutputFormat::Pdf + }) + } +} + /// Execute a compilation command. pub fn compile(mut command: CompileCommand) -> StrResult<()> { let mut world = SystemWorld::new(&command.common)?; @@ -97,14 +124,10 @@ pub fn compile_once( /// Export into the target format. fn export(document: &Document, command: &CompileCommand) -> StrResult<()> { - match command.output().extension() { - Some(ext) if ext.eq_ignore_ascii_case("png") => { - export_image(document, command, ImageExportFormat::Png) - } - Some(ext) if ext.eq_ignore_ascii_case("svg") => { - export_image(document, command, ImageExportFormat::Svg) - } - _ => export_pdf(document, command), + match command.output_format()? { + OutputFormat::Png => export_image(document, command, ImageExportFormat::Png), + OutputFormat::Svg => export_image(document, command, ImageExportFormat::Svg), + OutputFormat::Pdf => export_pdf(document, command), } }