1use std::{borrow::Cow, fmt, rc::Rc};
2
3use libxml::tree::Node;
4
5use crate::{
6 BoxOps, Digested, TexMode,
7 common::{
8 arena::SymHashMap as HashMap, dimension::Dimension, error::*, font::Font, locator::Locator,
9 object::Object, store::Stored,
10 },
11 document::Document,
12 pin,
13 tokens::Tokens,
14};
15
16#[derive(Clone, Default)]
18pub struct List {
19 pub boxes: Vec<Digested>,
20 pub mode: Option<TexMode>,
21 pub font: Option<Rc<Font>>,
26 pub locator: Option<Locator>,
27 pub properties: HashMap<Stored>,
28}
29
30impl fmt::Debug for List {
31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32 write!(
33 f,
34 "{}",
35 self
36 .boxes
37 .iter()
38 .map(|d| d.stringify())
39 .collect::<Vec<_>>()
40 .join(", ")
41 )
42 }
43}
44
45impl fmt::Display for List {
46 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47 for inner in self.boxes.iter() {
48 write!(f, "{inner}")?;
49 }
50 Ok(())
51 }
52}
53
54impl PartialEq for List {
55 fn eq(&self, other: &Self) -> bool {
56 self.boxes.len() == other.boxes.len()
57 && self
58 .boxes
59 .iter()
60 .zip(other.boxes.iter())
61 .all(|(box1, box2)| box1 == box2)
62 }
63}
64
65impl Object for List {
66 fn stringify(&self) -> String { format!("List[{self:?}]") }
67 fn get_locator(&self) -> Option<Locator> { self.locator }
68
69 fn revert(&self) -> Result<Tokens> {
70 let mut reverted = Vec::with_capacity(self.boxes.len());
73 for tbox in self.boxes.iter() {
74 reverted.extend(tbox.revert()?.unlist());
75 }
76 Ok(Tokens::new(reverted))
77 }
78}
79impl BoxOps for List {
80 fn unlist(&self) -> Vec<Digested> { self.boxes.clone() }
81 fn unlist_ref(&self) -> Vec<Cow<'_, Digested>> { self.boxes.iter().map(Cow::Borrowed).collect() }
82 fn get_properties(&self) -> &HashMap<Stored> { &self.properties }
83 fn get_property(&self, key: &str) -> Option<Cow<'_, Stored>> {
84 self.properties.get(key).map(Cow::Borrowed)
85 }
86 fn with_properties<R, FnR>(&self, caller: FnR) -> R
87 where FnR: FnOnce(&HashMap<Stored>) -> R {
88 caller(&self.properties)
89 }
90 fn get_properties_mut(&mut self) -> &mut HashMap<Stored> { &mut self.properties }
91 fn set_property<T: Into<Stored>>(&mut self, key: &str, value: T) {
92 self.properties.insert(key, value.into());
93 }
94 fn get_string(&self) -> Result<Cow<'_, str>> { Ok(Cow::Owned(self.to_string())) }
95 fn be_absorbed(&self, document: &mut Document) -> Result<Vec<Node>> {
98 for box_item in &self.boxes {
99 document.absorb(box_item, None)?;
100 }
101 Ok(Vec::new())
102 }
103
104 fn get_font(&self) -> Result<Option<Rc<Font>>> { Ok(self.font.clone()) }
105 fn compute_size(
106 &self,
107 mut options: HashMap<Stored>,
108 ) -> Result<(Dimension, Dimension, Dimension)> {
109 let font = self
110 .font
111 .clone()
112 .unwrap_or_else(|| Rc::new(Font::text_default()));
113 if let Some(mode_str) = self.properties.get("mode") {
122 if let Stored::String(s) = mode_str {
123 options.insert("mode", Stored::String(*s));
124 }
125 } else if self.properties.get("width").is_some() && matches!(self.mode, Some(TexMode::Text)) {
126 options.insert("mode", Stored::String(pin!("horizontal")));
128 }
129 if let Some(Stored::String(s)) = self.properties.get("vattach") {
130 options.insert("vattach", Stored::String(*s));
131 }
132 if let Some(width) = self.properties.get("width")
133 && options.get("width").is_none()
134 {
135 options.insert("width", width.clone());
136 }
137 if let Some(baseline) = self.properties.get("baseline")
141 && options.get("baseline").is_none()
142 {
143 options.insert("baseline", baseline.clone());
144 }
145 font.compute_boxes_size(&self.boxes, options)
146 }
147}
148
149impl List {
150 pub fn new(boxes: Vec<Digested>) -> Self {
151 #[cfg(feature = "token-locators")]
166 let locator: Option<Locator> = boxes.iter().fold(None, |acc, bx| match bx.get_locator() {
167 Some(l) if l.from_line != 0 => match acc {
168 None => Some(l),
169 Some(a) => Locator::new_range(a, l).or(Some(a)),
170 },
171 _ => acc,
172 });
173 #[cfg(not(feature = "token-locators"))]
174 let locator: Option<Locator> = boxes.iter().rev().find_map(|bx| bx.get_locator());
175 let mut font: Option<Rc<Font>> = None;
189 for bx in boxes.iter().rev() {
190 if let Ok(Some(bx_font)) = bx.get_font() {
191 font = Some(bx_font);
192 break;
193 }
194 }
195 List {
196 boxes,
197 font,
198 mode: None,
199 locator,
200 properties: HashMap::default(),
201 }
202 }
203
204 pub fn is_empty(&self) -> bool {
205 self.get_property_bool("isEmpty")
208 || self.get_property_bool("isSpace")
209 || self
210 .boxes
211 .iter()
212 .all(|item| item.is_empty().unwrap_or(false))
213 }
214}
215
216impl From<List> for Result<Vec<Digested>> {
217 fn from(list: List) -> Result<Vec<Digested>> {
218 let tmp: Digested = list.into();
219 tmp.into()
220 }
221}
222
223impl From<List> for Result<Digested> {
224 fn from(value: List) -> Result<Digested> {
225 let tmp: Digested = value.into();
226 tmp.into()
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn list_default_is_empty() {
236 let l = List::default();
237 assert!(l.is_empty());
238 assert_eq!(l.boxes.len(), 0);
239 assert_eq!(l.mode, None);
240 assert_eq!(l.font, None);
241 }
242
243 #[test]
244 fn list_new_from_empty_vec() {
245 let l = List::new(vec![]);
246 assert!(l.is_empty());
247 assert_eq!(l.boxes.len(), 0);
248 }
249
250 #[test]
251 fn list_display_empty_is_empty_string() {
252 let l = List::default();
253 assert_eq!(format!("{l}"), "");
254 }
255
256 #[test]
257 fn list_equality_same_empty() {
258 let a = List::default();
259 let b = List::default();
260 assert_eq!(a, b);
261 }
262
263 #[test]
264 fn list_stringify_wraps_in_brackets() {
265 let l = List::default();
266 let s = l.stringify();
267 assert!(s.starts_with("List["), "got {s:?}");
268 assert!(s.ends_with(']'));
269 }
270
271 #[test]
272 fn list_get_properties_empty_by_default() {
273 let l = List::default();
274 assert_eq!(l.get_properties().len(), 0);
275 }
276
277 #[test]
278 fn list_set_property_persists() {
279 let mut l = List::default();
280 l.set_property("testkey", Stored::Bool(true));
281 assert!(l.get_properties().contains_key("testkey"));
282 }
283
284 #[test]
285 fn list_revert_empty_is_empty_tokens() {
286 let l = List::default();
287 let t = l.revert().expect("empty list reverts cleanly");
288 assert_eq!(t.len(), 0);
289 }
290
291 #[test]
292 fn list_unlist_ref_returns_borrowed_boxes() {
293 let l = List::default();
294 let refs = l.unlist_ref();
295 assert_eq!(refs.len(), 0);
296 }
297
298 #[test]
299 fn list_get_font_default_none() {
300 let l = List::default();
301 let f = l.get_font().unwrap();
302 assert!(f.is_none());
303 }
304}