mod attach;
mod convert;
mod image;
mod link;
mod metadata;
mod outline;
mod page;
mod paint;
mod shape;
mod tags;
mod text;
mod util;
pub use self::metadata::{Timestamp, Timezone};
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use comemo::Tracked;
use ecow::{EcoString, eco_format};
use krilla::configure::Accessibility;
use serde::{Deserialize, Serialize};
use typst_layout::PagedDocument;
use typst_library::diag::{HintedStrResult, HintedString, SourceResult, StrResult, bail};
use typst_library::foundations::Smart;
use typst_library::introspection::Location;
use typst_library::layout::PageRanges;
use typst_library::model::LateLinkResolver;
#[typst_macros::time(name = "pdf")]
pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult<Vec<u8>> {
convert::convert(document, options, &[], None)
}
#[typst_macros::time(name = "pdf in bundle")]
pub fn pdf_in_bundle(
document: &PagedDocument,
options: &PdfOptions,
anchors: &[(Location, EcoString)],
link_resolver: Tracked<LateLinkResolver>,
) -> SourceResult<Vec<u8>> {
convert::convert(document, options, anchors, Some(link_resolver))
}
#[derive(Debug, Hash)]
pub struct PdfOptions {
pub ident: Smart<String>,
pub creator: Smart<Option<String>>,
pub timestamp: Option<Timestamp>,
pub page_ranges: Option<PageRanges>,
pub standards: PdfStandards,
pub tagged: bool,
pub pretty: bool,
}
impl PdfOptions {
pub(crate) fn accessibility_validator(&self) -> Option<Accessibility> {
self.standards.config.validators().accessibility()
}
}
impl Default for PdfOptions {
fn default() -> Self {
Self {
ident: Smart::Auto,
creator: Smart::Auto,
timestamp: None,
page_ranges: None,
standards: PdfStandards::default(),
tagged: true,
pretty: false,
}
}
}
#[derive(Clone)]
pub struct PdfStandards {
pub(crate) config: krilla::configure::Configuration,
}
impl PdfStandards {
pub fn new(list: &[PdfStandard]) -> HintedStrResult<Self> {
use krilla::configure::{
Accessibility, Archival, ConfigurationBuilder, ConfigurationError, PdfVersion,
};
use crate::util::ValidatorsExt;
let mut version: Option<PdfVersion> = None;
let mut set_version = |v: PdfVersion| -> StrResult<()> {
if let Some(prev) = version {
bail!(
"PDF cannot conform to {} and {} at the same time",
prev.as_str(),
v.as_str(),
);
}
version = Some(v);
Ok(())
};
let mut archival_validator = None;
let mut set_archival_validator = |a: Archival| -> StrResult<()> {
if archival_validator.is_some() {
bail!("choose at most one PDF/A standard");
}
archival_validator = Some(a);
Ok(())
};
let mut accessibility_validator = None;
let mut set_accessibility_validator = |ua: Accessibility| -> StrResult<()> {
if accessibility_validator.is_some() {
bail!("choose at most one PDF/UA standard");
}
accessibility_validator = Some(ua);
Ok(())
};
for standard in list {
match standard {
PdfStandard::V_1_4 => set_version(PdfVersion::Pdf14)?,
PdfStandard::V_1_5 => set_version(PdfVersion::Pdf15)?,
PdfStandard::V_1_6 => set_version(PdfVersion::Pdf16)?,
PdfStandard::V_1_7 => set_version(PdfVersion::Pdf17)?,
PdfStandard::V_2_0 => set_version(PdfVersion::Pdf20)?,
PdfStandard::A_1b => set_archival_validator(Archival::A1_B)?,
PdfStandard::A_1a => set_archival_validator(Archival::A1_A)?,
PdfStandard::A_2b => set_archival_validator(Archival::A2_B)?,
PdfStandard::A_2u => set_archival_validator(Archival::A2_U)?,
PdfStandard::A_2a => set_archival_validator(Archival::A2_A)?,
PdfStandard::A_3b => set_archival_validator(Archival::A3_B)?,
PdfStandard::A_3u => set_archival_validator(Archival::A3_U)?,
PdfStandard::A_3a => set_archival_validator(Archival::A3_A)?,
PdfStandard::A_4 => set_archival_validator(Archival::A4)?,
PdfStandard::A_4f => set_archival_validator(Archival::A4F)?,
PdfStandard::A_4e => set_archival_validator(Archival::A4E)?,
PdfStandard::Ua_1 => set_accessibility_validator(Accessibility::UA1)?,
}
}
let mut builder = ConfigurationBuilder::new();
if let Some(version) = version {
builder = builder.with_version(version)
}
if let Some(archival_validator) = archival_validator {
builder = builder.with_archival_validator(archival_validator)
}
if let Some(accessibility_validator) = accessibility_validator {
builder = builder.with_accessibility_validator(accessibility_validator)
}
let config = builder.finish().map_err(|e| {
let (message, validators) = match e {
ConfigurationError::NoOverlappingValidatorsRange(validators) => {
let list = validators.to_and_list();
let message = eco_format!(
"{list} are mutually incompatible because \
they do not have any overlapping PDF versions"
);
(message, validators)
}
ConfigurationError::VersionDoesNotMatchValidatorsRange(
version,
validators,
) => {
let list = validators.to_and_list();
let message =
eco_format!("{} is not compatible with {list}", version.as_str());
(message, validators)
}
};
HintedString::new(message)
.with_hints(validators.into_iter().map(version_hint))
})?;
Ok(Self { config })
}
}
fn version_hint(validator: krilla::configure::Validator) -> EcoString {
let min = validator.min();
let max = validator.max();
if let Some(min) = min {
if min == max {
eco_format!("{} requires version {}", validator.as_str(), min.as_str())
} else {
eco_format!(
"{} requires a version between {} and {}",
validator.as_str(),
min.as_str(),
max.as_str()
)
}
} else {
eco_format!("{} requires at least {}", validator.as_str(), max.as_str())
}
}
impl Debug for PdfStandards {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad("PdfStandards(..)")
}
}
impl Default for PdfStandards {
fn default() -> Self {
use krilla::configure::{ConfigurationBuilder, PdfVersion};
Self {
config: ConfigurationBuilder::new()
.with_version(PdfVersion::Pdf17)
.finish()
.unwrap(),
}
}
}
impl Hash for PdfStandards {
fn hash<H: Hasher>(&self, state: &mut H) {
(self.config.version() as usize).hash(state);
for validator in self.config.validators() {
validator.hash(state);
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[allow(non_camel_case_types)]
#[non_exhaustive]
pub enum PdfStandard {
#[serde(rename = "1.4")]
V_1_4,
#[serde(rename = "1.5")]
V_1_5,
#[serde(rename = "1.6")]
V_1_6,
#[serde(rename = "1.7")]
V_1_7,
#[serde(rename = "2.0")]
V_2_0,
#[serde(rename = "a-1b")]
A_1b,
#[serde(rename = "a-1a")]
A_1a,
#[serde(rename = "a-2b")]
A_2b,
#[serde(rename = "a-2u")]
A_2u,
#[serde(rename = "a-2a")]
A_2a,
#[serde(rename = "a-3b")]
A_3b,
#[serde(rename = "a-3u")]
A_3u,
#[serde(rename = "a-3a")]
A_3a,
#[serde(rename = "a-4")]
A_4,
#[serde(rename = "a-4f")]
A_4f,
#[serde(rename = "a-4e")]
A_4e,
#[serde(rename = "ua-1")]
Ua_1,
}