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