1use alloc::string::{String, ToString};
5use core::fmt;
6
7#[cfg(feature = "parser")]
8use crate::props::basic::{
9 error::{InvalidValueErr, InvalidValueErrOwned},
10 length::parse_percentage_value,
11};
12use crate::props::{
13 basic::length::{PercentageParseError, PercentageValue},
14 formatter::PrintAsCssValue,
15};
16
17#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[repr(C)]
22pub struct StyleOpacity {
23 pub inner: PercentageValue,
24}
25
26impl Default for StyleOpacity {
27 fn default() -> Self {
28 Self {
29 inner: PercentageValue::const_new(100),
30 }
31 }
32}
33
34impl PrintAsCssValue for StyleOpacity {
35 fn print_as_css_value(&self) -> String {
36 format!("{}", self.inner.normalized())
37 }
38}
39
40#[cfg(feature = "parser")]
41impl_percentage_value!(StyleOpacity);
42
43#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
47#[repr(C)]
48#[derive(Default)]
49pub enum StyleVisibility {
50 #[default]
51 Visible,
52 Hidden,
53 Collapse,
54}
55
56
57impl PrintAsCssValue for StyleVisibility {
58 fn print_as_css_value(&self) -> String {
59 String::from(match self {
60 Self::Visible => "visible",
61 Self::Hidden => "hidden",
62 Self::Collapse => "collapse",
63 })
64 }
65}
66
67#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
72#[repr(C)]
73#[derive(Default)]
74pub enum StyleMixBlendMode {
75 #[default]
76 Normal,
77 Multiply,
78 Screen,
79 Overlay,
80 Darken,
81 Lighten,
82 ColorDodge,
83 ColorBurn,
84 HardLight,
85 SoftLight,
86 Difference,
87 Exclusion,
88 Hue,
89 Saturation,
90 Color,
91 Luminosity,
92}
93
94
95impl fmt::Display for StyleMixBlendMode {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(
98 f,
99 "{}",
100 match self {
101 Self::Normal => "normal",
102 Self::Multiply => "multiply",
103 Self::Screen => "screen",
104 Self::Overlay => "overlay",
105 Self::Darken => "darken",
106 Self::Lighten => "lighten",
107 Self::ColorDodge => "color-dodge",
108 Self::ColorBurn => "color-burn",
109 Self::HardLight => "hard-light",
110 Self::SoftLight => "soft-light",
111 Self::Difference => "difference",
112 Self::Exclusion => "exclusion",
113 Self::Hue => "hue",
114 Self::Saturation => "saturation",
115 Self::Color => "color",
116 Self::Luminosity => "luminosity",
117 }
118 )
119 }
120}
121
122impl PrintAsCssValue for StyleMixBlendMode {
123 fn print_as_css_value(&self) -> String {
124 self.to_string()
125 }
126}
127
128#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
133#[repr(C)]
134#[derive(Default)]
135pub enum StyleCursor {
136 Alias,
137 AllScroll,
138 Cell,
139 ColResize,
140 ContextMenu,
141 Copy,
142 Crosshair,
143 #[default]
144 Default,
145 EResize,
146 EwResize,
147 Grab,
148 Grabbing,
149 Help,
150 Move,
151 NResize,
152 NsResize,
153 NeswResize,
154 NwseResize,
155 Pointer,
156 Progress,
157 RowResize,
158 SResize,
159 SeResize,
160 Text,
161 Unset,
162 VerticalText,
163 WResize,
164 Wait,
165 ZoomIn,
166 ZoomOut,
167}
168
169
170impl PrintAsCssValue for StyleCursor {
171 fn print_as_css_value(&self) -> String {
172 String::from(match self {
173 Self::Alias => "alias",
174 Self::AllScroll => "all-scroll",
175 Self::Cell => "cell",
176 Self::ColResize => "col-resize",
177 Self::ContextMenu => "context-menu",
178 Self::Copy => "copy",
179 Self::Crosshair => "crosshair",
180 Self::Default => "default",
181 Self::EResize => "e-resize",
182 Self::EwResize => "ew-resize",
183 Self::Grab => "grab",
184 Self::Grabbing => "grabbing",
185 Self::Help => "help",
186 Self::Move => "move",
187 Self::NResize => "n-resize",
188 Self::NsResize => "ns-resize",
189 Self::NeswResize => "nesw-resize",
190 Self::NwseResize => "nwse-resize",
191 Self::Pointer => "pointer",
192 Self::Progress => "progress",
193 Self::RowResize => "row-resize",
194 Self::SResize => "s-resize",
195 Self::SeResize => "se-resize",
196 Self::Text => "text",
197 Self::Unset => "unset",
198 Self::VerticalText => "vertical-text",
199 Self::WResize => "w-resize",
200 Self::Wait => "wait",
201 Self::ZoomIn => "zoom-in",
202 Self::ZoomOut => "zoom-out",
203 })
204 }
205}
206
207#[cfg(feature = "parser")]
210pub mod parsers {
211 #[allow(clippy::wildcard_imports)] use super::*;
213 use crate::corety::AzString;
214 use crate::props::basic::error::{InvalidValueErr, InvalidValueErrOwned};
215
216 #[derive(Clone, PartialEq, Eq)]
219 pub enum OpacityParseError<'a> {
220 ParsePercentage(PercentageParseError, &'a str),
221 OutOfRange(&'a str),
222 }
223 impl_debug_as_display!(OpacityParseError<'a>);
224 impl_display! { OpacityParseError<'a>, {
225 ParsePercentage(e, s) => format!("Invalid opacity value \"{}\": {}", s, e),
226 OutOfRange(s) => format!("Invalid opacity value \"{}\": must be between 0 and 1", s),
227 }}
228
229 #[derive(Debug, Clone, PartialEq, Eq)]
231 #[repr(C)]
232 pub struct PercentageParseErrorWithInput {
233 pub error: PercentageParseError,
234 pub input: AzString,
235 }
236
237 #[derive(Debug, Clone, PartialEq, Eq)]
238 #[repr(C, u8)]
239 pub enum OpacityParseErrorOwned {
240 ParsePercentage(PercentageParseErrorWithInput),
241 OutOfRange(AzString),
242 }
243
244 impl OpacityParseError<'_> {
245 #[must_use] pub fn to_contained(&self) -> OpacityParseErrorOwned {
246 match self {
247 Self::ParsePercentage(err, s) => {
248 OpacityParseErrorOwned::ParsePercentage(PercentageParseErrorWithInput { error: err.clone(), input: (*s).to_string().into() })
249 }
250 Self::OutOfRange(s) => OpacityParseErrorOwned::OutOfRange((*s).to_string().into()),
251 }
252 }
253 }
254
255 impl OpacityParseErrorOwned {
256 #[must_use] pub fn to_shared(&self) -> OpacityParseError<'_> {
257 match self {
258 Self::ParsePercentage(e) => {
259 OpacityParseError::ParsePercentage(e.error.clone(), e.input.as_str())
260 }
261 Self::OutOfRange(s) => OpacityParseError::OutOfRange(s.as_str()),
262 }
263 }
264 }
265
266 pub fn parse_style_opacity(input: &str) -> Result<StyleOpacity, OpacityParseError<'_>> {
270 let val = parse_percentage_value(input)
271 .map_err(|e| OpacityParseError::ParsePercentage(e, input))?;
272
273 let normalized = val.normalized();
274 if !(0.0..=1.0).contains(&normalized) {
275 return Err(OpacityParseError::OutOfRange(input));
276 }
277
278 Ok(StyleOpacity { inner: val })
279 }
280
281 #[derive(Clone, PartialEq, Eq)]
284 pub enum StyleVisibilityParseError<'a> {
285 InvalidValue(InvalidValueErr<'a>),
286 }
287 impl_debug_as_display!(StyleVisibilityParseError<'a>);
288 impl_display! { StyleVisibilityParseError<'a>, {
289 InvalidValue(e) => format!("Invalid visibility value: \"{}\"", e.0),
290 }}
291 impl_from!(InvalidValueErr<'a>, StyleVisibilityParseError::InvalidValue);
292
293 #[derive(Debug, Clone, PartialEq, Eq)]
294 #[repr(C, u8)]
295 pub enum StyleVisibilityParseErrorOwned {
296 InvalidValue(InvalidValueErrOwned),
297 }
298
299 impl StyleVisibilityParseError<'_> {
300 #[must_use] pub fn to_contained(&self) -> StyleVisibilityParseErrorOwned {
301 match self {
302 Self::InvalidValue(e) => {
303 StyleVisibilityParseErrorOwned::InvalidValue(e.to_contained())
304 }
305 }
306 }
307 }
308
309 impl StyleVisibilityParseErrorOwned {
310 #[must_use] pub fn to_shared(&self) -> StyleVisibilityParseError<'_> {
311 match self {
312 Self::InvalidValue(e) => StyleVisibilityParseError::InvalidValue(e.to_shared()),
313 }
314 }
315 }
316
317 pub fn parse_style_visibility(
321 input: &str,
322 ) -> Result<StyleVisibility, StyleVisibilityParseError<'_>> {
323 let input = input.trim();
324 match input {
325 "visible" => Ok(StyleVisibility::Visible),
326 "hidden" => Ok(StyleVisibility::Hidden),
327 "collapse" => Ok(StyleVisibility::Collapse),
328 _ => Err(InvalidValueErr(input).into()),
329 }
330 }
331
332 #[derive(Clone, PartialEq, Eq)]
335 pub enum MixBlendModeParseError<'a> {
336 InvalidValue(InvalidValueErr<'a>),
337 }
338 impl_debug_as_display!(MixBlendModeParseError<'a>);
339 impl_display! { MixBlendModeParseError<'a>, {
340 InvalidValue(e) => format!("Invalid mix-blend-mode value: \"{}\"", e.0),
341 }}
342 impl_from!(InvalidValueErr<'a>, MixBlendModeParseError::InvalidValue);
343
344 #[derive(Debug, Clone, PartialEq, Eq)]
345 #[repr(C, u8)]
346 pub enum MixBlendModeParseErrorOwned {
347 InvalidValue(InvalidValueErrOwned),
348 }
349
350 impl MixBlendModeParseError<'_> {
351 #[must_use] pub fn to_contained(&self) -> MixBlendModeParseErrorOwned {
352 match self {
353 Self::InvalidValue(e) => {
354 MixBlendModeParseErrorOwned::InvalidValue(e.to_contained())
355 }
356 }
357 }
358 }
359
360 impl MixBlendModeParseErrorOwned {
361 #[must_use] pub fn to_shared(&self) -> MixBlendModeParseError<'_> {
362 match self {
363 Self::InvalidValue(e) => MixBlendModeParseError::InvalidValue(e.to_shared()),
364 }
365 }
366 }
367
368 pub fn parse_style_mix_blend_mode(
372 input: &str,
373 ) -> Result<StyleMixBlendMode, MixBlendModeParseError<'_>> {
374 let input = input.trim();
375 match input {
376 "normal" => Ok(StyleMixBlendMode::Normal),
377 "multiply" => Ok(StyleMixBlendMode::Multiply),
378 "screen" => Ok(StyleMixBlendMode::Screen),
379 "overlay" => Ok(StyleMixBlendMode::Overlay),
380 "darken" => Ok(StyleMixBlendMode::Darken),
381 "lighten" => Ok(StyleMixBlendMode::Lighten),
382 "color-dodge" => Ok(StyleMixBlendMode::ColorDodge),
383 "color-burn" => Ok(StyleMixBlendMode::ColorBurn),
384 "hard-light" => Ok(StyleMixBlendMode::HardLight),
385 "soft-light" => Ok(StyleMixBlendMode::SoftLight),
386 "difference" => Ok(StyleMixBlendMode::Difference),
387 "exclusion" => Ok(StyleMixBlendMode::Exclusion),
388 "hue" => Ok(StyleMixBlendMode::Hue),
389 "saturation" => Ok(StyleMixBlendMode::Saturation),
390 "color" => Ok(StyleMixBlendMode::Color),
391 "luminosity" => Ok(StyleMixBlendMode::Luminosity),
392 _ => Err(InvalidValueErr(input).into()),
393 }
394 }
395
396 #[derive(Clone, PartialEq, Eq)]
399 pub enum CursorParseError<'a> {
400 InvalidValue(InvalidValueErr<'a>),
401 }
402 impl_debug_as_display!(CursorParseError<'a>);
403 impl_display! { CursorParseError<'a>, {
404 InvalidValue(e) => format!("Invalid cursor value: \"{}\"", e.0),
405 }}
406 impl_from!(InvalidValueErr<'a>, CursorParseError::InvalidValue);
407
408 #[derive(Debug, Clone, PartialEq, Eq)]
409 #[repr(C, u8)]
410 pub enum CursorParseErrorOwned {
411 InvalidValue(InvalidValueErrOwned),
412 }
413
414 impl CursorParseError<'_> {
415 #[must_use] pub fn to_contained(&self) -> CursorParseErrorOwned {
416 match self {
417 Self::InvalidValue(e) => CursorParseErrorOwned::InvalidValue(e.to_contained()),
418 }
419 }
420 }
421
422 impl CursorParseErrorOwned {
423 #[must_use] pub fn to_shared(&self) -> CursorParseError<'_> {
424 match self {
425 Self::InvalidValue(e) => CursorParseError::InvalidValue(e.to_shared()),
426 }
427 }
428 }
429
430 pub fn parse_style_cursor(input: &str) -> Result<StyleCursor, CursorParseError<'_>> {
434 let input = input.trim();
435 match input {
436 "alias" => Ok(StyleCursor::Alias),
437 "all-scroll" => Ok(StyleCursor::AllScroll),
438 "cell" => Ok(StyleCursor::Cell),
439 "col-resize" => Ok(StyleCursor::ColResize),
440 "context-menu" => Ok(StyleCursor::ContextMenu),
441 "copy" => Ok(StyleCursor::Copy),
442 "crosshair" => Ok(StyleCursor::Crosshair),
443 "default" => Ok(StyleCursor::Default),
444 "e-resize" => Ok(StyleCursor::EResize),
445 "ew-resize" => Ok(StyleCursor::EwResize),
446 "grab" => Ok(StyleCursor::Grab),
447 "grabbing" => Ok(StyleCursor::Grabbing),
448 "help" => Ok(StyleCursor::Help),
449 "move" => Ok(StyleCursor::Move),
450 "n-resize" => Ok(StyleCursor::NResize),
451 "ns-resize" => Ok(StyleCursor::NsResize),
452 "nesw-resize" => Ok(StyleCursor::NeswResize),
453 "nwse-resize" => Ok(StyleCursor::NwseResize),
454 "pointer" => Ok(StyleCursor::Pointer),
455 "progress" => Ok(StyleCursor::Progress),
456 "row-resize" => Ok(StyleCursor::RowResize),
457 "s-resize" => Ok(StyleCursor::SResize),
458 "se-resize" => Ok(StyleCursor::SeResize),
459 "text" => Ok(StyleCursor::Text),
460 "unset" => Ok(StyleCursor::Unset),
461 "vertical-text" => Ok(StyleCursor::VerticalText),
462 "w-resize" => Ok(StyleCursor::WResize),
463 "wait" => Ok(StyleCursor::Wait),
464 "zoom-in" => Ok(StyleCursor::ZoomIn),
465 "zoom-out" => Ok(StyleCursor::ZoomOut),
466 _ => Err(InvalidValueErr(input).into()),
467 }
468 }
469}
470
471#[cfg(feature = "parser")]
472pub use self::parsers::*;
473
474#[cfg(all(test, feature = "parser"))]
475mod tests {
476 #![allow(clippy::float_cmp)]
478 use super::*;
479
480 #[test]
481 fn test_parse_opacity() {
482 assert_eq!(parse_style_opacity("0.5").unwrap().inner.normalized(), 0.5);
483 assert_eq!(parse_style_opacity("1").unwrap().inner.normalized(), 1.0);
484 assert_eq!(parse_style_opacity("50%").unwrap().inner.normalized(), 0.5);
485 assert_eq!(parse_style_opacity("0").unwrap().inner.normalized(), 0.0);
486 assert_eq!(
487 parse_style_opacity(" 75% ").unwrap().inner.normalized(),
488 0.75
489 );
490 assert!(parse_style_opacity("1.1").is_err());
491 assert!(parse_style_opacity("-0.1").is_err());
492 assert!(parse_style_opacity("auto").is_err());
493 }
494
495 #[test]
496 fn test_parse_mix_blend_mode() {
497 assert_eq!(
498 parse_style_mix_blend_mode("multiply").unwrap(),
499 StyleMixBlendMode::Multiply
500 );
501 assert_eq!(
502 parse_style_mix_blend_mode("screen").unwrap(),
503 StyleMixBlendMode::Screen
504 );
505 assert_eq!(
506 parse_style_mix_blend_mode("color-dodge").unwrap(),
507 StyleMixBlendMode::ColorDodge
508 );
509 assert!(parse_style_mix_blend_mode("mix").is_err());
510 }
511
512 #[test]
513 fn test_parse_visibility() {
514 assert_eq!(
515 parse_style_visibility("visible").unwrap(),
516 StyleVisibility::Visible
517 );
518 assert_eq!(
519 parse_style_visibility("hidden").unwrap(),
520 StyleVisibility::Hidden
521 );
522 assert_eq!(
523 parse_style_visibility("collapse").unwrap(),
524 StyleVisibility::Collapse
525 );
526 assert_eq!(
527 parse_style_visibility(" visible ").unwrap(),
528 StyleVisibility::Visible
529 );
530 assert!(parse_style_visibility("none").is_err());
531 assert!(parse_style_visibility("show").is_err());
532 }
533
534 #[test]
535 fn test_parse_cursor() {
536 assert_eq!(parse_style_cursor("pointer").unwrap(), StyleCursor::Pointer);
537 assert_eq!(parse_style_cursor("wait").unwrap(), StyleCursor::Wait);
538 assert_eq!(
539 parse_style_cursor("col-resize").unwrap(),
540 StyleCursor::ColResize
541 );
542 assert_eq!(parse_style_cursor(" text ").unwrap(), StyleCursor::Text);
543 assert!(parse_style_cursor("hand").is_err()); }
545
546 #[test]
547 fn test_parse_object_fit() {
548 assert_eq!(parse_style_object_fit("fill").unwrap(), StyleObjectFit::Fill);
549 assert_eq!(parse_style_object_fit("contain").unwrap(), StyleObjectFit::Contain);
550 assert_eq!(parse_style_object_fit("cover").unwrap(), StyleObjectFit::Cover);
551 assert_eq!(parse_style_object_fit("none").unwrap(), StyleObjectFit::None);
552 assert_eq!(parse_style_object_fit("scale-down").unwrap(), StyleObjectFit::ScaleDown);
553 assert_eq!(parse_style_object_fit(" cover ").unwrap(), StyleObjectFit::Cover);
554 assert!(parse_style_object_fit("stretch").is_err());
555 assert!(parse_style_object_fit("").is_err());
556 }
557
558 #[test]
559 fn test_parse_text_orientation() {
560 assert_eq!(parse_style_text_orientation("mixed").unwrap(), StyleTextOrientation::Mixed);
561 assert_eq!(parse_style_text_orientation("upright").unwrap(), StyleTextOrientation::Upright);
562 assert_eq!(parse_style_text_orientation("sideways").unwrap(), StyleTextOrientation::Sideways);
563 assert_eq!(parse_style_text_orientation(" mixed ").unwrap(), StyleTextOrientation::Mixed);
564 assert!(parse_style_text_orientation("vertical").is_err());
565 }
566
567 #[test]
568 fn test_parse_object_position() {
569 use crate::props::style::background::{BackgroundPositionHorizontal, BackgroundPositionVertical};
570 let centered = parse_style_object_position("center").unwrap();
571 assert_eq!(centered, parse_style_object_position("center center").unwrap());
572
573 let lt = parse_style_object_position("left top").unwrap();
574 assert_eq!(lt.horizontal, BackgroundPositionHorizontal::Left);
575 assert_eq!(lt.vertical, BackgroundPositionVertical::Top);
576
577 let rb = parse_style_object_position("right bottom").unwrap();
578 assert_eq!(rb.horizontal, BackgroundPositionHorizontal::Right);
579 assert_eq!(rb.vertical, BackgroundPositionVertical::Bottom);
580
581 assert!(parse_style_object_position("left top center").is_err());
582 assert!(parse_style_object_position("invalid").is_err());
583 }
584
585 #[test]
586 fn test_parse_aspect_ratio() {
587 assert_eq!(parse_style_aspect_ratio("auto").unwrap(), StyleAspectRatio::Auto);
588 assert_eq!(
589 parse_style_aspect_ratio("16 / 9").unwrap(),
590 StyleAspectRatio::Ratio(AspectRatioValue { width: 16000, height: 9000 })
591 );
592 assert_eq!(
593 parse_style_aspect_ratio("16/9").unwrap(),
594 StyleAspectRatio::Ratio(AspectRatioValue { width: 16000, height: 9000 })
595 );
596 assert_eq!(
597 parse_style_aspect_ratio("1.5").unwrap(),
598 StyleAspectRatio::Ratio(AspectRatioValue { width: 1500, height: 1000 })
599 );
600 assert_eq!(
601 parse_style_aspect_ratio(" 4 / 3 ").unwrap(),
602 StyleAspectRatio::Ratio(AspectRatioValue { width: 4000, height: 3000 })
603 );
604 assert!(parse_style_aspect_ratio("0 / 1").is_err());
605 assert!(parse_style_aspect_ratio("1 / 0").is_err());
606 assert!(parse_style_aspect_ratio("-1 / 1").is_err());
607 assert!(parse_style_aspect_ratio("abc").is_err());
608 }
609}
610
611#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
616#[repr(C)]
617#[derive(Default)]
618pub enum StyleObjectFit {
619 #[default]
620 Fill,
621 Contain,
622 Cover,
623 None,
624 ScaleDown,
625}
626
627
628crate::impl_option!(StyleObjectFit, OptionStyleObjectFit, [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
629
630impl PrintAsCssValue for StyleObjectFit {
631 fn print_as_css_value(&self) -> String {
632 String::from(match self {
633 Self::Fill => "fill",
634 Self::Contain => "contain",
635 Self::Cover => "cover",
636 Self::None => "none",
637 Self::ScaleDown => "scale-down",
638 })
639 }
640}
641
642#[cfg(feature = "parser")]
643#[derive(Clone, PartialEq, Eq)]
644pub enum StyleObjectFitParseError<'a> {
645 InvalidValue(&'a str),
646}
647
648#[cfg(feature = "parser")]
649crate::impl_debug_as_display!(StyleObjectFitParseError<'a>);
650
651#[cfg(feature = "parser")]
652crate::impl_display! { StyleObjectFitParseError<'a>, {
653 InvalidValue(val) => format!("Invalid object-fit value: \"{}\"", val),
654}}
655
656#[cfg(feature = "parser")]
657#[derive(Debug, Clone, PartialEq, Eq)]
658#[repr(C, u8)]
659pub enum StyleObjectFitParseErrorOwned {
660 InvalidValue(crate::AzString),
661}
662
663#[cfg(feature = "parser")]
664impl StyleObjectFitParseError<'_> {
665 #[must_use] pub fn to_contained(&self) -> StyleObjectFitParseErrorOwned {
666 match self {
667 Self::InvalidValue(s) => StyleObjectFitParseErrorOwned::InvalidValue((*s).to_string().into()),
668 }
669 }
670}
671
672#[cfg(feature = "parser")]
673impl StyleObjectFitParseErrorOwned {
674 #[must_use] pub fn to_shared(&self) -> StyleObjectFitParseError<'_> {
675 match self {
676 Self::InvalidValue(s) => StyleObjectFitParseError::InvalidValue(s.as_str()),
677 }
678 }
679}
680
681#[cfg(feature = "parser")]
682pub fn parse_style_object_fit(
686 input: &str,
687) -> Result<StyleObjectFit, StyleObjectFitParseError<'_>> {
688 let input = input.trim();
689 match input {
690 "fill" => Ok(StyleObjectFit::Fill),
691 "contain" => Ok(StyleObjectFit::Contain),
692 "cover" => Ok(StyleObjectFit::Cover),
693 "none" => Ok(StyleObjectFit::None),
694 "scale-down" => Ok(StyleObjectFit::ScaleDown),
695 _ => Err(StyleObjectFitParseError::InvalidValue(input)),
696 }
697}
698
699#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
704#[repr(C)]
705#[derive(Default)]
706pub enum StyleTextOrientation {
707 #[default]
708 Mixed,
709 Upright,
710 Sideways,
711}
712
713
714crate::impl_option!(StyleTextOrientation, OptionStyleTextOrientation, [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
715
716impl PrintAsCssValue for StyleTextOrientation {
717 fn print_as_css_value(&self) -> String {
718 String::from(match self {
719 Self::Mixed => "mixed",
720 Self::Upright => "upright",
721 Self::Sideways => "sideways",
722 })
723 }
724}
725
726#[cfg(feature = "parser")]
727#[derive(Clone, PartialEq, Eq)]
728pub enum StyleTextOrientationParseError<'a> {
729 InvalidValue(&'a str),
730}
731
732#[cfg(feature = "parser")]
733crate::impl_debug_as_display!(StyleTextOrientationParseError<'a>);
734
735#[cfg(feature = "parser")]
736crate::impl_display! { StyleTextOrientationParseError<'a>, {
737 InvalidValue(val) => format!("Invalid text-orientation value: \"{}\"", val),
738}}
739
740#[cfg(feature = "parser")]
741#[derive(Debug, Clone, PartialEq, Eq)]
742#[repr(C, u8)]
743pub enum StyleTextOrientationParseErrorOwned {
744 InvalidValue(crate::AzString),
745}
746
747#[cfg(feature = "parser")]
748impl StyleTextOrientationParseError<'_> {
749 #[must_use] pub fn to_contained(&self) -> StyleTextOrientationParseErrorOwned {
750 match self {
751 Self::InvalidValue(s) => StyleTextOrientationParseErrorOwned::InvalidValue((*s).to_string().into()),
752 }
753 }
754}
755
756#[cfg(feature = "parser")]
757impl StyleTextOrientationParseErrorOwned {
758 #[must_use] pub fn to_shared(&self) -> StyleTextOrientationParseError<'_> {
759 match self {
760 Self::InvalidValue(s) => StyleTextOrientationParseError::InvalidValue(s.as_str()),
761 }
762 }
763}
764
765#[cfg(feature = "parser")]
766pub fn parse_style_text_orientation(
770 input: &str,
771) -> Result<StyleTextOrientation, StyleTextOrientationParseError<'_>> {
772 let input = input.trim();
773 match input {
774 "mixed" => Ok(StyleTextOrientation::Mixed),
775 "upright" => Ok(StyleTextOrientation::Upright),
776 "sideways" => Ok(StyleTextOrientation::Sideways),
777 _ => Err(StyleTextOrientationParseError::InvalidValue(input)),
778 }
779}
780
781#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
786#[repr(C)]
787pub struct StyleObjectPosition {
788 pub horizontal: crate::props::style::background::BackgroundPositionHorizontal,
789 pub vertical: crate::props::style::background::BackgroundPositionVertical,
790}
791
792impl Default for StyleObjectPosition {
793 fn default() -> Self {
794 use crate::props::basic::pixel::PixelValue;
795 Self {
796 horizontal: crate::props::style::background::BackgroundPositionHorizontal::Exact(
797 PixelValue::percent(50.0),
798 ),
799 vertical: crate::props::style::background::BackgroundPositionVertical::Exact(
800 PixelValue::percent(50.0),
801 ),
802 }
803 }
804}
805
806crate::impl_option!(StyleObjectPosition, OptionStyleObjectPosition, [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
807
808impl PrintAsCssValue for StyleObjectPosition {
809 fn print_as_css_value(&self) -> String {
810 format!(
811 "{} {}",
812 self.horizontal.print_as_css_value(),
813 self.vertical.print_as_css_value()
814 )
815 }
816}
817
818#[cfg(feature = "parser")]
819#[derive(Clone, PartialEq, Eq)]
820pub enum StyleObjectPositionParseError<'a> {
821 InvalidValue(&'a str),
822}
823
824#[cfg(feature = "parser")]
825crate::impl_debug_as_display!(StyleObjectPositionParseError<'a>);
826
827#[cfg(feature = "parser")]
828crate::impl_display! { StyleObjectPositionParseError<'a>, {
829 InvalidValue(val) => format!("Invalid object-position value: \"{}\"", val),
830}}
831
832#[cfg(feature = "parser")]
833#[derive(Debug, Clone, PartialEq, Eq)]
834#[repr(C, u8)]
835pub enum StyleObjectPositionParseErrorOwned {
836 InvalidValue(crate::AzString),
837}
838
839#[cfg(feature = "parser")]
840impl StyleObjectPositionParseError<'_> {
841 #[must_use] pub fn to_contained(&self) -> StyleObjectPositionParseErrorOwned {
842 match self {
843 Self::InvalidValue(s) => StyleObjectPositionParseErrorOwned::InvalidValue((*s).to_string().into()),
844 }
845 }
846}
847
848#[cfg(feature = "parser")]
849impl StyleObjectPositionParseErrorOwned {
850 #[must_use] pub fn to_shared(&self) -> StyleObjectPositionParseError<'_> {
851 match self {
852 Self::InvalidValue(s) => StyleObjectPositionParseError::InvalidValue(s.as_str()),
853 }
854 }
855}
856
857#[cfg(feature = "parser")]
860pub fn parse_style_object_position(
864 input: &str,
865) -> Result<StyleObjectPosition, StyleObjectPositionParseError<'_>> {
866 use crate::props::style::background::{
867 BackgroundPositionHorizontal, BackgroundPositionVertical,
868 };
869 use crate::props::basic::pixel::parse_pixel_value;
870
871 let input = input.trim();
872 let parts: Vec<&str> = input.split_whitespace().collect();
873
874 let (h, v) = match parts.len() {
875 1 => {
876 let val = parts[0];
877 match val {
878 "center" => (BackgroundPositionHorizontal::Center, BackgroundPositionVertical::Center),
879 "left" => (BackgroundPositionHorizontal::Left, BackgroundPositionVertical::Center),
880 "right" => (BackgroundPositionHorizontal::Right, BackgroundPositionVertical::Center),
881 "top" => (BackgroundPositionHorizontal::Center, BackgroundPositionVertical::Top),
882 "bottom" => (BackgroundPositionHorizontal::Center, BackgroundPositionVertical::Bottom),
883 _ => {
884 let px = parse_pixel_value(val)
885 .map_err(|_| StyleObjectPositionParseError::InvalidValue(input))?;
886 (BackgroundPositionHorizontal::Exact(px), BackgroundPositionVertical::Exact(px))
887 }
888 }
889 }
890 2 => {
891 let h = match parts[0] {
892 "left" => BackgroundPositionHorizontal::Left,
893 "center" => BackgroundPositionHorizontal::Center,
894 "right" => BackgroundPositionHorizontal::Right,
895 other => {
896 let px = parse_pixel_value(other)
897 .map_err(|_| StyleObjectPositionParseError::InvalidValue(input))?;
898 BackgroundPositionHorizontal::Exact(px)
899 }
900 };
901 let v = match parts[1] {
902 "top" => BackgroundPositionVertical::Top,
903 "center" => BackgroundPositionVertical::Center,
904 "bottom" => BackgroundPositionVertical::Bottom,
905 other => {
906 let px = parse_pixel_value(other)
907 .map_err(|_| StyleObjectPositionParseError::InvalidValue(input))?;
908 BackgroundPositionVertical::Exact(px)
909 }
910 };
911 (h, v)
912 }
913 _ => return Err(StyleObjectPositionParseError::InvalidValue(input)),
914 };
915
916 Ok(StyleObjectPosition { horizontal: h, vertical: v })
917}
918
919#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
923#[repr(C)]
924pub struct AspectRatioValue {
925 pub width: u32,
926 pub height: u32,
927}
928#[allow(variant_size_differences)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
934#[repr(C, u8)]
935#[derive(Default)]
936pub enum StyleAspectRatio {
937 #[default]
939 Auto,
940 Ratio(AspectRatioValue),
942}
943
944
945crate::impl_option!(StyleAspectRatio, OptionStyleAspectRatio, [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
946
947impl PrintAsCssValue for StyleAspectRatio {
948 fn print_as_css_value(&self) -> String {
949 match self {
950 Self::Auto => String::from("auto"),
951 Self::Ratio(r) => format!("{} / {}", r.width, r.height),
952 }
953 }
954}
955
956#[cfg(feature = "parser")]
957#[derive(Clone, PartialEq, Eq)]
958pub enum StyleAspectRatioParseError<'a> {
959 InvalidValue(&'a str),
960}
961
962#[cfg(feature = "parser")]
963crate::impl_debug_as_display!(StyleAspectRatioParseError<'a>);
964
965#[cfg(feature = "parser")]
966crate::impl_display! { StyleAspectRatioParseError<'a>, {
967 InvalidValue(val) => format!("Invalid aspect-ratio value: \"{}\"", val),
968}}
969
970#[cfg(feature = "parser")]
971#[derive(Debug, Clone, PartialEq, Eq)]
972#[repr(C, u8)]
973pub enum StyleAspectRatioParseErrorOwned {
974 InvalidValue(crate::AzString),
975}
976
977#[cfg(feature = "parser")]
978impl StyleAspectRatioParseError<'_> {
979 #[must_use] pub fn to_contained(&self) -> StyleAspectRatioParseErrorOwned {
980 match self {
981 Self::InvalidValue(s) => StyleAspectRatioParseErrorOwned::InvalidValue((*s).to_string().into()),
982 }
983 }
984}
985
986#[cfg(feature = "parser")]
987impl StyleAspectRatioParseErrorOwned {
988 #[must_use] pub fn to_shared(&self) -> StyleAspectRatioParseError<'_> {
989 match self {
990 Self::InvalidValue(s) => StyleAspectRatioParseError::InvalidValue(s.as_str()),
991 }
992 }
993}
994
995#[cfg(feature = "parser")]
999#[inline]
1000#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1001const fn aspect_f32_to_u32(v: f32) -> u32 {
1002 v as u32
1003}
1004
1005#[cfg(feature = "parser")]
1007pub fn parse_style_aspect_ratio(
1011 input: &str,
1012) -> Result<StyleAspectRatio, StyleAspectRatioParseError<'_>> {
1013 let input = input.trim();
1014 if input == "auto" {
1015 return Ok(StyleAspectRatio::Auto);
1016 }
1017 if let Some(slash_pos) = input.find('/') {
1019 let w_str = input[..slash_pos].trim();
1020 let h_str = input[slash_pos + 1..].trim();
1021 let w: f32 = w_str.parse().map_err(|_| StyleAspectRatioParseError::InvalidValue(input))?;
1022 let h: f32 = h_str.parse().map_err(|_| StyleAspectRatioParseError::InvalidValue(input))?;
1023 if h <= 0.0 || w <= 0.0 || w > 100_000.0 || h > 100_000.0 {
1024 return Err(StyleAspectRatioParseError::InvalidValue(input));
1025 }
1026 return Ok(StyleAspectRatio::Ratio(AspectRatioValue {
1027 width: aspect_f32_to_u32((w * 1000.0).round()),
1028 height: aspect_f32_to_u32((h * 1000.0).round()),
1029 }));
1030 }
1031 let w: f32 = input.parse().map_err(|_| StyleAspectRatioParseError::InvalidValue(input))?;
1033 if w <= 0.0 || w > 100_000.0 {
1034 return Err(StyleAspectRatioParseError::InvalidValue(input));
1035 }
1036 Ok(StyleAspectRatio::Ratio(AspectRatioValue {
1037 width: aspect_f32_to_u32((w * 1000.0).round()),
1038 height: 1000,
1039 }))
1040}