1use crate::engine::bridge::text;
4use crate::engine::css::{
5 matcher::{ElementChain, ElementInfo},
6 values::{CssValue, Unit},
7};
8use crate::engine::html::HtmlNodeType;
9use crate::engine::tree::TreeNode;
10
11use std::cell::RefCell;
12use std::collections::HashMap;
13use std::rc::Rc;
14
15use ui_layout::{
16 AlignItems, BoxSizing, Display, FlexDirection, Fragment, ItemFragment, JustifyContent,
17 LayoutNode, Length, Style,
18};
19
20use super::css_resolver::ResolvedStyles;
21use super::types::{
22 BorderStyle, Color, ContainerRole, ContainerStyle, FontStyle, FontWeight, InfoNode, NodeKind,
23 TextAlign, TextDecoration, TextStyle,
24};
25
26pub fn build_layout_and_info(
67 dom: &Rc<RefCell<TreeNode<HtmlNodeType>>>,
68 resolved_styles: &ResolvedStyles,
69 measurer: &dyn text::TextMeasurer<TextStyle>,
70 parent_text_style: TextStyle,
71 mut chain: ElementChain,
72) -> (LayoutNode, InfoNode) {
73 let html_node = dom.borrow().value.clone();
74
75 let mut style = Style::default();
79
80 let mut text_style = parent_text_style;
81 let mut container_style = ContainerStyle::default();
82
83 if let HtmlNodeType::Element {
87 tag_name,
88 attributes,
89 ..
90 } = &html_node
91 {
92 let id = attributes
93 .iter()
94 .find(|a| a.name == "id")
95 .map(|a| a.value.clone());
96
97 let class_list: Vec<String> = attributes
98 .iter()
99 .find(|attr| attr.name == "class")
100 .map(|attr| {
101 attr.value
102 .split_whitespace()
103 .map(|s| s.to_string())
104 .collect()
105 })
106 .unwrap_or_default();
107
108 chain.insert(
109 0,
110 ElementInfo {
111 tag_name: tag_name.clone(),
112 id,
113 classes: class_list,
114 },
115 );
116
117 let candidates = collect_candidates(resolved_styles, &chain);
118
119 for (name, (value, _, _)) in candidates {
120 if name.starts_with("--") {
121 continue;
122 }
123 apply_declaration(
124 &name,
125 &value,
126 &mut style,
127 &mut container_style,
128 &mut text_style,
129 );
130 }
131 }
132
133 let (mut kind, inline_fragments_opt) = if let HtmlNodeType::Text(t) = &html_node {
134 let t = normalize_whitespace(t);
135
136 let mut kind = NodeKind::Text {
137 texts: split_fragments(&t),
138 style: text_style,
139 };
140
141 let inline_fragments = build_inline_fragments(&mut kind, measurer);
142
143 (kind, Some(inline_fragments))
144 } else if let Some(name) = html_node.tag_name()
145 && name == "a"
146 && let Some(href) = html_node.get_attr("href")
147 {
148 (
149 NodeKind::Container {
150 scroll_x: false,
151 scroll_y: false,
152 scroll_offset_x: 0.0,
153 scroll_offset_y: 0.0,
154 style: container_style,
155 role: ContainerRole::Link {
156 href: href.to_string(),
157 },
158 },
159 None,
160 )
161 } else {
162 (
163 NodeKind::Container {
164 scroll_x: false,
165 scroll_y: false,
166 scroll_offset_x: 0.0,
167 scroll_offset_y: 0.0,
168 style: container_style,
169 role: ContainerRole::Normal,
170 },
171 None,
172 )
173 };
174
175 let (layout, info) = if let Some(inline_fragments) = inline_fragments_opt {
177 let style = Style {
182 display: Display::Inline,
183 ..style
184 };
185
186 let mut layout = LayoutNode::new(style);
187
188 layout.set_fragments(inline_fragments);
189
190 let info = InfoNode {
191 kind,
192 children: vec![],
193 };
194
195 (layout, info)
196 } else {
197 let mut layout_children = Vec::new();
206 let mut info_children = Vec::new();
207
208 if !matches!(style.display, Display::None) {
209 let mut has_text_child = false;
210
211 for child_dom in dom.borrow().children() {
212 if matches!(child_dom.borrow().value, HtmlNodeType::Text(_)) {
213 has_text_child = true;
214 break;
215 }
216 }
217
218 if has_text_child && matches!(style.display, Display::Block) {
220 style.display = Display::Flex {
221 flex_direction: FlexDirection::Row,
222 };
223 }
224
225 match &html_node {
227 HtmlNodeType::Element { tag_name, .. }
228 if tag_name == "table"
229 || tag_name == "tbody"
230 || tag_name == "thead"
231 || tag_name == "tfoot" =>
232 {
233 style.display = Display::Flex {
234 flex_direction: FlexDirection::Column,
235 };
236 }
237 HtmlNodeType::Element { tag_name, .. } if tag_name == "tr" => {
238 style.display = Display::Flex {
239 flex_direction: FlexDirection::Row,
240 };
241 }
242 _ => {}
243 }
244
245 for child_dom in dom.borrow().children() {
246 let (child_layout, child_info) = build_layout_and_info(
247 child_dom,
248 resolved_styles,
249 measurer,
250 text_style,
251 chain.clone(),
252 );
253
254 if dom.borrow().value.tag_name() == Some("html")
255 && child_dom.borrow().value.tag_name() == Some("body")
256 && let NodeKind::Container { style, .. } = &mut kind
257 && style.background_color == Color(0, 0, 0, 0)
258 {
259 let background_color = {
260 let NodeKind::Container { style, .. } = &child_info.kind else {
261 continue;
262 };
263 style.background_color
264 };
265 style.background_color = background_color;
268 }
269
270 layout_children.push(child_layout);
271 info_children.push(child_info);
272 }
273 }
274
275 let layout = LayoutNode::with_children(style, layout_children);
276
277 let info = InfoNode {
278 kind,
279 children: info_children,
280 };
281
282 (layout, info)
283 };
284
285 (layout, info)
286}
287
288fn split_fragments(text: &str) -> Vec<String> {
290 let mut out = Vec::new();
291 let mut buf = String::new();
292
293 for c in text.chars() {
294 buf.push(c);
295
296 if c.is_whitespace() || c == '-' || !c.is_ascii() {
297 out.push(buf.clone());
298 buf.clear();
299 }
300 }
301
302 if !buf.is_empty() {
303 out.push(buf);
304 }
305
306 out
307}
308
309fn build_inline_fragments(
310 kind: &mut NodeKind,
311 measurer: &dyn text::TextMeasurer<TextStyle>,
312) -> Vec<ItemFragment> {
313 let NodeKind::Text { texts, style } = kind else {
314 return vec![];
315 };
316
317 let mut inline_fragments = Vec::with_capacity(texts.len());
318
319 for text in texts {
320 let req = text::TextMeasureRequest {
321 text: text.clone(),
322 style: *style,
323 max_width: None,
324 wrap: false,
325 };
326
327 let (width, height) = measurer
328 .measure(&req)
329 .map(|m| (m.width, m.height))
330 .unwrap_or((800.0, style.font_size * 1.2));
331
332 let fragment = ItemFragment::Fragment(Fragment { width, height });
333
334 inline_fragments.push(fragment);
335 }
336
337 inline_fragments
338}
339
340fn normalize_whitespace(text: &str) -> String {
341 let mut result = String::new();
342 let mut prev_was_space = false;
343
344 for c in text.chars() {
345 if c.is_whitespace() {
346 if !prev_was_space {
347 result.push(' ');
348 prev_was_space = true;
349 }
350 } else {
351 result.push(c);
352 prev_was_space = false;
353 }
354 }
355
356 result
357}
358
359fn collect_candidates(
360 resolved_styles: &ResolvedStyles,
361 chain: &ElementChain,
362) -> HashMap<String, (CssValue, (u32, u32, u32), usize)> {
363 let mut candidates: HashMap<String, (CssValue, (u32, u32, u32), usize)> = HashMap::new();
364
365 for decl in resolved_styles {
366 if decl.selector.matches(chain) {
367 let entry = candidates.get(&decl.name);
368
369 let should_replace = match entry {
370 None => true,
371 Some((_, spec, order)) => {
372 decl.specificity > *spec || (decl.specificity == *spec && decl.order > *order)
373 }
374 };
375
376 if should_replace {
377 candidates.insert(
378 decl.name.clone(),
379 (decl.value.clone(), decl.specificity, decl.order),
380 );
381 }
382 }
383 }
384
385 candidates
386}
387
388fn apply_declaration(
389 name: &str,
390 value: &CssValue,
391 style: &mut Style,
392 container_style: &mut ContainerStyle,
393 text_style: &mut TextStyle,
394) -> Option<()> {
395 fn expand_box<F>(
396 value: &CssValue,
397 text_style: &TextStyle,
398 resolve_css_len: &impl Fn(&CssValue, &TextStyle) -> Option<Length>,
399 mut set: F,
400 ) -> Option<()>
401 where
402 F: FnMut(Length, Length, Length, Length),
403 {
404 let resolve = |v: &CssValue| -> Option<Length> { resolve_css_len(v, text_style) };
405
406 match value {
407 CssValue::List(values) => {
408 let vals: Vec<Length> = values.iter().map(resolve).collect::<Option<_>>()?;
409
410 match vals.as_slice() {
411 [a] => set(a.clone(), a.clone(), a.clone(), a.clone()),
412 [v, h] => set(v.clone(), h.clone(), v.clone(), h.clone()),
413 [t, h, b] => set(t.clone(), h.clone(), b.clone(), h.clone()),
414 [t, r, b, l] => set(t.clone(), r.clone(), b.clone(), l.clone()),
415 _ => return None,
416 }
417 }
418
419 _ => {
420 let v = resolve_css_len(value, text_style)?;
421 set(v.clone(), v.clone(), v.clone(), v);
422 }
423 }
424
425 Some(())
426 }
427
428 fn parse_border_shorthand(
429 value: &CssValue,
430 text_style: &TextStyle,
431 ) -> Option<(Option<Length>, Option<BorderStyle>, Option<Color>)> {
432 let mut width: Option<Length> = None;
433 let mut style_v: Option<BorderStyle> = None;
434 let mut color_v: Option<Color> = None;
435
436 let items: Vec<&CssValue> = match value {
437 CssValue::List(values) => values.iter().collect(),
438 _ => vec![value],
439 };
440
441 for v in items {
442 let token = v;
443
444 if width.is_none()
446 && let Some(l) = resolve_css_len(token, text_style)
447 {
448 width = Some(l);
449 continue;
450 }
451
452 if width.is_none()
454 && let CssValue::Keyword(s) = token
455 {
456 match s.as_str().to_ascii_lowercase().as_str() {
457 "thin" => {
458 width = Some(Length::Px(1.0));
459 continue;
460 }
461 "medium" => {
462 width = Some(Length::Px(3.0));
463 continue;
464 }
465 "midium" => {
466 width = Some(Length::Px(3.0));
467 continue;
468 } "thick" => {
470 width = Some(Length::Px(5.0));
471 continue;
472 }
473 _ => {}
474 }
475 }
476
477 if style_v.is_none()
479 && let CssValue::Keyword(s) = token
480 {
481 let s_lower = s.as_str();
482 let parsed = match s_lower {
483 "none" => Some(BorderStyle::None),
484 "solid" => Some(BorderStyle::Solid),
485 "dashed" => Some(BorderStyle::Dashed),
486 "dotted" => Some(BorderStyle::Dotted),
487 _ => None,
488 };
489
490 if let Some(p) = parsed {
491 style_v = Some(p);
492 continue;
493 }
494 }
495
496 if color_v.is_none()
498 && let Some(c) = resolve_css_color(token)
499 {
500 color_v = Some(c);
501 continue;
502 }
503
504 }
506
507 Some((width, style_v, color_v))
508 }
509
510 match (name, value) {
511 ("display", CssValue::Keyword(v)) => {
515 style.display = match v.as_str() {
516 "block" => Display::Block,
517 "flex" => Display::Flex {
518 flex_direction: FlexDirection::Row,
519 },
520 "inline" => Display::Inline,
521 "none" => Display::None,
522 _ => style.display,
523 };
524 }
525
526 ("background-color", _) => {
530 container_style.background_color = match value {
531 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("inherit") => {
532 text_style.color
534 }
535 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("currentColor") => {
536 text_style.color
537 }
538 _ => resolve_css_color(value)?,
539 };
540 }
541
542 ("background", _) => {
543 container_style.background_color = match value {
544 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("inherit") => {
545 text_style.color
547 }
548 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("currentColor") => {
549 text_style.color
550 }
551 _ => resolve_css_color(value)?,
552 };
553 }
554
555 ("color", _) => {
556 text_style.color = match value {
557 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("inherit") => {
558 text_style.color
560 }
561 CssValue::Keyword(kw) if kw.eq_ignore_ascii_case("currentColor") => {
562 text_style.color
563 }
564 _ => resolve_css_color(value)?,
565 }
566 }
567
568 ("font-size", CssValue::Length(_, _)) => {
569 let len = resolve_css_len(value, text_style)?;
571 let px = match &len {
572 Length::Px(v) => *v,
573 Length::Percent(v) => *v * text_style.font_size / 100.0,
574 _ => {
575 log::error!(target: "Layouter", "Unknown size type for font-size: {:?}", len);
576 return None;
577 }
578 };
579 text_style.font_size = px;
580 }
581
582 ("font-weight", CssValue::Keyword(v)) => {
583 text_style.font_weight = match v.as_str() {
584 "normal" => FontWeight::NORMAL,
585 "bold" => FontWeight::BOLD,
586 _ => text_style.font_weight,
587 };
588 }
589 ("font-weight", CssValue::Number(v)) => {
590 text_style.font_weight = FontWeight(*v as u16);
591 }
592
593 ("font-style", CssValue::Keyword(v)) => {
594 text_style.font_style = match v.as_str() {
595 "normal" => FontStyle::Normal,
596 "italic" => FontStyle::Italic,
597 "oblique" => FontStyle::Oblique,
598 _ => text_style.font_style,
599 };
600 }
601
602 ("text-decoration", CssValue::Keyword(v)) => {
603 text_style.text_decoration = match v.as_str() {
604 "none" => TextDecoration::None,
605 "underline" => TextDecoration::Underline,
606 "line-through" => TextDecoration::LineThrough,
607 "overline" => TextDecoration::Overline,
608 _ => TextDecoration::None,
609 };
610 }
611
612 ("text-align", CssValue::Keyword(v)) if v == "left" => {
613 text_style.text_align = TextAlign::Left;
614 }
615 ("text-align", CssValue::Keyword(v)) if v == "center" => {
616 text_style.text_align = TextAlign::Center;
617 }
618 ("text-align", CssValue::Keyword(v)) if v == "right" => {
619 text_style.text_align = TextAlign::Right;
620 }
621
622 ("box-sizing", CssValue::Keyword(v)) => {
626 style.box_sizing = match v.as_str() {
627 "content-box" => BoxSizing::ContentBox,
628 "border-box" => BoxSizing::BorderBox,
629 _ => BoxSizing::ContentBox,
630 };
631 }
632
633 ("border-style", CssValue::Keyword(v)) => {
634 let s = match v.as_str() {
635 "none" => BorderStyle::None,
636 "solid" => BorderStyle::Solid,
637 "dashed" => BorderStyle::Dashed,
638 "dotted" => BorderStyle::Dotted,
639 _ => BorderStyle::None,
640 };
641
642 container_style.border_style.top = s;
643 container_style.border_style.right = s;
644 container_style.border_style.bottom = s;
645 container_style.border_style.left = s;
646 }
647
648 ("margin", v) => {
649 expand_box(v, text_style, &resolve_css_len, |t, r, b, l| {
650 style.spacing.margin_top = t;
651 style.spacing.margin_right = r;
652 style.spacing.margin_bottom = b;
653 style.spacing.margin_left = l;
654 })?;
655 }
656 ("margin-top", _) => {
657 style.spacing.margin_top = resolve_css_len(value, text_style)?;
658 }
659 ("margin-right", _) => {
660 style.spacing.margin_right = resolve_css_len(value, text_style)?;
661 }
662 ("margin-bottom", _) => {
663 style.spacing.margin_bottom = resolve_css_len(value, text_style)?;
664 }
665 ("margin-left", _) => {
666 style.spacing.margin_left = resolve_css_len(value, text_style)?;
667 }
668
669 ("border", v) => {
670 let (maybe_width, maybe_style, maybe_color) = parse_border_shorthand(v, text_style)?;
671
672 if let Some(w) = maybe_width {
673 style.spacing.border_top = w.clone();
674 style.spacing.border_right = w.clone();
675 style.spacing.border_bottom = w.clone();
676 style.spacing.border_left = w;
677 }
678
679 if let Some(s) = maybe_style {
680 container_style.border_style.top = s;
681 container_style.border_style.right = s;
682 container_style.border_style.bottom = s;
683 container_style.border_style.left = s;
684 }
685
686 if let Some(c) = maybe_color {
687 container_style.border_color.top = c;
688 container_style.border_color.right = c;
689 container_style.border_color.bottom = c;
690 container_style.border_color.left = c;
691 }
692 }
693 ("border-top", _) => {
694 if let CssValue::List(_) = value {
695 let (maybe_width, maybe_style, maybe_color) =
696 parse_border_shorthand(value, text_style)?;
697 if let Some(w) = maybe_width {
698 style.spacing.border_top = w;
699 }
700 if let Some(s) = maybe_style {
701 container_style.border_style.top = s;
702 }
703 if let Some(c) = maybe_color {
704 container_style.border_color.top = c;
705 }
706 } else {
707 style.spacing.border_top = resolve_css_len(value, text_style)?;
708 }
709 }
710 ("border-right", _) => {
711 if let CssValue::List(_) = value {
712 let (maybe_width, maybe_style, maybe_color) =
713 parse_border_shorthand(value, text_style)?;
714 if let Some(w) = maybe_width {
715 style.spacing.border_right = w;
716 }
717 if let Some(s) = maybe_style {
718 container_style.border_style.right = s;
719 }
720 if let Some(c) = maybe_color {
721 container_style.border_color.right = c;
722 }
723 } else {
724 style.spacing.border_right = resolve_css_len(value, text_style)?;
725 }
726 }
727 ("border-bottom", _) => {
728 if let CssValue::List(_) = value {
729 let (maybe_width, maybe_style, maybe_color) =
730 parse_border_shorthand(value, text_style)?;
731 if let Some(w) = maybe_width {
732 style.spacing.border_bottom = w;
733 }
734 if let Some(s) = maybe_style {
735 container_style.border_style.bottom = s;
736 }
737 if let Some(c) = maybe_color {
738 container_style.border_color.bottom = c;
739 }
740 } else {
741 style.spacing.border_bottom = resolve_css_len(value, text_style)?;
742 }
743 }
744 ("border-left", _) => {
745 if let CssValue::List(_) = value {
746 let (maybe_width, maybe_style, maybe_color) =
747 parse_border_shorthand(value, text_style)?;
748 if let Some(w) = maybe_width {
749 style.spacing.border_left = w;
750 }
751 if let Some(s) = maybe_style {
752 container_style.border_style.left = s;
753 }
754 if let Some(c) = maybe_color {
755 container_style.border_color.left = c;
756 }
757 } else {
758 style.spacing.border_left = resolve_css_len(value, text_style)?;
759 }
760 }
761
762 ("padding", v) => {
763 expand_box(v, text_style, &resolve_css_len, |t, r, b, l| {
764 style.spacing.padding_top = t;
765 style.spacing.padding_right = r;
766 style.spacing.padding_bottom = b;
767 style.spacing.padding_left = l;
768 })?;
769 }
770 ("padding-top", _) => {
771 style.spacing.padding_top = resolve_css_len(value, text_style)?;
772 }
773 ("padding-right", _) => {
774 style.spacing.padding_right = resolve_css_len(value, text_style)?;
775 }
776 ("padding-bottom", _) => {
777 style.spacing.padding_bottom = resolve_css_len(value, text_style)?;
778 }
779 ("padding-left", _) => {
780 style.spacing.padding_left = resolve_css_len(value, text_style)?;
781 }
782
783 ("width", _) => {
787 style.size.width = resolve_css_len(value, text_style)?;
788 }
789 ("height", _) => {
790 style.size.height = resolve_css_len(value, text_style)?;
791 }
792 ("min-width", _) => {
793 style.size.min_width = resolve_css_len(value, text_style)?;
794 }
795 ("min-height", _) => {
796 style.size.min_height = resolve_css_len(value, text_style)?;
797 }
798 ("max-width", _) => {
799 style.size.max_width = resolve_css_len(value, text_style)?;
800 }
801 ("max-height", _) => {
802 style.size.max_height = resolve_css_len(value, text_style)?;
803 }
804
805 ("flex-direction", CssValue::Keyword(v)) => {
809 if let Display::Flex { flex_direction } = &mut style.display {
810 *flex_direction = match v.as_str() {
811 "row" => FlexDirection::Row,
812 "column" => FlexDirection::Column,
813 _ => return None,
814 };
815 }
816 }
817
818 ("justify-content", CssValue::Keyword(v)) => {
819 style.justify_content = match v.as_str() {
820 "flex-start" => JustifyContent::Start,
821 "center" => JustifyContent::Center,
822 "flex-end" => JustifyContent::End,
823 "space-between" => JustifyContent::SpaceBetween,
824 "space-around" => JustifyContent::SpaceAround,
825 _ => return None,
826 };
827 }
828
829 ("align-items", CssValue::Keyword(v)) => {
830 style.align_items = match v.as_str() {
831 "stretch" => AlignItems::Stretch,
832 "flex-start" => AlignItems::Start,
833 "center" => AlignItems::Center,
834 "flex-end" => AlignItems::End,
835 _ => return None,
836 };
837 }
838
839 ("gap", _) => {
840 let gap = resolve_css_len(value, text_style)?;
841 style.row_gap = gap.clone();
842 style.column_gap = gap;
843 }
844
845 ("align-self", CssValue::Keyword(v)) => {
846 style.item_style.align_self = match v.as_str() {
847 "stretch" => Some(AlignItems::Stretch),
848 "flex-start" => Some(AlignItems::Start),
849 "center" => Some(AlignItems::Center),
850 "flex-end" => Some(AlignItems::End),
851 _ => return None,
852 };
853 }
854
855 ("flex-grow", CssValue::Number(v)) => {
856 style.item_style.flex_grow = *v;
857 }
858
859 ("flex-basis", _) => {
860 style.item_style.flex_basis = resolve_css_len(value, text_style)?;
861 }
862
863 _ => {}
864 }
865 Some(())
866}
867
868fn resolve_css_len(css_len: &CssValue, text_style: &TextStyle) -> Option<Length> {
870 match &css_len {
871 CssValue::Length(v, Unit::Em) => Some(Length::Px(text_style.font_size * v)),
872 CssValue::Length(v, Unit::Rem) => Some(Length::Px(16.0 * v)), CssValue::Length(v, u) => match u {
874 Unit::Percent => Some(Length::Percent(*v)),
875 Unit::Px => Some(Length::Px(*v)),
876 Unit::Vw => Some(Length::Vw(*v)),
877 Unit::Vh => Some(Length::Vh(*v)),
878 Unit::Em | Unit::Rem => unreachable!(),
879 },
880 CssValue::Number(0.0) => Some(Length::Px(0.0)),
881 CssValue::Keyword(s) => match s.as_str() {
882 "auto" => Some(Length::Auto),
883 _ => None,
884 },
885 CssValue::Function(name, args) if name == "calc" && !args.is_empty() => {
886 let mut iter = args.iter();
887 let mut result = resolve_css_len(iter.next().unwrap(), text_style)?;
888
889 while let (Some(op), Some(val)) = (iter.next(), iter.next()) {
890 match op {
891 CssValue::Keyword(o) if o == "+" => {
892 let val_resolved = resolve_css_len(val, text_style)?;
893 result = Length::Add(Box::new(result), Box::new(val_resolved));
894 }
895 CssValue::Keyword(o) if o == "-" => {
896 let val_resolved = resolve_css_len(val, text_style)?;
897 result = Length::Sub(Box::new(result), Box::new(val_resolved));
898 }
899 CssValue::Keyword(o) if o == "*" => {
900 if let CssValue::Number(factor) = val {
901 result = Length::Mul(Box::new(result), *factor);
902 } else {
903 log::error!(target: "Layouter", "Invalid operand for multiplication in calc(): {:?}", val);
904 return None;
905 }
906 }
907 CssValue::Keyword(o) if o == "/" => {
908 if let CssValue::Number(factor) = val {
909 if *factor == 0.0 {
910 log::error!(target: "Layouter", "Division by zero in calc()");
911 return None;
912 }
913 result = Length::Div(Box::new(result), *factor);
914 } else {
915 log::error!(target: "Layouter", "Invalid operand for division in calc(): {:?}", val);
916 return None;
917 }
918 }
919 _ => {
920 log::error!(target: "Layouter", "Unknown operator for calc function: {:?}", op);
921 return None;
922 }
923 }
924 }
925
926 Some(result)
927 }
928 _ => {
929 log::error!(target: "Layouter", "Unknown CSS Length type: {:?}", css_len);
930 None
931 }
932 }
933}
934
935fn resolve_css_color(css_color: &CssValue) -> Option<Color> {
943 fn keyword_color_to_color(keyword: &str) -> Option<Color> {
944 match keyword.to_ascii_lowercase().as_str() {
948 "black" => Some(Color(0, 0, 0, 255)),
950 "white" => Some(Color(255, 255, 255, 255)),
951 "red" => Some(Color(255, 0, 0, 255)),
952 "green" => Some(Color(0, 128, 0, 255)),
953 "blue" => Some(Color(0, 0, 255, 255)),
954 "yellow" => Some(Color(255, 255, 0, 255)),
955
956 "gray" | "grey" => Some(Color(128, 128, 128, 255)),
958 "lightgray" | "lightgrey" => Some(Color(211, 211, 211, 255)),
959 "darkgray" | "darkgrey" => Some(Color(169, 169, 169, 255)),
960
961 "royalblue" => Some(Color(65, 105, 225, 255)),
963 "cornflowerblue" => Some(Color(100, 149, 237, 255)),
964 "skyblue" => Some(Color(135, 206, 235, 255)),
965 "lightblue" => Some(Color(173, 216, 230, 255)),
966
967 "orange" => Some(Color(255, 165, 0, 255)),
968 "pink" => Some(Color(255, 192, 203, 255)),
969 "purple" => Some(Color(128, 0, 128, 255)),
970 "brown" => Some(Color(165, 42, 42, 255)),
971
972 "transparent" => Some(Color(0, 0, 0, 0)),
974 "initial" => Some(Color(0, 0, 0, 255)),
975
976 "buttonface" => Some(Color(240, 240, 240, 255)),
979 "buttontext" => Some(Color(0, 0, 0, 255)),
980 "linktext" => Some(Color(0, 0, 255, 255)),
981
982 "none" => Some(Color(0, 0, 0, 0)),
984
985 _ => {
986 log::error!(target: "Layouter", "Unknown CSS color keyword: {}", keyword);
987 None
988 }
989 }
990 }
991
992 fn hsla_to_rgba(h: f32, s: f32, l: f32, a: f32) -> (u8, u8, u8, u8) {
994 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
996 let h_prime = h / 60.0;
997 let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs());
998
999 let (r1, g1, b1) = match h_prime as u32 {
1001 0 => (c, x, 0.0),
1002 1 => (x, c, 0.0),
1003 2 => (0.0, c, x),
1004 3 => (0.0, x, c),
1005 4 => (x, 0.0, c),
1006 5 | 6 => (c, 0.0, x),
1007 _ => (0.0, 0.0, 0.0),
1008 };
1009
1010 let m = l - c / 2.0;
1012 let r = ((r1 + m) * 255.0).round().clamp(0.0, 255.0) as u8;
1013 let g = ((g1 + m) * 255.0).round().clamp(0.0, 255.0) as u8;
1014 let b = ((b1 + m) * 255.0).round().clamp(0.0, 255.0) as u8;
1015 let a = (a * 255.0).round().clamp(0.0, 255.0) as u8;
1016
1017 (r, g, b, a)
1018 }
1019
1020 match css_color {
1021 CssValue::Color(_) => {
1023 let (r, g, b, a) = css_color.to_rgba_tuple()?;
1024 Some(Color(r, g, b, a))
1025 }
1026
1027 CssValue::Keyword(value) => keyword_color_to_color(value),
1029
1030 CssValue::Function(func, args) if func == "rgb" || func == "rgba" => {
1032 let mut numbers = Vec::new();
1034 let mut alpha: Option<f32> = None;
1035 let mut after_slash = false;
1036
1037 for arg in args {
1038 match arg {
1039 CssValue::Keyword(k) if k == "/" => {
1040 after_slash = true;
1041 }
1042 CssValue::Number(n) => {
1043 if after_slash {
1044 alpha = Some(*n);
1045 } else {
1046 numbers.push(*n);
1047 }
1048 }
1049 _ => return None,
1050 }
1051 }
1052
1053 if numbers.len() != 3 {
1054 return None;
1055 }
1056
1057 let a = alpha.unwrap_or(1.0);
1058
1059 Some(Color(
1060 (numbers[0] * 255.0).round() as u8,
1061 (numbers[1] * 255.0).round() as u8,
1062 (numbers[2] * 255.0).round() as u8,
1063 (a * 255.0).round() as u8,
1064 ))
1065 }
1066
1067 CssValue::Function(func, args) if func == "hsl" || func == "hsla" => {
1069 let mut numbers = Vec::new();
1071 let mut alpha: Option<f32> = None;
1072 let mut after_slash = false;
1073
1074 for arg in args {
1075 match arg {
1076 CssValue::Keyword(k) if k == "/" => {
1077 after_slash = true;
1078 }
1079 CssValue::Number(n) => {
1080 if after_slash {
1081 alpha = Some(*n);
1082 } else {
1083 numbers.push(*n);
1084 }
1085 }
1086 _ => return None,
1087 }
1088 }
1089
1090 if numbers.len() != 3 {
1091 return None;
1092 }
1093
1094 let a = alpha.unwrap_or(1.0);
1095 let (r, g, b, a) = hsla_to_rgba(numbers[0], numbers[1], numbers[2], a);
1096
1097 Some(Color(r, g, b, a))
1098 }
1099
1100 _ => {
1102 log::error!(
1103 target: "Layouter",
1104 "Unexpected CSS color value at layout stage: {:?}",
1105 css_color
1106 );
1107 None
1108 }
1109 }
1110}