typst_library/model/
document.rs

1use ecow::EcoString;
2
3use crate::diag::{HintedStrResult, SourceResult, bail};
4use crate::engine::Engine;
5use crate::foundations::{
6    Args, Array, Construct, Content, Datetime, OneOrMultiple, Smart, StyleChain, Styles,
7    Value, cast, elem,
8};
9use crate::text::{Locale, TextElem};
10
11/// The root element of a document and its metadata.
12///
13/// All documents are automatically wrapped in a `document` element. You cannot
14/// create a document element yourself. This function is only used with
15/// [set rules]($styling/#set-rules) to specify document metadata. Such a set
16/// rule must not occur inside of any layout container.
17///
18/// ```example
19/// #set document(title: [Hello])
20///
21/// This has no visible output, but
22/// embeds metadata into the PDF!
23/// ```
24///
25/// Note that metadata set with this function is not rendered within the
26/// document. Instead, it is embedded in the compiled PDF file.
27#[elem(Construct)]
28pub struct DocumentElem {
29    /// The document's title. This is rendered as the title of the PDF viewer
30    /// window or the browser tab of the page.
31    ///
32    /// Adding a title is important for accessibility, as it makes it easier to
33    /// navigate to your document and identify it among other open documents.
34    /// When exporting to PDF/UA, a title is required.
35    ///
36    /// While this can be arbitrary content, PDF viewers only support plain text
37    /// titles, so the conversion might be lossy.
38    #[ghost]
39    pub title: Option<Content>,
40
41    /// The document's authors.
42    #[ghost]
43    pub author: OneOrMultiple<EcoString>,
44
45    /// The document's description.
46    #[ghost]
47    pub description: Option<Content>,
48
49    /// The document's keywords.
50    #[ghost]
51    pub keywords: OneOrMultiple<EcoString>,
52
53    /// The document's creation date.
54    ///
55    /// If this is `{auto}` (default), Typst uses the current date and time.
56    /// Setting it to `{none}` prevents Typst from embedding any creation date
57    /// into the PDF metadata.
58    ///
59    /// The year component must be at least zero in order to be embedded into a
60    /// PDF.
61    ///
62    /// If you want to create byte-by-byte reproducible PDFs, set this to
63    /// something other than `{auto}`.
64    #[ghost]
65    pub date: Smart<Option<Datetime>>,
66}
67
68impl Construct for DocumentElem {
69    fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
70        bail!(args.span, "can only be used in set rules")
71    }
72}
73
74/// A list of authors.
75#[derive(Debug, Default, Clone, PartialEq, Hash)]
76pub struct Author(Vec<EcoString>);
77
78cast! {
79    Author,
80    self => self.0.into_value(),
81    v: EcoString => Self(vec![v]),
82    v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
83}
84
85/// A list of keywords.
86#[derive(Debug, Default, Clone, PartialEq, Hash)]
87pub struct Keywords(Vec<EcoString>);
88
89cast! {
90    Keywords,
91    self => self.0.into_value(),
92    v: EcoString => Self(vec![v]),
93    v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
94}
95
96/// Details about the document.
97#[derive(Debug, Default, Clone, PartialEq, Hash)]
98pub struct DocumentInfo {
99    /// The document's title.
100    pub title: Option<EcoString>,
101    /// The document's author(s).
102    pub author: Vec<EcoString>,
103    /// The document's description.
104    pub description: Option<EcoString>,
105    /// The document's keywords.
106    pub keywords: Vec<EcoString>,
107    /// The document's creation date.
108    pub date: Smart<Option<Datetime>>,
109    /// The document's language, set from the first top-level set rule, e.g.
110    ///
111    /// ```typc
112    /// set text(lang: "...", region: "...")
113    /// ```
114    pub locale: Smart<Locale>,
115}
116
117impl DocumentInfo {
118    /// Populate this document info with details from the given styles.
119    ///
120    /// Document set rules are a bit special, so we need to do this manually.
121    pub fn populate(&mut self, styles: &Styles) {
122        let chain = StyleChain::new(styles);
123        if styles.has(DocumentElem::title) {
124            self.title = chain
125                .get_ref(DocumentElem::title)
126                .as_ref()
127                .map(|content| content.plain_text());
128        }
129        if styles.has(DocumentElem::author) {
130            self.author = chain.get_cloned(DocumentElem::author).0;
131        }
132        if styles.has(DocumentElem::description) {
133            self.description = chain
134                .get_ref(DocumentElem::description)
135                .as_ref()
136                .map(|content| content.plain_text());
137        }
138        if styles.has(DocumentElem::keywords) {
139            self.keywords = chain.get_cloned(DocumentElem::keywords).0;
140        }
141        if styles.has(DocumentElem::date) {
142            self.date = chain.get(DocumentElem::date);
143        }
144    }
145
146    /// Populate this document info with locale details from the given styles.
147    pub fn populate_locale(&mut self, styles: &Styles) {
148        if self.locale.is_custom() {
149            return;
150        }
151
152        let chain = StyleChain::new(styles);
153        let mut locale: Option<Locale> = None;
154        if styles.has(TextElem::lang) {
155            locale.get_or_insert_default().lang = chain.get(TextElem::lang);
156        }
157        if styles.has(TextElem::region) {
158            locale.get_or_insert_default().region = chain.get(TextElem::region);
159        }
160        self.locale = Smart::from(locale);
161    }
162}