1use alloc::{
4 string::{String, ToString},
5 vec::Vec,
6};
7use core::fmt;
8use std::num::ParseFloatError;
9use crate::corety::AzString;
10
11#[cfg(feature = "parser")]
12use crate::props::basic::{
13 error::WrongComponentCountError,
14 length::parse_float_value,
15 parse::{parse_parentheses, ParenthesisParseError, ParenthesisParseErrorOwned},
16};
17use crate::{
18 codegen::format::GetHash,
19 props::{
20 basic::{
21 angle::{
22 parse_angle_value, AngleValue, CssAngleValueParseError,
23 CssAngleValueParseErrorOwned,
24 },
25 length::{PercentageParseError, PercentageValue},
26 pixel::{
27 parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned,
28 PixelValue,
29 },
30 FloatValue,
31 },
32 formatter::PrintAsCssValue,
33 },
34};
35
36#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[repr(C)]
41pub struct StylePerspectiveOrigin {
42 pub x: PixelValue,
43 pub y: PixelValue,
44}
45
46impl StylePerspectiveOrigin {
47 #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
48 Self {
49 x: self.x.interpolate(&other.x, t),
50 y: self.y.interpolate(&other.y, t),
51 }
52 }
53}
54
55impl PrintAsCssValue for StylePerspectiveOrigin {
56 fn print_as_css_value(&self) -> String {
57 format!("{} {}", self.x, self.y)
58 }
59}
60
61#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
63#[repr(C)]
64pub struct StyleTransformOrigin {
65 pub x: PixelValue,
66 pub y: PixelValue,
67}
68
69impl Default for StyleTransformOrigin {
70 fn default() -> Self {
71 Self {
72 x: PixelValue::const_percent(50),
73 y: PixelValue::const_percent(50),
74 }
75 }
76}
77
78impl StyleTransformOrigin {
79 #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
80 Self {
81 x: self.x.interpolate(&other.x, t),
82 y: self.y.interpolate(&other.y, t),
83 }
84 }
85}
86
87impl PrintAsCssValue for StyleTransformOrigin {
88 fn print_as_css_value(&self) -> String {
89 format!("{} {}", self.x, self.y)
90 }
91}
92
93impl crate::codegen::format::FormatAsRustCode for StylePerspectiveOrigin {
95 fn format_as_rust_code(&self, _tabs: usize) -> String {
96 format!(
97 "StylePerspectiveOrigin {{ x: {}, y: {} }}",
98 crate::codegen::format::format_pixel_value(&self.x),
99 crate::codegen::format::format_pixel_value(&self.y)
100 )
101 }
102}
103
104impl crate::codegen::format::FormatAsRustCode for StyleTransformOrigin {
106 fn format_as_rust_code(&self, _tabs: usize) -> String {
107 format!(
108 "StyleTransformOrigin {{ x: {}, y: {} }}",
109 crate::codegen::format::format_pixel_value(&self.x),
110 crate::codegen::format::format_pixel_value(&self.y)
111 )
112 }
113}
114
115#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
117#[repr(C)]
118pub enum StyleBackfaceVisibility {
119 #[default]
120 Visible,
121 Hidden,
122}
123
124impl PrintAsCssValue for StyleBackfaceVisibility {
125 fn print_as_css_value(&self) -> String {
126 String::from(match self {
127 Self::Hidden => "hidden",
128 Self::Visible => "visible",
129 })
130 }
131}
132
133#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
135#[repr(C, u8)]
136pub enum StyleTransform {
137 Matrix(StyleTransformMatrix2D),
138 Matrix3D(StyleTransformMatrix3D),
139 Translate(StyleTransformTranslate2D),
140 Translate3D(StyleTransformTranslate3D),
141 TranslateX(PixelValue),
142 TranslateY(PixelValue),
143 TranslateZ(PixelValue),
144 Rotate(AngleValue),
145 Rotate3D(StyleTransformRotate3D),
146 RotateX(AngleValue),
147 RotateY(AngleValue),
148 RotateZ(AngleValue),
149 Scale(StyleTransformScale2D),
150 Scale3D(StyleTransformScale3D),
151 ScaleX(PercentageValue),
152 ScaleY(PercentageValue),
153 ScaleZ(PercentageValue),
154 Skew(StyleTransformSkew2D),
155 SkewX(AngleValue),
156 SkewY(AngleValue),
157 Perspective(PixelValue),
158}
159
160impl_option!(
161 StyleTransform,
162 OptionStyleTransform,
163 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd]
164);
165
166impl_vec!(StyleTransform, StyleTransformVec, StyleTransformVecDestructor, StyleTransformVecDestructorType, StyleTransformVecSlice, OptionStyleTransform);
167impl_vec_debug!(StyleTransform, StyleTransformVec);
168impl_vec_partialord!(StyleTransform, StyleTransformVec);
169impl_vec_ord!(StyleTransform, StyleTransformVec);
170impl_vec_clone!(
171 StyleTransform,
172 StyleTransformVec,
173 StyleTransformVecDestructor
174);
175impl_vec_partialeq!(StyleTransform, StyleTransformVec);
176impl_vec_eq!(StyleTransform, StyleTransformVec);
177impl_vec_hash!(StyleTransform, StyleTransformVec);
178
179impl PrintAsCssValue for StyleTransformVec {
180 fn print_as_css_value(&self) -> String {
181 self.as_ref()
182 .iter()
183 .map(PrintAsCssValue::print_as_css_value)
184 .collect::<Vec<_>>()
185 .join(" ")
186 }
187}
188
189impl crate::codegen::format::FormatAsRustCode for StyleTransformVec {
191 fn format_as_rust_code(&self, _tabs: usize) -> String {
192 format!(
193 "StyleTransformVec::from_const_slice(STYLE_TRANSFORM_{}_ITEMS)",
194 self.get_hash()
195 )
196 }
197}
198
199impl PrintAsCssValue for StyleTransform {
200 fn print_as_css_value(&self) -> String {
201 match self {
202 Self::Matrix(m) => format!(
203 "matrix({}, {}, {}, {}, {}, {})",
204 m.a, m.b, m.c, m.d, m.tx, m.ty
205 ),
206 Self::Matrix3D(m) => format!(
207 "matrix3d({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
208 m.m11,
209 m.m12,
210 m.m13,
211 m.m14,
212 m.m21,
213 m.m22,
214 m.m23,
215 m.m24,
216 m.m31,
217 m.m32,
218 m.m33,
219 m.m34,
220 m.m41,
221 m.m42,
222 m.m43,
223 m.m44
224 ),
225 Self::Translate(t) => format!("translate({}, {})", t.x, t.y),
226 Self::Translate3D(t) => format!("translate3d({}, {}, {})", t.x, t.y, t.z),
227 Self::TranslateX(x) => format!("translateX({x})"),
228 Self::TranslateY(y) => format!("translateY({y})"),
229 Self::TranslateZ(z) => format!("translateZ({z})"),
230 Self::Rotate(r) => format!("rotate({r})"),
231 Self::Rotate3D(r) => {
232 format!("rotate3d({}, {}, {}, {})", r.x, r.y, r.z, r.angle)
233 }
234 Self::RotateX(x) => format!("rotateX({x})"),
235 Self::RotateY(y) => format!("rotateY({y})"),
236 Self::RotateZ(z) => format!("rotateZ({z})"),
237 Self::Scale(s) => format!("scale({}, {})", s.x, s.y),
238 Self::Scale3D(s) => format!("scale3d({}, {}, {})", s.x, s.y, s.z),
239 Self::ScaleX(x) => format!("scaleX({x})"),
240 Self::ScaleY(y) => format!("scaleY({y})"),
241 Self::ScaleZ(z) => format!("scaleZ({z})"),
242 Self::Skew(sk) => format!("skew({}, {})", sk.x, sk.y),
243 Self::SkewX(x) => format!("skewX({x})"),
244 Self::SkewY(y) => format!("skewY({y})"),
245 Self::Perspective(dist) => format!("perspective({dist})"),
246 }
247 }
248}
249
250#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
252#[repr(C)]
253pub struct StyleTransformMatrix2D {
254 pub a: FloatValue,
255 pub b: FloatValue,
256 pub c: FloatValue,
257 pub d: FloatValue,
258 pub tx: FloatValue,
259 pub ty: FloatValue,
260}
261
262impl Default for StyleTransformMatrix2D {
263 fn default() -> Self {
264 Self {
265 a: FloatValue::const_new(1),
266 b: FloatValue::const_new(0),
267 c: FloatValue::const_new(0),
268 d: FloatValue::const_new(1),
269 tx: FloatValue::const_new(0),
270 ty: FloatValue::const_new(0),
271 }
272 }
273}
274
275#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
277#[repr(C)]
278pub struct StyleTransformMatrix3D {
279 pub m11: FloatValue,
280 pub m12: FloatValue,
281 pub m13: FloatValue,
282 pub m14: FloatValue,
283 pub m21: FloatValue,
284 pub m22: FloatValue,
285 pub m23: FloatValue,
286 pub m24: FloatValue,
287 pub m31: FloatValue,
288 pub m32: FloatValue,
289 pub m33: FloatValue,
290 pub m34: FloatValue,
291 pub m41: FloatValue,
292 pub m42: FloatValue,
293 pub m43: FloatValue,
294 pub m44: FloatValue,
295}
296
297impl Default for StyleTransformMatrix3D {
298 fn default() -> Self {
299 Self {
300 m11: FloatValue::const_new(1),
301 m12: FloatValue::const_new(0),
302 m13: FloatValue::const_new(0),
303 m14: FloatValue::const_new(0),
304 m21: FloatValue::const_new(0),
305 m22: FloatValue::const_new(1),
306 m23: FloatValue::const_new(0),
307 m24: FloatValue::const_new(0),
308 m31: FloatValue::const_new(0),
309 m32: FloatValue::const_new(0),
310 m33: FloatValue::const_new(1),
311 m34: FloatValue::const_new(0),
312 m41: FloatValue::const_new(0),
313 m42: FloatValue::const_new(0),
314 m43: FloatValue::const_new(0),
315 m44: FloatValue::const_new(1),
316 }
317 }
318}
319
320#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
322#[repr(C)]
323pub struct StyleTransformTranslate2D {
324 pub x: PixelValue,
325 pub y: PixelValue,
326}
327
328#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
330#[repr(C)]
331pub struct StyleTransformTranslate3D {
332 pub x: PixelValue,
333 pub y: PixelValue,
334 pub z: PixelValue,
335}
336
337#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
339#[repr(C)]
340pub struct StyleTransformRotate3D {
341 pub x: FloatValue,
342 pub y: FloatValue,
343 pub z: FloatValue,
344 pub angle: AngleValue,
345}
346
347#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
349#[repr(C)]
350pub struct StyleTransformScale2D {
351 pub x: FloatValue,
352 pub y: FloatValue,
353}
354
355#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
357#[repr(C)]
358pub struct StyleTransformScale3D {
359 pub x: FloatValue,
360 pub y: FloatValue,
361 pub z: FloatValue,
362}
363
364#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
366#[repr(C)]
367pub struct StyleTransformSkew2D {
368 pub x: AngleValue,
369 pub y: AngleValue,
370}
371
372#[derive(Clone, PartialEq, Eq)]
375pub enum CssStyleTransformParseError<'a> {
376 InvalidTransform(&'a str),
377 InvalidParenthesis(ParenthesisParseError<'a>),
378 WrongNumberOfComponents {
379 expected: usize,
380 got: usize,
381 input: &'a str,
382 },
383 NumberParseError(ParseFloatError),
384 PixelValueParseError(CssPixelValueParseError<'a>),
385 AngleValueParseError(CssAngleValueParseError<'a>),
386 PercentageValueParseError(PercentageParseError),
387}
388
389impl_debug_as_display!(CssStyleTransformParseError<'a>);
390impl_display! { CssStyleTransformParseError<'a>, {
391 InvalidTransform(e) => format!("Invalid transform property: \"{}\"", e),
392 InvalidParenthesis(e) => format!("Invalid transform property - parenthesis error: {}", e),
393 WrongNumberOfComponents { expected, got, input } => format!("Invalid number of components: expected {}, got {}: \"{}\"", expected, got, input),
394 NumberParseError(e) => format!("Could not parse number: {}", e),
395 PixelValueParseError(e) => format!("Invalid pixel value: {}", e),
396 AngleValueParseError(e) => format!("Invalid angle value: {}", e),
397 PercentageValueParseError(e) => format!("Error parsing percentage: {}", e),
398}}
399
400impl_from! { ParenthesisParseError<'a>, CssStyleTransformParseError::InvalidParenthesis }
401impl_from! { CssPixelValueParseError<'a>, CssStyleTransformParseError::PixelValueParseError }
402impl_from! { CssAngleValueParseError<'a>, CssStyleTransformParseError::AngleValueParseError }
403impl From<ParseFloatError> for CssStyleTransformParseError<'_> {
406 fn from(e: ParseFloatError) -> Self {
407 Self::NumberParseError(e)
408 }
409}
410
411impl From<PercentageParseError> for CssStyleTransformParseError<'_> {
412 fn from(p: PercentageParseError) -> Self {
413 Self::PercentageValueParseError(p)
414 }
415}
416
417#[derive(Debug, Clone, PartialEq, Eq)]
418#[repr(C, u8)]
419pub enum CssStyleTransformParseErrorOwned {
420 InvalidTransform(AzString),
421 InvalidParenthesis(ParenthesisParseErrorOwned),
422 WrongNumberOfComponents(WrongComponentCountError),
423 NumberParseError(crate::props::basic::error::ParseFloatError),
424 PixelValueParseError(CssPixelValueParseErrorOwned),
425 AngleValueParseError(CssAngleValueParseErrorOwned),
426 PercentageValueParseError(PercentageParseError),
427}
428
429impl CssStyleTransformParseError<'_> {
430 #[must_use] pub fn to_contained(&self) -> CssStyleTransformParseErrorOwned {
431 match self {
432 Self::InvalidTransform(s) => {
433 CssStyleTransformParseErrorOwned::InvalidTransform((*s).to_string().into())
434 }
435 Self::InvalidParenthesis(e) => {
436 CssStyleTransformParseErrorOwned::InvalidParenthesis(e.to_contained())
437 }
438 Self::WrongNumberOfComponents {
439 expected,
440 got,
441 input,
442 } => CssStyleTransformParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
443 expected: *expected,
444 got: *got,
445 input: (*input).to_string().into(),
446 }),
447 Self::NumberParseError(e) => {
448 CssStyleTransformParseErrorOwned::NumberParseError(e.clone().into())
449 }
450 Self::PixelValueParseError(e) => {
451 CssStyleTransformParseErrorOwned::PixelValueParseError(e.to_contained())
452 }
453 Self::AngleValueParseError(e) => {
454 CssStyleTransformParseErrorOwned::AngleValueParseError(e.to_contained())
455 }
456 Self::PercentageValueParseError(e) => {
457 CssStyleTransformParseErrorOwned::PercentageValueParseError(e.clone())
458 }
459 }
460 }
461}
462
463impl CssStyleTransformParseErrorOwned {
464 #[must_use] pub fn to_shared(&self) -> CssStyleTransformParseError<'_> {
465 match self {
466 Self::InvalidTransform(s) => CssStyleTransformParseError::InvalidTransform(s),
467 Self::InvalidParenthesis(e) => {
468 CssStyleTransformParseError::InvalidParenthesis(e.to_shared())
469 }
470 Self::WrongNumberOfComponents(e) => CssStyleTransformParseError::WrongNumberOfComponents {
471 expected: e.expected,
472 got: e.got,
473 input: e.input.as_str(),
474 },
475 Self::NumberParseError(e) => CssStyleTransformParseError::NumberParseError(e.to_std()),
476 Self::PixelValueParseError(e) => {
477 CssStyleTransformParseError::PixelValueParseError(e.to_shared())
478 }
479 Self::AngleValueParseError(e) => {
480 CssStyleTransformParseError::AngleValueParseError(e.to_shared())
481 }
482 Self::PercentageValueParseError(e) => {
483 CssStyleTransformParseError::PercentageValueParseError(e.clone())
484 }
485 }
486 }
487}
488
489#[derive(Clone, PartialEq, Eq)]
490pub enum CssStyleTransformOriginParseError<'a> {
491 WrongNumberOfComponents {
492 expected: usize,
493 got: usize,
494 input: &'a str,
495 },
496 PixelValueParseError(CssPixelValueParseError<'a>),
497}
498
499impl_debug_as_display!(CssStyleTransformOriginParseError<'a>);
500impl_display! { CssStyleTransformOriginParseError<'a>, {
501 WrongNumberOfComponents { expected, got, input } => format!("Invalid number of components: expected {}, got {}: \"{}\"", expected, got, input),
502 PixelValueParseError(e) => format!("Invalid pixel value: {}", e),
503}}
504impl_from! { CssPixelValueParseError<'a>, CssStyleTransformOriginParseError::PixelValueParseError }
505
506#[derive(Debug, Clone, PartialEq, Eq)]
507#[repr(C, u8)]
508pub enum CssStyleTransformOriginParseErrorOwned {
509 WrongNumberOfComponents(WrongComponentCountError),
510 PixelValueParseError(CssPixelValueParseErrorOwned),
511}
512
513impl CssStyleTransformOriginParseError<'_> {
514 #[must_use] pub fn to_contained(&self) -> CssStyleTransformOriginParseErrorOwned {
515 match self {
516 Self::WrongNumberOfComponents {
517 expected,
518 got,
519 input,
520 } => CssStyleTransformOriginParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
521 expected: *expected,
522 got: *got,
523 input: (*input).to_string().into(),
524 }),
525 Self::PixelValueParseError(e) => {
526 CssStyleTransformOriginParseErrorOwned::PixelValueParseError(e.to_contained())
527 }
528 }
529 }
530}
531
532impl CssStyleTransformOriginParseErrorOwned {
533 #[must_use] pub fn to_shared(&self) -> CssStyleTransformOriginParseError<'_> {
534 match self {
535 Self::WrongNumberOfComponents(e) => CssStyleTransformOriginParseError::WrongNumberOfComponents {
536 expected: e.expected,
537 got: e.got,
538 input: e.input.as_str(),
539 },
540 Self::PixelValueParseError(e) => {
541 CssStyleTransformOriginParseError::PixelValueParseError(e.to_shared())
542 }
543 }
544 }
545}
546
547#[derive(Clone, PartialEq, Eq)]
548pub enum CssStylePerspectiveOriginParseError<'a> {
549 WrongNumberOfComponents {
550 expected: usize,
551 got: usize,
552 input: &'a str,
553 },
554 PixelValueParseError(CssPixelValueParseError<'a>),
555}
556
557impl_debug_as_display!(CssStylePerspectiveOriginParseError<'a>);
558impl_display! { CssStylePerspectiveOriginParseError<'a>, {
559 WrongNumberOfComponents { expected, got, input } => format!("Invalid number of components: expected {}, got {}: \"{}\"", expected, got, input),
560 PixelValueParseError(e) => format!("Invalid pixel value: {}", e),
561}}
562impl_from! { CssPixelValueParseError<'a>, CssStylePerspectiveOriginParseError::PixelValueParseError }
563
564#[derive(Debug, Clone, PartialEq, Eq)]
565#[repr(C, u8)]
566pub enum CssStylePerspectiveOriginParseErrorOwned {
567 WrongNumberOfComponents(WrongComponentCountError),
568 PixelValueParseError(CssPixelValueParseErrorOwned),
569}
570
571impl CssStylePerspectiveOriginParseError<'_> {
572 #[must_use] pub fn to_contained(&self) -> CssStylePerspectiveOriginParseErrorOwned {
573 match self {
574 Self::WrongNumberOfComponents {
575 expected,
576 got,
577 input,
578 } => CssStylePerspectiveOriginParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
579 expected: *expected,
580 got: *got,
581 input: (*input).to_string().into(),
582 }),
583 Self::PixelValueParseError(e) => {
584 CssStylePerspectiveOriginParseErrorOwned::PixelValueParseError(e.to_contained())
585 }
586 }
587 }
588}
589
590impl CssStylePerspectiveOriginParseErrorOwned {
591 #[must_use] pub fn to_shared(&self) -> CssStylePerspectiveOriginParseError<'_> {
592 match self {
593 Self::WrongNumberOfComponents(e) => CssStylePerspectiveOriginParseError::WrongNumberOfComponents {
594 expected: e.expected,
595 got: e.got,
596 input: e.input.as_str(),
597 },
598 Self::PixelValueParseError(e) => {
599 CssStylePerspectiveOriginParseError::PixelValueParseError(e.to_shared())
600 }
601 }
602 }
603}
604
605#[derive(Clone, PartialEq, Eq)]
606pub enum CssBackfaceVisibilityParseError<'a> {
607 InvalidValue(&'a str),
608}
609
610impl_debug_as_display!(CssBackfaceVisibilityParseError<'a>);
611impl_display! { CssBackfaceVisibilityParseError<'a>, {
612 InvalidValue(s) => format!("Invalid value for backface-visibility: \"{}\", expected \"visible\" or \"hidden\"", s),
613}}
614
615#[derive(Debug, Clone, PartialEq, Eq)]
616#[repr(C, u8)]
617pub enum CssBackfaceVisibilityParseErrorOwned {
618 InvalidValue(AzString),
619}
620
621impl CssBackfaceVisibilityParseError<'_> {
622 #[must_use] pub fn to_contained(&self) -> CssBackfaceVisibilityParseErrorOwned {
623 match self {
624 Self::InvalidValue(s) => {
625 CssBackfaceVisibilityParseErrorOwned::InvalidValue((*s).to_string().into())
626 }
627 }
628 }
629}
630
631impl CssBackfaceVisibilityParseErrorOwned {
632 #[must_use] pub fn to_shared(&self) -> CssBackfaceVisibilityParseError<'_> {
633 match self {
634 Self::InvalidValue(s) => CssBackfaceVisibilityParseError::InvalidValue(s),
635 }
636 }
637}
638
639#[cfg(feature = "parser")]
642pub fn parse_style_transform_vec(
646 input: &str,
647) -> Result<StyleTransformVec, CssStyleTransformParseError<'_>> {
648 crate::props::basic::parse::split_string_respect_whitespace(input)
649 .iter()
650 .map(|i| parse_style_transform(i))
651 .collect::<Result<Vec<_>, _>>()
652 .map(Into::into)
653}
654
655#[cfg(feature = "parser")]
656#[allow(clippy::too_many_lines)] pub fn parse_style_transform(
661 input: &str,
662) -> Result<StyleTransform, CssStyleTransformParseError<'_>> {
663 fn get_numbers(
664 input: &str,
665 expected: usize,
666 ) -> Result<Vec<f32>, CssStyleTransformParseError<'_>> {
667 let numbers: Vec<_> = input
668 .split(',')
669 .map(|s| s.trim().parse::<f32>())
670 .collect::<Result<_, _>>()?;
671 if numbers.len() == expected {
672 Ok(numbers)
673 } else {
674 Err(CssStyleTransformParseError::WrongNumberOfComponents {
675 expected,
676 got: numbers.len(),
677 input,
678 })
679 }
680 }
681
682 let (transform_type, transform_values) = parse_parentheses(
683 input,
684 &[
685 "matrix",
686 "matrix3d",
687 "translate",
688 "translate3d",
689 "translateX",
690 "translateY",
691 "translateZ",
692 "rotate",
693 "rotate3d",
694 "rotateX",
695 "rotateY",
696 "rotateZ",
697 "scale",
698 "scale3d",
699 "scaleX",
700 "scaleY",
701 "scaleZ",
702 "skew",
703 "skewX",
704 "skewY",
705 "perspective",
706 ],
707 )?;
708
709 match transform_type {
710 "matrix" => {
711 let nums = get_numbers(transform_values, 6)?;
712 Ok(StyleTransform::Matrix(StyleTransformMatrix2D {
713 a: FloatValue::new(nums[0]),
714 b: FloatValue::new(nums[1]),
715 c: FloatValue::new(nums[2]),
716 d: FloatValue::new(nums[3]),
717 tx: FloatValue::new(nums[4]),
718 ty: FloatValue::new(nums[5]),
719 }))
720 }
721 "matrix3d" => {
722 let nums = get_numbers(transform_values, 16)?;
723 Ok(StyleTransform::Matrix3D(StyleTransformMatrix3D {
724 m11: FloatValue::new(nums[0]),
725 m12: FloatValue::new(nums[1]),
726 m13: FloatValue::new(nums[2]),
727 m14: FloatValue::new(nums[3]),
728 m21: FloatValue::new(nums[4]),
729 m22: FloatValue::new(nums[5]),
730 m23: FloatValue::new(nums[6]),
731 m24: FloatValue::new(nums[7]),
732 m31: FloatValue::new(nums[8]),
733 m32: FloatValue::new(nums[9]),
734 m33: FloatValue::new(nums[10]),
735 m34: FloatValue::new(nums[11]),
736 m41: FloatValue::new(nums[12]),
737 m42: FloatValue::new(nums[13]),
738 m43: FloatValue::new(nums[14]),
739 m44: FloatValue::new(nums[15]),
740 }))
741 }
742 "translate" => {
743 let components: Vec<_> = transform_values.split(',').collect();
744
745 if components.len() > 2 {
747 return Err(CssStyleTransformParseError::WrongNumberOfComponents {
748 expected: 2,
749 got: components.len(),
750 input: transform_values,
751 });
752 }
753
754 let x = parse_pixel_value(
755 components.first()
756 .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
757 expected: 2,
758 got: 0,
759 input: transform_values,
760 })?
761 .trim(),
762 )?;
763 let y = match components.get(1) {
764 Some(c) => parse_pixel_value(c.trim())?,
765 None => PixelValue::px(0.0),
766 };
767 Ok(StyleTransform::Translate(StyleTransformTranslate2D {
768 x,
769 y,
770 }))
771 }
772 "translate3d" => {
773 let components: Vec<_> = transform_values.split(',').collect();
774 let x = parse_pixel_value(
775 components.first()
776 .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
777 expected: 3,
778 got: 0,
779 input: transform_values,
780 })?
781 .trim(),
782 )?;
783 let y = parse_pixel_value(
784 components
785 .get(1)
786 .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
787 expected: 3,
788 got: 1,
789 input: transform_values,
790 })?
791 .trim(),
792 )?;
793 let z = parse_pixel_value(
794 components
795 .get(2)
796 .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
797 expected: 3,
798 got: 2,
799 input: transform_values,
800 })?
801 .trim(),
802 )?;
803 Ok(StyleTransform::Translate3D(StyleTransformTranslate3D {
804 x,
805 y,
806 z,
807 }))
808 }
809 "translateX" => Ok(StyleTransform::TranslateX(parse_pixel_value(
810 transform_values,
811 )?)),
812 "translateY" => Ok(StyleTransform::TranslateY(parse_pixel_value(
813 transform_values,
814 )?)),
815 "translateZ" => Ok(StyleTransform::TranslateZ(parse_pixel_value(
816 transform_values,
817 )?)),
818 "rotate" => Ok(StyleTransform::Rotate(parse_angle_value(transform_values)?)),
819 "rotate3d" => {
820 let parts: Vec<_> = transform_values.splitn(4, ',').collect();
821 if parts.len() != 4 {
822 return Err(CssStyleTransformParseError::WrongNumberOfComponents {
823 expected: 4,
824 got: parts.len(),
825 input: transform_values,
826 });
827 }
828 let x = parts[0].trim().parse::<f32>()?;
829 let y = parts[1].trim().parse::<f32>()?;
830 let z = parts[2].trim().parse::<f32>()?;
831 let angle = parse_angle_value(parts[3].trim())?;
832 Ok(StyleTransform::Rotate3D(StyleTransformRotate3D {
833 x: FloatValue::new(x),
834 y: FloatValue::new(y),
835 z: FloatValue::new(z),
836 angle,
837 }))
838 }
839 "rotateX" => Ok(StyleTransform::RotateX(parse_angle_value(
840 transform_values,
841 )?)),
842 "rotateY" => Ok(StyleTransform::RotateY(parse_angle_value(
843 transform_values,
844 )?)),
845 "rotateZ" => Ok(StyleTransform::RotateZ(parse_angle_value(
846 transform_values,
847 )?)),
848 "scale" => {
849 let parts: Vec<_> = transform_values.split(',').collect();
850 if parts.is_empty() || parts.len() > 2 {
851 return Err(CssStyleTransformParseError::WrongNumberOfComponents {
852 expected: 2,
853 got: parts.len(),
854 input: transform_values,
855 });
856 }
857 let x = parts[0].trim().parse::<f32>()?;
858 let y = if parts.len() == 2 {
859 parts[1].trim().parse::<f32>()?
860 } else {
861 x
862 };
863 Ok(StyleTransform::Scale(StyleTransformScale2D {
864 x: FloatValue::new(x),
865 y: FloatValue::new(y),
866 }))
867 }
868 "scale3d" => {
869 let nums = get_numbers(transform_values, 3)?;
870 Ok(StyleTransform::Scale3D(StyleTransformScale3D {
871 x: FloatValue::new(nums[0]),
872 y: FloatValue::new(nums[1]),
873 z: FloatValue::new(nums[2]),
874 }))
875 }
876 "scaleX" => Ok(StyleTransform::ScaleX(PercentageValue::new(
877 transform_values.trim().parse::<f32>()? * 100.0,
878 ))),
879 "scaleY" => Ok(StyleTransform::ScaleY(PercentageValue::new(
880 transform_values.trim().parse::<f32>()? * 100.0,
881 ))),
882 "scaleZ" => Ok(StyleTransform::ScaleZ(PercentageValue::new(
883 transform_values.trim().parse::<f32>()? * 100.0,
884 ))),
885 "skew" => {
886 let components: Vec<_> = transform_values.split(',').collect();
887 if components.is_empty() || components.len() > 2 {
888 return Err(CssStyleTransformParseError::WrongNumberOfComponents {
889 expected: 2,
890 got: components.len(),
891 input: transform_values,
892 });
893 }
894 let x = parse_angle_value(components[0].trim())?;
895 let y = match components.get(1) {
896 Some(c) => parse_angle_value(c.trim())?,
897 None => AngleValue::deg(0.0),
898 };
899 Ok(StyleTransform::Skew(StyleTransformSkew2D { x, y }))
900 }
901 "skewX" => Ok(StyleTransform::SkewX(parse_angle_value(transform_values)?)),
902 "skewY" => Ok(StyleTransform::SkewY(parse_angle_value(transform_values)?)),
903 "perspective" => Ok(StyleTransform::Perspective(parse_pixel_value(
904 transform_values,
905 )?)),
906 _ => unreachable!(),
907 }
908}
909
910#[cfg(feature = "parser")]
911pub fn parse_style_transform_origin(
915 input: &str,
916) -> Result<StyleTransformOrigin, CssStyleTransformOriginParseError<'_>> {
917 fn parse_position_component(
919 s: &str,
920 is_horizontal: bool,
921 ) -> Result<PixelValue, CssPixelValueParseError<'_>> {
922 match s.trim() {
923 "left" if is_horizontal => Ok(PixelValue::percent(0.0)),
924 "center" => Ok(PixelValue::percent(50.0)),
925 "right" if is_horizontal => Ok(PixelValue::percent(100.0)),
926 "top" if !is_horizontal => Ok(PixelValue::percent(0.0)),
927 "bottom" if !is_horizontal => Ok(PixelValue::percent(100.0)),
928 _ => parse_pixel_value(s),
929 }
930 }
931
932 let components: Vec<_> = input.split_whitespace().collect();
933 if components.len() != 2 {
934 return Err(CssStyleTransformOriginParseError::WrongNumberOfComponents {
935 expected: 2,
936 got: components.len(),
937 input,
938 });
939 }
940
941 let x = parse_position_component(components[0], true)?;
942 let y = parse_position_component(components[1], false)?;
943 Ok(StyleTransformOrigin { x, y })
944}
945
946#[cfg(feature = "parser")]
947pub fn parse_style_perspective_origin(
951 input: &str,
952) -> Result<StylePerspectiveOrigin, CssStylePerspectiveOriginParseError<'_>> {
953 let components: Vec<_> = input.split_whitespace().collect();
954 if components.len() != 2 {
955 return Err(
956 CssStylePerspectiveOriginParseError::WrongNumberOfComponents {
957 expected: 2,
958 got: components.len(),
959 input,
960 },
961 );
962 }
963 let x = parse_pixel_value(components[0])?;
964 let y = parse_pixel_value(components[1])?;
965 Ok(StylePerspectiveOrigin { x, y })
966}
967
968#[cfg(feature = "parser")]
969pub fn parse_style_backface_visibility(
973 input: &str,
974) -> Result<StyleBackfaceVisibility, CssBackfaceVisibilityParseError<'_>> {
975 match input.trim() {
976 "visible" => Ok(StyleBackfaceVisibility::Visible),
977 "hidden" => Ok(StyleBackfaceVisibility::Hidden),
978 _ => Err(CssBackfaceVisibilityParseError::InvalidValue(input)),
979 }
980}
981
982#[cfg(all(test, feature = "parser"))]
983mod tests {
984 #![allow(clippy::float_cmp)]
986 use super::*;
987
988 #[test]
989 fn test_parse_transform_vec() {
990 let result =
991 parse_style_transform_vec("translateX(10px) rotate(90deg) scale(0.5, 0.5)").unwrap();
992 assert_eq!(result.len(), 3);
993 assert!(matches!(
994 result.as_slice()[0],
995 StyleTransform::TranslateX(_)
996 ));
997 assert!(matches!(result.as_slice()[1], StyleTransform::Rotate(_)));
998 assert!(matches!(result.as_slice()[2], StyleTransform::Scale(_)));
999 }
1000
1001 #[test]
1002 fn test_parse_transform_functions() {
1003 assert_eq!(
1005 parse_style_transform("translateX(50%)").unwrap(),
1006 StyleTransform::TranslateX(PixelValue::percent(50.0))
1007 );
1008 let translate = parse_style_transform("translate(10px, -20px)").unwrap();
1009 if let StyleTransform::Translate(t) = translate {
1010 assert_eq!(t.x, PixelValue::px(10.0));
1011 assert_eq!(t.y, PixelValue::px(-20.0));
1012 } else {
1013 panic!("Expected Translate");
1014 }
1015
1016 assert_eq!(
1018 parse_style_transform("scaleY(1.2)").unwrap(),
1019 StyleTransform::ScaleY(PercentageValue::new(120.0))
1020 );
1021 let scale = parse_style_transform("scale(2, 0.5)").unwrap();
1022 if let StyleTransform::Scale(s) = scale {
1023 assert_eq!(s.x.get(), 2.0);
1024 assert_eq!(s.y.get(), 0.5);
1025 } else {
1026 panic!("Expected Scale");
1027 }
1028
1029 assert_eq!(
1031 parse_style_transform("rotate(0.25turn)").unwrap(),
1032 StyleTransform::Rotate(AngleValue::turn(0.25))
1033 );
1034
1035 assert_eq!(
1037 parse_style_transform("skewX(-10deg)").unwrap(),
1038 StyleTransform::SkewX(AngleValue::deg(-10.0))
1039 );
1040 let skew = parse_style_transform("skew(20deg, 30deg)").unwrap();
1041 if let StyleTransform::Skew(s) = skew {
1042 assert_eq!(s.x, AngleValue::deg(20.0));
1043 assert_eq!(s.y, AngleValue::deg(30.0));
1044 } else {
1045 panic!("Expected Skew");
1046 }
1047 }
1048
1049 #[test]
1050 fn test_parse_transform_origin() {
1051 let result = parse_style_transform_origin("50% 50%").unwrap();
1052 assert_eq!(result.x, PixelValue::percent(50.0));
1053 assert_eq!(result.y, PixelValue::percent(50.0));
1054
1055 let result = parse_style_transform_origin("left top").unwrap();
1056 assert_eq!(result.x, PixelValue::percent(0.0));
1057 assert_eq!(result.y, PixelValue::percent(0.0));
1058
1059 let result = parse_style_transform_origin("20px bottom").unwrap();
1060 assert_eq!(result.x, PixelValue::px(20.0));
1061 assert_eq!(result.y, PixelValue::percent(100.0));
1062 }
1063
1064 #[test]
1065 fn test_parse_backface_visibility() {
1066 assert_eq!(
1067 parse_style_backface_visibility("visible").unwrap(),
1068 StyleBackfaceVisibility::Visible
1069 );
1070 assert_eq!(
1071 parse_style_backface_visibility("hidden").unwrap(),
1072 StyleBackfaceVisibility::Hidden
1073 );
1074 assert!(parse_style_backface_visibility("none").is_err());
1075 }
1076
1077 #[test]
1078 fn test_parse_transform_errors() {
1079 assert!(parse_style_transform("translatex(10px)").is_err());
1081 assert!(parse_style_transform("translate(1, 2, 3)").is_err());
1083 let scale1 = parse_style_transform("scale(2)").unwrap();
1085 if let StyleTransform::Scale(s) = scale1 {
1086 assert_eq!(s.x.get(), 2.0);
1087 assert_eq!(s.y.get(), 2.0);
1088 } else {
1089 panic!("Expected Scale");
1090 }
1091 let translate1 = parse_style_transform("translate(10px)").unwrap();
1092 if let StyleTransform::Translate(t) = translate1 {
1093 assert_eq!(t.x, PixelValue::px(10.0));
1094 assert_eq!(t.y, PixelValue::px(0.0));
1095 } else {
1096 panic!("Expected Translate");
1097 }
1098 let skew1 = parse_style_transform("skew(20deg)").unwrap();
1099 if let StyleTransform::Skew(s) = skew1 {
1100 assert_eq!(s.x, AngleValue::deg(20.0));
1101 assert_eq!(s.y, AngleValue::deg(0.0));
1102 } else {
1103 panic!("Expected Skew");
1104 }
1105 let rot3d = parse_style_transform("rotate3d(1, 0, 0, 45deg)").unwrap();
1107 if let StyleTransform::Rotate3D(r) = rot3d {
1108 assert_eq!(r.x.get(), 1.0);
1109 assert_eq!(r.angle, AngleValue::deg(45.0));
1110 } else {
1111 panic!("Expected Rotate3D");
1112 }
1113 assert!(parse_style_transform("rotate(10px)").is_err());
1115 assert!(parse_style_transform("translateX(auto)").is_err());
1116 }
1117}
1118
1119#[cfg(all(test, feature = "parser"))]
1120#[allow(clippy::too_many_lines, clippy::float_cmp)]
1121mod autotest_generated {
1122 use super::*;
1126 use crate::props::basic::length::SizeMetric;
1127
1128 fn assert_encodable(pv: PixelValue) {
1135 assert!(pv.number.get().is_finite());
1136 }
1137
1138 fn all_roundtrippable_transforms() -> Vec<StyleTransform> {
1139 vec![
1140 StyleTransform::Matrix(StyleTransformMatrix2D::default()),
1141 StyleTransform::Matrix3D(StyleTransformMatrix3D::default()),
1142 StyleTransform::Translate(StyleTransformTranslate2D {
1143 x: PixelValue::px(10.0),
1144 y: PixelValue::px(-20.0),
1145 }),
1146 StyleTransform::Translate3D(StyleTransformTranslate3D {
1147 x: PixelValue::px(1.0),
1148 y: PixelValue::em(2.0),
1149 z: PixelValue::pt(-3.5),
1150 }),
1151 StyleTransform::TranslateX(PixelValue::percent(50.0)),
1152 StyleTransform::TranslateY(PixelValue::px(0.0)),
1153 StyleTransform::TranslateZ(PixelValue::rem(1.25)),
1154 StyleTransform::Rotate(AngleValue::deg(90.0)),
1155 StyleTransform::Rotate3D(StyleTransformRotate3D {
1156 x: FloatValue::new(1.0),
1157 y: FloatValue::new(0.0),
1158 z: FloatValue::new(0.0),
1159 angle: AngleValue::turn(0.25),
1160 }),
1161 StyleTransform::RotateX(AngleValue::rad(1.5)),
1162 StyleTransform::RotateY(AngleValue::grad(100.0)),
1163 StyleTransform::RotateZ(AngleValue::deg(-45.0)),
1164 StyleTransform::Scale(StyleTransformScale2D {
1165 x: FloatValue::new(2.0),
1166 y: FloatValue::new(0.5),
1167 }),
1168 StyleTransform::Scale3D(StyleTransformScale3D {
1169 x: FloatValue::new(1.0),
1170 y: FloatValue::new(-1.0),
1171 z: FloatValue::new(0.25),
1172 }),
1173 StyleTransform::Skew(StyleTransformSkew2D {
1174 x: AngleValue::deg(20.0),
1175 y: AngleValue::deg(30.0),
1176 }),
1177 StyleTransform::SkewX(AngleValue::deg(-10.0)),
1178 StyleTransform::SkewY(AngleValue::deg(10.0)),
1179 StyleTransform::Perspective(PixelValue::px(500.0)),
1180 ]
1181 }
1182
1183 #[test]
1188 fn transform_rejects_empty_and_whitespace_only_input() {
1189 for input in ["", " ", "\t\n", "\r\n\t "] {
1190 let err = parse_style_transform(input).unwrap_err();
1191 assert!(
1192 matches!(
1193 err,
1194 CssStyleTransformParseError::InvalidParenthesis(
1195 ParenthesisParseError::EmptyInput
1196 )
1197 ),
1198 "expected EmptyInput for {input:?}, got {err}"
1199 );
1200 }
1201 }
1202
1203 #[test]
1204 fn transform_rejects_garbage_without_panicking() {
1205 assert!(matches!(
1207 parse_style_transform("garbage").unwrap_err(),
1208 CssStyleTransformParseError::InvalidParenthesis(
1209 ParenthesisParseError::NoOpeningBraceFound
1210 )
1211 ));
1212 assert!(matches!(
1214 parse_style_transform("rotate(90deg").unwrap_err(),
1215 CssStyleTransformParseError::InvalidParenthesis(
1216 ParenthesisParseError::NoClosingBraceFound
1217 )
1218 ));
1219 assert!(matches!(
1222 parse_style_transform("translatex(10px)").unwrap_err(),
1223 CssStyleTransformParseError::InvalidParenthesis(
1224 ParenthesisParseError::StopWordNotFound("translatex")
1225 )
1226 ));
1227 assert!(matches!(
1228 parse_style_transform("ROTATE(90deg)").unwrap_err(),
1229 CssStyleTransformParseError::InvalidParenthesis(
1230 ParenthesisParseError::StopWordNotFound("ROTATE")
1231 )
1232 ));
1233 for input in [
1235 "((((",
1236 ")",
1237 "()",
1238 "(rotate)",
1239 "rotate",
1240 ";;;",
1241 "\0(\0)",
1242 "-1",
1243 "rotate(,,,,)",
1244 "matrix(,)",
1245 ] {
1246 assert!(
1247 parse_style_transform(input).is_err(),
1248 "expected Err for {input:?}"
1249 );
1250 }
1251 }
1252
1253 #[test]
1254 fn transform_never_hits_the_unreachable_arm_for_near_miss_stopwords() {
1255 for name in [
1259 "translat", "translateXX", "xtranslateX", "rotate3", "rotate3D", "scale4d", "skewZ",
1260 "perspectives", "matrix2d", "MATRIX", "", " rotate",
1261 ] {
1262 let input = alloc::format!("{name}(1)");
1263 let res = parse_style_transform(&input);
1264 let _ = res;
1267 }
1268 assert_eq!(
1270 parse_style_transform(" rotate(1) ").unwrap(),
1271 StyleTransform::Rotate(AngleValue::deg(1.0))
1272 );
1273 }
1274
1275 #[test]
1276 fn transform_handles_non_ascii_and_multibyte_input() {
1277 for input in [
1280 "\u{1F600}",
1281 "\u{1F600}(1)",
1282 "rotate(\u{1F600})",
1283 "rotate\u{0301}(1deg)",
1284 "\u{1F600}rotate(1deg)",
1285 "translateX(\u{1F600}px)",
1286 "translate(\u{1F600}, \u{1F600})",
1287 "matrix3d(\u{4F60}\u{597D})",
1288 "sk\u{0435}wX(10deg)", ] {
1290 assert!(
1291 parse_style_transform(input).is_err(),
1292 "expected Err for {input:?}"
1293 );
1294 }
1295 assert!(matches!(
1296 parse_style_transform("rotate(\u{1F600})").unwrap_err(),
1297 CssStyleTransformParseError::AngleValueParseError(
1298 CssAngleValueParseError::InvalidAngle("\u{1F600}")
1299 )
1300 ));
1301 }
1302
1303 #[test]
1304 fn transform_boundary_numbers_saturate_instead_of_panicking() {
1305 assert_eq!(
1307 parse_style_transform("translateX(-0)").unwrap(),
1308 StyleTransform::TranslateX(PixelValue::px(0.0))
1309 );
1310 assert_eq!(
1311 parse_style_transform("translateX(0)").unwrap(),
1312 StyleTransform::TranslateX(PixelValue::px(0.0))
1313 );
1314
1315 let StyleTransform::TranslateX(nan_px) = parse_style_transform("translateX(NaN)").unwrap()
1318 else {
1319 panic!("expected TranslateX");
1320 };
1321 assert_eq!(nan_px.number.number(), 0);
1322 assert_encodable(nan_px);
1323
1324 for input in ["translateX(inf)", "translateX(1e400)", "translateX(1e400px)"] {
1326 let StyleTransform::TranslateX(px) = parse_style_transform(input).unwrap() else {
1327 panic!("expected TranslateX for {input}");
1328 };
1329 assert_eq!(px.number.number(), isize::MAX, "{input}");
1330 assert_encodable(px);
1331 }
1332 let StyleTransform::TranslateX(neg) = parse_style_transform("translateX(-inf)").unwrap()
1333 else {
1334 panic!("expected TranslateX");
1335 };
1336 assert_eq!(neg.number.number(), isize::MIN);
1337 assert_encodable(neg);
1338
1339 for input in [
1341 "translateX(9223372036854775807px)",
1342 "translateX(-9223372036854775808px)",
1343 "translateX(1e-400px)",
1344 "translateX(0.0000000000001px)",
1345 ] {
1346 assert!(parse_style_transform(input).is_ok(), "{input}");
1347 }
1348
1349 assert_eq!(
1351 parse_style_transform("rotate(NaN)").unwrap(),
1352 StyleTransform::Rotate(AngleValue::deg(0.0))
1353 );
1354 let StyleTransform::Rotate(a) = parse_style_transform("rotate(infdeg)").unwrap() else {
1355 panic!("expected Rotate");
1356 };
1357 assert_eq!(a.number.number(), isize::MAX);
1358
1359 let StyleTransform::ScaleX(p) = parse_style_transform("scaleX(NaN)").unwrap() else {
1361 panic!("expected ScaleX");
1362 };
1363 assert_eq!(p, PercentageValue::new(0.0));
1364 assert!(parse_style_transform("scaleX(inf)").is_ok());
1365 assert!(parse_style_transform("scaleX(1e40)").is_ok());
1366 }
1367
1368 #[test]
1369 fn transform_component_counts_are_enforced() {
1370 assert!(matches!(
1372 parse_style_transform("matrix(1,2,3,4,5)").unwrap_err(),
1373 CssStyleTransformParseError::WrongNumberOfComponents {
1374 expected: 6,
1375 got: 5,
1376 ..
1377 }
1378 ));
1379 assert!(matches!(
1380 parse_style_transform("matrix(1,2,3,4,5,6,7)").unwrap_err(),
1381 CssStyleTransformParseError::WrongNumberOfComponents {
1382 expected: 6,
1383 got: 7,
1384 ..
1385 }
1386 ));
1387 assert!(matches!(
1389 parse_style_transform("matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0)").unwrap_err(),
1390 CssStyleTransformParseError::WrongNumberOfComponents {
1391 expected: 16,
1392 got: 15,
1393 ..
1394 }
1395 ));
1396 assert!(matches!(
1398 parse_style_transform("translate(1px, 2px, 3px)").unwrap_err(),
1399 CssStyleTransformParseError::WrongNumberOfComponents {
1400 expected: 2,
1401 got: 3,
1402 ..
1403 }
1404 ));
1405 assert!(matches!(
1407 parse_style_transform("scale(1, 2, 3)").unwrap_err(),
1408 CssStyleTransformParseError::WrongNumberOfComponents {
1409 expected: 2,
1410 got: 3,
1411 ..
1412 }
1413 ));
1414 assert!(matches!(
1416 parse_style_transform("skew(1deg, 2deg, 3deg)").unwrap_err(),
1417 CssStyleTransformParseError::WrongNumberOfComponents {
1418 expected: 2,
1419 got: 3,
1420 ..
1421 }
1422 ));
1423 assert!(matches!(
1426 parse_style_transform("rotate3d(1, 0, 0)").unwrap_err(),
1427 CssStyleTransformParseError::WrongNumberOfComponents {
1428 expected: 4,
1429 got: 3,
1430 ..
1431 }
1432 ));
1433 assert!(parse_style_transform("rotate3d(1, 0, 0, 45deg, 99)").is_err());
1434 assert!(matches!(
1436 parse_style_transform("translate3d(1px, 2px)").unwrap_err(),
1437 CssStyleTransformParseError::WrongNumberOfComponents {
1438 expected: 3,
1439 got: 2,
1440 ..
1441 }
1442 ));
1443 assert!(matches!(
1445 parse_style_transform("scale3d(1, 2)").unwrap_err(),
1446 CssStyleTransformParseError::WrongNumberOfComponents {
1447 expected: 3,
1448 got: 2,
1449 ..
1450 }
1451 ));
1452 }
1453
1454 #[test]
1455 fn transform_translate3d_silently_ignores_extra_components() {
1456 let parsed = parse_style_transform("translate3d(1px, 2px, 3px, 4px, 5px)").unwrap();
1461 assert_eq!(
1462 parsed,
1463 StyleTransform::Translate3D(StyleTransformTranslate3D {
1464 x: PixelValue::px(1.0),
1465 y: PixelValue::px(2.0),
1466 z: PixelValue::px(3.0),
1467 })
1468 );
1469 }
1470
1471 #[test]
1472 fn transform_ignores_junk_after_the_closing_paren() {
1473 assert_eq!(
1477 parse_style_transform("rotate(90deg)garbage").unwrap(),
1478 StyleTransform::Rotate(AngleValue::deg(90.0))
1479 );
1480 assert_eq!(
1481 parse_style_transform("rotate(90deg) ;drop table").unwrap(),
1482 StyleTransform::Rotate(AngleValue::deg(90.0))
1483 );
1484 assert!(parse_style_transform("rotate(90deg))").is_err());
1487 assert!(parse_style_transform("rotate(90deg) rotate(1deg)").is_err());
1488 }
1489
1490 #[test]
1491 fn transform_empty_argument_lists_are_rejected() {
1492 for input in [
1493 "matrix()",
1494 "matrix3d()",
1495 "translate()",
1496 "translate3d()",
1497 "translateX()",
1498 "translateY()",
1499 "translateZ()",
1500 "rotate()",
1501 "rotate3d()",
1502 "rotateX()",
1503 "rotateY()",
1504 "rotateZ()",
1505 "scale()",
1506 "scale3d()",
1507 "scaleX()",
1508 "scaleY()",
1509 "scaleZ()",
1510 "skew()",
1511 "skewX()",
1512 "skewY()",
1513 "perspective()",
1514 ] {
1515 assert!(
1516 parse_style_transform(input).is_err(),
1517 "expected Err for {input:?}"
1518 );
1519 }
1520 for input in [
1522 "translate(10px,)",
1523 "translate3d(1px,2px,)",
1524 "scale(2,)",
1525 "skew(10deg,)",
1526 "matrix(1,2,3,4,5,)",
1527 ] {
1528 assert!(
1529 parse_style_transform(input).is_err(),
1530 "expected Err for {input:?}"
1531 );
1532 }
1533 }
1534
1535 #[test]
1536 fn transform_wrong_unit_kinds_are_rejected() {
1537 assert!(parse_style_transform("rotate(10px)").is_err());
1539 assert!(parse_style_transform("rotateX(10px)").is_err());
1540 assert!(parse_style_transform("skewY(10px)").is_err());
1541 assert!(parse_style_transform("translateX(10deg)").is_err());
1542 assert!(parse_style_transform("perspective(10deg)").is_err());
1543 assert!(parse_style_transform("translateX(auto)").is_err());
1545 assert!(parse_style_transform("translateX(none)").is_err());
1546 assert!(parse_style_transform("scaleX(120%)").is_err());
1548 }
1549
1550 #[test]
1551 fn transform_extremely_long_input_does_not_hang_or_panic() {
1552 let huge = alloc::format!("translateX({}px)", "9".repeat(100_000));
1554 let StyleTransform::TranslateX(px) = parse_style_transform(&huge).unwrap() else {
1555 panic!("expected TranslateX");
1556 };
1557 assert_eq!(px.number.number(), isize::MAX);
1558
1559 let junk = alloc::format!("translateX({})", "a".repeat(100_000));
1561 assert!(parse_style_transform(&junk).is_err());
1562
1563 let long_name = alloc::format!("{}(1px)", "x".repeat(100_000));
1565 assert!(parse_style_transform(&long_name).is_err());
1566 }
1567
1568 #[test]
1569 fn transform_deeply_nested_parens_do_not_stack_overflow() {
1570 let open_only = "rotate(".repeat(10_000);
1573 assert!(matches!(
1574 parse_style_transform(&open_only).unwrap_err(),
1575 CssStyleTransformParseError::InvalidParenthesis(
1576 ParenthesisParseError::NoClosingBraceFound
1577 )
1578 ));
1579
1580 let braces = "(".repeat(10_000);
1581 assert!(matches!(
1582 parse_style_transform(&braces).unwrap_err(),
1583 CssStyleTransformParseError::InvalidParenthesis(
1584 ParenthesisParseError::StopWordNotFound("")
1585 )
1586 ));
1587
1588 let balanced = alloc::format!(
1589 "translateX({}1px{})",
1590 "translateX(".repeat(1_000),
1591 ")".repeat(1_000)
1592 );
1593 assert!(parse_style_transform(&balanced).is_err());
1594
1595 let closers = ")".repeat(10_000);
1596 assert!(parse_style_transform(&closers).is_err());
1597 }
1598
1599 #[test]
1600 fn transform_valid_minimal_positive_control() {
1601 assert_eq!(
1602 parse_style_transform("rotate(0deg)").unwrap(),
1603 StyleTransform::Rotate(AngleValue::deg(0.0))
1604 );
1605 }
1606
1607 #[test]
1612 fn transform_vec_accepts_empty_and_whitespace_only_input_as_an_empty_list() {
1613 for input in ["", " ", "\t\n", "\r \n \t"] {
1618 let v = parse_style_transform_vec(input).unwrap();
1619 assert_eq!(v.len(), 0, "{input:?}");
1620 }
1621 }
1622
1623 #[test]
1624 fn transform_vec_propagates_the_first_error() {
1625 assert!(parse_style_transform_vec("translateX(10px) garbage").is_err());
1626 assert!(parse_style_transform_vec("garbage translateX(10px)").is_err());
1627 assert!(parse_style_transform_vec("translateX(10px) rotate(10px)").is_err());
1628 assert!(parse_style_transform_vec("\u{1F600}").is_err());
1629 }
1630
1631 #[test]
1632 fn transform_vec_keeps_whitespace_inside_parens_together() {
1633 let v = parse_style_transform_vec("scale(2, 0.5) matrix(1, 0, 0, 1, 0, 0)").unwrap();
1635 assert_eq!(v.len(), 2);
1636 assert!(matches!(v.as_slice()[0], StyleTransform::Scale(_)));
1637 assert!(matches!(v.as_slice()[1], StyleTransform::Matrix(_)));
1638
1639 let v = parse_style_transform_vec(" translateX(1px)\t\trotate(2deg)\n ").unwrap();
1641 assert_eq!(v.len(), 2);
1642 }
1643
1644 #[test]
1645 fn transform_vec_extremely_long_list_does_not_hang() {
1646 let long = "translateX(1px) ".repeat(20_000);
1647 let v = parse_style_transform_vec(&long).unwrap();
1648 assert_eq!(v.len(), 20_000);
1649 for t in v.as_slice() {
1650 assert_eq!(*t, StyleTransform::TranslateX(PixelValue::px(1.0)));
1651 }
1652 }
1653
1654 #[test]
1655 fn transform_vec_unbalanced_parens_do_not_underflow_the_depth_counter() {
1656 let closers = ")".repeat(10_000);
1659 assert!(parse_style_transform_vec(&closers).is_err());
1660 let mixed = alloc::format!("{} {}", ")".repeat(5_000), "(".repeat(5_000));
1661 assert!(parse_style_transform_vec(&mixed).is_err());
1662 }
1663
1664 #[test]
1669 fn transform_origin_requires_exactly_two_components() {
1670 for (input, got) in [("", 0), ("50%", 1), ("left", 1), ("50% 50% 50%", 3)] {
1671 let err = parse_style_transform_origin(input).unwrap_err();
1672 assert!(
1673 matches!(
1674 err,
1675 CssStyleTransformOriginParseError::WrongNumberOfComponents {
1676 expected: 2,
1677 got: g,
1678 ..
1679 } if g == got
1680 ),
1681 "{input:?} -> {err}"
1682 );
1683 }
1684 assert!(matches!(
1686 parse_style_transform_origin(" \t\n ").unwrap_err(),
1687 CssStyleTransformOriginParseError::WrongNumberOfComponents { got: 0, .. }
1688 ));
1689 }
1690
1691 #[test]
1692 fn transform_origin_keywords_are_position_sensitive() {
1693 assert_eq!(
1694 parse_style_transform_origin("left top").unwrap(),
1695 StyleTransformOrigin {
1696 x: PixelValue::percent(0.0),
1697 y: PixelValue::percent(0.0),
1698 }
1699 );
1700 assert_eq!(
1701 parse_style_transform_origin("right bottom").unwrap(),
1702 StyleTransformOrigin {
1703 x: PixelValue::percent(100.0),
1704 y: PixelValue::percent(100.0),
1705 }
1706 );
1707 assert_eq!(
1708 parse_style_transform_origin("center center").unwrap(),
1709 StyleTransformOrigin::default()
1710 );
1711 assert!(parse_style_transform_origin("top left").is_err());
1716 assert!(parse_style_transform_origin("bottom right").is_err());
1717 assert!(parse_style_transform_origin("left left").is_err());
1718 assert!(parse_style_transform_origin("top top").is_err());
1719 }
1720
1721 #[test]
1722 fn transform_origin_garbage_and_unicode_do_not_panic() {
1723 for input in [
1724 "\u{1F600} \u{1F600}",
1725 "left \u{1F600}",
1726 "NaN NaN",
1727 "auto auto",
1728 "-- --",
1729 "10 20", "1px;2px", ] {
1732 let _ = parse_style_transform_origin(input);
1733 }
1734 assert_eq!(
1736 parse_style_transform_origin("10 20").unwrap(),
1737 StyleTransformOrigin {
1738 x: PixelValue::px(10.0),
1739 y: PixelValue::px(20.0),
1740 }
1741 );
1742 assert!(matches!(
1743 parse_style_transform_origin("\u{1F600} \u{1F600}").unwrap_err(),
1744 CssStyleTransformOriginParseError::PixelValueParseError(_)
1745 ));
1746 }
1747
1748 #[test]
1749 fn transform_origin_boundary_numbers_saturate() {
1750 let o = parse_style_transform_origin("inf% -inf%").unwrap();
1751 assert_eq!(o.x.number.number(), isize::MAX);
1752 assert_eq!(o.y.number.number(), isize::MIN);
1753 assert_encodable(o.x);
1754 assert_encodable(o.y);
1755
1756 let o = parse_style_transform_origin("NaNpx NaN%").unwrap();
1758 assert_eq!(o.x, PixelValue::px(0.0));
1759 assert_eq!(o.y, PixelValue::percent(0.0));
1760
1761 let o = parse_style_transform_origin("-0px 1e400px").unwrap();
1762 assert_eq!(o.x.number.number(), 0);
1763 assert_eq!(o.y.number.number(), isize::MAX);
1764 }
1765
1766 #[test]
1767 fn transform_origin_extremely_long_input_does_not_hang() {
1768 let many = "50% ".repeat(20_000);
1769 assert!(matches!(
1770 parse_style_transform_origin(&many).unwrap_err(),
1771 CssStyleTransformOriginParseError::WrongNumberOfComponents { got: 20_000, .. }
1772 ));
1773 let huge = alloc::format!("{}px 0px", "9".repeat(100_000));
1774 assert!(parse_style_transform_origin(&huge).is_ok());
1775 }
1776
1777 #[test]
1778 fn transform_origin_round_trips_through_its_css_repr() {
1779 for origin in [
1780 StyleTransformOrigin::default(),
1781 StyleTransformOrigin {
1782 x: PixelValue::px(20.0),
1783 y: PixelValue::percent(100.0),
1784 },
1785 StyleTransformOrigin {
1786 x: PixelValue::em(-1.5),
1787 y: PixelValue::rem(2.25),
1788 },
1789 StyleTransformOrigin {
1790 x: PixelValue::px(0.0),
1791 y: PixelValue::px(0.0),
1792 },
1793 ] {
1794 let css = origin.print_as_css_value();
1795 let reparsed = parse_style_transform_origin(&css)
1796 .unwrap_or_else(|e| panic!("{css:?} did not re-parse: {e}"));
1797 assert_eq!(reparsed, origin, "round-trip failed for {css:?}");
1798 }
1799 }
1800
1801 #[test]
1806 fn perspective_origin_requires_exactly_two_components() {
1807 for (input, got) in [("", 0), ("50%", 1), ("1px 2px 3px", 3)] {
1808 let err = parse_style_perspective_origin(input).unwrap_err();
1809 assert!(
1810 matches!(
1811 err,
1812 CssStylePerspectiveOriginParseError::WrongNumberOfComponents {
1813 expected: 2,
1814 got: g,
1815 ..
1816 } if g == got
1817 ),
1818 "{input:?} -> {err}"
1819 );
1820 }
1821 }
1822
1823 #[test]
1824 fn perspective_origin_does_not_accept_position_keywords() {
1825 assert!(parse_style_perspective_origin("left top").is_err());
1829 assert!(parse_style_perspective_origin("center center").is_err());
1830 assert!(matches!(
1831 parse_style_perspective_origin("center center").unwrap_err(),
1832 CssStylePerspectiveOriginParseError::PixelValueParseError(_)
1833 ));
1834 }
1835
1836 #[test]
1837 fn perspective_origin_garbage_boundary_and_unicode() {
1838 for input in ["\u{1F600} \u{1F600}", "auto auto", "-- --", "px px"] {
1839 assert!(
1840 parse_style_perspective_origin(input).is_err(),
1841 "expected Err for {input:?}"
1842 );
1843 }
1844 let o = parse_style_perspective_origin("inf -inf").unwrap();
1845 assert_eq!(o.x.number.number(), isize::MAX);
1846 assert_eq!(o.y.number.number(), isize::MIN);
1847 assert_encodable(o.x);
1848 assert_encodable(o.y);
1849
1850 let o = parse_style_perspective_origin("NaN -0").unwrap();
1851 assert_eq!(o.x, PixelValue::px(0.0));
1852 assert_eq!(o.y, PixelValue::px(0.0));
1853
1854 let huge = alloc::format!("{}px 0px", "9".repeat(100_000));
1855 assert!(parse_style_perspective_origin(&huge).is_ok());
1856 }
1857
1858 #[test]
1859 fn perspective_origin_round_trips_through_its_css_repr() {
1860 for origin in [
1861 StylePerspectiveOrigin::default(),
1862 StylePerspectiveOrigin {
1863 x: PixelValue::px(100.0),
1864 y: PixelValue::percent(50.0),
1865 },
1866 StylePerspectiveOrigin {
1867 x: PixelValue::pt(-3.5),
1868 y: PixelValue::cm(1.0),
1869 },
1870 ] {
1871 let css = origin.print_as_css_value();
1872 let reparsed = parse_style_perspective_origin(&css)
1873 .unwrap_or_else(|e| panic!("{css:?} did not re-parse: {e}"));
1874 assert_eq!(reparsed, origin, "round-trip failed for {css:?}");
1875 }
1876 }
1877
1878 #[test]
1883 fn backface_visibility_accepts_only_the_two_keywords() {
1884 assert_eq!(
1885 parse_style_backface_visibility("visible").unwrap(),
1886 StyleBackfaceVisibility::Visible
1887 );
1888 assert_eq!(
1889 parse_style_backface_visibility("hidden").unwrap(),
1890 StyleBackfaceVisibility::Hidden
1891 );
1892 assert_eq!(
1894 parse_style_backface_visibility(" \t visible \n ").unwrap(),
1895 StyleBackfaceVisibility::Visible
1896 );
1897 for input in [
1900 "",
1901 " ",
1902 "Visible",
1903 "HIDDEN",
1904 "visible hidden",
1905 "vis",
1906 "visiblee",
1907 "none",
1908 "0",
1909 "NaN",
1910 "\u{1F600}",
1911 "visible\u{200B}",
1912 "hidden;",
1913 ] {
1914 assert!(
1915 parse_style_backface_visibility(input).is_err(),
1916 "expected Err for {input:?}"
1917 );
1918 }
1919 }
1920
1921 #[test]
1922 fn backface_visibility_error_carries_the_untrimmed_input() {
1923 let err = parse_style_backface_visibility(" bogus ").unwrap_err();
1926 assert_eq!(err, CssBackfaceVisibilityParseError::InvalidValue(" bogus "));
1927 assert!(alloc::format!("{err}").contains(" bogus "));
1928 }
1929
1930 #[test]
1931 fn backface_visibility_extremely_long_input_does_not_hang() {
1932 let huge = "visible".repeat(100_000);
1933 assert!(parse_style_backface_visibility(&huge).is_err());
1934 }
1935
1936 #[test]
1937 fn backface_visibility_round_trips_through_its_css_repr() {
1938 for v in [
1939 StyleBackfaceVisibility::Visible,
1940 StyleBackfaceVisibility::Hidden,
1941 ] {
1942 let css = v.print_as_css_value();
1943 assert_eq!(parse_style_backface_visibility(&css).unwrap(), v);
1944 }
1945 assert_eq!(
1946 StyleBackfaceVisibility::default(),
1947 StyleBackfaceVisibility::Visible
1948 );
1949 }
1950
1951 #[test]
1956 fn transform_print_parse_round_trip() {
1957 for t in all_roundtrippable_transforms() {
1958 let css = t.print_as_css_value();
1959 let reparsed = parse_style_transform(&css)
1960 .unwrap_or_else(|e| panic!("{css:?} did not re-parse: {e}"));
1961 assert_eq!(reparsed, t, "round-trip failed for {css:?}");
1962 }
1963 }
1964
1965 #[test]
1966 fn transform_vec_print_parse_round_trip() {
1967 let v: StyleTransformVec = all_roundtrippable_transforms().into();
1968 let css = v.print_as_css_value();
1969 let reparsed = parse_style_transform_vec(&css)
1970 .unwrap_or_else(|e| panic!("{css:?} did not re-parse: {e}"));
1971 assert_eq!(reparsed.len(), v.len());
1972 assert_eq!(reparsed.as_slice(), v.as_slice());
1973 }
1974
1975 #[test]
1976 fn scale_axis_print_does_not_round_trip() {
1977 for t in [
1984 StyleTransform::ScaleX(PercentageValue::new(120.0)),
1985 StyleTransform::ScaleY(PercentageValue::new(120.0)),
1986 StyleTransform::ScaleZ(PercentageValue::new(120.0)),
1987 ] {
1988 let css = t.print_as_css_value();
1989 assert!(css.contains('%'), "{css:?}");
1990 assert!(
1991 parse_style_transform(&css).is_err(),
1992 "{css:?} unexpectedly re-parsed - the ScaleX round-trip bug may be fixed"
1993 );
1994 }
1995 assert_eq!(
1997 parse_style_transform("scaleX(1.2)").unwrap(),
1998 StyleTransform::ScaleX(PercentageValue::new(120.0))
1999 );
2000 }
2001
2002 #[test]
2007 fn transform_origin_interpolate_endpoints_are_exact() {
2008 let a = StyleTransformOrigin {
2009 x: PixelValue::px(10.0),
2010 y: PixelValue::percent(0.0),
2011 };
2012 let b = StyleTransformOrigin {
2013 x: PixelValue::px(30.0),
2014 y: PixelValue::percent(100.0),
2015 };
2016 assert_eq!(a.interpolate(&b, 0.0), a);
2017 assert_eq!(a.interpolate(&b, 1.0), b);
2018 assert_eq!(
2019 a.interpolate(&b, 0.5),
2020 StyleTransformOrigin {
2021 x: PixelValue::px(20.0),
2022 y: PixelValue::percent(50.0),
2023 }
2024 );
2025 for t in [-1.0, 0.0, 0.25, 1.0, 2.0, 1e30] {
2027 assert_eq!(a.interpolate(&a, t), a, "t = {t}");
2028 }
2029 }
2030
2031 #[test]
2032 fn transform_origin_interpolate_extrapolates_outside_zero_one() {
2033 let a = StyleTransformOrigin {
2034 x: PixelValue::px(10.0),
2035 y: PixelValue::px(10.0),
2036 };
2037 let b = StyleTransformOrigin {
2038 x: PixelValue::px(30.0),
2039 y: PixelValue::px(30.0),
2040 };
2041 assert_eq!(a.interpolate(&b, -1.0).x, PixelValue::px(-10.0));
2043 assert_eq!(a.interpolate(&b, 2.0).x, PixelValue::px(50.0));
2044 }
2045
2046 #[test]
2047 fn transform_origin_interpolate_with_nan_or_infinite_t_stays_defined() {
2048 let a = StyleTransformOrigin {
2049 x: PixelValue::px(10.0),
2050 y: PixelValue::percent(10.0),
2051 };
2052 let b = StyleTransformOrigin {
2053 x: PixelValue::px(30.0),
2054 y: PixelValue::percent(30.0),
2055 };
2056
2057 let nan = a.interpolate(&b, f32::NAN);
2059 assert_eq!(nan.x.number.number(), 0);
2060 assert_eq!(nan.y.number.number(), 0);
2061 assert_eq!(nan.x.metric, SizeMetric::Px);
2062 assert_eq!(nan.y.metric, SizeMetric::Percent);
2063 assert_encodable(nan.x);
2064 assert_encodable(nan.y);
2065
2066 let pos = a.interpolate(&b, f32::INFINITY);
2068 assert_eq!(pos.x.number.number(), isize::MAX);
2069 assert_encodable(pos.x);
2070 let neg = a.interpolate(&b, f32::NEG_INFINITY);
2071 assert_eq!(neg.x.number.number(), isize::MIN);
2072 assert_encodable(neg.x);
2073
2074 let degenerate = a.interpolate(&a, f32::INFINITY);
2076 assert_eq!(degenerate.x.number.number(), 0);
2077 assert_encodable(degenerate.x);
2078 }
2079
2080 #[test]
2081 fn transform_origin_interpolate_between_saturated_extremes_does_not_panic() {
2082 let a = StyleTransformOrigin {
2083 x: PixelValue::px(f32::MAX),
2084 y: PixelValue::px(f32::MIN),
2085 };
2086 let b = StyleTransformOrigin {
2087 x: PixelValue::px(f32::MIN),
2088 y: PixelValue::px(f32::MAX),
2089 };
2090 assert_eq!(a.x.number.number(), isize::MAX);
2092 assert_eq!(a.y.number.number(), isize::MIN);
2093
2094 for t in [
2095 -1e30,
2096 -1.0,
2097 0.0,
2098 0.5,
2099 1.0,
2100 1e30,
2101 f32::NAN,
2102 f32::INFINITY,
2103 f32::NEG_INFINITY,
2104 ] {
2105 let out = a.interpolate(&b, t);
2106 assert_encodable(out.x);
2107 assert_encodable(out.y);
2108 }
2109 }
2110
2111 #[test]
2112 fn transform_origin_interpolate_across_metrics_falls_back_to_px() {
2113 let a = StyleTransformOrigin {
2114 x: PixelValue::px(0.0),
2115 y: PixelValue::px(0.0),
2116 };
2117 let b = StyleTransformOrigin {
2118 x: PixelValue::percent(100.0),
2119 y: PixelValue::em(2.0),
2120 };
2121 for t in [0.0, 0.5, 1.0, f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
2122 let out = a.interpolate(&b, t);
2123 assert_eq!(out.x.metric, SizeMetric::Px, "t = {t}");
2124 assert_eq!(out.y.metric, SizeMetric::Px, "t = {t}");
2125 assert_encodable(out.x);
2126 assert_encodable(out.y);
2127 }
2128 }
2129
2130 #[test]
2131 fn perspective_origin_interpolate_matches_transform_origin_semantics() {
2132 let a = StylePerspectiveOrigin {
2133 x: PixelValue::px(10.0),
2134 y: PixelValue::percent(0.0),
2135 };
2136 let b = StylePerspectiveOrigin {
2137 x: PixelValue::px(30.0),
2138 y: PixelValue::percent(100.0),
2139 };
2140 assert_eq!(a.interpolate(&b, 0.0), a);
2141 assert_eq!(a.interpolate(&b, 1.0), b);
2142 assert_eq!(
2143 a.interpolate(&b, 0.5),
2144 StylePerspectiveOrigin {
2145 x: PixelValue::px(20.0),
2146 y: PixelValue::percent(50.0),
2147 }
2148 );
2149 let d = StylePerspectiveOrigin::default();
2151 assert_eq!(d.interpolate(&d, 0.5), d);
2152
2153 let nan = a.interpolate(&b, f32::NAN);
2155 assert_eq!(nan.x.number.number(), 0);
2156 assert_encodable(nan.x);
2157 let inf = a.interpolate(&b, f32::INFINITY);
2158 assert_eq!(inf.x.number.number(), isize::MAX);
2159 assert_encodable(inf.x);
2160
2161 let lo = StylePerspectiveOrigin {
2163 x: PixelValue::px(f32::MIN),
2164 y: PixelValue::px(f32::MIN),
2165 };
2166 let hi = StylePerspectiveOrigin {
2167 x: PixelValue::px(f32::MAX),
2168 y: PixelValue::px(f32::MAX),
2169 };
2170 for t in [0.0, 0.5, 1.0, f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
2171 let out = lo.interpolate(&hi, t);
2172 assert_encodable(out.x);
2173 assert_encodable(out.y);
2174 }
2175 }
2176
2177 fn transform_errors() -> Vec<CssStyleTransformParseError<'static>> {
2182 vec![
2183 CssStyleTransformParseError::InvalidTransform("rotate"),
2184 CssStyleTransformParseError::InvalidTransform(""),
2186 CssStyleTransformParseError::InvalidTransform("\u{1F600}"),
2187 CssStyleTransformParseError::InvalidParenthesis(ParenthesisParseError::EmptyInput),
2188 CssStyleTransformParseError::InvalidParenthesis(
2189 ParenthesisParseError::UnclosedBraces,
2190 ),
2191 CssStyleTransformParseError::InvalidParenthesis(
2192 ParenthesisParseError::NoOpeningBraceFound,
2193 ),
2194 CssStyleTransformParseError::InvalidParenthesis(
2195 ParenthesisParseError::NoClosingBraceFound,
2196 ),
2197 CssStyleTransformParseError::InvalidParenthesis(
2198 ParenthesisParseError::StopWordNotFound("nope"),
2199 ),
2200 CssStyleTransformParseError::WrongNumberOfComponents {
2201 expected: 6,
2202 got: 5,
2203 input: "1,2,3,4,5",
2204 },
2205 CssStyleTransformParseError::WrongNumberOfComponents {
2207 expected: usize::MAX,
2208 got: usize::MAX,
2209 input: "",
2210 },
2211 CssStyleTransformParseError::WrongNumberOfComponents {
2212 expected: 0,
2213 got: 0,
2214 input: "",
2215 },
2216 CssStyleTransformParseError::NumberParseError("x".parse::<f32>().unwrap_err()),
2217 CssStyleTransformParseError::NumberParseError("".parse::<f32>().unwrap_err()),
2218 CssStyleTransformParseError::PixelValueParseError(
2219 CssPixelValueParseError::EmptyString,
2220 ),
2221 CssStyleTransformParseError::PixelValueParseError(
2222 CssPixelValueParseError::InvalidPixelValue("auto"),
2223 ),
2224 CssStyleTransformParseError::AngleValueParseError(
2225 CssAngleValueParseError::EmptyString,
2226 ),
2227 CssStyleTransformParseError::AngleValueParseError(CssAngleValueParseError::InvalidAngle(
2228 "",
2229 )),
2230 CssStyleTransformParseError::PercentageValueParseError(
2231 PercentageParseError::NoPercentSign,
2232 ),
2233 CssStyleTransformParseError::PercentageValueParseError(
2234 PercentageParseError::InvalidUnit(AzString::from("")),
2235 ),
2236 ]
2237 }
2238
2239 #[test]
2240 fn transform_parse_error_round_trips_through_owned() {
2241 for err in transform_errors() {
2242 let owned = err.to_contained();
2243 let shared = owned.to_shared();
2244 assert_eq!(shared, err, "round-trip failed for {err}");
2245 assert_eq!(shared.to_contained(), owned);
2247 }
2248 }
2249
2250 #[test]
2251 fn transform_parse_error_round_trips_errors_from_the_real_parsers() {
2252 for input in [
2254 "",
2255 "garbage",
2256 "translatex(1px)",
2257 "matrix(1,2,3)",
2258 "rotate(10px)",
2259 "translateX(auto)",
2260 "scaleX(abc)",
2261 "translate3d(1px,2px)",
2262 ] {
2263 let err = parse_style_transform(input).unwrap_err();
2264 assert_eq!(err.to_contained().to_shared(), err, "for {input:?}");
2265 assert!(!alloc::format!("{err}").is_empty());
2266 assert_eq!(alloc::format!("{err:?}"), alloc::format!("{err}"));
2268 }
2269 }
2270
2271 #[test]
2272 fn transform_origin_parse_error_round_trips_through_owned() {
2273 let errs = [
2274 CssStyleTransformOriginParseError::WrongNumberOfComponents {
2275 expected: 2,
2276 got: 0,
2277 input: "",
2278 },
2279 CssStyleTransformOriginParseError::WrongNumberOfComponents {
2280 expected: usize::MAX,
2281 got: usize::MAX,
2282 input: "\u{1F600}",
2283 },
2284 CssStyleTransformOriginParseError::PixelValueParseError(
2285 CssPixelValueParseError::EmptyString,
2286 ),
2287 CssStyleTransformOriginParseError::PixelValueParseError(
2288 CssPixelValueParseError::InvalidPixelValue(""),
2289 ),
2290 ];
2291 for err in errs {
2292 let owned = err.to_contained();
2293 assert_eq!(owned.to_shared(), err, "round-trip failed for {err}");
2294 assert!(!alloc::format!("{err}").is_empty());
2295 }
2296 let err = parse_style_transform_origin("top left").unwrap_err();
2298 assert_eq!(err.to_contained().to_shared(), err);
2299 }
2300
2301 #[test]
2302 fn perspective_origin_parse_error_round_trips_through_owned() {
2303 let errs = [
2304 CssStylePerspectiveOriginParseError::WrongNumberOfComponents {
2305 expected: 2,
2306 got: 0,
2307 input: "",
2308 },
2309 CssStylePerspectiveOriginParseError::WrongNumberOfComponents {
2310 expected: 0,
2311 got: usize::MAX,
2312 input: "\u{1F600}\u{0301}",
2313 },
2314 CssStylePerspectiveOriginParseError::PixelValueParseError(
2315 CssPixelValueParseError::EmptyString,
2316 ),
2317 ];
2318 for err in errs {
2319 let owned = err.to_contained();
2320 assert_eq!(owned.to_shared(), err, "round-trip failed for {err}");
2321 assert!(!alloc::format!("{err}").is_empty());
2322 }
2323 let err = parse_style_perspective_origin("center center").unwrap_err();
2324 assert_eq!(err.to_contained().to_shared(), err);
2325 }
2326
2327 #[test]
2328 fn backface_visibility_parse_error_round_trips_through_owned() {
2329 for payload in ["", "none", "\u{1F600}", " bogus "] {
2330 let err = CssBackfaceVisibilityParseError::InvalidValue(payload);
2331 let owned = err.to_contained();
2332 assert_eq!(owned.to_shared(), err, "round-trip failed for {payload:?}");
2333 assert!(alloc::format!("{err}").contains(payload) || payload.is_empty());
2334 }
2335 let err = parse_style_backface_visibility("nope").unwrap_err();
2336 assert_eq!(err.to_contained().to_shared(), err);
2337 }
2338
2339 #[test]
2340 fn owned_errors_borrow_from_themselves_not_from_the_original_input() {
2341 let owned = {
2344 let input = String::from("translatex(1px)");
2345 parse_style_transform(&input).unwrap_err().to_contained()
2346 };
2347 assert_eq!(
2348 owned,
2349 CssStyleTransformParseErrorOwned::InvalidParenthesis(
2350 ParenthesisParseErrorOwned::StopWordNotFound(AzString::from("translatex"))
2351 )
2352 );
2353 assert!(matches!(
2355 owned.to_shared(),
2356 CssStyleTransformParseError::InvalidParenthesis(
2357 ParenthesisParseError::StopWordNotFound("translatex")
2358 )
2359 ));
2360 }
2361
2362 #[test]
2363 fn wrong_number_of_components_preserves_counts_across_the_owned_conversion() {
2364 let err = CssStyleTransformParseError::WrongNumberOfComponents {
2365 expected: usize::MAX,
2366 got: 0,
2367 input: "\u{1F600}",
2368 };
2369 let CssStyleTransformParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
2370 expected,
2371 got,
2372 input,
2373 }) = err.to_contained()
2374 else {
2375 panic!("expected WrongNumberOfComponents");
2376 };
2377 assert_eq!(expected, usize::MAX);
2378 assert_eq!(got, 0);
2379 assert_eq!(input.as_str(), "\u{1F600}");
2380 }
2381
2382 #[test]
2387 fn float_value_parse_helper_saturates_and_rejects_garbage() {
2388 assert_eq!(parse_float_value("1.5").unwrap(), FloatValue::new(1.5));
2389 assert_eq!(parse_float_value(" -0 ").unwrap(), FloatValue::new(0.0));
2390 assert_eq!(parse_float_value("inf").unwrap().number(), isize::MAX);
2391 assert_eq!(parse_float_value("-inf").unwrap().number(), isize::MIN);
2392 assert_eq!(parse_float_value("NaN").unwrap().number(), 0);
2393 assert!(parse_float_value("").is_err());
2394 assert!(parse_float_value("abc").is_err());
2395 assert!(parse_float_value("\u{1F600}").is_err());
2396 }
2397}