1use bitcode::{Decode, Encode};
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, fmt::Display};
4
5use crate::{cf_types::ConditionalFormatting, expressions::token::Error};
6
7fn default_as_false() -> bool {
8 false
9}
10
11fn is_false(b: &bool) -> bool {
12 !*b
13}
14
15#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Default)]
16#[serde(untagged)]
17pub enum Color {
18 Rgb(String),
19 Theme(i32, f64),
21 #[default]
23 None,
24}
25
26fn is_valid_hex_color(color: &str) -> bool {
29 if color.chars().count() != 7 {
30 return false;
31 }
32 if !color.starts_with('#') {
33 return false;
34 }
35 if let Ok(z) = i32::from_str_radix(&color[1..], 16) {
36 if (0..=0xffffff).contains(&z) {
37 return true;
38 }
39 }
40 false
41}
42
43impl Color {
44 pub fn is_none(&self) -> bool {
45 matches!(self, Color::None)
46 }
47
48 pub fn is_some(&self) -> bool {
49 !matches!(self, Color::None)
50 }
51
52 pub fn to_rgb(&self, theme: &Theme) -> String {
55 match self {
56 Color::Rgb(s) => s.clone(),
57 Color::Theme(idx, tint) => theme.resolve(*idx, *tint),
58 Color::None => String::new(),
59 }
60 }
61
62 pub fn from_rgb(color: &str) -> Result<Self, String> {
63 if is_valid_hex_color(color) {
64 return Ok(Color::Rgb(color.to_string()));
65 }
66 Err(format!("Invalid color: '{}'.", color))
67 }
68
69 pub fn from_param(s: &str) -> Result<Self, String> {
74 if s.is_empty() {
75 return Ok(Color::None);
76 }
77 if is_valid_hex_color(s) {
78 return Ok(Color::Rgb(s.to_string()));
79 }
80 if let Some(inner) = s.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
81 let mut parts = inner.splitn(2, ',');
82 if let (Some(idx_str), Some(tint_str)) = (parts.next(), parts.next()) {
83 if let (Ok(idx), Ok(tint)) = (
84 idx_str.trim().parse::<i32>(),
85 tint_str.trim().parse::<f64>(),
86 ) {
87 return Ok(Color::Theme(idx, tint));
88 }
89 }
90 }
91 Err(format!("Invalid color: '{}'.", s))
92 }
93}
94
95#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
96pub struct Metadata {
97 pub application: String,
98 pub app_version: String,
99 pub creator: String,
100 pub last_modified_by: String,
101 pub created: String, pub last_modified: String, }
104
105#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
106pub struct WorkbookSettings {
107 pub tz: String,
108 pub locale: String,
109}
110
111#[derive(Encode, Decode, Debug, PartialEq, Clone)]
113pub struct WorkbookView {
114 pub sheet: u32,
116 pub window_width: i64,
118 pub window_height: i64,
120}
121
122#[derive(Encode, Decode, Debug, PartialEq, Clone)]
124pub struct Workbook {
125 pub shared_strings: Vec<String>,
126 pub defined_names: Vec<DefinedName>,
127 pub worksheets: Vec<Worksheet>,
128 pub styles: Styles,
129 pub name: String,
130 pub settings: WorkbookSettings,
131 pub metadata: Metadata,
132 pub tables: HashMap<String, Table>,
133 pub views: HashMap<u32, WorkbookView>,
134 pub theme: Theme,
135}
136
137#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
139pub struct DefinedName {
140 pub name: String,
141 pub formula: String,
142 pub sheet_id: Option<u32>,
143}
144
145#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
149pub enum SheetState {
150 Visible,
151 Hidden,
152 VeryHidden,
153}
154
155impl Display for SheetState {
156 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
157 match self {
158 SheetState::Visible => write!(formatter, "visible"),
159 SheetState::Hidden => write!(formatter, "hidden"),
160 SheetState::VeryHidden => write!(formatter, "veryHidden"),
161 }
162 }
163}
164
165#[derive(Encode, Decode, Debug, PartialEq, Clone)]
169pub struct WorksheetView {
170 pub row: i32,
172 pub column: i32,
174 pub range: [i32; 4],
178 pub focus_row: i32,
183 pub focus_column: i32,
185 pub top_row: i32,
187 pub left_column: i32,
189}
190
191#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
195#[serde(tag = "type")]
196pub enum Link {
197 External {
201 target: String,
202 tooltip: Option<String>,
203 },
204 Internal {
207 location: String,
208 tooltip: Option<String>,
209 },
210}
211
212#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Copy)]
217pub struct MergedCell {
218 pub row: i32,
220 pub column: i32,
222 pub width: i32,
224 pub height: i32,
226}
227
228impl MergedCell {
229 pub fn last_row(&self) -> i32 {
231 self.row + self.height - 1
232 }
233
234 pub fn last_column(&self) -> i32 {
236 self.column + self.width - 1
237 }
238
239 pub fn contains(&self, row: i32, column: i32) -> bool {
241 row >= self.row
242 && row <= self.last_row()
243 && column >= self.column
244 && column <= self.last_column()
245 }
246
247 pub fn intersects(&self, row: i32, column: i32, width: i32, height: i32) -> bool {
250 row <= self.last_row()
251 && row + height > self.row
252 && column <= self.last_column()
253 && column + width > self.column
254 }
255}
256
257#[derive(Encode, Decode, Debug, PartialEq, Clone)]
259pub struct Worksheet {
260 pub dimension: String,
261 pub cols: Vec<Col>,
262 pub rows: Vec<Row>,
263 pub name: String,
264 pub sheet_data: SheetData,
265 pub shared_formulas: Vec<String>,
266 pub sheet_id: u32,
267 pub state: SheetState,
268 pub color: Color,
269 pub merged_cells: Vec<MergedCell>,
270 pub comments: Vec<Comment>,
271 pub frozen_rows: i32,
272 pub frozen_columns: i32,
273 pub views: HashMap<u32, WorksheetView>,
274 pub show_grid_lines: bool,
276 pub conditional_formatting: Vec<ConditionalFormatting>,
277 pub links: HashMap<(i32, i32), Link>,
279}
280
281pub use crate::sheet_data::SheetData;
284
285#[derive(Encode, Decode, Debug, PartialEq, Clone)]
287pub struct Row {
288 pub r: i32,
290 pub height: f64,
291 pub custom_format: bool,
292 pub custom_height: bool,
293 pub s: i32,
294 pub hidden: bool,
295}
296
297#[derive(Encode, Decode, Debug, PartialEq, Clone)]
299pub struct Col {
300 pub min: i32,
303 pub max: i32,
305 pub width: f64,
306 pub custom_width: bool,
307 pub hidden: bool,
308 pub style: Option<i32>,
309}
310
311#[derive(Debug, Eq, PartialEq)]
313pub enum CellType {
314 Number = 1,
315 Text = 2,
316 LogicalValue = 4,
317 ErrorValue = 16,
318 Array = 64,
319 CompoundData = 128,
320}
321
322#[derive(Encode, Decode, Debug, Clone, PartialEq)]
325pub enum FormulaValue {
326 Unevaluated,
327 Boolean(bool),
328 Number(f64),
329 Text(String),
330 Error {
331 ei: Error,
332 d: Box<ErrorDetails>,
335 },
336}
337
338#[derive(Encode, Decode, Debug, Clone, PartialEq)]
340pub struct ErrorDetails {
341 pub o: String,
343 pub m: String,
345}
346
347impl FormulaValue {
348 pub fn new_error(ei: Error, o: String, m: String) -> FormulaValue {
350 FormulaValue::Error {
351 ei,
352 d: Box::new(ErrorDetails { o, m }),
353 }
354 }
355}
356
357#[derive(Encode, Decode, Debug, Clone, PartialEq)]
359pub enum SpillValue {
360 Boolean(bool),
361 Number(f64),
362 Text(String),
363 Error(Error),
364}
365
366#[derive(Encode, Decode, Debug, Clone, PartialEq)]
368pub enum ArrayKind {
369 Cse,
371 Dynamic,
373}
374
375#[derive(Encode, Decode, Debug, Clone, PartialEq)]
385pub enum Cell {
386 EmptyCell {
387 s: i32,
388 },
389 BooleanCell {
390 v: bool,
391 s: i32,
392 },
393 NumberCell {
394 v: f64,
395 s: i32,
396 },
397 ErrorCell {
399 ei: Error,
400 s: i32,
401 },
402 SharedString {
404 si: i32,
405 s: i32,
406 },
407 CellFormula {
410 f: i32,
411 s: i32,
412 v: FormulaValue,
413 },
414 ArrayFormula {
418 f: i32,
419 s: i32,
420 r: (i32, i32),
421 kind: ArrayKind,
422 v: FormulaValue,
423 },
424 SpillCell {
426 s: i32,
427 a: (i32, i32),
428 v: SpillValue,
429 },
430}
431
432impl Default for Cell {
433 fn default() -> Self {
434 Cell::EmptyCell { s: 0 }
435 }
436}
437
438#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
439pub struct Comment {
440 pub text: String,
441 pub author_name: String,
442 pub author_id: Option<String>,
443 pub cell_ref: String,
444}
445
446#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
448pub struct Table {
449 pub name: String,
450 pub display_name: String,
451 pub sheet_name: String,
452 pub reference: String,
453 pub totals_row_count: u32,
454 pub header_row_count: u32,
455 pub header_row_dxf_id: Option<u32>,
456 pub data_dxf_id: Option<u32>,
457 pub totals_row_dxf_id: Option<u32>,
458 pub columns: Vec<TableColumn>,
459 pub style_info: TableStyleInfo,
460 pub has_filters: bool,
461}
462
463#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
466pub struct TableColumn {
467 pub id: u32,
468 pub name: String,
469 pub totals_row_label: Option<String>,
470 pub header_row_dxf_id: Option<u32>,
471 pub data_dxf_id: Option<u32>,
472 pub totals_row_dxf_id: Option<u32>,
473 pub totals_row_function: Option<String>,
474}
475
476impl Default for TableColumn {
477 fn default() -> Self {
478 TableColumn {
479 id: 0,
480 name: "Column".to_string(),
481 totals_row_label: None,
482 totals_row_function: None,
483 data_dxf_id: None,
484 header_row_dxf_id: None,
485 totals_row_dxf_id: None,
486 }
487 }
488}
489
490#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
491pub struct TableStyleInfo {
492 pub name: Option<String>,
493 pub show_first_column: bool,
494 pub show_last_column: bool,
495 pub show_row_stripes: bool,
496 pub show_column_stripes: bool,
497}
498
499#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Default)]
500pub struct DxfFont {
501 #[serde(skip_serializing_if = "Option::is_none")]
502 pub strike: Option<bool>,
503 #[serde(skip_serializing_if = "Option::is_none")]
504 pub u: Option<bool>,
505 #[serde(skip_serializing_if = "Option::is_none")]
506 pub b: Option<bool>,
507 #[serde(skip_serializing_if = "Option::is_none")]
508 pub i: Option<bool>,
509 #[serde(skip_serializing_if = "Option::is_none")]
510 pub sz: Option<i32>,
511 #[serde(skip_serializing_if = "Color::is_none")]
512 #[serde(default)]
513 pub color: Color,
514}
515
516#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Default)]
521pub struct Dxf {
522 pub font: Option<DxfFont>,
523 pub fill: Option<Fill>,
524 pub border: Option<Border>,
525 pub num_fmt: Option<NumFmt>,
526 pub alignment: Option<Alignment>,
527}
528
529#[derive(Encode, Decode, Debug, PartialEq, Clone)]
530pub struct Styles {
531 pub num_fmts: Vec<NumFmt>,
532 pub fonts: Vec<Font>,
533 pub fills: Vec<Fill>,
534 pub borders: Vec<Border>,
535 pub cell_style_xfs: Vec<CellStyleXfs>,
536 pub cell_xfs: Vec<CellXfs>,
537 pub cell_styles: Vec<CellStyles>,
538 pub dxfs: Vec<Dxf>,
539}
540
541impl Default for Styles {
542 fn default() -> Self {
543 Styles {
544 num_fmts: vec![],
545 fonts: vec![Default::default()],
546 fills: vec![Default::default(), Default::default()],
547 borders: vec![Default::default()],
548 cell_style_xfs: vec![Default::default()],
549 cell_xfs: vec![Default::default()],
550 cell_styles: vec![Default::default()],
551 dxfs: vec![],
552 }
553 }
554}
555
556#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
557pub struct Style {
558 #[serde(skip_serializing_if = "Option::is_none")]
559 pub alignment: Option<Alignment>,
560 pub num_fmt: String,
561 pub fill: Fill,
562 pub font: Font,
563 pub border: Border,
564 pub quote_prefix: bool,
565}
566
567impl Default for Style {
568 fn default() -> Self {
569 Style {
570 alignment: None,
571 num_fmt: "general".to_string(),
572 fill: Fill::default(),
573 font: Font::default(),
574 border: Border::default(),
575 quote_prefix: false,
576 }
577 }
578}
579
580#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
581pub struct NumFmt {
582 pub num_fmt_id: i32,
583 pub format_code: String,
584}
585
586impl Default for NumFmt {
587 fn default() -> Self {
588 NumFmt {
589 num_fmt_id: 0,
590 format_code: "general".to_string(),
591 }
592 }
593}
594
595#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
599#[serde(rename_all = "lowercase")]
600#[derive(Default)]
601pub enum FontScheme {
602 #[default]
603 Minor,
604 Major,
605 None,
606}
607
608impl Display for FontScheme {
609 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
610 match self {
611 FontScheme::Minor => write!(formatter, "minor"),
612 FontScheme::Major => write!(formatter, "major"),
613 FontScheme::None => write!(formatter, "none"),
614 }
615 }
616}
617
618#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
619pub struct Font {
620 #[serde(default = "default_as_false")]
621 #[serde(skip_serializing_if = "is_false")]
622 pub strike: bool,
623 #[serde(default = "default_as_false")]
624 #[serde(skip_serializing_if = "is_false")]
625 pub u: bool, #[serde(default = "default_as_false")]
627 #[serde(skip_serializing_if = "is_false")]
628 pub b: bool,
629 #[serde(default = "default_as_false")]
630 #[serde(skip_serializing_if = "is_false")]
631 pub i: bool,
632 pub sz: i32,
633 #[serde(skip_serializing_if = "Color::is_none")]
634 #[serde(default)]
635 pub color: Color,
636 pub name: String,
637 pub family: i32,
643 pub scheme: FontScheme,
644}
645
646impl Default for Font {
647 fn default() -> Self {
648 Font {
649 strike: false,
650 u: false,
651 b: false,
652 i: false,
653 sz: 12,
654 color: Color::None,
655 name: "Inter".to_string(),
656 family: 2,
657 scheme: FontScheme::Minor,
658 }
659 }
660}
661
662#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Default)]
663pub struct Fill {
664 #[serde(skip_serializing_if = "Color::is_none")]
665 #[serde(default)]
666 pub color: Color,
667}
668
669#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
670#[serde(rename_all = "lowercase")]
671#[derive(Default)]
672pub enum HorizontalAlignment {
673 Center,
674 CenterContinuous,
675 Distributed,
676 Fill,
677 #[default]
678 General,
679 Justify,
680 Left,
681 Right,
682}
683
684impl HorizontalAlignment {
687 fn is_default(&self) -> bool {
688 self == &HorizontalAlignment::default()
689 }
690}
691
692impl Display for HorizontalAlignment {
694 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
695 match self {
696 HorizontalAlignment::Center => write!(formatter, "center"),
697 HorizontalAlignment::CenterContinuous => write!(formatter, "centerContinuous"),
698 HorizontalAlignment::Distributed => write!(formatter, "distributed"),
699 HorizontalAlignment::Fill => write!(formatter, "fill"),
700 HorizontalAlignment::General => write!(formatter, "general"),
701 HorizontalAlignment::Justify => write!(formatter, "justify"),
702 HorizontalAlignment::Left => write!(formatter, "left"),
703 HorizontalAlignment::Right => write!(formatter, "right"),
704 }
705 }
706}
707
708#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
709#[serde(rename_all = "lowercase")]
710#[derive(Default)]
711pub enum VerticalAlignment {
712 #[default]
713 Bottom,
714 Center,
715 Distributed,
716 Justify,
717 Top,
718}
719
720impl VerticalAlignment {
721 fn is_default(&self) -> bool {
722 self == &VerticalAlignment::default()
723 }
724}
725
726impl Display for VerticalAlignment {
727 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
728 match self {
729 VerticalAlignment::Bottom => write!(formatter, "bottom"),
730 VerticalAlignment::Center => write!(formatter, "center"),
731 VerticalAlignment::Distributed => write!(formatter, "distributed"),
732 VerticalAlignment::Justify => write!(formatter, "justify"),
733 VerticalAlignment::Top => write!(formatter, "top"),
734 }
735 }
736}
737
738#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
740pub struct Alignment {
741 #[serde(default)]
742 #[serde(skip_serializing_if = "HorizontalAlignment::is_default")]
743 pub horizontal: HorizontalAlignment,
744 #[serde(skip_serializing_if = "VerticalAlignment::is_default")]
745 #[serde(default)]
746 pub vertical: VerticalAlignment,
747 #[serde(default = "default_as_false")]
748 #[serde(skip_serializing_if = "is_false")]
749 pub wrap_text: bool,
750}
751
752#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
753pub struct CellStyleXfs {
754 pub num_fmt_id: i32,
755 pub font_id: i32,
756 pub fill_id: i32,
757 pub border_id: i32,
758 pub apply_number_format: bool,
759 pub apply_border: bool,
760 pub apply_alignment: bool,
761 pub apply_protection: bool,
762 pub apply_font: bool,
763 pub apply_fill: bool,
764}
765
766impl Default for CellStyleXfs {
767 fn default() -> Self {
768 CellStyleXfs {
769 num_fmt_id: 0,
770 font_id: 0,
771 fill_id: 0,
772 border_id: 0,
773 apply_number_format: true,
774 apply_border: true,
775 apply_alignment: true,
776 apply_protection: true,
777 apply_font: true,
778 apply_fill: true,
779 }
780 }
781}
782
783#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Copy)]
789#[serde(default)]
790pub struct StyleIncludes {
791 pub number_format: bool,
792 pub font: bool,
793 pub fill: bool,
794 pub border: bool,
795 pub alignment: bool,
796 pub protection: bool,
797}
798
799impl Default for StyleIncludes {
800 fn default() -> Self {
801 StyleIncludes {
802 number_format: true,
803 font: true,
804 fill: true,
805 border: true,
806 alignment: true,
807 protection: true,
808 }
809 }
810}
811
812#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
813pub struct CellXfs {
814 pub xf_id: i32,
815 pub num_fmt_id: i32,
816 pub font_id: i32,
817 pub fill_id: i32,
818 pub border_id: i32,
819 pub apply_number_format: bool,
820 pub apply_border: bool,
821 pub apply_alignment: bool,
822 pub apply_protection: bool,
823 pub apply_font: bool,
824 pub apply_fill: bool,
825 pub quote_prefix: bool,
826 pub alignment: Option<Alignment>,
827}
828
829#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
830pub struct CellStyles {
831 pub name: String,
832 pub xf_id: i32,
833 pub builtin_id: i32,
834}
835
836impl Default for CellStyles {
837 fn default() -> Self {
838 CellStyles {
839 name: "normal".to_string(),
840 xf_id: 0,
841 builtin_id: 0,
842 }
843 }
844}
845
846#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Clone)]
847#[serde(rename_all = "lowercase")]
848pub enum BorderStyle {
849 Thin,
850 Medium,
851 Thick,
852 Double,
853 Dotted,
854 SlantDashDot,
855 MediumDashed,
856 MediumDashDotDot,
857 MediumDashDot,
858}
859
860impl Display for BorderStyle {
861 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
862 match self {
863 BorderStyle::Thin => write!(formatter, "thin"),
864 BorderStyle::Thick => write!(formatter, "thick"),
865 BorderStyle::SlantDashDot => write!(formatter, "slantdashdot"),
866 BorderStyle::MediumDashed => write!(formatter, "mediumdashed"),
867 BorderStyle::MediumDashDotDot => write!(formatter, "mediumdashdotdot"),
868 BorderStyle::MediumDashDot => write!(formatter, "mediumdashdot"),
869 BorderStyle::Medium => write!(formatter, "medium"),
870 BorderStyle::Double => write!(formatter, "double"),
871 BorderStyle::Dotted => write!(formatter, "dotted"),
872 }
873 }
874}
875
876#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
877pub struct BorderItem {
878 pub style: BorderStyle,
879 #[serde(skip_serializing_if = "Color::is_none")]
880 #[serde(default)]
881 pub color: Color,
882}
883
884#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Default)]
885pub struct Border {
886 #[serde(default = "default_as_false")]
887 #[serde(skip_serializing_if = "is_false")]
888 pub diagonal_up: bool,
889 #[serde(default = "default_as_false")]
890 #[serde(skip_serializing_if = "is_false")]
891 pub diagonal_down: bool,
892 #[serde(skip_serializing_if = "Option::is_none")]
893 pub left: Option<BorderItem>,
894 #[serde(skip_serializing_if = "Option::is_none")]
895 pub right: Option<BorderItem>,
896 #[serde(skip_serializing_if = "Option::is_none")]
897 pub top: Option<BorderItem>,
898 #[serde(skip_serializing_if = "Option::is_none")]
899 pub bottom: Option<BorderItem>,
900 #[serde(skip_serializing_if = "Option::is_none")]
901 pub diagonal: Option<BorderItem>,
902}
903
904#[derive(Serialize, Deserialize, Debug, PartialEq)]
907pub struct SheetProperties {
908 pub name: String,
909 pub state: String,
910 pub sheet_id: u32,
911 #[serde(skip_serializing_if = "Color::is_none")]
912 #[serde(default)]
913 pub color: Color,
914}
915
916#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
917pub struct Theme {
918 pub name: String,
919 pub dk1: String,
920 pub lt1: String,
921 pub dk2: String,
922 pub lt2: String,
923 pub accent1: String,
924 pub accent2: String,
925 pub accent3: String,
926 pub accent4: String,
927 pub accent5: String,
928 pub accent6: String,
929 pub hlink: String,
930 pub fol_hlink: String,
931}
932
933impl Default for Theme {
934 fn default() -> Self {
935 Theme {
936 name: "Office".to_string(),
937 dk1: "#000000".to_string(),
938 lt1: "#FFFFFF".to_string(),
939 dk2: "#44546A".to_string(),
940 lt2: "#E7E6E6".to_string(),
941 accent1: "#4472C4".to_string(),
942 accent2: "#ED7D31".to_string(),
943 accent3: "#A5A5A5".to_string(),
944 accent4: "#FFC000".to_string(),
945 accent5: "#5B9BD5".to_string(),
946 accent6: "#70AD47".to_string(),
947 hlink: "#0563C1".to_string(),
948 fol_hlink: "#954F72".to_string(),
949 }
950 }
951}
952
953impl Theme {
954 pub fn resolve(&self, theme_index: i32, tint: f64) -> String {
957 use crate::colors::hex_with_tint_to_rgb;
958 let color = match theme_index {
959 0 => &self.lt1,
960 1 => &self.dk1,
961 2 => &self.lt2,
962 3 => &self.dk2,
963 4 => &self.accent1,
964 5 => &self.accent2,
965 6 => &self.accent3,
966 7 => &self.accent4,
967 8 => &self.accent5,
968 9 => &self.accent6,
969 10 => &self.hlink,
970 11 => &self.fol_hlink,
971 _ => &self.dk1,
972 };
973 hex_with_tint_to_rgb(color, tint)
974 }
975}
976
977#[cfg(test)]
978mod test {
979 use super::*;
980 #[test]
981 fn test_is_valid_hex_color() {
982 assert!(is_valid_hex_color("#000000"));
983 assert!(is_valid_hex_color("#ffffff"));
984
985 assert!(!is_valid_hex_color("000000"));
986 assert!(!is_valid_hex_color("ffffff"));
987
988 assert!(!is_valid_hex_color("#gggggg"));
989
990 assert!(!is_valid_hex_color("#ffffff "));
992 assert!(!is_valid_hex_color("#fff")); assert!(!is_valid_hex_color("#ffffff00")); }
995}