ferroui_base/media/
unicode_range.rs1use std::rc::Rc;
2use std::str::FromStr;
3
4use crate::utilities::FormatError;
5
6#[derive(Clone, Debug, PartialEq, Eq, Hash)]
9pub struct UnicodeRange {
10 single: UnicodeRangeSegment,
11 segments: Option<Rc<[UnicodeRangeSegment]>>,
12}
13
14impl UnicodeRange {
15 pub fn new(start: i32, end: i32) -> Self {
17 Self { single: UnicodeRangeSegment::new(start, end), segments: None }
18 }
19
20 pub fn from_segment(single: UnicodeRangeSegment) -> Self {
22 Self { single, segments: None }
23 }
24
25 pub fn from_segments(segments: Vec<UnicodeRangeSegment>) -> Self {
29 if segments.is_empty() {
30 panic!("segments must not be empty");
31 }
32
33 Self { single: segments[0], segments: Some(Rc::from(segments)) }
34 }
35
36 pub fn default_range() -> Self {
38 Self::new(0, 0x10FFFD)
39 }
40
41 #[allow(dead_code)] pub(crate) fn single(&self) -> UnicodeRangeSegment {
43 self.single
44 }
45
46 #[allow(dead_code)] pub(crate) fn segments(&self) -> Option<&[UnicodeRangeSegment]> {
48 self.segments.as_deref()
49 }
50
51 pub fn is_in_range(&self, value: i32) -> bool {
53 match &self.segments {
54 None => self.single.is_in_range(value),
55 Some(segments) => segments.iter().any(|segment| segment.is_in_range(value)),
56 }
57 }
58
59 pub fn parse(s: &str) -> Result<UnicodeRange, FormatError> {
61 if s.is_empty() {
62 return Err(FormatError::new("Could not parse specified Unicode range."));
63 }
64
65 let mut parts = s.split(',');
66
67 if !s.contains(',') {
68 return Ok(UnicodeRange::from_segment(UnicodeRangeSegment::parse(s)?));
69 }
70
71 let segments = parts.by_ref().map(|part| UnicodeRangeSegment::parse(part.trim())).collect::<Result<Vec<_>, _>>()?;
72
73 Ok(UnicodeRange::from_segments(segments))
74 }
75}
76
77impl Default for UnicodeRange {
78 fn default() -> Self {
79 Self::default_range()
80 }
81}
82
83impl FromStr for UnicodeRange {
84 type Err = FormatError;
85
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
87 Self::parse(s)
88 }
89}
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
93pub struct UnicodeRangeSegment {
94 start: i32,
95 end: i32,
96}
97
98impl UnicodeRangeSegment {
99 pub const fn new(start: i32, end: i32) -> Self {
100 Self { start, end }
101 }
102
103 pub const fn start(&self) -> i32 {
105 self.start
106 }
107
108 pub const fn end(&self) -> i32 {
110 self.end
111 }
112
113 pub const fn is_in_range(&self, value: i32) -> bool {
115 self.start <= value && value <= self.end
116 }
117
118 fn match_value(s: &str) -> Option<&str> {
122 let value = s.strip_prefix("u+").or_else(|| s.strip_prefix("U+")).unwrap_or(s);
123 let bytes = value.as_bytes();
124
125 if bytes.is_empty() || bytes.len() > 6 || !bytes[0].is_ascii_hexdigit() {
126 return None;
127 }
128
129 if bytes[1..].iter().all(|b| b.is_ascii_hexdigit() || *b == b'?') {
130 Some(value)
131 } else {
132 None
133 }
134 }
135
136 fn parse_hex(value: &str) -> Result<i32, FormatError> {
137 i32::from_str_radix(value, 16)
138 .map_err(|_| FormatError::new("Could not parse specified Unicode range segment."))
139 }
140
141 pub fn parse(s: &str) -> Result<UnicodeRangeSegment, FormatError> {
143 const ERROR: FormatError = FormatError::new("Could not parse specified Unicode range segment.");
144
145 if s.is_empty() {
146 return Err(ERROR);
147 }
148
149 let mut parts = s.split('-');
150 let first = parts.next().unwrap_or("");
151 let second = parts.next();
152
153 if parts.next().is_some() {
154 return Err(ERROR);
155 }
156
157 match second {
158 None => {
159 let single = Self::match_value(first).ok_or(ERROR)?;
161
162 if !single.contains('?') {
163 let start = Self::parse_hex(single)?;
164 Ok(UnicodeRangeSegment::new(start, start))
165 } else {
166 let start = Self::parse_hex(&single.replace('?', "0"))?;
167 let end = Self::parse_hex(&single.replace('?', "F"))?;
168 Ok(UnicodeRangeSegment::new(start, end))
169 }
170 }
171 Some(second) => {
172 let first = Self::match_value(first).ok_or(ERROR)?;
173 let second = Self::match_value(second).ok_or(ERROR)?;
174
175 Ok(UnicodeRangeSegment::new(Self::parse_hex(first)?, Self::parse_hex(second)?))
176 }
177 }
178 }
179}
180
181impl FromStr for UnicodeRangeSegment {
182 type Err = FormatError;
183
184 fn from_str(s: &str) -> Result<Self, Self::Err> {
185 Self::parse(s)
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn should_parse_segments() {
195 let range = UnicodeRange::parse("U+0, U+1, U+2, U+3").unwrap();
196
197 let segments = range.segments().expect("the range has segments");
198
199 assert_eq!(segments.iter().map(UnicodeRangeSegment::start).collect::<Vec<_>>(), [0, 1, 2, 3]);
200 }
201
202 #[test]
203 fn should_parse() {
204 for (s, expected_start, expected_end) in
205 [("u+00-FF", 0, 255), ("U+00-FF", 0, 255), ("U+00-U+FF", 0, 255), ("U+AB??", 43776, 44031)]
206 {
207 let segment = UnicodeRangeSegment::parse(s).unwrap();
208
209 assert_eq!(segment.start(), expected_start, "{s}");
210 assert_eq!(segment.end(), expected_end, "{s}");
211 }
212 }
213
214 #[test]
215 fn in_range_should_return_false_for_values_outside_range() {
216 let segment = UnicodeRangeSegment::new(20, 25);
217
218 for value in [0, 19, 26, 100] {
219 assert!(!segment.is_in_range(value));
220 }
221 }
222
223 #[test]
224 fn in_range_should_return_true_for_values_within_range() {
225 let segment = UnicodeRangeSegment::new(20, 22);
226
227 for value in [20, 21, 22] {
228 assert!(segment.is_in_range(value));
229 }
230 }
231
232 #[test]
233 fn malformed_ranges_are_errors() {
234 for s in ["", "U+", "U+G", "U+1-2-3", "U+1234567", "x"] {
235 assert!(UnicodeRangeSegment::parse(s).is_err(), "{s}");
236 }
237
238 assert!(UnicodeRange::parse("").is_err());
239 assert!(UnicodeRange::parse("U+1, x").is_err());
240 assert!(UnicodeRange::default().is_in_range(0x10FFFD));
241 assert!(UnicodeRange::parse("U+41").unwrap().is_in_range(0x41));
242 }
243}