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