1use std::{borrow::Cow, fmt::Write as WriteStr, io, str};
4
5#[cfg(any(feature = "svg", feature = "test"))]
6pub use self::rgb_color::RgbColor;
7#[cfg(feature = "svg")]
8pub use self::rgb_color::RgbColorParseError;
9
10pub(crate) struct WriteAdapter<'a> {
12 inner: &'a mut dyn WriteStr,
13}
14
15impl<'a> WriteAdapter<'a> {
16 pub fn new(output: &'a mut dyn WriteStr) -> Self {
17 Self { inner: output }
18 }
19}
20
21impl io::Write for WriteAdapter<'_> {
22 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
23 let segment =
24 str::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
25 self.inner.write_str(segment).map_err(io::Error::other)?;
26 Ok(buf.len())
27 }
28
29 fn flush(&mut self) -> io::Result<()> {
30 Ok(())
31 }
32}
33
34pub(crate) fn normalize_newlines(s: &str) -> Cow<'_, str> {
35 if s.contains("\r\n") {
36 Cow::Owned(s.replace("\r\n", "\n"))
37 } else {
38 Cow::Borrowed(s)
39 }
40}
41
42#[cfg(not(windows))]
43pub(crate) fn is_recoverable_kill_error(err: &io::Error) -> bool {
44 matches!(err.kind(), io::ErrorKind::InvalidInput)
45}
46
47#[cfg(windows)]
52pub(crate) fn is_recoverable_kill_error(err: &io::Error) -> bool {
53 matches!(
54 err.kind(),
55 io::ErrorKind::InvalidInput | io::ErrorKind::PermissionDenied
56 )
57}
58
59#[cfg(any(feature = "svg", feature = "test"))]
60mod rgb_color {
61 use std::{error::Error as StdError, fmt, num::ParseIntError, str::FromStr};
62
63 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67 pub struct RgbColor(pub u8, pub u8, pub u8);
68
69 impl fmt::LowerHex for RgbColor {
70 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71 write!(formatter, "#{:02x}{:02x}{:02x}", self.0, self.1, self.2)
72 }
73 }
74
75 #[derive(Debug)]
77 #[non_exhaustive]
78 pub enum RgbColorParseError {
79 NotAscii,
81 NoHashPrefix,
83 IncorrectLen(usize),
87 IncorrectDigit(ParseIntError),
89 }
90
91 impl fmt::Display for RgbColorParseError {
92 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
93 match self {
94 Self::NotAscii => formatter.write_str("color string contains non-ASCII chars"),
95 Self::NoHashPrefix => formatter.write_str("missing '#' prefix"),
96 Self::IncorrectLen(len) => write!(
97 formatter,
98 "unexpected byte length {len} of color string, expected 4 or 7"
99 ),
100 Self::IncorrectDigit(err) => write!(formatter, "error parsing hex digit: {err}"),
101 }
102 }
103 }
104
105 impl StdError for RgbColorParseError {
106 fn source(&self) -> Option<&(dyn StdError + 'static)> {
107 match self {
108 Self::IncorrectDigit(err) => Some(err),
109 _ => None,
110 }
111 }
112 }
113
114 impl FromStr for RgbColor {
115 type Err = RgbColorParseError;
116
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
118 if s.is_empty() || s.as_bytes()[0] != b'#' {
119 Err(RgbColorParseError::NoHashPrefix)
120 } else if s.len() == 4 {
121 if !s.is_ascii() {
122 return Err(RgbColorParseError::NotAscii);
123 }
124
125 let r =
126 u8::from_str_radix(&s[1..2], 16).map_err(RgbColorParseError::IncorrectDigit)?;
127 let g =
128 u8::from_str_radix(&s[2..3], 16).map_err(RgbColorParseError::IncorrectDigit)?;
129 let b =
130 u8::from_str_radix(&s[3..], 16).map_err(RgbColorParseError::IncorrectDigit)?;
131 Ok(Self(r * 17, g * 17, b * 17))
132 } else if s.len() == 7 {
133 if !s.is_ascii() {
134 return Err(RgbColorParseError::NotAscii);
135 }
136
137 let r =
138 u8::from_str_radix(&s[1..3], 16).map_err(RgbColorParseError::IncorrectDigit)?;
139 let g =
140 u8::from_str_radix(&s[3..5], 16).map_err(RgbColorParseError::IncorrectDigit)?;
141 let b =
142 u8::from_str_radix(&s[5..], 16).map_err(RgbColorParseError::IncorrectDigit)?;
143 Ok(Self(r, g, b))
144 } else {
145 Err(RgbColorParseError::IncorrectLen(s.len()))
146 }
147 }
148 }
149}
150
151#[cfg(all(test, any(feature = "svg", feature = "test")))]
152mod tests {
153 use assert_matches::assert_matches;
154
155 use super::*;
156
157 #[test]
158 fn parsing_color() {
159 let RgbColor(r, g, b) = "#fed".parse().unwrap();
160 assert_eq!((r, g, b), (0xff, 0xee, 0xdd));
161 let RgbColor(r, g, b) = "#c0ffee".parse().unwrap();
162 assert_eq!((r, g, b), (0xc0, 0xff, 0xee));
163 }
164
165 #[test]
166 fn errors_parsing_color() {
167 let err = "123".parse::<RgbColor>().unwrap_err();
168 assert_matches!(err, RgbColorParseError::NoHashPrefix);
169 let err = "#12".parse::<RgbColor>().unwrap_err();
170 assert_matches!(err, RgbColorParseError::IncorrectLen(3));
171 let err = "#тэг".parse::<RgbColor>().unwrap_err();
172 assert_matches!(err, RgbColorParseError::NotAscii);
173 let err = "#coffee".parse::<RgbColor>().unwrap_err();
174 assert_matches!(err, RgbColorParseError::IncorrectDigit(_));
175 }
176}