1use alloc::{
4 string::{String, ToString},
5 vec::Vec,
6};
7use core::fmt;
8
9#[cfg(feature = "parser")]
10use crate::props::basic::{
11 error::{InvalidValueErr, InvalidValueErrOwned},
12 parse::{
13 parse_parentheses, parse_image, split_string_respect_comma,
14 CssImageParseError, CssImageParseErrorOwned,
15 ParenthesisParseError, ParenthesisParseErrorOwned,
16 },
17 color::parse_color_or_system,
18};
19use crate::{
20 corety::AzString,
21 format_rust_code::GetHash,
22 props::{
23 basic::{
24 angle::{
25 parse_angle_value, AngleValue, CssAngleValueParseError,
26 CssAngleValueParseErrorOwned, OptionAngleValue,
27 },
28 color::{parse_css_color, ColorU, ColorOrSystem, CssColorParseError, CssColorParseErrorOwned},
29 direction::{
30 parse_direction, CssDirectionParseError, CssDirectionParseErrorOwned, Direction,
31 },
32 length::{
33 parse_percentage_value, OptionPercentageValue, PercentageParseError,
34 PercentageParseErrorOwned, PercentageValue,
35 },
36 pixel::{
37 parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned,
38 PixelValue,
39 },
40 },
41 formatter::PrintAsCssValue,
42 },
43};
44
45#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
49#[repr(C)]
50#[derive(Default)]
51pub enum ExtendMode {
52 #[default]
53 Clamp,
54 Repeat,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[repr(C, u8)]
62pub enum StyleBackgroundContent {
63 LinearGradient(LinearGradient),
64 RadialGradient(RadialGradient),
65 ConicGradient(ConicGradient),
66 Image(AzString),
67 Color(ColorU),
68}
69
70impl_option!(
71 StyleBackgroundContent,
72 OptionStyleBackgroundContent,
73 copy = false,
74 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
75);
76
77impl_vec!(StyleBackgroundContent, StyleBackgroundContentVec, StyleBackgroundContentVecDestructor, StyleBackgroundContentVecDestructorType, StyleBackgroundContentVecSlice, OptionStyleBackgroundContent);
78impl_vec_debug!(StyleBackgroundContent, StyleBackgroundContentVec);
79impl_vec_partialord!(StyleBackgroundContent, StyleBackgroundContentVec);
80impl_vec_ord!(StyleBackgroundContent, StyleBackgroundContentVec);
81impl_vec_clone!(
82 StyleBackgroundContent,
83 StyleBackgroundContentVec,
84 StyleBackgroundContentVecDestructor
85);
86impl_vec_partialeq!(StyleBackgroundContent, StyleBackgroundContentVec);
87impl_vec_eq!(StyleBackgroundContent, StyleBackgroundContentVec);
88impl_vec_hash!(StyleBackgroundContent, StyleBackgroundContentVec);
89
90impl Default for StyleBackgroundContent {
91 fn default() -> StyleBackgroundContent {
92 StyleBackgroundContent::Color(ColorU::TRANSPARENT)
93 }
94}
95
96impl PrintAsCssValue for StyleBackgroundContent {
97 fn print_as_css_value(&self) -> String {
98 match self {
99 StyleBackgroundContent::LinearGradient(lg) => {
100 let prefix = if lg.extend_mode == ExtendMode::Repeat {
101 "repeating-linear-gradient"
102 } else {
103 "linear-gradient"
104 };
105 format!("{}({})", prefix, lg.print_as_css_value())
106 }
107 StyleBackgroundContent::RadialGradient(rg) => {
108 let prefix = if rg.extend_mode == ExtendMode::Repeat {
109 "repeating-radial-gradient"
110 } else {
111 "radial-gradient"
112 };
113 format!("{}({})", prefix, rg.print_as_css_value())
114 }
115 StyleBackgroundContent::ConicGradient(cg) => {
116 let prefix = if cg.extend_mode == ExtendMode::Repeat {
117 "repeating-conic-gradient"
118 } else {
119 "conic-gradient"
120 };
121 format!("{}({})", prefix, cg.print_as_css_value())
122 }
123 StyleBackgroundContent::Image(id) => format!("url(\"{}\")", id.as_str()),
124 StyleBackgroundContent::Color(c) => c.to_hash(),
125 }
126 }
127}
128
129impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundContent {
132 fn format_as_rust_code(&self, _tabs: usize) -> String {
133 format!("StyleBackgroundContent::from_css(\"{}\")", self.print_as_css_value())
135 }
136}
137
138impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundSizeVec {
139 fn format_as_rust_code(&self, _tabs: usize) -> String {
140 format!(
141 "StyleBackgroundSizeVec::from_const_slice(STYLE_BACKGROUND_SIZE_{}_ITEMS)",
142 self.get_hash()
143 )
144 }
145}
146
147impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundRepeatVec {
148 fn format_as_rust_code(&self, _tabs: usize) -> String {
149 format!(
150 "StyleBackgroundRepeatVec::from_const_slice(STYLE_BACKGROUND_REPEAT_{}_ITEMS)",
151 self.get_hash()
152 )
153 }
154}
155
156impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundContentVec {
157 fn format_as_rust_code(&self, _tabs: usize) -> String {
158 format!(
159 "StyleBackgroundContentVec::from_const_slice(STYLE_BACKGROUND_CONTENT_{}_ITEMS)",
160 self.get_hash()
161 )
162 }
163}
164
165impl PrintAsCssValue for StyleBackgroundContentVec {
166 fn print_as_css_value(&self) -> String {
167 self.as_ref()
168 .iter()
169 .map(|f| f.print_as_css_value())
170 .collect::<Vec<_>>()
171 .join(", ")
172 }
173}
174
175#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
179#[repr(C)]
180pub struct LinearGradient {
181 pub direction: Direction,
182 pub extend_mode: ExtendMode,
183 pub stops: NormalizedLinearColorStopVec,
184}
185impl Default for LinearGradient {
186 fn default() -> Self {
187 Self {
188 direction: Direction::default(),
189 extend_mode: ExtendMode::default(),
190 stops: Vec::new().into(),
191 }
192 }
193}
194impl PrintAsCssValue for LinearGradient {
195 fn print_as_css_value(&self) -> String {
196 let dir_str = self.direction.print_as_css_value();
197 let stops_str = self
198 .stops
199 .iter()
200 .map(|s| s.print_as_css_value())
201 .collect::<Vec<_>>()
202 .join(", ");
203 if stops_str.is_empty() {
204 dir_str
205 } else {
206 format!("{}, {}", dir_str, stops_str)
207 }
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
213#[repr(C)]
214pub struct RadialGradient {
215 pub shape: Shape,
216 pub size: RadialGradientSize,
217 pub position: StyleBackgroundPosition,
218 pub extend_mode: ExtendMode,
219 pub stops: NormalizedLinearColorStopVec,
220}
221impl Default for RadialGradient {
222 fn default() -> Self {
223 Self {
224 shape: Shape::default(),
225 size: RadialGradientSize::default(),
226 position: StyleBackgroundPosition::default(),
227 extend_mode: ExtendMode::default(),
228 stops: Vec::new().into(),
229 }
230 }
231}
232impl PrintAsCssValue for RadialGradient {
233 fn print_as_css_value(&self) -> String {
234 let stops_str = self
235 .stops
236 .iter()
237 .map(|s| s.print_as_css_value())
238 .collect::<Vec<_>>()
239 .join(", ");
240 format!(
241 "{} {} at {}, {}",
242 self.shape,
243 self.size,
244 self.position.print_as_css_value(),
245 stops_str
246 )
247 }
248}
249
250#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
252#[repr(C)]
253pub struct ConicGradient {
254 pub extend_mode: ExtendMode,
255 pub center: StyleBackgroundPosition,
256 pub angle: AngleValue,
257 pub stops: NormalizedRadialColorStopVec,
258}
259impl Default for ConicGradient {
260 fn default() -> Self {
261 Self {
262 extend_mode: ExtendMode::default(),
263 center: StyleBackgroundPosition::default(),
264 angle: AngleValue::default(),
265 stops: Vec::new().into(),
266 }
267 }
268}
269impl PrintAsCssValue for ConicGradient {
270 fn print_as_css_value(&self) -> String {
271 let stops_str = self
272 .stops
273 .iter()
274 .map(|s| s.print_as_css_value())
275 .collect::<Vec<_>>()
276 .join(", ");
277 format!(
278 "from {} at {}, {}",
279 self.angle,
280 self.center.print_as_css_value(),
281 stops_str
282 )
283 }
284}
285
286#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
290#[repr(C)]
291#[derive(Default)]
292pub enum Shape {
293 #[default]
294 Ellipse,
295 Circle,
296}
297impl fmt::Display for Shape {
298 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
299 write!(
300 f,
301 "{}",
302 match self {
303 Shape::Ellipse => "ellipse",
304 Shape::Circle => "circle",
305 }
306 )
307 }
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
312#[repr(C)]
313#[derive(Default)]
314pub enum RadialGradientSize {
315 ClosestSide,
316 ClosestCorner,
317 FarthestSide,
318 #[default]
319 FarthestCorner,
320}
321impl fmt::Display for RadialGradientSize {
322 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323 write!(
324 f,
325 "{}",
326 match self {
327 Self::ClosestSide => "closest-side",
328 Self::ClosestCorner => "closest-corner",
329 Self::FarthestSide => "farthest-side",
330 Self::FarthestCorner => "farthest-corner",
331 }
332 )
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
337#[repr(C)]
338pub struct NormalizedLinearColorStop {
339 pub offset: PercentageValue,
340 pub color: ColorOrSystem,
342}
343
344impl NormalizedLinearColorStop {
345 pub const fn new(offset: PercentageValue, color: ColorU) -> Self {
347 Self { offset, color: ColorOrSystem::color(color) }
348 }
349
350 pub fn resolve(&self, system_colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
352 self.color.resolve(system_colors, fallback)
353 }
354}
355
356impl_option!(
357 NormalizedLinearColorStop,
358 OptionNormalizedLinearColorStop,
359 copy = false,
360 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
361);
362impl_vec!(NormalizedLinearColorStop, NormalizedLinearColorStopVec, NormalizedLinearColorStopVecDestructor, NormalizedLinearColorStopVecDestructorType, NormalizedLinearColorStopVecSlice, OptionNormalizedLinearColorStop);
363impl_vec_debug!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
364impl_vec_partialord!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
365impl_vec_ord!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
366impl_vec_clone!(
367 NormalizedLinearColorStop,
368 NormalizedLinearColorStopVec,
369 NormalizedLinearColorStopVecDestructor
370);
371impl_vec_partialeq!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
372impl_vec_eq!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
373impl_vec_hash!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
374impl PrintAsCssValue for NormalizedLinearColorStop {
375 fn print_as_css_value(&self) -> String {
376 match &self.color {
377 ColorOrSystem::Color(c) => format!("{} {}", c.to_hash(), self.offset),
378 ColorOrSystem::System(s) => format!("{} {}", s.as_css_str(), self.offset),
379 }
380 }
381}
382
383#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
384#[repr(C)]
385pub struct NormalizedRadialColorStop {
386 pub angle: AngleValue,
387 pub color: ColorOrSystem,
389}
390
391impl NormalizedRadialColorStop {
392 pub const fn new(angle: AngleValue, color: ColorU) -> Self {
394 Self { angle, color: ColorOrSystem::color(color) }
395 }
396
397 pub fn resolve(&self, system_colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
399 self.color.resolve(system_colors, fallback)
400 }
401}
402
403impl_option!(
404 NormalizedRadialColorStop,
405 OptionNormalizedRadialColorStop,
406 copy = false,
407 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
408);
409impl_vec!(NormalizedRadialColorStop, NormalizedRadialColorStopVec, NormalizedRadialColorStopVecDestructor, NormalizedRadialColorStopVecDestructorType, NormalizedRadialColorStopVecSlice, OptionNormalizedRadialColorStop);
410impl_vec_debug!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
411impl_vec_partialord!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
412impl_vec_ord!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
413impl_vec_clone!(
414 NormalizedRadialColorStop,
415 NormalizedRadialColorStopVec,
416 NormalizedRadialColorStopVecDestructor
417);
418impl_vec_partialeq!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
419impl_vec_eq!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
420impl_vec_hash!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
421impl PrintAsCssValue for NormalizedRadialColorStop {
422 fn print_as_css_value(&self) -> String {
423 match &self.color {
424 ColorOrSystem::Color(c) => format!("{} {}", c.to_hash(), self.angle),
425 ColorOrSystem::System(s) => format!("{} {}", s.as_css_str(), self.angle),
426 }
427 }
428}
429
430#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
439pub struct LinearColorStop {
440 pub color: ColorOrSystem,
441 pub offset1: OptionPercentageValue,
443 pub offset2: OptionPercentageValue,
446}
447
448#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
457pub struct RadialColorStop {
458 pub color: ColorOrSystem,
459 pub offset1: OptionAngleValue,
461 pub offset2: OptionAngleValue,
464}
465
466#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
470#[repr(C)]
471pub struct StyleBackgroundPosition {
472 pub horizontal: BackgroundPositionHorizontal,
473 pub vertical: BackgroundPositionVertical,
474}
475
476impl_option!(
477 StyleBackgroundPosition,
478 OptionStyleBackgroundPosition,
479 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
480);
481impl_vec!(StyleBackgroundPosition, StyleBackgroundPositionVec, StyleBackgroundPositionVecDestructor, StyleBackgroundPositionVecDestructorType, StyleBackgroundPositionVecSlice, OptionStyleBackgroundPosition);
482impl_vec_debug!(StyleBackgroundPosition, StyleBackgroundPositionVec);
483impl_vec_partialord!(StyleBackgroundPosition, StyleBackgroundPositionVec);
484impl_vec_ord!(StyleBackgroundPosition, StyleBackgroundPositionVec);
485impl_vec_clone!(
486 StyleBackgroundPosition,
487 StyleBackgroundPositionVec,
488 StyleBackgroundPositionVecDestructor
489);
490impl_vec_partialeq!(StyleBackgroundPosition, StyleBackgroundPositionVec);
491impl_vec_eq!(StyleBackgroundPosition, StyleBackgroundPositionVec);
492impl_vec_hash!(StyleBackgroundPosition, StyleBackgroundPositionVec);
493impl Default for StyleBackgroundPosition {
494 fn default() -> Self {
495 Self {
496 horizontal: BackgroundPositionHorizontal::Left,
497 vertical: BackgroundPositionVertical::Top,
498 }
499 }
500}
501
502impl StyleBackgroundPosition {
503 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
504 self.horizontal.scale_for_dpi(scale_factor);
505 self.vertical.scale_for_dpi(scale_factor);
506 }
507}
508
509impl PrintAsCssValue for StyleBackgroundPosition {
510 fn print_as_css_value(&self) -> String {
511 format!(
512 "{} {}",
513 self.horizontal.print_as_css_value(),
514 self.vertical.print_as_css_value()
515 )
516 }
517}
518impl PrintAsCssValue for StyleBackgroundPositionVec {
519 fn print_as_css_value(&self) -> String {
520 self.iter()
521 .map(|v| v.print_as_css_value())
522 .collect::<Vec<_>>()
523 .join(", ")
524 }
525}
526
527impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundPositionVec {
529 fn format_as_rust_code(&self, _tabs: usize) -> String {
530 format!(
531 "StyleBackgroundPositionVec::from_const_slice(STYLE_BACKGROUND_POSITION_{}_ITEMS)",
532 self.get_hash()
533 )
534 }
535}
536
537#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
539#[repr(C, u8)]
540pub enum BackgroundPositionHorizontal {
541 Left,
542 Center,
543 Right,
544 Exact(PixelValue),
545}
546
547impl BackgroundPositionHorizontal {
548 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
549 if let BackgroundPositionHorizontal::Exact(s) = self {
550 s.scale_for_dpi(scale_factor);
551 }
552 }
553}
554
555impl PrintAsCssValue for BackgroundPositionHorizontal {
556 fn print_as_css_value(&self) -> String {
557 match self {
558 Self::Left => "left".to_string(),
559 Self::Center => "center".to_string(),
560 Self::Right => "right".to_string(),
561 Self::Exact(px) => px.print_as_css_value(),
562 }
563 }
564}
565
566#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
568#[repr(C, u8)]
569pub enum BackgroundPositionVertical {
570 Top,
571 Center,
572 Bottom,
573 Exact(PixelValue),
574}
575
576impl BackgroundPositionVertical {
577 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
578 if let BackgroundPositionVertical::Exact(s) = self {
579 s.scale_for_dpi(scale_factor);
580 }
581 }
582}
583
584impl PrintAsCssValue for BackgroundPositionVertical {
585 fn print_as_css_value(&self) -> String {
586 match self {
587 Self::Top => "top".to_string(),
588 Self::Center => "center".to_string(),
589 Self::Bottom => "bottom".to_string(),
590 Self::Exact(px) => px.print_as_css_value(),
591 }
592 }
593}
594
595#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
597#[repr(C, u8)]
598#[derive(Default)]
599pub enum StyleBackgroundSize {
600 ExactSize(PixelValueSize),
601 #[default]
602 Contain,
603 Cover,
604}
605
606impl_option!(
607 StyleBackgroundSize,
608 OptionStyleBackgroundSize,
609 copy = false,
610 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
611);
612
613#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
616#[repr(C)]
617pub struct PixelValueSize {
618 pub width: PixelValue,
619 pub height: PixelValue,
620}
621
622impl_vec!(StyleBackgroundSize, StyleBackgroundSizeVec, StyleBackgroundSizeVecDestructor, StyleBackgroundSizeVecDestructorType, StyleBackgroundSizeVecSlice, OptionStyleBackgroundSize);
623impl_vec_debug!(StyleBackgroundSize, StyleBackgroundSizeVec);
624impl_vec_partialord!(StyleBackgroundSize, StyleBackgroundSizeVec);
625impl_vec_ord!(StyleBackgroundSize, StyleBackgroundSizeVec);
626impl_vec_clone!(
627 StyleBackgroundSize,
628 StyleBackgroundSizeVec,
629 StyleBackgroundSizeVecDestructor
630);
631impl_vec_partialeq!(StyleBackgroundSize, StyleBackgroundSizeVec);
632impl_vec_eq!(StyleBackgroundSize, StyleBackgroundSizeVec);
633impl_vec_hash!(StyleBackgroundSize, StyleBackgroundSizeVec);
634
635impl StyleBackgroundSize {
636 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
637 if let StyleBackgroundSize::ExactSize(size) = self {
638 size.width.scale_for_dpi(scale_factor);
639 size.height.scale_for_dpi(scale_factor);
640 }
641 }
642}
643
644impl PrintAsCssValue for StyleBackgroundSize {
645 fn print_as_css_value(&self) -> String {
646 match self {
647 Self::Contain => "contain".to_string(),
648 Self::Cover => "cover".to_string(),
649 Self::ExactSize(size) => {
650 format!(
651 "{} {}",
652 size.width.print_as_css_value(),
653 size.height.print_as_css_value()
654 )
655 }
656 }
657 }
658}
659impl PrintAsCssValue for StyleBackgroundSizeVec {
660 fn print_as_css_value(&self) -> String {
661 self.iter()
662 .map(|v| v.print_as_css_value())
663 .collect::<Vec<_>>()
664 .join(", ")
665 }
666}
667
668#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
670#[repr(C)]
671#[derive(Default)]
672pub enum StyleBackgroundRepeat {
673 NoRepeat,
674 #[default]
675 PatternRepeat,
676 RepeatX,
677 RepeatY,
678}
679
680impl_option!(
681 StyleBackgroundRepeat,
682 OptionStyleBackgroundRepeat,
683 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
684);
685impl_vec!(StyleBackgroundRepeat, StyleBackgroundRepeatVec, StyleBackgroundRepeatVecDestructor, StyleBackgroundRepeatVecDestructorType, StyleBackgroundRepeatVecSlice, OptionStyleBackgroundRepeat);
686impl_vec_debug!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
687impl_vec_partialord!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
688impl_vec_ord!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
689impl_vec_clone!(
690 StyleBackgroundRepeat,
691 StyleBackgroundRepeatVec,
692 StyleBackgroundRepeatVecDestructor
693);
694impl_vec_partialeq!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
695impl_vec_eq!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
696impl_vec_hash!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
697impl PrintAsCssValue for StyleBackgroundRepeat {
698 fn print_as_css_value(&self) -> String {
699 match self {
700 Self::NoRepeat => "no-repeat".to_string(),
701 Self::PatternRepeat => "repeat".to_string(),
702 Self::RepeatX => "repeat-x".to_string(),
703 Self::RepeatY => "repeat-y".to_string(),
704 }
705 }
706}
707impl PrintAsCssValue for StyleBackgroundRepeatVec {
708 fn print_as_css_value(&self) -> String {
709 self.iter()
710 .map(|v| v.print_as_css_value())
711 .collect::<Vec<_>>()
712 .join(", ")
713 }
714}
715
716#[derive(Clone, PartialEq)]
719pub enum CssBackgroundParseError<'a> {
720 Error(&'a str),
721 InvalidBackground(ParenthesisParseError<'a>),
722 UnclosedGradient(&'a str),
723 NoDirection(&'a str),
724 TooFewGradientStops(&'a str),
725 DirectionParseError(CssDirectionParseError<'a>),
726 GradientParseError(CssGradientStopParseError<'a>),
727 ConicGradient(CssConicGradientParseError<'a>),
728 ShapeParseError(CssShapeParseError<'a>),
729 ImageParseError(CssImageParseError<'a>),
730 ColorParseError(CssColorParseError<'a>),
731}
732
733impl_debug_as_display!(CssBackgroundParseError<'a>);
734impl_display! { CssBackgroundParseError<'a>, {
735 Error(e) => e,
736 InvalidBackground(val) => format!("Invalid background value: \"{}\"", val),
737 UnclosedGradient(val) => format!("Unclosed gradient: \"{}\"", val),
738 NoDirection(val) => format!("Gradient has no direction: \"{}\"", val),
739 TooFewGradientStops(val) => format!("Failed to parse gradient due to too few gradient steps: \"{}\"", val),
740 DirectionParseError(e) => format!("Failed to parse gradient direction: \"{}\"", e),
741 GradientParseError(e) => format!("Failed to parse gradient: {}", e),
742 ConicGradient(e) => format!("Failed to parse conic gradient: {}", e),
743 ShapeParseError(e) => format!("Failed to parse shape of radial gradient: {}", e),
744 ImageParseError(e) => format!("Failed to parse image() value: {}", e),
745 ColorParseError(e) => format!("Failed to parse color value: {}", e),
746}}
747
748#[cfg(feature = "parser")]
749impl_from!(
750 ParenthesisParseError<'a>,
751 CssBackgroundParseError::InvalidBackground
752);
753#[cfg(feature = "parser")]
754impl_from!(
755 CssDirectionParseError<'a>,
756 CssBackgroundParseError::DirectionParseError
757);
758#[cfg(feature = "parser")]
759impl_from!(
760 CssGradientStopParseError<'a>,
761 CssBackgroundParseError::GradientParseError
762);
763#[cfg(feature = "parser")]
764impl_from!(
765 CssShapeParseError<'a>,
766 CssBackgroundParseError::ShapeParseError
767);
768#[cfg(feature = "parser")]
769impl_from!(
770 CssImageParseError<'a>,
771 CssBackgroundParseError::ImageParseError
772);
773#[cfg(feature = "parser")]
774impl_from!(
775 CssColorParseError<'a>,
776 CssBackgroundParseError::ColorParseError
777);
778#[cfg(feature = "parser")]
779impl_from!(
780 CssConicGradientParseError<'a>,
781 CssBackgroundParseError::ConicGradient
782);
783
784#[derive(Debug, Clone, PartialEq)]
785#[repr(C, u8)]
786pub enum CssBackgroundParseErrorOwned {
787 Error(AzString),
788 InvalidBackground(ParenthesisParseErrorOwned),
789 UnclosedGradient(AzString),
790 NoDirection(AzString),
791 TooFewGradientStops(AzString),
792 DirectionParseError(CssDirectionParseErrorOwned),
793 GradientParseError(CssGradientStopParseErrorOwned),
794 ConicGradient(CssConicGradientParseErrorOwned),
795 ShapeParseError(CssShapeParseErrorOwned),
796 ImageParseError(CssImageParseErrorOwned),
797 ColorParseError(CssColorParseErrorOwned),
798}
799
800impl<'a> CssBackgroundParseError<'a> {
801 pub fn to_contained(&self) -> CssBackgroundParseErrorOwned {
802 match self {
803 Self::Error(s) => CssBackgroundParseErrorOwned::Error(s.to_string().into()),
804 Self::InvalidBackground(e) => {
805 CssBackgroundParseErrorOwned::InvalidBackground(e.to_contained())
806 }
807 Self::UnclosedGradient(s) => {
808 CssBackgroundParseErrorOwned::UnclosedGradient(s.to_string().into())
809 }
810 Self::NoDirection(s) => CssBackgroundParseErrorOwned::NoDirection(s.to_string().into()),
811 Self::TooFewGradientStops(s) => {
812 CssBackgroundParseErrorOwned::TooFewGradientStops(s.to_string().into())
813 }
814 Self::DirectionParseError(e) => {
815 CssBackgroundParseErrorOwned::DirectionParseError(e.to_contained())
816 }
817 Self::GradientParseError(e) => {
818 CssBackgroundParseErrorOwned::GradientParseError(e.to_contained())
819 }
820 Self::ConicGradient(e) => CssBackgroundParseErrorOwned::ConicGradient(e.to_contained()),
821 Self::ShapeParseError(e) => {
822 CssBackgroundParseErrorOwned::ShapeParseError(e.to_contained())
823 }
824 Self::ImageParseError(e) => {
825 CssBackgroundParseErrorOwned::ImageParseError(e.to_contained())
826 }
827 Self::ColorParseError(e) => {
828 CssBackgroundParseErrorOwned::ColorParseError(e.to_contained())
829 }
830 }
831 }
832}
833
834impl CssBackgroundParseErrorOwned {
835 pub fn to_shared<'a>(&'a self) -> CssBackgroundParseError<'a> {
836 match self {
837 Self::Error(s) => CssBackgroundParseError::Error(s),
838 Self::InvalidBackground(e) => CssBackgroundParseError::InvalidBackground(e.to_shared()),
839 Self::UnclosedGradient(s) => CssBackgroundParseError::UnclosedGradient(s),
840 Self::NoDirection(s) => CssBackgroundParseError::NoDirection(s),
841 Self::TooFewGradientStops(s) => CssBackgroundParseError::TooFewGradientStops(s),
842 Self::DirectionParseError(e) => {
843 CssBackgroundParseError::DirectionParseError(e.to_shared())
844 }
845 Self::GradientParseError(e) => {
846 CssBackgroundParseError::GradientParseError(e.to_shared())
847 }
848 Self::ConicGradient(e) => CssBackgroundParseError::ConicGradient(e.to_shared()),
849 Self::ShapeParseError(e) => CssBackgroundParseError::ShapeParseError(e.to_shared()),
850 Self::ImageParseError(e) => CssBackgroundParseError::ImageParseError(e.to_shared()),
851 Self::ColorParseError(e) => CssBackgroundParseError::ColorParseError(e.to_shared()),
852 }
853 }
854}
855
856#[derive(Clone, PartialEq)]
857pub enum CssGradientStopParseError<'a> {
858 Error(&'a str),
859 Percentage(PercentageParseError),
860 Angle(CssAngleValueParseError<'a>),
861 ColorParseError(CssColorParseError<'a>),
862}
863
864impl_debug_as_display!(CssGradientStopParseError<'a>);
865impl_display! { CssGradientStopParseError<'a>, {
866 Error(e) => e,
867 Percentage(e) => format!("Failed to parse offset percentage: {}", e),
868 Angle(e) => format!("Failed to parse angle: {}", e),
869 ColorParseError(e) => format!("{}", e),
870}}
871#[cfg(feature = "parser")]
872impl_from!(
873 CssColorParseError<'a>,
874 CssGradientStopParseError::ColorParseError
875);
876
877#[derive(Debug, Clone, PartialEq)]
878#[repr(C, u8)]
879pub enum CssGradientStopParseErrorOwned {
880 Error(AzString),
881 Percentage(PercentageParseErrorOwned),
882 Angle(CssAngleValueParseErrorOwned),
883 ColorParseError(CssColorParseErrorOwned),
884}
885
886impl<'a> CssGradientStopParseError<'a> {
887 pub fn to_contained(&self) -> CssGradientStopParseErrorOwned {
888 match self {
889 Self::Error(s) => CssGradientStopParseErrorOwned::Error(s.to_string().into()),
890 Self::Percentage(e) => CssGradientStopParseErrorOwned::Percentage(e.to_contained()),
891 Self::Angle(e) => CssGradientStopParseErrorOwned::Angle(e.to_contained()),
892 Self::ColorParseError(e) => {
893 CssGradientStopParseErrorOwned::ColorParseError(e.to_contained())
894 }
895 }
896 }
897}
898
899impl CssGradientStopParseErrorOwned {
900 pub fn to_shared<'a>(&'a self) -> CssGradientStopParseError<'a> {
901 match self {
902 Self::Error(s) => CssGradientStopParseError::Error(s),
903 Self::Percentage(e) => CssGradientStopParseError::Percentage(e.to_shared()),
904 Self::Angle(e) => CssGradientStopParseError::Angle(e.to_shared()),
905 Self::ColorParseError(e) => CssGradientStopParseError::ColorParseError(e.to_shared()),
906 }
907 }
908}
909
910#[derive(Clone, PartialEq)]
911pub enum CssConicGradientParseError<'a> {
912 Position(CssBackgroundPositionParseError<'a>),
913 Angle(CssAngleValueParseError<'a>),
914 NoAngle(&'a str),
915}
916impl_debug_as_display!(CssConicGradientParseError<'a>);
917impl_display! { CssConicGradientParseError<'a>, {
918 Position(val) => format!("Invalid position attribute: \"{}\"", val),
919 Angle(val) => format!("Invalid angle value: \"{}\"", val),
920 NoAngle(val) => format!("Expected angle: \"{}\"", val),
921}}
922#[cfg(feature = "parser")]
923impl_from!(
924 CssAngleValueParseError<'a>,
925 CssConicGradientParseError::Angle
926);
927#[cfg(feature = "parser")]
928impl_from!(
929 CssBackgroundPositionParseError<'a>,
930 CssConicGradientParseError::Position
931);
932
933#[derive(Debug, Clone, PartialEq)]
934#[repr(C, u8)]
935pub enum CssConicGradientParseErrorOwned {
936 Position(CssBackgroundPositionParseErrorOwned),
937 Angle(CssAngleValueParseErrorOwned),
938 NoAngle(AzString),
939}
940impl<'a> CssConicGradientParseError<'a> {
941 pub fn to_contained(&self) -> CssConicGradientParseErrorOwned {
942 match self {
943 Self::Position(e) => CssConicGradientParseErrorOwned::Position(e.to_contained()),
944 Self::Angle(e) => CssConicGradientParseErrorOwned::Angle(e.to_contained()),
945 Self::NoAngle(s) => CssConicGradientParseErrorOwned::NoAngle(s.to_string().into()),
946 }
947 }
948}
949impl CssConicGradientParseErrorOwned {
950 pub fn to_shared<'a>(&'a self) -> CssConicGradientParseError<'a> {
951 match self {
952 Self::Position(e) => CssConicGradientParseError::Position(e.to_shared()),
953 Self::Angle(e) => CssConicGradientParseError::Angle(e.to_shared()),
954 Self::NoAngle(s) => CssConicGradientParseError::NoAngle(s),
955 }
956 }
957}
958
959#[derive(Debug, Copy, Clone, PartialEq)]
960pub enum CssShapeParseError<'a> {
961 ShapeErr(InvalidValueErr<'a>),
962}
963impl_display! {CssShapeParseError<'a>, {
964 ShapeErr(e) => format!("\"{}\"", e.0),
965}}
966#[derive(Debug, Clone, PartialEq)]
967#[repr(C, u8)]
968pub enum CssShapeParseErrorOwned {
969 ShapeErr(InvalidValueErrOwned),
970}
971impl<'a> CssShapeParseError<'a> {
972 pub fn to_contained(&self) -> CssShapeParseErrorOwned {
973 match self {
974 Self::ShapeErr(err) => CssShapeParseErrorOwned::ShapeErr(err.to_contained()),
975 }
976 }
977}
978impl CssShapeParseErrorOwned {
979 pub fn to_shared<'a>(&'a self) -> CssShapeParseError<'a> {
980 match self {
981 Self::ShapeErr(err) => CssShapeParseError::ShapeErr(err.to_shared()),
982 }
983 }
984}
985
986#[derive(Debug, Clone, PartialEq)]
987pub enum CssBackgroundPositionParseError<'a> {
988 NoPosition(&'a str),
989 TooManyComponents(&'a str),
990 FirstComponentWrong(CssPixelValueParseError<'a>),
991 SecondComponentWrong(CssPixelValueParseError<'a>),
992}
993
994impl_display! {CssBackgroundPositionParseError<'a>, {
995 NoPosition(e) => format!("First background position missing: \"{}\"", e),
996 TooManyComponents(e) => format!("background-position can only have one or two components, not more: \"{}\"", e),
997 FirstComponentWrong(e) => format!("Failed to parse first component: \"{}\"", e),
998 SecondComponentWrong(e) => format!("Failed to parse second component: \"{}\"", e),
999}}
1000#[derive(Debug, Clone, PartialEq)]
1001#[repr(C, u8)]
1002pub enum CssBackgroundPositionParseErrorOwned {
1003 NoPosition(AzString),
1004 TooManyComponents(AzString),
1005 FirstComponentWrong(CssPixelValueParseErrorOwned),
1006 SecondComponentWrong(CssPixelValueParseErrorOwned),
1007}
1008impl<'a> CssBackgroundPositionParseError<'a> {
1009 pub fn to_contained(&self) -> CssBackgroundPositionParseErrorOwned {
1010 match self {
1011 Self::NoPosition(s) => CssBackgroundPositionParseErrorOwned::NoPosition(s.to_string().into()),
1012 Self::TooManyComponents(s) => {
1013 CssBackgroundPositionParseErrorOwned::TooManyComponents(s.to_string().into())
1014 }
1015 Self::FirstComponentWrong(e) => {
1016 CssBackgroundPositionParseErrorOwned::FirstComponentWrong(e.to_contained())
1017 }
1018 Self::SecondComponentWrong(e) => {
1019 CssBackgroundPositionParseErrorOwned::SecondComponentWrong(e.to_contained())
1020 }
1021 }
1022 }
1023}
1024impl CssBackgroundPositionParseErrorOwned {
1025 pub fn to_shared<'a>(&'a self) -> CssBackgroundPositionParseError<'a> {
1026 match self {
1027 Self::NoPosition(s) => CssBackgroundPositionParseError::NoPosition(s),
1028 Self::TooManyComponents(s) => CssBackgroundPositionParseError::TooManyComponents(s),
1029 Self::FirstComponentWrong(e) => {
1030 CssBackgroundPositionParseError::FirstComponentWrong(e.to_shared())
1031 }
1032 Self::SecondComponentWrong(e) => {
1033 CssBackgroundPositionParseError::SecondComponentWrong(e.to_shared())
1034 }
1035 }
1036 }
1037}
1038
1039#[cfg(feature = "parser")]
1042pub mod parser {
1043 use super::*;
1044
1045 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
1046 enum GradientType {
1047 LinearGradient,
1048 RepeatingLinearGradient,
1049 RadialGradient,
1050 RepeatingRadialGradient,
1051 ConicGradient,
1052 RepeatingConicGradient,
1053 }
1054
1055 impl GradientType {
1056 pub const fn get_extend_mode(&self) -> ExtendMode {
1057 match self {
1058 Self::LinearGradient | Self::RadialGradient | Self::ConicGradient => {
1059 ExtendMode::Clamp
1060 }
1061 Self::RepeatingLinearGradient
1062 | Self::RepeatingRadialGradient
1063 | Self::RepeatingConicGradient => ExtendMode::Repeat,
1064 }
1065 }
1066 }
1067
1068 pub fn parse_style_background_content_multiple<'a>(
1072 input: &'a str,
1073 ) -> Result<StyleBackgroundContentVec, CssBackgroundParseError<'a>> {
1074 Ok(split_string_respect_comma(input)
1075 .iter()
1076 .map(|i| parse_style_background_content(i))
1077 .collect::<Result<Vec<_>, _>>()?
1078 .into())
1079 }
1080
1081 pub fn parse_style_background_content<'a>(
1083 input: &'a str,
1084 ) -> Result<StyleBackgroundContent, CssBackgroundParseError<'a>> {
1085 match parse_parentheses(
1086 input,
1087 &[
1088 "linear-gradient",
1089 "repeating-linear-gradient",
1090 "radial-gradient",
1091 "repeating-radial-gradient",
1092 "conic-gradient",
1093 "repeating-conic-gradient",
1094 "image",
1095 "url",
1096 ],
1097 ) {
1098 Ok((background_type, brace_contents)) => {
1099 let gradient_type = match background_type {
1100 "linear-gradient" => GradientType::LinearGradient,
1101 "repeating-linear-gradient" => GradientType::RepeatingLinearGradient,
1102 "radial-gradient" => GradientType::RadialGradient,
1103 "repeating-radial-gradient" => GradientType::RepeatingRadialGradient,
1104 "conic-gradient" => GradientType::ConicGradient,
1105 "repeating-conic-gradient" => GradientType::RepeatingConicGradient,
1106 "image" | "url" => {
1107 return Ok(StyleBackgroundContent::Image(
1108 parse_image(brace_contents)?,
1109 ))
1110 }
1111 _ => unreachable!(),
1112 };
1113 parse_gradient(brace_contents, gradient_type)
1114 }
1115 Err(_) => Ok(StyleBackgroundContent::Color(parse_css_color(input)?)),
1116 }
1117 }
1118
1119 pub fn parse_style_background_position_multiple<'a>(
1121 input: &'a str,
1122 ) -> Result<StyleBackgroundPositionVec, CssBackgroundPositionParseError<'a>> {
1123 Ok(split_string_respect_comma(input)
1124 .iter()
1125 .map(|i| parse_style_background_position(i))
1126 .collect::<Result<Vec<_>, _>>()?
1127 .into())
1128 }
1129
1130 pub fn parse_style_background_position<'a>(
1132 input: &'a str,
1133 ) -> Result<StyleBackgroundPosition, CssBackgroundPositionParseError<'a>> {
1134 let input = input.trim();
1135 let mut whitespace_iter = input.split_whitespace();
1136
1137 let first = whitespace_iter
1138 .next()
1139 .ok_or(CssBackgroundPositionParseError::NoPosition(input))?;
1140 let second = whitespace_iter.next();
1141
1142 if whitespace_iter.next().is_some() {
1143 return Err(CssBackgroundPositionParseError::TooManyComponents(input));
1144 }
1145
1146 if let Ok(horizontal) = parse_background_position_horizontal(first) {
1148 let vertical = match second {
1149 Some(s) => parse_background_position_vertical(s)
1150 .map_err(CssBackgroundPositionParseError::SecondComponentWrong)?,
1151 None => BackgroundPositionVertical::Center,
1152 };
1153 return Ok(StyleBackgroundPosition {
1154 horizontal,
1155 vertical,
1156 });
1157 }
1158
1159 if let Ok(vertical) = parse_background_position_vertical(first) {
1161 let horizontal = match second {
1162 Some(s) => parse_background_position_horizontal(s)
1163 .map_err(CssBackgroundPositionParseError::FirstComponentWrong)?,
1164 None => BackgroundPositionHorizontal::Center,
1165 };
1166 return Ok(StyleBackgroundPosition {
1167 horizontal,
1168 vertical,
1169 });
1170 }
1171
1172 Err(CssBackgroundPositionParseError::FirstComponentWrong(
1173 CssPixelValueParseError::InvalidPixelValue(first),
1174 ))
1175 }
1176
1177 pub fn parse_style_background_size_multiple<'a>(
1179 input: &'a str,
1180 ) -> Result<StyleBackgroundSizeVec, InvalidValueErr<'a>> {
1181 Ok(split_string_respect_comma(input)
1182 .iter()
1183 .map(|i| parse_style_background_size(i))
1184 .collect::<Result<Vec<_>, _>>()?
1185 .into())
1186 }
1187
1188 pub fn parse_style_background_size<'a>(
1190 input: &'a str,
1191 ) -> Result<StyleBackgroundSize, InvalidValueErr<'a>> {
1192 let input = input.trim();
1193 match input {
1194 "contain" => Ok(StyleBackgroundSize::Contain),
1195 "cover" => Ok(StyleBackgroundSize::Cover),
1196 other => {
1197 let mut iter = other.split_whitespace();
1198 let x_val = iter.next().ok_or(InvalidValueErr(input))?;
1199 let x_pos = parse_pixel_value(x_val).map_err(|_| InvalidValueErr(input))?;
1200 let y_pos = match iter.next() {
1201 Some(y_val) => parse_pixel_value(y_val).map_err(|_| InvalidValueErr(input))?,
1202 None => x_pos, };
1204 Ok(StyleBackgroundSize::ExactSize(PixelValueSize {
1205 width: x_pos,
1206 height: y_pos,
1207 }))
1208 }
1209 }
1210 }
1211
1212 pub fn parse_style_background_repeat_multiple<'a>(
1214 input: &'a str,
1215 ) -> Result<StyleBackgroundRepeatVec, InvalidValueErr<'a>> {
1216 Ok(split_string_respect_comma(input)
1217 .iter()
1218 .map(|i| parse_style_background_repeat(i))
1219 .collect::<Result<Vec<_>, _>>()?
1220 .into())
1221 }
1222
1223 pub fn parse_style_background_repeat<'a>(
1225 input: &'a str,
1226 ) -> Result<StyleBackgroundRepeat, InvalidValueErr<'a>> {
1227 match input.trim() {
1228 "no-repeat" => Ok(StyleBackgroundRepeat::NoRepeat),
1229 "repeat" => Ok(StyleBackgroundRepeat::PatternRepeat),
1230 "repeat-x" => Ok(StyleBackgroundRepeat::RepeatX),
1231 "repeat-y" => Ok(StyleBackgroundRepeat::RepeatY),
1232 _ => Err(InvalidValueErr(input)),
1233 }
1234 }
1235
1236 fn parse_gradient<'a>(
1240 input: &'a str,
1241 gradient_type: GradientType,
1242 ) -> Result<StyleBackgroundContent, CssBackgroundParseError<'a>> {
1243 let input = input.trim();
1244 let comma_separated_items = split_string_respect_comma(input);
1245 let mut brace_iterator = comma_separated_items.iter();
1246 let first_brace_item = brace_iterator
1247 .next()
1248 .ok_or(CssBackgroundParseError::NoDirection(input))?;
1249
1250 match gradient_type {
1251 GradientType::LinearGradient | GradientType::RepeatingLinearGradient => {
1252 let mut linear_gradient = LinearGradient {
1253 extend_mode: gradient_type.get_extend_mode(),
1254 ..Default::default()
1255 };
1256 let mut linear_stops = Vec::new();
1257
1258 if let Ok(dir) = parse_direction(first_brace_item) {
1259 linear_gradient.direction = dir;
1260 } else {
1261 linear_stops.push(parse_linear_color_stop(first_brace_item)?);
1262 }
1263
1264 for item in brace_iterator {
1265 linear_stops.push(parse_linear_color_stop(item)?);
1266 }
1267
1268 linear_gradient.stops = get_normalized_linear_stops(&linear_stops).into();
1269 Ok(StyleBackgroundContent::LinearGradient(linear_gradient))
1270 }
1271 GradientType::RadialGradient | GradientType::RepeatingRadialGradient => {
1272 let mut radial_gradient = RadialGradient {
1275 extend_mode: gradient_type.get_extend_mode(),
1276 ..Default::default()
1277 };
1278 let mut radial_stops = Vec::new();
1279 let mut current_item = *first_brace_item;
1280 let mut items_consumed = false;
1281
1282 loop {
1284 let mut consumed_in_iteration = false;
1285 let mut temp_iter = current_item.split_whitespace();
1286 for word in temp_iter {
1287 if let Ok(shape) = parse_shape(word) {
1288 radial_gradient.shape = shape;
1289 consumed_in_iteration = true;
1290 } else if let Ok(size) = parse_radial_gradient_size(word) {
1291 radial_gradient.size = size;
1292 consumed_in_iteration = true;
1293 } else if let Ok(pos) = parse_style_background_position(current_item) {
1294 radial_gradient.position = pos;
1295 consumed_in_iteration = true;
1296 break; }
1299 }
1300 if consumed_in_iteration {
1301 if let Some(next_item) = brace_iterator.next() {
1302 current_item = next_item;
1303 items_consumed = true;
1304 } else {
1305 break;
1306 }
1307 } else {
1308 break;
1309 }
1310 }
1311
1312 if items_consumed || parse_linear_color_stop(current_item).is_ok() {
1313 radial_stops.push(parse_linear_color_stop(current_item)?);
1314 }
1315
1316 for item in brace_iterator {
1317 radial_stops.push(parse_linear_color_stop(item)?);
1318 }
1319
1320 radial_gradient.stops = get_normalized_linear_stops(&radial_stops).into();
1321 Ok(StyleBackgroundContent::RadialGradient(radial_gradient))
1322 }
1323 GradientType::ConicGradient | GradientType::RepeatingConicGradient => {
1324 let mut conic_gradient = ConicGradient {
1325 extend_mode: gradient_type.get_extend_mode(),
1326 ..Default::default()
1327 };
1328 let mut conic_stops = Vec::new();
1329
1330 if let Some((angle, center)) = parse_conic_first_item(first_brace_item)? {
1331 conic_gradient.angle = angle;
1332 conic_gradient.center = center;
1333 } else {
1334 conic_stops.push(parse_radial_color_stop(first_brace_item)?);
1335 }
1336
1337 for item in brace_iterator {
1338 conic_stops.push(parse_radial_color_stop(item)?);
1339 }
1340
1341 conic_gradient.stops = get_normalized_radial_stops(&conic_stops).into();
1342 Ok(StyleBackgroundContent::ConicGradient(conic_gradient))
1343 }
1344 }
1345 }
1346
1347 fn parse_linear_color_stop<'a>(
1356 input: &'a str,
1357 ) -> Result<LinearColorStop, CssGradientStopParseError<'a>> {
1358 let input = input.trim();
1359 let (color_str, offset1_str, offset2_str) = split_color_and_offsets(input);
1360
1361 let color = parse_color_or_system(color_str)?;
1362 let offset1 = match offset1_str {
1363 None => OptionPercentageValue::None,
1364 Some(s) => OptionPercentageValue::Some(
1365 parse_percentage_value(s).map_err(CssGradientStopParseError::Percentage)?,
1366 ),
1367 };
1368 let offset2 = match offset2_str {
1369 None => OptionPercentageValue::None,
1370 Some(s) => OptionPercentageValue::Some(
1371 parse_percentage_value(s).map_err(CssGradientStopParseError::Percentage)?,
1372 ),
1373 };
1374
1375 Ok(LinearColorStop {
1376 color,
1377 offset1,
1378 offset2,
1379 })
1380 }
1381
1382 fn parse_radial_color_stop<'a>(
1389 input: &'a str,
1390 ) -> Result<RadialColorStop, CssGradientStopParseError<'a>> {
1391 let input = input.trim();
1392 let (color_str, offset1_str, offset2_str) = split_color_and_offsets(input);
1393
1394 let color = parse_color_or_system(color_str)?;
1395 let offset1 = match offset1_str {
1396 None => OptionAngleValue::None,
1397 Some(s) => OptionAngleValue::Some(
1398 parse_angle_value(s).map_err(CssGradientStopParseError::Angle)?,
1399 ),
1400 };
1401 let offset2 = match offset2_str {
1402 None => OptionAngleValue::None,
1403 Some(s) => OptionAngleValue::Some(
1404 parse_angle_value(s).map_err(CssGradientStopParseError::Angle)?,
1405 ),
1406 };
1407
1408 Ok(RadialColorStop {
1409 color,
1410 offset1,
1411 offset2,
1412 })
1413 }
1414
1415 fn split_color_and_offsets(input: &str) -> (&str, Option<&str>, Option<&str>) {
1423 let input = input.trim();
1428
1429 if let Some((remaining, last_offset)) = try_split_last_offset(input) {
1431 if let Some((color_part, first_offset)) = try_split_last_offset(remaining) {
1433 return (color_part.trim(), Some(first_offset), Some(last_offset));
1434 }
1435 return (remaining.trim(), Some(last_offset), None);
1436 }
1437
1438 (input, None, None)
1439 }
1440
1441 fn try_split_last_offset(input: &str) -> Option<(&str, &str)> {
1444 let input = input.trim();
1445 if let Some(last_ws_idx) = input.rfind(char::is_whitespace) {
1446 let (potential_color, potential_offset) = input.split_at(last_ws_idx);
1447 let potential_offset = potential_offset.trim();
1448
1449 if is_likely_offset(potential_offset) {
1452 return Some((potential_color, potential_offset));
1453 }
1454 }
1455 None
1456 }
1457
1458 fn is_likely_offset(s: &str) -> bool {
1461 if !s.contains(|c: char| c.is_ascii_digit()) {
1462 return false;
1463 }
1464 let units = [
1466 "%", "px", "em", "rem", "ex", "ch", "vw", "vh", "vmin", "vmax", "cm", "mm", "in", "pt",
1467 "pc", "deg", "rad", "grad", "turn",
1468 ];
1469 units.iter().any(|u| s.ends_with(u))
1470 }
1471
1472 fn parse_conic_first_item<'a>(
1474 input: &'a str,
1475 ) -> Result<Option<(AngleValue, StyleBackgroundPosition)>, CssConicGradientParseError<'a>> {
1476 let input = input.trim();
1477 if !input.starts_with("from") {
1478 return Ok(None);
1479 }
1480
1481 let mut parts = input["from".len()..].trim().split("at");
1482 let angle_part = parts
1483 .next()
1484 .ok_or(CssConicGradientParseError::NoAngle(input))?
1485 .trim();
1486 let angle = parse_angle_value(angle_part)?;
1487
1488 let position = match parts.next() {
1489 Some(pos_part) => parse_style_background_position(pos_part.trim())?,
1490 None => StyleBackgroundPosition::default(),
1491 };
1492
1493 Ok(Some((angle, position)))
1494 }
1495
1496 macro_rules! impl_get_normalized_stops {
1499 (
1500 fn $fn_name:ident($input_stop:ty) -> Vec<$output_stop:ident>,
1501 pos_type = $pos_ty:ty,
1502 default_start = $default_start:expr,
1503 default_end = $default_end:expr,
1504 pos_ctor = $pos_ctor:expr,
1505 pos_to_f32 = $pos_to_f32:expr,
1506 output_field = $out_field:ident,
1507 ) => {
1508 fn $fn_name(stops: &[$input_stop]) -> Vec<$output_stop> {
1509 if stops.is_empty() {
1510 return Vec::new();
1511 }
1512
1513 let mut expanded: Vec<(ColorOrSystem, Option<$pos_ty>)> = Vec::new();
1514
1515 for stop in stops {
1516 match (stop.offset1.into_option(), stop.offset2.into_option()) {
1517 (None, _) => {
1518 expanded.push((stop.color, None));
1519 }
1520 (Some(pos1), None) => {
1521 expanded.push((stop.color, Some(pos1)));
1522 }
1523 (Some(pos1), Some(pos2)) => {
1524 expanded.push((stop.color, Some(pos1)));
1525 expanded.push((stop.color, Some(pos2)));
1526 }
1527 }
1528 }
1529
1530 if expanded.is_empty() {
1531 return Vec::new();
1532 }
1533
1534 let pos_ctor: fn(f32) -> $pos_ty = $pos_ctor;
1535 let pos_to_f32: fn(&$pos_ty) -> f32 = $pos_to_f32;
1536
1537 if expanded[0].1.is_none() {
1538 expanded[0].1 = Some(pos_ctor($default_start));
1539 }
1540 let last_idx = expanded.len() - 1;
1541 if expanded[last_idx].1.is_none() {
1542 expanded[last_idx].1 = Some(pos_ctor($default_end));
1543 }
1544
1545 let mut max_so_far: f32 = 0.0;
1546 for (_, pos) in expanded.iter_mut() {
1547 if let Some(p) = pos {
1548 let val = pos_to_f32(p);
1549 if val < max_so_far {
1550 *p = pos_ctor(max_so_far);
1551 } else {
1552 max_so_far = val;
1553 }
1554 }
1555 }
1556
1557 let mut i = 0;
1558 while i < expanded.len() {
1559 if expanded[i].1.is_none() {
1560 let run_start = i;
1561 let mut run_end = i;
1562 while run_end < expanded.len() && expanded[run_end].1.is_none() {
1563 run_end += 1;
1564 }
1565
1566 let prev_pos = if run_start > 0 {
1567 pos_to_f32(&expanded[run_start - 1].1.unwrap())
1568 } else {
1569 $default_start
1570 };
1571
1572 let next_pos = if run_end < expanded.len() {
1573 pos_to_f32(&expanded[run_end].1.unwrap())
1574 } else {
1575 $default_end
1576 };
1577
1578 let run_len = run_end - run_start;
1579 let step = (next_pos - prev_pos) / (run_len + 1) as f32;
1580
1581 for j in 0..run_len {
1582 expanded[run_start + j].1 =
1583 Some(pos_ctor(prev_pos + step * (j + 1) as f32));
1584 }
1585
1586 i = run_end;
1587 } else {
1588 i += 1;
1589 }
1590 }
1591
1592 expanded
1593 .into_iter()
1594 .map(|(color, pos)| {
1595 $output_stop {
1596 $out_field: pos.unwrap_or(pos_ctor($default_start)),
1597 color,
1598 }
1599 })
1600 .collect()
1601 }
1602 };
1603 }
1604
1605 impl_get_normalized_stops! {
1606 fn get_normalized_linear_stops(LinearColorStop) -> Vec<NormalizedLinearColorStop>,
1607 pos_type = PercentageValue,
1608 default_start = 0.0,
1609 default_end = 100.0,
1610 pos_ctor = (|v| PercentageValue::new(v)),
1611 pos_to_f32 = (|p: &PercentageValue| p.normalized() * 100.0),
1612 output_field = offset,
1613 }
1614
1615 impl_get_normalized_stops! {
1616 fn get_normalized_radial_stops(RadialColorStop) -> Vec<NormalizedRadialColorStop>,
1617 pos_type = AngleValue,
1618 default_start = 0.0,
1619 default_end = 360.0,
1620 pos_ctor = (|v| AngleValue::deg(v)),
1621 pos_to_f32 = (|p: &AngleValue| p.to_degrees_raw()),
1622 output_field = angle,
1623 }
1624
1625 fn parse_background_position_horizontal<'a>(
1628 input: &'a str,
1629 ) -> Result<BackgroundPositionHorizontal, CssPixelValueParseError<'a>> {
1630 Ok(match input {
1631 "left" => BackgroundPositionHorizontal::Left,
1632 "center" => BackgroundPositionHorizontal::Center,
1633 "right" => BackgroundPositionHorizontal::Right,
1634 other => BackgroundPositionHorizontal::Exact(parse_pixel_value(other)?),
1635 })
1636 }
1637
1638 fn parse_background_position_vertical<'a>(
1639 input: &'a str,
1640 ) -> Result<BackgroundPositionVertical, CssPixelValueParseError<'a>> {
1641 Ok(match input {
1642 "top" => BackgroundPositionVertical::Top,
1643 "center" => BackgroundPositionVertical::Center,
1644 "bottom" => BackgroundPositionVertical::Bottom,
1645 other => BackgroundPositionVertical::Exact(parse_pixel_value(other)?),
1646 })
1647 }
1648
1649 fn parse_shape<'a>(input: &'a str) -> Result<Shape, CssShapeParseError<'a>> {
1650 match input.trim() {
1651 "circle" => Ok(Shape::Circle),
1652 "ellipse" => Ok(Shape::Ellipse),
1653 _ => Err(CssShapeParseError::ShapeErr(InvalidValueErr(input))),
1654 }
1655 }
1656
1657 fn parse_radial_gradient_size<'a>(
1658 input: &'a str,
1659 ) -> Result<RadialGradientSize, InvalidValueErr<'a>> {
1660 match input.trim() {
1661 "closest-side" => Ok(RadialGradientSize::ClosestSide),
1662 "closest-corner" => Ok(RadialGradientSize::ClosestCorner),
1663 "farthest-side" => Ok(RadialGradientSize::FarthestSide),
1664 "farthest-corner" => Ok(RadialGradientSize::FarthestCorner),
1665 _ => Err(InvalidValueErr(input)),
1666 }
1667 }
1668}
1669
1670#[cfg(feature = "parser")]
1671pub use self::parser::*;
1672
1673#[cfg(all(test, feature = "parser"))]
1674mod tests {
1675 use super::*;
1676 use crate::props::basic::{DirectionCorner, DirectionCorners};
1677
1678 #[test]
1679 fn test_parse_single_background_content() {
1680 assert_eq!(
1682 parse_style_background_content("red").unwrap(),
1683 StyleBackgroundContent::Color(ColorU::RED)
1684 );
1685 assert_eq!(
1686 parse_style_background_content("#ff00ff").unwrap(),
1687 StyleBackgroundContent::Color(ColorU::new_rgb(255, 0, 255))
1688 );
1689
1690 assert_eq!(
1692 parse_style_background_content("url(\"image.png\")").unwrap(),
1693 StyleBackgroundContent::Image("image.png".into())
1694 );
1695
1696 let lg = parse_style_background_content("linear-gradient(to right, red, blue)").unwrap();
1698 assert!(matches!(lg, StyleBackgroundContent::LinearGradient(_)));
1699 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1700 assert_eq!(grad.stops.len(), 2);
1701 assert_eq!(
1702 grad.direction,
1703 Direction::FromTo(DirectionCorners {
1704 dir_from: DirectionCorner::Left,
1705 dir_to: DirectionCorner::Right
1706 })
1707 );
1708 }
1709
1710 let rg = parse_style_background_content("radial-gradient(circle, white, black)").unwrap();
1712 assert!(matches!(rg, StyleBackgroundContent::RadialGradient(_)));
1713 if let StyleBackgroundContent::RadialGradient(grad) = rg {
1714 assert_eq!(grad.stops.len(), 2);
1715 assert_eq!(grad.shape, Shape::Circle);
1716 }
1717
1718 let cg = parse_style_background_content("conic-gradient(from 90deg, red, blue)").unwrap();
1720 assert!(matches!(cg, StyleBackgroundContent::ConicGradient(_)));
1721 if let StyleBackgroundContent::ConicGradient(grad) = cg {
1722 assert_eq!(grad.stops.len(), 2);
1723 assert_eq!(grad.angle, AngleValue::deg(90.0));
1724 }
1725 }
1726
1727 #[test]
1728 fn test_parse_multiple_background_content() {
1729 let result =
1730 parse_style_background_content_multiple("url(foo.png), linear-gradient(red, blue)")
1731 .unwrap();
1732 assert_eq!(result.len(), 2);
1733 assert!(matches!(
1734 result.as_slice()[0],
1735 StyleBackgroundContent::Image(_)
1736 ));
1737 assert!(matches!(
1738 result.as_slice()[1],
1739 StyleBackgroundContent::LinearGradient(_)
1740 ));
1741 }
1742
1743 #[test]
1744 fn test_parse_background_position() {
1745 let result = parse_style_background_position("center").unwrap();
1747 assert_eq!(result.horizontal, BackgroundPositionHorizontal::Center);
1748 assert_eq!(result.vertical, BackgroundPositionVertical::Center);
1749
1750 let result = parse_style_background_position("25%").unwrap();
1751 assert_eq!(
1752 result.horizontal,
1753 BackgroundPositionHorizontal::Exact(PixelValue::percent(25.0))
1754 );
1755 assert_eq!(result.vertical, BackgroundPositionVertical::Center);
1756
1757 let result = parse_style_background_position("right 50px").unwrap();
1759 assert_eq!(result.horizontal, BackgroundPositionHorizontal::Right);
1760 assert_eq!(
1761 result.vertical,
1762 BackgroundPositionVertical::Exact(PixelValue::px(50.0))
1763 );
1764
1765 assert!(parse_style_background_position("left 10px top 20px").is_err());
1767 }
1768
1769 #[test]
1770 fn test_parse_background_size() {
1771 assert_eq!(
1772 parse_style_background_size("contain").unwrap(),
1773 StyleBackgroundSize::Contain
1774 );
1775 assert_eq!(
1776 parse_style_background_size("cover").unwrap(),
1777 StyleBackgroundSize::Cover
1778 );
1779 assert_eq!(
1780 parse_style_background_size("50%").unwrap(),
1781 StyleBackgroundSize::ExactSize(PixelValueSize {
1782 width: PixelValue::percent(50.0),
1783 height: PixelValue::percent(50.0)
1784 })
1785 );
1786 assert_eq!(
1787 parse_style_background_size("100px 20em").unwrap(),
1788 StyleBackgroundSize::ExactSize(PixelValueSize {
1789 width: PixelValue::px(100.0),
1790 height: PixelValue::em(20.0)
1791 })
1792 );
1793 assert!(parse_style_background_size("auto").is_err());
1794 }
1795
1796 #[test]
1797 fn test_parse_background_repeat() {
1798 assert_eq!(
1799 parse_style_background_repeat("repeat").unwrap(),
1800 StyleBackgroundRepeat::PatternRepeat
1801 );
1802 assert_eq!(
1803 parse_style_background_repeat("repeat-x").unwrap(),
1804 StyleBackgroundRepeat::RepeatX
1805 );
1806 assert_eq!(
1807 parse_style_background_repeat("repeat-y").unwrap(),
1808 StyleBackgroundRepeat::RepeatY
1809 );
1810 assert_eq!(
1811 parse_style_background_repeat("no-repeat").unwrap(),
1812 StyleBackgroundRepeat::NoRepeat
1813 );
1814 assert!(parse_style_background_repeat("repeat-xy").is_err());
1815 }
1816
1817 #[test]
1822 fn test_gradient_no_position_stops() {
1823 let lg = parse_style_background_content("linear-gradient(red, blue)").unwrap();
1825 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1826 assert_eq!(grad.stops.len(), 2);
1827 assert!((grad.stops.as_ref()[0].offset.normalized() - 0.0).abs() < 0.001);
1829 assert!((grad.stops.as_ref()[1].offset.normalized() - 1.0).abs() < 0.001);
1831 } else {
1832 panic!("Expected LinearGradient");
1833 }
1834 }
1835
1836 #[test]
1837 fn test_gradient_single_position_stops() {
1838 let lg = parse_style_background_content("linear-gradient(red 25%, blue 75%)").unwrap();
1840 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1841 assert_eq!(grad.stops.len(), 2);
1842 assert!((grad.stops.as_ref()[0].offset.normalized() - 0.25).abs() < 0.001);
1843 assert!((grad.stops.as_ref()[1].offset.normalized() - 0.75).abs() < 0.001);
1844 } else {
1845 panic!("Expected LinearGradient");
1846 }
1847 }
1848
1849 #[test]
1850 fn test_gradient_multi_position_stops() {
1851 let lg = parse_style_background_content("linear-gradient(red 10% 30%, blue)").unwrap();
1853 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1854 assert_eq!(grad.stops.len(), 3, "Expected 3 stops for multi-position");
1856 assert!((grad.stops.as_ref()[0].offset.normalized() - 0.10).abs() < 0.001);
1857 assert!((grad.stops.as_ref()[1].offset.normalized() - 0.30).abs() < 0.001);
1858 assert!((grad.stops.as_ref()[2].offset.normalized() - 1.0).abs() < 0.001);
1859 assert_eq!(grad.stops.as_ref()[0].color, grad.stops.as_ref()[1].color);
1861 } else {
1862 panic!("Expected LinearGradient");
1863 }
1864 }
1865
1866 #[test]
1867 fn test_gradient_three_colors_no_positions() {
1868 let lg = parse_style_background_content("linear-gradient(red, green, blue)").unwrap();
1870 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1871 assert_eq!(grad.stops.len(), 3);
1872 assert!((grad.stops.as_ref()[0].offset.normalized() - 0.0).abs() < 0.001);
1874 assert!((grad.stops.as_ref()[1].offset.normalized() - 0.5).abs() < 0.001);
1875 assert!((grad.stops.as_ref()[2].offset.normalized() - 1.0).abs() < 0.001);
1876 } else {
1877 panic!("Expected LinearGradient");
1878 }
1879 }
1880
1881 #[test]
1882 fn test_gradient_fixup_ascending_order() {
1883 let lg = parse_style_background_content("linear-gradient(red 50%, blue 20%)").unwrap();
1886 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1887 assert_eq!(grad.stops.len(), 2);
1888 assert!((grad.stops.as_ref()[0].offset.normalized() - 0.50).abs() < 0.001);
1890 assert!((grad.stops.as_ref()[1].offset.normalized() - 0.50).abs() < 0.001);
1892 } else {
1893 panic!("Expected LinearGradient");
1894 }
1895 }
1896
1897 #[test]
1898 fn test_gradient_distribute_unpositioned() {
1899 let lg =
1902 parse_style_background_content("linear-gradient(red 0%, yellow, green, blue 100%)")
1903 .unwrap();
1904 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1905 assert_eq!(grad.stops.len(), 4);
1906 assert!((grad.stops.as_ref()[0].offset.normalized() - 0.0).abs() < 0.001);
1908 assert!((grad.stops.as_ref()[1].offset.normalized() - 0.333).abs() < 0.01);
1909 assert!((grad.stops.as_ref()[2].offset.normalized() - 0.666).abs() < 0.01);
1910 assert!((grad.stops.as_ref()[3].offset.normalized() - 1.0).abs() < 0.001);
1911 } else {
1912 panic!("Expected LinearGradient");
1913 }
1914 }
1915
1916 #[test]
1917 fn test_gradient_direction_to_corner() {
1918 let lg =
1920 parse_style_background_content("linear-gradient(to top right, red, blue)").unwrap();
1921 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1922 assert_eq!(
1923 grad.direction,
1924 Direction::FromTo(DirectionCorners {
1925 dir_from: DirectionCorner::BottomLeft,
1926 dir_to: DirectionCorner::TopRight
1927 })
1928 );
1929 } else {
1930 panic!("Expected LinearGradient");
1931 }
1932 }
1933
1934 #[test]
1935 fn test_gradient_direction_angle() {
1936 let lg = parse_style_background_content("linear-gradient(45deg, red, blue)").unwrap();
1938 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1939 assert_eq!(grad.direction, Direction::Angle(AngleValue::deg(45.0)));
1940 } else {
1941 panic!("Expected LinearGradient");
1942 }
1943 }
1944
1945 #[test]
1946 fn test_repeating_gradient() {
1947 let lg =
1949 parse_style_background_content("repeating-linear-gradient(red, blue 20%)").unwrap();
1950 if let StyleBackgroundContent::LinearGradient(grad) = lg {
1951 assert_eq!(grad.extend_mode, ExtendMode::Repeat);
1952 } else {
1953 panic!("Expected LinearGradient");
1954 }
1955 }
1956
1957 #[test]
1958 fn test_radial_gradient_circle() {
1959 let rg = parse_style_background_content("radial-gradient(circle, red, blue)").unwrap();
1961 if let StyleBackgroundContent::RadialGradient(grad) = rg {
1962 assert_eq!(grad.shape, Shape::Circle);
1963 assert_eq!(grad.stops.len(), 2);
1964 assert_eq!(grad.position.horizontal, BackgroundPositionHorizontal::Left);
1966 assert_eq!(grad.position.vertical, BackgroundPositionVertical::Top);
1967 } else {
1968 panic!("Expected RadialGradient");
1969 }
1970 }
1971
1972 #[test]
1973 fn test_radial_gradient_ellipse() {
1974 let rg = parse_style_background_content("radial-gradient(ellipse, red, blue)").unwrap();
1976 if let StyleBackgroundContent::RadialGradient(grad) = rg {
1977 assert_eq!(grad.shape, Shape::Ellipse);
1978 assert_eq!(grad.stops.len(), 2);
1979 } else {
1980 panic!("Expected RadialGradient");
1981 }
1982 }
1983
1984 #[test]
1985 fn test_radial_gradient_size_keywords() {
1986 let rg = parse_style_background_content("radial-gradient(circle closest-side, red, blue)")
1988 .unwrap();
1989 if let StyleBackgroundContent::RadialGradient(grad) = rg {
1990 assert_eq!(grad.shape, Shape::Circle);
1991 assert_eq!(grad.size, RadialGradientSize::ClosestSide);
1992 } else {
1993 panic!("Expected RadialGradient");
1994 }
1995 }
1996
1997 #[test]
1998 fn test_radial_gradient_stop_positions() {
1999 let rg = parse_style_background_content("radial-gradient(red 0%, blue 100%)").unwrap();
2001 if let StyleBackgroundContent::RadialGradient(grad) = rg {
2002 assert_eq!(grad.stops.len(), 2);
2003 assert!((grad.stops.as_ref()[0].offset.normalized() - 0.0).abs() < 0.001);
2004 assert!((grad.stops.as_ref()[1].offset.normalized() - 1.0).abs() < 0.001);
2005 } else {
2006 panic!("Expected RadialGradient");
2007 }
2008 }
2009
2010 #[test]
2011 fn test_repeating_radial_gradient() {
2012 let rg = parse_style_background_content("repeating-radial-gradient(circle, red, blue 20%)")
2013 .unwrap();
2014 if let StyleBackgroundContent::RadialGradient(grad) = rg {
2015 assert_eq!(grad.extend_mode, ExtendMode::Repeat);
2016 assert_eq!(grad.shape, Shape::Circle);
2017 } else {
2018 panic!("Expected RadialGradient");
2019 }
2020 }
2021
2022 #[test]
2023 fn test_conic_gradient_angle() {
2024 let cg = parse_style_background_content("conic-gradient(from 45deg, red, blue)").unwrap();
2026 if let StyleBackgroundContent::ConicGradient(grad) = cg {
2027 assert_eq!(grad.angle, AngleValue::deg(45.0));
2028 assert_eq!(grad.stops.len(), 2);
2029 } else {
2030 panic!("Expected ConicGradient");
2031 }
2032 }
2033
2034 #[test]
2035 fn test_conic_gradient_default() {
2036 let cg = parse_style_background_content("conic-gradient(red, blue)").unwrap();
2038 if let StyleBackgroundContent::ConicGradient(grad) = cg {
2039 assert_eq!(grad.stops.len(), 2);
2040 assert!(
2042 (grad.stops.as_ref()[0].angle.to_degrees_raw() - 0.0).abs() < 0.001,
2043 "First stop should be 0deg, got {}",
2044 grad.stops.as_ref()[0].angle.to_degrees_raw()
2045 );
2046 assert!(
2048 (grad.stops.as_ref()[1].angle.to_degrees_raw() - 360.0).abs() < 0.001,
2049 "Last stop should be 360deg, got {}",
2050 grad.stops.as_ref()[1].angle.to_degrees_raw()
2051 );
2052 } else {
2053 panic!("Expected ConicGradient");
2054 }
2055 }
2056
2057 #[test]
2058 fn test_conic_gradient_with_positions() {
2059 let cg =
2061 parse_style_background_content("conic-gradient(red 0deg, blue 180deg, green 360deg)")
2062 .unwrap();
2063 if let StyleBackgroundContent::ConicGradient(grad) = cg {
2064 assert_eq!(grad.stops.len(), 3);
2065 assert!(
2067 (grad.stops.as_ref()[0].angle.to_degrees_raw() - 0.0).abs() < 0.001,
2068 "First stop should be 0deg, got {}",
2069 grad.stops.as_ref()[0].angle.to_degrees_raw()
2070 );
2071 assert!(
2072 (grad.stops.as_ref()[1].angle.to_degrees_raw() - 180.0).abs() < 0.001,
2073 "Second stop should be 180deg, got {}",
2074 grad.stops.as_ref()[1].angle.to_degrees_raw()
2075 );
2076 assert!(
2077 (grad.stops.as_ref()[2].angle.to_degrees_raw() - 360.0).abs() < 0.001,
2078 "Last stop should be 360deg, got {}",
2079 grad.stops.as_ref()[2].angle.to_degrees_raw()
2080 );
2081 } else {
2082 panic!("Expected ConicGradient");
2083 }
2084 }
2085
2086 #[test]
2087 fn test_repeating_conic_gradient() {
2088 let cg =
2089 parse_style_background_content("repeating-conic-gradient(red, blue 30deg)").unwrap();
2090 if let StyleBackgroundContent::ConicGradient(grad) = cg {
2091 assert_eq!(grad.extend_mode, ExtendMode::Repeat);
2092 } else {
2093 panic!("Expected ConicGradient");
2094 }
2095 }
2096
2097 #[test]
2098 fn test_gradient_with_rgba_color() {
2099 let lg =
2101 parse_style_background_content("linear-gradient(rgba(255,0,0,0.5), blue)").unwrap();
2102 if let StyleBackgroundContent::LinearGradient(grad) = lg {
2103 assert_eq!(grad.stops.len(), 2);
2104 let first_color = grad.stops.as_ref()[0].color.to_color_u_default();
2106 assert!(first_color.a >= 127 && first_color.a <= 128);
2107 } else {
2108 panic!("Expected LinearGradient");
2109 }
2110 }
2111
2112 #[test]
2113 fn test_gradient_with_rgba_and_position() {
2114 let lg =
2116 parse_style_background_content("linear-gradient(rgba(0,0,0,0.5) 50%, white)").unwrap();
2117 if let StyleBackgroundContent::LinearGradient(grad) = lg {
2118 assert_eq!(grad.stops.len(), 2);
2119 assert!((grad.stops.as_ref()[0].offset.normalized() - 0.5).abs() < 0.001);
2120 } else {
2121 panic!("Expected LinearGradient");
2122 }
2123 }
2124
2125 #[test]
2126 fn test_gradient_resolves_system_color_stop() {
2127 use crate::props::basic::color::ColorOrSystem;
2132 use crate::system::SystemColors;
2133
2134 let lg = parse_style_background_content(
2135 "linear-gradient(red, system:accent)",
2136 )
2137 .unwrap();
2138 let StyleBackgroundContent::LinearGradient(grad) = lg else {
2139 panic!("Expected LinearGradient");
2140 };
2141 let stops = grad.stops.as_ref();
2142 assert_eq!(stops.len(), 2);
2143
2144 let accent_stop = &stops[1];
2145 assert!(matches!(accent_stop.color, ColorOrSystem::System(_)));
2146
2147 let mut populated = SystemColors::default();
2148 populated.accent =
2149 crate::props::basic::color::OptionColorU::Some(ColorU::new_rgb(0, 122, 255));
2150
2151 let resolved = accent_stop.resolve(&populated, ColorU::TRANSPARENT);
2152 assert_eq!(resolved, ColorU::new_rgb(0, 122, 255));
2153
2154 let empty = SystemColors::default();
2155 let fallback = accent_stop.resolve(&empty, ColorU::TRANSPARENT);
2156 assert_eq!(fallback, ColorU::TRANSPARENT);
2157 }
2158}