1use crate::model::{Edges, MarginEdges, Position};
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Default, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct Style {
17 pub width: Option<Dimension>,
20 pub height: Option<Dimension>,
22 pub min_width: Option<Dimension>,
24 pub min_height: Option<Dimension>,
26 pub max_width: Option<Dimension>,
28 pub max_height: Option<Dimension>,
30
31 #[serde(default)]
33 pub padding: Option<Edges>,
34 #[serde(default)]
36 pub margin: Option<MarginEdges>,
37
38 pub display: Option<Display>,
41
42 #[serde(default)]
45 pub flex_direction: Option<FlexDirection>,
46 #[serde(default)]
48 pub justify_content: Option<JustifyContent>,
49 #[serde(default)]
51 pub align_items: Option<AlignItems>,
52 #[serde(default)]
54 pub align_self: Option<AlignItems>,
55 #[serde(default)]
57 pub flex_wrap: Option<FlexWrap>,
58 pub align_content: Option<AlignContent>,
60 pub flex_grow: Option<f64>,
62 pub flex_shrink: Option<f64>,
64 pub flex_basis: Option<Dimension>,
66 pub gap: Option<f64>,
68 pub row_gap: Option<f64>,
70 pub column_gap: Option<f64>,
72
73 pub grid_template_columns: Option<Vec<GridTrackSize>>,
76 pub grid_template_rows: Option<Vec<GridTrackSize>>,
78 pub grid_auto_rows: Option<GridTrackSize>,
80 pub grid_auto_columns: Option<GridTrackSize>,
82 pub grid_placement: Option<GridPlacement>,
84
85 pub font_family: Option<String>,
88 pub font_size: Option<f64>,
90 pub font_weight: Option<u32>,
92 pub font_style: Option<FontStyle>,
94 pub line_height: Option<f64>,
96 pub text_align: Option<TextAlign>,
98 pub letter_spacing: Option<f64>,
100 pub word_spacing: Option<f64>,
105 pub text_decoration: Option<TextDecoration>,
107 pub text_transform: Option<TextTransform>,
109 pub hyphens: Option<Hyphens>,
111 pub lang: Option<String>,
113 pub direction: Option<Direction>,
115 pub text_overflow: Option<TextOverflow>,
117 pub line_breaking: Option<LineBreaking>,
119
120 pub overflow: Option<Overflow>,
122
123 pub vertical_align: Option<VerticalAlign>,
125
126 pub color: Option<Color>,
129 pub background_color: Option<Color>,
131 pub background: Option<Background>,
136 pub opacity: Option<f64>,
138 pub box_shadow: Option<BoxShadow>,
142
143 pub border_width: Option<EdgeValues<f64>>,
146 pub border_color: Option<EdgeValues<Color>>,
148 pub border_style: Option<EdgeValues<BorderStyle>>,
150 pub border_radius: Option<CornerValues>,
152
153 pub position: Option<Position>,
156 pub top: Option<f64>,
158 pub right: Option<f64>,
160 pub bottom: Option<f64>,
162 pub left: Option<f64>,
164
165 pub wrap: Option<bool>,
170
171 pub break_before: Option<bool>,
173
174 pub min_widow_lines: Option<u32>,
177
178 pub min_orphan_lines: Option<u32>,
181
182 pub transform: Option<Vec<TransformOp>>,
187
188 pub transform_origin: Option<(f64, f64)>,
191}
192
193#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
197#[serde(tag = "type", rename_all = "camelCase")]
198pub enum TransformOp {
199 Rotate { deg: f64 },
201 Scale { x: f64, y: f64 },
203 Translate { x: f64, y: f64 },
205}
206
207#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
209pub enum Dimension {
210 Pt(f64),
212 Percent(f64),
214 Auto,
216}
217
218impl Dimension {
219 pub fn resolve(&self, parent_size: f64) -> Option<f64> {
222 match self {
223 Dimension::Pt(v) => Some(*v),
224 Dimension::Percent(p) => Some(parent_size * p / 100.0),
225 Dimension::Auto => None,
226 }
227 }
228}
229
230#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
232pub enum Display {
233 #[default]
235 Flex,
236 Grid,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242pub enum GridTrackSize {
243 Pt(f64),
245 Fr(f64),
247 Auto,
249 MinMax(Box<GridTrackSize>, Box<GridTrackSize>),
251}
252
253#[derive(Debug, Clone, Default, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct GridPlacement {
257 pub column_start: Option<i32>,
259 pub column_end: Option<i32>,
261 pub row_start: Option<i32>,
263 pub row_end: Option<i32>,
265 pub column_span: Option<u32>,
267 pub row_span: Option<u32>,
269}
270
271#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
272pub enum FlexDirection {
273 #[default]
274 Column,
275 Row,
276 ColumnReverse,
277 RowReverse,
278}
279
280#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
281pub enum JustifyContent {
282 #[default]
283 FlexStart,
284 FlexEnd,
285 Center,
286 SpaceBetween,
287 SpaceAround,
288 SpaceEvenly,
289}
290
291#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
292pub enum AlignItems {
293 FlexStart,
294 FlexEnd,
295 Center,
296 #[default]
297 Stretch,
298 Baseline,
299}
300
301#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
302pub enum FlexWrap {
303 #[default]
304 NoWrap,
305 Wrap,
306 WrapReverse,
307}
308
309#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
310pub enum AlignContent {
311 #[default]
312 FlexStart,
313 FlexEnd,
314 Center,
315 SpaceBetween,
316 SpaceAround,
317 SpaceEvenly,
318 Stretch,
319}
320
321#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
322pub enum FontStyle {
323 #[default]
324 Normal,
325 Italic,
326 Oblique,
327}
328
329#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
330pub enum TextAlign {
331 #[default]
332 Left,
333 Right,
334 Center,
335 Justify,
336}
337
338#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
339pub enum TextDecoration {
340 #[default]
341 None,
342 Underline,
343 LineThrough,
344}
345
346#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
347pub enum TextTransform {
348 #[default]
349 None,
350 Uppercase,
351 Lowercase,
352 Capitalize,
353}
354
355#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
357#[serde(rename_all = "lowercase")]
358pub enum VerticalAlign {
359 #[default]
362 Top,
363 Middle,
364 Bottom,
365 Baseline,
370}
371
372#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
374pub enum Overflow {
375 #[default]
377 Visible,
378 Hidden,
380}
381
382#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
384pub enum TextOverflow {
385 #[default]
387 Wrap,
388 Ellipsis,
390 Clip,
392}
393
394#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
396#[serde(rename_all = "lowercase")]
397pub enum Direction {
398 #[default]
400 Ltr,
401 Rtl,
403 Auto,
405}
406
407#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
409#[serde(rename_all = "lowercase")]
410pub enum LineBreaking {
411 #[default]
413 Optimal,
414 Greedy,
416}
417
418#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
420#[serde(rename_all = "lowercase")]
421pub enum Hyphens {
422 None,
424 #[default]
426 Manual,
427 Auto,
429}
430
431#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
433pub struct Color {
434 pub r: f64, pub g: f64,
436 pub b: f64,
437 pub a: f64,
438}
439
440#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
444#[serde(rename_all = "camelCase")]
445pub struct BoxShadow {
446 pub offset_x: f64,
447 pub offset_y: f64,
448 #[serde(default)]
450 pub blur: f64,
451 pub color: Color,
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize)]
462#[serde(tag = "type", rename_all = "camelCase")]
463pub enum Background {
464 Color(Color),
465 Linear(LinearGradient),
466 Radial(RadialGradient),
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
470#[serde(rename_all = "camelCase")]
471pub struct LinearGradient {
472 pub angle_deg: f64,
475 pub stops: Vec<GradientStop>,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
479#[serde(rename_all = "camelCase")]
480pub struct RadialGradient {
481 pub stops: Vec<GradientStop>,
482}
483
484#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
485#[serde(rename_all = "camelCase")]
486pub struct GradientStop {
487 pub position: f64,
489 pub color: Color,
490}
491
492impl Color {
493 pub const BLACK: Color = Color {
494 r: 0.0,
495 g: 0.0,
496 b: 0.0,
497 a: 1.0,
498 };
499 pub const WHITE: Color = Color {
500 r: 1.0,
501 g: 1.0,
502 b: 1.0,
503 a: 1.0,
504 };
505 pub const TRANSPARENT: Color = Color {
506 r: 0.0,
507 g: 0.0,
508 b: 0.0,
509 a: 0.0,
510 };
511
512 pub fn rgb(r: f64, g: f64, b: f64) -> Self {
513 Self { r, g, b, a: 1.0 }
514 }
515
516 pub fn hex(hex: &str) -> Self {
517 let hex = hex.trim_start_matches('#');
518 let (r, g, b) = match hex.len() {
519 3 => {
520 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).unwrap_or(0);
521 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).unwrap_or(0);
522 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).unwrap_or(0);
523 (r, g, b)
524 }
525 6 => {
526 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
527 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
528 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
529 (r, g, b)
530 }
531 _ => (0, 0, 0),
532 };
533 Self {
534 r: r as f64 / 255.0,
535 g: g as f64 / 255.0,
536 b: b as f64 / 255.0,
537 a: 1.0,
538 }
539 }
540}
541
542impl Default for Color {
543 fn default() -> Self {
544 Color::BLACK
545 }
546}
547
548#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
553#[serde(rename_all = "lowercase")]
554pub enum BorderStyle {
555 #[default]
556 Solid,
557 Dashed,
558 Dotted,
559}
560
561#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
563pub struct EdgeValues<T: Copy> {
564 pub top: T,
565 pub right: T,
566 pub bottom: T,
567 pub left: T,
568}
569
570impl<T: Copy> EdgeValues<T> {
571 pub fn uniform(v: T) -> Self {
572 Self {
573 top: v,
574 right: v,
575 bottom: v,
576 left: v,
577 }
578 }
579}
580
581#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
583pub struct CornerValues {
584 pub top_left: f64,
585 pub top_right: f64,
586 pub bottom_right: f64,
587 pub bottom_left: f64,
588}
589
590impl CornerValues {
591 pub fn uniform(v: f64) -> Self {
592 Self {
593 top_left: v,
594 top_right: v,
595 bottom_right: v,
596 bottom_left: v,
597 }
598 }
599}
600
601#[derive(Debug, Clone)]
604pub struct ResolvedStyle {
605 pub width: SizeConstraint,
607 pub height: SizeConstraint,
608 pub min_width: f64,
609 pub min_height: f64,
610 pub max_width: f64,
611 pub max_height: f64,
612 pub padding: Edges,
613 pub margin: MarginEdges,
614
615 pub display: Display,
617
618 pub flex_direction: FlexDirection,
620 pub justify_content: JustifyContent,
621 pub align_items: AlignItems,
622 pub align_self: Option<AlignItems>,
623 pub flex_wrap: FlexWrap,
624 pub align_content: AlignContent,
625 pub flex_grow: f64,
626 pub flex_shrink: f64,
627 pub flex_basis: SizeConstraint,
628 pub gap: f64,
629 pub row_gap: f64,
630 pub column_gap: f64,
631
632 pub grid_template_columns: Option<Vec<GridTrackSize>>,
634 pub grid_template_rows: Option<Vec<GridTrackSize>>,
635 pub grid_auto_rows: Option<GridTrackSize>,
636 pub grid_auto_columns: Option<GridTrackSize>,
637 pub grid_placement: Option<GridPlacement>,
638
639 pub font_family: String,
641 pub font_size: f64,
642 pub font_weight: u32,
643 pub font_style: FontStyle,
644 pub line_height: f64,
645 pub text_align: TextAlign,
646 pub letter_spacing: f64,
647 pub word_spacing: f64,
648 pub text_decoration: TextDecoration,
649 pub text_transform: TextTransform,
650 pub hyphens: Hyphens,
651 pub lang: Option<String>,
652 pub direction: Direction,
653 pub text_overflow: TextOverflow,
654 pub line_breaking: LineBreaking,
655 pub vertical_align: VerticalAlign,
656
657 pub color: Color,
659 pub background_color: Option<Color>,
660 pub opacity: f64,
661 pub overflow: Overflow,
662 pub border_width: Edges,
663 pub border_color: EdgeValues<Color>,
664 pub border_style: EdgeValues<BorderStyle>,
665 pub border_radius: CornerValues,
666 pub box_shadow: Option<BoxShadow>,
667 pub background: Option<Background>,
668
669 pub position: Position,
671 pub positioned: bool,
676 pub top: Option<f64>,
677 pub right: Option<f64>,
678 pub bottom: Option<f64>,
679 pub left: Option<f64>,
680
681 pub breakable: bool,
683 pub break_before: bool,
684 pub min_widow_lines: u32,
685 pub min_orphan_lines: u32,
686
687 pub transform: Vec<TransformOp>,
689 pub transform_origin: (f64, f64),
692}
693
694#[derive(Debug, Clone, Copy)]
695pub enum SizeConstraint {
696 Fixed(f64),
697 Auto,
698}
699
700impl Style {
701 pub fn resolve(&self, parent: Option<&ResolvedStyle>, available_width: f64) -> ResolvedStyle {
703 let parent_font_size = parent.map(|p| p.font_size).unwrap_or(12.0);
704 let parent_color = parent.map(|p| p.color).unwrap_or(Color::BLACK);
705 let parent_font_family = parent
706 .map(|p| p.font_family.clone())
707 .unwrap_or_else(|| "Helvetica".to_string());
708
709 let font_size = self.font_size.unwrap_or(parent_font_size);
710
711 ResolvedStyle {
712 width: self
713 .width
714 .map(|d| match d {
715 Dimension::Pt(v) => SizeConstraint::Fixed(v),
716 Dimension::Percent(p) => SizeConstraint::Fixed(available_width * p / 100.0),
717 Dimension::Auto => SizeConstraint::Auto,
718 })
719 .unwrap_or(SizeConstraint::Auto),
720
721 height: self
722 .height
723 .map(|d| match d {
724 Dimension::Pt(v) => SizeConstraint::Fixed(v),
725 Dimension::Percent(p) => SizeConstraint::Fixed(p), Dimension::Auto => SizeConstraint::Auto,
727 })
728 .unwrap_or(SizeConstraint::Auto),
729
730 min_width: self
731 .min_width
732 .and_then(|d| d.resolve(available_width))
733 .unwrap_or(0.0),
734 min_height: self.min_height.and_then(|d| d.resolve(0.0)).unwrap_or(0.0),
735 max_width: self
736 .max_width
737 .and_then(|d| d.resolve(available_width))
738 .unwrap_or(f64::INFINITY),
739 max_height: self
740 .max_height
741 .and_then(|d| d.resolve(0.0))
742 .unwrap_or(f64::INFINITY),
743
744 padding: self.padding.unwrap_or_default(),
745 margin: self.margin.unwrap_or_default(),
746
747 display: self.display.unwrap_or_default(),
748
749 flex_direction: self.flex_direction.unwrap_or_default(),
750 justify_content: self.justify_content.unwrap_or_default(),
751 align_items: self.align_items.unwrap_or_default(),
752 align_self: self.align_self,
753 flex_wrap: self.flex_wrap.unwrap_or_default(),
754 align_content: self.align_content.unwrap_or_default(),
755 flex_grow: self.flex_grow.unwrap_or(0.0),
756 flex_shrink: self.flex_shrink.unwrap_or(1.0),
757 flex_basis: self
758 .flex_basis
759 .map(|d| match d {
760 Dimension::Pt(v) => SizeConstraint::Fixed(v),
761 Dimension::Percent(p) => SizeConstraint::Fixed(available_width * p / 100.0),
762 Dimension::Auto => SizeConstraint::Auto,
763 })
764 .unwrap_or(SizeConstraint::Auto),
765 gap: self.gap.unwrap_or(0.0),
766 row_gap: self.row_gap.or(self.gap).unwrap_or(0.0),
767 column_gap: self.column_gap.or(self.gap).unwrap_or(0.0),
768
769 grid_template_columns: self.grid_template_columns.clone(),
770 grid_template_rows: self.grid_template_rows.clone(),
771 grid_auto_rows: self.grid_auto_rows.clone(),
772 grid_auto_columns: self.grid_auto_columns.clone(),
773 grid_placement: self.grid_placement.clone(),
774
775 font_family: self.font_family.clone().unwrap_or(parent_font_family),
776 font_size,
777 font_weight: self
778 .font_weight
779 .unwrap_or(parent.map(|p| p.font_weight).unwrap_or(400)),
780 font_style: self
781 .font_style
782 .unwrap_or(parent.map(|p| p.font_style).unwrap_or_default()),
783 line_height: self
784 .line_height
785 .unwrap_or(parent.map(|p| p.line_height).unwrap_or(1.4)),
786 text_align: {
787 let direction = self
788 .direction
789 .unwrap_or(parent.map(|p| p.direction).unwrap_or_default());
790 self.text_align.unwrap_or_else(|| {
791 if matches!(direction, Direction::Rtl) {
792 TextAlign::Right
793 } else {
794 parent.map(|p| p.text_align).unwrap_or_default()
795 }
796 })
797 },
798 letter_spacing: self
804 .letter_spacing
805 .unwrap_or(parent.map(|p| p.letter_spacing).unwrap_or(0.0)),
806 word_spacing: self
807 .word_spacing
808 .unwrap_or(parent.map(|p| p.word_spacing).unwrap_or(0.0)),
809 text_decoration: self
810 .text_decoration
811 .unwrap_or(parent.map(|p| p.text_decoration).unwrap_or_default()),
812 text_transform: self
813 .text_transform
814 .unwrap_or(parent.map(|p| p.text_transform).unwrap_or_default()),
815 hyphens: self
816 .hyphens
817 .unwrap_or(parent.map(|p| p.hyphens).unwrap_or_default()),
818 lang: self
819 .lang
820 .clone()
821 .or_else(|| parent.and_then(|p| p.lang.clone())),
822 direction: self
823 .direction
824 .unwrap_or(parent.map(|p| p.direction).unwrap_or_default()),
825 text_overflow: self.text_overflow.unwrap_or_default(),
826 vertical_align: self.vertical_align.unwrap_or_default(),
827 line_breaking: self
828 .line_breaking
829 .unwrap_or(parent.map(|p| p.line_breaking).unwrap_or_default()),
830
831 color: self.color.unwrap_or(parent_color),
832 background_color: self.background_color,
833 opacity: self.opacity.unwrap_or(1.0),
834 overflow: self.overflow.unwrap_or_default(),
835 box_shadow: self.box_shadow,
836 background: self.background.clone(),
837
838 border_width: self
839 .border_width
840 .map(|e| Edges {
841 top: e.top,
842 right: e.right,
843 bottom: e.bottom,
844 left: e.left,
845 })
846 .unwrap_or_default(),
847
848 border_color: self
849 .border_color
850 .unwrap_or(EdgeValues::uniform(Color::BLACK)),
851 border_style: self
852 .border_style
853 .unwrap_or(EdgeValues::uniform(BorderStyle::Solid)),
854 border_radius: self.border_radius.unwrap_or(CornerValues::uniform(0.0)),
855
856 position: self.position.unwrap_or_default(),
857 positioned: self.position.is_some(),
858 top: self.top,
859 right: self.right,
860 bottom: self.bottom,
861 left: self.left,
862
863 breakable: self.wrap.unwrap_or(true),
864 break_before: self.break_before.unwrap_or(false),
865 min_widow_lines: self.min_widow_lines.unwrap_or(2),
866 min_orphan_lines: self.min_orphan_lines.unwrap_or(2),
867
868 transform: self.transform.clone().unwrap_or_default(),
870 transform_origin: self.transform_origin.unwrap_or((0.5, 0.5)),
871 }
872 }
873}