1use std::collections::BTreeMap;
6use std::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq, Default)]
10pub struct Color {
11 pub alpha: u8,
13 pub red: u8,
15 pub green: u8,
17 pub blue: u8,
19}
20
21impl Color {
22 pub fn new(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
24 Self {
25 alpha,
26 red,
27 green,
28 blue,
29 }
30 }
31
32 pub fn rgb(red: u8, green: u8, blue: u8) -> Self {
34 Self::new(255, red, green, blue)
35 }
36
37 pub fn from_argb(argb: u32) -> Self {
39 Self {
40 alpha: ((argb >> 24) & 0xFF) as u8,
41 red: ((argb >> 16) & 0xFF) as u8,
42 green: ((argb >> 8) & 0xFF) as u8,
43 blue: (argb & 0xFF) as u8,
44 }
45 }
46
47 pub fn to_argb(&self) -> u32 {
49 ((self.alpha as u32) << 24)
50 | ((self.red as u32) << 16)
51 | ((self.green as u32) << 8)
52 | (self.blue as u32)
53 }
54
55 pub fn is_black(&self) -> bool {
57 self.red == 0 && self.green == 0 && self.blue == 0
58 }
59
60 pub fn is_white(&self) -> bool {
62 self.red == 255 && self.green == 255 && self.blue == 255
63 }
64}
65
66impl fmt::Display for Color {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "#{:02X}{:02X}{:02X}", self.red, self.green, self.blue)
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Default)]
74pub enum BorderStyle {
75 #[default]
77 None,
78 Thin,
80 Medium,
82 Thick,
84 Double,
86 Hair,
88 Dashed,
90 Dotted,
92 MediumDashed,
94 DashDot,
96 DashDotDot,
98 SlantDashDot,
100}
101
102#[derive(Debug, Clone, Default, PartialEq)]
104pub struct Border {
105 pub style: BorderStyle,
107 pub color: Option<Color>,
109}
110
111impl Border {
112 pub fn new(style: BorderStyle) -> Self {
114 Self { style, color: None }
115 }
116
117 pub fn with_color(style: BorderStyle, color: Color) -> Self {
119 Self {
120 style,
121 color: Some(color),
122 }
123 }
124
125 pub fn is_visible(&self) -> bool {
127 self.style != BorderStyle::None
128 }
129}
130
131#[derive(Debug, Clone, Default, PartialEq)]
133pub struct Borders {
134 pub left: Border,
136 pub right: Border,
138 pub top: Border,
140 pub bottom: Border,
142 pub diagonal_down: Border,
144 pub diagonal_up: Border,
146}
147
148impl Borders {
149 pub fn new() -> Self {
151 Self::default()
152 }
153
154 pub fn has_visible_borders(&self) -> bool {
156 self.left.is_visible()
157 || self.right.is_visible()
158 || self.top.is_visible()
159 || self.bottom.is_visible()
160 || self.diagonal_down.is_visible()
161 || self.diagonal_up.is_visible()
162 }
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Default)]
167pub enum FontWeight {
168 #[default]
170 Normal,
171 Bold,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Default)]
177pub enum FontStyle {
178 #[default]
180 Normal,
181 Italic,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Default)]
187pub enum UnderlineStyle {
188 #[default]
190 None,
191 Single,
193 Double,
195 SingleAccounting,
197 DoubleAccounting,
199}
200
201#[derive(Debug, Clone, Default, PartialEq)]
203pub struct Font {
204 pub name: Option<String>,
206 pub size: Option<f64>,
208 pub weight: FontWeight,
210 pub style: FontStyle,
212 pub underline: UnderlineStyle,
214 pub strikethrough: bool,
216 pub color: Option<Color>,
218 pub family: Option<String>,
220}
221
222impl Font {
223 pub fn new() -> Self {
225 Self::default()
226 }
227
228 pub fn with_name(mut self, name: String) -> Self {
230 self.name = Some(name);
231 self
232 }
233
234 pub fn with_size(mut self, size: f64) -> Self {
236 self.size = Some(size);
237 self
238 }
239
240 pub fn with_weight(mut self, weight: FontWeight) -> Self {
242 self.weight = weight;
243 self
244 }
245
246 pub fn with_style(mut self, style: FontStyle) -> Self {
248 self.style = style;
249 self
250 }
251
252 pub fn with_underline(mut self, underline: UnderlineStyle) -> Self {
254 self.underline = underline;
255 self
256 }
257
258 pub fn with_strikethrough(mut self, strikethrough: bool) -> Self {
260 self.strikethrough = strikethrough;
261 self
262 }
263
264 pub fn with_color(mut self, color: Color) -> Self {
266 self.color = Some(color);
267 self
268 }
269
270 pub fn with_family(mut self, family: String) -> Self {
272 self.family = Some(family);
273 self
274 }
275
276 pub fn is_bold(&self) -> bool {
278 self.weight == FontWeight::Bold
279 }
280
281 pub fn is_italic(&self) -> bool {
283 self.style == FontStyle::Italic
284 }
285
286 pub fn has_underline(&self) -> bool {
288 self.underline != UnderlineStyle::None
289 }
290
291 pub fn has_strikethrough(&self) -> bool {
293 self.strikethrough
294 }
295}
296
297#[derive(Debug, Clone, PartialEq, Default)]
299pub struct TextRun {
300 pub text: String,
302 pub font: Option<Font>,
304}
305
306impl TextRun {
307 pub fn new(text: String) -> Self {
309 Self { text, font: None }
310 }
311
312 pub fn with_font(text: String, font: Font) -> Self {
314 Self {
315 text,
316 font: Some(font),
317 }
318 }
319
320 pub fn has_formatting(&self) -> bool {
322 self.font.is_some()
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Default)]
332pub struct RichText {
333 pub runs: Vec<TextRun>,
335}
336
337impl RichText {
338 pub fn new() -> Self {
340 Self::default()
341 }
342
343 pub fn from_runs(runs: Vec<TextRun>) -> Self {
345 Self { runs }
346 }
347
348 pub fn push(&mut self, run: TextRun) {
350 self.runs.push(run);
351 }
352
353 pub fn push_text(&mut self, text: String) {
355 self.runs.push(TextRun::new(text));
356 }
357
358 pub fn push_formatted(&mut self, text: String, font: Font) {
360 self.runs.push(TextRun::with_font(text, font));
361 }
362
363 pub fn plain_text(&self) -> String {
365 self.runs.iter().map(|r| r.text.as_str()).collect()
366 }
367
368 pub fn is_empty(&self) -> bool {
370 self.runs.is_empty() || self.runs.iter().all(|r| r.text.is_empty())
371 }
372
373 pub fn len(&self) -> usize {
375 self.runs.len()
376 }
377
378 pub fn has_formatting(&self) -> bool {
380 self.runs.iter().any(|r| r.has_formatting())
381 }
382
383 pub fn iter(&self) -> impl Iterator<Item = &TextRun> {
385 self.runs.iter()
386 }
387}
388
389impl std::fmt::Display for RichText {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 write!(f, "{}", self.plain_text())
392 }
393}
394
395#[derive(Debug, Clone, Copy, PartialEq, Default)]
397pub enum HorizontalAlignment {
398 Left,
400 Center,
402 Right,
404 Justify,
406 Distributed,
408 Fill,
410 #[default]
412 General,
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Default)]
417pub enum VerticalAlignment {
418 Top,
420 Center,
422 #[default]
424 Bottom,
425 Justify,
427 Distributed,
429}
430
431#[derive(Debug, Clone, Copy, PartialEq, Default)]
433pub enum TextRotation {
434 #[default]
436 None,
437 Degrees(u16),
439 Stacked,
441}
442
443#[derive(Debug, Clone, Default, PartialEq)]
445pub struct Alignment {
446 pub horizontal: HorizontalAlignment,
448 pub vertical: VerticalAlignment,
450 pub text_rotation: TextRotation,
452 pub wrap_text: bool,
454 pub indent: Option<u8>,
456 pub shrink_to_fit: bool,
458}
459
460impl Alignment {
461 pub fn new() -> Self {
463 Self::default()
464 }
465
466 pub fn with_horizontal(mut self, horizontal: HorizontalAlignment) -> Self {
468 self.horizontal = horizontal;
469 self
470 }
471
472 pub fn with_vertical(mut self, vertical: VerticalAlignment) -> Self {
474 self.vertical = vertical;
475 self
476 }
477
478 pub fn with_text_rotation(mut self, rotation: TextRotation) -> Self {
480 self.text_rotation = rotation;
481 self
482 }
483
484 pub fn with_wrap_text(mut self, wrap: bool) -> Self {
486 self.wrap_text = wrap;
487 self
488 }
489
490 pub fn with_indent(mut self, indent: u8) -> Self {
492 self.indent = Some(indent);
493 self
494 }
495
496 pub fn with_shrink_to_fit(mut self, shrink: bool) -> Self {
498 self.shrink_to_fit = shrink;
499 self
500 }
501}
502
503#[derive(Debug, Clone, Copy, PartialEq, Default)]
505pub enum FillPattern {
506 #[default]
508 None,
509 Solid,
511 DarkGray,
513 MediumGray,
515 LightGray,
517 Gray125,
519 Gray0625,
521 DarkHorizontal,
523 DarkVertical,
525 DarkDown,
527 DarkUp,
529 DarkGrid,
531 DarkTrellis,
533 LightHorizontal,
535 LightVertical,
537 LightDown,
539 LightUp,
541 LightGrid,
543 LightTrellis,
545}
546
547#[derive(Debug, Clone, Default, PartialEq)]
549pub struct Fill {
550 pub pattern: FillPattern,
552 pub foreground_color: Option<Color>,
554 pub background_color: Option<Color>,
556}
557
558impl Fill {
559 pub fn new() -> Self {
561 Self::default()
562 }
563
564 pub fn solid(color: Color) -> Self {
566 Self {
567 pattern: FillPattern::Solid,
568 foreground_color: Some(color),
569 background_color: None,
570 }
571 }
572
573 pub fn with_pattern(mut self, pattern: FillPattern) -> Self {
575 self.pattern = pattern;
576 self
577 }
578
579 pub fn with_foreground_color(mut self, color: Color) -> Self {
581 self.foreground_color = Some(color);
582 self
583 }
584
585 pub fn with_background_color(mut self, color: Color) -> Self {
587 self.background_color = Some(color);
588 self
589 }
590
591 pub fn is_visible(&self) -> bool {
593 self.pattern != FillPattern::None
594 }
595
596 pub fn get_color(&self) -> Option<Color> {
598 self.foreground_color.or(self.background_color)
599 }
600}
601
602#[derive(Debug, Clone, PartialEq)]
604pub struct NumberFormat {
605 pub format_code: String,
607 pub format_id: Option<u32>,
609}
610
611impl NumberFormat {
612 pub fn new(format_code: String) -> Self {
614 Self {
615 format_code,
616 format_id: None,
617 }
618 }
619
620 pub fn with_id(mut self, format_id: u32) -> Self {
622 self.format_id = Some(format_id);
623 self
624 }
625}
626
627impl Default for NumberFormat {
628 fn default() -> Self {
629 Self {
630 format_code: "General".to_string(),
631 format_id: None,
632 }
633 }
634}
635
636#[derive(Debug, Clone, Default, PartialEq)]
638pub struct Protection {
639 pub locked: bool,
641 pub hidden: bool,
643}
644
645impl Protection {
646 pub fn new() -> Self {
648 Self::default()
649 }
650
651 pub fn with_locked(mut self, locked: bool) -> Self {
653 self.locked = locked;
654 self
655 }
656
657 pub fn with_hidden(mut self, hidden: bool) -> Self {
659 self.hidden = hidden;
660 self
661 }
662}
663
664#[derive(Debug, Clone, PartialEq)]
666pub struct ColumnWidth {
667 pub column: u32,
669 pub width: f64,
671 pub custom_width: bool,
673 pub hidden: bool,
675 pub best_fit: bool,
677}
678
679impl ColumnWidth {
680 pub fn new(column: u32, width: f64) -> Self {
682 Self {
683 column,
684 width,
685 custom_width: false,
686 hidden: false,
687 best_fit: false,
688 }
689 }
690
691 pub fn with_custom_width(mut self, custom: bool) -> Self {
693 self.custom_width = custom;
694 self
695 }
696
697 pub fn with_hidden(mut self, hidden: bool) -> Self {
699 self.hidden = hidden;
700 self
701 }
702
703 pub fn with_best_fit(mut self, best_fit: bool) -> Self {
705 self.best_fit = best_fit;
706 self
707 }
708
709 pub fn is_visible(&self) -> bool {
711 !self.hidden
712 }
713}
714
715#[derive(Debug, Clone, PartialEq)]
717pub struct RowHeight {
718 pub row: u32,
720 pub height: f64,
722 pub custom_height: bool,
724 pub hidden: bool,
726 pub thick_top: bool,
728 pub thick_bottom: bool,
730}
731
732impl RowHeight {
733 pub fn new(row: u32, height: f64) -> Self {
735 Self {
736 row,
737 height,
738 custom_height: false,
739 hidden: false,
740 thick_top: false,
741 thick_bottom: false,
742 }
743 }
744
745 pub fn with_custom_height(mut self, custom: bool) -> Self {
747 self.custom_height = custom;
748 self
749 }
750
751 pub fn with_hidden(mut self, hidden: bool) -> Self {
753 self.hidden = hidden;
754 self
755 }
756
757 pub fn with_thick_top(mut self, thick_top: bool) -> Self {
759 self.thick_top = thick_top;
760 self
761 }
762
763 pub fn with_thick_bottom(mut self, thick_bottom: bool) -> Self {
765 self.thick_bottom = thick_bottom;
766 self
767 }
768
769 pub fn is_visible(&self) -> bool {
771 !self.hidden
772 }
773}
774
775#[derive(Debug, Clone, Default, PartialEq)]
777pub struct WorksheetLayout {
778 pub column_widths: BTreeMap<u32, ColumnWidth>,
780 pub row_heights: BTreeMap<u32, RowHeight>,
782 pub default_column_width: Option<f64>,
784 pub default_row_height: Option<f64>,
786}
787
788impl WorksheetLayout {
789 pub fn new() -> Self {
791 Self::default()
792 }
793
794 pub fn add_column_width(mut self, column_width: ColumnWidth) -> Self {
796 self.column_widths.insert(column_width.column, column_width);
797 self
798 }
799
800 pub fn add_row_height(mut self, row_height: RowHeight) -> Self {
802 self.row_heights.insert(row_height.row, row_height);
803 self
804 }
805
806 pub fn with_default_column_width(mut self, width: f64) -> Self {
808 self.default_column_width = Some(width);
809 self
810 }
811
812 pub fn with_default_row_height(mut self, height: f64) -> Self {
814 self.default_row_height = Some(height);
815 self
816 }
817
818 pub fn get_column_width(&self, column: u32) -> Option<&ColumnWidth> {
820 self.column_widths.get(&column)
821 }
822
823 pub fn get_row_height(&self, row: u32) -> Option<&RowHeight> {
825 self.row_heights.get(&row)
826 }
827
828 pub fn get_effective_column_width(&self, column: u32) -> f64 {
839 self.get_column_width(column)
840 .map(|cw| cw.width)
841 .or(self.default_column_width)
842 .unwrap_or(8.43)
843 }
844
845 pub fn get_effective_row_height(&self, row: u32) -> f64 {
854 self.get_row_height(row)
855 .map(|rh| rh.height)
856 .or(self.default_row_height)
857 .unwrap_or(15.0)
858 }
859
860 pub fn has_custom_dimensions(&self) -> bool {
862 !self.column_widths.is_empty()
863 || !self.row_heights.is_empty()
864 || self.default_column_width.is_some()
865 || self.default_row_height.is_some()
866 }
867}
868
869#[derive(Debug, Clone, Default, PartialEq)]
871pub struct Style {
872 pub font: Option<Font>,
874 pub fill: Option<Fill>,
876 pub borders: Option<Borders>,
878 pub alignment: Option<Alignment>,
880 pub number_format: Option<NumberFormat>,
882 pub protection: Option<Protection>,
884 pub style_id: Option<u32>,
886}
887
888impl Style {
889 pub fn new() -> Self {
891 Self::default()
892 }
893
894 pub fn with_font(mut self, font: Font) -> Self {
896 self.font = Some(font);
897 self
898 }
899
900 pub fn with_fill(mut self, fill: Fill) -> Self {
902 self.fill = Some(fill);
903 self
904 }
905
906 pub fn with_borders(mut self, borders: Borders) -> Self {
908 self.borders = Some(borders);
909 self
910 }
911
912 pub fn with_alignment(mut self, alignment: Alignment) -> Self {
914 self.alignment = Some(alignment);
915 self
916 }
917
918 pub fn with_number_format(mut self, number_format: NumberFormat) -> Self {
920 self.number_format = Some(number_format);
921 self
922 }
923
924 pub fn with_protection(mut self, protection: Protection) -> Self {
926 self.protection = Some(protection);
927 self
928 }
929
930 pub fn with_style_id(mut self, style_id: u32) -> Self {
932 self.style_id = Some(style_id);
933 self
934 }
935
936 pub fn get_font(&self) -> Option<&Font> {
938 self.font.as_ref()
939 }
940
941 pub fn get_fill(&self) -> Option<&Fill> {
943 self.fill.as_ref()
944 }
945
946 pub fn get_borders(&self) -> Option<&Borders> {
948 self.borders.as_ref()
949 }
950
951 pub fn get_alignment(&self) -> Option<&Alignment> {
953 self.alignment.as_ref()
954 }
955
956 pub fn get_number_format(&self) -> Option<&NumberFormat> {
958 self.number_format.as_ref()
959 }
960
961 pub fn get_protection(&self) -> Option<&Protection> {
963 self.protection.as_ref()
964 }
965
966 pub fn is_empty(&self) -> bool {
968 self.font.is_none()
969 && self.fill.is_none()
970 && self.borders.is_none()
971 && self.alignment.is_none()
972 && self.number_format.is_none()
973 && self.protection.is_none()
974 }
975
976 pub fn has_visible_properties(&self) -> bool {
978 (self
979 .font
980 .as_ref()
981 .is_some_and(|f| f.color.is_some() || f.is_bold() || f.is_italic()))
982 || (self.fill.as_ref().is_some_and(|f| f.is_visible()))
983 || (self
984 .borders
985 .as_ref()
986 .is_some_and(|b| b.has_visible_borders()))
987 || (self.alignment.as_ref().is_some_and(|a| {
988 a.horizontal != HorizontalAlignment::General
989 || a.vertical != VerticalAlignment::Bottom
990 || a.text_rotation != TextRotation::None
991 || a.wrap_text
992 || a.indent.is_some()
993 || a.shrink_to_fit
994 }))
995 }
996}
997
998#[derive(Debug, Clone)]
1000struct StyleRun {
1001 style_id: u16,
1003 count: u32,
1005}
1006
1007#[derive(Debug, Clone, Default)]
1016pub struct StyleRange {
1017 start: (u32, u32),
1018 end: (u32, u32),
1019 palette: Vec<Style>,
1021 runs: Vec<StyleRun>,
1023 total_cells: u64,
1025}
1026
1027impl StyleRange {
1028 pub fn empty() -> Self {
1030 Self::default()
1031 }
1032
1033 pub fn from_style_ids(cells: Vec<(u32, u32, usize)>, palette: Vec<Style>) -> Self {
1040 if cells.is_empty() {
1041 return Self::empty();
1042 }
1043
1044 let mut row_start = u32::MAX;
1046 let mut row_end = 0;
1047 let mut col_start = u32::MAX;
1048 let mut col_end = 0;
1049 for (r, c, _) in &cells {
1050 row_start = row_start.min(*r);
1051 row_end = row_end.max(*r);
1052 col_start = col_start.min(*c);
1053 col_end = col_end.max(*c);
1054 }
1055
1056 let width = (col_end - col_start + 1) as usize;
1057 let height = (row_end - row_start + 1) as usize;
1058 let total_cells = (width * height) as u64;
1059
1060 let mut style_ids = vec![0u16; width * height];
1062
1063 for (r, c, style_id) in cells {
1064 let row = (r - row_start) as usize;
1065 let col = (c - col_start) as usize;
1066 let idx = row * width + col;
1067 style_ids[idx] = style_id.min(u16::MAX as usize) as u16;
1069 }
1070
1071 let mut runs = Vec::new();
1073 if !style_ids.is_empty() {
1074 let mut current_style = style_ids[0];
1075 let mut count = 1u32;
1076
1077 for &style_id in &style_ids[1..] {
1078 if style_id == current_style {
1079 count += 1;
1080 } else {
1081 runs.push(StyleRun {
1082 style_id: current_style,
1083 count,
1084 });
1085 current_style = style_id;
1086 count = 1;
1087 }
1088 }
1089 runs.push(StyleRun {
1090 style_id: current_style,
1091 count,
1092 });
1093 }
1094
1095 runs.shrink_to_fit();
1096
1097 StyleRange {
1098 start: (row_start, col_start),
1099 end: (row_end, col_end),
1100 palette,
1101 runs,
1102 total_cells,
1103 }
1104 }
1105
1106 pub fn from_sparse(cells: Vec<(u32, u32, Style)>) -> Self {
1110 if cells.is_empty() {
1111 return Self::empty();
1112 }
1113
1114 let mut row_start = u32::MAX;
1116 let mut row_end = 0;
1117 let mut col_start = u32::MAX;
1118 let mut col_end = 0;
1119 for (r, c, _) in &cells {
1120 row_start = row_start.min(*r);
1121 row_end = row_end.max(*r);
1122 col_start = col_start.min(*c);
1123 col_end = col_end.max(*c);
1124 }
1125
1126 let width = (col_end - col_start + 1) as usize;
1127 let height = (row_end - row_start + 1) as usize;
1128 let total_cells = (width * height) as u64;
1129
1130 let mut palette: Vec<Style> = vec![Style::default()]; let mut style_to_id: std::collections::HashMap<u32, u16> = std::collections::HashMap::new();
1134
1135 let mut style_ids = vec![0u16; width * height];
1137
1138 for (r, c, style) in cells {
1139 let row = (r - row_start) as usize;
1140 let col = (c - col_start) as usize;
1141 let idx = row * width + col;
1142
1143 if style.is_empty() {
1144 continue; }
1146
1147 let excel_style_id = style.style_id.unwrap_or_else(|| {
1150 palette.len() as u32
1152 });
1153
1154 let style_id = if let Some(&id) = style_to_id.get(&excel_style_id) {
1155 id
1156 } else {
1157 let id = palette.len() as u16;
1158 palette.push(style);
1159 style_to_id.insert(excel_style_id, id);
1160 id
1161 };
1162
1163 style_ids[idx] = style_id;
1164 }
1165
1166 let mut runs = Vec::new();
1168 if !style_ids.is_empty() {
1169 let mut current_style = style_ids[0];
1170 let mut count = 1u32;
1171
1172 for &style_id in &style_ids[1..] {
1173 if style_id == current_style {
1174 count += 1;
1175 } else {
1176 runs.push(StyleRun {
1177 style_id: current_style,
1178 count,
1179 });
1180 current_style = style_id;
1181 count = 1;
1182 }
1183 }
1184 runs.push(StyleRun {
1186 style_id: current_style,
1187 count,
1188 });
1189 }
1190
1191 runs.shrink_to_fit();
1192 palette.shrink_to_fit();
1193
1194 StyleRange {
1195 start: (row_start, col_start),
1196 end: (row_end, col_end),
1197 palette,
1198 runs,
1199 total_cells,
1200 }
1201 }
1202
1203 pub fn start(&self) -> Option<(u32, u32)> {
1205 if self.is_empty() {
1206 None
1207 } else {
1208 Some(self.start)
1209 }
1210 }
1211
1212 pub fn end(&self) -> Option<(u32, u32)> {
1214 if self.is_empty() {
1215 None
1216 } else {
1217 Some(self.end)
1218 }
1219 }
1220
1221 pub fn is_empty(&self) -> bool {
1223 self.runs.is_empty()
1224 }
1225
1226 pub fn width(&self) -> usize {
1228 if self.is_empty() {
1229 0
1230 } else {
1231 (self.end.1 - self.start.1 + 1) as usize
1232 }
1233 }
1234
1235 pub fn height(&self) -> usize {
1237 if self.is_empty() {
1238 0
1239 } else {
1240 (self.end.0 - self.start.0 + 1) as usize
1241 }
1242 }
1243
1244 pub fn get(&self, pos: (usize, usize)) -> Option<&Style> {
1248 let width = self.width();
1249 let height = self.height();
1250
1251 if pos.0 >= height || pos.1 >= width {
1252 return None;
1253 }
1254
1255 let linear_idx = pos.0 * width + pos.1;
1256 let style_id = self.style_id_at(linear_idx)?;
1257 self.palette.get(style_id as usize)
1258 }
1259
1260 fn style_id_at(&self, linear_idx: usize) -> Option<u16> {
1262 let mut offset = 0usize;
1263 for run in &self.runs {
1264 let run_end = offset + run.count as usize;
1265 if linear_idx < run_end {
1266 return Some(run.style_id);
1267 }
1268 offset = run_end;
1269 }
1270 None
1271 }
1272
1273 pub fn cells(&self) -> StyleRangeCells<'_> {
1275 StyleRangeCells {
1276 range: self,
1277 run_idx: 0,
1278 run_offset: 0,
1279 linear_idx: 0,
1280 }
1281 }
1282
1283 pub fn unique_style_count(&self) -> usize {
1285 self.palette.len().saturating_sub(1)
1286 }
1287
1288 pub fn run_count(&self) -> usize {
1290 self.runs.len()
1291 }
1292
1293 pub fn compression_ratio(&self) -> f64 {
1295 if self.runs.is_empty() {
1296 0.0
1297 } else {
1298 self.total_cells as f64 / self.runs.len() as f64
1299 }
1300 }
1301}
1302
1303pub struct StyleRangeCells<'a> {
1305 range: &'a StyleRange,
1306 run_idx: usize,
1307 run_offset: u32,
1308 linear_idx: u64,
1309}
1310
1311impl<'a> Iterator for StyleRangeCells<'a> {
1312 type Item = (usize, usize, &'a Style);
1313
1314 fn next(&mut self) -> Option<Self::Item> {
1315 if self.run_idx >= self.range.runs.len() {
1316 return None;
1317 }
1318
1319 let width = self.range.width();
1320 if width == 0 {
1321 return None;
1322 }
1323
1324 let row = (self.linear_idx / width as u64) as usize;
1325 let col = (self.linear_idx % width as u64) as usize;
1326
1327 let run = &self.range.runs[self.run_idx];
1328 let style = self.range.palette.get(run.style_id as usize)?;
1329
1330 self.linear_idx += 1;
1331 self.run_offset += 1;
1332
1333 if self.run_offset >= run.count {
1334 self.run_idx += 1;
1335 self.run_offset = 0;
1336 }
1337
1338 Some((row, col, style))
1339 }
1340}
1341
1342#[cfg(test)]
1343mod tests {
1344 use super::*;
1345
1346 #[test]
1347 fn test_color() {
1348 let color = Color::rgb(255, 0, 128);
1349 assert_eq!(color.red, 255);
1350 assert_eq!(color.green, 0);
1351 assert_eq!(color.blue, 128);
1352 assert_eq!(color.alpha, 255);
1353 assert_eq!(color.to_string(), "#FF0080");
1354 }
1355
1356 #[test]
1357 fn test_font() {
1358 let font = Font::new()
1359 .with_name("Arial".to_string())
1360 .with_size(12.0)
1361 .with_weight(FontWeight::Bold)
1362 .with_color(Color::rgb(255, 0, 0));
1363
1364 assert_eq!(font.name, Some("Arial".to_string()));
1365 assert_eq!(font.size, Some(12.0));
1366 assert!(font.is_bold());
1367 assert_eq!(font.color, Some(Color::rgb(255, 0, 0)));
1368 }
1369
1370 #[test]
1371 fn test_style() {
1372 let style = Style::new()
1373 .with_font(Font::new().with_name("Arial".to_string()))
1374 .with_fill(Fill::solid(Color::rgb(255, 255, 0)));
1375
1376 assert!(!style.is_empty());
1377 assert!(style.get_font().is_some());
1378 assert!(style.get_fill().is_some());
1379 }
1380
1381 #[test]
1382 fn test_border_with_color() {
1383 let border = Border::with_color(BorderStyle::Thin, Color::rgb(255, 0, 0));
1384 assert_eq!(border.style, BorderStyle::Thin);
1385 assert_eq!(border.color, Some(Color::rgb(255, 0, 0)));
1386 assert!(border.is_visible());
1387 }
1388
1389 #[test]
1390 fn test_border_without_color() {
1391 let border = Border::new(BorderStyle::Medium);
1392 assert_eq!(border.style, BorderStyle::Medium);
1393 assert_eq!(border.color, None);
1394 assert!(border.is_visible());
1395 }
1396
1397 #[test]
1398 fn test_borders_with_mixed_colors() {
1399 let mut borders = Borders::new();
1400 borders.left = Border::with_color(BorderStyle::Thin, Color::rgb(255, 0, 0));
1401 borders.right = Border::new(BorderStyle::Medium);
1402 borders.top = Border::with_color(BorderStyle::Thick, Color::rgb(0, 255, 0));
1403
1404 assert_eq!(borders.left.color, Some(Color::rgb(255, 0, 0)));
1405 assert_eq!(borders.right.color, None);
1406 assert_eq!(borders.top.color, Some(Color::rgb(0, 255, 0)));
1407 assert!(borders.has_visible_borders());
1408 }
1409
1410 #[test]
1411 fn test_column_width() {
1412 let column_width = ColumnWidth::new(5, 12.5)
1413 .with_custom_width(true)
1414 .with_hidden(false)
1415 .with_best_fit(true);
1416
1417 assert_eq!(column_width.column, 5);
1418 assert_eq!(column_width.width, 12.5);
1419 assert!(column_width.custom_width);
1420 assert!(!column_width.hidden);
1421 assert!(column_width.best_fit);
1422 assert!(column_width.is_visible());
1423 }
1424
1425 #[test]
1426 fn test_row_height() {
1427 let row_height = RowHeight::new(10, 20.0)
1428 .with_custom_height(true)
1429 .with_hidden(false)
1430 .with_thick_top(true)
1431 .with_thick_bottom(false);
1432
1433 assert_eq!(row_height.row, 10);
1434 assert_eq!(row_height.height, 20.0);
1435 assert!(row_height.custom_height);
1436 assert!(!row_height.hidden);
1437 assert!(row_height.thick_top);
1438 assert!(!row_height.thick_bottom);
1439 assert!(row_height.is_visible());
1440 }
1441
1442 #[test]
1443 fn test_worksheet_layout() {
1444 let layout = WorksheetLayout::new()
1445 .add_column_width(ColumnWidth::new(0, 10.0))
1446 .add_column_width(ColumnWidth::new(1, 15.0))
1447 .add_row_height(RowHeight::new(0, 18.0))
1448 .add_row_height(RowHeight::new(1, 22.0))
1449 .with_default_column_width(8.43)
1450 .with_default_row_height(15.0);
1451
1452 assert_eq!(layout.column_widths.len(), 2);
1453 assert_eq!(layout.row_heights.len(), 2);
1454 assert_eq!(layout.default_column_width, Some(8.43));
1455 assert_eq!(layout.default_row_height, Some(15.0));
1456 assert!(layout.has_custom_dimensions());
1457
1458 let col_width = layout.get_column_width(0).unwrap();
1460 assert_eq!(col_width.width, 10.0);
1461
1462 let row_height = layout.get_row_height(1).unwrap();
1464 assert_eq!(row_height.height, 22.0);
1465
1466 assert_eq!(layout.get_effective_column_width(0), 10.0); assert_eq!(layout.get_effective_column_width(5), 8.43); assert_eq!(layout.get_effective_row_height(0), 18.0); assert_eq!(layout.get_effective_row_height(5), 15.0); }
1472
1473 #[test]
1474 fn test_worksheet_layout_defaults() {
1475 let layout = WorksheetLayout::new();
1476
1477 assert!(!layout.has_custom_dimensions());
1478 assert_eq!(layout.get_effective_column_width(0), 8.43); assert_eq!(layout.get_effective_row_height(0), 15.0); }
1481}