use crate::model::{Edges, MarginEdges, Position};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Style {
pub width: Option<Dimension>,
pub height: Option<Dimension>,
pub min_width: Option<Dimension>,
pub min_height: Option<Dimension>,
pub max_width: Option<Dimension>,
pub max_height: Option<Dimension>,
#[serde(default)]
pub padding: Option<Edges>,
#[serde(default)]
pub margin: Option<MarginEdges>,
pub display: Option<Display>,
#[serde(default)]
pub flex_direction: Option<FlexDirection>,
#[serde(default)]
pub justify_content: Option<JustifyContent>,
#[serde(default)]
pub align_items: Option<AlignItems>,
#[serde(default)]
pub align_self: Option<AlignItems>,
#[serde(default)]
pub flex_wrap: Option<FlexWrap>,
pub align_content: Option<AlignContent>,
pub flex_grow: Option<f64>,
pub flex_shrink: Option<f64>,
pub flex_basis: Option<Dimension>,
pub gap: Option<f64>,
pub row_gap: Option<f64>,
pub column_gap: Option<f64>,
pub grid_template_columns: Option<Vec<GridTrackSize>>,
pub grid_template_rows: Option<Vec<GridTrackSize>>,
pub grid_auto_rows: Option<GridTrackSize>,
pub grid_auto_columns: Option<GridTrackSize>,
pub grid_placement: Option<GridPlacement>,
pub font_family: Option<String>,
pub font_size: Option<f64>,
pub font_weight: Option<u32>,
pub font_style: Option<FontStyle>,
pub line_height: Option<f64>,
pub text_align: Option<TextAlign>,
pub letter_spacing: Option<f64>,
pub text_decoration: Option<TextDecoration>,
pub text_transform: Option<TextTransform>,
pub hyphens: Option<Hyphens>,
pub lang: Option<String>,
pub direction: Option<Direction>,
pub text_overflow: Option<TextOverflow>,
pub line_breaking: Option<LineBreaking>,
pub overflow: Option<Overflow>,
pub color: Option<Color>,
pub background_color: Option<Color>,
pub opacity: Option<f64>,
pub border_width: Option<EdgeValues<f64>>,
pub border_color: Option<EdgeValues<Color>>,
pub border_radius: Option<CornerValues>,
pub position: Option<Position>,
pub top: Option<f64>,
pub right: Option<f64>,
pub bottom: Option<f64>,
pub left: Option<f64>,
pub wrap: Option<bool>,
pub break_before: Option<bool>,
pub min_widow_lines: Option<u32>,
pub min_orphan_lines: Option<u32>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Dimension {
Pt(f64),
Percent(f64),
Auto,
}
impl Dimension {
pub fn resolve(&self, parent_size: f64) -> Option<f64> {
match self {
Dimension::Pt(v) => Some(*v),
Dimension::Percent(p) => Some(parent_size * p / 100.0),
Dimension::Auto => None,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Display {
#[default]
Flex,
Grid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum GridTrackSize {
Pt(f64),
Fr(f64),
Auto,
MinMax(Box<GridTrackSize>, Box<GridTrackSize>),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GridPlacement {
pub column_start: Option<i32>,
pub column_end: Option<i32>,
pub row_start: Option<i32>,
pub row_end: Option<i32>,
pub column_span: Option<u32>,
pub row_span: Option<u32>,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum FlexDirection {
#[default]
Column,
Row,
ColumnReverse,
RowReverse,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum JustifyContent {
#[default]
FlexStart,
FlexEnd,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum AlignItems {
FlexStart,
FlexEnd,
Center,
#[default]
Stretch,
Baseline,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum FlexWrap {
#[default]
NoWrap,
Wrap,
WrapReverse,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum AlignContent {
#[default]
FlexStart,
FlexEnd,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly,
Stretch,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum FontStyle {
#[default]
Normal,
Italic,
Oblique,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum TextAlign {
#[default]
Left,
Right,
Center,
Justify,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum TextDecoration {
#[default]
None,
Underline,
LineThrough,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum TextTransform {
#[default]
None,
Uppercase,
Lowercase,
Capitalize,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Overflow {
#[default]
Visible,
Hidden,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum TextOverflow {
#[default]
Wrap,
Ellipsis,
Clip,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Direction {
#[default]
Ltr,
Rtl,
Auto,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LineBreaking {
#[default]
Optimal,
Greedy,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Hyphens {
None,
#[default]
Manual,
Auto,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Color {
pub r: f64, pub g: f64,
pub b: f64,
pub a: f64,
}
impl Color {
pub const BLACK: Color = Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const WHITE: Color = Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
pub const TRANSPARENT: Color = Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
pub fn rgb(r: f64, g: f64, b: f64) -> Self {
Self { r, g, b, a: 1.0 }
}
pub fn hex(hex: &str) -> Self {
let hex = hex.trim_start_matches('#');
let (r, g, b) = match hex.len() {
3 => {
let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).unwrap_or(0);
let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).unwrap_or(0);
let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).unwrap_or(0);
(r, g, b)
}
6 => {
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
(r, g, b)
}
_ => (0, 0, 0),
};
Self {
r: r as f64 / 255.0,
g: g as f64 / 255.0,
b: b as f64 / 255.0,
a: 1.0,
}
}
}
impl Default for Color {
fn default() -> Self {
Color::BLACK
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct EdgeValues<T: Copy> {
pub top: T,
pub right: T,
pub bottom: T,
pub left: T,
}
impl<T: Copy> EdgeValues<T> {
pub fn uniform(v: T) -> Self {
Self {
top: v,
right: v,
bottom: v,
left: v,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct CornerValues {
pub top_left: f64,
pub top_right: f64,
pub bottom_right: f64,
pub bottom_left: f64,
}
impl CornerValues {
pub fn uniform(v: f64) -> Self {
Self {
top_left: v,
top_right: v,
bottom_right: v,
bottom_left: v,
}
}
}
#[derive(Debug, Clone)]
pub struct ResolvedStyle {
pub width: SizeConstraint,
pub height: SizeConstraint,
pub min_width: f64,
pub min_height: f64,
pub max_width: f64,
pub max_height: f64,
pub padding: Edges,
pub margin: MarginEdges,
pub display: Display,
pub flex_direction: FlexDirection,
pub justify_content: JustifyContent,
pub align_items: AlignItems,
pub align_self: Option<AlignItems>,
pub flex_wrap: FlexWrap,
pub align_content: AlignContent,
pub flex_grow: f64,
pub flex_shrink: f64,
pub flex_basis: SizeConstraint,
pub gap: f64,
pub row_gap: f64,
pub column_gap: f64,
pub grid_template_columns: Option<Vec<GridTrackSize>>,
pub grid_template_rows: Option<Vec<GridTrackSize>>,
pub grid_auto_rows: Option<GridTrackSize>,
pub grid_auto_columns: Option<GridTrackSize>,
pub grid_placement: Option<GridPlacement>,
pub font_family: String,
pub font_size: f64,
pub font_weight: u32,
pub font_style: FontStyle,
pub line_height: f64,
pub text_align: TextAlign,
pub letter_spacing: f64,
pub text_decoration: TextDecoration,
pub text_transform: TextTransform,
pub hyphens: Hyphens,
pub lang: Option<String>,
pub direction: Direction,
pub text_overflow: TextOverflow,
pub line_breaking: LineBreaking,
pub color: Color,
pub background_color: Option<Color>,
pub opacity: f64,
pub overflow: Overflow,
pub border_width: Edges,
pub border_color: EdgeValues<Color>,
pub border_radius: CornerValues,
pub position: Position,
pub top: Option<f64>,
pub right: Option<f64>,
pub bottom: Option<f64>,
pub left: Option<f64>,
pub breakable: bool,
pub break_before: bool,
pub min_widow_lines: u32,
pub min_orphan_lines: u32,
}
#[derive(Debug, Clone, Copy)]
pub enum SizeConstraint {
Fixed(f64),
Auto,
}
impl Style {
pub fn resolve(&self, parent: Option<&ResolvedStyle>, available_width: f64) -> ResolvedStyle {
let parent_font_size = parent.map(|p| p.font_size).unwrap_or(12.0);
let parent_color = parent.map(|p| p.color).unwrap_or(Color::BLACK);
let parent_font_family = parent
.map(|p| p.font_family.clone())
.unwrap_or_else(|| "Helvetica".to_string());
let font_size = self.font_size.unwrap_or(parent_font_size);
ResolvedStyle {
width: self
.width
.map(|d| match d {
Dimension::Pt(v) => SizeConstraint::Fixed(v),
Dimension::Percent(p) => SizeConstraint::Fixed(available_width * p / 100.0),
Dimension::Auto => SizeConstraint::Auto,
})
.unwrap_or(SizeConstraint::Auto),
height: self
.height
.map(|d| match d {
Dimension::Pt(v) => SizeConstraint::Fixed(v),
Dimension::Percent(p) => SizeConstraint::Fixed(p), Dimension::Auto => SizeConstraint::Auto,
})
.unwrap_or(SizeConstraint::Auto),
min_width: self
.min_width
.and_then(|d| d.resolve(available_width))
.unwrap_or(0.0),
min_height: self.min_height.and_then(|d| d.resolve(0.0)).unwrap_or(0.0),
max_width: self
.max_width
.and_then(|d| d.resolve(available_width))
.unwrap_or(f64::INFINITY),
max_height: self
.max_height
.and_then(|d| d.resolve(0.0))
.unwrap_or(f64::INFINITY),
padding: self.padding.unwrap_or_default(),
margin: self.margin.unwrap_or_default(),
display: self.display.unwrap_or_default(),
flex_direction: self.flex_direction.unwrap_or_default(),
justify_content: self.justify_content.unwrap_or_default(),
align_items: self.align_items.unwrap_or_default(),
align_self: self.align_self,
flex_wrap: self.flex_wrap.unwrap_or_default(),
align_content: self.align_content.unwrap_or_default(),
flex_grow: self.flex_grow.unwrap_or(0.0),
flex_shrink: self.flex_shrink.unwrap_or(1.0),
flex_basis: self
.flex_basis
.map(|d| match d {
Dimension::Pt(v) => SizeConstraint::Fixed(v),
Dimension::Percent(p) => SizeConstraint::Fixed(available_width * p / 100.0),
Dimension::Auto => SizeConstraint::Auto,
})
.unwrap_or(SizeConstraint::Auto),
gap: self.gap.unwrap_or(0.0),
row_gap: self.row_gap.or(self.gap).unwrap_or(0.0),
column_gap: self.column_gap.or(self.gap).unwrap_or(0.0),
grid_template_columns: self.grid_template_columns.clone(),
grid_template_rows: self.grid_template_rows.clone(),
grid_auto_rows: self.grid_auto_rows.clone(),
grid_auto_columns: self.grid_auto_columns.clone(),
grid_placement: self.grid_placement.clone(),
font_family: self.font_family.clone().unwrap_or(parent_font_family),
font_size,
font_weight: self
.font_weight
.unwrap_or(parent.map(|p| p.font_weight).unwrap_or(400)),
font_style: self
.font_style
.unwrap_or(parent.map(|p| p.font_style).unwrap_or_default()),
line_height: self
.line_height
.unwrap_or(parent.map(|p| p.line_height).unwrap_or(1.4)),
text_align: {
let direction = self
.direction
.unwrap_or(parent.map(|p| p.direction).unwrap_or_default());
self.text_align.unwrap_or_else(|| {
if matches!(direction, Direction::Rtl) {
TextAlign::Right
} else {
parent.map(|p| p.text_align).unwrap_or_default()
}
})
},
letter_spacing: self.letter_spacing.unwrap_or(0.0),
text_decoration: self
.text_decoration
.unwrap_or(parent.map(|p| p.text_decoration).unwrap_or_default()),
text_transform: self
.text_transform
.unwrap_or(parent.map(|p| p.text_transform).unwrap_or_default()),
hyphens: self
.hyphens
.unwrap_or(parent.map(|p| p.hyphens).unwrap_or_default()),
lang: self
.lang
.clone()
.or_else(|| parent.and_then(|p| p.lang.clone())),
direction: self
.direction
.unwrap_or(parent.map(|p| p.direction).unwrap_or_default()),
text_overflow: self.text_overflow.unwrap_or_default(),
line_breaking: self
.line_breaking
.unwrap_or(parent.map(|p| p.line_breaking).unwrap_or_default()),
color: self.color.unwrap_or(parent_color),
background_color: self.background_color,
opacity: self.opacity.unwrap_or(1.0),
overflow: self.overflow.unwrap_or_default(),
border_width: self
.border_width
.map(|e| Edges {
top: e.top,
right: e.right,
bottom: e.bottom,
left: e.left,
})
.unwrap_or_default(),
border_color: self
.border_color
.unwrap_or(EdgeValues::uniform(Color::BLACK)),
border_radius: self.border_radius.unwrap_or(CornerValues::uniform(0.0)),
position: self.position.unwrap_or_default(),
top: self.top,
right: self.right,
bottom: self.bottom,
left: self.left,
breakable: self.wrap.unwrap_or(true),
break_before: self.break_before.unwrap_or(false),
min_widow_lines: self.min_widow_lines.unwrap_or(2),
min_orphan_lines: self.min_orphan_lines.unwrap_or(2),
}
}
}