use std::collections::BTreeMap;
use std::fmt;
use std::num::NonZeroUsize;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompileOptions {
pub inputs: BTreeMap<String, String>,
pub pdf_standards: Vec<PdfStandard>,
pub creation_timestamp: Option<i64>,
pub pages: Vec<PageRange>,
pub pdf_tags: bool,
}
impl Default for CompileOptions {
fn default() -> Self {
Self {
inputs: BTreeMap::new(),
pdf_standards: Vec::new(),
creation_timestamp: None,
pages: Vec::new(),
pdf_tags: true,
}
}
}
macro_rules! pdf_standards {
($($variant:ident => $name:literal, $typst:ident, $doc:literal;)*) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PdfStandard {
$(#[doc = $doc] $variant,)*
}
impl PdfStandard {
pub const ALL: &'static [PdfStandard] = &[$(PdfStandard::$variant,)*];
pub fn name(self) -> &'static str {
match self {
$(PdfStandard::$variant => $name,)*
}
}
pub(crate) fn to_typst(self) -> typst_pdf::PdfStandard {
match self {
$(PdfStandard::$variant => typst_pdf::PdfStandard::$typst,)*
}
}
}
};
}
pdf_standards! {
V1_4 => "1.4", V_1_4, "PDF 1.4.";
V1_5 => "1.5", V_1_5, "PDF 1.5.";
V1_6 => "1.6", V_1_6, "PDF 1.6.";
V1_7 => "1.7", V_1_7, "PDF 1.7.";
V2_0 => "2.0", V_2_0, "PDF 2.0.";
A1b => "a-1b", A_1b, "PDF/A-1b.";
A1a => "a-1a", A_1a, "PDF/A-1a.";
A2b => "a-2b", A_2b, "PDF/A-2b.";
A2u => "a-2u", A_2u, "PDF/A-2u.";
A2a => "a-2a", A_2a, "PDF/A-2a.";
A3b => "a-3b", A_3b, "PDF/A-3b.";
A3u => "a-3u", A_3u, "PDF/A-3u.";
A3a => "a-3a", A_3a, "PDF/A-3a.";
A4 => "a-4", A_4, "PDF/A-4.";
A4f => "a-4f", A_4f, "PDF/A-4f.";
A4e => "a-4e", A_4e, "PDF/A-4e.";
Ua1 => "ua-1", Ua_1, "PDF/UA-1.";
}
impl fmt::Display for PdfStandard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl FromStr for PdfStandard {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::ALL
.iter()
.copied()
.find(|standard| standard.name() == s)
.ok_or_else(|| {
let names: Vec<_> = Self::ALL.iter().map(|standard| standard.name()).collect();
format!(
"unknown PDF standard {s:?}, expected one of: {}",
names.join(", ")
)
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PageRange {
pub first: Option<NonZeroUsize>,
pub last: Option<NonZeroUsize>,
}
impl FromStr for PageRange {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let page = |value: &str| -> Result<Option<NonZeroUsize>, String> {
if value.is_empty() {
return Ok(None);
}
value
.parse::<NonZeroUsize>()
.map(Some)
.map_err(|_| format!("invalid page number {value:?} in page range {s:?}"))
};
let range = match s.split_once('-') {
Some((first, last)) => Self {
first: page(first)?,
last: page(last)?,
},
None => {
let single = page(s)?;
if single.is_none() {
return Err("empty page range".into());
}
Self {
first: single,
last: single,
}
}
};
if let (Some(first), Some(last)) = (range.first, range.last) {
if first > last {
return Err(format!(
"page range {s:?} starts after it ends ({first} > {last})"
));
}
}
Ok(range)
}
}