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