// Copyright (c) 2021 Teddy Wing
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
use anyhow;
use derive_builder::Builder;
use getopts::Options;
use pdf_forms::{Form, FieldType};
use serde::{Deserialize, Serialize};
use std::env;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
#[derive(Debug, Default, Deserialize, Serialize)]
struct TextForm<'a> {
#[serde(borrow)]
fields: Vec>,
}
#[derive(Debug, Builder, Deserialize, Serialize)]
struct Field<'a> {
id: usize,
#[builder(default)]
value: Option<&'a str>,
#[builder(default)]
state: Option,
}
fn print_usage(opts: &Options) {
print!(
"{}",
opts.usage("usage: formurapid [options] (--generate | --fill) PDF_FILE..."),
);
}
fn main() {
match run() {
Ok(_) => (),
Err(e) => {
eprintln!("error: {}", e);
process::exit(exitcode::SOFTWARE);
},
}
}
fn run() -> anyhow::Result<()> {
let args: Vec = env::args().collect();
let mut opts = Options::new();
opts.optflag("", "fill", "fill in the form using a markup file");
opts.optflag("", "generate", "generate helper files to fill in the form");
opts.optflag("h", "help", "print this help menu");
opts.optflag("V", "version", "show the program version");
let opt_matches = opts.parse(&args[1..])?;
if opt_matches.opt_present("h") {
print_usage(&opts);
process::exit(exitcode::USAGE);
}
if opt_matches.opt_present("V") {
println!("{}", env!("CARGO_PKG_VERSION"));
process::exit(exitcode::OK);
}
if opt_matches.free.is_empty() {
print_usage(&opts);
process::exit(exitcode::USAGE);
}
for pdf_path in &opt_matches.free {
let form_path = Path::new(&pdf_path);
let form_file_prefix = form_path
.file_stem()
.ok_or(anyhow::anyhow!("no file name"))?
.to_str()
.ok_or(anyhow::anyhow!("error converting file name"))?;
let toml_file_name = format!("{}.toml", form_file_prefix);
let toml_path = form_path.with_file_name(toml_file_name);
let output_file_name = format!("{}-filled.pdf", form_file_prefix);
let mut form = Form::load(form_path)?;
if opt_matches.opt_present("fill") {
fill(
toml_path,
form_path.with_file_name(output_file_name),
&mut form,
)?;
} else if opt_matches.opt_present("generate") {
let ids_path = form_path.with_file_name(
format!("{}-ids.pdf", form_file_prefix)
);
generate_fill_helpers(
toml_path,
ids_path,
&mut form,
)?;
} else {
print_usage(&opts);
process::exit(exitcode::USAGE);
}
}
Ok(())
}
/// Generate files to fill in the form.
///
/// Generates a TOML file at `data_path`, and a PDF with IDs entered in the
/// form's text fields in `output_path`.
fn generate_fill_helpers>(
data_path: P,
output_path: P,
form: &mut Form,
) -> anyhow::Result<()> {
let mut data = TextForm::default();
for i in 0..form.len() {
let field_type = form.get_type(i);
match field_type {
FieldType::Text => {
form.set_text(i, format!("{}", i))?;
data.fields.push(
FieldBuilder::default()
.id(i)
.value(Some(""))
.build()?
);
},
FieldType::CheckBox => {
data.fields.push(
FieldBuilder::default()
.id(i)
.state(Some(false))
.build()?
);
},
_ => (),
}
}
let mut toml_file = OpenOptions::new()
.create_new(true)
.write(true)
.open(data_path)?;
toml_file.write_all(&toml::to_vec(&data)?)?;
form.save(output_path)?;
Ok(())
}
/// Fill in `form` with values from `data_path` and write to `output_path`.
fn fill>(
data_path: P,
output_path: P,
form: &mut Form,
) -> anyhow::Result<()> {
let mut buf = Vec::new();
let mut file = File::open(data_path)?;
file.read_to_end(&mut buf)?;
let data: TextForm = toml::from_slice(&buf)?;
for field in data.fields {
if let Some(value) = field.value {
form.set_text(field.id, value.to_owned())?;
} else if let Some(state) = field.state {
form.set_check_box(field.id, state)?;
}
}
form.save(output_path)?;
Ok(())
}