mirror of
https://github.com/typst/typst
synced 2025-05-22 04:55:29 +08:00
CLI: Support more page number templates in output file name (#3933)
This commit is contained in:
parent
45245f0695
commit
b9457421de
@ -68,7 +68,11 @@ pub struct CompileCommand {
|
|||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub common: SharedArgs,
|
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))]
|
#[clap(required_if_eq("input", "-"), value_parser = ValueParser::new(output_value_parser))]
|
||||||
pub output: Option<Output>,
|
pub output: Option<Output>,
|
||||||
|
|
||||||
|
@ -206,25 +206,24 @@ fn export_image(
|
|||||||
watching: bool,
|
watching: bool,
|
||||||
fmt: ImageExportFormat,
|
fmt: ImageExportFormat,
|
||||||
) -> StrResult<()> {
|
) -> StrResult<()> {
|
||||||
// Determine whether we have a `{n}` numbering.
|
|
||||||
let output = command.output();
|
let output = command.output();
|
||||||
|
// Determine whether we have indexable templates in output
|
||||||
let can_handle_multiple = match output {
|
let can_handle_multiple = match output {
|
||||||
Output::Stdout => false,
|
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 {
|
if !can_handle_multiple && document.pages.len() > 1 {
|
||||||
let s = match output {
|
let err = match output {
|
||||||
Output::Stdout => "to stdout",
|
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();
|
let cache = world.export_cache();
|
||||||
|
|
||||||
// The results are collected in a `Vec<()>` which does not allocate.
|
// The results are collected in a `Vec<()>` which does not allocate.
|
||||||
@ -238,10 +237,11 @@ fn export_image(
|
|||||||
Output::Path(ref path) => {
|
Output::Path(ref path) => {
|
||||||
let storage;
|
let storage;
|
||||||
let path = if can_handle_multiple {
|
let path = if can_handle_multiple {
|
||||||
storage = path
|
storage = output_template::format(
|
||||||
.to_str()
|
path.to_str().unwrap_or_default(),
|
||||||
.unwrap_or_default()
|
i + 1,
|
||||||
.replace("{n}", &format!("{:0width$}", i + 1));
|
document.pages.len(),
|
||||||
|
);
|
||||||
Path::new(&storage)
|
Path::new(&storage)
|
||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
@ -267,6 +267,35 @@ fn export_image(
|
|||||||
Ok(())
|
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.
|
/// Export single image.
|
||||||
fn export_image_page(
|
fn export_image_page(
|
||||||
command: &CompileCommand,
|
command: &CompileCommand,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user