CLI: Support more page number templates in output file name (#3933)

This commit is contained in:
Karthik Nishanth 2024-04-19 07:32:06 -07:00 committed by GitHub
parent 45245f0695
commit b9457421de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 15 deletions

View File

@ -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>,

View File

@ -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,