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],
176 pub top_row: i32,
178 pub left_column: i32,
180}
181
182#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
186#[serde(tag = "type")]
187pub enum Link {
188 External {
192 target: String,
193 tooltip: Option<String>,
194 },
195 Internal {
198 location: String,
199 tooltip: Option<String>,
200 },
201}
202
203#[derive(Encode, Decode, Debug, PartialEq, Clone)]
205pub struct Worksheet {
206 pub dimension: String,
207 pub cols: Vec<Col>,
208 pub rows: Vec<Row>,
209 pub name: String,
210 pub sheet_data: SheetData,
211 pub shared_formulas: Vec<String>,
212 pub sheet_id: u32,
213 pub state: SheetState,
214 pub color: Color,
215 pub merge_cells: Vec<String>,
216 pub comments: Vec<Comment>,
217 pub frozen_rows: i32,
218 pub frozen_columns: i32,
219 pub views: HashMap<u32, WorksheetView>,
220 pub show_grid_lines: bool,
222 pub conditional_formatting: Vec<ConditionalFormatting>,
223 pub links: HashMap<(i32, i32), Link>,
225}
226
227pub type SheetData = HashMap<i32, HashMap<i32, Cell>>;
230
231#[derive(Encode, Decode, Debug, PartialEq, Clone)]
233pub struct Row {
234 pub r: i32,
236 pub height: f64,
237 pub custom_format: bool,
238 pub custom_height: bool,
239 pub s: i32,
240 pub hidden: bool,
241}
242
243#[derive(Encode, Decode, Debug, PartialEq, Clone)]
245pub struct Col {
246 pub min: i32,
249 pub max: i32,
251 pub width: f64,
252 pub custom_width: bool,
253 pub hidden: bool,
254 pub style: Option<i32>,
255}
256
257#[derive(Debug, Eq, PartialEq)]
259pub enum CellType {
260 Number = 1,
261 Text = 2,
262 LogicalValue = 4,
263 ErrorValue = 16,
264 Array = 64,
265 CompoundData = 128,
266}
267
268#[derive(Encode, Decode, Debug, Clone, PartialEq)]
271pub enum FormulaValue {
272 Unevaluated,
273 Boolean(bool),
274 Number(f64),
275 Text(String),
276 Error {
277 ei: Error,
278 o: String,
280 m: String,
282 },
283}
284
285#[derive(Encode, Decode, Debug, Clone, PartialEq)]
287pub enum SpillValue {
288 Boolean(bool),
289 Number(f64),
290 Text(String),
291 Error(Error),
292}
293
294#[derive(Encode, Decode, Debug, Clone, PartialEq)]
296pub enum ArrayKind {
297 Cse,
299 Dynamic,
301}
302
303#[derive(Encode, Decode, Debug, Clone, PartialEq)]
313pub enum Cell {
314 EmptyCell {
315 s: i32,
316 },
317 BooleanCell {
318 v: bool,
319 s: i32,
320 },
321 NumberCell {
322 v: f64,
323 s: i32,
324 },
325 ErrorCell {
327 ei: Error,
328 s: i32,
329 },
330 SharedString {
332 si: i32,
333 s: i32,
334 },
335 CellFormula {
338 f: i32,
339 s: i32,
340 v: FormulaValue,
341 },
342 ArrayFormula {
346 f: i32,
347 s: i32,
348 r: (i32, i32),
349 kind: ArrayKind,
350 v: FormulaValue,
351 },
352 SpillCell {
354 s: i32,
355 a: (i32, i32),
356 v: SpillValue,
357 },
358}
359
360impl Default for Cell {
361 fn default() -> Self {
362 Cell::EmptyCell { s: 0 }
363 }
364}
365
366#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
367pub struct Comment {
368 pub text: String,
369 pub author_name: String,
370 pub author_id: Option<String>,
371 pub cell_ref: String,
372}
373
374#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
376pub struct Table {
377 pub name: String,
378 pub display_name: String,
379 pub sheet_name: String,
380 pub reference: String,
381 pub totals_row_count: u32,
382 pub header_row_count: u32,
383 pub header_row_dxf_id: Option<u32>,
384 pub data_dxf_id: Option<u32>,
385 pub totals_row_dxf_id: Option<u32>,
386 pub columns: Vec<TableColumn>,
387 pub style_info: TableStyleInfo,
388 pub has_filters: bool,
389}
390
391#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
394pub struct TableColumn {
395 pub id: u32,
396 pub name: String,
397 pub totals_row_label: Option<String>,
398 pub header_row_dxf_id: Option<u32>,
399 pub data_dxf_id: Option<u32>,
400 pub totals_row_dxf_id: Option<u32>,
401 pub totals_row_function: Option<String>,
402}
403
404impl Default for TableColumn {
405 fn default() -> Self {
406 TableColumn {
407 id: 0,
408 name: "Column".to_string(),
409 totals_row_label: None,
410 totals_row_function: None,
411 data_dxf_id: None,
412 header_row_dxf_id: None,
413 totals_row_dxf_id: None,
414 }
415 }
416}
417
418#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
419pub struct TableStyleInfo {
420 pub name: Option<String>,
421 pub show_first_column: bool,
422 pub show_last_column: bool,
423 pub show_row_stripes: bool,
424 pub show_column_stripes: bool,
425}
426
427#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Default)]
428pub struct DxfFont {
429 #[serde(skip_serializing_if = "Option::is_none")]
430 pub strike: Option<bool>,
431 #[serde(skip_serializing_if = "Option::is_none")]
432 pub u: Option<bool>,
433 #[serde(skip_serializing_if = "Option::is_none")]
434 pub b: Option<bool>,
435 #[serde(skip_serializing_if = "Option::is_none")]
436 pub i: Option<bool>,
437 #[serde(skip_serializing_if = "Option::is_none")]
438 pub sz: Option<i32>,
439 #[serde(skip_serializing_if = "Color::is_none")]
440 #[serde(default)]
441 pub color: Color,
442}
443
444#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Default)]
449pub struct Dxf {
450 pub font: Option<DxfFont>,
451 pub fill: Option<Fill>,
452 pub border: Option<Border>,
453 pub num_fmt: Option<NumFmt>,
454 pub alignment: Option<Alignment>,
455}
456
457#[derive(Encode, Decode, Debug, PartialEq, Clone)]
458pub struct Styles {
459 pub num_fmts: Vec<NumFmt>,
460 pub fonts: Vec<Font>,
461 pub fills: Vec<Fill>,
462 pub borders: Vec<Border>,
463 pub cell_style_xfs: Vec<CellStyleXfs>,
464 pub cell_xfs: Vec<CellXfs>,
465 pub cell_styles: Vec<CellStyles>,
466 pub dxfs: Vec<Dxf>,
467}
468
469impl Default for Styles {
470 fn default() -> Self {
471 Styles {
472 num_fmts: vec![],
473 fonts: vec![Default::default()],
474 fills: vec![Default::default(), Default::default()],
475 borders: vec![Default::default()],
476 cell_style_xfs: vec![Default::default()],
477 cell_xfs: vec![Default::default()],
478 cell_styles: vec![Default::default()],
479 dxfs: vec![],
480 }
481 }
482}
483
484#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
485pub struct Style {
486 #[serde(skip_serializing_if = "Option::is_none")]
487 pub alignment: Option<Alignment>,
488 pub num_fmt: String,
489 pub fill: Fill,
490 pub font: Font,
491 pub border: Border,
492 pub quote_prefix: bool,
493}
494
495impl Default for Style {
496 fn default() -> Self {
497 Style {
498 alignment: None,
499 num_fmt: "general".to_string(),
500 fill: Fill::default(),
501 font: Font::default(),
502 border: Border::default(),
503 quote_prefix: false,
504 }
505 }
506}
507
508#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
509pub struct NumFmt {
510 pub num_fmt_id: i32,
511 pub format_code: String,
512}
513
514impl Default for NumFmt {
515 fn default() -> Self {
516 NumFmt {
517 num_fmt_id: 0,
518 format_code: "general".to_string(),
519 }
520 }
521}
522
523#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
527#[serde(rename_all = "lowercase")]
528#[derive(Default)]
529pub enum FontScheme {
530 #[default]
531 Minor,
532 Major,
533 None,
534}
535
536impl Display for FontScheme {
537 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
538 match self {
539 FontScheme::Minor => write!(formatter, "minor"),
540 FontScheme::Major => write!(formatter, "major"),
541 FontScheme::None => write!(formatter, "none"),
542 }
543 }
544}
545
546#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
547pub struct Font {
548 #[serde(default = "default_as_false")]
549 #[serde(skip_serializing_if = "is_false")]
550 pub strike: bool,
551 #[serde(default = "default_as_false")]
552 #[serde(skip_serializing_if = "is_false")]
553 pub u: bool, #[serde(default = "default_as_false")]
555 #[serde(skip_serializing_if = "is_false")]
556 pub b: bool,
557 #[serde(default = "default_as_false")]
558 #[serde(skip_serializing_if = "is_false")]
559 pub i: bool,
560 pub sz: i32,
561 #[serde(skip_serializing_if = "Color::is_none")]
562 #[serde(default)]
563 pub color: Color,
564 pub name: String,
565 pub family: i32,
571 pub scheme: FontScheme,
572}
573
574impl Default for Font {
575 fn default() -> Self {
576 Font {
577 strike: false,
578 u: false,
579 b: false,
580 i: false,
581 sz: 12,
582 color: Color::None,
583 name: "Inter".to_string(),
584 family: 2,
585 scheme: FontScheme::Minor,
586 }
587 }
588}
589
590#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Default)]
591pub struct Fill {
592 #[serde(skip_serializing_if = "Color::is_none")]
593 #[serde(default)]
594 pub color: Color,
595}
596
597#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
598#[serde(rename_all = "lowercase")]
599#[derive(Default)]
600pub enum HorizontalAlignment {
601 Center,
602 CenterContinuous,
603 Distributed,
604 Fill,
605 #[default]
606 General,
607 Justify,
608 Left,
609 Right,
610}
611
612impl HorizontalAlignment {
615 fn is_default(&self) -> bool {
616 self == &HorizontalAlignment::default()
617 }
618}
619
620impl Display for HorizontalAlignment {
622 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
623 match self {
624 HorizontalAlignment::Center => write!(formatter, "center"),
625 HorizontalAlignment::CenterContinuous => write!(formatter, "centerContinuous"),
626 HorizontalAlignment::Distributed => write!(formatter, "distributed"),
627 HorizontalAlignment::Fill => write!(formatter, "fill"),
628 HorizontalAlignment::General => write!(formatter, "general"),
629 HorizontalAlignment::Justify => write!(formatter, "justify"),
630 HorizontalAlignment::Left => write!(formatter, "left"),
631 HorizontalAlignment::Right => write!(formatter, "right"),
632 }
633 }
634}
635
636#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
637#[serde(rename_all = "lowercase")]
638#[derive(Default)]
639pub enum VerticalAlignment {
640 #[default]
641 Bottom,
642 Center,
643 Distributed,
644 Justify,
645 Top,
646}
647
648impl VerticalAlignment {
649 fn is_default(&self) -> bool {
650 self == &VerticalAlignment::default()
651 }
652}
653
654impl Display for VerticalAlignment {
655 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
656 match self {
657 VerticalAlignment::Bottom => write!(formatter, "bottom"),
658 VerticalAlignment::Center => write!(formatter, "center"),
659 VerticalAlignment::Distributed => write!(formatter, "distributed"),
660 VerticalAlignment::Justify => write!(formatter, "justify"),
661 VerticalAlignment::Top => write!(formatter, "top"),
662 }
663 }
664}
665
666#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
668pub struct Alignment {
669 #[serde(default)]
670 #[serde(skip_serializing_if = "HorizontalAlignment::is_default")]
671 pub horizontal: HorizontalAlignment,
672 #[serde(skip_serializing_if = "VerticalAlignment::is_default")]
673 #[serde(default)]
674 pub vertical: VerticalAlignment,
675 #[serde(default = "default_as_false")]
676 #[serde(skip_serializing_if = "is_false")]
677 pub wrap_text: bool,
678}
679
680#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
681pub struct CellStyleXfs {
682 pub num_fmt_id: i32,
683 pub font_id: i32,
684 pub fill_id: i32,
685 pub border_id: i32,
686 pub apply_number_format: bool,
687 pub apply_border: bool,
688 pub apply_alignment: bool,
689 pub apply_protection: bool,
690 pub apply_font: bool,
691 pub apply_fill: bool,
692}
693
694impl Default for CellStyleXfs {
695 fn default() -> Self {
696 CellStyleXfs {
697 num_fmt_id: 0,
698 font_id: 0,
699 fill_id: 0,
700 border_id: 0,
701 apply_number_format: true,
702 apply_border: true,
703 apply_alignment: true,
704 apply_protection: true,
705 apply_font: true,
706 apply_fill: true,
707 }
708 }
709}
710
711#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Copy)]
717#[serde(default)]
718pub struct StyleIncludes {
719 pub number_format: bool,
720 pub font: bool,
721 pub fill: bool,
722 pub border: bool,
723 pub alignment: bool,
724 pub protection: bool,
725}
726
727impl Default for StyleIncludes {
728 fn default() -> Self {
729 StyleIncludes {
730 number_format: true,
731 font: true,
732 fill: true,
733 border: true,
734 alignment: true,
735 protection: true,
736 }
737 }
738}
739
740#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
741pub struct CellXfs {
742 pub xf_id: i32,
743 pub num_fmt_id: i32,
744 pub font_id: i32,
745 pub fill_id: i32,
746 pub border_id: i32,
747 pub apply_number_format: bool,
748 pub apply_border: bool,
749 pub apply_alignment: bool,
750 pub apply_protection: bool,
751 pub apply_font: bool,
752 pub apply_fill: bool,
753 pub quote_prefix: bool,
754 pub alignment: Option<Alignment>,
755}
756
757#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
758pub struct CellStyles {
759 pub name: String,
760 pub xf_id: i32,
761 pub builtin_id: i32,
762}
763
764impl Default for CellStyles {
765 fn default() -> Self {
766 CellStyles {
767 name: "normal".to_string(),
768 xf_id: 0,
769 builtin_id: 0,
770 }
771 }
772}
773
774#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Clone)]
775#[serde(rename_all = "lowercase")]
776pub enum BorderStyle {
777 Thin,
778 Medium,
779 Thick,
780 Double,
781 Dotted,
782 SlantDashDot,
783 MediumDashed,
784 MediumDashDotDot,
785 MediumDashDot,
786}
787
788impl Display for BorderStyle {
789 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
790 match self {
791 BorderStyle::Thin => write!(formatter, "thin"),
792 BorderStyle::Thick => write!(formatter, "thick"),
793 BorderStyle::SlantDashDot => write!(formatter, "slantdashdot"),
794 BorderStyle::MediumDashed => write!(formatter, "mediumdashed"),
795 BorderStyle::MediumDashDotDot => write!(formatter, "mediumdashdotdot"),
796 BorderStyle::MediumDashDot => write!(formatter, "mediumdashdot"),
797 BorderStyle::Medium => write!(formatter, "medium"),
798 BorderStyle::Double => write!(formatter, "double"),
799 BorderStyle::Dotted => write!(formatter, "dotted"),
800 }
801 }
802}
803
804#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
805pub struct BorderItem {
806 pub style: BorderStyle,
807 #[serde(skip_serializing_if = "Color::is_none")]
808 #[serde(default)]
809 pub color: Color,
810}
811
812#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Default)]
813pub struct Border {
814 #[serde(default = "default_as_false")]
815 #[serde(skip_serializing_if = "is_false")]
816 pub diagonal_up: bool,
817 #[serde(default = "default_as_false")]
818 #[serde(skip_serializing_if = "is_false")]
819 pub diagonal_down: bool,
820 #[serde(skip_serializing_if = "Option::is_none")]
821 pub left: Option<BorderItem>,
822 #[serde(skip_serializing_if = "Option::is_none")]
823 pub right: Option<BorderItem>,
824 #[serde(skip_serializing_if = "Option::is_none")]
825 pub top: Option<BorderItem>,
826 #[serde(skip_serializing_if = "Option::is_none")]
827 pub bottom: Option<BorderItem>,
828 #[serde(skip_serializing_if = "Option::is_none")]
829 pub diagonal: Option<BorderItem>,
830}
831
832#[derive(Serialize, Deserialize, Debug, PartialEq)]
835pub struct SheetProperties {
836 pub name: String,
837 pub state: String,
838 pub sheet_id: u32,
839 #[serde(skip_serializing_if = "Color::is_none")]
840 #[serde(default)]
841 pub color: Color,
842}
843
844#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
845pub struct Theme {
846 pub name: String,
847 pub dk1: String,
848 pub lt1: String,
849 pub dk2: String,
850 pub lt2: String,
851 pub accent1: String,
852 pub accent2: String,
853 pub accent3: String,
854 pub accent4: String,
855 pub accent5: String,
856 pub accent6: String,
857 pub hlink: String,
858 pub fol_hlink: String,
859}
860
861impl Default for Theme {
862 fn default() -> Self {
863 Theme {
864 name: "Office".to_string(),
865 dk1: "#000000".to_string(),
866 lt1: "#FFFFFF".to_string(),
867 dk2: "#44546A".to_string(),
868 lt2: "#E7E6E6".to_string(),
869 accent1: "#4472C4".to_string(),
870 accent2: "#ED7D31".to_string(),
871 accent3: "#A5A5A5".to_string(),
872 accent4: "#FFC000".to_string(),
873 accent5: "#5B9BD5".to_string(),
874 accent6: "#70AD47".to_string(),
875 hlink: "#0563C1".to_string(),
876 fol_hlink: "#954F72".to_string(),
877 }
878 }
879}
880
881impl Theme {
882 pub fn resolve(&self, theme_index: i32, tint: f64) -> String {
885 use crate::colors::hex_with_tint_to_rgb;
886 let color = match theme_index {
887 0 => &self.lt1,
888 1 => &self.dk1,
889 2 => &self.lt2,
890 3 => &self.dk2,
891 4 => &self.accent1,
892 5 => &self.accent2,
893 6 => &self.accent3,
894 7 => &self.accent4,
895 8 => &self.accent5,
896 9 => &self.accent6,
897 10 => &self.hlink,
898 11 => &self.fol_hlink,
899 _ => &self.dk1,
900 };
901 hex_with_tint_to_rgb(color, tint)
902 }
903}
904
905#[cfg(test)]
906mod test {
907 use super::*;
908 #[test]
909 fn test_is_valid_hex_color() {
910 assert!(is_valid_hex_color("#000000"));
911 assert!(is_valid_hex_color("#ffffff"));
912
913 assert!(!is_valid_hex_color("000000"));
914 assert!(!is_valid_hex_color("ffffff"));
915
916 assert!(!is_valid_hex_color("#gggggg"));
917
918 assert!(!is_valid_hex_color("#ffffff "));
920 assert!(!is_valid_hex_color("#fff")); assert!(!is_valid_hex_color("#ffffff00")); }
923}