1use std::sync::LazyLock;
2
3use smol_str::SmolStr;
4
5use crate::engine::css::values::{CssValue, Unit};
6use crate::engine::layouter::builder::css_resolve::parse_grid_template;
7use crate::engine::layouter::types::{CursorKeyword, CursorStyle, CustomCursor};
8use crate::engine::renderer_model::AffineTransform;
9
10use ui_layout::{
11 AlignContent, AlignItems, BoxSizing, Display, FlexDirection, FlexWrap, JustifyContent,
12 JustifyItems, Length, LengthOrAuto, OuterDisplay, Position, Style,
13};
14
15use super::{
16 CalcValue, apply_background_shorthand_geometry, extract_font_families, length_to_px,
17 one_or_two_values, parse_background_position, parse_background_repeat,
18 parse_background_shorthand, parse_background_size, parse_grid_line, parse_grid_line_end,
19 parse_grid_placement, parse_grid_template_areas, parse_grid_tracks, resolve_calc_value,
20 resolve_css_color, resolve_css_len, resolve_css_len_auto, resolve_font_size_px,
21};
22use crate::engine::layouter::builder::parse_anchor_spec;
23use crate::engine::layouter::types::{
24 AnchorInsetSide, Background, BorderRadius, BorderStyle, ClipPath, Color, ColorScheme,
25 ContainerStyle, CornerRadius, CssClear, CssFloat, Direction, FontStyle, FontWeight, LineHeight,
26 Overflow, TextAlign, TextDecoration, TextFlowStyle, TextStyle, TextTransform, VerticalAlign,
27 Visibility, WhiteSpace, WritingMode,
28};
29
30macro_rules! apply_property {
31 ($field:ident, $target:ident, $parent:ident, $default:expr, $value:expr, $parse:expr) => {
32 $target.$field = if let CssValue::Keyword(v) = $value
33 && v == "initial"
34 {
35 $default.$field.clone()
36 } else if let CssValue::Keyword(v) = $value
37 && v == "inherit"
38 {
39 $parent.$field.clone()
40 } else if let CssValue::Keyword(v) = $value
41 && v == "unset"
42 {
43 $default.$field.clone()
44 } else {
45 $parse
46 };
47 };
48
49 ($field:ident, $target:ident, $parent:ident, $default:expr, $value:expr, $pattern:pat, $parse:expr) => {
50 $target.$field = if let CssValue::Keyword(v) = $value
51 && v == "initial"
52 {
53 $default.$field.clone()
54 } else if let CssValue::Keyword(v) = $value
55 && v == "inherit"
56 {
57 $parent.$field.clone()
58 } else if let CssValue::Keyword(v) = $value
59 && v == "unset"
60 {
61 $default.$field.clone()
62 } else if let $pattern = $value {
63 $parse
64 } else {
65 return None;
66 };
67 };
68}
69
70static DEFAULT_STYLE: LazyLock<Style> = LazyLock::new(Style::default);
71static DEFAULT_CONTAINER_STYLE: LazyLock<ContainerStyle> = LazyLock::new(ContainerStyle::default);
72static DEFAULT_TEXT_STYLE: LazyLock<TextStyle> = LazyLock::new(TextStyle::default);
73static DEFAULT_TEXT_FLOW_STYLE: LazyLock<TextFlowStyle> = LazyLock::new(TextFlowStyle::default);
74
75pub fn blockify_out_of_flow_positioned(style: &mut Style) {
76 if style.position.kind.is_out_of_flow()
77 && style.display.outer() == Some(OuterDisplay::Inline)
78 && let Display::OutsideInner { inner, .. } = style.display
79 {
80 style.display = Display::OutsideInner {
81 outer: OuterDisplay::Block,
82 inner,
83 };
84 }
85}
86
87fn resolve_flex_shorthand(
88 value: &CssValue,
89 text_flow_style: &TextFlowStyle,
90) -> Option<(f32, f32, LengthOrAuto)> {
91 let zero_basis = LengthOrAuto::Length(Length::Percent(0.0));
92
93 match value {
94 CssValue::Keyword(keyword) => match keyword.as_str() {
95 "none" => Some((0.0, 0.0, LengthOrAuto::Auto)),
96 "auto" => Some((1.0, 1.0, LengthOrAuto::Auto)),
97 "initial" => Some((0.0, 1.0, LengthOrAuto::Auto)),
98 "unset" => Some((0.0, 1.0, LengthOrAuto::Auto)),
99 _ => None,
100 },
101 CssValue::Number(grow) if *grow >= 0.0 => Some((*grow, 1.0, zero_basis)),
102 CssValue::Length(_, _) => Some((
103 1.0,
104 1.0,
105 resolve_css_len_auto("flex", value, text_flow_style)?,
106 )),
107 CssValue::List(values) => match values.as_slice() {
108 [CssValue::Number(grow), CssValue::Number(shrink)]
109 if *grow >= 0.0 && *shrink >= 0.0 =>
110 {
111 Some((*grow, *shrink, zero_basis))
112 }
113 [CssValue::Number(grow), basis] if *grow >= 0.0 => Some((
114 *grow,
115 1.0,
116 resolve_css_len_auto("flex", basis, text_flow_style)?,
117 )),
118 [CssValue::Number(grow), CssValue::Number(shrink), basis]
119 if *grow >= 0.0 && *shrink >= 0.0 =>
120 {
121 Some((
122 *grow,
123 *shrink,
124 resolve_css_len_auto("flex", basis, text_flow_style)?,
125 ))
126 }
127 _ => None,
128 },
129 _ => None,
130 }
131}
132
133fn flex_direction_keyword(keyword: &str) -> Option<FlexDirection> {
134 match keyword {
135 "row" => Some(FlexDirection::Row),
136 "column" => Some(FlexDirection::Column),
137 "row-reverse" => Some(FlexDirection::RowReverse),
138 "column-reverse" => Some(FlexDirection::ColumnReverse),
139 _ => None,
140 }
141}
142
143fn flex_wrap_keyword(keyword: &str) -> Option<FlexWrap> {
144 match keyword {
145 "nowrap" => Some(FlexWrap::NoWrap),
146 "wrap" => Some(FlexWrap::Wrap),
147 "wrap-reverse" => Some(FlexWrap::WrapReverse),
148 _ => None,
149 }
150}
151
152fn resolve_flex_flow(value: &CssValue) -> Option<(FlexDirection, FlexWrap)> {
153 let values = match value {
154 CssValue::Keyword(keyword) if keyword == "initial" => {
155 return Some((FlexDirection::Row, FlexWrap::NoWrap));
156 }
157 CssValue::Keyword(_) => std::slice::from_ref(value),
158 CssValue::List(values) if (1..=2).contains(&values.len()) => values.as_slice(),
159 _ => return None,
160 };
161 let mut direction = None;
162 let mut wrap = None;
163 for value in values {
164 let CssValue::Keyword(keyword) = value else {
165 return None;
166 };
167 if let Some(parsed) = flex_direction_keyword(keyword) {
168 if direction.replace(parsed).is_some() {
169 return None;
170 }
171 } else {
172 let parsed = flex_wrap_keyword(keyword)?;
173 if wrap.replace(parsed).is_some() {
174 return None;
175 }
176 }
177 }
178
179 Some((
180 direction.unwrap_or(FlexDirection::Row),
181 wrap.unwrap_or(FlexWrap::NoWrap),
182 ))
183}
184
185fn resolve_justify_items(keyword: &str) -> Option<JustifyItems> {
186 match keyword {
187 "stretch" => Some(JustifyItems::Stretch),
188 "flex-start" | "start" => Some(JustifyItems::Start),
189 "center" => Some(JustifyItems::Center),
190 "flex-end" | "end" => Some(JustifyItems::End),
191 _ => None,
192 }
193}
194
195fn resolve_align_items(keyword: &str) -> Option<AlignItems> {
196 match keyword {
197 "stretch" => Some(AlignItems::Stretch),
198 "flex-start" | "start" => Some(AlignItems::Start),
199 "center" => Some(AlignItems::Center),
200 "flex-end" | "end" => Some(AlignItems::End),
201 _ => None,
202 }
203}
204
205fn transform_angle_radians(value: &CssValue) -> Option<f32> {
207 match value {
208 CssValue::Length(v, Unit::Deg) => Some(v.to_radians()),
209 CssValue::Length(v, Unit::Rad) => Some(*v),
210 CssValue::Length(v, Unit::Grad) => Some(v * std::f32::consts::PI / 200.0),
211 CssValue::Length(v, Unit::Turn) => Some(v * 2.0 * std::f32::consts::PI),
212 CssValue::Number(n) => Some(n.to_radians()),
213 _ => None,
214 }
215}
216
217fn transform_length_px(
225 name: &str,
226 value: &CssValue,
227 text_flow_style: &TextFlowStyle,
228 element_size: Option<(f32, f32)>,
229 is_x: bool,
230) -> Option<f32> {
231 match value {
232 CssValue::Number(n) => Some(*n),
233 _ => {
234 let len = resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?;
235 match len {
236 Length::Px(v) => Some(v),
237 Length::Percent(p) => {
238 let base = if is_x {
239 element_size
240 .map(|(w, _)| w)
241 .unwrap_or(text_flow_style.font_size)
242 } else {
243 element_size
244 .map(|(_, h)| h)
245 .unwrap_or(text_flow_style.font_size)
246 };
247 Some(base * p / 100.0)
248 }
249 _ => Some(length_to_px(&len, text_flow_style.font_size)),
250 }
251 }
252 }
253}
254
255fn flatten_function_args(args: &[Vec<CssValue>]) -> Vec<&CssValue> {
259 args.iter().flatten().collect()
260}
261
262fn parse_transform_function(
272 name: &str,
273 func: &CssValue,
274 style: &Style,
275 text_flow_style: &TextFlowStyle,
276) -> Option<AffineTransform> {
277 let CssValue::Function(fn_name, args) = func else {
278 return None;
279 };
280 let flat = flatten_function_args(args);
281
282 let element_width = match style.size.width {
283 LengthOrAuto::Length(Length::Px(v)) => Some(v),
284 _ => None,
285 };
286 let element_height = match style.size.height {
287 LengthOrAuto::Length(Length::Px(v)) => Some(v),
288 _ => None,
289 };
290 let element_size = match (element_width, element_height) {
291 (Some(w), Some(h)) => Some((w, h)),
292 _ => None,
293 };
294
295 match fn_name.to_ascii_lowercase().as_str() {
296 "none" => Some(AffineTransform::identity()),
297
298 "rotate" => {
299 let angle = transform_angle_radians(flat.first()?)?;
300 Some(AffineTransform::rotate(angle))
301 }
302
303 "scale" => {
304 let sx = transform_length_px(name, flat.first()?, text_flow_style, element_size, true)?;
305 let sy = flat
306 .get(1)
307 .and_then(|v| transform_length_px(name, v, text_flow_style, element_size, true))
308 .unwrap_or(sx);
309 Some(AffineTransform::scale(sx, sy))
310 }
311
312 "translate" => {
313 let tx = transform_length_px(name, flat.first()?, text_flow_style, element_size, true)?;
314 let ty = flat
315 .get(1)
316 .and_then(|v| transform_length_px(name, v, text_flow_style, element_size, false))
317 .unwrap_or(0.0);
318 Some(AffineTransform::translate(tx, ty))
319 }
320
321 "translatex" => {
322 let tx = transform_length_px(name, flat.first()?, text_flow_style, element_size, true)?;
323 Some(AffineTransform::translate(tx, 0.0))
324 }
325
326 "translatey" => {
327 let ty =
328 transform_length_px(name, flat.first()?, text_flow_style, element_size, false)?;
329 Some(AffineTransform::translate(0.0, ty))
330 }
331
332 _ => None,
333 }
334}
335
336fn parse_transform(
340 name: &str,
341 value: &CssValue,
342 style: &Style,
343 text_flow_style: &TextFlowStyle,
344) -> Option<AffineTransform> {
345 match value {
346 CssValue::Keyword(k) if k.eq_ignore_ascii_case("none") => None,
347
348 CssValue::List(funcs) => {
349 let mut accumulated = AffineTransform::identity();
350 for func in funcs {
351 let t = parse_transform_function(name, func, style, text_flow_style)?;
352 accumulated = accumulated.then(&t);
353 }
354 (!accumulated.eq(&AffineTransform::identity())).then_some(accumulated)
355 }
356 CssValue::Function(..) => {
357 let t = parse_transform_function(name, value, style, text_flow_style)?;
358 (!t.eq(&AffineTransform::identity())).then_some(t)
359 }
360 _ => None,
361 }
362}
363
364fn set_background_color(container_style: &mut ContainerStyle, color: Color) {
367 container_style.background.color = Some(color);
368}
369
370#[allow(clippy::too_many_arguments)]
371pub fn apply_declaration(
372 name: &str,
373 value: &CssValue,
374 style: &mut Style,
375 container_style: &mut ContainerStyle,
376 text_style: &mut TextStyle,
377 text_flow_style: &mut TextFlowStyle,
378 parent_style: &Style,
379 parent_container_style: &ContainerStyle,
380 parent_text_style: &TextStyle,
381 parent_text_flow_style: &TextFlowStyle,
382 overflow: &mut Overflow,
383 color_scheme: ColorScheme,
384) -> Option<()> {
385 fn expand_box<T: Clone, F>(
386 name: &str,
387 value: &CssValue,
388 text_flow_style: &TextFlowStyle,
389 resolve: &impl Fn(&str, &CssValue, &TextFlowStyle) -> Option<T>,
390 mut set: F,
391 ) -> Option<()>
392 where
393 F: FnMut(T, T, T, T),
394 {
395 let resolve = |v: &CssValue| -> Option<T> { resolve(name, v, text_flow_style) };
396
397 match value {
398 CssValue::List(values) => {
399 let vals: Vec<T> = values.iter().map(resolve).collect::<Option<_>>()?;
400
401 match vals.as_slice() {
402 [a] => set(a.clone(), a.clone(), a.clone(), a.clone()),
403 [v, h] => set(v.clone(), h.clone(), v.clone(), h.clone()),
404 [t, h, b] => set(t.clone(), h.clone(), b.clone(), h.clone()),
405 [t, r, b, l] => set(t.clone(), r.clone(), b.clone(), l.clone()),
406 _ => return None,
407 }
408 }
409
410 _ => {
411 let v = resolve(value)?;
412 set(v.clone(), v.clone(), v.clone(), v);
413 }
414 }
415
416 Some(())
417 }
418
419 fn split_radius_lists<'a>(value: &'a CssValue) -> (Vec<&'a CssValue>, Vec<&'a CssValue>) {
423 match value {
424 CssValue::List(vals) => {
425 let mut horiz: Vec<&'a CssValue> = Vec::new();
426 let mut vert: Vec<&'a CssValue> = Vec::new();
427 let mut after_slash = false;
428 for v in vals {
429 if let CssValue::Keyword(k) = v
430 && k == "/"
431 {
432 after_slash = true;
433 continue;
434 }
435 if after_slash {
436 vert.push(v);
437 } else {
438 horiz.push(v);
439 }
440 }
441 (horiz, vert)
442 }
443 _ => (vec![value], Vec::new()),
444 }
445 }
446
447 fn expand_radius_axis(
450 name: &str,
451 values: &[&CssValue],
452 text_flow_style: &TextFlowStyle,
453 ) -> Option<[Length; 4]> {
454 let vals: Vec<Length> = values
455 .iter()
456 .map(|v| resolve_css_len(name, std::slice::from_ref(v), text_flow_style))
457 .collect::<Option<_>>()?;
458 match vals.as_slice() {
459 [a] => Some([a.clone(), a.clone(), a.clone(), a.clone()]),
460 [v, h] => Some([v.clone(), h.clone(), v.clone(), h.clone()]),
461 [t, h, b] => Some([t.clone(), h.clone(), b.clone(), h.clone()]),
462 [t, r, b, l] => Some([t.clone(), r.clone(), b.clone(), l.clone()]),
463 _ => None,
464 }
465 }
466
467 fn parse_border_radius_shorthand(
470 name: &str,
471 value: &CssValue,
472 text_flow_style: &TextFlowStyle,
473 ) -> Option<(CornerRadius, CornerRadius, CornerRadius, CornerRadius)> {
474 let (horiz, vert) = split_radius_lists(value);
475 let h = expand_radius_axis(name, &horiz, text_flow_style)?;
476 let v = if vert.is_empty() {
477 h.clone()
478 } else {
479 expand_radius_axis(name, &vert, text_flow_style)?
480 };
481 Some((
482 CornerRadius {
483 x: h[0].clone(),
484 y: v[0].clone(),
485 },
486 CornerRadius {
487 x: h[1].clone(),
488 y: v[1].clone(),
489 },
490 CornerRadius {
491 x: h[2].clone(),
492 y: v[2].clone(),
493 },
494 CornerRadius {
495 x: h[3].clone(),
496 y: v[3].clone(),
497 },
498 ))
499 }
500
501 fn parse_corner_radius(
504 name: &str,
505 value: &CssValue,
506 text_flow_style: &TextFlowStyle,
507 ) -> Option<CornerRadius> {
508 let (horiz, vert) = split_radius_lists(value);
509 let x = expand_radius_axis(name, &horiz, text_flow_style)?;
510 let y = if vert.is_empty() {
511 x.clone()
512 } else {
513 expand_radius_axis(name, &vert, text_flow_style)?
514 };
515 Some(CornerRadius {
516 x: x[0].clone(),
517 y: y[0].clone(),
518 })
519 }
520
521 fn overflow_scrollable(keyword: &str) -> Option<bool> {
523 match keyword {
524 "visible" | "clip" => Some(false),
525 "hidden" | "scroll" | "auto" => Some(true),
526 _ => None,
527 }
528 }
529
530 fn overflow_flags(value: &CssValue) -> Option<(bool, bool)> {
532 match value {
533 CssValue::Keyword(k) => overflow_scrollable(k).map(|b| (b, b)),
534 CssValue::List(l) => {
535 let x = match l.first()? {
536 CssValue::Keyword(k) => overflow_scrollable(k)?,
537 _ => return None,
538 };
539 let y = match l.get(1) {
540 Some(CssValue::Keyword(k)) => overflow_scrollable(k)?,
541 Some(_) => return None,
542 None => x,
543 };
544 Some((x, y))
545 }
546 _ => None,
547 }
548 }
549
550 fn parse_border_shorthand(
551 name: &str,
552 value: &CssValue,
553 text_flow_style: &TextFlowStyle,
554 color_scheme: ColorScheme,
555 ) -> Option<(Option<Length>, Option<BorderStyle>, Option<Color>)> {
556 let mut width: Option<Length> = None;
557 let mut style_v: Option<BorderStyle> = None;
558 let mut color_v: Option<Color> = None;
559
560 let items: Vec<&CssValue> = match value {
561 CssValue::List(values) => values.iter().collect(),
562 _ => vec![value],
563 };
564
565 for v in items {
566 let token = v;
567
568 if width.is_none()
570 && let Some(l) = resolve_css_len(name, std::slice::from_ref(token), text_flow_style)
571 {
572 width = Some(l);
573 continue;
574 }
575
576 if width.is_none()
578 && let CssValue::Keyword(s) = token
579 {
580 match s.as_str().to_ascii_lowercase().as_str() {
581 "thin" => {
582 width = Some(Length::Px(1.0));
583 continue;
584 }
585 "medium" => {
586 width = Some(Length::Px(3.0));
587 continue;
588 }
589 "midium" => {
590 width = Some(Length::Px(3.0));
591 continue;
592 } "thick" => {
594 width = Some(Length::Px(5.0));
595 continue;
596 }
597 _ => {}
598 }
599 }
600
601 if style_v.is_none()
603 && let CssValue::Keyword(s) = token
604 {
605 let s_lower = s.as_str();
606 let parsed = match s_lower {
607 "none" => Some(BorderStyle::None),
608 "solid" => Some(BorderStyle::Solid),
609 "dashed" => Some(BorderStyle::Dashed),
610 "dotted" => Some(BorderStyle::Dotted),
611 "inset" | "outset" | "groove" | "ridge" | "double" | "hidden" => {
612 style_v = Some(BorderStyle::Solid);
614 continue;
615 }
616 _ => None,
617 };
618
619 if let Some(p) = parsed {
620 style_v = Some(p);
621 continue;
622 }
623 }
624
625 if color_v.is_none()
627 && let Some(c) = resolve_css_color(name, token, color_scheme)
628 {
629 color_v = Some(c);
630 continue;
631 }
632
633 }
635
636 Some((width, style_v, color_v))
637 }
638
639 fn parse_clip_path(
640 name: &str,
641 value: &CssValue,
642 text_flow_style: &TextFlowStyle,
643 ) -> Option<ClipPath> {
644 match value {
645 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("none") => Some(ClipPath::None),
646
647 CssValue::Function(func, args) if func.eq_ignore_ascii_case("circle") => {
648 let mut radius = 0.5f32; let mut center_x = 0.5f32;
650 let mut center_y = 0.5f32;
651 let mut after_at = false;
652
653 for arg in args.iter().flatten() {
654 match arg {
655 CssValue::Keyword(k) if k.eq_ignore_ascii_case("at") => {
656 after_at = true;
657 }
658 CssValue::Keyword(k) if k.eq_ignore_ascii_case("closest-side") => {
659 if !after_at {
660 radius = 0.5;
661 }
662 }
663 CssValue::Keyword(k) if k.eq_ignore_ascii_case("farthest-side") => {
664 if !after_at {
665 radius = 1.0;
666 }
667 }
668 CssValue::Keyword(k) if k.eq_ignore_ascii_case("farthest-corner") => {
669 if !after_at {
670 radius = std::f32::consts::SQRT_2;
671 }
672 }
673 CssValue::Length(v, unit) => {
674 let pct = match unit {
675 Unit::Percent => v / 100.0,
676 _ => resolve_css_len(
677 name,
678 std::slice::from_ref(arg),
679 text_flow_style,
680 )
681 .map(|l| length_to_px(&l, text_flow_style.font_size) / 100.0)
682 .unwrap_or(0.0),
683 };
684 if after_at {
685 if center_x != 0.5 {
687 center_y = pct;
689 } else {
690 center_x = pct;
691 }
692 } else {
693 radius = pct;
694 }
695 }
696 CssValue::Number(n) => {
697 if after_at {
698 if center_x != 0.5 {
699 center_y = *n;
700 } else {
701 center_x = *n;
702 }
703 } else {
704 radius = *n;
705 }
706 }
707 _ => {}
708 }
709 }
710 Some(ClipPath::Circle {
711 radius: radius.clamp(0.0, 1.0),
712 center_x: center_x.clamp(0.0, 1.0),
713 center_y: center_y.clamp(0.0, 1.0),
714 })
715 }
716
717 CssValue::Function(func, args) if func.eq_ignore_ascii_case("ellipse") => {
718 let mut rx = 0.5f32;
719 let mut ry = 0.5f32;
720 let mut center_x = 0.5f32;
721 let mut center_y = 0.5f32;
722 let mut after_at = false;
723 let mut got_ry = false;
724
725 for arg in args.iter().flatten() {
726 match arg {
727 CssValue::Keyword(k) if k.eq_ignore_ascii_case("at") => {
728 after_at = true;
729 }
730 CssValue::Keyword(k)
731 if k.eq_ignore_ascii_case("closest-side")
732 || k.eq_ignore_ascii_case("farthest-side")
733 || k.eq_ignore_ascii_case("farthest-corner") =>
734 {
735 }
737 CssValue::Length(v, unit) => {
738 let pct = match unit {
739 Unit::Percent => v / 100.0,
740 _ => resolve_css_len(
741 name,
742 std::slice::from_ref(arg),
743 text_flow_style,
744 )
745 .map(|l| length_to_px(&l, text_flow_style.font_size) / 100.0)
746 .unwrap_or(0.0),
747 };
748 if after_at {
749 if center_x != 0.5 {
750 center_y = pct;
751 } else {
752 center_x = pct;
753 }
754 } else if !got_ry {
755 rx = pct;
756 got_ry = true;
757 } else {
758 ry = pct;
759 }
760 }
761 CssValue::Number(n) => {
762 if after_at {
763 if center_x != 0.5 {
764 center_y = *n;
765 } else {
766 center_x = *n;
767 }
768 } else if !got_ry {
769 rx = *n;
770 got_ry = true;
771 } else {
772 ry = *n;
773 }
774 }
775 _ => {}
776 }
777 }
778 Some(ClipPath::Ellipse {
779 rx: rx.clamp(0.0, 1.0),
780 ry: ry.clamp(0.0, 1.0),
781 center_x: center_x.clamp(0.0, 1.0),
782 center_y: center_y.clamp(0.0, 1.0),
783 })
784 }
785
786 CssValue::Function(func, args) if func.eq_ignore_ascii_case("inset") => {
787 let mut top = 0.0f32;
788 let mut right = 0.0f32;
789 let mut bottom = 0.0f32;
790 let mut left = 0.0f32;
791 let mut idx = 0;
792
793 for arg in args.iter().flatten() {
794 if let CssValue::Keyword(k) = arg
795 && k.eq_ignore_ascii_case("round")
796 {
797 break; }
799 let pct = match arg {
800 CssValue::Length(v, Unit::Percent) => v / 100.0,
801 CssValue::Length(v, _) => {
802 resolve_css_len(name, std::slice::from_ref(arg), text_flow_style)
803 .map(|l| length_to_px(&l, text_flow_style.font_size) / 100.0)
804 .unwrap_or(*v / 100.0)
805 }
806 CssValue::Number(n) => *n,
807 _ => continue,
808 };
809 match idx {
810 0 => {
811 top = pct;
812 right = pct;
813 bottom = pct;
814 left = pct;
815 }
816 1 => {
817 right = pct;
818 left = pct;
819 }
820 2 => bottom = pct,
821 3 => left = pct,
822 _ => {}
823 }
824 idx += 1;
825 }
826 Some(ClipPath::Inset {
827 top: top.clamp(0.0, 1.0),
828 right: right.clamp(0.0, 1.0),
829 bottom: bottom.clamp(0.0, 1.0),
830 left: left.clamp(0.0, 1.0),
831 })
832 }
833
834 CssValue::Function(func, args) if func.eq_ignore_ascii_case("rect") => {
842 let mut top = 0.0f32;
843 let mut right = 0.0f32;
844 let mut bottom = 0.0f32;
845 let mut left = 0.0f32;
846 let mut idx = 0;
847
848 for arg in args.iter().flatten() {
849 let pct = match arg {
850 CssValue::Length(v, Unit::Percent) => v / 100.0,
851 CssValue::Length(v, _) => {
852 resolve_css_len(name, std::slice::from_ref(arg), text_flow_style)
853 .map(|l| length_to_px(&l, text_flow_style.font_size) / 100.0)
854 .unwrap_or(*v / 100.0)
855 }
856 CssValue::Number(n) => *n,
857 _ => continue,
858 };
859 match idx {
860 0 => top = pct,
861 1 => right = pct,
862 2 => bottom = pct,
863 3 => left = pct,
864 _ => {}
865 }
866 idx += 1;
867 }
868 Some(ClipPath::Inset {
869 top: top.clamp(0.0, 1.0),
870 left: left.clamp(0.0, 1.0),
871 right: (1.0 - right).clamp(0.0, 1.0),
872 bottom: (1.0 - bottom).clamp(0.0, 1.0),
873 })
874 }
875
876 CssValue::Function(func, args) if func.eq_ignore_ascii_case("polygon") => {
877 let mut points = Vec::new();
878 let mut x_opt: Option<f32> = None;
879 for arg in args.iter().flatten() {
880 match arg {
881 CssValue::Length(v, Unit::Percent) => {
882 let pct = v / 100.0;
883 if let Some(x) = x_opt.take() {
884 points.push((x, pct));
885 } else {
886 x_opt = Some(pct);
887 }
888 }
889 CssValue::Length(v, _) => {
890 let pct =
891 resolve_css_len(name, std::slice::from_ref(arg), text_flow_style)
892 .map(|l| length_to_px(&l, text_flow_style.font_size) / 100.0)
893 .unwrap_or(*v / 100.0);
894 if let Some(x) = x_opt.take() {
895 points.push((x, pct));
896 } else {
897 x_opt = Some(pct);
898 }
899 }
900 CssValue::Number(n) => {
901 if let Some(x) = x_opt.take() {
902 points.push((x, *n));
903 } else {
904 x_opt = Some(*n);
905 }
906 }
907 CssValue::Keyword(k)
908 if k.eq_ignore_ascii_case("fill")
909 || k.eq_ignore_ascii_case("stroke")
910 || k.eq_ignore_ascii_case("evenodd") =>
911 {
912 }
914 _ => {}
915 }
916 }
917 if points.is_empty() {
918 None
919 } else {
920 Some(ClipPath::Polygon { points })
921 }
922 }
923
924 _ => None,
925 }
926 }
927
928 match (name, value) {
929 ("display", _) => {
933 apply_property!(
934 display,
935 style,
936 parent_style,
937 DEFAULT_STYLE,
938 value,
939 CssValue::Keyword(v),
940 Display::parse(v.as_str())?
941 );
942 }
943
944 ("z-index", _) => {
945 let f = |v: &CssValue| match v {
946 CssValue::Number(v) => {
947 if v.is_finite() && v.fract().abs() < f32::EPSILON {
948 Some(Some(*v as i32))
949 } else {
950 None
951 }
952 }
953 CssValue::Keyword(v) if v.eq_ignore_ascii_case("auto") => Some(None),
954 _ => None,
955 };
956
957 apply_property!(
958 z_index,
959 container_style,
960 parent_container_style,
961 DEFAULT_CONTAINER_STYLE,
962 value,
963 f(value)?
964 );
965 }
966
967 ("visibility", _) => {
968 let f = |v: &SmolStr| match v.to_ascii_lowercase().as_str() {
969 "visible" => Some(Visibility::Visible),
970 "hidden" => Some(Visibility::Hidden),
971 "collapse" => Some(Visibility::Collapse),
972 _ => None,
973 };
974
975 apply_property!(
976 visibility,
977 container_style,
978 parent_container_style,
979 DEFAULT_CONTAINER_STYLE,
980 value,
981 CssValue::Keyword(v),
982 f(v)?
983 );
984 }
985
986 ("opacity", _) => {
987 let f = |v: &CssValue| match v {
991 CssValue::Number(value) => Some(value.clamp(0.0, 1.0)),
992 CssValue::Length(value, Unit::Percent) => Some((value / 100.0).clamp(0.0, 1.0)),
993 _ => None,
994 };
995
996 apply_property!(
997 opacity,
998 container_style,
999 parent_container_style,
1000 DEFAULT_CONTAINER_STYLE,
1001 value,
1002 f(value)?
1003 );
1004 }
1005
1006 ("float", _) => {
1007 let f = |v: &SmolStr| match v.to_ascii_lowercase().as_str() {
1008 "left" => Some(CssFloat::Left),
1009 "right" => Some(CssFloat::Right),
1010 "none" => Some(CssFloat::None),
1011 _ => None,
1012 };
1013
1014 apply_property!(
1015 css_float,
1016 container_style,
1017 parent_container_style,
1018 DEFAULT_CONTAINER_STYLE,
1019 value,
1020 CssValue::Keyword(v),
1021 f(v)?
1022 );
1023 }
1024
1025 ("clear", _) => {
1026 let f = |v: &SmolStr| match v.to_ascii_lowercase().as_str() {
1027 "left" => Some(CssClear::Left),
1028 "right" => Some(CssClear::Right),
1029 "both" => Some(CssClear::Both),
1030 "none" => Some(CssClear::None),
1031 _ => None,
1032 };
1033
1034 apply_property!(
1035 css_clear,
1036 container_style,
1037 parent_container_style,
1038 DEFAULT_CONTAINER_STYLE,
1039 value,
1040 CssValue::Keyword(v),
1041 f(v)?
1042 );
1043 }
1044
1045 ("cursor", _) => {
1046 container_style.cursor = if let CssValue::Keyword(v) = value
1047 && v == "initial"
1048 {
1049 DEFAULT_CONTAINER_STYLE.cursor.clone()
1050 } else if let CssValue::Keyword(v) = value
1051 && v == "inherit"
1052 {
1053 parent_container_style.cursor.clone()
1054 } else {
1055 parse_cursor_value(value, text_flow_style.font_size)
1056 };
1057 }
1058
1059 ("background-color", _) => {
1063 let color = match value {
1064 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("inherit") => text_style.color,
1065 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("currentColor") => {
1066 text_style.color
1067 }
1068 CssValue::Keyword(kw)
1069 if kw.eq_ignore_ascii_case("initial") || kw.eq_ignore_ascii_case("unset") =>
1070 {
1071 Color(0, 0, 0, 0)
1072 }
1073 _ => resolve_css_color(name, value, color_scheme)?,
1074 };
1075 set_background_color(container_style, color);
1076 }
1077
1078 ("background", _) => {
1079 container_style.background =
1080 parse_background_shorthand(name, value, text_style, text_flow_style, color_scheme);
1081 apply_background_shorthand_geometry(value, text_flow_style.font_size, container_style);
1082 }
1083
1084 ("background-image", _) => {
1085 let existing_color = container_style.background.color;
1086 let parsed =
1087 parse_background_shorthand(name, value, text_style, text_flow_style, color_scheme);
1088 if !parsed.layers.is_empty() {
1089 container_style.background = Background {
1090 color: existing_color,
1091 layers: parsed.layers,
1092 };
1093 }
1094 }
1095
1096 ("background-repeat", _) => {
1097 let repeats = parse_background_repeat(value);
1098 if repeats.len() == 1 {
1099 let r = repeats[0];
1100 for layer in &mut container_style.background.layers {
1101 layer.repeat = r;
1102 }
1103 } else {
1104 for (i, layer) in container_style.background.layers.iter_mut().enumerate() {
1105 layer.repeat = repeats.get(i).copied().unwrap_or_default();
1106 }
1107 }
1108 }
1109
1110 ("background-size", _) => {
1111 let sizes = parse_background_size(value, text_flow_style.font_size);
1112 if sizes.len() == 1 {
1113 let s = sizes[0];
1114 for layer in &mut container_style.background.layers {
1115 layer.size = s;
1116 }
1117 } else {
1118 for (i, layer) in container_style.background.layers.iter_mut().enumerate() {
1119 layer.size = sizes.get(i).copied().unwrap_or_default();
1120 }
1121 }
1122 }
1123
1124 ("background-position", _) => {
1125 let positions = parse_background_position(value, text_flow_style.font_size);
1126 if positions.len() == 1 {
1127 let p = positions[0];
1128 for layer in &mut container_style.background.layers {
1129 layer.position = p;
1130 }
1131 } else {
1132 for (i, layer) in container_style.background.layers.iter_mut().enumerate() {
1133 layer.position = positions.get(i).copied().unwrap_or_default();
1134 }
1135 }
1136 }
1137
1138 ("color", _) => {
1139 apply_property!(
1140 color,
1141 text_style,
1142 parent_text_style,
1143 DEFAULT_TEXT_STYLE,
1144 value,
1145 resolve_css_color(name, value, color_scheme)?
1146 );
1147 }
1148
1149 ("color-scheme", _) => {}
1151
1152 ("font-size", _) => {
1153 let px = match value {
1154 CssValue::Keyword(k) => match k.to_ascii_lowercase().as_str() {
1155 "xx-small" => Some(9.0),
1157 "x-small" => Some(10.0),
1158 "small" => Some(13.0),
1159 "medium" => Some(16.0),
1160 "large" => Some(18.0),
1161 "x-large" => Some(24.0),
1162 "xx-large" => Some(32.0),
1163 "smaller" => Some(text_flow_style.font_size * 5.0 / 6.0),
1165 "larger" => Some(text_flow_style.font_size * 6.0 / 5.0),
1166 _ => None,
1167 },
1168 _ => {
1169 let len = resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?;
1170 Some(resolve_font_size_px(&len, text_flow_style.font_size)?)
1171 }
1172 }?;
1173 text_flow_style.font_size = px;
1174 }
1175
1176 ("line-height", _) => {
1177 let f = |v: &CssValue| match v {
1178 CssValue::Number(factor) => Some(LineHeight::Number(*factor)),
1179 CssValue::Keyword(v) if v.eq_ignore_ascii_case("normal") => {
1180 Some(LineHeight::Normal)
1181 }
1182 _ => match resolve_calc_value(name, v, text_flow_style) {
1183 Some(CalcValue::Number(factor)) => Some(LineHeight::Number(factor)),
1184 Some(CalcValue::Length(len)) => Some(LineHeight::Px(length_to_px(
1185 &len,
1186 text_flow_style.font_size,
1187 ))),
1188 Some(CalcValue::Angle(_)) | None => None,
1189 },
1190 };
1191
1192 apply_property!(
1193 line_height,
1194 text_flow_style,
1195 parent_text_flow_style,
1196 DEFAULT_TEXT_FLOW_STYLE,
1197 value,
1198 f(value)?
1199 );
1200 }
1201
1202 ("font-weight", CssValue::Keyword(v)) => {
1203 text_style.font_weight = match v.as_str() {
1204 "normal" => FontWeight::NORMAL,
1205 "bold" => FontWeight::BOLD,
1206 _ => text_style.font_weight,
1207 };
1208 }
1209 ("font-weight", CssValue::Number(v)) => {
1210 text_style.font_weight = FontWeight(*v as u16);
1211 }
1212
1213 ("font-style", _) => {
1214 apply_property!(
1215 font_style,
1216 text_style,
1217 parent_text_style,
1218 DEFAULT_TEXT_STYLE,
1219 value,
1220 CssValue::Keyword(v),
1221 match v.as_str() {
1222 "normal" => FontStyle::Normal,
1223 "italic" => FontStyle::Italic,
1224 "oblique" => FontStyle::Oblique,
1225 _ => text_style.font_style,
1226 }
1227 );
1228 }
1229
1230 ("font-family", _) => {
1231 let families = extract_font_families(value);
1232 if !families.is_empty() {
1233 text_style.font_families = families;
1234 }
1235 }
1236
1237 ("font", v) => {
1238 let values: Vec<&CssValue> = match v {
1241 CssValue::List(list) => list.iter().collect(),
1242 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("inherit") => {
1243 text_style.font_style = parent_text_style.font_style;
1244 text_style.font_weight = parent_text_style.font_weight;
1245 text_style
1246 .font_families
1247 .clone_from(&parent_text_style.font_families);
1248 text_flow_style.font_size = parent_text_flow_style.font_size;
1249 text_flow_style.line_height = parent_text_flow_style.line_height;
1250 return Some(());
1251 }
1252 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("initial") => {
1253 text_style.font_style = DEFAULT_TEXT_STYLE.font_style;
1254 text_style.font_weight = DEFAULT_TEXT_STYLE.font_weight;
1255 text_style
1256 .font_families
1257 .clone_from(&DEFAULT_TEXT_STYLE.font_families);
1258 text_flow_style.font_size = DEFAULT_TEXT_FLOW_STYLE.font_size;
1259 text_flow_style.line_height = DEFAULT_TEXT_FLOW_STYLE.line_height;
1260 return Some(());
1261 }
1262 _ => return None,
1263 };
1264
1265 if values.is_empty() {
1266 return None;
1267 }
1268
1269 let font_size_idx = values
1271 .iter()
1272 .position(|v| matches!(v, CssValue::Length(_, _)))?;
1273
1274 let len = resolve_css_len(
1276 name,
1277 std::slice::from_ref(values[font_size_idx]),
1278 text_flow_style,
1279 )?;
1280 let px = resolve_font_size_px(&len, text_flow_style.font_size)?;
1281 text_flow_style.font_size = px;
1282
1283 for v in &values[..font_size_idx] {
1285 if let CssValue::Keyword(kw) = v {
1286 match kw.as_str() {
1287 "italic" => text_style.font_style = FontStyle::Italic,
1288 "oblique" => text_style.font_style = FontStyle::Oblique,
1289 "normal" => text_style.font_style = FontStyle::Normal,
1290 "bold" => text_style.font_weight = FontWeight::BOLD,
1291 "bolder" | "lighter" | "small-caps" => {} s if s.len() <= 3 && s.bytes().all(|b| b.is_ascii_digit()) => {
1293 if let Ok(w) = s.parse::<u16>()
1294 && (100..=900).contains(&w)
1295 {
1296 text_style.font_weight = FontWeight(w);
1297 }
1298 }
1299 _ => {}
1300 }
1301 } else if let CssValue::Number(n) = v
1302 && *n >= 100.0
1303 && *n <= 900.0
1304 && n.fract().abs() < f32::EPSILON
1305 {
1306 text_style.font_weight = FontWeight(*n as u16);
1307 }
1308 }
1309
1310 let after = &values[font_size_idx + 1..];
1312 let family_start = if after
1313 .first()
1314 .is_some_and(|v| matches!(v, CssValue::Keyword(k) if k == "/"))
1315 {
1316 if after.len() > 1 {
1317 match resolve_calc_value(name, after[1], text_flow_style) {
1318 Some(CalcValue::Number(factor)) => {
1319 text_flow_style.line_height = LineHeight::Number(factor);
1320 }
1321 Some(CalcValue::Length(lh_len)) => {
1322 text_flow_style.line_height =
1323 LineHeight::Px(length_to_px(&lh_len, text_flow_style.font_size));
1324 }
1325 Some(CalcValue::Angle(_)) | None => {}
1326 }
1327 2 } else {
1329 1 }
1331 } else {
1332 0
1333 };
1334
1335 let family_values = &values[font_size_idx + 1 + family_start..];
1337 if !family_values.is_empty() {
1338 let families: Vec<String> = family_values
1339 .iter()
1340 .filter_map(|v| match v {
1341 CssValue::Keyword(k) if !k.is_empty() => Some(k.to_string()),
1342 CssValue::String(s) if !s.is_empty() => Some(s.clone()),
1343 _ => None,
1344 })
1345 .collect();
1346 if !families.is_empty() {
1347 text_style.font_families = families;
1348 }
1349 }
1350
1351 return Some(());
1352 }
1353
1354 ("text-decoration", v) => {
1355 let items: Vec<&CssValue> = match v {
1356 CssValue::List(list) => list.iter().collect(),
1357 _ => vec![v],
1358 };
1359 for item in items {
1360 match item {
1361 CssValue::Keyword(k) => match k.as_str() {
1362 "none" => text_style.text_decoration = TextDecoration::None,
1363 "underline" => text_style.text_decoration = TextDecoration::Underline,
1364 "line-through" => text_style.text_decoration = TextDecoration::LineThrough,
1365 "overline" => text_style.text_decoration = TextDecoration::Overline,
1366 _ => {}
1367 },
1368 _ => {
1369 if let Some(c) = resolve_css_color(name, item, color_scheme) {
1370 text_style.text_decoration_color = Some(c);
1371 }
1372 }
1373 }
1374 }
1375 }
1376
1377 ("text-decoration-color", _) => {
1378 if let Some(c) = resolve_css_color(name, value, color_scheme) {
1379 text_style.text_decoration_color = Some(c);
1380 }
1381 }
1382
1383 ("vertical-align", _) => {
1384 apply_property!(
1385 vertical_align,
1386 text_flow_style,
1387 parent_text_flow_style,
1388 DEFAULT_TEXT_FLOW_STYLE,
1389 value,
1390 CssValue::Keyword(v),
1391 match v.as_str() {
1392 "sub" => VerticalAlign::Sub,
1393 "super" | "sup" => VerticalAlign::Super,
1394 _ => text_flow_style.vertical_align,
1395 }
1396 );
1397 }
1398
1399 ("text-transform", _) => {
1400 apply_property!(
1401 text_transform,
1402 text_style,
1403 parent_text_style,
1404 DEFAULT_TEXT_STYLE,
1405 value,
1406 CssValue::Keyword(v),
1407 match v.as_str() {
1408 "none" => TextTransform::None,
1409 "uppercase" => TextTransform::Uppercase,
1410 "lowercase" => TextTransform::Lowercase,
1411 _ => TextTransform::None,
1412 }
1413 );
1414 }
1415
1416 ("text-align", _) => {
1417 apply_property!(
1418 text_align,
1419 text_flow_style,
1420 parent_text_flow_style,
1421 DEFAULT_TEXT_FLOW_STYLE,
1422 value,
1423 CssValue::Keyword(v),
1424 match v.as_str() {
1425 "left" => TextAlign::Left,
1426 "center" => TextAlign::Center,
1427 "right" => TextAlign::Right,
1428 _ => return None,
1429 }
1430 );
1431 }
1432
1433 ("white-space", _) => {
1434 apply_property!(
1435 white_space,
1436 text_flow_style,
1437 parent_text_flow_style,
1438 DEFAULT_TEXT_FLOW_STYLE,
1439 value,
1440 CssValue::Keyword(v),
1441 match v.as_str() {
1442 "normal" => WhiteSpace::Normal,
1443 "nowrap" => WhiteSpace::Nowrap,
1444 "pre" => WhiteSpace::Pre,
1445 "pre-wrap" => WhiteSpace::PreWrap,
1446 "pre-line" => WhiteSpace::PreLine,
1447 "break-spaces" => WhiteSpace::BreakSpaces,
1448 _ => text_flow_style.white_space,
1449 }
1450 );
1451 }
1452
1453 ("box-sizing", _) => {
1457 apply_property!(
1458 box_sizing,
1459 style,
1460 parent_style,
1461 DEFAULT_STYLE,
1462 value,
1463 CssValue::Keyword(v),
1464 match v.as_str() {
1465 "content-box" => BoxSizing::ContentBox,
1466 "border-box" => BoxSizing::BorderBox,
1467 _ => BoxSizing::ContentBox,
1468 }
1469 );
1470 }
1471
1472 ("margin", v) => {
1473 expand_box(
1474 name,
1475 v,
1476 text_flow_style,
1477 &|_, cv, tfs| match cv {
1478 CssValue::Keyword(s) if s == "auto" => Some(ui_layout::LengthOrAuto::Auto),
1479 _ => resolve_css_len(name, std::slice::from_ref(cv), tfs)
1480 .map(ui_layout::LengthOrAuto::Length),
1481 },
1482 |t, r, b, l| {
1483 style.spacing.margin_top = t;
1484 style.spacing.margin_right = r;
1485 style.spacing.margin_bottom = b;
1486 style.spacing.margin_left = l;
1487 },
1488 )?;
1489 }
1490 ("margin-top", _) => {
1491 style.spacing.margin_top = resolve_css_len_auto(name, value, text_flow_style)?;
1492 }
1493 ("margin-right", _) => {
1494 style.spacing.margin_right = resolve_css_len_auto(name, value, text_flow_style)?;
1495 }
1496 ("margin-bottom", _) => {
1497 style.spacing.margin_bottom = resolve_css_len_auto(name, value, text_flow_style)?;
1498 }
1499 ("margin-left", _) => {
1500 style.spacing.margin_left = resolve_css_len_auto(name, value, text_flow_style)?;
1501 }
1502 ("margin-inline", _) => {
1503 let values = one_or_two_values(value)?;
1504 style.spacing.margin_left = resolve_css_len_auto(name, values.0, text_flow_style)?;
1505 style.spacing.margin_right = resolve_css_len_auto(name, values.1, text_flow_style)?;
1506 }
1507 ("margin-inline-start", _) => {
1508 style.spacing.margin_left = resolve_css_len_auto(name, value, text_flow_style)?;
1509 }
1510 ("margin-inline-end", _) => {
1511 style.spacing.margin_right = resolve_css_len_auto(name, value, text_flow_style)?;
1512 }
1513
1514 ("border", v) => {
1515 let (maybe_width, maybe_style, maybe_color) = if let CssValue::Keyword(k) = v
1516 && (k.eq_ignore_ascii_case("inset") || k.eq_ignore_ascii_case("initial"))
1517 {
1518 (Some(Length::Px(0.0)), None, None)
1519 } else {
1520 parse_border_shorthand(name, v, text_flow_style, color_scheme)?
1521 };
1522
1523 if let Some(w) = maybe_width {
1524 style.spacing.border_top = w.clone();
1525 style.spacing.border_right = w.clone();
1526 style.spacing.border_bottom = w.clone();
1527 style.spacing.border_left = w;
1528 }
1529
1530 if let Some(s) = maybe_style {
1531 container_style.border_style.top = s;
1532 container_style.border_style.right = s;
1533 container_style.border_style.bottom = s;
1534 container_style.border_style.left = s;
1535 }
1536
1537 if let Some(c) = maybe_color {
1538 container_style.border_color.top = c;
1539 container_style.border_color.right = c;
1540 container_style.border_color.bottom = c;
1541 container_style.border_color.left = c;
1542 }
1543 }
1544 ("border-top", _) => {
1545 let (maybe_width, maybe_style, maybe_color) =
1546 parse_border_shorthand(name, value, text_flow_style, color_scheme)?;
1547 if let Some(w) = maybe_width {
1548 style.spacing.border_top = w;
1549 }
1550 if let Some(s) = maybe_style {
1551 container_style.border_style.top = s;
1552 }
1553 if let Some(c) = maybe_color {
1554 container_style.border_color.top = c;
1555 }
1556 }
1557 ("border-right", _) => {
1558 let (maybe_width, maybe_style, maybe_color) =
1559 parse_border_shorthand(name, value, text_flow_style, color_scheme)?;
1560 if let Some(w) = maybe_width {
1561 style.spacing.border_right = w;
1562 }
1563 if let Some(s) = maybe_style {
1564 container_style.border_style.right = s;
1565 }
1566 if let Some(c) = maybe_color {
1567 container_style.border_color.right = c;
1568 }
1569 }
1570 ("border-bottom", _) => {
1571 let (maybe_width, maybe_style, maybe_color) =
1572 parse_border_shorthand(name, value, text_flow_style, color_scheme)?;
1573 if let Some(w) = maybe_width {
1574 style.spacing.border_bottom = w;
1575 }
1576 if let Some(s) = maybe_style {
1577 container_style.border_style.bottom = s;
1578 }
1579 if let Some(c) = maybe_color {
1580 container_style.border_color.bottom = c;
1581 }
1582 }
1583 ("border-left", _) => {
1584 let (maybe_width, maybe_style, maybe_color) =
1585 parse_border_shorthand(name, value, text_flow_style, color_scheme)?;
1586 if let Some(w) = maybe_width {
1587 style.spacing.border_left = w;
1588 }
1589 if let Some(s) = maybe_style {
1590 container_style.border_style.left = s;
1591 }
1592 if let Some(c) = maybe_color {
1593 container_style.border_color.left = c;
1594 }
1595 }
1596
1597 ("border-radius", v) => {
1598 let (tl, tr, br, bl) = parse_border_radius_shorthand(name, v, text_flow_style)?;
1599 container_style.border_radius = BorderRadius {
1600 top_left: tl,
1601 top_right: tr,
1602 bottom_right: br,
1603 bottom_left: bl,
1604 };
1605 }
1606 ("border-top-left-radius", v) => {
1607 container_style.border_radius.top_left = parse_corner_radius(name, v, text_flow_style)?;
1608 }
1609 ("border-top-right-radius", v) => {
1610 container_style.border_radius.top_right =
1611 parse_corner_radius(name, v, text_flow_style)?;
1612 }
1613 ("border-bottom-right-radius", v) => {
1614 container_style.border_radius.bottom_right =
1615 parse_corner_radius(name, v, text_flow_style)?;
1616 }
1617 ("border-bottom-left-radius", v) => {
1618 container_style.border_radius.bottom_left =
1619 parse_corner_radius(name, v, text_flow_style)?;
1620 }
1621
1622 ("clip-path", _) => {
1623 container_style.clip_path = parse_clip_path(name, value, text_flow_style)?;
1624 }
1625
1626 ("transform", _) => {
1627 container_style.transform = parse_transform(name, value, style, text_flow_style);
1628 }
1629
1630 ("border-style", v) => {
1631 expand_box(
1632 name,
1633 v,
1634 text_flow_style,
1635 &|_, cv, _| {
1636 let style = match cv {
1637 CssValue::Keyword(v) => match v.as_str() {
1638 "none" => BorderStyle::None,
1639 "solid" => BorderStyle::Solid,
1640 "dashed" => BorderStyle::Dashed,
1641 "dotted" => BorderStyle::Dotted,
1642 _ => return None,
1643 },
1644 _ => return None,
1645 };
1646
1647 Some(style)
1648 },
1649 |t, r, b, l| {
1650 container_style.border_style.top = t;
1651 container_style.border_style.right = r;
1652 container_style.border_style.bottom = b;
1653 container_style.border_style.left = l;
1654 },
1655 )?;
1656 }
1657
1658 ("border-color", v) => {
1659 expand_box(
1660 name,
1661 v,
1662 text_flow_style,
1663 &|_, cv, _| resolve_css_color(name, cv, color_scheme),
1664 |t, r, b, l| {
1665 container_style.border_color.top = t;
1666 container_style.border_color.right = r;
1667 container_style.border_color.bottom = b;
1668 container_style.border_color.left = l;
1669 },
1670 )?;
1671 }
1672 ("border-top-color", _) => {
1673 container_style.border_color.top = resolve_css_color(name, value, color_scheme)?;
1674 }
1675 ("border-right-color", _) => {
1676 container_style.border_color.right = resolve_css_color(name, value, color_scheme)?;
1677 }
1678 ("border-bottom-color", _) => {
1679 container_style.border_color.bottom = resolve_css_color(name, value, color_scheme)?;
1680 }
1681 ("border-left-color", _) => {
1682 container_style.border_color.left = resolve_css_color(name, value, color_scheme)?;
1683 }
1684
1685 ("border-width", v) => {
1686 expand_box(
1687 name,
1688 v,
1689 text_flow_style,
1690 &|_, cv, ts| match cv {
1691 CssValue::Keyword(s) => match s.as_str() {
1692 "thin" => Some(Length::Px(1.0)),
1693 "medium" => Some(Length::Px(3.0)),
1694 "thick" => Some(Length::Px(5.0)),
1695 _ => resolve_css_len(name, std::slice::from_ref(cv), ts),
1696 },
1697 _ => resolve_css_len(name, std::slice::from_ref(cv), ts),
1698 },
1699 |t, r, b, l| {
1700 style.spacing.border_top = t;
1701 style.spacing.border_right = r;
1702 style.spacing.border_bottom = b;
1703 style.spacing.border_left = l;
1704 },
1705 )?;
1706 }
1707 ("border-top-width", _) => {
1708 style.spacing.border_top = match value {
1709 CssValue::Keyword(s) => match s.as_str() {
1710 "thin" => Length::Px(1.0),
1711 "medium" => Length::Px(3.0),
1712 "thick" => Length::Px(5.0),
1713 _ => resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?,
1714 },
1715 _ => resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?,
1716 };
1717 }
1718 ("border-right-width", _) => {
1719 style.spacing.border_right = match value {
1720 CssValue::Keyword(s) => match s.as_str() {
1721 "thin" => Length::Px(1.0),
1722 "medium" => Length::Px(3.0),
1723 "thick" => Length::Px(5.0),
1724 _ => resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?,
1725 },
1726 _ => resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?,
1727 };
1728 }
1729 ("border-bottom-width", _) => {
1730 style.spacing.border_bottom = match value {
1731 CssValue::Keyword(s) => match s.as_str() {
1732 "thin" => Length::Px(1.0),
1733 "medium" => Length::Px(3.0),
1734 "thick" => Length::Px(5.0),
1735 _ => resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?,
1736 },
1737 _ => resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?,
1738 };
1739 }
1740 ("border-left-width", _) => {
1741 style.spacing.border_left = match value {
1742 CssValue::Keyword(s) => match s.as_str() {
1743 "thin" => Length::Px(1.0),
1744 "medium" => Length::Px(3.0),
1745 "thick" => Length::Px(5.0),
1746 _ => resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?,
1747 },
1748 _ => resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?,
1749 };
1750 }
1751
1752 ("padding", v) => {
1753 expand_box(
1754 name,
1755 v,
1756 text_flow_style,
1757 &|_, v, ts| resolve_css_len(name, std::slice::from_ref(v), ts),
1758 |t, r, b, l| {
1759 style.spacing.padding_top = t;
1760 style.spacing.padding_right = r;
1761 style.spacing.padding_bottom = b;
1762 style.spacing.padding_left = l;
1763 },
1764 )?;
1765 }
1766 ("padding-top", _) => {
1767 style.spacing.padding_top =
1768 resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?;
1769 }
1770 ("padding-right", _) => {
1771 style.spacing.padding_right =
1772 resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?;
1773 }
1774 ("padding-bottom", _) => {
1775 style.spacing.padding_bottom =
1776 resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?;
1777 }
1778 ("padding-left", _) => {
1779 style.spacing.padding_left =
1780 resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?;
1781 }
1782 ("padding-inline", _) => {
1783 let values = one_or_two_values(value)?;
1784 style.spacing.padding_left =
1785 resolve_css_len(name, std::slice::from_ref(values.0), text_flow_style)?;
1786 style.spacing.padding_right =
1787 resolve_css_len(name, std::slice::from_ref(values.1), text_flow_style)?;
1788 }
1789 ("padding-inline-start", _) => {
1790 style.spacing.padding_left =
1791 resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?;
1792 }
1793 ("padding-inline-end", _) => {
1794 style.spacing.padding_right =
1795 resolve_css_len(name, std::slice::from_ref(value), text_flow_style)?;
1796 }
1797
1798 ("width", _) => {
1802 style.size.width = resolve_css_len_auto(name, value, text_flow_style)?;
1803 }
1804 ("height", _) => {
1805 style.size.height = resolve_css_len_auto(name, value, text_flow_style)?;
1806 }
1807 ("min-width", _) => {
1808 style.size.min_width = resolve_css_len_auto(name, value, text_flow_style)?;
1809 }
1810 ("min-height", _) => {
1811 style.size.min_height = resolve_css_len_auto(name, value, text_flow_style)?;
1812 }
1813 ("max-width", _) => {
1814 style.size.max_width = resolve_css_len_auto(name, value, text_flow_style)?;
1815 }
1816 ("max-height", _) => {
1817 style.size.max_height = resolve_css_len_auto(name, value, text_flow_style)?;
1818 }
1819
1820 ("position", _) => {
1824 style.position.kind = match value {
1825 CssValue::Keyword(v) => match v.to_ascii_lowercase().as_str() {
1826 "static" => Position::Static,
1827 "relative" => Position::Relative,
1828 "absolute" => Position::Absolute,
1829 "fixed" => Position::Fixed,
1830 "sticky" => Position::Sticky,
1831 _ => return None,
1832 },
1833 _ => return None,
1834 };
1835 }
1836 ("anchor-name", _) => {
1837 container_style.anchor.anchors.clear();
1838 match value {
1839 CssValue::Keyword(k) if k.eq_ignore_ascii_case("none") => {}
1840 CssValue::List(list) => {
1841 for v in list {
1842 if let CssValue::Keyword(k) = v {
1843 if k.eq_ignore_ascii_case("none") {
1844 continue;
1845 }
1846 if k.starts_with("--") {
1847 container_style.anchor.anchors.push(k.to_string());
1848 }
1849 }
1850 }
1851 }
1852 CssValue::Keyword(k) if k.starts_with("--") => {
1853 container_style.anchor.anchors.push(k.to_string());
1854 }
1855 _ => return None,
1856 }
1857 }
1858 ("position-anchor", _) => match value {
1859 CssValue::Keyword(k) if k.eq_ignore_ascii_case("auto") => {
1860 container_style.anchor.position_anchor = None;
1861 }
1862 CssValue::Keyword(k) if k.eq_ignore_ascii_case("none") => {
1863 container_style.anchor.position_anchor = None;
1864 }
1865 CssValue::Keyword(k) if k.starts_with("--") => {
1866 container_style.anchor.position_anchor = Some(k.to_string());
1867 }
1868 _ => return None,
1869 },
1870 ("anchor-scope", _) => match value {
1871 CssValue::Keyword(k) if k.eq_ignore_ascii_case("none") => {
1872 container_style.anchor.anchor_scope = None;
1873 }
1874 CssValue::Keyword(k) if k.starts_with("--") => {
1875 container_style.anchor.anchor_scope = Some(k.to_string());
1876 }
1877 _ => return None,
1878 },
1879 ("direction", _) => {
1880 apply_property!(
1881 direction,
1882 container_style,
1883 parent_container_style,
1884 DEFAULT_CONTAINER_STYLE,
1885 value,
1886 CssValue::Keyword(v),
1887 match v.as_str() {
1888 "ltr" => Direction::Ltr,
1889 "rtl" => Direction::Rtl,
1890 _ => return None,
1891 }
1892 );
1893 }
1894 ("writing-mode", _) => {
1895 apply_property!(
1896 writing_mode,
1897 container_style,
1898 parent_container_style,
1899 DEFAULT_CONTAINER_STYLE,
1900 value,
1901 CssValue::Keyword(v),
1902 match v.as_str() {
1903 "horizontal-tb" => WritingMode::HorizontalTb,
1904 "vertical-rl" => WritingMode::VerticalRl,
1905 "vertical-lr" => WritingMode::VerticalLr,
1906 _ => return None,
1907 }
1908 );
1909 }
1910 ("top", _) => {
1911 if let CssValue::Function(name_fn, _) = value
1913 && name_fn.eq_ignore_ascii_case("anchor")
1914 && let Some(spec) = parse_anchor_spec(value)
1915 {
1916 container_style.anchor.top = Some(spec.clone());
1917 style.position.top = spec.fallback.unwrap_or(ui_layout::LengthOrAuto::Auto);
1918 return Some(());
1919 }
1920 style.position.top = resolve_css_len_auto(name, value, text_flow_style)?;
1921 }
1922 ("right", _) => {
1923 if let CssValue::Function(name_fn, _) = value
1924 && name_fn.eq_ignore_ascii_case("anchor")
1925 && let Some(spec) = parse_anchor_spec(value)
1926 {
1927 container_style.anchor.right = Some(spec.clone());
1928 style.position.right = spec.fallback.unwrap_or(ui_layout::LengthOrAuto::Auto);
1929 return Some(());
1930 }
1931 style.position.right = resolve_css_len_auto(name, value, text_flow_style)?;
1932 }
1933 ("bottom", _) => {
1934 if let CssValue::Function(name_fn, _) = value
1935 && name_fn.eq_ignore_ascii_case("anchor")
1936 && let Some(spec) = parse_anchor_spec(value)
1937 {
1938 container_style.anchor.bottom = Some(spec.clone());
1939 style.position.bottom = spec.fallback.unwrap_or(ui_layout::LengthOrAuto::Auto);
1940 return Some(());
1941 }
1942 style.position.bottom = resolve_css_len_auto(name, value, text_flow_style)?;
1943 }
1944 ("left", _) => {
1945 if let CssValue::Function(name_fn, _) = value
1946 && name_fn.eq_ignore_ascii_case("anchor")
1947 && let Some(spec) = parse_anchor_spec(value)
1948 {
1949 container_style.anchor.left = Some(spec.clone());
1950 style.position.left = spec.fallback.unwrap_or(ui_layout::LengthOrAuto::Auto);
1951 return Some(());
1952 }
1953 style.position.left = resolve_css_len_auto(name, value, text_flow_style)?;
1954 }
1955 ("inset", v) => {
1956 let (tv, rv, bv, lv): (&CssValue, &CssValue, &CssValue, &CssValue) = match v {
1957 CssValue::List(values) => match values.as_slice() {
1958 [a] => (a, a, a, a),
1959 [vert, horiz] => (vert, horiz, vert, horiz),
1960 [t, h, b] => (t, h, b, h),
1961 [t, r, b, l] => (t, r, b, l),
1962 _ => return None,
1963 },
1964 _ => (v, v, v, v),
1965 };
1966 for (side, cv) in [
1967 (AnchorInsetSide::Top, tv),
1968 (AnchorInsetSide::Right, rv),
1969 (AnchorInsetSide::Bottom, bv),
1970 (AnchorInsetSide::Left, lv),
1971 ] {
1972 if let CssValue::Function(nf, _) = cv
1973 && nf.eq_ignore_ascii_case("anchor")
1974 && let Some(spec) = parse_anchor_spec(cv)
1975 {
1976 *container_style.anchor.inset_mut(side) = Some(spec.clone());
1977 let fallback = spec.fallback.unwrap_or(ui_layout::LengthOrAuto::Auto);
1978 match side {
1979 AnchorInsetSide::Top => style.position.top = fallback,
1980 AnchorInsetSide::Right => style.position.right = fallback,
1981 AnchorInsetSide::Bottom => style.position.bottom = fallback,
1982 AnchorInsetSide::Left => style.position.left = fallback,
1983 }
1984 continue;
1985 }
1986 let resolved = match cv {
1987 CssValue::Keyword(s) if s.eq_ignore_ascii_case("auto") => {
1988 ui_layout::LengthOrAuto::Auto
1989 }
1990 _ => ui_layout::LengthOrAuto::Length(resolve_css_len(
1991 name,
1992 std::slice::from_ref(cv),
1993 text_flow_style,
1994 )?),
1995 };
1996 match side {
1997 AnchorInsetSide::Top => style.position.top = resolved,
1998 AnchorInsetSide::Right => style.position.right = resolved,
1999 AnchorInsetSide::Bottom => style.position.bottom = resolved,
2000 AnchorInsetSide::Left => style.position.left = resolved,
2001 }
2002 }
2003 }
2004
2005 ("overflow", _) => {
2009 let (x, y) = overflow_flags(value)?;
2010 overflow.x = x;
2011 overflow.y = y;
2012 }
2013 ("overflow-x", CssValue::Keyword(k)) => {
2014 overflow.x = overflow_scrollable(k)?;
2015 }
2016 ("overflow-y", CssValue::Keyword(k)) => {
2017 overflow.y = overflow_scrollable(k)?;
2018 }
2019
2020 ("flex-direction", _) => {
2024 apply_property!(
2025 flex_direction,
2026 style,
2027 parent_style,
2028 DEFAULT_STYLE,
2029 value,
2030 CssValue::Keyword(v),
2031 flex_direction_keyword(v)?
2032 );
2033 }
2034
2035 ("flex-wrap", _) => {
2036 apply_property!(
2037 flex_wrap,
2038 style,
2039 parent_style,
2040 DEFAULT_STYLE,
2041 value,
2042 CssValue::Keyword(v),
2043 flex_wrap_keyword(v)?
2044 );
2045 }
2046
2047 ("flex-flow", _) => {
2048 let (direction, wrap) = resolve_flex_flow(value)?;
2049 style.flex_direction = direction;
2050 style.flex_wrap = wrap;
2051 }
2052
2053 ("place-items", value) => {
2054 let values = match value {
2055 CssValue::Keyword(_) => std::slice::from_ref(value),
2056 CssValue::List(values) if (1..=2).contains(&values.len()) => values.as_slice(),
2057 _ => return None,
2058 };
2059
2060 let align_items = match &values[0] {
2061 CssValue::Keyword(v) => resolve_align_items(v)?,
2062 _ => return None,
2063 };
2064
2065 let justify_items = match values.get(1).unwrap_or(&values[0]) {
2066 CssValue::Keyword(v) => resolve_justify_items(v)?,
2067 _ => return None,
2068 };
2069
2070 style.align_items = align_items;
2071 style.justify_items = justify_items;
2072 }
2073
2074 ("justify-items", _) => {
2075 apply_property!(
2076 justify_items,
2077 style,
2078 parent_style,
2079 DEFAULT_STYLE,
2080 value,
2081 CssValue::Keyword(v),
2082 resolve_justify_items(v)?
2083 );
2084 }
2085
2086 ("justify-content", _) => {
2087 apply_property!(
2088 justify_content,
2089 style,
2090 parent_style,
2091 DEFAULT_STYLE,
2092 value,
2093 CssValue::Keyword(v),
2094 match v.as_str() {
2095 "flex-start" | "start" => JustifyContent::Start,
2096 "center" => JustifyContent::Center,
2097 "flex-end" | "end" => JustifyContent::End,
2098 "space-between" => JustifyContent::SpaceBetween,
2099 "space-around" => JustifyContent::SpaceAround,
2100 "space-evenly" => JustifyContent::SpaceEvenly,
2101 _ => return None,
2102 }
2103 );
2104 }
2105
2106 ("align-items", _) => {
2107 apply_property!(
2108 align_items,
2109 style,
2110 parent_style,
2111 DEFAULT_STYLE,
2112 value,
2113 CssValue::Keyword(v),
2114 resolve_align_items(v)?
2115 );
2116 }
2117
2118 ("align-content", _) => {
2119 apply_property!(
2120 align_content,
2121 style,
2122 parent_style,
2123 DEFAULT_STYLE,
2124 value,
2125 CssValue::Keyword(v),
2126 match v.as_str() {
2127 "normal" | "stretch" => AlignContent::Stretch,
2128 "flex-start" | "start" => AlignContent::Start,
2129 "center" => AlignContent::Center,
2130 "flex-end" | "end" => AlignContent::End,
2131 "space-between" => AlignContent::SpaceBetween,
2132 "space-around" => AlignContent::SpaceAround,
2133 "space-evenly" => AlignContent::SpaceEvenly,
2134 _ => return None,
2135 }
2136 );
2137 }
2138
2139 ("gap", _) => match value {
2140 CssValue::List(l) if l.len() == 2 => {
2141 let mut l = l.iter();
2142 let gap = resolve_css_len_auto(name, l.next()?, text_flow_style)?;
2143 style.row_gap = gap;
2144 let gap = resolve_css_len_auto(name, l.next()?, text_flow_style)?;
2145 style.column_gap = gap;
2146 }
2147 CssValue::Length(_, _) => {
2148 let gap = resolve_css_len_auto(name, value, text_flow_style)?;
2149 style.row_gap = gap.clone();
2150 style.column_gap = gap;
2151 }
2152 _ => {}
2153 },
2154
2155 ("flex", _) => {
2156 let (grow, shrink, basis) = resolve_flex_shorthand(value, text_flow_style)?;
2157 style.item_style.flex_grow = grow;
2158 style.item_style.flex_shrink = shrink;
2159 style.item_style.flex_basis = basis;
2160 }
2161
2162 ("flex-grow", value) => {
2163 style.item_style.flex_grow = match value {
2164 CssValue::Keyword(k) if k == "initial" => DEFAULT_STYLE.item_style.flex_grow,
2165 CssValue::Keyword(k) if k == "inherit" => parent_style.item_style.flex_grow,
2166 CssValue::Keyword(k) if k == "unset" => DEFAULT_STYLE.item_style.flex_grow,
2167 CssValue::Number(v) if *v >= 0.0 => *v,
2168 _ => return None,
2169 };
2170 }
2171
2172 ("flex-basis", value) => {
2173 style.item_style.flex_basis = match value {
2174 CssValue::Keyword(k) if k == "initial" => {
2175 DEFAULT_STYLE.item_style.flex_basis.clone()
2176 }
2177 CssValue::Keyword(k) if k == "inherit" => {
2178 parent_style.item_style.flex_basis.clone()
2179 }
2180 CssValue::Keyword(k) if k == "unset" => DEFAULT_STYLE.item_style.flex_basis.clone(),
2181 _ => resolve_css_len_auto(name, value, text_flow_style)?,
2182 };
2183 }
2184
2185 ("flex-shrink", value) => {
2186 style.item_style.flex_shrink = match value {
2187 CssValue::Keyword(k) if k == "initial" => DEFAULT_STYLE.item_style.flex_shrink,
2188 CssValue::Keyword(k) if k == "inherit" => parent_style.item_style.flex_shrink,
2189 CssValue::Keyword(k) if k == "unset" => DEFAULT_STYLE.item_style.flex_shrink,
2190 CssValue::Number(v) if *v >= 0.0 => *v,
2191 _ => return None,
2192 };
2193 }
2194
2195 ("place-self", value) => {
2196 match value {
2197 CssValue::Keyword(k) if k == "initial" => {
2198 style.item_style.align_self = DEFAULT_STYLE.item_style.align_self;
2199 style.item_style.justify_self = DEFAULT_STYLE.item_style.justify_self;
2200 }
2201 CssValue::Keyword(k) if k == "inherit" => {
2202 style.item_style.align_self = parent_style.item_style.align_self;
2203 style.item_style.justify_self = parent_style.item_style.justify_self;
2204 }
2205 CssValue::Keyword(k) if k == "unset" => {
2206 style.item_style.align_self = DEFAULT_STYLE.item_style.align_self;
2207 style.item_style.justify_self = DEFAULT_STYLE.item_style.justify_self;
2208 }
2209 _ => {}
2210 }
2211 let values = match value {
2212 CssValue::Keyword(_) => std::slice::from_ref(value),
2213 CssValue::List(values) if (1..=2).contains(&values.len()) => values.as_slice(),
2214 _ => return None,
2215 };
2216
2217 let align_items = match &values[0] {
2218 CssValue::Keyword(v) => resolve_align_items(v)?,
2219 _ => return None,
2220 };
2221
2222 let justify_items = match values.get(1).unwrap_or(&values[0]) {
2223 CssValue::Keyword(v) => resolve_justify_items(v)?,
2224 _ => return None,
2225 };
2226
2227 style.item_style.align_self = Some(align_items);
2228 style.item_style.justify_self = Some(justify_items);
2229 }
2230
2231 ("align-self", CssValue::Keyword(v)) => {
2232 style.item_style.align_self = Some(match v.as_str() {
2233 "stretch" => AlignItems::Stretch,
2234 "flex-start" | "start" => AlignItems::Start,
2235 "center" => AlignItems::Center,
2236 "flex-end" | "end" => AlignItems::End,
2237 "auto" => return Some(()),
2238 _ => return None,
2239 });
2240 }
2241
2242 ("justify-self", CssValue::Keyword(v)) => {
2243 style.item_style.justify_self = Some(match v.as_str() {
2244 "stretch" => JustifyItems::Stretch,
2245 "flex-start" | "start" => JustifyItems::Start,
2246 "center" => JustifyItems::Center,
2247 "flex-end" | "end" => JustifyItems::End,
2248 "auto" => return Some(()),
2249 _ => return None,
2250 });
2251 }
2252
2253 ("column-gap", _) => {
2254 style.column_gap = resolve_css_len_auto(name, value, text_flow_style)?;
2255 }
2256
2257 ("row-gap", _) => {
2258 style.row_gap = resolve_css_len_auto(name, value, text_flow_style)?;
2259 }
2260
2261 ("grid-template-columns", _) => {
2265 style.grid_template_columns = parse_grid_tracks(name, value, text_flow_style)?;
2266 }
2267
2268 ("grid-template-rows", _) => {
2269 style.grid_template_rows = parse_grid_tracks(name, value, text_flow_style)?;
2270 }
2271
2272 ("grid-template", _) => {
2273 let template = parse_grid_template(value, text_flow_style)?;
2276
2277 style.grid_template_rows = template.rows;
2278 style.grid_template_columns = template.columns;
2279 style.grid_template_areas = template.areas;
2280 }
2281
2282 ("grid-template-areas", _) => {
2283 style.grid_template_areas = parse_grid_template_areas(value)?;
2284 }
2285
2286 ("grid-area", CssValue::Keyword(area)) => {
2287 style.grid_area = Some(area.to_string());
2288 }
2289
2290 ("grid-column", _) => {
2291 style.grid_column = parse_grid_placement(value)?;
2292 }
2293
2294 ("grid-row", _) => {
2295 style.grid_row = parse_grid_placement(value)?;
2296 }
2297
2298 ("grid-column-start", _) => {
2299 style.grid_column.start = Some(parse_grid_line(value)?);
2300 }
2301
2302 ("grid-column-end", _) => {
2303 style.grid_column.end = parse_grid_line_end(value)?;
2304 }
2305
2306 ("grid-row-start", _) => {
2307 style.grid_row.start = Some(parse_grid_line(value)?);
2308 }
2309
2310 ("grid-row-end", _) => {
2311 style.grid_row.end = parse_grid_line_end(value)?;
2312 }
2313
2314 ("content", _) => {}
2318
2319 ("counter-reset", _) => {}
2322 ("counter-increment", _) => {}
2323
2324 _ => {
2325 return None;
2331 }
2332 }
2333 Some(())
2334}
2335
2336fn parse_cursor_value(value: &CssValue, _font_size: f32) -> CursorStyle {
2344 let items: Vec<&CssValue> = match value {
2346 CssValue::List(items) => items.iter().collect(),
2347 _ => vec![value],
2348 };
2349
2350 let mut images: Vec<CustomCursor> = Vec::new();
2351 let mut fallback: Option<CursorKeyword> = None;
2352
2353 for item in items {
2354 match item {
2355 CssValue::Function(fn_name, args) if fn_name.eq_ignore_ascii_case("url") => {
2356 let source = args.iter().flatten().find_map(|v| match v {
2357 CssValue::String(s) => Some(s.clone()),
2358 CssValue::Keyword(s) => Some(s.to_string()),
2359 _ => None,
2360 });
2361 if let Some(src) = source {
2362 let hotspot = parse_cursor_hotspot(args);
2364 images.push(CustomCursor {
2365 source: src,
2366 hotspot,
2367 });
2368 }
2369 }
2370 CssValue::Keyword(kw) => {
2371 if images.is_empty() && fallback.is_none() {
2373 return CursorStyle::Keyword(CursorKeyword::from_keyword(kw));
2374 }
2375 fallback = Some(CursorKeyword::from_keyword(kw));
2376 }
2377 _ => {}
2378 }
2379 }
2380
2381 if images.is_empty() {
2382 CursorStyle::Keyword(fallback.unwrap_or(CursorKeyword::Auto))
2383 } else {
2384 CursorStyle::Custom {
2385 images,
2386 fallback: fallback.unwrap_or(CursorKeyword::Auto),
2387 }
2388 }
2389}
2390
2391fn parse_cursor_hotspot(args: &[Vec<CssValue>]) -> Option<(u32, u32)> {
2396 let mut nums: Vec<u32> = Vec::new();
2397 for group in args {
2398 for item in group {
2399 if let CssValue::Number(n) = item {
2400 nums.push(*n as u32);
2401 }
2402 }
2403 }
2404 if nums.len() >= 2 {
2405 Some((nums[0], nums[1]))
2406 } else {
2407 None
2408 }
2409}
2410
2411impl CursorKeyword {
2413 fn from_keyword(kw: &SmolStr) -> CursorKeyword {
2414 match kw.as_str() {
2415 "auto" => CursorKeyword::Auto,
2416 "default" => CursorKeyword::Default,
2417 "none" => CursorKeyword::None,
2418 "pointer" => CursorKeyword::Pointer,
2419 "text" => CursorKeyword::Text,
2420 "move" => CursorKeyword::Move,
2421 "not-allowed" => CursorKeyword::NotAllowed,
2422 "wait" => CursorKeyword::Wait,
2423 "crosshair" => CursorKeyword::Crosshair,
2424 "grab" => CursorKeyword::Grab,
2425 "grabbing" => CursorKeyword::Grabbing,
2426 _ => CursorKeyword::Auto,
2427 }
2428 }
2429}