escpos_md/
markdown.rs

1use crate::error::{Error, Result};
2use crate::instruction::{EscposImage, ImageOptions};
3use crate::printer::{Printer, PrinterDevice};
4use crate::pulldown_cmark_ext::{EventExt, TagExt};
5use crate::style::{StyleSheet, StyleTag};
6use pulldown_cmark::{Event, Tag};
7
8#[derive(Debug, Clone, Default)]
9pub struct MarkdownRenderOptions {
10    pub styles: StyleSheet,
11    pub image: ImageOptions,
12}
13
14#[derive(Debug, Clone, Copy)]
15pub enum TagState {
16    Stateless,
17    Item(u64),
18}
19
20impl TagState {
21    pub fn num(&self) -> Option<u64> {
22        match self {
23            Self::Stateless => None,
24            Self::Item(num) => Some(*num),
25        }
26    }
27}
28
29#[derive(Default)]
30struct RendererState<'a> {
31    tree: Vec<(Tag<'a>, TagState)>,
32}
33
34impl<'a> RendererState<'a> {
35    fn push_tag(&mut self, tag: Tag<'a>) -> Result<()> {
36        let state = match tag {
37            Tag::List(Some(num)) => TagState::Item(num),
38            Tag::Item => {
39                match self
40                    .tag_state_mut()
41                    .ok_or_else(|| Error::UnexpectedTag(tag.clone().to_static()))?
42                {
43                    TagState::Stateless => TagState::Stateless,
44                    TagState::Item(num) => {
45                        let state = TagState::Item(*num);
46                        *num += 1;
47                        state
48                    }
49                }
50            }
51            _ => TagState::Stateless,
52        };
53        self.tree.push((tag, state));
54        Ok(())
55    }
56
57    fn pop_tag(&mut self, tag: &Tag<'a>) -> Result<()> {
58        if self.tag() != Some(tag) {
59            Err(Error::UnexpectedTag(tag.clone().to_static()))
60        } else {
61            self.tree.pop();
62            Ok(())
63        }
64    }
65
66    fn tag(&self) -> Option<&Tag<'a>> {
67        self.tree.last().map(|item| &item.0)
68    }
69
70    fn tag_state(&self) -> Option<&TagState> {
71        self.tree.last().map(|item| &item.1)
72    }
73
74    fn tag_state_mut(&mut self) -> Option<&mut TagState> {
75        self.tree.last_mut().map(|item| &mut item.1)
76    }
77
78    fn style_tags(&self) -> Result<Vec<StyleTag>> {
79        self.tree.iter().map(|(tag, _)| tag.style_tag()).collect()
80    }
81}
82
83impl<D> Printer<D>
84where
85    D: PrinterDevice,
86{
87    pub fn markdown<'a, I>(&mut self, iter: I, opts: &MarkdownRenderOptions) -> Result<&mut Self>
88    where
89        I: Iterator<Item = Event<'a>>,
90    {
91        let mut state = RendererState::default();
92        for event in iter {
93            match event {
94                Event::Start(tag) => {
95                    state.push_tag(tag.clone())?;
96                    let style_tags = state.style_tags()?;
97                    let style = opts.styles.get(&style_tags);
98                    self.font_style(&style)?;
99                    self.begin_block_style(&style, state.tag_state())?;
100
101                    match tag {
102                        Tag::Image(_, filename, _) => {
103                            let img = image::open(filename.as_ref())?;
104                            let escpos_img = EscposImage::new(&img, &opts.image);
105                            self.image(&escpos_img)?;
106
107                            let mut img_caption_tags = style_tags;
108                            img_caption_tags.push(StyleTag::ImgCaption);
109                            let img_caption_style = opts.styles.get(&img_caption_tags);
110                            self.font_style(&img_caption_style)?;
111                            self.begin_block_style(&img_caption_style, None)?;
112                        }
113                        _ => {}
114                    }
115                }
116                Event::End(tag) => {
117                    let style_tags = state.style_tags()?;
118                    match tag {
119                        Tag::Image(..) => {
120                            let mut img_caption_tags = style_tags.clone();
121                            img_caption_tags.push(StyleTag::ImgCaption);
122                            let img_caption_style = opts.styles.get(&img_caption_tags);
123                            self.end_block_style(&img_caption_style)?;
124                        }
125                        _ => {}
126                    }
127                    let style = opts.styles.get(&style_tags);
128                    self.end_block_style(&style)?;
129                    state.pop_tag(&tag)?;
130                    let style = opts.styles.get(&state.style_tags()?);
131                    self.font_style(&style)?;
132                }
133                Event::Text(text) => {
134                    self.print(text)?;
135                }
136                Event::Code(text) => {
137                    let mut style_tags = state.style_tags()?;
138                    style_tags.push(StyleTag::Code);
139                    let style = opts.styles.get(&style_tags);
140                    self.font_style(&style)?;
141                    self.begin_block_style(&style, None)?;
142
143                    self.print(text)?;
144
145                    self.end_block_style(&style)?;
146                    let style = opts.styles.get(&state.style_tags()?);
147                    self.font_style(&style)?;
148                }
149                Event::SoftBreak => {
150                    self.print(" ")?;
151                }
152                Event::HardBreak => {
153                    self.println("")?;
154                }
155                Event::Rule => {
156                    let mut style_tags = state.style_tags()?;
157                    style_tags.push(StyleTag::Hr);
158                    let style = opts.styles.get(&style_tags);
159                    self.font_style(&style)?;
160                    self.begin_block_style(&style, None)?;
161
162                    let num_bars = self.printable_width() / self.calc_char_size();
163                    self.println(vec!["─"; num_bars].join(""))?;
164
165                    self.end_block_style(&style)?;
166                    let style = opts.styles.get(&state.style_tags()?);
167                    self.font_style(&style)?;
168                }
169                event => return Err(Error::MarkdownEventUnimplemented(event.to_static())),
170            }
171        }
172        Ok(self)
173    }
174}