1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 //
5 // Copyright © 2018 Corporation for Digital Scholarship
6 
7 use super::{LocalizedQuotes, OutputFormat};
8 use crate::utils::{Intercalate, JoinMany};
9 use csl::{FontStyle, FontVariant, FontWeight, Formatting, TextDecoration, VerticalAlignment};
10 
11 use pandoc_types::definition::Inline::*;
12 use pandoc_types::definition::{Attr, Inline, QuoteType, Target};
13 
14 #[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
15 pub struct Pandoc {}
16 
17 impl Default for Pandoc {
default() -> Self18     fn default() -> Self {
19         Pandoc {}
20     }
21 }
22 
23 impl Pandoc {
24     /// Wrap some nodes with formatting
25     ///
26     /// In pandoc, Emph, Strong and SmallCaps, Superscript and Subscript are all single-use styling
27     /// elements. So formatting with two of those styles at once requires wrapping twice, in any
28     /// order.
29 
fmt_vec(&self, inlines: Vec<Inline>, formatting: Option<Formatting>) -> Vec<Inline>30     fn fmt_vec(&self, inlines: Vec<Inline>, formatting: Option<Formatting>) -> Vec<Inline> {
31         if let Some(f) = formatting {
32             let mut current = inlines;
33 
34             current = match f.font_style {
35                 FontStyle::Italic | FontStyle::Oblique => vec![Emph(current)],
36                 _ => current,
37             };
38             current = match f.font_weight {
39                 FontWeight::Bold => vec![Strong(current)],
40                 // Light => unimplemented!(),
41                 _ => current,
42             };
43             current = match f.font_variant {
44                 FontVariant::SmallCaps => vec![SmallCaps(current)],
45                 _ => current,
46             };
47             current = match f.text_decoration {
48                 Some(TextDecoration::Underline) => vec![Span(attr_class("underline"), current)],
49                 _ => current,
50             };
51             current = match f.vertical_alignment {
52                 Some(VerticalAlignment::Superscript) => vec![Superscript(current)],
53                 Some(VerticalAlignment::Subscript) => vec![Subscript(current)],
54                 _ => current,
55             };
56 
57             current
58         } else {
59             inlines
60         }
61     }
62 }
63 
64 impl OutputFormat for Pandoc {
65     type Input = Vec<Inline>;
66     type Build = Vec<Inline>;
67     type Output = Vec<Inline>;
68 
69     #[inline]
ingest(&self, input: Self::Input) -> Self::Build70     fn ingest(&self, input: Self::Input) -> Self::Build {
71         input
72     }
73 
74     #[inline]
plain(&self, s: &str) -> Self::Build75     fn plain(&self, s: &str) -> Self::Build {
76         self.text_node(s.to_owned(), None)
77     }
78 
text_node(&self, text: String, f: Option<Formatting>) -> Vec<Inline>79     fn text_node(&self, text: String, f: Option<Formatting>) -> Vec<Inline> {
80         let v: Vec<Inline> = text
81             // TODO: write a nom tokenizer, don't use split/intercalate
82             .split(' ')
83             .map(|s| Str(s.to_owned()))
84             .intercalate(&Space)
85             .into_iter()
86             .filter(|t| match t {
87                 Str(ref s) if s == "" => false,
88                 _ => true,
89             })
90             .collect();
91 
92         self.fmt_vec(v, f)
93     }
94 
95     #[inline]
seq(&self, nodes: impl Iterator<Item = Self::Build>) -> Self::Build96     fn seq(&self, nodes: impl Iterator<Item = Self::Build>) -> Self::Build {
97         itertools::concat(nodes)
98     }
99 
join_delim(&self, a: Self::Build, delim: &str, b: Self::Build) -> Self::Build100     fn join_delim(&self, a: Self::Build, delim: &str, b: Self::Build) -> Self::Build {
101         [a, b].join_many(&self.plain(delim))
102     }
103 
group( &self, nodes: Vec<Self::Build>, delimiter: &str, formatting: Option<Formatting>, ) -> Self::Build104     fn group(
105         &self,
106         nodes: Vec<Self::Build>,
107         delimiter: &str,
108         formatting: Option<Formatting>,
109     ) -> Self::Build {
110         if nodes.len() == 1 {
111             self.fmt_vec(nodes.into_iter().nth(0).unwrap(), formatting)
112         } else {
113             let delim = self.plain(delimiter);
114             self.fmt_vec(nodes.join_many(&delim), formatting)
115         }
116     }
117 
118     #[inline]
with_format(&self, a: Self::Build, f: Option<Formatting>) -> Self::Build119     fn with_format(&self, a: Self::Build, f: Option<Formatting>) -> Self::Build {
120         self.fmt_vec(a, f)
121     }
122 
quoted(&self, b: Self::Build, quotes: &LocalizedQuotes) -> Self::Build123     fn quoted(&self, b: Self::Build, quotes: &LocalizedQuotes) -> Self::Build {
124         let qt = match quotes {
125             LocalizedQuotes::Single(..) => QuoteType::SingleQuote,
126             LocalizedQuotes::Double(..) => QuoteType::DoubleQuote,
127         };
128         vec![Inline::Quoted(qt, b)]
129     }
130 
hyperlinked(&self, a: Self::Build, target: Option<&str>) -> Self::Build131     fn hyperlinked(&self, a: Self::Build, target: Option<&str>) -> Self::Build {
132         // TODO: allow internal linking using the Attr parameter (e.g.
133         // first-reference-note-number)
134         if let Some(target) = target {
135             vec![Inline::Link(
136                 Default::default(),
137                 a,
138                 Target(target.to_string(), "".to_string()),
139             )]
140         } else {
141             a
142         }
143     }
144 
output(&self, inter: Vec<Inline>) -> Vec<Inline>145     fn output(&self, inter: Vec<Inline>) -> Vec<Inline> {
146         let null = FlipFlopState::default();
147         flip_flop_inlines(&inter, &null)
148         // TODO: convert quotes to inner and outer quote terms
149     }
150 }
151 
152 #[derive(Default, Debug, Clone)]
153 struct FlipFlopState {
154     in_emph: bool,
155     in_strong: bool,
156     in_small_caps: bool,
157     in_outer_quotes: bool,
158 }
159 
attr_class(class: &str) -> Attr160 fn attr_class(class: &str) -> Attr {
161     Attr("".to_owned(), vec![class.to_owned()], vec![])
162 }
163 
flip_flop_inlines(inlines: &[Inline], state: &FlipFlopState) -> Vec<Inline>164 fn flip_flop_inlines(inlines: &[Inline], state: &FlipFlopState) -> Vec<Inline> {
165     inlines
166         .iter()
167         .map(|inl| flip_flop(inl, state).unwrap_or_else(|| inl.clone()))
168         .collect()
169 }
170 
flip_flop(inline: &Inline, state: &FlipFlopState) -> Option<Inline>171 fn flip_flop(inline: &Inline, state: &FlipFlopState) -> Option<Inline> {
172     use pandoc_types::definition::*;
173     let fl = |ils: &[Inline], st| flip_flop_inlines(ils, st);
174     match inline {
175         // Note(ref blocks) => {
176         //     if let Some(Block::Para(ref ils)) = blocks.into_iter().nth(0) {
177         //         Some(Note(vec![Block::Para(fl(ils, state))]))
178         //     } else {
179         //         None
180         //     }
181         // }
182         Emph(ref ils) => {
183             let mut flop = state.clone();
184             flop.in_emph = !flop.in_emph;
185             if state.in_emph {
186                 Some(Span(attr_class("csl-no-emph"), fl(ils, &flop)))
187             } else {
188                 Some(Emph(fl(ils, &flop)))
189             }
190         }
191 
192         Strong(ref ils) => {
193             let mut flop = state.clone();
194             flop.in_strong = !flop.in_strong;
195             let subs = fl(ils, &flop);
196             if state.in_strong {
197                 Some(Span(attr_class("csl-no-strong"), subs))
198             } else {
199                 Some(Strong(subs))
200             }
201         }
202 
203         SmallCaps(ref ils) => {
204             let mut flop = state.clone();
205             flop.in_small_caps = !flop.in_small_caps;
206             let subs = fl(ils, &flop);
207             if state.in_small_caps {
208                 Some(Span(attr_class("csl-no-smallcaps"), subs))
209             } else {
210                 Some(SmallCaps(subs))
211             }
212         }
213 
214         Quoted(ref _q, ref ils) => {
215             let mut flop = state.clone();
216             flop.in_outer_quotes = !flop.in_outer_quotes;
217             let subs = fl(ils, &flop);
218             if !state.in_outer_quotes {
219                 Some(Quoted(QuoteType::SingleQuote, subs))
220             } else {
221                 Some(Quoted(QuoteType::DoubleQuote, subs))
222             }
223         }
224 
225         Strikeout(ref ils) => {
226             let subs = fl(ils, state);
227             Some(Strikeout(subs))
228         }
229 
230         Superscript(ref ils) => {
231             let subs = fl(ils, state);
232             Some(Superscript(subs))
233         }
234 
235         Subscript(ref ils) => {
236             let subs = fl(ils, state);
237             Some(Subscript(subs))
238         }
239 
240         Link(attr, ref ils, t) => {
241             let subs = fl(ils, state);
242             Some(Link(attr.clone(), subs, t.clone()))
243         }
244 
245         _ => None,
246     }
247 
248     // a => a
249 }
250 
251 #[cfg(test)]
252 mod test {
253     use super::*;
254 
255     #[test]
test_space()256     fn test_space() {
257         let f = Pandoc::default();
258         assert_eq!(f.plain(" ")[0], Space);
259         assert_eq!(f.plain("  "), &[Space, Space]);
260         assert_eq!(f.plain(" h "), &[Space, Str("h".into()), Space]);
261         assert_eq!(
262             f.plain("  hello "),
263             &[Space, Space, Str("hello".into()), Space]
264         );
265     }
266 
267     #[test]
test_flip_emph()268     fn test_flip_emph() {
269         let f = Pandoc::default();
270         let a = f.plain("normal");
271         let b = f.text_node("emph".into(), Some(Formatting::italic()));
272         let c = f.plain("normal");
273         let group = f.group(vec![a, b, c], " ", Some(Formatting::italic()));
274         let out = f.output(group.clone());
275         assert_ne!(group, out);
276     }
277 }
278