1use oxiui_core::Color;
27
28#[derive(Debug, Clone, PartialEq)]
32pub enum CssValue {
33 Color(Color),
35 Number(f32),
37 Keyword(String),
39 Inherit,
41 Initial,
43 Unset,
45}
46
47#[derive(Debug, Clone, Default, PartialEq)]
49pub struct ComputedStyle {
50 pub color: Option<CssValue>,
52 pub background_color: Option<CssValue>,
54 pub padding: Option<f32>,
56 pub margin: Option<f32>,
58 pub font_size: Option<f32>,
60 pub font_weight: Option<f32>,
62 pub border_color: Option<CssValue>,
64 pub border_width: Option<f32>,
66 pub opacity: Option<f32>,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
77pub struct Specificity(pub u32, pub u32, pub u32);
78
79impl Specificity {
80 pub fn add_id(&mut self) {
82 self.0 += 1;
83 }
84
85 pub fn add_class(&mut self) {
87 self.1 += 1;
88 }
89
90 pub fn add_type(&mut self) {
92 self.2 += 1;
93 }
94}
95
96#[derive(Debug, Clone)]
98pub enum SelectorPart {
99 Type(String),
101 Class(String),
103 Id(String),
105}
106
107#[derive(Debug, Clone)]
109pub struct Selector {
110 pub parts: Vec<SelectorPart>,
112 pub specificity: Specificity,
114}
115
116#[derive(Debug, Clone)]
118pub struct Rule {
119 pub selectors: Vec<Selector>,
121 pub style: ComputedStyle,
123 pub source_order: usize,
125}
126
127#[derive(Debug, Clone, Default)]
129pub struct StyleSheet {
130 pub rules: Vec<Rule>,
132}
133
134#[derive(Debug, Clone)]
136pub struct ParseDiagnostic {
137 pub offset: usize,
139 pub message: String,
141}
142
143#[derive(Debug, Clone, Default)]
145pub struct ParseResult {
146 pub stylesheet: StyleSheet,
149 pub diagnostics: Vec<ParseDiagnostic>,
151}
152
153impl StyleSheet {
156 pub fn parse(input: &str) -> ParseResult {
162 let mut parser = Parser::new(input);
163 parser.parse_stylesheet()
164 }
165
166 pub fn matching_rules<'a>(
172 &'a self,
173 widget_type: &str,
174 classes: &[&str],
175 id: Option<&str>,
176 ) -> Vec<(&'a Rule, Specificity)> {
177 let mut matches = Vec::new();
178 for rule in &self.rules {
179 for selector in &rule.selectors {
180 if selector_matches(selector, widget_type, classes, id) {
181 matches.push((rule, selector.specificity));
182 break; }
184 }
185 }
186 matches.sort_by(|a, b| a.1.cmp(&b.1).then(a.0.source_order.cmp(&b.0.source_order)));
187 matches
188 }
189
190 pub fn compute_style(
196 &self,
197 widget_type: &str,
198 classes: &[&str],
199 id: Option<&str>,
200 ) -> ComputedStyle {
201 let mut result = ComputedStyle::default();
202 for (rule, _) in self.matching_rules(widget_type, classes, id) {
203 apply_rule(&mut result, &rule.style);
204 }
205 result
206 }
207}
208
209pub(crate) fn selector_matches(
211 sel: &Selector,
212 widget_type: &str,
213 classes: &[&str],
214 id: Option<&str>,
215) -> bool {
216 for part in &sel.parts {
217 match part {
218 SelectorPart::Type(t) => {
219 if t != widget_type {
220 return false;
221 }
222 }
223 SelectorPart::Class(c) => {
224 if !classes.contains(&c.as_str()) {
225 return false;
226 }
227 }
228 SelectorPart::Id(i) => {
229 if id != Some(i.as_str()) {
230 return false;
231 }
232 }
233 }
234 }
235 true
236}
237
238pub(crate) fn apply_rule(target: &mut ComputedStyle, source: &ComputedStyle) {
240 if let Some(v) = &source.color {
241 target.color = Some(v.clone());
242 }
243 if let Some(v) = &source.background_color {
244 target.background_color = Some(v.clone());
245 }
246 if let Some(v) = source.padding {
247 target.padding = Some(v);
248 }
249 if let Some(v) = source.margin {
250 target.margin = Some(v);
251 }
252 if let Some(v) = source.font_size {
253 target.font_size = Some(v);
254 }
255 if let Some(v) = source.font_weight {
256 target.font_weight = Some(v);
257 }
258 if let Some(v) = &source.border_color {
259 target.border_color = Some(v.clone());
260 }
261 if let Some(v) = source.border_width {
262 target.border_width = Some(v);
263 }
264 if let Some(v) = source.opacity {
265 target.opacity = Some(v);
266 }
267}
268
269struct Parser<'a> {
272 input: &'a str,
273 pos: usize,
274 source_order: usize,
275}
276
277impl<'a> Parser<'a> {
278 fn new(input: &'a str) -> Self {
279 Self {
280 input,
281 pos: 0,
282 source_order: 0,
283 }
284 }
285
286 fn remaining(&self) -> &str {
287 &self.input[self.pos..]
288 }
289
290 fn is_eof(&self) -> bool {
291 self.pos >= self.input.len()
292 }
293
294 fn skip_whitespace(&mut self) {
295 while !self.is_eof() {
296 let ch = self.remaining().chars().next().unwrap_or('\0');
297 if ch.is_whitespace() {
298 self.pos += ch.len_utf8();
299 } else if self.remaining().starts_with("/*") {
300 if let Some(end) = self.remaining().find("*/") {
301 self.pos += end + 2;
302 } else {
303 self.pos = self.input.len();
304 }
305 } else {
306 break;
307 }
308 }
309 }
310
311 fn parse_ident(&mut self) -> Option<String> {
312 self.skip_whitespace();
313 let start = self.pos;
314 let mut end = start;
315 for (i, ch) in self.remaining().char_indices() {
316 if ch.is_alphanumeric() || ch == '-' || ch == '_' {
317 end = start + i + ch.len_utf8();
318 } else {
319 break;
320 }
321 }
322 if end > start {
323 let ident = self.input[start..end].to_owned();
324 self.pos = end;
325 Some(ident)
326 } else {
327 None
328 }
329 }
330
331 fn consume_char(&mut self, expected: char) -> bool {
332 self.skip_whitespace();
333 if self.remaining().starts_with(expected) {
334 self.pos += expected.len_utf8();
335 true
336 } else {
337 false
338 }
339 }
340
341 fn parse_stylesheet(&mut self) -> ParseResult {
342 let mut rules = Vec::new();
343 let mut diagnostics = Vec::new();
344 self.skip_whitespace();
345 while !self.is_eof() {
346 let before = self.pos;
347 match self.parse_rule() {
348 Ok(Some(rule)) => rules.push(rule),
349 Ok(None) => {}
350 Err(d) => {
351 diagnostics.push(d);
352 self.recover_to_next_rule();
353 }
354 }
355 if self.pos == before {
357 let step = self
358 .remaining()
359 .chars()
360 .next()
361 .map(char::len_utf8)
362 .unwrap_or(1);
363 self.pos += step;
364 }
365 self.skip_whitespace();
366 }
367 ParseResult {
368 stylesheet: StyleSheet { rules },
369 diagnostics,
370 }
371 }
372
373 fn recover_to_next_rule(&mut self) {
374 while !self.is_eof() {
375 if self.remaining().starts_with('}') {
376 self.pos += 1;
377 break;
378 }
379 self.pos += 1;
380 }
381 }
382
383 fn parse_rule(&mut self) -> Result<Option<Rule>, ParseDiagnostic> {
384 let selectors = self.parse_selector_list()?;
385 if selectors.is_empty() {
386 return Ok(None);
387 }
388 if !self.consume_char('{') {
389 return Err(ParseDiagnostic {
390 offset: self.pos,
391 message: "expected '{'".into(),
392 });
393 }
394 let style = self.parse_declarations();
395 self.consume_char('}');
396 let order = self.source_order;
397 self.source_order += 1;
398 Ok(Some(Rule {
399 selectors,
400 style,
401 source_order: order,
402 }))
403 }
404
405 fn parse_selector_list(&mut self) -> Result<Vec<Selector>, ParseDiagnostic> {
406 let mut selectors = Vec::new();
407 loop {
408 self.skip_whitespace();
409 if self.is_eof() || self.remaining().starts_with('{') {
410 break;
411 }
412 if let Some(sel) = self.parse_selector() {
413 selectors.push(sel);
414 }
415 self.skip_whitespace();
416 if self.remaining().starts_with(',') {
417 self.pos += 1;
418 } else {
419 break;
420 }
421 }
422 Ok(selectors)
423 }
424
425 fn parse_selector(&mut self) -> Option<Selector> {
426 self.skip_whitespace();
427 let mut parts = Vec::new();
428 let mut spec = Specificity::default();
429 loop {
430 self.skip_whitespace();
431 if self.is_eof()
432 || self.remaining().starts_with('{')
433 || self.remaining().starts_with(',')
434 {
435 break;
436 }
437 if self.remaining().starts_with('.') {
438 self.pos += 1;
439 if let Some(class) = self.parse_ident() {
440 spec.add_class();
441 parts.push(SelectorPart::Class(class));
442 }
443 } else if self.remaining().starts_with('#') {
444 self.pos += 1;
445 if let Some(id) = self.parse_ident() {
446 spec.add_id();
447 parts.push(SelectorPart::Id(id));
448 }
449 } else if let Some(ident) = self.parse_ident() {
450 spec.add_type();
451 parts.push(SelectorPart::Type(ident));
452 } else {
453 break;
454 }
455 }
456 if parts.is_empty() {
457 None
458 } else {
459 Some(Selector {
460 parts,
461 specificity: spec,
462 })
463 }
464 }
465
466 fn parse_declarations(&mut self) -> ComputedStyle {
467 let mut style = ComputedStyle::default();
468 loop {
469 self.skip_whitespace();
470 if self.is_eof() || self.remaining().starts_with('}') {
471 break;
472 }
473 self.parse_declaration(&mut style);
474 }
475 style
476 }
477
478 fn parse_declaration(&mut self, style: &mut ComputedStyle) {
479 self.skip_whitespace();
480 let prop = match self.parse_ident() {
481 Some(p) => p,
482 None => {
483 self.skip_to_semicolon();
484 return;
485 }
486 };
487 self.skip_whitespace();
488 if !self.consume_char(':') {
489 self.skip_to_semicolon();
490 return;
491 }
492 self.skip_whitespace();
493 let value = self.parse_value();
494 self.skip_to_semicolon();
495 if let Some(v) = value {
496 match prop.as_str() {
497 "color" => style.color = Some(v),
498 "background" | "background-color" => style.background_color = Some(v),
499 "padding" => {
500 if let CssValue::Number(n) = &v {
501 style.padding = Some(*n);
502 }
503 }
504 "margin" => {
505 if let CssValue::Number(n) = &v {
506 style.margin = Some(*n);
507 }
508 }
509 "font-size" => {
510 if let CssValue::Number(n) = &v {
511 style.font_size = Some(*n);
512 }
513 }
514 "font-weight" => {
515 if let CssValue::Number(n) = &v {
516 style.font_weight = Some(*n);
517 }
518 }
519 "border-color" => style.border_color = Some(v),
520 "border-width" => {
521 if let CssValue::Number(n) = &v {
522 style.border_width = Some(*n);
523 }
524 }
525 "opacity" => {
526 if let CssValue::Number(n) = &v {
527 style.opacity = Some(*n);
528 }
529 }
530 _ => {} }
532 }
533 }
534
535 fn parse_value(&mut self) -> Option<CssValue> {
536 self.skip_whitespace();
537 if self.remaining().starts_with('#') {
538 self.pos += 1;
539 return self.parse_hex_color();
540 }
541 if self.remaining().starts_with("rgba(") || self.remaining().starts_with("rgb(") {
542 return self.parse_rgb_color();
543 }
544 let start = self.pos;
546 let mut end = start;
547 let mut has_digit = false;
548 let mut has_dot = false;
549 for (i, ch) in self.remaining().char_indices() {
550 if ch.is_ascii_digit() {
551 has_digit = true;
552 end = start + i + 1;
553 } else if ch == '.' && !has_dot {
554 has_dot = true;
555 end = start + i + 1;
556 } else if ch == 'p' || ch == 'x' {
557 end = start + i + 1; } else {
559 break;
560 }
561 }
562 if has_digit {
563 let num_str: String = self.input[start..end]
564 .chars()
565 .filter(|c| c.is_ascii_digit() || *c == '.')
566 .collect();
567 self.pos = end;
568 return num_str.parse::<f32>().ok().map(CssValue::Number);
569 }
570 if let Some(ident) = self.parse_ident() {
572 return Some(match ident.as_str() {
573 "inherit" => CssValue::Inherit,
574 "initial" => CssValue::Initial,
575 "unset" => CssValue::Unset,
576 _ => CssValue::Keyword(ident),
577 });
578 }
579 None
580 }
581
582 fn parse_hex_color(&mut self) -> Option<CssValue> {
583 let start = self.pos;
584 let hex: String = self
585 .remaining()
586 .chars()
587 .take_while(|c| c.is_ascii_hexdigit())
588 .collect();
589 self.pos += hex.len();
590 let color = match hex.len() {
591 6 => {
592 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
593 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
594 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
595 Color(r, g, b, 255)
596 }
597 8 => {
598 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
599 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
600 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
601 let a = u8::from_str_radix(&hex[6..8], 16).ok()?;
602 Color(r, g, b, a)
603 }
604 3 => {
605 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok()?;
606 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok()?;
607 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok()?;
608 Color(r, g, b, 255)
609 }
610 _ => {
611 self.pos = start;
612 return None;
613 }
614 };
615 Some(CssValue::Color(color))
616 }
617
618 fn parse_rgb_color(&mut self) -> Option<CssValue> {
619 let skip = if self.remaining().starts_with("rgba(") {
620 5
621 } else {
622 4
623 };
624 self.pos += skip;
625 let r = self.parse_number_u8()?;
626 self.consume_char(',');
627 let g = self.parse_number_u8()?;
628 self.consume_char(',');
629 let b = self.parse_number_u8()?;
630 let a = if self.remaining().trim_start().starts_with(',') {
631 self.consume_char(',');
632 self.skip_whitespace();
633 let alpha_str: String = self
634 .remaining()
635 .chars()
636 .take_while(|c| c.is_ascii_digit() || *c == '.')
637 .collect();
638 self.pos += alpha_str.len();
639 (alpha_str.parse::<f32>().unwrap_or(1.0) * 255.0) as u8
640 } else {
641 255
642 };
643 self.consume_char(')');
644 Some(CssValue::Color(Color(r, g, b, a)))
645 }
646
647 fn parse_number_u8(&mut self) -> Option<u8> {
648 self.skip_whitespace();
649 let digits: String = self
650 .remaining()
651 .chars()
652 .take_while(|c| c.is_ascii_digit())
653 .collect();
654 if digits.is_empty() {
655 return None;
656 }
657 self.pos += digits.len();
658 digits.parse::<u8>().ok()
659 }
660
661 fn skip_to_semicolon(&mut self) {
662 while !self.is_eof() {
663 if self.remaining().starts_with(';') {
664 self.pos += 1;
665 break;
666 }
667 if self.remaining().starts_with('}') {
668 break; }
670 self.pos += 1;
671 }
672 }
673}