1use alloc::{
9 string::{String, ToString},
10 vec::Vec,
11};
12use core::{fmt, num::ParseFloatError};
13
14#[cfg(feature = "parser")]
15use crate::props::basic::{
16 error::{InvalidValueErr, InvalidValueErrOwned, WrongComponentCountError},
17 length::parse_float_value,
18 parse::{parse_parentheses, ParenthesisParseError, ParenthesisParseErrorOwned},
19};
20use crate::{
21 codegen::format::GetHash,
22 props::{
23 basic::{
24 angle::{AngleValue, parse_angle_value, CssAngleValueParseError, CssAngleValueParseErrorOwned},
25 color::{parse_css_color, ColorU, CssColorParseError, CssColorParseErrorOwned},
26 length::{FloatValue, PercentageParseError, PercentageValue},
27 pixel::{
28 parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned,
29 PixelValue,
30 },
31 },
32 formatter::PrintAsCssValue,
33 style::{
34 box_shadow::{
35 parse_style_box_shadow, CssShadowParseError, CssShadowParseErrorOwned,
36 StyleBoxShadow,
37 },
38 effects::{parse_style_mix_blend_mode, MixBlendModeParseError, StyleMixBlendMode},
39 },
40 },
41};
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46#[repr(C, u8)]
47pub enum StyleFilter {
48 Blend(StyleMixBlendMode),
49 Flood(ColorU),
50 Blur(StyleBlur),
51 Opacity(PercentageValue),
52 ColorMatrix(StyleColorMatrix),
53 DropShadow(StyleBoxShadow),
54 ComponentTransfer,
55 Offset(StyleFilterOffset),
56 Composite(StyleCompositeFilter),
57 Brightness(PercentageValue),
59 Contrast(PercentageValue),
60 Grayscale(PercentageValue),
61 HueRotate(AngleValue),
62 Invert(PercentageValue),
63 Saturate(PercentageValue),
64 Sepia(PercentageValue),
65}
66
67impl_option!(
68 StyleFilter,
69 OptionStyleFilter,
70 copy = false,
71 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
72);
73
74impl_vec!(StyleFilter, StyleFilterVec, StyleFilterVecDestructor, StyleFilterVecDestructorType, StyleFilterVecSlice, OptionStyleFilter);
75impl_vec_clone!(StyleFilter, StyleFilterVec, StyleFilterVecDestructor);
76impl_vec_debug!(StyleFilter, StyleFilterVec);
77impl_vec_eq!(StyleFilter, StyleFilterVec);
78impl_vec_ord!(StyleFilter, StyleFilterVec);
79impl_vec_hash!(StyleFilter, StyleFilterVec);
80impl_vec_partialeq!(StyleFilter, StyleFilterVec);
81impl_vec_partialord!(StyleFilter, StyleFilterVec);
82
83#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
84#[repr(C)]
85pub struct StyleBlur {
86 pub width: PixelValue,
87 pub height: PixelValue,
88}
89
90#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93#[repr(C)]
94pub struct StyleColorMatrix {
95 pub m0: FloatValue,
96 pub m1: FloatValue,
97 pub m2: FloatValue,
98 pub m3: FloatValue,
99 pub m4: FloatValue,
100 pub m5: FloatValue,
101 pub m6: FloatValue,
102 pub m7: FloatValue,
103 pub m8: FloatValue,
104 pub m9: FloatValue,
105 pub m10: FloatValue,
106 pub m11: FloatValue,
107 pub m12: FloatValue,
108 pub m13: FloatValue,
109 pub m14: FloatValue,
110 pub m15: FloatValue,
111 pub m16: FloatValue,
112 pub m17: FloatValue,
113 pub m18: FloatValue,
114 pub m19: FloatValue,
115}
116
117impl StyleColorMatrix {
118 #[must_use] pub const fn to_array(&self) -> [FloatValue; 20] {
119 [
120 self.m0, self.m1, self.m2, self.m3, self.m4, self.m5, self.m6, self.m7, self.m8,
121 self.m9, self.m10, self.m11, self.m12, self.m13, self.m14, self.m15, self.m16,
122 self.m17, self.m18, self.m19,
123 ]
124 }
125}
126
127#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
128#[repr(C)]
129pub struct StyleFilterOffset {
130 pub x: PixelValue,
131 pub y: PixelValue,
132}
133
134#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
137#[repr(C)]
138pub struct ArithmeticCoefficients {
139 pub k1: FloatValue,
140 pub k2: FloatValue,
141 pub k3: FloatValue,
142 pub k4: FloatValue,
143}
144#[allow(variant_size_differences)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
146#[repr(C, u8)]
147pub enum StyleCompositeFilter {
148 Over,
149 In,
150 Atop,
151 Out,
152 Xor,
153 Lighter,
154 Arithmetic(ArithmeticCoefficients),
155}
156
157impl PrintAsCssValue for StyleFilterVec {
160 fn print_as_css_value(&self) -> String {
161 self.as_ref()
162 .iter()
163 .map(PrintAsCssValue::print_as_css_value)
164 .collect::<Vec<_>>()
165 .join(" ")
166 }
167}
168
169impl crate::codegen::format::FormatAsRustCode for StyleFilterVec {
171 fn format_as_rust_code(&self, _tabs: usize) -> String {
172 format!(
173 "StyleFilterVec::from_const_slice(STYLE_FILTER_{}_ITEMS)",
174 self.get_hash()
175 )
176 }
177}
178
179impl PrintAsCssValue for StyleFilter {
180 fn print_as_css_value(&self) -> String {
181 match self {
182 Self::Blend(mode) => format!("blend({})", mode.print_as_css_value()),
183 Self::Flood(c) => format!("flood({})", c.to_hash()),
184 Self::Blur(c) => {
185 if c.width == c.height {
186 format!("blur({})", c.width)
187 } else {
188 format!("blur({} {})", c.width, c.height)
189 }
190 }
191 Self::Opacity(c) => format!("opacity({c})"),
192 Self::ColorMatrix(c) => format!(
193 "color-matrix({})",
194 c.to_array()
195 .iter()
196 .map(|s| format!("{s}"))
197 .collect::<Vec<_>>()
198 .join(" ")
199 ),
200 Self::DropShadow(shadow) => {
201 format!("drop-shadow({})", shadow.print_as_css_value())
202 }
203 Self::ComponentTransfer => "component-transfer".to_string(),
204 Self::Offset(o) => format!("offset({} {})", o.x, o.y),
205 Self::Composite(c) => format!("composite({})", c.print_as_css_value()),
206 Self::Brightness(v) => format!("brightness({v})"),
207 Self::Contrast(v) => format!("contrast({v})"),
208 Self::Grayscale(v) => format!("grayscale({v})"),
209 Self::HueRotate(a) => format!("hue-rotate({a})"),
210 Self::Invert(v) => format!("invert({v})"),
211 Self::Saturate(v) => format!("saturate({v})"),
212 Self::Sepia(v) => format!("sepia({v})"),
213 }
214 }
215}
216
217impl PrintAsCssValue for StyleCompositeFilter {
218 fn print_as_css_value(&self) -> String {
219 match self {
220 Self::Over => "over".to_string(),
221 Self::In => "in".to_string(),
222 Self::Atop => "atop".to_string(),
223 Self::Out => "out".to_string(),
224 Self::Xor => "xor".to_string(),
225 Self::Lighter => "lighter".to_string(),
226 Self::Arithmetic(fv) => {
227 format!("arithmetic {} {} {} {}", fv.k1, fv.k2, fv.k3, fv.k4)
228 }
229 }
230 }
231}
232
233#[cfg(feature = "parser")]
236pub mod parser {
237 #[allow(clippy::wildcard_imports)] use super::*;
239 use crate::props::basic::parse_percentage_value;
240 use crate::corety::AzString;
241
242 #[derive(Clone, PartialEq)]
245 pub enum CssStyleFilterParseError<'a> {
246 InvalidFilter(&'a str),
247 InvalidParenthesis(ParenthesisParseError<'a>),
248 Shadow(CssShadowParseError<'a>),
249 BlendMode(InvalidValueErr<'a>),
250 Color(CssColorParseError<'a>),
251 Opacity(PercentageParseError),
252 Brightness(PercentageParseError),
253 Contrast(PercentageParseError),
254 Saturate(PercentageParseError),
255 Blur(CssStyleBlurParseError<'a>),
256 ColorMatrix(CssStyleColorMatrixParseError<'a>),
257 Offset(CssStyleFilterOffsetParseError<'a>),
258 Composite(CssStyleCompositeFilterParseError<'a>),
259 Angle(CssAngleValueParseError<'a>),
260 }
261
262 impl_debug_as_display!(CssStyleFilterParseError<'a>);
263 impl_display! { CssStyleFilterParseError<'a>, {
264 InvalidFilter(e) => format!("Invalid filter function: \"{}\"", e),
265 InvalidParenthesis(e) => format!("Invalid filter syntax - parenthesis error: {}", e),
266 Shadow(e) => format!("Error parsing drop-shadow(): {}", e),
267 BlendMode(e) => format!("Error parsing blend(): invalid value \"{}\"", e.0),
268 Color(e) => format!("Error parsing flood(): {}", e),
269 Opacity(e) => format!("Error parsing opacity(): {}", e),
270 Brightness(e) => format!("Error parsing brightness(): {}", e),
271 Contrast(e) => format!("Error parsing contrast(): {}", e),
272 Saturate(e) => format!("Error parsing saturate(): {}", e),
273 Blur(e) => format!("Error parsing blur(): {}", e),
274 ColorMatrix(e) => format!("Error parsing color-matrix(): {}", e),
275 Offset(e) => format!("Error parsing offset(): {}", e),
276 Composite(e) => format!("Error parsing composite(): {}", e),
277 Angle(e) => format!("Error parsing hue-rotate(): {}", e),
278 }}
279
280 impl_from!(
281 ParenthesisParseError<'a>,
282 CssStyleFilterParseError::InvalidParenthesis
283 );
284 impl_from!(InvalidValueErr<'a>, CssStyleFilterParseError::BlendMode);
285 impl_from!(CssStyleBlurParseError<'a>, CssStyleFilterParseError::Blur);
286 impl_from!(CssColorParseError<'a>, CssStyleFilterParseError::Color);
287 impl_from!(
288 CssStyleColorMatrixParseError<'a>,
289 CssStyleFilterParseError::ColorMatrix
290 );
291 impl_from!(
292 CssStyleFilterOffsetParseError<'a>,
293 CssStyleFilterParseError::Offset
294 );
295 impl_from!(
296 CssStyleCompositeFilterParseError<'a>,
297 CssStyleFilterParseError::Composite
298 );
299 impl_from!(CssShadowParseError<'a>, CssStyleFilterParseError::Shadow);
300 impl_from!(CssAngleValueParseError<'a>, CssStyleFilterParseError::Angle);
301
302 impl From<PercentageParseError> for CssStyleFilterParseError<'_> {
303 fn from(p: PercentageParseError) -> Self {
304 Self::Opacity(p)
305 }
306 }
307
308 impl<'a> From<MixBlendModeParseError<'a>> for CssStyleFilterParseError<'a> {
309 fn from(e: MixBlendModeParseError<'a>) -> Self {
310 match e {
312 MixBlendModeParseError::InvalidValue(err) => Self::BlendMode(err),
313 }
314 }
315 }
316
317 #[derive(Debug, Clone, PartialEq)]
318 #[repr(C, u8)]
319 pub enum CssStyleFilterParseErrorOwned {
320 InvalidFilter(AzString),
321 InvalidParenthesis(ParenthesisParseErrorOwned),
322 Shadow(CssShadowParseErrorOwned),
323 BlendMode(InvalidValueErrOwned),
324 Color(CssColorParseErrorOwned),
325 Opacity(PercentageParseError),
326 Brightness(PercentageParseError),
327 Contrast(PercentageParseError),
328 Saturate(PercentageParseError),
329 Blur(CssStyleBlurParseErrorOwned),
330 ColorMatrix(CssStyleColorMatrixParseErrorOwned),
331 Offset(CssStyleFilterOffsetParseErrorOwned),
332 Composite(CssStyleCompositeFilterParseErrorOwned),
333 Angle(CssAngleValueParseErrorOwned),
334 }
335
336 impl CssStyleFilterParseError<'_> {
337 #[must_use] pub fn to_contained(&self) -> CssStyleFilterParseErrorOwned {
338 match self {
339 Self::InvalidFilter(s) => {
340 CssStyleFilterParseErrorOwned::InvalidFilter((*s).to_string().into())
341 }
342 Self::InvalidParenthesis(e) => {
343 CssStyleFilterParseErrorOwned::InvalidParenthesis(e.to_contained())
344 }
345 Self::Shadow(e) => CssStyleFilterParseErrorOwned::Shadow(e.to_contained()),
346 Self::BlendMode(e) => CssStyleFilterParseErrorOwned::BlendMode(e.to_contained()),
347 Self::Color(e) => CssStyleFilterParseErrorOwned::Color(e.to_contained()),
348 Self::Opacity(e) => CssStyleFilterParseErrorOwned::Opacity(e.clone()),
349 Self::Brightness(e) => CssStyleFilterParseErrorOwned::Brightness(e.clone()),
350 Self::Contrast(e) => CssStyleFilterParseErrorOwned::Contrast(e.clone()),
351 Self::Saturate(e) => CssStyleFilterParseErrorOwned::Saturate(e.clone()),
352 Self::Blur(e) => CssStyleFilterParseErrorOwned::Blur(e.to_contained()),
353 Self::ColorMatrix(e) => {
354 CssStyleFilterParseErrorOwned::ColorMatrix(e.to_contained())
355 }
356 Self::Offset(e) => CssStyleFilterParseErrorOwned::Offset(e.to_contained()),
357 Self::Composite(e) => CssStyleFilterParseErrorOwned::Composite(e.to_contained()),
358 Self::Angle(e) => CssStyleFilterParseErrorOwned::Angle(e.to_contained()),
359 }
360 }
361 }
362
363 impl CssStyleFilterParseErrorOwned {
364 #[must_use] pub fn to_shared(&self) -> CssStyleFilterParseError<'_> {
365 match self {
366 Self::InvalidFilter(s) => CssStyleFilterParseError::InvalidFilter(s),
367 Self::InvalidParenthesis(e) => {
368 CssStyleFilterParseError::InvalidParenthesis(e.to_shared())
369 }
370 Self::Shadow(e) => CssStyleFilterParseError::Shadow(e.to_shared()),
371 Self::BlendMode(e) => CssStyleFilterParseError::BlendMode(e.to_shared()),
372 Self::Color(e) => CssStyleFilterParseError::Color(e.to_shared()),
373 Self::Opacity(e) => CssStyleFilterParseError::Opacity(e.clone()),
374 Self::Brightness(e) => CssStyleFilterParseError::Brightness(e.clone()),
375 Self::Contrast(e) => CssStyleFilterParseError::Contrast(e.clone()),
376 Self::Saturate(e) => CssStyleFilterParseError::Saturate(e.clone()),
377 Self::Blur(e) => CssStyleFilterParseError::Blur(e.to_shared()),
378 Self::ColorMatrix(e) => CssStyleFilterParseError::ColorMatrix(e.to_shared()),
379 Self::Offset(e) => CssStyleFilterParseError::Offset(e.to_shared()),
380 Self::Composite(e) => CssStyleFilterParseError::Composite(e.to_shared()),
381 Self::Angle(e) => CssStyleFilterParseError::Angle(e.to_shared()),
382 }
383 }
384 }
385
386 #[derive(Clone, PartialEq, Eq)]
389 pub enum CssStyleBlurParseError<'a> {
390 Pixel(CssPixelValueParseError<'a>),
391 TooManyComponents(&'a str),
392 }
393
394 impl_debug_as_display!(CssStyleBlurParseError<'a>);
395 impl_display! { CssStyleBlurParseError<'a>, {
396 Pixel(e) => format!("Invalid pixel value: {}", e),
397 TooManyComponents(input) => format!("Expected 1 or 2 components, got more: \"{}\"", input),
398 }}
399 impl_from!(CssPixelValueParseError<'a>, CssStyleBlurParseError::Pixel);
400
401 #[derive(Debug, Clone, PartialEq, Eq)]
402 #[repr(C, u8)]
403 pub enum CssStyleBlurParseErrorOwned {
404 Pixel(CssPixelValueParseErrorOwned),
405 TooManyComponents(AzString),
406 }
407
408 impl CssStyleBlurParseError<'_> {
409 #[must_use] pub fn to_contained(&self) -> CssStyleBlurParseErrorOwned {
410 match self {
411 Self::Pixel(e) => CssStyleBlurParseErrorOwned::Pixel(e.to_contained()),
412 Self::TooManyComponents(s) => {
413 CssStyleBlurParseErrorOwned::TooManyComponents((*s).to_string().into())
414 }
415 }
416 }
417 }
418
419 impl CssStyleBlurParseErrorOwned {
420 #[must_use] pub fn to_shared(&self) -> CssStyleBlurParseError<'_> {
421 match self {
422 Self::Pixel(e) => CssStyleBlurParseError::Pixel(e.to_shared()),
423 Self::TooManyComponents(s) => CssStyleBlurParseError::TooManyComponents(s),
424 }
425 }
426 }
427
428 #[derive(Clone, PartialEq, Eq)]
429 pub enum CssStyleColorMatrixParseError<'a> {
430 Float(ParseFloatError),
431 WrongNumberOfComponents {
432 expected: usize,
433 got: usize,
434 input: &'a str,
435 },
436 }
437
438 impl_debug_as_display!(CssStyleColorMatrixParseError<'a>);
439 impl_display! { CssStyleColorMatrixParseError<'a>, {
440 Float(e) => format!("Error parsing floating-point value: {}", e),
441 WrongNumberOfComponents { expected, got, input } => format!("Expected {} components, got {}: \"{}\"", expected, got, input),
442 }}
443 impl From<ParseFloatError> for CssStyleColorMatrixParseError<'_> {
444 fn from(p: ParseFloatError) -> Self {
445 Self::Float(p)
446 }
447 }
448 #[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, Eq)]
450 #[repr(C, u8)]
451 pub enum CssStyleColorMatrixParseErrorOwned {
452 Float(crate::props::basic::error::ParseFloatError),
453 WrongNumberOfComponents(WrongComponentCountError),
454 }
455
456 impl CssStyleColorMatrixParseError<'_> {
457 #[must_use] pub fn to_contained(&self) -> CssStyleColorMatrixParseErrorOwned {
458 match self {
459 Self::Float(e) => CssStyleColorMatrixParseErrorOwned::Float(e.clone().into()),
460 Self::WrongNumberOfComponents {
461 expected,
462 got,
463 input,
464 } => CssStyleColorMatrixParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
465 expected: *expected,
466 got: *got,
467 input: (*input).to_string().into(),
468 }),
469 }
470 }
471 }
472
473 impl CssStyleColorMatrixParseErrorOwned {
474 #[must_use] pub fn to_shared(&self) -> CssStyleColorMatrixParseError<'_> {
475 match self {
476 Self::Float(e) => CssStyleColorMatrixParseError::Float(e.to_std()),
477 Self::WrongNumberOfComponents(e) => CssStyleColorMatrixParseError::WrongNumberOfComponents {
478 expected: e.expected,
479 got: e.got,
480 input: e.input.as_str(),
481 },
482 }
483 }
484 }
485
486 #[derive(Clone, PartialEq, Eq)]
487 pub enum CssStyleFilterOffsetParseError<'a> {
488 Pixel(CssPixelValueParseError<'a>),
489 WrongNumberOfComponents {
490 expected: usize,
491 got: usize,
492 input: &'a str,
493 },
494 }
495
496 impl_debug_as_display!(CssStyleFilterOffsetParseError<'a>);
497 impl_display! { CssStyleFilterOffsetParseError<'a>, {
498 Pixel(e) => format!("Invalid pixel value: {}", e),
499 WrongNumberOfComponents { expected, got, input } => format!("Expected {} components, got {}: \"{}\"", expected, got, input),
500 }}
501 impl_from!(
502 CssPixelValueParseError<'a>,
503 CssStyleFilterOffsetParseError::Pixel
504 );
505
506 #[derive(Debug, Clone, PartialEq, Eq)]
507 #[repr(C, u8)]
508 pub enum CssStyleFilterOffsetParseErrorOwned {
509 Pixel(CssPixelValueParseErrorOwned),
510 WrongNumberOfComponents(WrongComponentCountError),
511 }
512
513 impl CssStyleFilterOffsetParseError<'_> {
514 #[must_use] pub fn to_contained(&self) -> CssStyleFilterOffsetParseErrorOwned {
515 match self {
516 Self::Pixel(e) => CssStyleFilterOffsetParseErrorOwned::Pixel(e.to_contained()),
517 Self::WrongNumberOfComponents {
518 expected,
519 got,
520 input,
521 } => CssStyleFilterOffsetParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
522 expected: *expected,
523 got: *got,
524 input: (*input).to_string().into(),
525 }),
526 }
527 }
528 }
529
530 impl CssStyleFilterOffsetParseErrorOwned {
531 #[must_use] pub fn to_shared(&self) -> CssStyleFilterOffsetParseError<'_> {
532 match self {
533 Self::Pixel(e) => CssStyleFilterOffsetParseError::Pixel(e.to_shared()),
534 Self::WrongNumberOfComponents(e) => CssStyleFilterOffsetParseError::WrongNumberOfComponents {
535 expected: e.expected,
536 got: e.got,
537 input: e.input.as_str(),
538 },
539 }
540 }
541 }
542
543 #[derive(Clone, PartialEq, Eq)]
544 pub enum CssStyleCompositeFilterParseError<'a> {
545 Invalid(InvalidValueErr<'a>),
546 Float(ParseFloatError),
547 WrongNumberOfComponents {
548 expected: usize,
549 got: usize,
550 input: &'a str,
551 },
552 }
553
554 impl_debug_as_display!(CssStyleCompositeFilterParseError<'a>);
555 impl_display! { CssStyleCompositeFilterParseError<'a>, {
556 Invalid(s) => format!("Invalid composite operator: {}", s.0),
557 Float(e) => format!("Error parsing floating-point value for arithmetic(): {}", e),
558 WrongNumberOfComponents { expected, got, input } => format!("Expected {} components for arithmetic(), got {}: \"{}\"", expected, got, input),
559 }}
560 impl_from!(
561 InvalidValueErr<'a>,
562 CssStyleCompositeFilterParseError::Invalid
563 );
564 impl From<ParseFloatError> for CssStyleCompositeFilterParseError<'_> {
565 fn from(p: ParseFloatError) -> Self {
566 Self::Float(p)
567 }
568 }
569
570 #[derive(Debug, Clone, PartialEq, Eq)]
571 #[repr(C, u8)]
572 pub enum CssStyleCompositeFilterParseErrorOwned {
573 Invalid(InvalidValueErrOwned),
574 Float(crate::props::basic::error::ParseFloatError),
575 WrongNumberOfComponents(WrongComponentCountError),
576 }
577
578 impl CssStyleCompositeFilterParseError<'_> {
579 #[must_use] pub fn to_contained(&self) -> CssStyleCompositeFilterParseErrorOwned {
580 match self {
581 Self::Invalid(e) => {
582 CssStyleCompositeFilterParseErrorOwned::Invalid(e.to_contained())
583 }
584 Self::Float(e) => CssStyleCompositeFilterParseErrorOwned::Float(e.clone().into()),
585 Self::WrongNumberOfComponents {
586 expected,
587 got,
588 input,
589 } => CssStyleCompositeFilterParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
590 expected: *expected,
591 got: *got,
592 input: (*input).to_string().into(),
593 }),
594 }
595 }
596 }
597
598 impl CssStyleCompositeFilterParseErrorOwned {
599 #[must_use] pub fn to_shared(&self) -> CssStyleCompositeFilterParseError<'_> {
600 match self {
601 Self::Invalid(e) => CssStyleCompositeFilterParseError::Invalid(e.to_shared()),
602 Self::Float(e) => CssStyleCompositeFilterParseError::Float(e.to_std()),
603 Self::WrongNumberOfComponents(e) => CssStyleCompositeFilterParseError::WrongNumberOfComponents {
604 expected: e.expected,
605 got: e.got,
606 input: e.input.as_str(),
607 },
608 }
609 }
610 }
611
612 pub fn parse_style_filter_vec(
619 input: &str,
620 ) -> Result<StyleFilterVec, CssStyleFilterParseError<'_>> {
621 let mut filters = Vec::new();
622 let mut remaining = input.trim();
623 while !remaining.is_empty() {
624 let (filter, rest) = parse_one_filter_function(remaining)?;
625 filters.push(filter);
626 remaining = rest.trim_start();
627 }
628 Ok(filters.into())
629 }
630
631 fn parse_one_filter_function(
634 input: &str,
635 ) -> Result<(StyleFilter, &str), CssStyleFilterParseError<'_>> {
636 let open_paren = input
637 .find('(')
638 .ok_or(CssStyleFilterParseError::InvalidFilter(input))?;
639 let func_name = &input[..open_paren];
640
641 let mut balance = 1;
642 let mut close_paren = 0;
643 for (i, c) in input.char_indices().skip(open_paren + 1) {
644 if c == '(' {
645 balance += 1;
646 } else if c == ')' {
647 balance -= 1;
648 if balance == 0 {
649 close_paren = i;
650 break;
651 }
652 }
653 }
654
655 if balance != 0 {
656 return Err(ParenthesisParseError::UnclosedBraces.into());
657 }
658
659 let full_function = &input[..=close_paren];
660 let rest = &input[(close_paren + 1)..];
661
662 let filter = parse_style_filter(full_function)?;
663 Ok((filter, rest))
664 }
665
666 pub fn parse_style_filter(
671 input: &str,
672 ) -> Result<StyleFilter, CssStyleFilterParseError<'_>> {
673 let (filter_type, filter_values) = parse_parentheses(
674 input,
675 &[
676 "blend",
677 "flood",
678 "blur",
679 "opacity",
680 "color-matrix",
681 "drop-shadow",
682 "component-transfer",
683 "offset",
684 "composite",
685 "brightness",
686 "contrast",
687 "grayscale",
688 "hue-rotate",
689 "invert",
690 "saturate",
691 "sepia",
692 ],
693 )?;
694
695 match filter_type {
696 "blend" => Ok(StyleFilter::Blend(parse_style_mix_blend_mode(
697 filter_values,
698 )?)),
699 "flood" => Ok(StyleFilter::Flood(parse_css_color(filter_values)?)),
700 "blur" => Ok(StyleFilter::Blur(parse_style_blur(filter_values)?)),
701 "opacity" => {
702 let val = parse_percentage_value(filter_values)?;
703 let normalized = val.normalized();
705 if !(0.0..=1.0).contains(&normalized) {
706 return Err(CssStyleFilterParseError::Opacity(
707 PercentageParseError::InvalidUnit(filter_values.to_string().into()),
708 ));
709 }
710 Ok(StyleFilter::Opacity(val))
711 }
712 "color-matrix" => Ok(StyleFilter::ColorMatrix(parse_color_matrix(filter_values)?)),
713 "drop-shadow" => Ok(StyleFilter::DropShadow(parse_style_box_shadow(
714 filter_values,
715 )?)),
716 "component-transfer" => Ok(StyleFilter::ComponentTransfer),
717 "offset" => Ok(StyleFilter::Offset(parse_filter_offset(filter_values)?)),
718 "composite" => Ok(StyleFilter::Composite(parse_filter_composite(
719 filter_values,
720 )?)),
721 "brightness" => {
722 let val = parse_percentage_value(filter_values)?;
723 if val.normalized() < 0.0 {
724 return Err(CssStyleFilterParseError::Brightness(
725 PercentageParseError::InvalidUnit(filter_values.to_string().into()),
726 ));
727 }
728 Ok(StyleFilter::Brightness(val))
729 }
730 "contrast" => {
731 let val = parse_percentage_value(filter_values)?;
732 if val.normalized() < 0.0 {
733 return Err(CssStyleFilterParseError::Contrast(
734 PercentageParseError::InvalidUnit(filter_values.to_string().into()),
735 ));
736 }
737 Ok(StyleFilter::Contrast(val))
738 }
739 "grayscale" => Ok(StyleFilter::Grayscale(parse_percentage_value(filter_values)?)),
740 "hue-rotate" => Ok(StyleFilter::HueRotate(parse_angle_value(filter_values)?)),
741 "invert" => Ok(StyleFilter::Invert(parse_percentage_value(filter_values)?)),
742 "saturate" => {
743 let val = parse_percentage_value(filter_values)?;
744 if val.normalized() < 0.0 {
745 return Err(CssStyleFilterParseError::Saturate(
746 PercentageParseError::InvalidUnit(filter_values.to_string().into()),
747 ));
748 }
749 Ok(StyleFilter::Saturate(val))
750 }
751 "sepia" => Ok(StyleFilter::Sepia(parse_percentage_value(filter_values)?)),
752 _ => unreachable!(),
753 }
754 }
755
756 fn parse_style_blur(input: &str) -> Result<StyleBlur, CssStyleBlurParseError<'_>> {
757 let mut iter = input.split_whitespace();
758 let width_str = iter.next().unwrap_or("");
759 let height_str = iter.next();
760
761 if iter.next().is_some() {
762 return Err(CssStyleBlurParseError::TooManyComponents(input));
763 }
764
765 let width = parse_pixel_value(width_str)?;
766 let height = match height_str {
767 Some(s) => parse_pixel_value(s)?,
768 None => width, };
770
771 Ok(StyleBlur { width, height })
772 }
773
774 fn parse_color_matrix(
775 input: &str,
776 ) -> Result<StyleColorMatrix, CssStyleColorMatrixParseError<'_>> {
777 let components: Vec<_> = input.split_whitespace().collect();
778 if components.len() != 20 {
779 return Err(CssStyleColorMatrixParseError::WrongNumberOfComponents {
780 expected: 20,
781 got: components.len(),
782 input,
783 });
784 }
785
786 let mut values = [FloatValue::const_new(0); 20];
787 for (i, comp) in components.iter().enumerate() {
788 values[i] = parse_float_value(comp)?;
789 }
790
791 Ok(StyleColorMatrix {
792 m0: values[0],
793 m1: values[1],
794 m2: values[2],
795 m3: values[3],
796 m4: values[4],
797 m5: values[5],
798 m6: values[6],
799 m7: values[7],
800 m8: values[8],
801 m9: values[9],
802 m10: values[10],
803 m11: values[11],
804 m12: values[12],
805 m13: values[13],
806 m14: values[14],
807 m15: values[15],
808 m16: values[16],
809 m17: values[17],
810 m18: values[18],
811 m19: values[19],
812 })
813 }
814
815 fn parse_filter_offset(
816 input: &str,
817 ) -> Result<StyleFilterOffset, CssStyleFilterOffsetParseError<'_>> {
818 let components: Vec<_> = input.split_whitespace().collect();
819 if components.len() != 2 {
820 return Err(CssStyleFilterOffsetParseError::WrongNumberOfComponents {
821 expected: 2,
822 got: components.len(),
823 input,
824 });
825 }
826
827 let x = parse_pixel_value(components[0])?;
828 let y = parse_pixel_value(components[1])?;
829
830 Ok(StyleFilterOffset { x, y })
831 }
832
833 fn parse_filter_composite(
834 input: &str,
835 ) -> Result<StyleCompositeFilter, CssStyleCompositeFilterParseError<'_>> {
836 let mut iter = input.split_whitespace();
837 let operator = iter.next().unwrap_or("");
838
839 match operator {
840 "over" => Ok(StyleCompositeFilter::Over),
841 "in" => Ok(StyleCompositeFilter::In),
842 "atop" => Ok(StyleCompositeFilter::Atop),
843 "out" => Ok(StyleCompositeFilter::Out),
844 "xor" => Ok(StyleCompositeFilter::Xor),
845 "lighter" => Ok(StyleCompositeFilter::Lighter),
846 "arithmetic" => {
847 let mut values = [FloatValue::const_new(0); 4];
848 for (i, val) in values.iter_mut().enumerate() {
849 let s = iter.next().ok_or(
850 CssStyleCompositeFilterParseError::WrongNumberOfComponents {
851 expected: 4,
852 got: i,
853 input,
854 },
855 )?;
856 *val = parse_float_value(s)?;
857 }
858 Ok(StyleCompositeFilter::Arithmetic(ArithmeticCoefficients {
859 k1: values[0],
860 k2: values[1],
861 k3: values[2],
862 k4: values[3],
863 }))
864 }
865 _ => Err(InvalidValueErr(operator).into()),
866 }
867 }
868}
869#[cfg(feature = "parser")]
870pub use parser::{
871 parse_style_filter_vec, CssStyleBlurParseError, CssStyleBlurParseErrorOwned,
872 CssStyleColorMatrixParseError, CssStyleColorMatrixParseErrorOwned,
873 CssStyleCompositeFilterParseError, CssStyleCompositeFilterParseErrorOwned,
874 CssStyleFilterOffsetParseError, CssStyleFilterOffsetParseErrorOwned, CssStyleFilterParseError,
875 CssStyleFilterParseErrorOwned,
876};
877
878#[cfg(all(test, feature = "parser"))]
879mod tests {
880 #![allow(clippy::float_cmp)]
882 use super::*;
883 use crate::props::style::filter::parser::parse_style_filter;
884
885 #[test]
886 fn test_parse_single_filter_functions() {
887 let blur = parse_style_filter("blur(5px)").unwrap();
889 assert!(matches!(blur, StyleFilter::Blur(_)));
890 if let StyleFilter::Blur(b) = blur {
891 assert_eq!(b.width, PixelValue::px(5.0));
892 assert_eq!(b.height, PixelValue::px(5.0));
893 }
894
895 let blur2 = parse_style_filter("blur(2px 4px)").unwrap();
897 if let StyleFilter::Blur(b) = blur2 {
898 assert_eq!(b.width, PixelValue::px(2.0));
899 assert_eq!(b.height, PixelValue::px(4.0));
900 }
901
902 let shadow = parse_style_filter("drop-shadow(10px 5px 5px #888)").unwrap();
904 assert!(matches!(shadow, StyleFilter::DropShadow(_)));
905 if let StyleFilter::DropShadow(s) = shadow {
906 assert_eq!(s.offset_x.inner, PixelValue::px(10.0));
907 assert_eq!(s.blur_radius.inner, PixelValue::px(5.0));
908 assert_eq!(s.color, ColorU::new_rgb(0x88, 0x88, 0x88));
909 }
910
911 let opacity = parse_style_filter("opacity(50%)").unwrap();
913 assert!(matches!(opacity, StyleFilter::Opacity(_)));
914 if let StyleFilter::Opacity(p) = opacity {
915 assert_eq!(p.normalized(), 0.5);
916 }
917
918 let flood = parse_style_filter("flood(red)").unwrap();
920 assert_eq!(flood, StyleFilter::Flood(ColorU::RED));
921
922 let composite = parse_style_filter("composite(in)").unwrap();
924 assert_eq!(composite, StyleFilter::Composite(StyleCompositeFilter::In));
925
926 let offset = parse_style_filter("offset(10px 20%)").unwrap();
928 if let StyleFilter::Offset(o) = offset {
929 assert_eq!(o.x, PixelValue::px(10.0));
930 assert_eq!(o.y, PixelValue::percent(20.0));
931 }
932 }
933
934 #[test]
935 fn test_parse_filter_vec() {
936 let filters =
937 parse_style_filter_vec("blur(5px) drop-shadow(10px 5px #888) opacity(0.8)").unwrap();
938 assert_eq!(filters.len(), 3);
939 assert!(matches!(filters.as_slice()[0], StyleFilter::Blur(_)));
940 assert!(matches!(filters.as_slice()[1], StyleFilter::DropShadow(_)));
941 assert!(matches!(filters.as_slice()[2], StyleFilter::Opacity(_)));
942 }
943
944 #[test]
945 fn test_parse_standard_css_filters() {
946 let brightness = parse_style_filter("brightness(150%)").unwrap();
947 if let StyleFilter::Brightness(v) = brightness {
948 assert!((v.normalized() - 1.5).abs() < 0.001);
949 } else {
950 panic!("expected Brightness");
951 }
952
953 let contrast = parse_style_filter("contrast(200%)").unwrap();
954 if let StyleFilter::Contrast(v) = contrast {
955 assert!((v.normalized() - 2.0).abs() < 0.001);
956 } else {
957 panic!("expected Contrast");
958 }
959
960 let grayscale = parse_style_filter("grayscale(100%)").unwrap();
961 if let StyleFilter::Grayscale(v) = grayscale {
962 assert!((v.normalized() - 1.0).abs() < 0.001);
963 } else {
964 panic!("expected Grayscale");
965 }
966
967 let hue = parse_style_filter("hue-rotate(90deg)").unwrap();
968 assert!(matches!(hue, StyleFilter::HueRotate(_)));
969
970 let invert = parse_style_filter("invert(75%)").unwrap();
971 if let StyleFilter::Invert(v) = invert {
972 assert!((v.normalized() - 0.75).abs() < 0.001);
973 } else {
974 panic!("expected Invert");
975 }
976
977 let saturate = parse_style_filter("saturate(50%)").unwrap();
978 if let StyleFilter::Saturate(v) = saturate {
979 assert!((v.normalized() - 0.5).abs() < 0.001);
980 } else {
981 panic!("expected Saturate");
982 }
983
984 let sepia = parse_style_filter("sepia(60%)").unwrap();
985 if let StyleFilter::Sepia(v) = sepia {
986 assert!((v.normalized() - 0.6).abs() < 0.001);
987 } else {
988 panic!("expected Sepia");
989 }
990 }
991
992 #[test]
993 fn test_negative_values_rejected() {
994 assert!(parse_style_filter("brightness(-50%)").is_err());
995 assert!(parse_style_filter("contrast(-10%)").is_err());
996 assert!(parse_style_filter("saturate(-20%)").is_err());
997 }
998
999 #[test]
1000 fn test_parse_filter_errors() {
1001 assert!(parse_style_filter_vec("blurry(5px)").is_err());
1003 assert!(parse_style_filter_vec("blur(5px 10px 15px)").is_err());
1005 assert!(parse_style_filter_vec("opacity(2)").is_err()); assert!(parse_style_filter_vec("blur(5px").is_err());
1008 }
1009}