1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use std::collections::HashMap;

use super::constants::COLORS;

pub type Specificity = (usize, usize, usize);

#[derive(Debug, Clone, PartialEq)]
pub enum CssRule {
    Normal(NormalRule),
    Comment(String),
}

#[derive(Debug, Default, Clone, PartialEq)]
pub struct NormalRule {
    pub selectors: Vec<Selector>,
    pub declarations: HashMap<String, Value>,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Selector {
    Simple(SimpleSelector),
}

#[derive(Debug, Clone, Default, PartialEq)]
pub struct SimpleSelector {
    pub id: Option<String>,
    pub class: Vec<String>,
    pub tag_name: Option<String>,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    Keyword(String),
    Length(f32, Unit),
    Color(Color),
    StringLiteral(String),
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Unit {
    /// Centimeters
    Cm,
    /// Millieters
    Mm,
    /// Inches
    In,
    /// Pixels
    Px,
    /// Points
    Pt,
    /// Picas
    Pc,
    /// Relative to the font-size of the element
    Em,
    /// Relative to the x-height of the current font
    Ex,
    /// Relative to the width of the "0"
    Ch,
    /// Relative to font-size of the root element
    Rem,
    /// Relative to 1% of the width of the viewport*
    Vw,
    /// Relative to 1% of the height of the viewport*
    Vh,
    /// Relative to 1% of viewport's* smaller dimension
    VMin,
    /// Relative to 1% of viewport's* larger dimension
    VMax,
    /// Relative to the parent element
    Percent,
}

#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Color {
    pub r: u8,
    pub g: u8,
    pub b: u8,
    pub a: u8,
}

impl Selector {
    /// Computes the specificity of a CSS selector as defined by the W3C specification.
    ///
    /// Returns the count of ID, class, and tag name selectors in a [`Selector::Simple`].
    /// If the selector is not a [`Selector::Simple`], returns `None`.
    ///
    /// See [W3C Selectors Level 3](https://www.w3.org/TR/selectors/#specificity).
    pub fn specificity(&self) -> Option<Specificity> {
        match self {
            Selector::Simple(simple) => {
                let a = simple.id.iter().count();
                let b = simple.class.len();
                let c = simple.tag_name.iter().count();
                Some((a, b, c))
            }
        }
    }
}

impl Unit {
    pub fn from_str(str: &str) -> Option<Self> {
        match str {
            "cm" => Some(Unit::Cm),
            "mm" => Some(Unit::Mm),
            "in" => Some(Unit::In),
            "px" => Some(Unit::Px),
            "pt" => Some(Unit::Pt),
            "pc" => Some(Unit::Pc),

            "em" => Some(Unit::Em),
            "ex" => Some(Unit::Ex),
            "ch" => Some(Unit::Ch),
            "rem" => Some(Unit::Rem),
            "vw" => Some(Unit::Vw),
            "vh" => Some(Unit::Vh),
            "vmin" => Some(Unit::VMin),
            "vmax" => Some(Unit::VMax),
            "%" => Some(Unit::Percent),

            _ => None,
        }
    }
}

impl Value {
    /// Return the length in px, or zero for non-lengths.
    pub fn to_px(&self) -> f32 {
        match *self {
            Value::Length(f, _) => f,
            _ => 0.0,
        }
    }
}

impl Color {
    pub fn from_hex(hex: &str) -> Self {
        let hex = hex.trim_start_matches('#');
        let num = i32::from_str_radix(&hex[0..], 16).unwrap();
        let r = (num >> 16) as u8;
        let g = (num >> 8) as u8;
        let b = num as u8;

        Self {
            r,
            g,
            b,
            a: 255
        }
    }

    pub fn from_keyword(name: &str) -> Self {
        let unlocked = COLORS.lock().unwrap();
        unlocked.get(&name.to_lowercase()).unwrap().clone()
    }
}