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 color: Option<Color>,
126 pub background_color: Option<Color>,
128 pub background: Option<Background>,
133 pub opacity: Option<f64>,
135 pub box_shadow: Option<BoxShadow>,
139
140 pub border_width: Option<EdgeValues<f64>>,
143 pub border_color: Option<EdgeValues<Color>>,
145 pub border_radius: Option<CornerValues>,
147
148 pub position: Option<Position>,
151 pub top: Option<f64>,
153 pub right: Option<f64>,
155 pub bottom: Option<f64>,
157 pub left: Option<f64>,
159
160 pub wrap: Option<bool>,
165
166 pub break_before: Option<bool>,
168
169 pub min_widow_lines: Option<u32>,
172
173 pub min_orphan_lines: Option<u32>,
176
177 pub transform: Option<Vec<TransformOp>>,
182
183 pub transform_origin: Option<(f64, f64)>,
186}
187
188#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
192#[serde(tag = "type", rename_all = "camelCase")]
193pub enum TransformOp {
194 Rotate { deg: f64 },
196 Scale { x: f64, y: f64 },
198 Translate { x: f64, y: f64 },
200}
201
202#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
204pub enum Dimension {
205 Pt(f64),
207 Percent(f64),
209 Auto,
211}
212
213impl Dimension {
214 pub fn resolve(&self, parent_size: f64) -> Option<f64> {
217 match self {
218 Dimension::Pt(v) => Some(*v),
219 Dimension::Percent(p) => Some(parent_size * p / 100.0),
220 Dimension::Auto => None,
221 }
222 }
223}
224
225#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
227pub enum Display {
228 #[default]
230 Flex,
231 Grid,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237pub enum GridTrackSize {
238 Pt(f64),
240 Fr(f64),
242 Auto,
244 MinMax(Box<GridTrackSize>, Box<GridTrackSize>),
246}
247
248#[derive(Debug, Clone, Default, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct GridPlacement {
252 pub column_start: Option<i32>,
254 pub column_end: Option<i32>,
256 pub row_start: Option<i32>,
258 pub row_end: Option<i32>,
260 pub column_span: Option<u32>,
262 pub row_span: Option<u32>,
264}
265
266#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
267pub enum FlexDirection {
268 #[default]
269 Column,
270 Row,
271 ColumnReverse,
272 RowReverse,
273}
274
275#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
276pub enum JustifyContent {
277 #[default]
278 FlexStart,
279 FlexEnd,
280 Center,
281 SpaceBetween,
282 SpaceAround,
283 SpaceEvenly,
284}
285
286#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
287pub enum AlignItems {
288 FlexStart,
289 FlexEnd,
290 Center,
291 #[default]
292 Stretch,
293 Baseline,
294}
295
296#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
297pub enum FlexWrap {
298 #[default]
299 NoWrap,
300 Wrap,
301 WrapReverse,
302}
303
304#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
305pub enum AlignContent {
306 #[default]
307 FlexStart,
308 FlexEnd,
309 Center,
310 SpaceBetween,
311 SpaceAround,
312 SpaceEvenly,
313 Stretch,
314}
315
316#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
317pub enum FontStyle {
318 #[default]
319 Normal,
320 Italic,
321 Oblique,
322}
323
324#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
325pub enum TextAlign {
326 #[default]
327 Left,
328 Right,
329 Center,
330 Justify,
331}
332
333#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
334pub enum TextDecoration {
335 #[default]
336 None,
337 Underline,
338 LineThrough,
339}
340
341#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
342pub enum TextTransform {
343 #[default]
344 None,
345 Uppercase,
346 Lowercase,
347 Capitalize,
348}
349
350#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
352pub enum Overflow {
353 #[default]
355 Visible,
356 Hidden,
358}
359
360#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
362pub enum TextOverflow {
363 #[default]
365 Wrap,
366 Ellipsis,
368 Clip,
370}
371
372#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
374#[serde(rename_all = "lowercase")]
375pub enum Direction {
376 #[default]
378 Ltr,
379 Rtl,
381 Auto,
383}
384
385#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(rename_all = "lowercase")]
388pub enum LineBreaking {
389 #[default]
391 Optimal,
392 Greedy,
394}
395
396#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
398#[serde(rename_all = "lowercase")]
399pub enum Hyphens {
400 None,
402 #[default]
404 Manual,
405 Auto,
407}
408
409#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
411pub struct Color {
412 pub r: f64, pub g: f64,
414 pub b: f64,
415 pub a: f64,
416}
417
418#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
422#[serde(rename_all = "camelCase")]
423pub struct BoxShadow {
424 pub offset_x: f64,
425 pub offset_y: f64,
426 #[serde(default)]
428 pub blur: f64,
429 pub color: Color,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
440#[serde(tag = "type", rename_all = "camelCase")]
441pub enum Background {
442 Color(Color),
443 Linear(LinearGradient),
444 Radial(RadialGradient),
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize)]
448#[serde(rename_all = "camelCase")]
449pub struct LinearGradient {
450 pub angle_deg: f64,
453 pub stops: Vec<GradientStop>,
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize)]
457#[serde(rename_all = "camelCase")]
458pub struct RadialGradient {
459 pub stops: Vec<GradientStop>,
460}
461
462#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
463#[serde(rename_all = "camelCase")]
464pub struct GradientStop {
465 pub position: f64,
467 pub color: Color,
468}
469
470impl Color {
471 pub const BLACK: Color = Color {
472 r: 0.0,
473 g: 0.0,
474 b: 0.0,
475 a: 1.0,
476 };
477 pub const WHITE: Color = Color {
478 r: 1.0,
479 g: 1.0,
480 b: 1.0,
481 a: 1.0,
482 };
483 pub const TRANSPARENT: Color = Color {
484 r: 0.0,
485 g: 0.0,
486 b: 0.0,
487 a: 0.0,
488 };
489
490 pub fn rgb(r: f64, g: f64, b: f64) -> Self {
491 Self { r, g, b, a: 1.0 }
492 }
493
494 pub fn hex(hex: &str) -> Self {
495 let hex = hex.trim_start_matches('#');
496 let (r, g, b) = match hex.len() {
497 3 => {
498 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).unwrap_or(0);
499 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).unwrap_or(0);
500 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).unwrap_or(0);
501 (r, g, b)
502 }
503 6 => {
504 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
505 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
506 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
507 (r, g, b)
508 }
509 _ => (0, 0, 0),
510 };
511 Self {
512 r: r as f64 / 255.0,
513 g: g as f64 / 255.0,
514 b: b as f64 / 255.0,
515 a: 1.0,
516 }
517 }
518}
519
520impl Default for Color {
521 fn default() -> Self {
522 Color::BLACK
523 }
524}
525
526#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
528pub struct EdgeValues<T: Copy> {
529 pub top: T,
530 pub right: T,
531 pub bottom: T,
532 pub left: T,
533}
534
535impl<T: Copy> EdgeValues<T> {
536 pub fn uniform(v: T) -> Self {
537 Self {
538 top: v,
539 right: v,
540 bottom: v,
541 left: v,
542 }
543 }
544}
545
546#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
548pub struct CornerValues {
549 pub top_left: f64,
550 pub top_right: f64,
551 pub bottom_right: f64,
552 pub bottom_left: f64,
553}
554
555impl CornerValues {
556 pub fn uniform(v: f64) -> Self {
557 Self {
558 top_left: v,
559 top_right: v,
560 bottom_right: v,
561 bottom_left: v,
562 }
563 }
564}
565
566#[derive(Debug, Clone)]
569pub struct ResolvedStyle {
570 pub width: SizeConstraint,
572 pub height: SizeConstraint,
573 pub min_width: f64,
574 pub min_height: f64,
575 pub max_width: f64,
576 pub max_height: f64,
577 pub padding: Edges,
578 pub margin: MarginEdges,
579
580 pub display: Display,
582
583 pub flex_direction: FlexDirection,
585 pub justify_content: JustifyContent,
586 pub align_items: AlignItems,
587 pub align_self: Option<AlignItems>,
588 pub flex_wrap: FlexWrap,
589 pub align_content: AlignContent,
590 pub flex_grow: f64,
591 pub flex_shrink: f64,
592 pub flex_basis: SizeConstraint,
593 pub gap: f64,
594 pub row_gap: f64,
595 pub column_gap: f64,
596
597 pub grid_template_columns: Option<Vec<GridTrackSize>>,
599 pub grid_template_rows: Option<Vec<GridTrackSize>>,
600 pub grid_auto_rows: Option<GridTrackSize>,
601 pub grid_auto_columns: Option<GridTrackSize>,
602 pub grid_placement: Option<GridPlacement>,
603
604 pub font_family: String,
606 pub font_size: f64,
607 pub font_weight: u32,
608 pub font_style: FontStyle,
609 pub line_height: f64,
610 pub text_align: TextAlign,
611 pub letter_spacing: f64,
612 pub word_spacing: f64,
613 pub text_decoration: TextDecoration,
614 pub text_transform: TextTransform,
615 pub hyphens: Hyphens,
616 pub lang: Option<String>,
617 pub direction: Direction,
618 pub text_overflow: TextOverflow,
619 pub line_breaking: LineBreaking,
620
621 pub color: Color,
623 pub background_color: Option<Color>,
624 pub opacity: f64,
625 pub overflow: Overflow,
626 pub border_width: Edges,
627 pub border_color: EdgeValues<Color>,
628 pub border_radius: CornerValues,
629 pub box_shadow: Option<BoxShadow>,
630 pub background: Option<Background>,
631
632 pub position: Position,
634 pub top: Option<f64>,
635 pub right: Option<f64>,
636 pub bottom: Option<f64>,
637 pub left: Option<f64>,
638
639 pub breakable: bool,
641 pub break_before: bool,
642 pub min_widow_lines: u32,
643 pub min_orphan_lines: u32,
644
645 pub transform: Vec<TransformOp>,
647 pub transform_origin: (f64, f64),
650}
651
652#[derive(Debug, Clone, Copy)]
653pub enum SizeConstraint {
654 Fixed(f64),
655 Auto,
656}
657
658impl Style {
659 pub fn resolve(&self, parent: Option<&ResolvedStyle>, available_width: f64) -> ResolvedStyle {
661 let parent_font_size = parent.map(|p| p.font_size).unwrap_or(12.0);
662 let parent_color = parent.map(|p| p.color).unwrap_or(Color::BLACK);
663 let parent_font_family = parent
664 .map(|p| p.font_family.clone())
665 .unwrap_or_else(|| "Helvetica".to_string());
666
667 let font_size = self.font_size.unwrap_or(parent_font_size);
668
669 ResolvedStyle {
670 width: self
671 .width
672 .map(|d| match d {
673 Dimension::Pt(v) => SizeConstraint::Fixed(v),
674 Dimension::Percent(p) => SizeConstraint::Fixed(available_width * p / 100.0),
675 Dimension::Auto => SizeConstraint::Auto,
676 })
677 .unwrap_or(SizeConstraint::Auto),
678
679 height: self
680 .height
681 .map(|d| match d {
682 Dimension::Pt(v) => SizeConstraint::Fixed(v),
683 Dimension::Percent(p) => SizeConstraint::Fixed(p), Dimension::Auto => SizeConstraint::Auto,
685 })
686 .unwrap_or(SizeConstraint::Auto),
687
688 min_width: self
689 .min_width
690 .and_then(|d| d.resolve(available_width))
691 .unwrap_or(0.0),
692 min_height: self.min_height.and_then(|d| d.resolve(0.0)).unwrap_or(0.0),
693 max_width: self
694 .max_width
695 .and_then(|d| d.resolve(available_width))
696 .unwrap_or(f64::INFINITY),
697 max_height: self
698 .max_height
699 .and_then(|d| d.resolve(0.0))
700 .unwrap_or(f64::INFINITY),
701
702 padding: self.padding.unwrap_or_default(),
703 margin: self.margin.unwrap_or_default(),
704
705 display: self.display.unwrap_or_default(),
706
707 flex_direction: self.flex_direction.unwrap_or_default(),
708 justify_content: self.justify_content.unwrap_or_default(),
709 align_items: self.align_items.unwrap_or_default(),
710 align_self: self.align_self,
711 flex_wrap: self.flex_wrap.unwrap_or_default(),
712 align_content: self.align_content.unwrap_or_default(),
713 flex_grow: self.flex_grow.unwrap_or(0.0),
714 flex_shrink: self.flex_shrink.unwrap_or(1.0),
715 flex_basis: self
716 .flex_basis
717 .map(|d| match d {
718 Dimension::Pt(v) => SizeConstraint::Fixed(v),
719 Dimension::Percent(p) => SizeConstraint::Fixed(available_width * p / 100.0),
720 Dimension::Auto => SizeConstraint::Auto,
721 })
722 .unwrap_or(SizeConstraint::Auto),
723 gap: self.gap.unwrap_or(0.0),
724 row_gap: self.row_gap.or(self.gap).unwrap_or(0.0),
725 column_gap: self.column_gap.or(self.gap).unwrap_or(0.0),
726
727 grid_template_columns: self.grid_template_columns.clone(),
728 grid_template_rows: self.grid_template_rows.clone(),
729 grid_auto_rows: self.grid_auto_rows.clone(),
730 grid_auto_columns: self.grid_auto_columns.clone(),
731 grid_placement: self.grid_placement.clone(),
732
733 font_family: self.font_family.clone().unwrap_or(parent_font_family),
734 font_size,
735 font_weight: self
736 .font_weight
737 .unwrap_or(parent.map(|p| p.font_weight).unwrap_or(400)),
738 font_style: self
739 .font_style
740 .unwrap_or(parent.map(|p| p.font_style).unwrap_or_default()),
741 line_height: self
742 .line_height
743 .unwrap_or(parent.map(|p| p.line_height).unwrap_or(1.4)),
744 text_align: {
745 let direction = self
746 .direction
747 .unwrap_or(parent.map(|p| p.direction).unwrap_or_default());
748 self.text_align.unwrap_or_else(|| {
749 if matches!(direction, Direction::Rtl) {
750 TextAlign::Right
751 } else {
752 parent.map(|p| p.text_align).unwrap_or_default()
753 }
754 })
755 },
756 letter_spacing: self.letter_spacing.unwrap_or(0.0),
757 word_spacing: self.word_spacing.unwrap_or(0.0),
758 text_decoration: self
759 .text_decoration
760 .unwrap_or(parent.map(|p| p.text_decoration).unwrap_or_default()),
761 text_transform: self
762 .text_transform
763 .unwrap_or(parent.map(|p| p.text_transform).unwrap_or_default()),
764 hyphens: self
765 .hyphens
766 .unwrap_or(parent.map(|p| p.hyphens).unwrap_or_default()),
767 lang: self
768 .lang
769 .clone()
770 .or_else(|| parent.and_then(|p| p.lang.clone())),
771 direction: self
772 .direction
773 .unwrap_or(parent.map(|p| p.direction).unwrap_or_default()),
774 text_overflow: self.text_overflow.unwrap_or_default(),
775 line_breaking: self
776 .line_breaking
777 .unwrap_or(parent.map(|p| p.line_breaking).unwrap_or_default()),
778
779 color: self.color.unwrap_or(parent_color),
780 background_color: self.background_color,
781 opacity: self.opacity.unwrap_or(1.0),
782 overflow: self.overflow.unwrap_or_default(),
783 box_shadow: self.box_shadow,
784 background: self.background.clone(),
785
786 border_width: self
787 .border_width
788 .map(|e| Edges {
789 top: e.top,
790 right: e.right,
791 bottom: e.bottom,
792 left: e.left,
793 })
794 .unwrap_or_default(),
795
796 border_color: self
797 .border_color
798 .unwrap_or(EdgeValues::uniform(Color::BLACK)),
799 border_radius: self.border_radius.unwrap_or(CornerValues::uniform(0.0)),
800
801 position: self.position.unwrap_or_default(),
802 top: self.top,
803 right: self.right,
804 bottom: self.bottom,
805 left: self.left,
806
807 breakable: self.wrap.unwrap_or(true),
808 break_before: self.break_before.unwrap_or(false),
809 min_widow_lines: self.min_widow_lines.unwrap_or(2),
810 min_orphan_lines: self.min_orphan_lines.unwrap_or(2),
811
812 transform: self.transform.clone().unwrap_or_default(),
814 transform_origin: self.transform_origin.unwrap_or((0.5, 0.5)),
815 }
816 }
817}