-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathhtml_color_reader.rs
121 lines (113 loc) · 5.33 KB
/
html_color_reader.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// This is still a preliminary file and won't make it into an actual version of Scarlet. It
// implements basic convenience functions that process valid HTML colors into vectors of either RGB,
// RGBA, HSL, or HSLA components.
// Takes in a hex string, such as "#23334412" or "#aabbcc", and returns an array of four color
// components R, G, B, and A as 0-255 integers. If the hex does not have an alpha value, it is
// assumed to be 255.
// This is not properly formatted for rustdoc because it doesn't fit within a larger framework yet.
use std::result::Result::Err;
extern crate regex;
use regex::Regex;
pub enum ColorParseError {
InvalidHTMLHex,
InvalidRGBAFunction,
OutOfRangeRGBA,
OutOfRangeHSLA,
InvalidHSLAFunction
}
fn hex_to_rgba(hex: &str) -> Result<Vec<u8>, ColorParseError> {
// Parse for valid hex: has to be of the form #rrggbb or #rrggbbaa, where the values after the
// # have to be valid hex (0-9, a-f, or A-F)
if hex.chars().nth(0) != Some('#') {
return Err(ColorParseError::InvalidHTMLHex)
};
let count = hex.chars().count();
if ![7, 9].contains(&count) {
Err(ColorParseError::InvalidHTMLHex)
} else if hex.chars().skip(1).any(|x| !("abcdefABCDEF0123456789".contains(x))) {
Err(ColorParseError::InvalidHTMLHex)
} else { // valid hex: return useful value
// it's easier to do math on the hex value than it is to slice strings
let mut full_num:u32 = u32::from_str_radix(hex.chars().skip(1).collect::<String>().as_str(), 16).unwrap();
let mut a:u8 = 255;
if count == 7 {
// no need to do anything, alpha is already set
}
else {
// alpha is the number modulo 16^2, divide the number by 16^2 to remove the alpha
// component afterwards
// this should become bit operations that are really fast, but I haven't checked it to
// see if this actually happens, I'm just assuming for now that the compiler's good
a = (full_num % 256) as u8;
full_num = full_num / 256;
}
// now we just modulo and divide three more times, as we've guaranteed it's now rrggbb
let b = (full_num % 256) as u8;
full_num = full_num / 256;
let g = (full_num % 256) as u8;
full_num = full_num / 256;
// the only thing left is now r
let r = full_num as u8;
Ok(vec![r, g, b, a])
}
}
// Given a string like "rgb(24, 33, 235)" or "rgba(23, 34, 11)", returns a Vector [r, g, b, a].
// Returns a ColorParseError if the RGB is out of range or there is incorrect syntax.
fn func_to_rgba(input: &str) -> Result<Vec<u8>, ColorParseError> {
// test for any characters that are not spaces, parentheses, commas, the letters rgba, or digits
if !(input.chars().all(|c| " ()rgba0123456789,".contains(c))) {
return Err(ColorParseError::InvalidRGBAFunction)
};
// now we don't have to worry about indexing issues, as everything is ASCII
// to simplify the string processing from here on out, we'll use regexes
// this gives us the added benefit of parsing the numbers as well and runs in linear time
// the tradeoff is that we won't know why the syntax is wrong, but it's not worth the 50 lines of
// code it would take to accomplish that and the increased error likelihood
// it's also a bit on the read-only side! testing is crucial here
// note that this does not verify that the numbers are in bounds, just that they're between 0 and 999
rgba_re = Regex::new(r"^rgba\((?: *([0-9]{1, 3}) *,\)){4}$").unwrap();
rgb_re = Regex::new(r"^rgb\((?: *([0-9]{1, 3}) *,\)){3}$").unwrap();
// try matching each one, falling back to rgb if rgba fails
let grps:Option<Captures<'t>> = match rgba_re.captures(input) {
None => match rgb_re.captures(input) {
None => None,
Some(x) => Some(x)
},
Some(x) => Some(x)
};
// now either return an error, process an RGBA string list, or an RGB string list
match grps {
None => Err(ColorParseError::InvalidRGBAFunction),
Some(x) if x.count() == 3 => {
let (rstr, gstr, bstr) = x.collect();
let a:u8 = 255;
// use u16s so that values > 255 will be detected
let r = u16::from_str_radix(rstr.as_str(), 10)?;
let g = u16::from_str_radix(gstr.as_str(), 10)?;
let b = u16::from_str_radix(bstr.as_str(), 10)?;
if (r > 255) | (g > 255) | (b > 255) {
Err(ColorParseError::OutOfRangeRGBA)
}
else {
Ok(vec![r as u8, g as u8, b as u8, a])
}
}
Some(x) if x.count() == 4 => {
let (rstr, gstr, bstr, astr) = x.collect();
let r = u16::from_str_radix(rstr.as_str(), 10)?;
let g = u16::from_str_radix(gstr.as_str(), 10)?;
let b = u16::from_str_radix(bstr.as_str(), 10)?;
let a = u16::from_str_radix(astr.as_str(), 10)?;
if (r > 255) | (g > 255) | (b > 255) | (a > 255) {
Err(ColorParseError::OutOfRangeRGBA)
}
else {
Ok(vec![r as u8, g as u8, b as u8, a as u8])
}
}
}
}
fn main() {
println!("{:?}", hex_to_rgba(&"#ff0080".to_string()).ok());
println!("{:?}", func_to_rgba(&"rgba(255, 0, 128, 56)".to_string()).ok());
}