1use std::str::FromStr;
10use std::cmp;
11
12use xmlparser::{
13 Stream,
14 StrSpan,
15 XmlByteExt,
16};
17
18use error::{
19 StreamError,
20 StreamResult,
21};
22use {
23 LengthUnit,
24 StreamExt,
25};
26use colors;
27
28#[derive(Copy, Clone, PartialEq, Debug)]
32#[allow(missing_docs)]
33pub struct Color {
34 pub red: u8,
35 pub green: u8,
36 pub blue: u8,
37}
38
39impl Color {
40 #[inline]
42 pub fn new(red: u8, green: u8, blue: u8) -> Color {
43 Color { red, green, blue }
44 }
45
46 pub fn from_span(span: StrSpan) -> StreamResult<Color> {
79 let mut s = Stream::from_span(span);
80
81 s.skip_spaces();
82
83 let start = s.pos();
84
85 let mut color = Color::new(0, 0, 0);
86
87 if s.curr_byte()? == b'#' {
88 s.advance(1);
89 let color_str = s.consume_bytes(|_, c| c.is_xml_hex_digit()).to_str().as_bytes();
90 match color_str.len() {
92 6 => {
93 color.red = hex_pair(color_str[0], color_str[1]);
95 color.green = hex_pair(color_str[2], color_str[3]);
96 color.blue = hex_pair(color_str[4], color_str[5]);
97 }
98 3 => {
99 color.red = short_hex(color_str[0]);
101 color.green = short_hex(color_str[1]);
102 color.blue = short_hex(color_str[2]);
103 }
104 _ => {
105 return Err(StreamError::InvalidColor(s.gen_error_pos_from(start)));
106 }
107 }
108 } else if is_rgb(&s) {
109 s.advance(4);
110
111 let l = s.parse_list_length()?;
112
113 if l.unit == LengthUnit::Percent {
114 fn from_persent(v: f64) -> u8 {
115 let d = 255.0 / 100.0;
116 let n = (v * d).round() as i32;
117 bound(0, n, 255) as u8
118 }
119
120 color.red = from_persent(l.num);
121 color.green = from_persent(s.parse_list_length()?.num);
122 color.blue = from_persent(s.parse_list_length()?.num);
123 } else {
124 color.red = bound(0, l.num as i32, 255) as u8;
125 color.green = bound(0, s.parse_list_integer()?, 255) as u8;
126 color.blue = bound(0, s.parse_list_integer()?, 255) as u8;
127 }
128
129 s.skip_spaces();
130 s.consume_byte(b')')?;
131 } else {
132 let name = s.consume_name()?.to_str().to_lowercase();
133 match colors::rgb_color_from_name(&name) {
134 Some(c) => {
135 color = c;
136 }
137 None => {
138 return Err(StreamError::InvalidColor(s.gen_error_pos_from(start)));
139 }
140 }
141 }
142
143 s.skip_spaces();
146 if !s.at_end() {
147 return Err(StreamError::InvalidColor(s.gen_error_pos()));
148 }
149
150 Ok(color)
151 }
152}
153
154impl FromStr for Color {
155 type Err = StreamError;
156
157 fn from_str(text: &str) -> StreamResult<Self> {
158 Color::from_span(StrSpan::from_str(text))
159 }
160}
161
162#[inline]
163fn from_hex(c: u8) -> u8 {
164 match c {
165 b'0'...b'9' => c - b'0',
166 b'a'...b'f' => c - b'a' + 10,
167 b'A'...b'F' => c - b'A' + 10,
168 _ => b'0',
169 }
170}
171
172#[inline]
173fn short_hex(c: u8) -> u8 {
174 let h = from_hex(c);
175 (h << 4) | h
176}
177
178#[inline]
179fn hex_pair(c1: u8, c2: u8) -> u8 {
180 let h1 = from_hex(c1);
181 let h2 = from_hex(c2);
182 (h1 << 4) | h2
183}
184
185fn is_rgb(s: &Stream) -> bool {
186 let mut s = s.clone();
187 let prefix = s.consume_bytes(|_, c| c != b'(').to_str();
188 if s.consume_byte(b'(').is_err() {
189 return false;
190 }
191
192 #[allow(unused_imports)]
193 use std::ascii::AsciiExt;
194
195 prefix.eq_ignore_ascii_case("rgb")
196}
197
198#[inline]
199fn bound<T: Ord>(min: T, val: T, max: T) -> T {
200 cmp::max(min, cmp::min(max, val))
201}