1mod attach;
4mod convert;
5mod image;
6mod link;
7mod metadata;
8mod outline;
9mod page;
10mod paint;
11mod shape;
12mod tags;
13mod text;
14mod util;
15
16pub use self::metadata::{Timestamp, Timezone};
17
18use std::fmt::{self, Debug, Formatter};
19use std::hash::{Hash, Hasher};
20
21use comemo::Tracked;
22use ecow::{EcoString, eco_format};
23use krilla::configure::Accessibility;
24use serde::{Deserialize, Serialize};
25use typst_layout::PagedDocument;
26use typst_library::diag::{HintedStrResult, HintedString, SourceResult, StrResult, bail};
27use typst_library::foundations::Smart;
28use typst_library::introspection::Location;
29use typst_library::layout::PageRanges;
30use typst_library::model::LateLinkResolver;
31
32#[typst_macros::time(name = "pdf")]
36pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult<Vec<u8>> {
37 convert::convert(document, options, &[], None)
38}
39
40#[typst_macros::time(name = "pdf in bundle")]
47pub fn pdf_in_bundle(
48 document: &PagedDocument,
49 options: &PdfOptions,
50 anchors: &[(Location, EcoString)],
51 link_resolver: Tracked<LateLinkResolver>,
52) -> SourceResult<Vec<u8>> {
53 convert::convert(document, options, anchors, Some(link_resolver))
54}
55
56#[derive(Debug, Hash)]
58pub struct PdfOptions {
59 pub ident: Smart<String>,
71 pub creator: Smart<Option<String>>,
74 pub timestamp: Option<Timestamp>,
77 pub page_ranges: Option<PageRanges>,
80 pub standards: PdfStandards,
82 pub tagged: bool,
87 pub pretty: bool,
89}
90
91impl PdfOptions {
92 pub(crate) fn accessibility_validator(&self) -> Option<Accessibility> {
95 self.standards.config.validators().accessibility()
96 }
97}
98
99impl Default for PdfOptions {
100 fn default() -> Self {
101 Self {
102 ident: Smart::Auto,
103 creator: Smart::Auto,
104 timestamp: None,
105 page_ranges: None,
106 standards: PdfStandards::default(),
107 tagged: true,
108 pretty: false,
109 }
110 }
111}
112
113#[derive(Clone)]
115pub struct PdfStandards {
116 pub(crate) config: krilla::configure::Configuration,
117}
118
119impl PdfStandards {
120 pub fn new(list: &[PdfStandard]) -> HintedStrResult<Self> {
123 use krilla::configure::{
124 Accessibility, Archival, ConfigurationBuilder, ConfigurationError, PdfVersion,
125 };
126
127 use crate::util::ValidatorsExt;
128
129 let mut version: Option<PdfVersion> = None;
130 let mut set_version = |v: PdfVersion| -> StrResult<()> {
131 if let Some(prev) = version {
132 bail!(
133 "PDF cannot conform to {} and {} at the same time",
134 prev.as_str(),
135 v.as_str(),
136 );
137 }
138 version = Some(v);
139 Ok(())
140 };
141
142 let mut archival_validator = None;
143 let mut set_archival_validator = |a: Archival| -> StrResult<()> {
144 if archival_validator.is_some() {
145 bail!("choose at most one PDF/A standard");
146 }
147 archival_validator = Some(a);
148 Ok(())
149 };
150
151 let mut accessibility_validator = None;
152 let mut set_accessibility_validator = |ua: Accessibility| -> StrResult<()> {
153 if accessibility_validator.is_some() {
154 bail!("choose at most one PDF/UA standard");
155 }
156 accessibility_validator = Some(ua);
157 Ok(())
158 };
159
160 for standard in list {
161 match standard {
162 PdfStandard::V_1_4 => set_version(PdfVersion::Pdf14)?,
163 PdfStandard::V_1_5 => set_version(PdfVersion::Pdf15)?,
164 PdfStandard::V_1_6 => set_version(PdfVersion::Pdf16)?,
165 PdfStandard::V_1_7 => set_version(PdfVersion::Pdf17)?,
166 PdfStandard::V_2_0 => set_version(PdfVersion::Pdf20)?,
167 PdfStandard::A_1b => set_archival_validator(Archival::A1_B)?,
168 PdfStandard::A_1a => set_archival_validator(Archival::A1_A)?,
169 PdfStandard::A_2b => set_archival_validator(Archival::A2_B)?,
170 PdfStandard::A_2u => set_archival_validator(Archival::A2_U)?,
171 PdfStandard::A_2a => set_archival_validator(Archival::A2_A)?,
172 PdfStandard::A_3b => set_archival_validator(Archival::A3_B)?,
173 PdfStandard::A_3u => set_archival_validator(Archival::A3_U)?,
174 PdfStandard::A_3a => set_archival_validator(Archival::A3_A)?,
175 PdfStandard::A_4 => set_archival_validator(Archival::A4)?,
176 PdfStandard::A_4f => set_archival_validator(Archival::A4F)?,
177 PdfStandard::A_4e => set_archival_validator(Archival::A4E)?,
178 PdfStandard::Ua_1 => set_accessibility_validator(Accessibility::UA1)?,
179 }
180 }
181
182 let mut builder = ConfigurationBuilder::new();
183
184 if let Some(version) = version {
185 builder = builder.with_version(version)
186 }
187
188 if let Some(archival_validator) = archival_validator {
189 builder = builder.with_archival_validator(archival_validator)
190 }
191
192 if let Some(accessibility_validator) = accessibility_validator {
193 builder = builder.with_accessibility_validator(accessibility_validator)
194 }
195
196 let config = builder.finish().map_err(|e| {
197 let (message, validators) = match e {
198 ConfigurationError::NoOverlappingValidatorsRange(validators) => {
199 let list = validators.to_and_list();
200 let message = eco_format!(
201 "{list} are mutually incompatible because \
202 they do not have any overlapping PDF versions"
203 );
204 (message, validators)
205 }
206 ConfigurationError::VersionDoesNotMatchValidatorsRange(
207 version,
208 validators,
209 ) => {
210 let list = validators.to_and_list();
211 let message =
212 eco_format!("{} is not compatible with {list}", version.as_str());
213 (message, validators)
214 }
215 };
216 HintedString::new(message)
217 .with_hints(validators.into_iter().map(version_hint))
218 })?;
219
220 Ok(Self { config })
221 }
222}
223
224fn version_hint(validator: krilla::configure::Validator) -> EcoString {
226 let min = validator.min();
227 let max = validator.max();
228 if let Some(min) = min {
229 if min == max {
230 eco_format!("{} requires version {}", validator.as_str(), min.as_str())
231 } else {
232 eco_format!(
233 "{} requires a version between {} and {}",
234 validator.as_str(),
235 min.as_str(),
236 max.as_str()
237 )
238 }
239 } else {
240 eco_format!("{} requires at least {}", validator.as_str(), max.as_str())
241 }
242}
243
244impl Debug for PdfStandards {
245 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
246 f.pad("PdfStandards(..)")
247 }
248}
249
250impl Default for PdfStandards {
251 fn default() -> Self {
252 use krilla::configure::{ConfigurationBuilder, PdfVersion};
253 Self {
254 config: ConfigurationBuilder::new()
255 .with_version(PdfVersion::Pdf17)
256 .finish()
257 .unwrap(),
258 }
259 }
260}
261
262impl Hash for PdfStandards {
265 fn hash<H: Hasher>(&self, state: &mut H) {
266 (self.config.version() as usize).hash(state);
267 for validator in self.config.validators() {
268 validator.hash(state);
269 }
270 }
271}
272
273#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
277#[allow(non_camel_case_types)]
278#[non_exhaustive]
279pub enum PdfStandard {
280 #[serde(rename = "1.4")]
282 V_1_4,
283 #[serde(rename = "1.5")]
285 V_1_5,
286 #[serde(rename = "1.6")]
288 V_1_6,
289 #[serde(rename = "1.7")]
291 V_1_7,
292 #[serde(rename = "2.0")]
294 V_2_0,
295 #[serde(rename = "a-1b")]
297 A_1b,
298 #[serde(rename = "a-1a")]
300 A_1a,
301 #[serde(rename = "a-2b")]
303 A_2b,
304 #[serde(rename = "a-2u")]
306 A_2u,
307 #[serde(rename = "a-2a")]
309 A_2a,
310 #[serde(rename = "a-3b")]
312 A_3b,
313 #[serde(rename = "a-3u")]
315 A_3u,
316 #[serde(rename = "a-3a")]
318 A_3a,
319 #[serde(rename = "a-4")]
321 A_4,
322 #[serde(rename = "a-4f")]
324 A_4f,
325 #[serde(rename = "a-4e")]
327 A_4e,
328 #[serde(rename = "ua-1")]
330 Ua_1,
331}