1use std::str::FromStr;
4
5use nom::{
6 Finish, IResult, ToUsize,
7 branch::alt,
8 bytes::complete::{tag, take_while_m_n},
9 character::complete::{char, multispace0, multispace1, one_of},
10 combinator::{eof, flat_map, map, map_parser, map_res, recognize, value},
11 error::{Error as NomError, ParseError},
12 multi::{count, many0, many1, separated_list1},
13 number::complete::float,
14 sequence::{delimited, preceded, separated_pair, terminated, tuple},
15};
16
17use super::shapes::ExternalImage;
18
19use super::{
20 draw::{FontCharacteristics, Rgba, Style},
21 ops::Op,
22 shapes::{Ellipse, Points, PointsType, Text, TextAlign},
23};
24
25fn take_bytes<'a, C: ToUsize, E: ParseError<&'a str>>(
29 count: C,
30) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str, E> {
31 let count = count.to_usize();
32 move |input: &str| {
33 if input.is_char_boundary(count) {
34 Ok((&input[count..], &input[..count]))
35 } else {
36 Err(nom::Err::Error(E::from_error_kind(
38 input,
39 nom::error::ErrorKind::Count,
40 )))
41 }
42 }
43}
44
45fn decimal(input: &str) -> IResult<&str, &str> {
46 recognize(many1(terminated(one_of("0123456789"), many0(char('_')))))(input)
47}
48
49fn ws<'a, F, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
52where
53 F: FnMut(&'a str) -> IResult<&'a str, O, E>,
54{
55 delimited(multispace0, inner, multispace0)
56}
57
58fn tagged<'a, F, O, E: ParseError<&'a str>>(
59 t: &'static str,
60 inner: F,
61) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
62where
63 F: FnMut(&'a str) -> IResult<&'a str, O, E>,
64{
65 preceded(tuple((tag(t), multispace1)), inner)
66}
67
68fn parse_string(input: &str) -> IResult<&str, &str> {
72 flat_map(map_res(decimal, usize::from_str), |n| {
73 preceded(tuple((multispace1, tag("-"))), take_bytes(n))
74 })(input)
75}
76
77fn parse_text_align(input: &str) -> IResult<&str, TextAlign> {
78 alt((
79 value(TextAlign::Left, tag("-1")),
80 value(TextAlign::Center, tag("0")),
81 value(TextAlign::Right, tag("1")),
82 ))(input)
83}
84
85fn from_hex(input: &str) -> Result<u8, std::num::ParseIntError> {
86 u8::from_str_radix(input, 16)
87}
88
89fn is_hex_digit(c: char) -> bool {
90 c.is_ascii_hexdigit()
91}
92
93fn hex_primary(input: &str) -> IResult<&str, u8> {
94 map_res(take_while_m_n(2, 2, is_hex_digit), from_hex)(input)
95}
96
97fn hex_color(input: &str) -> IResult<&str, Rgba> {
98 let (input, _) = tag("#")(input)?;
99 let (input, (r, g, b)) = tuple((hex_primary, hex_primary, hex_primary))(input)?;
100 Ok((input, Rgba { r, g, b, a: 0xff }))
101}
102
103fn parse_op_draw_shape_ellipse(input: &str) -> IResult<&str, Op> {
106 let (input, (c, x, y, w, h)) = tuple((
107 one_of("Ee"),
108 preceded(multispace1, float),
109 preceded(multispace1, float),
110 preceded(multispace1, float),
111 preceded(multispace1, float),
112 ))(input)?;
113 let ellip = Ellipse {
114 filled: c == 'E',
115 x,
116 y,
117 w,
118 h,
119 };
120 Ok((input, ellip.into()))
121}
122
123fn parse_op_draw_shape_points(input: &str) -> IResult<&str, Op> {
124 let (input, (c, points)) = tuple((
125 terminated(one_of("PpLBb"), multispace1),
126 flat_map(map_res(decimal, usize::from_str), |n| {
127 count(
128 tuple((preceded(multispace1, float), preceded(multispace1, float))),
129 n,
130 )
131 }),
132 ))(input)?;
133 let points = Points {
134 filled: c == 'P' || c == 'b',
135 r#type: match c {
136 'P' | 'p' => PointsType::Polygon,
137 'L' => PointsType::Polyline,
138 'B' | 'b' => PointsType::BSpline,
139 _ => unreachable!(),
140 },
141 points,
142 };
143 Ok((input, points.into()))
144}
145
146fn parse_op_draw_shape_text(input: &str) -> IResult<&str, Op> {
147 let (input, (x, y, align, width, text)) = tagged(
148 "T",
149 tuple((
150 terminated(float, multispace1), terminated(float, multispace1), terminated(parse_text_align, multispace1), terminated(float, multispace1), parse_string,
155 )),
156 )(input)?;
157 let text = Text {
158 x,
159 y,
160 align,
161 width,
162 text: text.to_owned(),
163 };
164 Ok((input, text.into()))
165}
166
167fn parse_op_draw_shape(input: &str) -> IResult<&str, Op> {
168 alt((
169 parse_op_draw_shape_ellipse,
170 parse_op_draw_shape_points,
171 parse_op_draw_shape_text,
172 ))(input)
173}
174
175fn parse_op_set_font_characteristics(input: &str) -> IResult<&str, Op> {
176 tagged(
177 "t",
178 map_res(decimal, |value| {
179 u128::from_str(value).map(|n| FontCharacteristics::from_bits_truncate(n).into())
180 }),
181 )(input)
182}
183
184fn parse_op_set_color<'a>(
185 t: &'static str,
186 op: impl FnMut(Rgba) -> Op,
187) -> impl FnMut(&'a str) -> IResult<&'a str, Op> {
188 map(tagged(t, map_parser(parse_string, hex_color)), op)
189}
190
191fn parse_op_set_fill_color(input: &str) -> IResult<&str, Op> {
192 parse_op_set_color("C", Op::SetFillColor)(input)
193}
194
195fn parse_op_set_pen_color(input: &str) -> IResult<&str, Op> {
196 parse_op_set_color("c", Op::SetPenColor)(input)
197}
198
199fn parse_op_set_font(input: &str) -> IResult<&str, Op> {
200 let (input, (size, name)) =
201 tagged("F", separated_pair(float, multispace1, parse_string))(input)?;
202 Ok((
203 input,
204 Op::SetFont {
205 size,
206 name: name.to_owned(),
207 },
208 ))
209}
210
211fn parse_op_set_style(input: &str) -> IResult<&str, Op> {
212 let (input, style) = tagged("S", map_res(parse_string, Style::from_str))(input)?;
213 Ok((input, style.into()))
214}
215
216fn parse_op_external_image(input: &str) -> IResult<&str, Op> {
217 use nom::combinator::{cut, fail};
219 map(tagged("I", cut(fail)), |_: ()| ExternalImage.into())(input)
220}
221
222fn parse_op(input: &str) -> IResult<&str, Op> {
223 alt((
224 parse_op_draw_shape,
225 parse_op_set_font_characteristics,
226 parse_op_set_fill_color,
227 parse_op_set_pen_color,
228 parse_op_set_font,
229 parse_op_set_style,
230 parse_op_external_image,
231 ))(input)
232}
233
234pub(super) fn parse(input: &str) -> Result<Vec<Op>, NomError<&str>> {
235 terminated(ws(separated_list1(multispace1, parse_op)), eof)(input)
236 .finish()
237 .map(|(rest, ops)| {
238 assert_eq!(rest, "");
239 ops
240 })
241}
242
243#[test]
244fn test_ellipse() {
245 assert_eq!(
246 parse_op_draw_shape_ellipse("e 27 90 18 3"),
247 Ok((
248 "",
249 Ellipse {
250 filled: false,
251 x: 27.,
252 y: 90.,
253 w: 18.,
254 h: 3.,
255 }
256 .into()
257 ))
258 )
259}
260
261#[test]
262fn test_b_spline() {
263 assert_eq!(
264 parse_op_draw_shape_points("B 4 27 71.7 27 60.85 27 46.92 27 36.1"),
265 Ok((
266 "",
267 Points {
268 filled: false,
269 r#type: PointsType::BSpline,
270 points: vec![(27., 71.7), (27., 60.85), (27., 46.92), (27., 36.1)]
271 }
272 .into()
273 ))
274 )
275}
276
277#[test]
278fn test_string_utf8() {
279 assert_eq!(parse_string("3 -äh"), Ok(("", "äh")))
280}