maomi_skin/
write_css.rs

1use std::fmt::{Result, Write};
2
3use crate::VarDynValue;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6enum LineStatus {
7    BlockStart,
8    LineStart,
9    Other,
10}
11
12/// A writable target for `CssWriter` .
13pub trait CssWriteTarget: Write {
14    fn position(&self) -> usize;
15}
16
17impl CssWriteTarget for String {
18    fn position(&self) -> usize {
19        self.len()
20    }
21}
22
23/// The placeholder as a replacement of CSS variable.
24#[derive(Debug, Clone, Copy, PartialEq)]
25pub enum CssWritePlaceholder {
26    ColorHash(usize),
27    QuoteStr(usize),
28    Num(usize),
29}
30
31impl CssWritePlaceholder {
32    pub fn index(&self) -> usize {
33        match self {
34            CssWritePlaceholder::ColorHash(x) => *x,
35            CssWritePlaceholder::QuoteStr(x) => *x,
36            CssWritePlaceholder::Num(x) => *x,
37        }
38    }
39}
40
41/// A CSS writer.
42pub struct CssWriter<'a, W: CssWriteTarget> {
43    pub(crate) w: &'a mut W,
44    pub(crate) sc: WriteCssSepCond,
45    pub(crate) debug_mode: bool,
46    placeholders: Vec<CssWritePlaceholder>,
47    tab_count: usize,
48    line_status: LineStatus,
49}
50
51impl<'a, W: CssWriteTarget> CssWriter<'a, W> {
52    pub fn new(w: &'a mut W, debug_mode: bool) -> Self {
53        Self {
54            w,
55            sc: WriteCssSepCond::BlockStart,
56            debug_mode,
57            placeholders: Vec::with_capacity(0),
58            tab_count: 0,
59            line_status: LineStatus::BlockStart,
60        }
61    }
62
63    pub fn target(&self) -> &W {
64        &self.w
65    }
66
67    pub fn placeholders(&self) -> &[CssWritePlaceholder] {
68        &self.placeholders
69    }
70
71    pub fn line_wrap(&mut self) -> Result {
72        if !self.debug_mode {
73            return Ok(());
74        }
75        if self.line_status == LineStatus::Other {
76            self.line_status = LineStatus::LineStart;
77            write!(self.w, "\n")?;
78        }
79        Ok(())
80    }
81
82    fn prepare_write(&mut self) -> Result {
83        if !self.debug_mode {
84            return Ok(());
85        }
86        if self.line_status == LineStatus::BlockStart {
87            self.line_status = LineStatus::LineStart;
88            write!(self.w, "\n")?;
89        }
90        if self.line_status == LineStatus::LineStart {
91            for _ in 0..self.tab_count {
92                write!(self.w, "    ")?;
93            }
94            self.line_status = LineStatus::Other;
95            self.sc = WriteCssSepCond::Whitespace;
96        }
97        Ok(())
98    }
99
100    pub(crate) fn custom_write(
101        &mut self,
102        f: impl FnOnce(
103            &mut W,
104            WriteCssSepCond,
105            bool,
106            &mut Vec<CssWritePlaceholder>,
107        ) -> std::result::Result<WriteCssSepCond, std::fmt::Error>,
108    ) -> Result {
109        self.prepare_write()?;
110        let CssWriter {
111            ref mut w,
112            ref mut sc,
113            debug_mode,
114            ref mut placeholders,
115            ..
116        } = self;
117        *sc = f(w, *sc, *debug_mode, placeholders)?;
118        Ok(())
119    }
120
121    pub fn write_ident(&mut self, ident: &str, prefer_sep_before: bool) -> Result {
122        self.prepare_write()?;
123        let CssWriter {
124            ref mut w,
125            ref mut sc,
126            debug_mode,
127            ..
128        } = self;
129        if *debug_mode && prefer_sep_before {
130            match sc {
131                WriteCssSepCond::BlockStart | WriteCssSepCond::Whitespace => {}
132                _ => {
133                    write!(w, " ")?;
134                }
135            }
136        } else {
137            match sc {
138                WriteCssSepCond::Ident
139                | WriteCssSepCond::NonIdentAlpha
140                | WriteCssSepCond::Digit
141                | WriteCssSepCond::PlusOrMinus
142                | WriteCssSepCond::At => {
143                    write!(w, " ")?;
144                }
145                _ => {}
146            }
147        }
148        write!(w, "{}", ident)?;
149        *sc = WriteCssSepCond::Ident;
150        Ok(())
151    }
152
153    pub fn write_at_keyword(&mut self, ident: &str) -> Result {
154        self.prepare_write()?;
155        let CssWriter {
156            ref mut w,
157            ref mut sc,
158            debug_mode,
159            ..
160        } = self;
161        if *debug_mode {
162            match sc {
163                WriteCssSepCond::BlockStart | WriteCssSepCond::Whitespace => {}
164                _ => {
165                    write!(w, " ")?;
166                }
167            }
168        }
169        write!(w, "@{}", ident)?;
170        *sc = WriteCssSepCond::NonIdentAlpha;
171        Ok(())
172    }
173
174    pub fn write_colon(&mut self) -> Result {
175        self.prepare_write()?;
176        let CssWriter {
177            ref mut w,
178            ref mut sc,
179            ..
180        } = self;
181        write!(w, ":")?;
182        *sc = WriteCssSepCond::Other;
183        Ok(())
184    }
185
186    pub fn write_semi(&mut self) -> Result {
187        self.prepare_write()?;
188        let CssWriter {
189            ref mut w,
190            ref mut sc,
191            ..
192        } = self;
193        write!(w, ";")?;
194        *sc = WriteCssSepCond::Other;
195        Ok(())
196    }
197
198    pub fn write_delim(&mut self, s: &str, prefer_sep_before: bool) -> Result {
199        self.prepare_write()?;
200        let CssWriter {
201            ref mut w,
202            ref mut sc,
203            debug_mode,
204            ..
205        } = self;
206        let need_sep = if *debug_mode && prefer_sep_before {
207            match sc {
208                WriteCssSepCond::BlockStart | WriteCssSepCond::Whitespace => false,
209                _ => true,
210            }
211        } else {
212            match sc {
213                WriteCssSepCond::Ident => s == "-",
214                WriteCssSepCond::NonIdentAlpha => s == "-",
215                WriteCssSepCond::Digit => s == "." || s == "-" || s == "%",
216                WriteCssSepCond::At => s == "-",
217                WriteCssSepCond::Equalable => s == "=",
218                WriteCssSepCond::Bar => s == "=" || s == "|" || s == "|=",
219                WriteCssSepCond::Slash => s == "*" || s == "*=",
220                _ => false,
221            }
222        };
223        if need_sep {
224            write!(w, " ")?;
225        }
226        write!(w, "{}", s)?;
227        *sc = match s {
228            "@" => WriteCssSepCond::At,
229            "." => WriteCssSepCond::Dot,
230            "+" | "-" => WriteCssSepCond::PlusOrMinus,
231            "$" | "^" | "~" | "*" => WriteCssSepCond::Equalable,
232            "|" => WriteCssSepCond::Bar,
233            "/" => WriteCssSepCond::Slash,
234            _ => WriteCssSepCond::Other,
235        };
236        Ok(())
237    }
238
239    pub fn write_function_block(
240        &mut self,
241        prefer_sep_before: bool,
242        name: &str,
243        f: impl FnOnce(&mut Self) -> Result,
244    ) -> Result {
245        self.prepare_write()?;
246        {
247            let CssWriter {
248                ref mut w,
249                ref mut sc,
250                debug_mode,
251                ..
252            } = self;
253            if *debug_mode && prefer_sep_before {
254                match sc {
255                    WriteCssSepCond::Whitespace => {}
256                    _ => {
257                        write!(w, " ")?;
258                    }
259                }
260            } else {
261                match sc {
262                    WriteCssSepCond::Ident
263                    | WriteCssSepCond::NonIdentAlpha
264                    | WriteCssSepCond::Digit
265                    | WriteCssSepCond::At => {
266                        write!(w, " ")?;
267                    }
268                    _ => {}
269                }
270            }
271            write!(w, "{}(", name)?;
272            *sc = WriteCssSepCond::BlockStart;
273        }
274        f(self)?;
275        {
276            let CssWriter {
277                ref mut w,
278                ref mut sc,
279                ..
280            } = self;
281            write!(w, ")")?;
282            *sc = WriteCssSepCond::Other;
283        }
284        Ok(())
285    }
286
287    pub fn write_paren_block(&mut self, f: impl FnOnce(&mut Self) -> Result) -> Result {
288        self.prepare_write()?;
289        {
290            let CssWriter {
291                ref mut w,
292                ref mut sc,
293                debug_mode,
294                ..
295            } = self;
296            if *debug_mode {
297                match sc {
298                    WriteCssSepCond::Whitespace => {}
299                    _ => {
300                        write!(w, " ")?;
301                    }
302                }
303            } else {
304                match sc {
305                    WriteCssSepCond::Ident => {
306                        write!(w, " ")?;
307                    }
308                    _ => {}
309                }
310            }
311            write!(w, "(")?;
312            *sc = WriteCssSepCond::BlockStart;
313        }
314        f(self)?;
315        {
316            let CssWriter {
317                ref mut w,
318                ref mut sc,
319                ..
320            } = self;
321            write!(w, ")")?;
322            *sc = WriteCssSepCond::Other;
323        }
324        Ok(())
325    }
326
327    pub fn write_bracket_block(&mut self, f: impl FnOnce(&mut Self) -> Result) -> Result {
328        self.prepare_write()?;
329        {
330            let CssWriter {
331                ref mut w,
332                ref mut sc,
333                debug_mode,
334                ..
335            } = self;
336            if *debug_mode {
337                match sc {
338                    WriteCssSepCond::Whitespace => {}
339                    _ => {
340                        write!(w, " ")?;
341                    }
342                }
343            }
344            write!(w, "[")?;
345            *sc = WriteCssSepCond::BlockStart;
346        }
347        f(self)?;
348        {
349            let CssWriter {
350                ref mut w,
351                ref mut sc,
352                ..
353            } = self;
354            write!(w, "]")?;
355            *sc = WriteCssSepCond::Other;
356        }
357        Ok(())
358    }
359
360    pub fn write_brace_block(&mut self, f: impl FnOnce(&mut Self) -> Result) -> Result {
361        self.prepare_write()?;
362        {
363            let CssWriter {
364                ref mut w,
365                ref mut sc,
366                debug_mode,
367                ..
368            } = self;
369            if *debug_mode {
370                match sc {
371                    WriteCssSepCond::Whitespace => {}
372                    _ => {
373                        write!(w, " ")?;
374                    }
375                }
376            }
377            write!(w, "{{")?;
378            *sc = WriteCssSepCond::BlockStart;
379        }
380        self.tab_count += 1;
381        self.line_wrap()?;
382        f(self)?;
383        self.tab_count -= 1;
384        if self.line_status == LineStatus::BlockStart {
385            self.line_status = LineStatus::LineStart;
386        }
387        self.line_wrap()?;
388        self.prepare_write()?;
389        {
390            let CssWriter {
391                ref mut w,
392                ref mut sc,
393                ..
394            } = self;
395            write!(w, "}}")?;
396            *sc = WriteCssSepCond::Other;
397        }
398        self.line_wrap()?;
399        self.line_status = LineStatus::BlockStart;
400        Ok(())
401    }
402}
403
404/// Display as CSS text
405pub trait WriteCss {
406    /// Write CSS text (with specified argument values)
407    fn write_css_with_args<W: CssWriteTarget>(
408        &self,
409        cssw: &mut CssWriter<W>,
410        var_values: &[VarDynValue],
411    ) -> Result;
412
413    /// Write CSS text
414    fn write_css<W: CssWriteTarget>(&self, cssw: &mut CssWriter<W>) -> Result {
415        self.write_css_with_args(cssw, &[])
416    }
417}
418
419/// Separator indicator for `WriteCss`
420#[derive(Debug, Clone, Copy, PartialEq)]
421pub enum WriteCssSepCond {
422    /// The CSS string ends with `CssIdent`
423    ///
424    /// It should not be followed by alphabets, digits, `+`, `-`, or `(` .
425    Ident,
426    /// The CSS string ends with alphabets or digits (but not an ident nor number), `-` or `#`
427    ///
428    /// It should not be followed by alphabets, digits, `+`, or `-` .
429    NonIdentAlpha,
430    /// The CSS string ends with `CssNumber`
431    ///
432    /// It should not be followed by alphabets, digits, `.`, `+`, `-`, or `%` .
433    Digit,
434    /// The CSS string ends with `@`
435    ///
436    /// It should not be followed by alphabets or `-` .
437    At,
438    /// The CSS string ends with `.`
439    ///
440    /// It should not be followed by digits.
441    Dot,
442    /// The CSS string ends with `+` `-`
443    ///
444    /// It should not be followed by alphabets or digits.
445    PlusOrMinus,
446    /// The CSS string ends with `$` `^` `~` `*`
447    ///
448    /// It should not be followed by `=` .
449    Equalable,
450    /// The CSS string ends with `|`
451    ///
452    /// It should not be followed by `=` `|` `|=` .
453    Bar,
454    /// The CSS string ends with `/`
455    ///
456    /// It should not be followed by `*` `*=` .
457    Slash,
458    /// The CSS string ends with `(` `[` `{`
459    ///
460    /// Always no separators needed, but separators may be added in debug mode.
461    BlockStart,
462    /// The CSS string ends with whitespace
463    ///
464    /// Always no separators needed.
465    Whitespace,
466    /// Other cases
467    ///
468    /// Always no separators needed, but separators may be added in debug mode.
469    Other,
470}
471
472impl<V: WriteCss> WriteCss for Option<V> {
473    fn write_css_with_args<W: CssWriteTarget>(
474        &self,
475        cssw: &mut CssWriter<W>,
476        values: &[VarDynValue],
477    ) -> std::result::Result<(), std::fmt::Error> {
478        match self {
479            Some(x) => x.write_css_with_args(cssw, values)?,
480            None => {}
481        }
482        Ok(())
483    }
484}