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 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 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}