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