1use cssparser::{
7 AtRuleParser, CowRcStr, DeclarationParser, ParseError as CssParseError, Parser, ParserInput,
8 ParserState, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, StyleSheetParser, Token,
9 match_ignore_ascii_case,
10};
11
12use crate::ast::{
13 Combinator, Declaration, PseudoClass, Rule, Selector, SimpleSelector, Stylesheet,
14};
15use crate::error::{ParseError, ParseErrorOwned};
16use crate::value::{Color, Length, SideValue, Value};
17
18pub fn parse(input: &str) -> Result<Stylesheet, ParseError> {
19 let mut parser_input = ParserInput::new(input);
20 let mut parser = Parser::new(&mut parser_input);
21 let mut rule_parser = StylesheetRuleParser;
22 let mut rules = Vec::new();
23 let iter = StyleSheetParser::new(&mut parser, &mut rule_parser);
24 for item in iter {
25 match item {
26 Ok(Some(rule)) => rules.push(rule),
27 Ok(None) => {}
30 Err(_) => {}
35 }
36 }
37 Ok(Stylesheet { rules })
38}
39
40struct StylesheetRuleParser;
41
42impl<'i> QualifiedRuleParser<'i> for StylesheetRuleParser {
43 type Prelude = Vec<Selector>;
44 type QualifiedRule = Option<Rule>;
45 type Error = ParseErrorOwned;
46
47 fn parse_prelude<'t>(
48 &mut self,
49 parser: &mut Parser<'i, 't>,
50 ) -> Result<Self::Prelude, CssParseError<'i, Self::Error>> {
51 parse_selector_list(parser)
52 }
53
54 fn parse_block<'t>(
55 &mut self,
56 selectors: Self::Prelude,
57 _start: &ParserState,
58 parser: &mut Parser<'i, 't>,
59 ) -> Result<Self::QualifiedRule, CssParseError<'i, Self::Error>> {
60 let mut declarations = Vec::new();
61 let mut decl_parser = DeclParser;
62 let body = RuleBodyParser::new(parser, &mut decl_parser);
63 for item in body {
68 match item {
69 Ok(decl) => declarations.push(decl),
70 Err(_) => continue,
71 }
72 }
73 Ok(Some(Rule {
74 selectors,
75 declarations,
76 }))
77 }
78}
79
80impl<'i> AtRuleParser<'i> for StylesheetRuleParser {
81 type Prelude = ();
82 type AtRule = Option<Rule>;
83 type Error = ParseErrorOwned;
84
85 fn parse_prelude<'t>(
92 &mut self,
93 _name: CowRcStr<'i>,
94 parser: &mut Parser<'i, 't>,
95 ) -> Result<Self::Prelude, CssParseError<'i, Self::Error>> {
96 while parser.next().is_ok() {}
97 Ok(())
98 }
99
100 fn rule_without_block(
101 &mut self,
102 _prelude: Self::Prelude,
103 _start: &ParserState,
104 ) -> Result<Self::AtRule, ()> {
105 Ok(None)
106 }
107
108 fn parse_block<'t>(
109 &mut self,
110 _prelude: Self::Prelude,
111 _start: &ParserState,
112 parser: &mut Parser<'i, 't>,
113 ) -> Result<Self::AtRule, CssParseError<'i, Self::Error>> {
114 while parser.next().is_ok() {}
117 Ok(None)
118 }
119}
120
121fn parse_selector_list<'i, 't>(
122 parser: &mut Parser<'i, 't>,
123) -> Result<Vec<Selector>, CssParseError<'i, ParseErrorOwned>> {
124 let mut selectors = Vec::new();
125 loop {
126 selectors.push(parse_compound_selector(parser)?);
127 if parser.expect_comma().is_err() {
128 break;
129 }
130 }
131 Ok(selectors)
132}
133
134fn parse_simple_selector<'i, 't>(
146 parser: &mut Parser<'i, 't>,
147 allow_type: bool,
148) -> Result<SimpleSelector, CssParseError<'i, ParseErrorOwned>> {
149 let mut sel = SimpleSelector::default();
150 let mut saw_anything = false;
151 loop {
152 let save = parser.state();
153 let next = parser.next_including_whitespace().cloned();
154 match next {
155 Ok(Token::Ident(name)) if allow_type && !saw_anything => {
156 sel.element = Some(name.to_string());
157 saw_anything = true;
158 }
159 Ok(Token::Delim('.')) => {
160 let name = parser.expect_ident()?.to_string();
161 sel.classes.push(name);
162 saw_anything = true;
163 }
164 Ok(Token::Colon) => {
165 let ident = parser.expect_ident_cloned()?;
166 let pseudo = match PseudoClass::from_ident(&ident) {
167 Some(p) => p,
168 None => {
169 return Err(parser.new_custom_error(ParseErrorOwned(format!(
170 "unknown pseudo-class :{ident}"
171 ))));
172 }
173 };
174 if sel.pseudo.is_some() {
175 return Err(parser.new_custom_error(ParseErrorOwned(
176 "multiple pseudo-classes per selector are not supported".to_string(),
177 )));
178 }
179 sel.pseudo = Some(pseudo);
180 saw_anything = true;
181 }
182 _ => {
183 parser.reset(&save);
184 break;
185 }
186 }
187 }
188 if !saw_anything {
189 return Err(parser.new_custom_error(ParseErrorOwned("empty selector".to_string())));
190 }
191 Ok(sel)
192}
193
194fn parse_compound_selector<'i, 't>(
198 parser: &mut Parser<'i, 't>,
199) -> Result<Selector, CssParseError<'i, ParseErrorOwned>> {
200 parser.skip_whitespace();
203 let first = parse_simple_selector(parser, true)?;
204 let mut parts = vec![first];
205 let mut combinators = vec![];
206
207 loop {
208 let save = parser.state();
211 let tok = parser.next_including_whitespace().cloned();
212
213 match tok {
214 Ok(Token::Delim('>')) => {
217 parser.skip_whitespace();
218 let next_simple = parse_simple_selector(parser, true)?;
219 parts.push(next_simple);
220 combinators.push(Combinator::Child);
221 }
222 Ok(Token::Delim('+')) => {
223 parser.skip_whitespace();
224 let next_simple = parse_simple_selector(parser, true)?;
225 parts.push(next_simple);
226 combinators.push(Combinator::AdjacentSibling);
227 }
228 Ok(Token::Delim('~')) => {
229 parser.skip_whitespace();
230 let next_simple = parse_simple_selector(parser, true)?;
231 parts.push(next_simple);
232 combinators.push(Combinator::GeneralSibling);
233 }
234 Ok(Token::WhiteSpace(_)) => {
235 parser.skip_whitespace();
246 let after_ws = parser.state();
247 let next_tok = parser.next_including_whitespace().cloned();
248 match next_tok {
249 Ok(Token::Delim('>')) => {
251 parser.skip_whitespace();
252 let next_simple = parse_simple_selector(parser, true)?;
253 parts.push(next_simple);
254 combinators.push(Combinator::Child);
255 }
256 Ok(Token::Delim('+')) => {
257 parser.skip_whitespace();
258 let next_simple = parse_simple_selector(parser, true)?;
259 parts.push(next_simple);
260 combinators.push(Combinator::AdjacentSibling);
261 }
262 Ok(Token::Delim('~')) => {
263 parser.skip_whitespace();
264 let next_simple = parse_simple_selector(parser, true)?;
265 parts.push(next_simple);
266 combinators.push(Combinator::GeneralSibling);
267 }
268 Ok(Token::Ident(_)) | Ok(Token::Delim('.')) | Ok(Token::Colon) => {
272 parser.reset(&after_ws);
273 let next_simple = parse_simple_selector(parser, true)?;
274 parts.push(next_simple);
275 combinators.push(Combinator::Descendant);
276 }
277 _ => {
278 parser.reset(&save);
281 break;
282 }
283 }
284 }
285 _ => {
286 parser.reset(&save);
287 break;
288 }
289 }
290 }
291
292 Ok(Selector { parts, combinators })
293}
294
295struct DeclParser;
296
297impl<'i> DeclarationParser<'i> for DeclParser {
298 type Declaration = Declaration;
299 type Error = ParseErrorOwned;
300
301 fn parse_value<'t>(
302 &mut self,
303 name: CowRcStr<'i>,
304 parser: &mut Parser<'i, 't>,
305 _start: &ParserState,
306 ) -> Result<Self::Declaration, CssParseError<'i, Self::Error>> {
307 let prop = name.to_string();
308 let (value, important) = parse_value(&prop, parser)?;
309 Ok(Declaration {
310 property: prop,
311 value,
312 important,
313 })
314 }
315}
316
317impl<'i> AtRuleParser<'i> for DeclParser {
318 type Prelude = ();
319 type AtRule = Declaration;
320 type Error = ParseErrorOwned;
321}
322
323impl<'i> QualifiedRuleParser<'i> for DeclParser {
324 type Prelude = ();
325 type QualifiedRule = Declaration;
326 type Error = ParseErrorOwned;
327}
328
329impl<'i> RuleBodyItemParser<'i, Declaration, ParseErrorOwned> for DeclParser {
330 fn parse_declarations(&self) -> bool {
331 true
332 }
333 fn parse_qualified(&self) -> bool {
334 false
335 }
336}
337
338fn parse_value<'i, 't>(
339 prop: &str,
340 parser: &mut Parser<'i, 't>,
341) -> Result<(Value, bool), CssParseError<'i, ParseErrorOwned>> {
342 let value = parse_value_inner(prop, parser)?;
343 let important = consume_important(parser);
344 parser.expect_exhausted()?;
345 Ok((value, important))
346}
347
348fn parse_value_inner<'i, 't>(
349 prop: &str,
350 parser: &mut Parser<'i, 't>,
351) -> Result<Value, CssParseError<'i, ParseErrorOwned>> {
352 match property_kind(prop) {
356 PropertyKind::Color => parse_color(parser).map(Value::Color),
357 PropertyKind::Length => parse_length(parser).map(Value::Length),
358 PropertyKind::LengthOrAuto => {
359 if parser.try_parse(expect_auto).is_ok() {
360 Ok(Value::Auto)
361 } else {
362 parse_length(parser).map(Value::Length)
363 }
364 }
365 PropertyKind::SideLengths => {
366 let mut lengths = Vec::new();
367 while let Ok(len) = parser.try_parse(parse_length) {
368 lengths.push(len);
369 if lengths.len() == 4 {
370 break;
371 }
372 }
373 if lengths.is_empty() {
374 return Err(parser
375 .new_custom_error(ParseErrorOwned(format!("expected length for `{prop}`"))));
376 }
377 Ok(Value::LengthSet(lengths))
378 }
379 PropertyKind::SideLengthsOrAuto => parse_side_lengths_or_auto(prop, parser),
380 PropertyKind::Keyword(allowed) => {
381 let ident = parser.try_parse(|p| p.expect_ident_cloned()).map_err(|_| {
382 parser.new_custom_error(ParseErrorOwned(format!("expected keyword for `{prop}`")))
383 })?;
384 let kw = ident.to_ascii_lowercase();
385 if allowed.contains(&kw.as_str()) {
386 Ok(Value::Keyword(kw))
387 } else {
388 Err(parser.new_custom_error(ParseErrorOwned(format!(
389 "unknown keyword `{kw}` for `{prop}`"
390 ))))
391 }
392 }
393 PropertyKind::Number => {
394 let n = parser.try_parse(|p| p.expect_number()).map_err(|_| {
395 parser.new_custom_error(ParseErrorOwned(format!("expected number for `{prop}`")))
396 })?;
397 if n < 0.0 {
402 return Err(parser.new_custom_error(ParseErrorOwned(format!(
403 "negative number not allowed for `{prop}`"
404 ))));
405 }
406 Ok(Value::Number(f64::from(n)))
407 }
408 PropertyKind::NumberOrLength => {
409 if let Ok(n) = parser.try_parse(|p| {
413 let loc = p.current_source_location();
414 let tok = p.next()?.clone();
415 match tok {
416 Token::Number { value, .. } => Ok(value),
418 other => Err(loc.new_custom_error::<ParseErrorOwned, ParseErrorOwned>(
419 ParseErrorOwned(format!("not a plain number: {other:?}")),
420 )),
421 }
422 }) {
423 Ok(Value::Number(f64::from(n)))
424 } else {
425 parse_length(parser).map(Value::Length)
426 }
427 }
428 PropertyKind::FontWeight => {
429 if let Ok(n) = parser.try_parse(|p| p.expect_number()) {
430 let n = f64::from(n);
431 if n.fract() != 0.0 || !(1.0..=1000.0).contains(&n) {
433 return Err(parser.new_custom_error(ParseErrorOwned(format!(
434 "font-weight numeric value `{n}` is out of range (must be integer 1–1000)"
435 ))));
436 }
437 Ok(Value::Number(n))
438 } else {
439 let ident = parser.try_parse(|p| p.expect_ident_cloned()).map_err(|_| {
440 parser.new_custom_error(ParseErrorOwned(
441 "expected number or keyword for `font-weight`".to_string(),
442 ))
443 })?;
444 let kw = ident.to_ascii_lowercase();
445 if ["normal", "bold"].contains(&kw.as_str()) {
446 Ok(Value::Keyword(kw))
447 } else {
448 Err(parser.new_custom_error(ParseErrorOwned(format!(
449 "unknown keyword `{kw}` for `font-weight`"
450 ))))
451 }
452 }
453 }
454 PropertyKind::FontFamily => parse_font_family(parser),
455 PropertyKind::Border => parse_border_shorthand(prop, parser),
456 PropertyKind::Unknown => {
457 if let Ok(c) = parser.try_parse(parse_color) {
461 return Ok(Value::Color(c));
462 }
463 if let Ok(len) = parser.try_parse(parse_length) {
464 return Ok(Value::Length(len));
465 }
466 if let Ok(ident) = parser.try_parse(|p| p.expect_ident_cloned()) {
467 return Ok(Value::Keyword(ident.to_ascii_lowercase()));
471 }
472 Err(parser.new_custom_error(ParseErrorOwned(format!(
473 "could not parse value for `{prop}`"
474 ))))
475 }
476 }
477}
478
479fn parse_side_lengths_or_auto<'i, 't>(
482 prop: &str,
483 parser: &mut Parser<'i, 't>,
484) -> Result<Value, CssParseError<'i, ParseErrorOwned>> {
485 let mut sides: Vec<SideValue> = Vec::new();
486 loop {
487 if sides.len() == 4 {
488 break;
489 }
490 if let Ok(()) = parser.try_parse(expect_auto) {
491 sides.push(SideValue::Auto);
492 } else if let Ok(len) = parser.try_parse(parse_length) {
493 sides.push(SideValue::Length(len));
494 } else {
495 break;
496 }
497 }
498 if sides.is_empty() {
499 return Err(parser.new_custom_error(ParseErrorOwned(format!(
500 "expected length or auto for `{prop}`"
501 ))));
502 }
503 if sides == [SideValue::Auto] {
505 return Ok(Value::Auto);
506 }
507 let all_lengths: Option<Vec<Length>> = sides
510 .iter()
511 .map(|sv| match sv {
512 SideValue::Length(l) => Some(*l),
513 SideValue::Auto => None,
514 })
515 .collect();
516 if let Some(lengths) = all_lengths {
517 return Ok(Value::LengthSet(lengths));
518 }
519 Ok(Value::SideSet(sides))
520}
521
522fn parse_font_family<'i, 't>(
525 parser: &mut Parser<'i, 't>,
526) -> Result<Value, CssParseError<'i, ParseErrorOwned>> {
527 let mut families: Vec<String> = Vec::new();
528 let mut pending_comma = false;
529 loop {
530 let pushed = if let Ok(s) = parser.try_parse(|p| p.expect_string_cloned()) {
532 families.push(s.to_string());
533 true
534 } else if let Ok(ident) = parser.try_parse(|p| p.expect_ident_cloned()) {
535 let mut name = ident.to_string();
538 loop {
539 let state = parser.state();
542 match parser.next_including_whitespace() {
543 Ok(Token::WhiteSpace(_)) => {
544 let state2 = parser.state();
545 match parser.next_including_whitespace() {
546 Ok(Token::Ident(next_ident)) => {
547 name.push(' ');
548 name.push_str(next_ident.as_ref());
549 }
550 _ => {
551 parser.reset(&state2);
552 break;
553 }
554 }
555 }
556 _ => {
557 parser.reset(&state);
558 break;
559 }
560 }
561 }
562 families.push(name);
563 true
564 } else {
565 false
566 };
567 if !pushed {
568 if pending_comma {
569 return Err(parser
573 .new_custom_error(ParseErrorOwned("trailing comma in font-family".into())));
574 }
575 break;
576 }
577 if parser.try_parse(|p| p.expect_comma()).is_err() {
578 break;
579 }
580 pending_comma = true;
581 }
582 if families.is_empty() {
583 return Err(parser.new_custom_error(ParseErrorOwned("expected font-family value".into())));
584 }
585 Ok(Value::FontFamilyList(families))
586}
587
588fn parse_border_shorthand<'i, 't>(
591 prop: &str,
592 parser: &mut Parser<'i, 't>,
593) -> Result<Value, CssParseError<'i, ParseErrorOwned>> {
594 let mut width: Option<Length> = None;
598 let mut color: Option<Color> = None;
599 let mut saw_none_style = false;
600
601 for _ in 0..3 {
603 if width.is_none()
604 && let Ok(len) = parser.try_parse(parse_length)
605 {
606 width = Some(len);
607 continue;
608 }
609 if color.is_none()
610 && let Ok(c) = parser.try_parse(parse_color)
611 {
612 color = Some(c);
613 continue;
614 }
615 if let Ok(ident) = parser.try_parse(|p| p.expect_ident_cloned()) {
617 let kw = ident.to_ascii_lowercase();
618 match kw.as_str() {
619 "none" => {
622 saw_none_style = true;
623 continue;
624 }
625 _ => continue,
632 }
633 }
634 break;
635 }
636
637 let width = if saw_none_style {
638 width.unwrap_or(Length::Px(0.0))
644 } else {
645 width.ok_or_else(|| {
646 parser.new_custom_error(ParseErrorOwned(format!("missing width in `{prop}`")))
647 })?
648 };
649 let color = match color {
653 Some(c) => c,
654 None if saw_none_style => Color::rgba(0, 0, 0, 0),
655 None => {
656 return Err(
657 parser.new_custom_error(ParseErrorOwned(format!("missing color in `{prop}`")))
658 );
659 }
660 };
661
662 Ok(Value::Border { width, color })
663}
664
665fn expect_auto<'i, 't>(
668 parser: &mut Parser<'i, 't>,
669) -> Result<(), CssParseError<'i, ParseErrorOwned>> {
670 let loc = parser.current_source_location();
671 let tok = parser.next()?.clone();
672 match &tok {
673 Token::Ident(name) if name.eq_ignore_ascii_case("auto") => Ok(()),
674 other => {
675 Err(loc.new_custom_error(ParseErrorOwned(format!("expected `auto`, got {other:?}"))))
676 }
677 }
678}
679
680#[derive(Debug, Clone)]
683enum PropertyKind {
684 Color,
685 Length,
686 LengthOrAuto,
688 SideLengths,
690 SideLengthsOrAuto,
692 Keyword(&'static [&'static str]),
694 Number,
696 NumberOrLength,
698 FontFamily,
699 FontWeight,
701 Border,
702 Unknown,
704}
705
706fn property_kind(name: &str) -> PropertyKind {
707 match name {
708 "color" | "background-color" => PropertyKind::Color,
709
710 "width" | "height" | "flex-basis" => PropertyKind::LengthOrAuto,
712
713 "padding" | "border-radius" => PropertyKind::SideLengths,
715 "margin" => PropertyKind::SideLengthsOrAuto,
716 "gap" | "row-gap" | "column-gap" => PropertyKind::Length,
717
718 "display" => PropertyKind::Keyword(&["flex", "block", "none"]),
720 "flex-direction" => {
721 PropertyKind::Keyword(&["row", "column", "row-reverse", "column-reverse"])
722 }
723 "align-items" => PropertyKind::Keyword(&["start", "end", "center", "stretch", "baseline"]),
724 "justify-content" => PropertyKind::Keyword(&[
725 "start",
726 "end",
727 "center",
728 "space-between",
729 "space-around",
730 "space-evenly",
731 ]),
732 "flex-grow" | "flex-shrink" => PropertyKind::Number,
733
734 "border" | "border-top" | "border-right" | "border-bottom" | "border-left" | "outline" => {
736 PropertyKind::Border
737 }
738
739 "border-width" => PropertyKind::SideLengths,
742 "border-color" => PropertyKind::Color,
743 "border-top-color" | "border-right-color" | "border-bottom-color" | "border-left-color" => {
744 PropertyKind::Color
745 }
746
747 "font-family" => PropertyKind::FontFamily,
749 "font-size" => PropertyKind::Length,
750 "font-weight" => PropertyKind::FontWeight,
751 "font-style" => PropertyKind::Keyword(&["normal", "italic", "oblique"]),
752 "text-align" => PropertyKind::Keyword(&["left", "center", "right", "justify"]),
753 "line-height" => PropertyKind::NumberOrLength,
754
755 _ => PropertyKind::Unknown,
756 }
757}
758
759fn consume_important(parser: &mut Parser<'_, '_>) -> bool {
765 parser
766 .try_parse(|p| -> Result<(), CssParseError<'_, ParseErrorOwned>> {
767 p.expect_delim('!')?;
768 let ident = p.expect_ident_cloned()?;
769 if ident.eq_ignore_ascii_case("important") {
770 Ok(())
771 } else {
772 Err(p.new_custom_error(ParseErrorOwned("not !important".to_string())))
773 }
774 })
775 .is_ok()
776}
777
778fn parse_length<'i, 't>(
779 parser: &mut Parser<'i, 't>,
780) -> Result<Length, CssParseError<'i, ParseErrorOwned>> {
781 let location = parser.current_source_location();
782 let token = parser.next()?.clone();
783 match token {
784 Token::Dimension { value, unit, .. } => match unit.as_ref() {
785 "px" => Ok(Length::Px(f64::from(value))),
786 other => Err(location.new_custom_error(ParseErrorOwned(format!(
787 "unsupported length unit `{other}`"
789 )))),
790 },
791 Token::Percentage { unit_value, .. } => Ok(Length::Percent(f64::from(unit_value) * 100.0)),
792 Token::Number { value, .. } => Ok(Length::Px(f64::from(value))),
793 other => {
794 Err(location
795 .new_custom_error(ParseErrorOwned(format!("expected length, got {other:?}"))))
796 }
797 }
798}
799
800fn parse_color<'i, 't>(
801 parser: &mut Parser<'i, 't>,
802) -> Result<Color, CssParseError<'i, ParseErrorOwned>> {
803 let location = parser.current_source_location();
804 let token = parser.next()?.clone();
805 match token {
806 Token::IDHash(h) | Token::Hash(h) => parse_hex(h.as_ref()).ok_or_else(|| {
810 location.new_custom_error(ParseErrorOwned(format!("bad hex color `#{h}`")))
811 }),
812 Token::Ident(name) => named_color(name.as_ref()).ok_or_else(|| {
813 location.new_custom_error(ParseErrorOwned(format!("unknown color name `{name}`")))
814 }),
815 Token::Function(name) => {
816 let name_lc = name.to_ascii_lowercase();
817 parser.parse_nested_block(|p| match name_lc.as_str() {
818 "rgb" => parse_rgb_args(p, false),
819 "rgba" => parse_rgb_args(p, true),
820 other => Err(p.new_custom_error(ParseErrorOwned(format!(
821 "unsupported color function `{other}`"
822 )))),
823 })
824 }
825 other => {
826 Err(location
827 .new_custom_error(ParseErrorOwned(format!("expected color, got {other:?}"))))
828 }
829 }
830}
831
832fn parse_rgb_args<'i, 't>(
833 parser: &mut Parser<'i, 't>,
834 expect_alpha: bool,
835) -> Result<Color, CssParseError<'i, ParseErrorOwned>> {
836 let r = parse_u8_channel(parser)?;
837 parser.expect_comma()?;
838 let g = parse_u8_channel(parser)?;
839 parser.expect_comma()?;
840 let b = parse_u8_channel(parser)?;
841 let a = if expect_alpha {
842 parser.expect_comma()?;
843 let f = parser.expect_number()?;
844 (f.clamp(0.0, 1.0) * 255.0).round() as u8
845 } else {
846 0xff
847 };
848 parser.expect_exhausted()?;
849 Ok(Color::rgba(r, g, b, a))
850}
851
852fn parse_u8_channel<'i, 't>(
853 parser: &mut Parser<'i, 't>,
854) -> Result<u8, CssParseError<'i, ParseErrorOwned>> {
855 let location = parser.current_source_location();
856 let token = parser.next()?.clone();
857 let n = match token {
858 Token::Number { value, .. } => value,
859 Token::Percentage { unit_value, .. } => unit_value * 255.0,
860 other => {
861 return Err(location
862 .new_custom_error(ParseErrorOwned(format!("expected channel, got {other:?}"))));
863 }
864 };
865 Ok(n.clamp(0.0, 255.0).round() as u8)
866}
867
868fn parse_hex(s: &str) -> Option<Color> {
869 let hex = |c: char| c.to_digit(16).map(|d| d as u8);
870 let chars: Vec<u8> = s.chars().filter_map(hex).collect();
871 if chars.len() != s.chars().count() {
872 return None;
873 }
874 let dup = |n: u8| (n << 4) | n;
875 Some(match chars.len() {
876 3 => Color::rgb(dup(chars[0]), dup(chars[1]), dup(chars[2])),
877 4 => Color::rgba(dup(chars[0]), dup(chars[1]), dup(chars[2]), dup(chars[3])),
878 6 => Color::rgb(
879 (chars[0] << 4) | chars[1],
880 (chars[2] << 4) | chars[3],
881 (chars[4] << 4) | chars[5],
882 ),
883 8 => Color::rgba(
884 (chars[0] << 4) | chars[1],
885 (chars[2] << 4) | chars[3],
886 (chars[4] << 4) | chars[5],
887 (chars[6] << 4) | chars[7],
888 ),
889 _ => return None,
890 })
891}
892
893fn named_color(name: &str) -> Option<Color> {
894 Some(match_ignore_ascii_case! { name,
896 "transparent" => Color::rgba(0, 0, 0, 0),
897 "black" => Color::rgb(0x00, 0x00, 0x00),
899 "silver" => Color::rgb(0xc0, 0xc0, 0xc0),
900 "gray" => Color::rgb(0x80, 0x80, 0x80),
901 "grey" => Color::rgb(0x80, 0x80, 0x80),
902 "white" => Color::rgb(0xff, 0xff, 0xff),
903 "maroon" => Color::rgb(0x80, 0x00, 0x00),
904 "red" => Color::rgb(0xff, 0x00, 0x00),
905 "purple" => Color::rgb(0x80, 0x00, 0x80),
906 "fuchsia" => Color::rgb(0xff, 0x00, 0xff),
907 "green" => Color::rgb(0x00, 0x80, 0x00),
908 "lime" => Color::rgb(0x00, 0xff, 0x00),
909 "olive" => Color::rgb(0x80, 0x80, 0x00),
910 "yellow" => Color::rgb(0xff, 0xff, 0x00),
911 "navy" => Color::rgb(0x00, 0x00, 0x80),
912 "blue" => Color::rgb(0x00, 0x00, 0xff),
913 "teal" => Color::rgb(0x00, 0x80, 0x80),
914 "aqua" => Color::rgb(0x00, 0xff, 0xff),
915 "cyan" => Color::rgb(0x00, 0xff, 0xff),
917 "magenta" => Color::rgb(0xff, 0x00, 0xff),
918 "orange" => Color::rgb(0xff, 0xa5, 0x00),
919 "brown" => Color::rgb(0xa5, 0x2a, 0x2a),
920 "pink" => Color::rgb(0xff, 0xc0, 0xcb),
921 _ => return None,
922 })
923}