Skip to main content

escpos_md/style/
style_sheet.rs

1use super::rule::{parse_rules, Rule};
2use super::style_tag::StyleTag;
3use crate::command::{CharMagnification, Font, Justification, UnderlineThickness};
4use crate::config::default::DEFAULT_CHAR_SPACING;
5use crate::error::Result;
6use crate::markdown::TagState;
7use crate::{Printer, PrinterDevice};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Display {
11    Inline,
12    Block,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct Style {
17    pub display: Display,
18    // Font styles
19    pub bold: bool,
20    pub char_magnification: CharMagnification,
21    pub char_spacing: usize,
22    pub font: Font,
23    pub line_spacing: Option<usize>,
24    pub split_words: bool,
25    pub underline: UnderlineThickness,
26    pub white_black_reverse: bool,
27    // block styles
28    pub prefix: String,
29    pub justification: Justification,
30    pub margin_left: usize,
31    pub margin_bottom: usize,
32    pub margin_top: usize,
33}
34
35impl<D> Printer<D>
36where
37    D: PrinterDevice,
38{
39    pub(crate) fn font_style(&mut self, style: &Style) -> Result<&mut Self> {
40        self.bold(style.bold)?
41            .char_size(style.char_magnification)?
42            .char_spacing(style.char_spacing)?
43            .font(style.font)?
44            .line_spacing(style.line_spacing)?
45            .split_words(style.split_words)?
46            .underline(style.underline)?
47            .white_black_reverse(style.white_black_reverse)?;
48        Ok(self)
49    }
50    pub(crate) fn begin_block_style(
51        &mut self,
52        style: &Style,
53        tag_state: Option<&TagState>,
54    ) -> Result<&mut Self> {
55        if matches!(style.display, Display::Block) {
56            self.justification(style.justification)?
57                .feed_paper(style.margin_top)?;
58            if style.margin_left != 0 {
59                let new_left_margin = self.state.left_margin + style.margin_left as u16;
60                self.left_margin(new_left_margin)?;
61            }
62        }
63        if !style.prefix.is_empty() {
64            let prefix = if let Some(num) = tag_state.and_then(|state| state.num()) {
65                style.prefix.replace("{num}", &num.to_string())
66            } else {
67                style.prefix.clone()
68            };
69            self.print(prefix)?;
70        }
71        Ok(self)
72    }
73
74    pub(crate) fn end_block_style(&mut self, style: &Style) -> Result<&mut Self> {
75        if matches!(style.display, Display::Block) {
76            self.feed_paper(style.margin_bottom)?;
77            if style.margin_left != 0 {
78                let new_left_margin = self.state.left_margin - style.margin_left as u16;
79                self.left_margin(new_left_margin)?;
80            }
81        }
82        Ok(self)
83    }
84}
85
86impl Default for Style {
87    fn default() -> Self {
88        Self {
89            display: Display::Block,
90            prefix: String::default(),
91            font: Font::default(),
92            char_magnification: CharMagnification::default(),
93            underline: UnderlineThickness::default(),
94            bold: false,
95            white_black_reverse: false,
96            split_words: true,
97            justification: Justification::default(),
98            line_spacing: None,
99            char_spacing: DEFAULT_CHAR_SPACING,
100            margin_top: 0,
101            margin_bottom: 0,
102            margin_left: 0,
103        }
104    }
105}
106
107#[derive(Default, Debug, Clone)]
108pub struct RelativeStyle {
109    pub display: Option<Display>,
110    pub prefix: Option<String>,
111    pub font: Option<Font>,
112    pub font_width: Option<u8>,
113    pub font_height: Option<u8>,
114    pub underline: Option<UnderlineThickness>,
115    pub bold: Option<bool>,
116    pub white_black_reverse: Option<bool>,
117    pub split_words: Option<bool>,
118    pub justification: Option<Justification>,
119    pub char_spacing: Option<usize>,
120    pub line_spacing: Option<Option<usize>>,
121    pub margin_top: Option<usize>,
122    pub margin_bottom: Option<usize>,
123    pub margin_left: Option<usize>,
124}
125
126macro_rules! apply_fields {
127    ($src:ident -> $dst:ident: $($field:ident),*) => {
128        $(
129            if let Some(ref $field) = $src.$field {
130                $dst.$field = $field.clone();
131            }
132        )*
133    }
134}
135
136impl Style {
137    pub fn apply_font(&mut self, style: &RelativeStyle) {
138        apply_fields!(
139            style -> self:
140            font,
141            underline,
142            bold,
143            white_black_reverse,
144            split_words,
145            char_spacing,
146            line_spacing
147        );
148        self.char_magnification = CharMagnification::clamped(
149            style
150                .font_width
151                .unwrap_or_else(|| self.char_magnification.width()),
152            style
153                .font_height
154                .unwrap_or_else(|| self.char_magnification.height()),
155        );
156    }
157    pub fn apply_block(&mut self, style: &RelativeStyle) {
158        apply_fields!(
159            style -> self:
160            display,
161            prefix,
162            justification,
163            margin_top,
164            margin_bottom,
165            margin_left
166        );
167    }
168}
169#[derive(Clone, Debug)]
170pub struct StyleSheet {
171    base: Style,
172    rules: Vec<(Rule, RelativeStyle)>,
173}
174
175impl StyleSheet {
176    pub fn new(base: Style) -> Self {
177        Self {
178            base,
179            rules: Vec::new(),
180        }
181    }
182
183    pub fn push(&mut self, rule: impl AsRef<str>, style: RelativeStyle) -> Result<()> {
184        for rule in parse_rules(rule)? {
185            self.rules.push((rule, style.clone()));
186        }
187        Ok(())
188    }
189
190    pub(crate) fn get(&self, tree: &[StyleTag]) -> Style {
191        let mut style = self.base.clone();
192        for (rule, rel_style) in &self.rules {
193            if rule.matches_loose(tree) {
194                style.apply_font(rel_style);
195                if rule.matches_exact(tree) {
196                    style.apply_block(rel_style);
197                }
198            }
199        }
200        style
201    }
202}
203
204impl Default for StyleSheet {
205    fn default() -> Self {
206        lazy_static! {
207            static ref DEFAULT_STYLESHEET: StyleSheet = {
208                let mut this = StyleSheet::new(Style::default());
209                this.push(
210                    "*",
211                    RelativeStyle {
212                        margin_top: Some(60),
213                        ..Default::default()
214                    },
215                )
216                .unwrap();
217                this.push(
218                    "ul ul, ul ol, ol ol, ol ul",
219                    RelativeStyle {
220                        margin_top: Some(0),
221                        margin_bottom: Some(0),
222                        ..Default::default()
223                    },
224                )
225                .unwrap();
226                this.push(
227                    "li",
228                    RelativeStyle {
229                        margin_top: Some(12),
230                        margin_left: Some(28),
231                        ..Default::default()
232                    },
233                )
234                .unwrap();
235                this.push(
236                    "> ul > li, > ol > li",
237                    RelativeStyle {
238                        margin_left: Some(0),
239                        ..Default::default()
240                    },
241                )
242                .unwrap();
243                this.push(
244                    "ul > li",
245                    RelativeStyle {
246                        prefix: Some("* ".into()),
247                        ..Default::default()
248                    },
249                )
250                .unwrap();
251                this.push(
252                    "ol > li",
253                    RelativeStyle {
254                        prefix: Some("{num}. ".into()),
255                        ..Default::default()
256                    },
257                )
258                .unwrap();
259                this.push(
260                    "a",
261                    RelativeStyle {
262                        display: Some(Display::Inline),
263                        underline: Some(UnderlineThickness::OneDot),
264                        ..Default::default()
265                    },
266                )
267                .unwrap();
268                this.push(
269                    "strong",
270                    RelativeStyle {
271                        display: Some(Display::Inline),
272                        bold: Some(true),
273                        ..Default::default()
274                    },
275                )
276                .unwrap();
277                this.push(
278                    "em",
279                    RelativeStyle {
280                        display: Some(Display::Inline),
281                        underline: Some(UnderlineThickness::TwoDot),
282                        ..Default::default()
283                    },
284                )
285                .unwrap();
286                this.push(
287                    "strikethrough",
288                    RelativeStyle {
289                        display: Some(Display::Inline),
290                        white_black_reverse: Some(true),
291                        ..Default::default()
292                    },
293                )
294                .unwrap();
295                this.push(
296                    "code",
297                    RelativeStyle {
298                        display: Some(Display::Inline),
299                        font: Some(Font::FontB),
300                        ..Default::default()
301                    },
302                )
303                .unwrap();
304                this.push(
305                    "codeblock",
306                    RelativeStyle {
307                        font: Some(Font::FontB),
308                        split_words: Some(false),
309                        margin_top: Some(80),
310                        margin_bottom: Some(20),
311                        ..Default::default()
312                    },
313                )
314                .unwrap();
315                this.push(
316                    "blockquote",
317                    RelativeStyle {
318                        margin_left: Some(28),
319                        ..Default::default()
320                    },
321                )
322                .unwrap();
323                this.push(
324                    "img",
325                    RelativeStyle {
326                        margin_top: Some(0),
327                        justification: Some(Justification::Center),
328                        ..Default::default()
329                    },
330                )
331                .unwrap();
332                this.push(
333                    "imgcaption",
334                    RelativeStyle {
335                        margin_top: Some(30),
336                        justification: Some(Justification::Center),
337                        ..Default::default()
338                    },
339                )
340                .unwrap();
341                this.push(
342                    "hr",
343                    RelativeStyle {
344                        char_spacing: Some(0),
345                        ..Default::default()
346                    },
347                )
348                .unwrap();
349                this.push(
350                    "h1",
351                    RelativeStyle {
352                        font_width: Some(3),
353                        font_height: Some(3),
354                        bold: Some(true),
355                        ..Default::default()
356                    },
357                )
358                .unwrap();
359                this.push(
360                    "h2",
361                    RelativeStyle {
362                        font_width: Some(3),
363                        font_height: Some(2),
364                        bold: Some(true),
365                        ..Default::default()
366                    },
367                )
368                .unwrap();
369                this.push(
370                    "h3",
371                    RelativeStyle {
372                        font_width: Some(2),
373                        font_height: Some(2),
374                        bold: Some(true),
375                        ..Default::default()
376                    },
377                )
378                .unwrap();
379                this.push(
380                    "h4",
381                    RelativeStyle {
382                        font_width: Some(2),
383                        bold: Some(true),
384                        ..Default::default()
385                    },
386                )
387                .unwrap();
388                this.push(
389                    "h5",
390                    RelativeStyle {
391                        bold: Some(true),
392                        ..Default::default()
393                    },
394                )
395                .unwrap();
396                this
397            };
398        }
399        DEFAULT_STYLESHEET.clone()
400    }
401}