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)]
|
||||
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>,
|
||||
|
||||
|
@ -206,24 +206,23 @@ 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",
|
||||
};
|
||||
bail!("cannot export multiple images {s}");
|
||||
Output::Path(_) => {
|
||||
"without a page number template ({p}, {0p}) in the output path"
|
||||
}
|
||||
};
|
||||
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();
|
||||
|
||||
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user