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