mod parser;
pub mod computed;
pub mod css_modules;
pub mod web_api;
pub use parser::*;
pub use computed::*;
pub use css_modules::*;
pub use web_api::*;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct Declaration {
pub property: String,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct CssRule {
pub selectors: Vec<String>,
pub declarations: Vec<Declaration>,
}
#[derive(Debug, Clone)]
pub struct MediaQuery {
pub media_type: String,
pub conditions: Vec<String>,
}
impl MediaQuery {
pub fn parse(query: &str) -> Self {
let query = query.trim();
let parts: Vec<&str> = query.split(" and ").collect();
let media_type = parts.first()
.map(|s| s.trim().to_lowercase())
.unwrap_or_else(|| "all".to_string());
let conditions = parts.iter()
.skip(1)
.filter_map(|s| {
let s = s.trim();
if s.starts_with('(') && s.ends_with(')') {
Some(s[1..s.len()-1].trim().to_string())
} else {
None
}
})
.collect();
Self { media_type, conditions }
}
pub fn matches(&self, viewport_width: f32, viewport_height: f32) -> bool {
if self.media_type != "screen" && self.media_type != "all" {
return false;
}
for cond in &self.conditions {
if !self.check_condition(cond, viewport_width, viewport_height) {
return false;
}
}
true
}
fn check_condition(&self, cond: &str, viewport_width: f32, viewport_height: f32) -> bool {
let parts: Vec<&str> = cond.split(':').collect();
if parts.len() != 2 {
return true;
}
let feature = parts[0].trim();
let raw_value = parts[1].trim();
let num: f32 = if raw_value.ends_with("rem") || raw_value.ends_with("em") {
let base_font_size = 16.0_f32;
raw_value.trim_end_matches("px")
.trim_end_matches("em")
.trim_end_matches("rem")
.parse::<f32>()
.unwrap_or(0.0) * base_font_size
} else {
raw_value.trim_end_matches("px")
.parse::<f32>()
.unwrap_or(0.0)
};
match feature {
"min-width" => viewport_width >= num,
"max-width" => viewport_width <= num,
"min-height" => viewport_height >= num,
"max-height" => viewport_height <= num,
_ => true,
}
}
}
#[derive(Debug, Clone)]
pub struct MediaRule {
pub query: MediaQuery,
pub rules: Vec<CssRule>,
}
#[derive(Debug, Clone)]
pub struct Keyframe {
pub selector: String,
pub declarations: Vec<Declaration>,
}
impl Keyframe {
pub fn percentage(&self) -> f32 {
match self.selector.to_lowercase().as_str() {
"from" => 0.0,
"to" => 1.0,
s if s.ends_with('%') => {
s.trim_end_matches('%')
.parse()
.unwrap_or(0.0) / 100.0
}
_ => 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct KeyframesRule {
pub name: String,
pub keyframes: Vec<Keyframe>,
}
#[derive(Debug, Clone, Default)]
pub struct StyleSheet {
pub rules: Vec<CssRule>,
pub media_rules: Vec<MediaRule>,
pub keyframes_rules: Vec<KeyframesRule>,
}
impl StyleSheet {
pub fn parse(css: &str) -> Result<Self, String> {
parser::parse_css(css)
}
pub fn declarations_for_class(&self, class: &str) -> Vec<Declaration> {
let mut result = Vec::new();
let class_selector = format!(".{}", class);
for rule in &self.rules {
if rule.selectors.iter().any(|s| {
s == &class_selector
|| s.split(',').any(|part| part.trim() == class_selector)
}) {
result.extend(rule.declarations.clone());
}
}
result
}
pub fn declarations_for_tag(&self, tag: &str) -> Vec<Declaration> {
let mut result = Vec::new();
for rule in &self.rules {
if rule.selectors.iter().any(|s| s == tag) {
result.extend(rule.declarations.clone());
}
}
result
}
pub fn compute(&self, classes: &[String], tag: &str) -> HashMap<String, String> {
let mut map = HashMap::new();
for class in classes {
for decl in self.declarations_for_class(class) {
map.insert(decl.property, decl.value);
}
}
for decl in self.declarations_for_tag(tag) {
map.entry(decl.property).or_insert(decl.value);
}
map
}
pub fn compute_with_media(&self, classes: &[String], tag: &str, viewport_width: f32, viewport_height: f32) -> HashMap<String, String> {
let mut map = self.compute(classes, tag);
for media_rule in &self.media_rules {
if media_rule.query.matches(viewport_width, viewport_height) {
for rule in &media_rule.rules {
let matches = rule.selectors.iter().any(|s| {
let s_lower = s.to_lowercase();
classes.iter().any(|c| s_lower == format!(".{}", c.to_lowercase())) ||
s_lower == tag.to_lowercase()
});
if matches {
for decl in &rule.declarations {
map.insert(decl.property.clone(), decl.value.clone());
}
}
}
}
}
map
}
pub fn get_keyframes(&self, name: &str) -> Option<&KeyframesRule> {
self.keyframes_rules.iter().find(|k| k.name == name)
}
}