typst_library/model/
document.rs1use 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#[elem(Construct)]
28pub struct DocumentElem {
29 #[ghost]
39 pub title: Option<Content>,
40
41 #[ghost]
43 pub author: OneOrMultiple<EcoString>,
44
45 #[ghost]
47 pub description: Option<Content>,
48
49 #[ghost]
51 pub keywords: OneOrMultiple<EcoString>,
52
53 #[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#[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#[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#[derive(Debug, Default, Clone, PartialEq, Hash)]
98pub struct DocumentInfo {
99 pub title: Option<EcoString>,
101 pub author: Vec<EcoString>,
103 pub description: Option<EcoString>,
105 pub keywords: Vec<EcoString>,
107 pub date: Smart<Option<Datetime>>,
109 pub locale: Smart<Locale>,
115}
116
117impl DocumentInfo {
118 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 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}