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 StyleOpacity {
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 use super::*;
212 use crate::corety::AzString;
213 use crate::props::basic::error::{InvalidValueErr, InvalidValueErrOwned};
214
215 #[derive(Clone, PartialEq)]
218 pub enum OpacityParseError<'a> {
219 ParsePercentage(PercentageParseError, &'a str),
220 OutOfRange(&'a str),
221 }
222 impl_debug_as_display!(OpacityParseError<'a>);
223 impl_display! { OpacityParseError<'a>, {
224 ParsePercentage(e, s) => format!("Invalid opacity value \"{}\": {}", s, e),
225 OutOfRange(s) => format!("Invalid opacity value \"{}\": must be between 0 and 1", s),
226 }}
227
228 #[derive(Debug, Clone, PartialEq)]
230 #[repr(C)]
231 pub struct PercentageParseErrorWithInput {
232 pub error: PercentageParseError,
233 pub input: AzString,
234 }
235
236 #[derive(Debug, Clone, PartialEq)]
237 #[repr(C, u8)]
238 pub enum OpacityParseErrorOwned {
239 ParsePercentage(PercentageParseErrorWithInput),
240 OutOfRange(AzString),
241 }
242
243 impl<'a> OpacityParseError<'a> {
244 pub fn to_contained(&self) -> OpacityParseErrorOwned {
245 match self {
246 Self::ParsePercentage(err, s) => {
247 OpacityParseErrorOwned::ParsePercentage(PercentageParseErrorWithInput { error: err.clone(), input: s.to_string().into() })
248 }
249 Self::OutOfRange(s) => OpacityParseErrorOwned::OutOfRange(s.to_string().into()),
250 }
251 }
252 }
253
254 impl OpacityParseErrorOwned {
255 pub fn to_shared<'a>(&'a self) -> OpacityParseError<'a> {
256 match self {
257 Self::ParsePercentage(e) => {
258 OpacityParseError::ParsePercentage(e.error.clone(), e.input.as_str())
259 }
260 Self::OutOfRange(s) => OpacityParseError::OutOfRange(s.as_str()),
261 }
262 }
263 }
264
265 pub fn parse_style_opacity<'a>(input: &'a str) -> Result<StyleOpacity, OpacityParseError<'a>> {
266 let val = parse_percentage_value(input)
267 .map_err(|e| OpacityParseError::ParsePercentage(e, input))?;
268
269 let normalized = val.normalized();
270 if !(0.0..=1.0).contains(&normalized) {
271 return Err(OpacityParseError::OutOfRange(input));
272 }
273
274 Ok(StyleOpacity { inner: val })
275 }
276
277 #[derive(Clone, PartialEq)]
280 pub enum StyleVisibilityParseError<'a> {
281 InvalidValue(InvalidValueErr<'a>),
282 }
283 impl_debug_as_display!(StyleVisibilityParseError<'a>);
284 impl_display! { StyleVisibilityParseError<'a>, {
285 InvalidValue(e) => format!("Invalid visibility value: \"{}\"", e.0),
286 }}
287 impl_from!(InvalidValueErr<'a>, StyleVisibilityParseError::InvalidValue);
288
289 #[derive(Debug, Clone, PartialEq)]
290 #[repr(C, u8)]
291 pub enum StyleVisibilityParseErrorOwned {
292 InvalidValue(InvalidValueErrOwned),
293 }
294
295 impl<'a> StyleVisibilityParseError<'a> {
296 pub fn to_contained(&self) -> StyleVisibilityParseErrorOwned {
297 match self {
298 Self::InvalidValue(e) => {
299 StyleVisibilityParseErrorOwned::InvalidValue(e.to_contained())
300 }
301 }
302 }
303 }
304
305 impl StyleVisibilityParseErrorOwned {
306 pub fn to_shared<'a>(&'a self) -> StyleVisibilityParseError<'a> {
307 match self {
308 Self::InvalidValue(e) => StyleVisibilityParseError::InvalidValue(e.to_shared()),
309 }
310 }
311 }
312
313 pub fn parse_style_visibility<'a>(
314 input: &'a str,
315 ) -> Result<StyleVisibility, StyleVisibilityParseError<'a>> {
316 let input = input.trim();
317 match input {
318 "visible" => Ok(StyleVisibility::Visible),
319 "hidden" => Ok(StyleVisibility::Hidden),
320 "collapse" => Ok(StyleVisibility::Collapse),
321 _ => Err(InvalidValueErr(input).into()),
322 }
323 }
324
325 #[derive(Clone, PartialEq)]
328 pub enum MixBlendModeParseError<'a> {
329 InvalidValue(InvalidValueErr<'a>),
330 }
331 impl_debug_as_display!(MixBlendModeParseError<'a>);
332 impl_display! { MixBlendModeParseError<'a>, {
333 InvalidValue(e) => format!("Invalid mix-blend-mode value: \"{}\"", e.0),
334 }}
335 impl_from!(InvalidValueErr<'a>, MixBlendModeParseError::InvalidValue);
336
337 #[derive(Debug, Clone, PartialEq)]
338 #[repr(C, u8)]
339 pub enum MixBlendModeParseErrorOwned {
340 InvalidValue(InvalidValueErrOwned),
341 }
342
343 impl<'a> MixBlendModeParseError<'a> {
344 pub fn to_contained(&self) -> MixBlendModeParseErrorOwned {
345 match self {
346 Self::InvalidValue(e) => {
347 MixBlendModeParseErrorOwned::InvalidValue(e.to_contained())
348 }
349 }
350 }
351 }
352
353 impl MixBlendModeParseErrorOwned {
354 pub fn to_shared<'a>(&'a self) -> MixBlendModeParseError<'a> {
355 match self {
356 Self::InvalidValue(e) => MixBlendModeParseError::InvalidValue(e.to_shared()),
357 }
358 }
359 }
360
361 pub fn parse_style_mix_blend_mode<'a>(
362 input: &'a str,
363 ) -> Result<StyleMixBlendMode, MixBlendModeParseError<'a>> {
364 let input = input.trim();
365 match input {
366 "normal" => Ok(StyleMixBlendMode::Normal),
367 "multiply" => Ok(StyleMixBlendMode::Multiply),
368 "screen" => Ok(StyleMixBlendMode::Screen),
369 "overlay" => Ok(StyleMixBlendMode::Overlay),
370 "darken" => Ok(StyleMixBlendMode::Darken),
371 "lighten" => Ok(StyleMixBlendMode::Lighten),
372 "color-dodge" => Ok(StyleMixBlendMode::ColorDodge),
373 "color-burn" => Ok(StyleMixBlendMode::ColorBurn),
374 "hard-light" => Ok(StyleMixBlendMode::HardLight),
375 "soft-light" => Ok(StyleMixBlendMode::SoftLight),
376 "difference" => Ok(StyleMixBlendMode::Difference),
377 "exclusion" => Ok(StyleMixBlendMode::Exclusion),
378 "hue" => Ok(StyleMixBlendMode::Hue),
379 "saturation" => Ok(StyleMixBlendMode::Saturation),
380 "color" => Ok(StyleMixBlendMode::Color),
381 "luminosity" => Ok(StyleMixBlendMode::Luminosity),
382 _ => Err(InvalidValueErr(input).into()),
383 }
384 }
385
386 #[derive(Clone, PartialEq)]
389 pub enum CursorParseError<'a> {
390 InvalidValue(InvalidValueErr<'a>),
391 }
392 impl_debug_as_display!(CursorParseError<'a>);
393 impl_display! { CursorParseError<'a>, {
394 InvalidValue(e) => format!("Invalid cursor value: \"{}\"", e.0),
395 }}
396 impl_from!(InvalidValueErr<'a>, CursorParseError::InvalidValue);
397
398 #[derive(Debug, Clone, PartialEq)]
399 #[repr(C, u8)]
400 pub enum CursorParseErrorOwned {
401 InvalidValue(InvalidValueErrOwned),
402 }
403
404 impl<'a> CursorParseError<'a> {
405 pub fn to_contained(&self) -> CursorParseErrorOwned {
406 match self {
407 Self::InvalidValue(e) => CursorParseErrorOwned::InvalidValue(e.to_contained()),
408 }
409 }
410 }
411
412 impl CursorParseErrorOwned {
413 pub fn to_shared<'a>(&'a self) -> CursorParseError<'a> {
414 match self {
415 Self::InvalidValue(e) => CursorParseError::InvalidValue(e.to_shared()),
416 }
417 }
418 }
419
420 pub fn parse_style_cursor<'a>(input: &'a str) -> Result<StyleCursor, CursorParseError<'a>> {
421 let input = input.trim();
422 match input {
423 "alias" => Ok(StyleCursor::Alias),
424 "all-scroll" => Ok(StyleCursor::AllScroll),
425 "cell" => Ok(StyleCursor::Cell),
426 "col-resize" => Ok(StyleCursor::ColResize),
427 "context-menu" => Ok(StyleCursor::ContextMenu),
428 "copy" => Ok(StyleCursor::Copy),
429 "crosshair" => Ok(StyleCursor::Crosshair),
430 "default" => Ok(StyleCursor::Default),
431 "e-resize" => Ok(StyleCursor::EResize),
432 "ew-resize" => Ok(StyleCursor::EwResize),
433 "grab" => Ok(StyleCursor::Grab),
434 "grabbing" => Ok(StyleCursor::Grabbing),
435 "help" => Ok(StyleCursor::Help),
436 "move" => Ok(StyleCursor::Move),
437 "n-resize" => Ok(StyleCursor::NResize),
438 "ns-resize" => Ok(StyleCursor::NsResize),
439 "nesw-resize" => Ok(StyleCursor::NeswResize),
440 "nwse-resize" => Ok(StyleCursor::NwseResize),
441 "pointer" => Ok(StyleCursor::Pointer),
442 "progress" => Ok(StyleCursor::Progress),
443 "row-resize" => Ok(StyleCursor::RowResize),
444 "s-resize" => Ok(StyleCursor::SResize),
445 "se-resize" => Ok(StyleCursor::SeResize),
446 "text" => Ok(StyleCursor::Text),
447 "unset" => Ok(StyleCursor::Unset),
448 "vertical-text" => Ok(StyleCursor::VerticalText),
449 "w-resize" => Ok(StyleCursor::WResize),
450 "wait" => Ok(StyleCursor::Wait),
451 "zoom-in" => Ok(StyleCursor::ZoomIn),
452 "zoom-out" => Ok(StyleCursor::ZoomOut),
453 _ => Err(InvalidValueErr(input).into()),
454 }
455 }
456}
457
458#[cfg(feature = "parser")]
459pub use self::parsers::*;
460
461#[cfg(all(test, feature = "parser"))]
462mod tests {
463 use super::*;
464
465 #[test]
466 fn test_parse_opacity() {
467 assert_eq!(parse_style_opacity("0.5").unwrap().inner.normalized(), 0.5);
468 assert_eq!(parse_style_opacity("1").unwrap().inner.normalized(), 1.0);
469 assert_eq!(parse_style_opacity("50%").unwrap().inner.normalized(), 0.5);
470 assert_eq!(parse_style_opacity("0").unwrap().inner.normalized(), 0.0);
471 assert_eq!(
472 parse_style_opacity(" 75% ").unwrap().inner.normalized(),
473 0.75
474 );
475 assert!(parse_style_opacity("1.1").is_err());
476 assert!(parse_style_opacity("-0.1").is_err());
477 assert!(parse_style_opacity("auto").is_err());
478 }
479
480 #[test]
481 fn test_parse_mix_blend_mode() {
482 assert_eq!(
483 parse_style_mix_blend_mode("multiply").unwrap(),
484 StyleMixBlendMode::Multiply
485 );
486 assert_eq!(
487 parse_style_mix_blend_mode("screen").unwrap(),
488 StyleMixBlendMode::Screen
489 );
490 assert_eq!(
491 parse_style_mix_blend_mode("color-dodge").unwrap(),
492 StyleMixBlendMode::ColorDodge
493 );
494 assert!(parse_style_mix_blend_mode("mix").is_err());
495 }
496
497 #[test]
498 fn test_parse_visibility() {
499 assert_eq!(
500 parse_style_visibility("visible").unwrap(),
501 StyleVisibility::Visible
502 );
503 assert_eq!(
504 parse_style_visibility("hidden").unwrap(),
505 StyleVisibility::Hidden
506 );
507 assert_eq!(
508 parse_style_visibility("collapse").unwrap(),
509 StyleVisibility::Collapse
510 );
511 assert_eq!(
512 parse_style_visibility(" visible ").unwrap(),
513 StyleVisibility::Visible
514 );
515 assert!(parse_style_visibility("none").is_err());
516 assert!(parse_style_visibility("show").is_err());
517 }
518
519 #[test]
520 fn test_parse_cursor() {
521 assert_eq!(parse_style_cursor("pointer").unwrap(), StyleCursor::Pointer);
522 assert_eq!(parse_style_cursor("wait").unwrap(), StyleCursor::Wait);
523 assert_eq!(
524 parse_style_cursor("col-resize").unwrap(),
525 StyleCursor::ColResize
526 );
527 assert_eq!(parse_style_cursor(" text ").unwrap(), StyleCursor::Text);
528 assert!(parse_style_cursor("hand").is_err()); }
530
531 #[test]
532 fn test_parse_object_fit() {
533 assert_eq!(parse_style_object_fit("fill").unwrap(), StyleObjectFit::Fill);
534 assert_eq!(parse_style_object_fit("contain").unwrap(), StyleObjectFit::Contain);
535 assert_eq!(parse_style_object_fit("cover").unwrap(), StyleObjectFit::Cover);
536 assert_eq!(parse_style_object_fit("none").unwrap(), StyleObjectFit::None);
537 assert_eq!(parse_style_object_fit("scale-down").unwrap(), StyleObjectFit::ScaleDown);
538 assert_eq!(parse_style_object_fit(" cover ").unwrap(), StyleObjectFit::Cover);
539 assert!(parse_style_object_fit("stretch").is_err());
540 assert!(parse_style_object_fit("").is_err());
541 }
542
543 #[test]
544 fn test_parse_text_orientation() {
545 assert_eq!(parse_style_text_orientation("mixed").unwrap(), StyleTextOrientation::Mixed);
546 assert_eq!(parse_style_text_orientation("upright").unwrap(), StyleTextOrientation::Upright);
547 assert_eq!(parse_style_text_orientation("sideways").unwrap(), StyleTextOrientation::Sideways);
548 assert_eq!(parse_style_text_orientation(" mixed ").unwrap(), StyleTextOrientation::Mixed);
549 assert!(parse_style_text_orientation("vertical").is_err());
550 }
551
552 #[test]
553 fn test_parse_object_position() {
554 let centered = parse_style_object_position("center").unwrap();
555 assert_eq!(centered, parse_style_object_position("center center").unwrap());
556
557 let lt = parse_style_object_position("left top").unwrap();
558 use crate::props::style::background::{BackgroundPositionHorizontal, BackgroundPositionVertical};
559 assert_eq!(lt.horizontal, BackgroundPositionHorizontal::Left);
560 assert_eq!(lt.vertical, BackgroundPositionVertical::Top);
561
562 let rb = parse_style_object_position("right bottom").unwrap();
563 assert_eq!(rb.horizontal, BackgroundPositionHorizontal::Right);
564 assert_eq!(rb.vertical, BackgroundPositionVertical::Bottom);
565
566 assert!(parse_style_object_position("left top center").is_err());
567 assert!(parse_style_object_position("invalid").is_err());
568 }
569
570 #[test]
571 fn test_parse_aspect_ratio() {
572 assert_eq!(parse_style_aspect_ratio("auto").unwrap(), StyleAspectRatio::Auto);
573 assert_eq!(
574 parse_style_aspect_ratio("16 / 9").unwrap(),
575 StyleAspectRatio::Ratio(AspectRatioValue { width: 16000, height: 9000 })
576 );
577 assert_eq!(
578 parse_style_aspect_ratio("16/9").unwrap(),
579 StyleAspectRatio::Ratio(AspectRatioValue { width: 16000, height: 9000 })
580 );
581 assert_eq!(
582 parse_style_aspect_ratio("1.5").unwrap(),
583 StyleAspectRatio::Ratio(AspectRatioValue { width: 1500, height: 1000 })
584 );
585 assert_eq!(
586 parse_style_aspect_ratio(" 4 / 3 ").unwrap(),
587 StyleAspectRatio::Ratio(AspectRatioValue { width: 4000, height: 3000 })
588 );
589 assert!(parse_style_aspect_ratio("0 / 1").is_err());
590 assert!(parse_style_aspect_ratio("1 / 0").is_err());
591 assert!(parse_style_aspect_ratio("-1 / 1").is_err());
592 assert!(parse_style_aspect_ratio("abc").is_err());
593 }
594}
595
596#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
601#[repr(C)]
602#[derive(Default)]
603pub enum StyleObjectFit {
604 #[default]
605 Fill,
606 Contain,
607 Cover,
608 None,
609 ScaleDown,
610}
611
612
613crate::impl_option!(StyleObjectFit, OptionStyleObjectFit, [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
614
615impl PrintAsCssValue for StyleObjectFit {
616 fn print_as_css_value(&self) -> String {
617 String::from(match self {
618 StyleObjectFit::Fill => "fill",
619 StyleObjectFit::Contain => "contain",
620 StyleObjectFit::Cover => "cover",
621 StyleObjectFit::None => "none",
622 StyleObjectFit::ScaleDown => "scale-down",
623 })
624 }
625}
626
627#[cfg(feature = "parser")]
628#[derive(Clone, PartialEq)]
629pub enum StyleObjectFitParseError<'a> {
630 InvalidValue(&'a str),
631}
632
633#[cfg(feature = "parser")]
634crate::impl_debug_as_display!(StyleObjectFitParseError<'a>);
635
636#[cfg(feature = "parser")]
637crate::impl_display! { StyleObjectFitParseError<'a>, {
638 InvalidValue(val) => format!("Invalid object-fit value: \"{}\"", val),
639}}
640
641#[cfg(feature = "parser")]
642#[derive(Debug, Clone, PartialEq)]
643#[repr(C, u8)]
644pub enum StyleObjectFitParseErrorOwned {
645 InvalidValue(crate::AzString),
646}
647
648#[cfg(feature = "parser")]
649impl<'a> StyleObjectFitParseError<'a> {
650 pub fn to_contained(&self) -> StyleObjectFitParseErrorOwned {
651 match self {
652 Self::InvalidValue(s) => StyleObjectFitParseErrorOwned::InvalidValue(s.to_string().into()),
653 }
654 }
655}
656
657#[cfg(feature = "parser")]
658impl StyleObjectFitParseErrorOwned {
659 pub fn to_shared<'a>(&'a self) -> StyleObjectFitParseError<'a> {
660 match self {
661 Self::InvalidValue(s) => StyleObjectFitParseError::InvalidValue(s.as_str()),
662 }
663 }
664}
665
666#[cfg(feature = "parser")]
667pub fn parse_style_object_fit<'a>(
668 input: &'a str,
669) -> Result<StyleObjectFit, StyleObjectFitParseError<'a>> {
670 let input = input.trim();
671 match input {
672 "fill" => Ok(StyleObjectFit::Fill),
673 "contain" => Ok(StyleObjectFit::Contain),
674 "cover" => Ok(StyleObjectFit::Cover),
675 "none" => Ok(StyleObjectFit::None),
676 "scale-down" => Ok(StyleObjectFit::ScaleDown),
677 _ => Err(StyleObjectFitParseError::InvalidValue(input)),
678 }
679}
680
681#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
686#[repr(C)]
687#[derive(Default)]
688pub enum StyleTextOrientation {
689 #[default]
690 Mixed,
691 Upright,
692 Sideways,
693}
694
695
696crate::impl_option!(StyleTextOrientation, OptionStyleTextOrientation, [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
697
698impl PrintAsCssValue for StyleTextOrientation {
699 fn print_as_css_value(&self) -> String {
700 String::from(match self {
701 StyleTextOrientation::Mixed => "mixed",
702 StyleTextOrientation::Upright => "upright",
703 StyleTextOrientation::Sideways => "sideways",
704 })
705 }
706}
707
708#[cfg(feature = "parser")]
709#[derive(Clone, PartialEq)]
710pub enum StyleTextOrientationParseError<'a> {
711 InvalidValue(&'a str),
712}
713
714#[cfg(feature = "parser")]
715crate::impl_debug_as_display!(StyleTextOrientationParseError<'a>);
716
717#[cfg(feature = "parser")]
718crate::impl_display! { StyleTextOrientationParseError<'a>, {
719 InvalidValue(val) => format!("Invalid text-orientation value: \"{}\"", val),
720}}
721
722#[cfg(feature = "parser")]
723#[derive(Debug, Clone, PartialEq)]
724#[repr(C, u8)]
725pub enum StyleTextOrientationParseErrorOwned {
726 InvalidValue(crate::AzString),
727}
728
729#[cfg(feature = "parser")]
730impl<'a> StyleTextOrientationParseError<'a> {
731 pub fn to_contained(&self) -> StyleTextOrientationParseErrorOwned {
732 match self {
733 Self::InvalidValue(s) => StyleTextOrientationParseErrorOwned::InvalidValue(s.to_string().into()),
734 }
735 }
736}
737
738#[cfg(feature = "parser")]
739impl StyleTextOrientationParseErrorOwned {
740 pub fn to_shared<'a>(&'a self) -> StyleTextOrientationParseError<'a> {
741 match self {
742 Self::InvalidValue(s) => StyleTextOrientationParseError::InvalidValue(s.as_str()),
743 }
744 }
745}
746
747#[cfg(feature = "parser")]
748pub fn parse_style_text_orientation<'a>(
749 input: &'a str,
750) -> Result<StyleTextOrientation, StyleTextOrientationParseError<'a>> {
751 let input = input.trim();
752 match input {
753 "mixed" => Ok(StyleTextOrientation::Mixed),
754 "upright" => Ok(StyleTextOrientation::Upright),
755 "sideways" => Ok(StyleTextOrientation::Sideways),
756 _ => Err(StyleTextOrientationParseError::InvalidValue(input)),
757 }
758}
759
760#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
765#[repr(C)]
766pub struct StyleObjectPosition {
767 pub horizontal: crate::props::style::background::BackgroundPositionHorizontal,
768 pub vertical: crate::props::style::background::BackgroundPositionVertical,
769}
770
771impl Default for StyleObjectPosition {
772 fn default() -> Self {
773 use crate::props::basic::pixel::PixelValue;
774 Self {
775 horizontal: crate::props::style::background::BackgroundPositionHorizontal::Exact(
776 PixelValue::percent(50.0),
777 ),
778 vertical: crate::props::style::background::BackgroundPositionVertical::Exact(
779 PixelValue::percent(50.0),
780 ),
781 }
782 }
783}
784
785crate::impl_option!(StyleObjectPosition, OptionStyleObjectPosition, [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
786
787impl PrintAsCssValue for StyleObjectPosition {
788 fn print_as_css_value(&self) -> String {
789 format!(
790 "{} {}",
791 self.horizontal.print_as_css_value(),
792 self.vertical.print_as_css_value()
793 )
794 }
795}
796
797#[cfg(feature = "parser")]
798#[derive(Clone, PartialEq)]
799pub enum StyleObjectPositionParseError<'a> {
800 InvalidValue(&'a str),
801}
802
803#[cfg(feature = "parser")]
804crate::impl_debug_as_display!(StyleObjectPositionParseError<'a>);
805
806#[cfg(feature = "parser")]
807crate::impl_display! { StyleObjectPositionParseError<'a>, {
808 InvalidValue(val) => format!("Invalid object-position value: \"{}\"", val),
809}}
810
811#[cfg(feature = "parser")]
812#[derive(Debug, Clone, PartialEq)]
813#[repr(C, u8)]
814pub enum StyleObjectPositionParseErrorOwned {
815 InvalidValue(crate::AzString),
816}
817
818#[cfg(feature = "parser")]
819impl<'a> StyleObjectPositionParseError<'a> {
820 pub fn to_contained(&self) -> StyleObjectPositionParseErrorOwned {
821 match self {
822 Self::InvalidValue(s) => StyleObjectPositionParseErrorOwned::InvalidValue(s.to_string().into()),
823 }
824 }
825}
826
827#[cfg(feature = "parser")]
828impl StyleObjectPositionParseErrorOwned {
829 pub fn to_shared<'a>(&'a self) -> StyleObjectPositionParseError<'a> {
830 match self {
831 Self::InvalidValue(s) => StyleObjectPositionParseError::InvalidValue(s.as_str()),
832 }
833 }
834}
835
836#[cfg(feature = "parser")]
839pub fn parse_style_object_position<'a>(
840 input: &'a str,
841) -> Result<StyleObjectPosition, StyleObjectPositionParseError<'a>> {
842 use crate::props::style::background::{
843 BackgroundPositionHorizontal, BackgroundPositionVertical,
844 };
845 use crate::props::basic::pixel::parse_pixel_value;
846
847 let input = input.trim();
848 let parts: Vec<&str> = input.split_whitespace().collect();
849
850 let (h, v) = match parts.len() {
851 1 => {
852 let val = parts[0];
853 match val {
854 "center" => (BackgroundPositionHorizontal::Center, BackgroundPositionVertical::Center),
855 "left" => (BackgroundPositionHorizontal::Left, BackgroundPositionVertical::Center),
856 "right" => (BackgroundPositionHorizontal::Right, BackgroundPositionVertical::Center),
857 "top" => (BackgroundPositionHorizontal::Center, BackgroundPositionVertical::Top),
858 "bottom" => (BackgroundPositionHorizontal::Center, BackgroundPositionVertical::Bottom),
859 _ => {
860 let px = parse_pixel_value(val)
861 .map_err(|_| StyleObjectPositionParseError::InvalidValue(input))?;
862 (BackgroundPositionHorizontal::Exact(px), BackgroundPositionVertical::Exact(px))
863 }
864 }
865 }
866 2 => {
867 let h = match parts[0] {
868 "left" => BackgroundPositionHorizontal::Left,
869 "center" => BackgroundPositionHorizontal::Center,
870 "right" => BackgroundPositionHorizontal::Right,
871 other => {
872 let px = parse_pixel_value(other)
873 .map_err(|_| StyleObjectPositionParseError::InvalidValue(input))?;
874 BackgroundPositionHorizontal::Exact(px)
875 }
876 };
877 let v = match parts[1] {
878 "top" => BackgroundPositionVertical::Top,
879 "center" => BackgroundPositionVertical::Center,
880 "bottom" => BackgroundPositionVertical::Bottom,
881 other => {
882 let px = parse_pixel_value(other)
883 .map_err(|_| StyleObjectPositionParseError::InvalidValue(input))?;
884 BackgroundPositionVertical::Exact(px)
885 }
886 };
887 (h, v)
888 }
889 _ => return Err(StyleObjectPositionParseError::InvalidValue(input)),
890 };
891
892 Ok(StyleObjectPosition { horizontal: h, vertical: v })
893}
894
895#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
899#[repr(C)]
900pub struct AspectRatioValue {
901 pub width: u32,
902 pub height: u32,
903}
904
905#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
910#[repr(C, u8)]
911#[derive(Default)]
912pub enum StyleAspectRatio {
913 #[default]
915 Auto,
916 Ratio(AspectRatioValue),
918}
919
920
921crate::impl_option!(StyleAspectRatio, OptionStyleAspectRatio, [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
922
923impl PrintAsCssValue for StyleAspectRatio {
924 fn print_as_css_value(&self) -> String {
925 match self {
926 StyleAspectRatio::Auto => String::from("auto"),
927 StyleAspectRatio::Ratio(r) => format!("{} / {}", r.width, r.height),
928 }
929 }
930}
931
932#[cfg(feature = "parser")]
933#[derive(Clone, PartialEq)]
934pub enum StyleAspectRatioParseError<'a> {
935 InvalidValue(&'a str),
936}
937
938#[cfg(feature = "parser")]
939crate::impl_debug_as_display!(StyleAspectRatioParseError<'a>);
940
941#[cfg(feature = "parser")]
942crate::impl_display! { StyleAspectRatioParseError<'a>, {
943 InvalidValue(val) => format!("Invalid aspect-ratio value: \"{}\"", val),
944}}
945
946#[cfg(feature = "parser")]
947#[derive(Debug, Clone, PartialEq)]
948#[repr(C, u8)]
949pub enum StyleAspectRatioParseErrorOwned {
950 InvalidValue(crate::AzString),
951}
952
953#[cfg(feature = "parser")]
954impl<'a> StyleAspectRatioParseError<'a> {
955 pub fn to_contained(&self) -> StyleAspectRatioParseErrorOwned {
956 match self {
957 Self::InvalidValue(s) => StyleAspectRatioParseErrorOwned::InvalidValue(s.to_string().into()),
958 }
959 }
960}
961
962#[cfg(feature = "parser")]
963impl StyleAspectRatioParseErrorOwned {
964 pub fn to_shared<'a>(&'a self) -> StyleAspectRatioParseError<'a> {
965 match self {
966 Self::InvalidValue(s) => StyleAspectRatioParseError::InvalidValue(s.as_str()),
967 }
968 }
969}
970
971#[cfg(feature = "parser")]
973pub fn parse_style_aspect_ratio<'a>(
974 input: &'a str,
975) -> Result<StyleAspectRatio, StyleAspectRatioParseError<'a>> {
976 let input = input.trim();
977 if input == "auto" {
978 return Ok(StyleAspectRatio::Auto);
979 }
980 if let Some(slash_pos) = input.find('/') {
982 let w_str = input[..slash_pos].trim();
983 let h_str = input[slash_pos + 1..].trim();
984 let w: f32 = w_str.parse().map_err(|_| StyleAspectRatioParseError::InvalidValue(input))?;
985 let h: f32 = h_str.parse().map_err(|_| StyleAspectRatioParseError::InvalidValue(input))?;
986 if h <= 0.0 || w <= 0.0 || w > 100_000.0 || h > 100_000.0 {
987 return Err(StyleAspectRatioParseError::InvalidValue(input));
988 }
989 return Ok(StyleAspectRatio::Ratio(AspectRatioValue {
990 width: (w * 1000.0).round() as u32,
991 height: (h * 1000.0).round() as u32,
992 }));
993 }
994 let w: f32 = input.parse().map_err(|_| StyleAspectRatioParseError::InvalidValue(input))?;
996 if w <= 0.0 || w > 100_000.0 {
997 return Err(StyleAspectRatioParseError::InvalidValue(input));
998 }
999 Ok(StyleAspectRatio::Ratio(AspectRatioValue {
1000 width: (w * 1000.0).round() as u32,
1001 height: 1000,
1002 }))
1003}