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 text_decoration: Option<TextDecoration>,
102 pub text_transform: Option<TextTransform>,
104 pub hyphens: Option<Hyphens>,
106 pub lang: Option<String>,
108 pub direction: Option<Direction>,
110 pub text_overflow: Option<TextOverflow>,
112 pub line_breaking: Option<LineBreaking>,
114
115 pub overflow: Option<Overflow>,
117
118 pub color: Option<Color>,
121 pub background_color: Option<Color>,
123 pub opacity: Option<f64>,
125
126 pub border_width: Option<EdgeValues<f64>>,
129 pub border_color: Option<EdgeValues<Color>>,
131 pub border_radius: Option<CornerValues>,
133
134 pub position: Option<Position>,
137 pub top: Option<f64>,
139 pub right: Option<f64>,
141 pub bottom: Option<f64>,
143 pub left: Option<f64>,
145
146 pub wrap: Option<bool>,
151
152 pub break_before: Option<bool>,
154
155 pub min_widow_lines: Option<u32>,
158
159 pub min_orphan_lines: Option<u32>,
162}
163
164#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
166pub enum Dimension {
167 Pt(f64),
169 Percent(f64),
171 Auto,
173}
174
175impl Dimension {
176 pub fn resolve(&self, parent_size: f64) -> Option<f64> {
179 match self {
180 Dimension::Pt(v) => Some(*v),
181 Dimension::Percent(p) => Some(parent_size * p / 100.0),
182 Dimension::Auto => None,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
189pub enum Display {
190 #[default]
192 Flex,
193 Grid,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199pub enum GridTrackSize {
200 Pt(f64),
202 Fr(f64),
204 Auto,
206 MinMax(Box<GridTrackSize>, Box<GridTrackSize>),
208}
209
210#[derive(Debug, Clone, Default, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct GridPlacement {
214 pub column_start: Option<i32>,
216 pub column_end: Option<i32>,
218 pub row_start: Option<i32>,
220 pub row_end: Option<i32>,
222 pub column_span: Option<u32>,
224 pub row_span: Option<u32>,
226}
227
228#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
229pub enum FlexDirection {
230 #[default]
231 Column,
232 Row,
233 ColumnReverse,
234 RowReverse,
235}
236
237#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
238pub enum JustifyContent {
239 #[default]
240 FlexStart,
241 FlexEnd,
242 Center,
243 SpaceBetween,
244 SpaceAround,
245 SpaceEvenly,
246}
247
248#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
249pub enum AlignItems {
250 FlexStart,
251 FlexEnd,
252 Center,
253 #[default]
254 Stretch,
255 Baseline,
256}
257
258#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
259pub enum FlexWrap {
260 #[default]
261 NoWrap,
262 Wrap,
263 WrapReverse,
264}
265
266#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
267pub enum AlignContent {
268 #[default]
269 FlexStart,
270 FlexEnd,
271 Center,
272 SpaceBetween,
273 SpaceAround,
274 SpaceEvenly,
275 Stretch,
276}
277
278#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
279pub enum FontStyle {
280 #[default]
281 Normal,
282 Italic,
283 Oblique,
284}
285
286#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
287pub enum TextAlign {
288 #[default]
289 Left,
290 Right,
291 Center,
292 Justify,
293}
294
295#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
296pub enum TextDecoration {
297 #[default]
298 None,
299 Underline,
300 LineThrough,
301}
302
303#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
304pub enum TextTransform {
305 #[default]
306 None,
307 Uppercase,
308 Lowercase,
309 Capitalize,
310}
311
312#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
314pub enum Overflow {
315 #[default]
317 Visible,
318 Hidden,
320}
321
322#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
324pub enum TextOverflow {
325 #[default]
327 Wrap,
328 Ellipsis,
330 Clip,
332}
333
334#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
336#[serde(rename_all = "lowercase")]
337pub enum Direction {
338 #[default]
340 Ltr,
341 Rtl,
343 Auto,
345}
346
347#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
349#[serde(rename_all = "lowercase")]
350pub enum LineBreaking {
351 #[default]
353 Optimal,
354 Greedy,
356}
357
358#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
360#[serde(rename_all = "lowercase")]
361pub enum Hyphens {
362 None,
364 #[default]
366 Manual,
367 Auto,
369}
370
371#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
373pub struct Color {
374 pub r: f64, pub g: f64,
376 pub b: f64,
377 pub a: f64,
378}
379
380impl Color {
381 pub const BLACK: Color = Color {
382 r: 0.0,
383 g: 0.0,
384 b: 0.0,
385 a: 1.0,
386 };
387 pub const WHITE: Color = Color {
388 r: 1.0,
389 g: 1.0,
390 b: 1.0,
391 a: 1.0,
392 };
393 pub const TRANSPARENT: Color = Color {
394 r: 0.0,
395 g: 0.0,
396 b: 0.0,
397 a: 0.0,
398 };
399
400 pub fn rgb(r: f64, g: f64, b: f64) -> Self {
401 Self { r, g, b, a: 1.0 }
402 }
403
404 pub fn hex(hex: &str) -> Self {
405 let hex = hex.trim_start_matches('#');
406 let (r, g, b) = match hex.len() {
407 3 => {
408 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).unwrap_or(0);
409 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).unwrap_or(0);
410 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).unwrap_or(0);
411 (r, g, b)
412 }
413 6 => {
414 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
415 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
416 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
417 (r, g, b)
418 }
419 _ => (0, 0, 0),
420 };
421 Self {
422 r: r as f64 / 255.0,
423 g: g as f64 / 255.0,
424 b: b as f64 / 255.0,
425 a: 1.0,
426 }
427 }
428}
429
430impl Default for Color {
431 fn default() -> Self {
432 Color::BLACK
433 }
434}
435
436#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
438pub struct EdgeValues<T: Copy> {
439 pub top: T,
440 pub right: T,
441 pub bottom: T,
442 pub left: T,
443}
444
445impl<T: Copy> EdgeValues<T> {
446 pub fn uniform(v: T) -> Self {
447 Self {
448 top: v,
449 right: v,
450 bottom: v,
451 left: v,
452 }
453 }
454}
455
456#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
458pub struct CornerValues {
459 pub top_left: f64,
460 pub top_right: f64,
461 pub bottom_right: f64,
462 pub bottom_left: f64,
463}
464
465impl CornerValues {
466 pub fn uniform(v: f64) -> Self {
467 Self {
468 top_left: v,
469 top_right: v,
470 bottom_right: v,
471 bottom_left: v,
472 }
473 }
474}
475
476#[derive(Debug, Clone)]
479pub struct ResolvedStyle {
480 pub width: SizeConstraint,
482 pub height: SizeConstraint,
483 pub min_width: f64,
484 pub min_height: f64,
485 pub max_width: f64,
486 pub max_height: f64,
487 pub padding: Edges,
488 pub margin: MarginEdges,
489
490 pub display: Display,
492
493 pub flex_direction: FlexDirection,
495 pub justify_content: JustifyContent,
496 pub align_items: AlignItems,
497 pub align_self: Option<AlignItems>,
498 pub flex_wrap: FlexWrap,
499 pub align_content: AlignContent,
500 pub flex_grow: f64,
501 pub flex_shrink: f64,
502 pub flex_basis: SizeConstraint,
503 pub gap: f64,
504 pub row_gap: f64,
505 pub column_gap: f64,
506
507 pub grid_template_columns: Option<Vec<GridTrackSize>>,
509 pub grid_template_rows: Option<Vec<GridTrackSize>>,
510 pub grid_auto_rows: Option<GridTrackSize>,
511 pub grid_auto_columns: Option<GridTrackSize>,
512 pub grid_placement: Option<GridPlacement>,
513
514 pub font_family: String,
516 pub font_size: f64,
517 pub font_weight: u32,
518 pub font_style: FontStyle,
519 pub line_height: f64,
520 pub text_align: TextAlign,
521 pub letter_spacing: f64,
522 pub text_decoration: TextDecoration,
523 pub text_transform: TextTransform,
524 pub hyphens: Hyphens,
525 pub lang: Option<String>,
526 pub direction: Direction,
527 pub text_overflow: TextOverflow,
528 pub line_breaking: LineBreaking,
529
530 pub color: Color,
532 pub background_color: Option<Color>,
533 pub opacity: f64,
534 pub overflow: Overflow,
535 pub border_width: Edges,
536 pub border_color: EdgeValues<Color>,
537 pub border_radius: CornerValues,
538
539 pub position: Position,
541 pub top: Option<f64>,
542 pub right: Option<f64>,
543 pub bottom: Option<f64>,
544 pub left: Option<f64>,
545
546 pub breakable: bool,
548 pub break_before: bool,
549 pub min_widow_lines: u32,
550 pub min_orphan_lines: u32,
551}
552
553#[derive(Debug, Clone, Copy)]
554pub enum SizeConstraint {
555 Fixed(f64),
556 Auto,
557}
558
559impl Style {
560 pub fn resolve(&self, parent: Option<&ResolvedStyle>, available_width: f64) -> ResolvedStyle {
562 let parent_font_size = parent.map(|p| p.font_size).unwrap_or(12.0);
563 let parent_color = parent.map(|p| p.color).unwrap_or(Color::BLACK);
564 let parent_font_family = parent
565 .map(|p| p.font_family.clone())
566 .unwrap_or_else(|| "Helvetica".to_string());
567
568 let font_size = self.font_size.unwrap_or(parent_font_size);
569
570 ResolvedStyle {
571 width: self
572 .width
573 .map(|d| match d {
574 Dimension::Pt(v) => SizeConstraint::Fixed(v),
575 Dimension::Percent(p) => SizeConstraint::Fixed(available_width * p / 100.0),
576 Dimension::Auto => SizeConstraint::Auto,
577 })
578 .unwrap_or(SizeConstraint::Auto),
579
580 height: self
581 .height
582 .map(|d| match d {
583 Dimension::Pt(v) => SizeConstraint::Fixed(v),
584 Dimension::Percent(p) => SizeConstraint::Fixed(p), Dimension::Auto => SizeConstraint::Auto,
586 })
587 .unwrap_or(SizeConstraint::Auto),
588
589 min_width: self
590 .min_width
591 .and_then(|d| d.resolve(available_width))
592 .unwrap_or(0.0),
593 min_height: self.min_height.and_then(|d| d.resolve(0.0)).unwrap_or(0.0),
594 max_width: self
595 .max_width
596 .and_then(|d| d.resolve(available_width))
597 .unwrap_or(f64::INFINITY),
598 max_height: self
599 .max_height
600 .and_then(|d| d.resolve(0.0))
601 .unwrap_or(f64::INFINITY),
602
603 padding: self.padding.unwrap_or_default(),
604 margin: self.margin.unwrap_or_default(),
605
606 display: self.display.unwrap_or_default(),
607
608 flex_direction: self.flex_direction.unwrap_or_default(),
609 justify_content: self.justify_content.unwrap_or_default(),
610 align_items: self.align_items.unwrap_or_default(),
611 align_self: self.align_self,
612 flex_wrap: self.flex_wrap.unwrap_or_default(),
613 align_content: self.align_content.unwrap_or_default(),
614 flex_grow: self.flex_grow.unwrap_or(0.0),
615 flex_shrink: self.flex_shrink.unwrap_or(1.0),
616 flex_basis: self
617 .flex_basis
618 .map(|d| match d {
619 Dimension::Pt(v) => SizeConstraint::Fixed(v),
620 Dimension::Percent(p) => SizeConstraint::Fixed(available_width * p / 100.0),
621 Dimension::Auto => SizeConstraint::Auto,
622 })
623 .unwrap_or(SizeConstraint::Auto),
624 gap: self.gap.unwrap_or(0.0),
625 row_gap: self.row_gap.or(self.gap).unwrap_or(0.0),
626 column_gap: self.column_gap.or(self.gap).unwrap_or(0.0),
627
628 grid_template_columns: self.grid_template_columns.clone(),
629 grid_template_rows: self.grid_template_rows.clone(),
630 grid_auto_rows: self.grid_auto_rows.clone(),
631 grid_auto_columns: self.grid_auto_columns.clone(),
632 grid_placement: self.grid_placement.clone(),
633
634 font_family: self.font_family.clone().unwrap_or(parent_font_family),
635 font_size,
636 font_weight: self
637 .font_weight
638 .unwrap_or(parent.map(|p| p.font_weight).unwrap_or(400)),
639 font_style: self
640 .font_style
641 .unwrap_or(parent.map(|p| p.font_style).unwrap_or_default()),
642 line_height: self
643 .line_height
644 .unwrap_or(parent.map(|p| p.line_height).unwrap_or(1.4)),
645 text_align: {
646 let direction = self
647 .direction
648 .unwrap_or(parent.map(|p| p.direction).unwrap_or_default());
649 self.text_align.unwrap_or_else(|| {
650 if matches!(direction, Direction::Rtl) {
651 TextAlign::Right
652 } else {
653 parent.map(|p| p.text_align).unwrap_or_default()
654 }
655 })
656 },
657 letter_spacing: self.letter_spacing.unwrap_or(0.0),
658 text_decoration: self
659 .text_decoration
660 .unwrap_or(parent.map(|p| p.text_decoration).unwrap_or_default()),
661 text_transform: self
662 .text_transform
663 .unwrap_or(parent.map(|p| p.text_transform).unwrap_or_default()),
664 hyphens: self
665 .hyphens
666 .unwrap_or(parent.map(|p| p.hyphens).unwrap_or_default()),
667 lang: self
668 .lang
669 .clone()
670 .or_else(|| parent.and_then(|p| p.lang.clone())),
671 direction: self
672 .direction
673 .unwrap_or(parent.map(|p| p.direction).unwrap_or_default()),
674 text_overflow: self.text_overflow.unwrap_or_default(),
675 line_breaking: self
676 .line_breaking
677 .unwrap_or(parent.map(|p| p.line_breaking).unwrap_or_default()),
678
679 color: self.color.unwrap_or(parent_color),
680 background_color: self.background_color,
681 opacity: self.opacity.unwrap_or(1.0),
682 overflow: self.overflow.unwrap_or_default(),
683
684 border_width: self
685 .border_width
686 .map(|e| Edges {
687 top: e.top,
688 right: e.right,
689 bottom: e.bottom,
690 left: e.left,
691 })
692 .unwrap_or_default(),
693
694 border_color: self
695 .border_color
696 .unwrap_or(EdgeValues::uniform(Color::BLACK)),
697 border_radius: self.border_radius.unwrap_or(CornerValues::uniform(0.0)),
698
699 position: self.position.unwrap_or_default(),
700 top: self.top,
701 right: self.right,
702 bottom: self.bottom,
703 left: self.left,
704
705 breakable: self.wrap.unwrap_or(true),
706 break_before: self.break_before.unwrap_or(false),
707 min_widow_lines: self.min_widow_lines.unwrap_or(2),
708 min_orphan_lines: self.min_orphan_lines.unwrap_or(2),
709 }
710 }
711}