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