ferroui_base/media/transformation/
transform_parser.rs1use crate::media::transformation::{TransformOperations, TransformOperationsBuilder};
2use crate::utilities::span_helpers::{try_parse_double, write_double, NumberStyles};
3use crate::utilities::{FormatError, MathUtilities};
4use crate::Matrix;
5use std::rc::Rc;
6
7pub struct TransformParser;
9
10const FUNCTION_MAPPING: [(&str, TransformFunction); 11] = [
11 ("translate", TransformFunction::Translate),
12 ("translateX", TransformFunction::TranslateX),
13 ("translateY", TransformFunction::TranslateY),
14 ("scale", TransformFunction::Scale),
15 ("scaleX", TransformFunction::ScaleX),
16 ("scaleY", TransformFunction::ScaleY),
17 ("skew", TransformFunction::Skew),
18 ("skewX", TransformFunction::SkewX),
19 ("skewY", TransformFunction::SkewY),
20 ("rotate", TransformFunction::Rotate),
21 ("matrix", TransformFunction::Matrix),
22];
23
24const UNIT_MAPPING: [(&str, Unit); 5] = [
25 ("deg", Unit::Degree),
26 ("grad", Unit::Gradian),
27 ("rad", Unit::Radian),
28 ("turn", Unit::Turn),
29 ("px", Unit::Pixel),
30];
31
32impl TransformParser {
33 pub fn parse(s: &str) -> Result<Rc<TransformOperations>, FormatError> {
34 let invalid_format = || FormatError::from_string(format!("Invalid transform string: '{s}'."));
35
36 if s.is_empty() {
37 return Err(invalid_format());
38 }
39
40 let mut span = s.trim();
41
42 if span.eq_ignore_ascii_case("none") {
43 return Ok(TransformOperations::identity());
44 }
45
46 let mut builder = TransformOperations::create_builder(0);
47
48 loop {
49 let begin_index = span.find('(');
50 let end_index = span.find(')');
51
52 let (Some(begin_index), Some(end_index)) = (begin_index, end_index) else {
53 return Err(invalid_format());
54 };
55 if end_index < begin_index {
56 return Err(invalid_format());
57 }
58
59 let name_part = span[..begin_index].trim();
60
61 let function = parse_transform_function(name_part);
62
63 if function == TransformFunction::Invalid {
64 return Err(invalid_format());
65 }
66
67 let value_part = span[begin_index + 1..end_index].trim();
68
69 parse_function(value_part, function, &mut builder)?;
70
71 span = &span[end_index + 1..];
72
73 if span.chars().all(char::is_whitespace) {
74 break;
75 }
76 }
77
78 Ok(builder.build())
79 }
80}
81
82fn parse_value(mut part: &str) -> Result<UnitValue, FormatError> {
83 let mut unit_index = None;
84
85 for (i, c) in part.char_indices() {
86 if c.is_ascii_digit() || c == '-' || c == '.' {
87 continue;
88 }
89
90 unit_index = Some(i);
91 break;
92 }
93
94 let mut unit = Unit::None;
95
96 if let Some(unit_index) = unit_index {
97 let unit_part = &part[unit_index..];
98
99 unit = parse_unit(unit_part)?;
100
101 part = &part[..unit_index];
102 }
103
104 let value = try_parse_double(part, NumberStyles::FLOAT).ok_or_else(|| FormatError::invalid_input(part))?;
105
106 Ok(UnitValue { unit, value })
107}
108
109fn parse_value_pair(part: &str, left_value: &mut UnitValue, right_value: &mut UnitValue) -> Result<usize, FormatError> {
110 if let Some(comma_index) = part.find(',') {
111 let left_part = part[..comma_index].trim();
112 let right_part = part[comma_index + 1..].trim();
113
114 *left_value = parse_value(left_part)?;
115 *right_value = parse_value(right_part)?;
116
117 return Ok(2);
118 }
119
120 *left_value = parse_value(part)?;
121
122 Ok(1)
123}
124
125fn parse_comma_delimited_values(mut part: &str, out_values: &mut [UnitValue]) -> Result<usize, FormatError> {
126 let mut value_index = 0;
127
128 loop {
129 if value_index >= out_values.len() {
130 return Err(FormatError::new("Too many provided values."));
131 }
132
133 let Some(comma_index) = part.find(',') else {
134 if !part.chars().all(char::is_whitespace) {
135 out_values[value_index] = parse_value(part)?;
136 value_index += 1;
137 }
138
139 break;
140 };
141
142 let value_part = part[..comma_index].trim();
143
144 out_values[value_index] = parse_value(value_part)?;
145 value_index += 1;
146
147 part = &part[comma_index + 1..];
148 }
149
150 Ok(value_index)
151}
152
153fn parse_function(
154 function_part: &str,
155 function: TransformFunction,
156 builder: &mut TransformOperationsBuilder,
157) -> Result<(), FormatError> {
158 match function {
159 TransformFunction::Scale | TransformFunction::ScaleX | TransformFunction::ScaleY => {
160 let mut scale_x = UnitValue::ONE;
161 let mut scale_y = UnitValue::ONE;
162
163 let count = parse_value_pair(function_part, &mut scale_x, &mut scale_y)?;
164
165 if count != 1 && (function == TransformFunction::ScaleX || function == TransformFunction::ScaleY) {
166 return Err(format_invalid_value_count(function, 1));
167 }
168
169 verify_zero_or_unit(function, &scale_x, Unit::None)?;
170 verify_zero_or_unit(function, &scale_y, Unit::None)?;
171
172 if function == TransformFunction::ScaleY {
173 scale_y = scale_x;
174 scale_x = UnitValue::ONE;
175 } else if function == TransformFunction::Scale && count == 1 {
176 scale_y = scale_x;
177 }
178
179 builder.append_scale(scale_x.value, scale_y.value);
180 }
181 TransformFunction::Skew | TransformFunction::SkewX | TransformFunction::SkewY => {
182 let mut skew_x = UnitValue::ZERO;
183 let mut skew_y = UnitValue::ZERO;
184
185 let count = parse_value_pair(function_part, &mut skew_x, &mut skew_y)?;
186
187 if count != 1 && (function == TransformFunction::SkewX || function == TransformFunction::SkewY) {
188 return Err(format_invalid_value_count(function, 1));
189 }
190
191 verify_zero_or_angle(function, &skew_x)?;
192 verify_zero_or_angle(function, &skew_y)?;
193
194 if function == TransformFunction::SkewY {
195 skew_y = skew_x;
196 skew_x = UnitValue::ZERO;
197 }
198
199 builder.append_skew(to_radians(&skew_x), to_radians(&skew_y));
200 }
201 TransformFunction::Rotate => {
202 let mut angle = UnitValue::ZERO;
203 let mut unused = UnitValue::ZERO;
204
205 let count = parse_value_pair(function_part, &mut angle, &mut unused)?;
206
207 if count != 1 {
208 return Err(format_invalid_value_count(function, 1));
209 }
210
211 verify_zero_or_angle(function, &angle)?;
212
213 builder.append_rotate(to_radians(&angle));
214 }
215 TransformFunction::Translate | TransformFunction::TranslateX | TransformFunction::TranslateY => {
216 let mut translate_x = UnitValue::ZERO;
217 let mut translate_y = UnitValue::ZERO;
218
219 let count = parse_value_pair(function_part, &mut translate_x, &mut translate_y)?;
220
221 if count != 1 && (function == TransformFunction::TranslateX || function == TransformFunction::TranslateY)
222 {
223 return Err(format_invalid_value_count(function, 1));
224 }
225
226 verify_zero_or_unit(function, &translate_x, Unit::Pixel)?;
227 verify_zero_or_unit(function, &translate_y, Unit::Pixel)?;
228
229 if function == TransformFunction::TranslateY {
230 translate_y = translate_x;
231 translate_x = UnitValue::ZERO;
232 }
233
234 builder.append_translate(translate_x.value, translate_y.value);
235 }
236 TransformFunction::Matrix => {
237 let mut values = [UnitValue::ZERO; 6];
238
239 let count = parse_comma_delimited_values(function_part, &mut values)?;
240
241 if count != 6 {
242 return Err(format_invalid_value_count(function, 6));
243 }
244
245 for value in &values {
246 verify_zero_or_unit(function, value, Unit::None)?;
247 }
248
249 let matrix = Matrix::new(
250 values[0].value,
251 values[1].value,
252 values[2].value,
253 values[3].value,
254 values[4].value,
255 values[5].value,
256 );
257
258 builder.append_matrix(matrix);
259 }
260 TransformFunction::Invalid => {}
261 }
262
263 Ok(())
264}
265
266fn verify_zero_or_unit(function: TransformFunction, value: &UnitValue, unit: Unit) -> Result<(), FormatError> {
267 let is_zero = value.unit == Unit::None && value.value == 0.0;
268
269 if !is_zero && value.unit != unit {
270 return Err(format_invalid_value(function, value));
271 }
272 Ok(())
273}
274
275fn verify_zero_or_angle(function: TransformFunction, value: &UnitValue) -> Result<(), FormatError> {
276 if value.value != 0.0 && !is_angle_unit(value.unit) {
277 return Err(format_invalid_value(function, value));
278 }
279 Ok(())
280}
281
282fn is_angle_unit(unit: Unit) -> bool {
283 matches!(unit, Unit::Radian | Unit::Gradian | Unit::Degree | Unit::Turn)
284}
285
286fn format_invalid_value(function: TransformFunction, value: &UnitValue) -> FormatError {
287 let unit_string = if value.unit == Unit::None { String::new() } else { format!("{:?}", value.unit) };
288 let mut number = String::new();
289 let _ = write_double(&mut number, value.value);
290
291 FormatError::from_string(format!("Invalid value {number} {unit_string} for {function:?}"))
292}
293
294fn format_invalid_value_count(function: TransformFunction, count: usize) -> FormatError {
295 FormatError::from_string(format!("Invalid format. {function:?} expects {count} value(s)."))
296}
297
298fn parse_unit(part: &str) -> Result<Unit, FormatError> {
299 for (name, unit) in UNIT_MAPPING {
300 if part.eq_ignore_ascii_case(name) {
301 return Ok(unit);
302 }
303 }
304
305 Err(FormatError::from_string(format!("Invalid unit: {part}")))
306}
307
308fn parse_transform_function(part: &str) -> TransformFunction {
309 for (name, transform_function) in FUNCTION_MAPPING {
310 if part.eq_ignore_ascii_case(name) {
311 return transform_function;
312 }
313 }
314
315 TransformFunction::Invalid
316}
317
318fn to_radians(value: &UnitValue) -> f64 {
319 match value.unit {
320 Unit::Radian => value.value,
321 Unit::Gradian => MathUtilities::grad2rad(value.value),
322 Unit::Degree => MathUtilities::deg2rad(value.value),
323 Unit::Turn => MathUtilities::turn2rad(value.value),
324 _ => value.value,
325 }
326}
327
328#[derive(Clone, Copy, Debug, PartialEq, Eq)]
329enum Unit {
330 None,
331 Pixel,
332 Radian,
333 Gradian,
334 Degree,
335 Turn,
336}
337
338#[derive(Clone, Copy, Debug)]
339struct UnitValue {
340 unit: Unit,
341 value: f64,
342}
343
344impl UnitValue {
345 const ZERO: UnitValue = UnitValue { unit: Unit::None, value: 0.0 };
346 const ONE: UnitValue = UnitValue { unit: Unit::None, value: 1.0 };
347}
348
349#[derive(Clone, Copy, Debug, PartialEq, Eq)]
350enum TransformFunction {
351 Invalid,
352 Translate,
353 TranslateX,
354 TranslateY,
355 Scale,
356 ScaleX,
357 ScaleY,
358 Skew,
359 SkewX,
360 SkewY,
361 Rotate,
362 Matrix,
363}