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