1#[cfg(test)]
4mod tests;
5
6use crate::component::ComponentId;
7use crate::layout::{Dimension, EdgeValues, LayoutStyle};
8use std::collections::HashMap;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
13pub struct Specificity(pub u32, pub u32, pub u32);
14
15#[derive(Debug, Clone, PartialEq)]
17pub struct CssProperty {
18 pub name: String,
20 pub value: String,
22}
23
24#[derive(Debug, Clone, PartialEq)]
26pub struct CssSelector {
27 pub selector: String,
29 pub properties: Vec<CssProperty>,
31}
32
33#[derive(Debug, Clone)]
35pub struct StyleRule {
36 pub selectors: Vec<CssSelector>,
38 pub scoped: bool,
40 pub specificity: Specificity,
42 pub source_order: usize,
44}
45
46#[derive(Debug, Clone, Default)]
48pub struct Stylesheet {
49 pub rules: Vec<StyleRule>,
51}
52
53impl StyleRule {
54 pub fn new(selectors: Vec<CssSelector>, scoped: bool, source_order: usize) -> Self {
56 let specificity = selectors
58 .iter()
59 .map(|s| Self::calculate_specificity(&s.selector))
60 .max()
61 .unwrap_or(Specificity(0, 0, 0));
62
63 Self {
64 selectors,
65 scoped,
66 specificity,
67 source_order,
68 }
69 }
70
71 fn calculate_specificity(selector: &str) -> Specificity {
76 let mut a = 0; let mut b = 0; let mut c = 0; for part in selector.split(' ') {
81 a += part.matches('#').count() as u32;
83
84 b += part.matches('.').count() as u32;
86 b += part.matches('[').count() as u32;
87 b += part.matches(':').count() as u32;
88 b -= part.matches("::").count() as u32; if !part.starts_with('.') && !part.starts_with('#') && !part.starts_with('[') {
92 c += 1;
93 }
94 c += part.matches("::").count() as u32;
95 }
96
97 Specificity(a, b, c)
98 }
99
100 pub fn apply_scoping(&mut self, component_id: &str) {
102 if self.scoped {
103 for selector in &mut self.selectors {
104 selector.selector = format!(".{} {}", component_id, selector.selector);
106 self.specificity = Self::calculate_specificity(&selector.selector);
107 }
108 }
109 }
110}
111
112impl Stylesheet {
113 pub fn new() -> Self {
115 Self::default()
116 }
117
118 pub fn add_rule(&mut self, rule: StyleRule) {
120 self.rules.push(rule);
121 }
122
123 pub fn parse(css: &str, scoped: bool) -> Result<Self, StyleError> {
125 let mut stylesheet = Self::new();
126 let mut current_selectors = Vec::new();
127 let mut current_properties = Vec::new();
128 let mut in_rule = false;
129 let mut source_order = 0;
130
131 for line in css.lines() {
132 let line = line.trim();
133
134 if line.is_empty() || line.starts_with("/*") {
136 continue;
137 }
138
139 if line.contains('{') {
140 in_rule = true;
142 current_selectors = line
143 .split('{')
144 .next()
145 .unwrap_or("")
146 .split(',')
147 .map(|s| CssSelector {
148 selector: s.trim().to_string(),
149 properties: Vec::new(),
150 })
151 .collect();
152 } else if line.contains('}') {
153 in_rule = false;
155 if !current_selectors.is_empty() {
156 for selector in &mut current_selectors {
157 selector.properties = current_properties.clone();
158 }
159 let rule = StyleRule::new(current_selectors.clone(), scoped, source_order);
160 stylesheet.add_rule(rule);
161 source_order += 1;
162 }
163 current_selectors.clear();
164 current_properties.clear();
165 } else if in_rule && line.contains(':') {
166 let parts: Vec<&str> = line.split(':').collect();
168 if parts.len() == 2 {
169 current_properties.push(CssProperty {
170 name: parts[0].trim().to_string(),
171 value: parts[1].trim().trim_end_matches(';').to_string(),
172 });
173 }
174 }
175 }
176
177 Ok(stylesheet)
178 }
179}
180
181impl std::fmt::Display for Stylesheet {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 let mut result = String::new();
184
185 for rule in &self.rules {
186 for selector in &rule.selectors {
187 result.push_str(&selector.selector);
188 if rule.scoped {
189 result.push_str(" /* scoped */");
190 }
191 result.push_str(" {\n");
192
193 for property in &selector.properties {
194 result.push_str(" ");
195 result.push_str(&property.name);
196 result.push_str(": ");
197 result.push_str(&property.value);
198 result.push_str(";\n");
199 }
200
201 result.push_str("}\n\n");
202 }
203 }
204
205 write!(f, "{result}")
206 }
207}
208
209#[derive(Debug, Clone, Default, PartialEq)]
211pub struct Style {
212 pub background_color: Option<Color>,
214 pub color: Option<Color>,
215 pub opacity: Option<f32>,
216 pub visibility: Option<Visibility>,
217
218 pub border_width: Option<EdgeValues>,
220 pub border_color: Option<EdgeColors>,
221 pub border_style: Option<BorderStyle>,
222 pub border_radius: Option<BorderRadius>,
223
224 pub font_family: Option<String>,
226 pub font_size: Option<f32>,
227 pub font_weight: Option<FontWeight>,
228 pub font_style: Option<FontStyle>,
229 pub line_height: Option<f32>,
230 pub letter_spacing: Option<f32>,
231 pub text_align: Option<TextAlign>,
232 pub text_decoration: Option<TextDecoration>,
233 pub text_transform: Option<TextTransform>,
234
235 pub layout_style: Option<LayoutStyle>,
237
238 pub transform: Option<Transform>,
240 pub transform_origin: Option<Point2D>,
241
242 pub transition_property: Option<Vec<String>>,
244 pub transition_duration: Option<f32>,
245 pub transition_timing_function: Option<TimingFunction>,
246 pub transition_delay: Option<f32>,
247
248 pub box_shadow: Option<Vec<BoxShadow>>,
250 pub text_shadow: Option<Vec<TextShadow>>,
251
252 pub filter: Option<Vec<Filter>>,
254 pub backdrop_filter: Option<Vec<Filter>>,
255 pub z_index: Option<i32>,
256 pub cursor: Option<CursorType>,
257
258 pub computed_hash: Option<u64>,
260 pub is_dirty: bool,
261}
262
263#[derive(Debug, Clone, PartialEq)]
265pub enum Color {
266 Rgba(f32, f32, f32, f32),
267 Hex(String),
268 Named(String),
269 Hsl(f32, f32, f32, f32),
270 CurrentColor,
271 Transparent,
272}
273
274impl std::hash::Hash for Color {
275 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
276 match self {
277 Color::Rgba(r, g, b, a) => {
278 0u8.hash(state);
279 r.to_bits().hash(state);
280 g.to_bits().hash(state);
281 b.to_bits().hash(state);
282 a.to_bits().hash(state);
283 }
284 Color::Hex(s) => {
285 1u8.hash(state);
286 s.hash(state);
287 }
288 Color::Named(s) => {
289 2u8.hash(state);
290 s.hash(state);
291 }
292 Color::Hsl(h, s, l, a) => {
293 3u8.hash(state);
294 h.to_bits().hash(state);
295 s.to_bits().hash(state);
296 l.to_bits().hash(state);
297 a.to_bits().hash(state);
298 }
299 Color::CurrentColor => {
300 4u8.hash(state);
301 }
302 Color::Transparent => {
303 5u8.hash(state);
304 }
305 }
306 }
307}
308
309#[derive(Debug, Clone, PartialEq)]
311pub enum Visibility {
312 Visible,
313 Hidden,
314 Collapse,
315}
316
317#[derive(Debug, Clone, PartialEq)]
319pub enum BorderStyle {
320 None,
321 Solid,
322 Dashed,
323 Dotted,
324 Double,
325 Groove,
326 Ridge,
327 Inset,
328 Outset,
329}
330
331#[derive(Debug, Clone, PartialEq)]
333pub struct BorderRadius {
334 pub top_left: f32,
335 pub top_right: f32,
336 pub bottom_right: f32,
337 pub bottom_left: f32,
338}
339
340#[derive(Debug, Clone, PartialEq)]
342pub struct EdgeColors {
343 pub top: Color,
344 pub right: Color,
345 pub bottom: Color,
346 pub left: Color,
347}
348
349#[derive(Debug, Clone, PartialEq)]
351pub enum FontWeight {
352 Thin, ExtraLight, Light, Normal, Medium, SemiBold, Bold, ExtraBold, Black, Numeric(u16),
362}
363
364#[derive(Debug, Clone, PartialEq)]
366pub enum FontStyle {
367 Normal,
368 Italic,
369 Oblique(f32), }
371
372#[derive(Debug, Clone, PartialEq)]
374pub enum TextAlign {
375 Left,
376 Right,
377 Center,
378 Justify,
379 Start,
380 End,
381}
382
383#[derive(Debug, Clone, PartialEq)]
385pub enum TextDecoration {
386 None,
387 Underline,
388 Overline,
389 LineThrough,
390 Blink,
391}
392
393#[derive(Debug, Clone, PartialEq)]
395pub enum TextTransform {
396 None,
397 Capitalize,
398 Uppercase,
399 Lowercase,
400}
401
402#[derive(Debug, Clone, PartialEq)]
404pub struct Point2D {
405 pub x: f32,
406 pub y: f32,
407}
408
409#[derive(Debug, Clone, PartialEq)]
411pub enum Transform {
412 None,
413 Matrix(f32, f32, f32, f32, f32, f32),
414 Translate(f32, f32),
415 TranslateX(f32),
416 TranslateY(f32),
417 Scale(f32, f32),
418 ScaleX(f32),
419 ScaleY(f32),
420 Rotate(f32), SkewX(f32), SkewY(f32), Multiple(Vec<Transform>),
424}
425
426#[derive(Debug, Clone, PartialEq)]
428pub enum TimingFunction {
429 Linear,
430 Ease,
431 EaseIn,
432 EaseOut,
433 EaseInOut,
434 CubicBezier(f32, f32, f32, f32),
435 Steps(u32, StepPosition),
436}
437
438#[derive(Debug, Clone, PartialEq)]
440pub enum StepPosition {
441 Start,
442 End,
443}
444
445#[derive(Debug, Clone, PartialEq)]
447pub struct BoxShadow {
448 pub offset_x: f32,
449 pub offset_y: f32,
450 pub blur_radius: f32,
451 pub spread_radius: f32,
452 pub color: Color,
453 pub inset: bool,
454}
455
456#[derive(Debug, Clone, PartialEq)]
458pub struct TextShadow {
459 pub offset_x: f32,
460 pub offset_y: f32,
461 pub blur_radius: f32,
462 pub color: Color,
463}
464
465#[derive(Debug, Clone, PartialEq)]
467pub enum Filter {
468 Blur(f32),
469 Brightness(f32),
470 Contrast(f32),
471 DropShadow(f32, f32, f32, Color),
472 Grayscale(f32),
473 HueRotate(f32),
474 Invert(f32),
475 Opacity(f32),
476 Saturate(f32),
477 Sepia(f32),
478}
479
480#[derive(Debug, Clone, PartialEq)]
482pub enum CursorType {
483 Auto,
484 Default,
485 None,
486 ContextMenu,
487 Help,
488 Pointer,
489 Progress,
490 Wait,
491 Cell,
492 Crosshair,
493 Text,
494 VerticalText,
495 Alias,
496 Copy,
497 Move,
498 NoDrop,
499 NotAllowed,
500 Grab,
501 Grabbing,
502 EResize,
503 NResize,
504 NeResize,
505 NwResize,
506 SResize,
507 SeResize,
508 SwResize,
509 WResize,
510 EwResize,
511 NsResize,
512 NeswResize,
513 NwseResize,
514 ColResize,
515 RowResize,
516 AllScroll,
517 ZoomIn,
518 ZoomOut,
519}
520
521#[derive(Debug)]
523pub struct StyleEngine {
524 computed_cache: HashMap<u64, ComputedStyle>,
526 inheritance_tree: HashMap<ComponentId, ComponentId>,
528 global_rules: Vec<StyleRule>,
530 component_rules: HashMap<ComponentId, Vec<StyleRule>>,
532 stats: StyleStats,
534 cache_hit_counter: AtomicU64,
536}
537
538#[derive(Debug, Clone, PartialEq)]
540pub struct ComputedStyle {
541 pub style: Style,
543 pub layout_style: LayoutStyle,
545 pub hash: u64,
547 pub is_animatable: bool,
549 pub computed_at: std::time::Instant,
551}
552
553#[derive(Debug, Default, Clone)]
555pub struct StyleStats {
556 pub computations: u64,
558 pub cache_hits: u64,
560 pub computation_time_ms: f32,
562 pub avg_computation_time_ms: f32,
564 pub layout_conversions: u64,
566 pub inheritance_operations: u64,
568 pub cache_size: usize,
570}
571
572#[derive(Debug)]
574pub struct StyleContext {
575 pub viewport_width: f32,
577 pub viewport_height: f32,
578 pub device_pixel_ratio: f32,
580 pub inherited_style: Option<ComputedStyle>,
582 pub component_id: Option<ComponentId>,
584 pub theme_variables: HashMap<String, String>,
586 pub performance_monitoring: bool,
588}
589
590impl Style {
591 pub fn new() -> Self {
593 Self::default()
594 }
595
596 pub fn merge(&mut self, other: &Style) {
598 if let Some(color) = &other.color {
599 self.color = Some(color.clone());
600 }
601 if let Some(bg) = &other.background_color {
602 self.background_color = Some(bg.clone());
603 }
604 }
606}
607
608#[derive(Debug, thiserror::Error)]
610pub enum StyleError {
611 #[error("Error parsing CSS: {0}")]
612 ParseError(String),
613}
614
615impl StyleEngine {
616 pub fn new() -> Self {
618 Self {
619 computed_cache: HashMap::new(),
620 inheritance_tree: HashMap::new(),
621 global_rules: Vec::new(),
622 component_rules: HashMap::new(),
623 stats: StyleStats::default(),
624 cache_hit_counter: AtomicU64::new(0),
625 }
626 }
627
628 pub fn add_global_rules(&mut self, rules: Vec<StyleRule>) {
630 self.global_rules.extend(rules);
631 }
632
633 pub fn add_component_rules(&mut self, component_id: ComponentId, rules: Vec<StyleRule>) {
635 self.component_rules
636 .entry(component_id)
637 .or_default()
638 .extend(rules);
639 }
640
641 pub fn compute_style(
643 &mut self,
644 component_id: ComponentId,
645 base_style: &Style,
646 context: &StyleContext,
647 ) -> Result<ComputedStyle, StyleError> {
648 let start_time = std::time::Instant::now();
649
650 let cache_key = self.generate_cache_key(component_id, base_style, context);
652
653 if let Some(computed) = self.computed_cache.get(&cache_key) {
655 self.cache_hit_counter.fetch_add(1, Ordering::Relaxed);
656 self.stats.cache_hits += 1;
657 return Ok(computed.clone());
658 }
659
660 let mut computed_style = base_style.clone();
662
663 if let Some(inherited) = &context.inherited_style {
665 self.apply_inheritance(&mut computed_style, &inherited.style);
666 }
667
668 self.apply_css_rules(&mut computed_style, &self.global_rules.clone(), context)?;
670
671 if let Some(component_rules) = self.component_rules.get(&component_id) {
673 self.apply_css_rules(&mut computed_style, component_rules, context)?;
674 }
675
676 let layout_style = self.style_to_layout_style(&computed_style, context)?;
678
679 let computed = ComputedStyle {
681 style: computed_style.clone(),
682 layout_style,
683 hash: cache_key,
684 is_animatable: self.is_style_animatable(&computed_style),
685 computed_at: std::time::Instant::now(),
686 };
687
688 self.computed_cache.insert(cache_key, computed.clone());
690
691 let computation_time = start_time.elapsed().as_secs_f32() * 1000.0;
693 self.stats.computations += 1;
694 self.stats.computation_time_ms += computation_time;
695 self.stats.avg_computation_time_ms =
696 self.stats.computation_time_ms / self.stats.computations as f32;
697 self.stats.cache_size = self.computed_cache.len();
698
699 Ok(computed)
700 }
701
702 fn generate_cache_key(
704 &self,
705 component_id: ComponentId,
706 style: &Style,
707 context: &StyleContext,
708 ) -> u64 {
709 use std::collections::hash_map::DefaultHasher;
710 use std::hash::{Hash, Hasher};
711
712 let mut hasher = DefaultHasher::new();
713 component_id.hash(&mut hasher);
714
715 style.color.hash(&mut hasher);
717 style.background_color.hash(&mut hasher);
718 style.font_size.map(|f| f.to_bits()).hash(&mut hasher);
719 style.opacity.map(|f| f.to_bits()).hash(&mut hasher);
720
721 context.viewport_width.to_bits().hash(&mut hasher);
723 context.viewport_height.to_bits().hash(&mut hasher);
724 context.device_pixel_ratio.to_bits().hash(&mut hasher);
725
726 hasher.finish()
727 }
728
729 fn apply_inheritance(&mut self, style: &mut Style, parent_style: &Style) {
731 if style.font_family.is_none() {
733 style.font_family = parent_style.font_family.clone();
734 }
735 if style.font_size.is_none() {
736 style.font_size = parent_style.font_size;
737 }
738 if style.font_weight.is_none() {
739 style.font_weight = parent_style.font_weight.clone();
740 }
741 if style.color.is_none() {
742 style.color = parent_style.color.clone();
743 }
744 if style.line_height.is_none() {
745 style.line_height = parent_style.line_height;
746 }
747 if style.letter_spacing.is_none() {
748 style.letter_spacing = parent_style.letter_spacing;
749 }
750 if style.text_align.is_none() {
751 style.text_align = parent_style.text_align.clone();
752 }
753
754 self.stats.inheritance_operations += 1;
755 }
756
757 fn apply_css_rules(
759 &self,
760 style: &mut Style,
761 rules: &[StyleRule],
762 context: &StyleContext,
763 ) -> Result<(), StyleError> {
764 let mut sorted_rules = rules.to_vec();
766 sorted_rules.sort_by(|a, b| {
767 a.specificity
768 .cmp(&b.specificity)
769 .then(a.source_order.cmp(&b.source_order))
770 });
771
772 for rule in sorted_rules {
774 for selector in &rule.selectors {
775 for property in &selector.properties {
776 self.apply_css_property(style, property, context)?;
777 }
778 }
779 }
780
781 Ok(())
782 }
783
784 fn apply_css_property(
786 &self,
787 style: &mut Style,
788 property: &CssProperty,
789 _context: &StyleContext,
790 ) -> Result<(), StyleError> {
791 match property.name.as_str() {
792 "color" => {
793 style.color = Some(self.parse_color(&property.value)?);
794 }
795 "background-color" => {
796 style.background_color = Some(self.parse_color(&property.value)?);
797 }
798 "opacity" => {
799 style.opacity = property.value.parse().ok();
800 }
801 "font-size" => {
802 style.font_size = self.parse_font_size(&property.value);
803 }
804 "font-weight" => {
805 style.font_weight = Some(self.parse_font_weight(&property.value)?);
806 }
807 "font-family" => {
808 style.font_family = Some(property.value.clone());
809 }
810 "text-align" => {
811 style.text_align = Some(self.parse_text_align(&property.value)?);
812 }
813 "border-radius" => {
814 style.border_radius = Some(self.parse_border_radius(&property.value)?);
815 }
816 "z-index" => {
817 style.z_index = property.value.parse().ok();
818 }
819 _ => {
820 }
822 }
823
824 Ok(())
825 }
826
827 fn style_to_layout_style(
829 &mut self,
830 style: &Style,
831 context: &StyleContext,
832 ) -> Result<LayoutStyle, StyleError> {
833 let mut layout_style = LayoutStyle::default();
834
835 if let Some(existing_layout) = &style.layout_style {
837 layout_style = existing_layout.clone();
838 }
839
840 if let Some(opacity) = style.opacity {
842 if opacity == 0.0 {
844 }
846 }
847
848 if let Some(border_width) = &style.border_width {
850 layout_style.border = *border_width;
852 }
853
854 if let Some(_transform) = &style.transform {
856 }
859
860 self.apply_responsive_sizing(&mut layout_style, context);
862
863 self.stats.layout_conversions += 1;
864 Ok(layout_style)
865 }
866 fn apply_responsive_sizing(&self, layout_style: &mut LayoutStyle, context: &StyleContext) {
868 let scale_factor = context.device_pixel_ratio;
870
871 if let Dimension::Points(width) = layout_style.width {
873 layout_style.width = Dimension::Points(width * scale_factor);
874 }
875 if let Dimension::Points(height) = layout_style.height {
876 layout_style.height = Dimension::Points(height * scale_factor);
877 }
878 }
879
880 fn is_style_animatable(&self, style: &Style) -> bool {
882 style.opacity.is_some()
883 || style.background_color.is_some()
884 || style.color.is_some()
885 || style.transform.is_some()
886 || style.border_radius.is_some()
887 || style.font_size.is_some()
888 || style.transition_duration.is_some()
889 }
890
891 fn parse_color(&self, value: &str) -> Result<Color, StyleError> {
893 let value = value.trim();
894
895 if value.starts_with('#') {
896 Ok(Color::Hex(value.to_string()))
897 } else if value.starts_with("rgb(") {
898 let inner = value.trim_start_matches("rgb(").trim_end_matches(')');
900 let parts: Vec<&str> = inner.split(',').collect();
901 if parts.len() == 3 {
902 let r = parts[0]
903 .trim()
904 .parse::<u8>()
905 .map_err(|_| StyleError::ParseError("Invalid red value".to_string()))?
906 as f32
907 / 255.0;
908 let g = parts[1]
909 .trim()
910 .parse::<u8>()
911 .map_err(|_| StyleError::ParseError("Invalid green value".to_string()))?
912 as f32
913 / 255.0;
914 let b = parts[2]
915 .trim()
916 .parse::<u8>()
917 .map_err(|_| StyleError::ParseError("Invalid blue value".to_string()))?
918 as f32
919 / 255.0;
920 Ok(Color::Rgba(r, g, b, 1.0))
921 } else {
922 Err(StyleError::ParseError("Invalid RGB format".to_string()))
923 }
924 } else if value.starts_with("rgba(") {
925 let inner = value.trim_start_matches("rgba(").trim_end_matches(')');
927 let parts: Vec<&str> = inner.split(',').collect();
928 if parts.len() == 4 {
929 let r = parts[0]
930 .trim()
931 .parse::<u8>()
932 .map_err(|_| StyleError::ParseError("Invalid red value".to_string()))?
933 as f32
934 / 255.0;
935 let g = parts[1]
936 .trim()
937 .parse::<u8>()
938 .map_err(|_| StyleError::ParseError("Invalid green value".to_string()))?
939 as f32
940 / 255.0;
941 let b = parts[2]
942 .trim()
943 .parse::<u8>()
944 .map_err(|_| StyleError::ParseError("Invalid blue value".to_string()))?
945 as f32
946 / 255.0;
947 let a = parts[3]
948 .trim()
949 .parse::<f32>()
950 .map_err(|_| StyleError::ParseError("Invalid alpha value".to_string()))?;
951 Ok(Color::Rgba(r, g, b, a))
952 } else {
953 Err(StyleError::ParseError("Invalid RGBA format".to_string()))
954 }
955 } else if value == "transparent" {
956 Ok(Color::Transparent)
957 } else if value == "currentColor" {
958 Ok(Color::CurrentColor)
959 } else {
960 Ok(Color::Named(value.to_string()))
961 }
962 }
963
964 fn parse_font_size(&self, value: &str) -> Option<f32> {
966 let value = value.trim();
967 if value.ends_with("px") {
968 value.trim_end_matches("px").parse().ok()
969 } else if value.ends_with("pt") {
970 value
971 .trim_end_matches("pt")
972 .parse::<f32>()
973 .map(|pt| pt * 1.333)
974 .ok()
975 } else if value.ends_with("em") {
976 value
977 .trim_end_matches("em")
978 .parse::<f32>()
979 .map(|em| em * 16.0)
980 .ok()
981 } else {
982 value.parse().ok()
983 }
984 }
985
986 fn parse_font_weight(&self, value: &str) -> Result<FontWeight, StyleError> {
988 match value.trim() {
989 "thin" | "100" => Ok(FontWeight::Thin),
990 "extra-light" | "200" => Ok(FontWeight::ExtraLight),
991 "light" | "300" => Ok(FontWeight::Light),
992 "normal" | "400" => Ok(FontWeight::Normal),
993 "medium" | "500" => Ok(FontWeight::Medium),
994 "semi-bold" | "600" => Ok(FontWeight::SemiBold),
995 "bold" | "700" => Ok(FontWeight::Bold),
996 "extra-bold" | "800" => Ok(FontWeight::ExtraBold),
997 "black" | "900" => Ok(FontWeight::Black),
998 _ => {
999 if let Ok(numeric) = value.parse::<u16>() {
1000 Ok(FontWeight::Numeric(numeric))
1001 } else {
1002 Err(StyleError::ParseError(format!(
1003 "Invalid font weight: {value}"
1004 )))
1005 }
1006 }
1007 }
1008 }
1009
1010 fn parse_text_align(&self, value: &str) -> Result<TextAlign, StyleError> {
1012 match value.trim() {
1013 "left" => Ok(TextAlign::Left),
1014 "right" => Ok(TextAlign::Right),
1015 "center" => Ok(TextAlign::Center),
1016 "justify" => Ok(TextAlign::Justify),
1017 "start" => Ok(TextAlign::Start),
1018 "end" => Ok(TextAlign::End),
1019 _ => Err(StyleError::ParseError(format!(
1020 "Invalid text align: {value}"
1021 ))),
1022 }
1023 }
1024
1025 fn parse_border_radius(&self, value: &str) -> Result<BorderRadius, StyleError> {
1027 let parts: Vec<&str> = value.split_whitespace().collect();
1028 match parts.len() {
1029 1 => {
1030 let radius = self.parse_length(parts[0])?;
1031 Ok(BorderRadius {
1032 top_left: radius,
1033 top_right: radius,
1034 bottom_right: radius,
1035 bottom_left: radius,
1036 })
1037 }
1038 2 => {
1039 let radius1 = self.parse_length(parts[0])?;
1040 let radius2 = self.parse_length(parts[1])?;
1041 Ok(BorderRadius {
1042 top_left: radius1,
1043 top_right: radius2,
1044 bottom_right: radius1,
1045 bottom_left: radius2,
1046 })
1047 }
1048 4 => Ok(BorderRadius {
1049 top_left: self.parse_length(parts[0])?,
1050 top_right: self.parse_length(parts[1])?,
1051 bottom_right: self.parse_length(parts[2])?,
1052 bottom_left: self.parse_length(parts[3])?,
1053 }),
1054 _ => Err(StyleError::ParseError(
1055 "Invalid border radius format".to_string(),
1056 )),
1057 }
1058 }
1059
1060 fn parse_length(&self, value: &str) -> Result<f32, StyleError> {
1062 let value = value.trim();
1063 if value.ends_with("px") {
1064 value
1065 .trim_end_matches("px")
1066 .parse()
1067 .map_err(|_| StyleError::ParseError("Invalid pixel value".to_string()))
1068 } else if value.ends_with("pt") {
1069 value
1070 .trim_end_matches("pt")
1071 .parse::<f32>()
1072 .map(|pt| pt * 1.333)
1073 .map_err(|_| StyleError::ParseError("Invalid point value".to_string()))
1074 } else if value.ends_with("em") {
1075 value
1076 .trim_end_matches("em")
1077 .parse::<f32>()
1078 .map(|em| em * 16.0)
1079 .map_err(|_| StyleError::ParseError("Invalid em value".to_string()))
1080 } else {
1081 value
1082 .parse()
1083 .map_err(|_| StyleError::ParseError("Invalid length value".to_string()))
1084 }
1085 }
1086
1087 pub fn get_stats(&self) -> StyleStats {
1089 let mut stats = self.stats.clone();
1090 stats.cache_hits = self.cache_hit_counter.load(Ordering::Relaxed);
1091 stats.cache_size = self.computed_cache.len();
1092 stats
1093 }
1094
1095 pub fn clear_cache(&mut self) {
1097 self.computed_cache.clear();
1098 self.stats.cache_size = 0;
1099 }
1100
1101 pub fn set_inheritance(&mut self, child_id: ComponentId, parent_id: ComponentId) {
1103 self.inheritance_tree.insert(child_id, parent_id);
1104 }
1105}
1106
1107impl Default for StyleEngine {
1108 fn default() -> Self {
1109 Self::new()
1110 }
1111}
1112
1113impl Default for StyleContext {
1114 fn default() -> Self {
1115 Self {
1116 viewport_width: 1920.0,
1117 viewport_height: 1080.0,
1118 device_pixel_ratio: 1.0,
1119 inherited_style: None,
1120 component_id: None,
1121 theme_variables: HashMap::new(),
1122 performance_monitoring: false,
1123 }
1124 }
1125}
1126
1127impl ComputedStyle {
1128 pub fn is_expired(&self, max_age_ms: u128) -> bool {
1130 self.computed_at.elapsed().as_millis() > max_age_ms
1131 }
1132
1133 pub fn get_property(&self, name: &str) -> Option<String> {
1135 match name {
1136 "color" => self.style.color.as_ref().map(|c| format!("{c:?}")),
1137 "background-color" => self
1138 .style
1139 .background_color
1140 .as_ref()
1141 .map(|c| format!("{c:?}")),
1142 "opacity" => self.style.opacity.map(|o| o.to_string()),
1143 "font-size" => self.style.font_size.map(|f| format!("{f}px")),
1144 "font-weight" => self.style.font_weight.as_ref().map(|w| format!("{w:?}")),
1145 _ => None,
1146 }
1147 }
1148}