diff options
| author | Ilia <43654815+istudyatuni@users.noreply.github.com> | 2024-04-01 22:36:21 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-01 19:36:21 +0000 |
| commit | e91baaca820cc83bdd90d1e440c1c9fcd077942c (patch) | |
| tree | 225015bb28e86f26d8c2e669e58318376eeb0b44 /crates/typst-cli/src/compile.rs | |
| parent | 82717b28694d56a25f1bcb381258073255ef74cd (diff) | |
Support writing document to stdout (#3632)
Diffstat (limited to 'crates/typst-cli/src/compile.rs')
| -rw-r--r-- | crates/typst-cli/src/compile.rs | 132 |
1 files changed, 87 insertions, 45 deletions
diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 9a252a53..272ca292 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -1,5 +1,6 @@ use std::fs; -use std::path::{Path, PathBuf}; +use std::io::Write; +use std::path::Path; use chrono::{Datelike, Timelike}; use codespan_reporting::diagnostic::{Diagnostic, Label}; @@ -16,7 +17,7 @@ use typst::syntax::{FileId, Source, Span}; use typst::visualize::Color; use typst::{World, WorldExt}; -use crate::args::{CompileCommand, DiagnosticFormat, Input, OutputFormat}; +use crate::args::{CompileCommand, DiagnosticFormat, Input, Output, OutputFormat}; use crate::timings::Timer; use crate::watch::Status; use crate::world::SystemWorld; @@ -27,18 +28,18 @@ type CodespanError = codespan_reporting::files::Error; impl CompileCommand { /// The output path. - pub fn output(&self) -> PathBuf { + pub fn output(&self) -> Output { self.output.clone().unwrap_or_else(|| { let Input::Path(path) = &self.common.input else { panic!("output must be specified when input is from stdin, as guarded by the CLI"); }; - path.with_extension( + Output::Path(path.with_extension( match self.output_format().unwrap_or(OutputFormat::Pdf) { OutputFormat::Pdf => "pdf", OutputFormat::Png => "png", OutputFormat::Svg => "svg", }, - ) + )) }) } @@ -48,7 +49,7 @@ impl CompileCommand { pub fn output_format(&self) -> StrResult<OutputFormat> { Ok(if let Some(specified) = self.format { specified - } else if let Some(output) = &self.output { + } else if let Some(Output::Path(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, @@ -118,7 +119,9 @@ pub fn compile_once( .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; if let Some(open) = command.open.take() { - open_file(open.as_deref(), &command.output())?; + if let Output::Path(file) = command.output() { + open_file(open.as_deref(), &file)?; + } } } @@ -164,8 +167,9 @@ fn export( /// Export to a PDF. fn export_pdf(document: &Document, command: &CompileCommand) -> StrResult<()> { let buffer = typst_pdf::pdf(document, Smart::Auto, now()); - let output = command.output(); - fs::write(output, buffer) + command + .output() + .write(&buffer) .map_err(|err| eco_format!("failed to write PDF file ({err})"))?; Ok(()) } @@ -184,12 +188,13 @@ fn now() -> Option<Datetime> { } /// An image format to export in. +#[derive(Clone, Copy)] enum ImageExportFormat { Png, Svg, } -/// Export to one or multiple PNGs. +/// Export to one or multiple images. fn export_image( world: &mut SystemWorld, document: &Document, @@ -199,10 +204,16 @@ fn export_image( ) -> StrResult<()> { // Determine whether we have a `{n}` numbering. let output = command.output(); - let string = output.to_str().unwrap_or_default(); - let numbered = string.contains("{n}"); - if !numbered && document.pages.len() > 1 { - bail!("cannot export multiple images without `{{n}}` in output path"); + let can_handle_multiple = match output { + Output::Stdout => false, + Output::Path(ref output) => output.to_str().unwrap_or_default().contains("{n}"), + }; + if !can_handle_multiple && document.pages.len() > 1 { + let s = match output { + Output::Stdout => "to stdout", + Output::Path(_) => "without `{n}` in output path", + }; + bail!("cannot export multiple images {s}"); } // Find a number width that accommodates all pages. For instance, the @@ -218,39 +229,33 @@ fn export_image( .par_iter() .enumerate() .map(|(i, page)| { - let storage; - let path = if numbered { - storage = string.replace("{n}", &format!("{:0width$}", i + 1)); - Path::new(&storage) - } else { - output.as_path() - }; - - // If we are not watching, don't use the cache. - // If the frame is in the cache, skip it. - // If the file does not exist, always create it. - if watching && cache.is_cached(i, &page.frame) && path.exists() { - return Ok(()); - } - - match fmt { - ImageExportFormat::Png => { - let pixmap = typst_render::render( - &page.frame, - command.ppi / 72.0, - Color::WHITE, - ); - pixmap - .save_png(path) - .map_err(|err| eco_format!("failed to write PNG file ({err})"))?; - } - ImageExportFormat::Svg => { - let svg = typst_svg::svg(&page.frame); - fs::write(path, svg.as_bytes()) - .map_err(|err| eco_format!("failed to write SVG file ({err})"))?; + // Use output with converted path. + let output = match output { + Output::Path(ref path) => { + let storage; + let path = if can_handle_multiple { + storage = path + .to_str() + .unwrap_or_default() + .replace("{n}", &format!("{:0width$}", i + 1)); + Path::new(&storage) + } else { + path + }; + + // If we are not watching, don't use the cache. + // If the frame is in the cache, skip it. + // If the file does not exist, always create it. + if watching && cache.is_cached(i, &page.frame) && path.exists() { + return Ok(()); + } + + Output::Path(path.to_owned()) } - } + Output::Stdout => Output::Stdout, + }; + export_image_page(command, &page.frame, &output, fmt)?; Ok(()) }) .collect::<Result<Vec<()>, EcoString>>()?; @@ -258,6 +263,43 @@ fn export_image( Ok(()) } +/// Export single image. +fn export_image_page( + command: &CompileCommand, + frame: &Frame, + output: &Output, + fmt: ImageExportFormat, +) -> StrResult<()> { + match fmt { + ImageExportFormat::Png => { + let pixmap = typst_render::render(frame, command.ppi / 72.0, Color::WHITE); + let buf = pixmap + .encode_png() + .map_err(|err| eco_format!("failed to encode PNG file ({err})"))?; + output + .write(&buf) + .map_err(|err| eco_format!("failed to write PNG file ({err})"))?; + } + ImageExportFormat::Svg => { + let svg = typst_svg::svg(frame); + output + .write(svg.as_bytes()) + .map_err(|err| eco_format!("failed to write SVG file ({err})"))?; + } + } + Ok(()) +} + +impl Output { + fn write(&self, buffer: &[u8]) -> StrResult<()> { + match self { + Output::Stdout => std::io::stdout().write_all(buffer), + Output::Path(path) => fs::write(path, buffer), + } + .map_err(|err| eco_format!("{err}")) + } +} + /// Caches exported files so that we can avoid re-exporting them if they haven't /// changed. /// |
