1mod parser;
8pub mod computed;
9pub mod css_modules;
10pub mod web_api;
11
12pub use parser::*;
13pub use computed::*;
14pub use css_modules::*;
15pub use web_api::*;
16
17use std::collections::HashMap;
18
19#[derive(Debug, Clone, PartialEq)]
21pub struct Declaration {
22 pub property: String,
23 pub value: String,
24}
25
26#[derive(Debug, Clone)]
28pub struct CssRule {
29 pub selectors: Vec<String>,
30 pub declarations: Vec<Declaration>,
31}
32
33#[derive(Debug, Clone)]
35pub struct MediaQuery {
36 pub media_type: String,
38 pub conditions: Vec<String>,
40}
41
42impl MediaQuery {
43 pub fn parse(query: &str) -> Self {
45 let query = query.trim();
46 let parts: Vec<&str> = query.split(" and ").collect();
47 let media_type = parts.first()
48 .map(|s| s.trim().to_lowercase())
49 .unwrap_or_else(|| "all".to_string());
50
51 let conditions = parts.iter()
52 .skip(1)
53 .filter_map(|s| {
54 let s = s.trim();
55 if s.starts_with('(') && s.ends_with(')') {
56 Some(s[1..s.len()-1].trim().to_string())
57 } else {
58 None
59 }
60 })
61 .collect();
62
63 Self { media_type, conditions }
64 }
65
66 pub fn matches(&self, viewport_width: f32, viewport_height: f32) -> bool {
68 if self.media_type != "screen" && self.media_type != "all" {
70 return false;
71 }
72
73 for cond in &self.conditions {
75 if !self.check_condition(cond, viewport_width, viewport_height) {
76 return false;
77 }
78 }
79 true
80 }
81
82 fn check_condition(&self, cond: &str, viewport_width: f32, viewport_height: f32) -> bool {
83 let parts: Vec<&str> = cond.split(':').collect();
84 if parts.len() != 2 {
85 return true;
86 }
87 let feature = parts[0].trim();
88 let raw_value = parts[1].trim();
89
90 let num: f32 = if raw_value.ends_with("rem") || raw_value.ends_with("em") {
92 let base_font_size = 16.0_f32;
94 raw_value.trim_end_matches("px")
95 .trim_end_matches("em")
96 .trim_end_matches("rem")
97 .parse::<f32>()
98 .unwrap_or(0.0) * base_font_size
99 } else {
100 raw_value.trim_end_matches("px")
101 .parse::<f32>()
102 .unwrap_or(0.0)
103 };
104
105 match feature {
106 "min-width" => viewport_width >= num,
107 "max-width" => viewport_width <= num,
108 "min-height" => viewport_height >= num,
109 "max-height" => viewport_height <= num,
110 _ => true,
111 }
112 }
113}
114
115#[derive(Debug, Clone)]
117pub struct MediaRule {
118 pub query: MediaQuery,
119 pub rules: Vec<CssRule>,
120}
121
122#[derive(Debug, Clone)]
124pub struct Keyframe {
125 pub selector: String,
127 pub declarations: Vec<Declaration>,
128}
129
130impl Keyframe {
131 pub fn percentage(&self) -> f32 {
133 match self.selector.to_lowercase().as_str() {
134 "from" => 0.0,
135 "to" => 1.0,
136 s if s.ends_with('%') => {
137 s.trim_end_matches('%')
138 .parse()
139 .unwrap_or(0.0) / 100.0
140 }
141 _ => 0.0,
142 }
143 }
144}
145
146#[derive(Debug, Clone)]
148pub struct KeyframesRule {
149 pub name: String,
150 pub keyframes: Vec<Keyframe>,
151}
152
153#[derive(Debug, Clone, Default)]
155pub struct StyleSheet {
156 pub rules: Vec<CssRule>,
157 pub media_rules: Vec<MediaRule>,
159 pub keyframes_rules: Vec<KeyframesRule>,
161}
162
163impl StyleSheet {
164 pub fn parse(css: &str) -> Result<Self, String> {
166 parser::parse_css(css)
167 }
168
169 pub fn declarations_for_class(&self, class: &str) -> Vec<Declaration> {
171 let mut result = Vec::new();
172 let class_selector = format!(".{}", class);
173 for rule in &self.rules {
174 if rule.selectors.iter().any(|s| {
175 s == &class_selector
177 || s.split(',').any(|part| part.trim() == class_selector)
178 }) {
179 result.extend(rule.declarations.clone());
180 }
181 }
182 result
183 }
184
185 pub fn declarations_for_tag(&self, tag: &str) -> Vec<Declaration> {
187 let mut result = Vec::new();
188 for rule in &self.rules {
189 if rule.selectors.iter().any(|s| s == tag) {
190 result.extend(rule.declarations.clone());
191 }
192 }
193 result
194 }
195
196 pub fn compute(&self, classes: &[String], tag: &str) -> HashMap<String, String> {
198 let mut map = HashMap::new();
199 for class in classes {
200 for decl in self.declarations_for_class(class) {
201 map.insert(decl.property, decl.value);
202 }
203 }
204 for decl in self.declarations_for_tag(tag) {
205 map.entry(decl.property).or_insert(decl.value);
206 }
207 map
208 }
209
210 pub fn compute_with_media(&self, classes: &[String], tag: &str, viewport_width: f32, viewport_height: f32) -> HashMap<String, String> {
212 let mut map = self.compute(classes, tag);
213
214 for media_rule in &self.media_rules {
216 if media_rule.query.matches(viewport_width, viewport_height) {
217 for rule in &media_rule.rules {
218 let matches = rule.selectors.iter().any(|s| {
220 let s_lower = s.to_lowercase();
221 classes.iter().any(|c| s_lower == format!(".{}", c.to_lowercase())) ||
223 s_lower == tag.to_lowercase()
225 });
226 if matches {
227 for decl in &rule.declarations {
228 map.insert(decl.property.clone(), decl.value.clone());
229 }
230 }
231 }
232 }
233 }
234
235 map
236 }
237
238 pub fn get_keyframes(&self, name: &str) -> Option<&KeyframesRule> {
240 self.keyframes_rules.iter().find(|k| k.name == name)
241 }
242}