typst_library/model/
document.rs

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