1use std::str::FromStr;
2
3use anyhow::{Error, bail, format_err};
4use linearize::Linearize;
5use regex::Regex;
6use schemars::JsonSchema;
7use serde_derive::Serialize;
8
9use crate::url::Url;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
12pub enum EnumeratedListType {
13 Arabic,
14 LowerAlpha,
15 UpperAlpha,
16 LowerRoman,
17 UpperRoman,
18}
19
20#[derive(Clone, Copy, Linearize, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
21pub enum FootnoteType {
22 Number,
23 Symbol,
24}
25
26impl TryFrom<char> for FootnoteType {
27 type Error = ();
28
29 fn try_from(c: char) -> Result<Self, Self::Error> {
30 match c {
31 '#' => Ok(FootnoteType::Number),
32 '*' => Ok(FootnoteType::Symbol),
33 _ => Err(()),
34 }
35 }
36}
37
38#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
39pub enum FixedSpace {
40 Default,
41 #[default]
43 Preserve,
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
47pub enum AlignH {
48 Left,
49 Center,
50 Right,
51}
52#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
53pub enum AlignHV {
54 Top,
55 Middle,
56 Bottom,
57 Left,
58 Center,
59 Right,
60}
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
62pub enum AlignV {
63 Top,
64 Middle,
65 Bottom,
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
69pub enum TableAlignH {
70 Left,
71 Right,
72 Center,
73 Justify,
74 Char,
75}
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
77pub enum TableBorder {
78 Top,
79 Bottom,
80 TopBottom,
81 All,
82 Sides,
83 None,
84}
85
86#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
87pub struct ID(pub String);
88#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
89pub struct NameToken(pub String);
90
91#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, JsonSchema)]
95pub struct TableGroupCols(pub usize);
96
97#[derive(Clone, Debug, PartialEq, Serialize, JsonSchema)]
99#[serde(tag = "unit", content = "value")]
100#[schemars(_unstable_ref_variants)]
101pub enum Measure {
102 Em(f64),
104 Ex(f64),
105 Mm(f64),
106 Cm(f64),
107 In(f64),
108 Px(f64),
109 Pt(f64),
110 Pc(f64),
111}
112
113impl FromStr for AlignHV {
114 type Err = Error;
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 use self::AlignHV as A;
117 Ok(match s {
118 "top" => A::Top,
119 "middle" => A::Middle,
120 "bottom" => A::Bottom,
121 "left" => A::Left,
122 "center" => A::Center,
123 "right" => A::Right,
124 s => bail!("Invalid Alignment {s}"),
125 })
126 }
127}
128
129impl From<&str> for ID {
130 fn from(s: &str) -> Self {
131 ID(s.to_owned().replace(' ', "-"))
132 }
133}
134
135impl From<&str> for NameToken {
136 fn from(s: &str) -> Self {
137 NameToken(s.to_owned())
138 }
139}
140
141impl FromStr for Measure {
142 type Err = Error;
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 use self::Measure as M;
145 let re =
146 Regex::new(r"(?P<float>\d+\.\d*|\.?\d+)\s*(?P<unit>em|ex|mm|cm|in|px|pt|pc)").unwrap();
147 let caps: regex::Captures = re
148 .captures(s)
149 .ok_or_else(|| format_err!("Invalid measure"))?;
150 let value: f64 = caps["float"].parse()?;
151 Ok(match &caps["unit"] {
152 "em" => M::Em(value),
153 "ex" => M::Ex(value),
154 "mm" => M::Mm(value),
155 "cm" => M::Cm(value),
156 "in" => M::In(value),
157 "px" => M::Px(value),
158 "pt" => M::Pt(value),
159 "pc" => M::Pc(value),
160 _ => unreachable!(),
161 })
162 }
163}
164
165#[cfg(test)]
166mod parse_tests {
167 use super::*;
168
169 #[test]
170 fn measure() {
171 let _a: Measure = "1.5em".parse().unwrap();
172 let _b: Measure = "20 mm".parse().unwrap();
173 let _c: Measure = ".5in".parse().unwrap();
174 let _d: Measure = "1.pc".parse().unwrap();
175 }
176}
177
178pub(crate) trait CanBeEmpty {
179 fn is_empty(&self) -> bool;
180}
181
182macro_rules! impl_cannot_be_empty {
188 ($t:ty) => {
189 impl CanBeEmpty for $t {
190 fn is_empty(&self) -> bool { false }
191 }
192 };
193 ($t:ty, $($ts:ty),*) => {
194 impl_cannot_be_empty!($t);
195 impl_cannot_be_empty!($($ts),*);
196 };
197}
198impl_cannot_be_empty!(Url);
199impl_cannot_be_empty!(TableGroupCols);
200
201impl<T> CanBeEmpty for Option<T> {
202 fn is_empty(&self) -> bool {
203 self.is_none()
204 }
205}
206
207impl<T> CanBeEmpty for Vec<T> {
208 fn is_empty(&self) -> bool {
209 self.is_empty()
210 }
211}
212
213impl CanBeEmpty for bool {
214 fn is_empty(&self) -> bool {
215 !self
216 }
217}
218
219impl CanBeEmpty for FixedSpace {
220 fn is_empty(&self) -> bool {
221 self == &FixedSpace::default()
222 }
223}