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)] #[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>,

View File

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