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