summaryrefslogtreecommitdiff
path: root/crates/typst-cli/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-cli/src')
-rw-r--r--crates/typst-cli/src/args.rs6
-rw-r--r--crates/typst-cli/src/compile.rs57
2 files changed, 48 insertions, 15 deletions
diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs
index bed5f49a..c115d5a9 100644
--- a/crates/typst-cli/src/args.rs
+++ b/crates/typst-cli/src/args.rs
@@ -68,7 +68,11 @@ pub struct CompileCommand {
#[clap(flatten)]
pub common: SharedArgs,
- /// Path to output file (PDF, PNG, or SVG), use `-` to write output to stdout
+ /// Path to output file (PDF, PNG, or SVG).
+ /// Use `-` to write output to stdout; For output formats emitting one file per page,
+ /// a page number template must be present if the source document renders to multiple pages.
+ /// Use `{p}` for page numbers, `{0p}` for zero padded page numbers, `{t}` for page count.
+ /// For example, `doc-page-{0p}-of-{t}.png` creates `doc-page-01-of-10.png` and so on.
#[clap(required_if_eq("input", "-"), value_parser = ValueParser::new(output_value_parser))]
pub output: Option<Output>,
diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs
index ce03fd45..abe8768f 100644
--- a/crates/typst-cli/src/compile.rs
+++ b/crates/typst-cli/src/compile.rs
@@ -206,25 +206,24 @@ fn export_image(
watching: bool,
fmt: ImageExportFormat,
) -> StrResult<()> {
- // Determine whether we have a `{n}` numbering.
let output = command.output();
+ // Determine whether we have indexable templates in output
let can_handle_multiple = match output {
Output::Stdout => false,
- Output::Path(ref output) => output.to_str().unwrap_or_default().contains("{n}"),
+ Output::Path(ref output) => {
+ output_template::has_indexable_template(output.to_str().unwrap_or_default())
+ }
};
if !can_handle_multiple && document.pages.len() > 1 {
- let s = match output {
+ let err = match output {
Output::Stdout => "to stdout",
- Output::Path(_) => "without `{n}` in output path",
+ Output::Path(_) => {
+ "without a page number template ({p}, {0p}) in the output path"
+ }
};
- bail!("cannot export multiple images {s}");
+ bail!("cannot export multiple images {err}");
}
- // Find a number width that accommodates all pages. For instance, the
- // first page should be numbered "001" if there are between 100 and
- // 999 pages.
- let width = 1 + document.pages.len().checked_ilog10().unwrap_or(0) as usize;
-
let cache = world.export_cache();
// The results are collected in a `Vec<()>` which does not allocate.
@@ -238,10 +237,11 @@ fn export_image(
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));
+ storage = output_template::format(
+ path.to_str().unwrap_or_default(),
+ i + 1,
+ document.pages.len(),
+ );
Path::new(&storage)
} else {
path
@@ -267,6 +267,35 @@ fn export_image(
Ok(())
}
+mod output_template {
+ const INDEXABLE: [&str; 3] = ["{p}", "{0p}", "{n}"];
+
+ pub fn has_indexable_template(output: &str) -> bool {
+ INDEXABLE.iter().any(|template| output.contains(template))
+ }
+
+ pub fn format(output: &str, this_page: usize, total_pages: usize) -> String {
+ // Find the base 10 width of number `i`
+ fn width(i: usize) -> usize {
+ 1 + i.checked_ilog10().unwrap_or(0) as usize
+ }
+
+ let other_templates = ["{t}"];
+ INDEXABLE.iter().chain(other_templates.iter()).fold(
+ output.to_string(),
+ |out, template| {
+ let replacement = match *template {
+ "{p}" => format!("{this_page}"),
+ "{0p}" | "{n}" => format!("{:01$}", this_page, width(total_pages)),
+ "{t}" => format!("{total_pages}"),
+ _ => unreachable!("unhandled template placeholder {template}"),
+ };
+ out.replace(template, replacement.as_str())
+ },
+ )
+ }
+}
+
/// Export single image.
fn export_image_page(
command: &CompileCommand,