1use sheetkit_xml::styles::{
9 Alignment, Border, BorderSide, Borders, Color, Fill, Fills, Font, Fonts, NumFmt, NumFmts,
10 PatternFill, Protection, StyleSheet, Xf,
11};
12
13use crate::error::{Error, Result};
14
15const MAX_CELL_XFS: usize = 65430;
17
18const CUSTOM_NUM_FMT_BASE: u32 = 164;
20
21pub mod builtin_num_fmts {
23 pub const GENERAL: u32 = 0;
25 pub const INTEGER: u32 = 1;
27 pub const DECIMAL_2: u32 = 2;
29 pub const THOUSANDS: u32 = 3;
31 pub const THOUSANDS_DECIMAL: u32 = 4;
33 pub const PERCENT: u32 = 9;
35 pub const PERCENT_DECIMAL: u32 = 10;
37 pub const SCIENTIFIC: u32 = 11;
39 pub const DATE_MDY: u32 = 14;
41 pub const DATE_DMY: u32 = 15;
43 pub const DATE_DM: u32 = 16;
45 pub const DATE_MY: u32 = 17;
47 pub const TIME_HM_AP: u32 = 18;
49 pub const TIME_HMS_AP: u32 = 19;
51 pub const TIME_HM: u32 = 20;
53 pub const TIME_HMS: u32 = 21;
55 pub const DATETIME: u32 = 22;
57 pub const TEXT: u32 = 49;
59}
60
61#[derive(Debug, Clone, Default)]
63pub struct Style {
64 pub font: Option<FontStyle>,
65 pub fill: Option<FillStyle>,
66 pub border: Option<BorderStyle>,
67 pub alignment: Option<AlignmentStyle>,
68 pub num_fmt: Option<NumFmtStyle>,
69 pub protection: Option<ProtectionStyle>,
70}
71
72#[derive(Debug, Clone, Default)]
74pub struct FontStyle {
75 pub name: Option<String>,
77 pub size: Option<f64>,
79 pub bold: bool,
81 pub italic: bool,
83 pub underline: bool,
85 pub strikethrough: bool,
87 pub color: Option<StyleColor>,
89}
90
91#[derive(Debug, Clone, PartialEq)]
93pub enum StyleColor {
94 Rgb(String),
96 Theme(u32),
98 Indexed(u32),
100}
101
102#[derive(Debug, Clone)]
104pub struct FillStyle {
105 pub pattern: PatternType,
107 pub fg_color: Option<StyleColor>,
109 pub bg_color: Option<StyleColor>,
111 pub gradient: Option<GradientFillStyle>,
113}
114
115#[derive(Debug, Clone)]
117pub struct GradientFillStyle {
118 pub gradient_type: GradientType,
120 pub degree: Option<f64>,
122 pub left: Option<f64>,
124 pub right: Option<f64>,
126 pub top: Option<f64>,
128 pub bottom: Option<f64>,
130 pub stops: Vec<GradientStop>,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq)]
136pub enum GradientType {
137 Linear,
138 Path,
139}
140
141impl GradientType {
142 fn from_str(s: &str) -> Self {
143 match s {
144 "path" => GradientType::Path,
145 _ => GradientType::Linear,
146 }
147 }
148}
149
150#[derive(Debug, Clone)]
152pub struct GradientStop {
153 pub position: f64,
155 pub color: StyleColor,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq)]
161pub enum PatternType {
162 None,
163 Solid,
164 Gray125,
165 DarkGray,
166 MediumGray,
167 LightGray,
168}
169
170impl PatternType {
171 fn as_str(&self) -> &str {
172 match self {
173 PatternType::None => "none",
174 PatternType::Solid => "solid",
175 PatternType::Gray125 => "gray125",
176 PatternType::DarkGray => "darkGray",
177 PatternType::MediumGray => "mediumGray",
178 PatternType::LightGray => "lightGray",
179 }
180 }
181
182 fn from_str(s: &str) -> Self {
183 match s {
184 "none" => PatternType::None,
185 "solid" => PatternType::Solid,
186 "gray125" => PatternType::Gray125,
187 "darkGray" => PatternType::DarkGray,
188 "mediumGray" => PatternType::MediumGray,
189 "lightGray" => PatternType::LightGray,
190 _ => PatternType::None,
191 }
192 }
193}
194
195#[derive(Debug, Clone, Default)]
197pub struct BorderStyle {
198 pub left: Option<BorderSideStyle>,
199 pub right: Option<BorderSideStyle>,
200 pub top: Option<BorderSideStyle>,
201 pub bottom: Option<BorderSideStyle>,
202 pub diagonal: Option<BorderSideStyle>,
203}
204
205#[derive(Debug, Clone)]
207pub struct BorderSideStyle {
208 pub style: BorderLineStyle,
209 pub color: Option<StyleColor>,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq)]
214pub enum BorderLineStyle {
215 Thin,
216 Medium,
217 Thick,
218 Dashed,
219 Dotted,
220 Double,
221 Hair,
222 MediumDashed,
223 DashDot,
224 MediumDashDot,
225 DashDotDot,
226 MediumDashDotDot,
227 SlantDashDot,
228}
229
230impl BorderLineStyle {
231 fn as_str(&self) -> &str {
232 match self {
233 BorderLineStyle::Thin => "thin",
234 BorderLineStyle::Medium => "medium",
235 BorderLineStyle::Thick => "thick",
236 BorderLineStyle::Dashed => "dashed",
237 BorderLineStyle::Dotted => "dotted",
238 BorderLineStyle::Double => "double",
239 BorderLineStyle::Hair => "hair",
240 BorderLineStyle::MediumDashed => "mediumDashed",
241 BorderLineStyle::DashDot => "dashDot",
242 BorderLineStyle::MediumDashDot => "mediumDashDot",
243 BorderLineStyle::DashDotDot => "dashDotDot",
244 BorderLineStyle::MediumDashDotDot => "mediumDashDotDot",
245 BorderLineStyle::SlantDashDot => "slantDashDot",
246 }
247 }
248
249 fn from_str(s: &str) -> Option<Self> {
250 match s {
251 "thin" => Some(BorderLineStyle::Thin),
252 "medium" => Some(BorderLineStyle::Medium),
253 "thick" => Some(BorderLineStyle::Thick),
254 "dashed" => Some(BorderLineStyle::Dashed),
255 "dotted" => Some(BorderLineStyle::Dotted),
256 "double" => Some(BorderLineStyle::Double),
257 "hair" => Some(BorderLineStyle::Hair),
258 "mediumDashed" => Some(BorderLineStyle::MediumDashed),
259 "dashDot" => Some(BorderLineStyle::DashDot),
260 "mediumDashDot" => Some(BorderLineStyle::MediumDashDot),
261 "dashDotDot" => Some(BorderLineStyle::DashDotDot),
262 "mediumDashDotDot" => Some(BorderLineStyle::MediumDashDotDot),
263 "slantDashDot" => Some(BorderLineStyle::SlantDashDot),
264 _ => None,
265 }
266 }
267}
268
269#[derive(Debug, Clone, Default)]
271pub struct AlignmentStyle {
272 pub horizontal: Option<HorizontalAlign>,
273 pub vertical: Option<VerticalAlign>,
274 pub wrap_text: bool,
275 pub text_rotation: Option<u32>,
276 pub indent: Option<u32>,
277 pub shrink_to_fit: bool,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq)]
282pub enum HorizontalAlign {
283 General,
284 Left,
285 Center,
286 Right,
287 Fill,
288 Justify,
289 CenterContinuous,
290 Distributed,
291}
292
293impl HorizontalAlign {
294 fn as_str(&self) -> &str {
295 match self {
296 HorizontalAlign::General => "general",
297 HorizontalAlign::Left => "left",
298 HorizontalAlign::Center => "center",
299 HorizontalAlign::Right => "right",
300 HorizontalAlign::Fill => "fill",
301 HorizontalAlign::Justify => "justify",
302 HorizontalAlign::CenterContinuous => "centerContinuous",
303 HorizontalAlign::Distributed => "distributed",
304 }
305 }
306
307 fn from_str(s: &str) -> Option<Self> {
308 match s {
309 "general" => Some(HorizontalAlign::General),
310 "left" => Some(HorizontalAlign::Left),
311 "center" => Some(HorizontalAlign::Center),
312 "right" => Some(HorizontalAlign::Right),
313 "fill" => Some(HorizontalAlign::Fill),
314 "justify" => Some(HorizontalAlign::Justify),
315 "centerContinuous" => Some(HorizontalAlign::CenterContinuous),
316 "distributed" => Some(HorizontalAlign::Distributed),
317 _ => None,
318 }
319 }
320}
321
322#[derive(Debug, Clone, Copy, PartialEq)]
324pub enum VerticalAlign {
325 Top,
326 Center,
327 Bottom,
328 Justify,
329 Distributed,
330}
331
332impl VerticalAlign {
333 fn as_str(&self) -> &str {
334 match self {
335 VerticalAlign::Top => "top",
336 VerticalAlign::Center => "center",
337 VerticalAlign::Bottom => "bottom",
338 VerticalAlign::Justify => "justify",
339 VerticalAlign::Distributed => "distributed",
340 }
341 }
342
343 fn from_str(s: &str) -> Option<Self> {
344 match s {
345 "top" => Some(VerticalAlign::Top),
346 "center" => Some(VerticalAlign::Center),
347 "bottom" => Some(VerticalAlign::Bottom),
348 "justify" => Some(VerticalAlign::Justify),
349 "distributed" => Some(VerticalAlign::Distributed),
350 _ => None,
351 }
352 }
353}
354
355#[derive(Debug, Clone)]
357pub enum NumFmtStyle {
358 Builtin(u32),
360 Custom(String),
362}
363
364#[derive(Debug, Clone)]
366pub struct ProtectionStyle {
367 pub locked: bool,
368 pub hidden: bool,
369}
370
371pub struct StyleBuilder {
376 style: Style,
377}
378
379impl StyleBuilder {
380 pub fn new() -> Self {
382 Self {
383 style: Style::default(),
384 }
385 }
386
387 pub fn bold(mut self, bold: bool) -> Self {
391 self.style.font.get_or_insert_with(FontStyle::default).bold = bold;
392 self
393 }
394
395 pub fn italic(mut self, italic: bool) -> Self {
397 self.style
398 .font
399 .get_or_insert_with(FontStyle::default)
400 .italic = italic;
401 self
402 }
403
404 pub fn underline(mut self, underline: bool) -> Self {
406 self.style
407 .font
408 .get_or_insert_with(FontStyle::default)
409 .underline = underline;
410 self
411 }
412
413 pub fn strikethrough(mut self, strikethrough: bool) -> Self {
415 self.style
416 .font
417 .get_or_insert_with(FontStyle::default)
418 .strikethrough = strikethrough;
419 self
420 }
421
422 pub fn font_name(mut self, name: &str) -> Self {
424 self.style.font.get_or_insert_with(FontStyle::default).name = Some(name.to_string());
425 self
426 }
427
428 pub fn font_size(mut self, size: f64) -> Self {
430 self.style.font.get_or_insert_with(FontStyle::default).size = Some(size);
431 self
432 }
433
434 pub fn font_color(mut self, color: StyleColor) -> Self {
436 self.style.font.get_or_insert_with(FontStyle::default).color = Some(color);
437 self
438 }
439
440 pub fn font_color_rgb(self, rgb: &str) -> Self {
442 self.font_color(StyleColor::Rgb(rgb.to_string()))
443 }
444
445 pub fn fill_pattern(mut self, pattern: PatternType) -> Self {
449 self.style
450 .fill
451 .get_or_insert(FillStyle {
452 pattern: PatternType::None,
453 fg_color: None,
454 bg_color: None,
455 gradient: None,
456 })
457 .pattern = pattern;
458 self
459 }
460
461 pub fn fill_fg_color(mut self, color: StyleColor) -> Self {
463 self.style
464 .fill
465 .get_or_insert(FillStyle {
466 pattern: PatternType::None,
467 fg_color: None,
468 bg_color: None,
469 gradient: None,
470 })
471 .fg_color = Some(color);
472 self
473 }
474
475 pub fn fill_fg_color_rgb(self, rgb: &str) -> Self {
477 self.fill_fg_color(StyleColor::Rgb(rgb.to_string()))
478 }
479
480 pub fn fill_bg_color(mut self, color: StyleColor) -> Self {
482 self.style
483 .fill
484 .get_or_insert(FillStyle {
485 pattern: PatternType::None,
486 fg_color: None,
487 bg_color: None,
488 gradient: None,
489 })
490 .bg_color = Some(color);
491 self
492 }
493
494 pub fn solid_fill(mut self, rgb: &str) -> Self {
496 self.style.fill = Some(FillStyle {
497 pattern: PatternType::Solid,
498 fg_color: Some(StyleColor::Rgb(rgb.to_string())),
499 bg_color: self.style.fill.and_then(|f| f.bg_color),
500 gradient: None,
501 });
502 self
503 }
504
505 pub fn border_left(mut self, style: BorderLineStyle, color: StyleColor) -> Self {
509 self.style
510 .border
511 .get_or_insert_with(BorderStyle::default)
512 .left = Some(BorderSideStyle {
513 style,
514 color: Some(color),
515 });
516 self
517 }
518
519 pub fn border_right(mut self, style: BorderLineStyle, color: StyleColor) -> Self {
521 self.style
522 .border
523 .get_or_insert_with(BorderStyle::default)
524 .right = Some(BorderSideStyle {
525 style,
526 color: Some(color),
527 });
528 self
529 }
530
531 pub fn border_top(mut self, style: BorderLineStyle, color: StyleColor) -> Self {
533 self.style
534 .border
535 .get_or_insert_with(BorderStyle::default)
536 .top = Some(BorderSideStyle {
537 style,
538 color: Some(color),
539 });
540 self
541 }
542
543 pub fn border_bottom(mut self, style: BorderLineStyle, color: StyleColor) -> Self {
545 self.style
546 .border
547 .get_or_insert_with(BorderStyle::default)
548 .bottom = Some(BorderSideStyle {
549 style,
550 color: Some(color),
551 });
552 self
553 }
554
555 pub fn border_all(mut self, style: BorderLineStyle, color: StyleColor) -> Self {
557 let side = || BorderSideStyle {
558 style,
559 color: Some(color.clone()),
560 };
561 let border = self.style.border.get_or_insert_with(BorderStyle::default);
562 border.left = Some(side());
563 border.right = Some(side());
564 border.top = Some(side());
565 border.bottom = Some(side());
566 self
567 }
568
569 pub fn horizontal_align(mut self, align: HorizontalAlign) -> Self {
573 self.style
574 .alignment
575 .get_or_insert_with(AlignmentStyle::default)
576 .horizontal = Some(align);
577 self
578 }
579
580 pub fn vertical_align(mut self, align: VerticalAlign) -> Self {
582 self.style
583 .alignment
584 .get_or_insert_with(AlignmentStyle::default)
585 .vertical = Some(align);
586 self
587 }
588
589 pub fn wrap_text(mut self, wrap: bool) -> Self {
591 self.style
592 .alignment
593 .get_or_insert_with(AlignmentStyle::default)
594 .wrap_text = wrap;
595 self
596 }
597
598 pub fn text_rotation(mut self, degrees: u32) -> Self {
600 self.style
601 .alignment
602 .get_or_insert_with(AlignmentStyle::default)
603 .text_rotation = Some(degrees);
604 self
605 }
606
607 pub fn indent(mut self, indent: u32) -> Self {
609 self.style
610 .alignment
611 .get_or_insert_with(AlignmentStyle::default)
612 .indent = Some(indent);
613 self
614 }
615
616 pub fn shrink_to_fit(mut self, shrink: bool) -> Self {
618 self.style
619 .alignment
620 .get_or_insert_with(AlignmentStyle::default)
621 .shrink_to_fit = shrink;
622 self
623 }
624
625 pub fn num_format_builtin(mut self, id: u32) -> Self {
629 self.style.num_fmt = Some(NumFmtStyle::Builtin(id));
630 self
631 }
632
633 pub fn num_format_custom(mut self, format: &str) -> Self {
635 self.style.num_fmt = Some(NumFmtStyle::Custom(format.to_string()));
636 self
637 }
638
639 pub fn locked(mut self, locked: bool) -> Self {
643 self.style
644 .protection
645 .get_or_insert(ProtectionStyle {
646 locked: true,
647 hidden: false,
648 })
649 .locked = locked;
650 self
651 }
652
653 pub fn hidden(mut self, hidden: bool) -> Self {
655 self.style
656 .protection
657 .get_or_insert(ProtectionStyle {
658 locked: true,
659 hidden: false,
660 })
661 .hidden = hidden;
662 self
663 }
664
665 pub fn build(self) -> Style {
669 self.style
670 }
671}
672
673impl Default for StyleBuilder {
674 fn default() -> Self {
675 Self::new()
676 }
677}
678
679fn style_color_to_xml(color: &StyleColor) -> Color {
681 match color {
682 StyleColor::Rgb(rgb) => Color {
683 auto: None,
684 indexed: None,
685 rgb: Some(rgb.clone()),
686 theme: None,
687 tint: None,
688 },
689 StyleColor::Theme(t) => Color {
690 auto: None,
691 indexed: None,
692 rgb: None,
693 theme: Some(*t),
694 tint: None,
695 },
696 StyleColor::Indexed(i) => Color {
697 auto: None,
698 indexed: Some(*i),
699 rgb: None,
700 theme: None,
701 tint: None,
702 },
703 }
704}
705
706fn xml_color_to_style(color: &Color) -> Option<StyleColor> {
708 if let Some(ref rgb) = color.rgb {
709 Some(StyleColor::Rgb(rgb.clone()))
710 } else if let Some(theme) = color.theme {
711 Some(StyleColor::Theme(theme))
712 } else {
713 color.indexed.map(StyleColor::Indexed)
714 }
715}
716
717fn font_style_to_xml(font: &FontStyle) -> Font {
719 use sheetkit_xml::styles::{BoolVal, FontName, FontSize, Underline};
720
721 Font {
722 b: if font.bold {
723 Some(BoolVal { val: None })
724 } else {
725 None
726 },
727 i: if font.italic {
728 Some(BoolVal { val: None })
729 } else {
730 None
731 },
732 strike: if font.strikethrough {
733 Some(BoolVal { val: None })
734 } else {
735 None
736 },
737 u: if font.underline {
738 Some(Underline { val: None })
739 } else {
740 None
741 },
742 sz: font.size.map(|val| FontSize { val }),
743 color: font.color.as_ref().map(style_color_to_xml),
744 name: font.name.as_ref().map(|val| FontName { val: val.clone() }),
745 family: None,
746 scheme: None,
747 }
748}
749
750fn xml_font_to_style(font: &Font) -> FontStyle {
752 FontStyle {
753 name: font.name.as_ref().map(|n| n.val.clone()),
754 size: font.sz.as_ref().map(|s| s.val),
755 bold: font.b.is_some(),
756 italic: font.i.is_some(),
757 underline: font.u.is_some(),
758 strikethrough: font.strike.is_some(),
759 color: font.color.as_ref().and_then(xml_color_to_style),
760 }
761}
762
763fn fill_style_to_xml(fill: &FillStyle) -> Fill {
765 if let Some(ref grad) = fill.gradient {
766 return Fill {
767 pattern_fill: None,
768 gradient_fill: Some(gradient_style_to_xml(grad)),
769 };
770 }
771 Fill {
772 pattern_fill: Some(PatternFill {
773 pattern_type: Some(fill.pattern.as_str().to_string()),
774 fg_color: fill.fg_color.as_ref().map(style_color_to_xml),
775 bg_color: fill.bg_color.as_ref().map(style_color_to_xml),
776 }),
777 gradient_fill: None,
778 }
779}
780
781fn gradient_style_to_xml(grad: &GradientFillStyle) -> sheetkit_xml::styles::GradientFill {
783 sheetkit_xml::styles::GradientFill {
784 gradient_type: match grad.gradient_type {
785 GradientType::Linear => None,
786 GradientType::Path => Some("path".to_string()),
787 },
788 degree: grad.degree,
789 left: grad.left,
790 right: grad.right,
791 top: grad.top,
792 bottom: grad.bottom,
793 stops: grad
794 .stops
795 .iter()
796 .map(|s| sheetkit_xml::styles::GradientStop {
797 position: s.position,
798 color: style_color_to_xml(&s.color),
799 })
800 .collect(),
801 }
802}
803
804fn xml_fill_to_style(fill: &Fill) -> Option<FillStyle> {
806 if let Some(ref gf) = fill.gradient_fill {
807 let gradient_type = gf
808 .gradient_type
809 .as_ref()
810 .map(|s| GradientType::from_str(s))
811 .unwrap_or(GradientType::Linear);
812 let stops: Vec<GradientStop> = gf
813 .stops
814 .iter()
815 .filter_map(|s| {
816 xml_color_to_style(&s.color).map(|c| GradientStop {
817 position: s.position,
818 color: c,
819 })
820 })
821 .collect();
822 return Some(FillStyle {
823 pattern: PatternType::None,
824 fg_color: None,
825 bg_color: None,
826 gradient: Some(GradientFillStyle {
827 gradient_type,
828 degree: gf.degree,
829 left: gf.left,
830 right: gf.right,
831 top: gf.top,
832 bottom: gf.bottom,
833 stops,
834 }),
835 });
836 }
837 let pf = fill.pattern_fill.as_ref()?;
838 let pattern = pf
839 .pattern_type
840 .as_ref()
841 .map(|s| PatternType::from_str(s))
842 .unwrap_or(PatternType::None);
843 Some(FillStyle {
844 pattern,
845 fg_color: pf.fg_color.as_ref().and_then(xml_color_to_style),
846 bg_color: pf.bg_color.as_ref().and_then(xml_color_to_style),
847 gradient: None,
848 })
849}
850
851fn border_side_to_xml(side: &BorderSideStyle) -> BorderSide {
853 BorderSide {
854 style: Some(side.style.as_str().to_string()),
855 color: side.color.as_ref().map(style_color_to_xml),
856 }
857}
858
859fn xml_border_side_to_style(side: &BorderSide) -> Option<BorderSideStyle> {
861 let style_str = side.style.as_ref()?;
862 let style = BorderLineStyle::from_str(style_str)?;
863 Some(BorderSideStyle {
864 style,
865 color: side.color.as_ref().and_then(xml_color_to_style),
866 })
867}
868
869fn border_style_to_xml(border: &BorderStyle) -> Border {
871 Border {
872 diagonal_up: None,
873 diagonal_down: None,
874 left: border.left.as_ref().map(border_side_to_xml),
875 right: border.right.as_ref().map(border_side_to_xml),
876 top: border.top.as_ref().map(border_side_to_xml),
877 bottom: border.bottom.as_ref().map(border_side_to_xml),
878 diagonal: border.diagonal.as_ref().map(border_side_to_xml),
879 }
880}
881
882fn xml_border_to_style(border: &Border) -> BorderStyle {
884 BorderStyle {
885 left: border.left.as_ref().and_then(xml_border_side_to_style),
886 right: border.right.as_ref().and_then(xml_border_side_to_style),
887 top: border.top.as_ref().and_then(xml_border_side_to_style),
888 bottom: border.bottom.as_ref().and_then(xml_border_side_to_style),
889 diagonal: border.diagonal.as_ref().and_then(xml_border_side_to_style),
890 }
891}
892
893fn alignment_style_to_xml(align: &AlignmentStyle) -> Alignment {
895 Alignment {
896 horizontal: align.horizontal.map(|h| h.as_str().to_string()),
897 vertical: align.vertical.map(|v| v.as_str().to_string()),
898 wrap_text: if align.wrap_text { Some(true) } else { None },
899 text_rotation: align.text_rotation,
900 indent: align.indent,
901 shrink_to_fit: if align.shrink_to_fit {
902 Some(true)
903 } else {
904 None
905 },
906 }
907}
908
909fn xml_alignment_to_style(align: &Alignment) -> AlignmentStyle {
911 AlignmentStyle {
912 horizontal: align
913 .horizontal
914 .as_ref()
915 .and_then(|s| HorizontalAlign::from_str(s)),
916 vertical: align
917 .vertical
918 .as_ref()
919 .and_then(|s| VerticalAlign::from_str(s)),
920 wrap_text: align.wrap_text.unwrap_or(false),
921 text_rotation: align.text_rotation,
922 indent: align.indent,
923 shrink_to_fit: align.shrink_to_fit.unwrap_or(false),
924 }
925}
926
927fn protection_style_to_xml(prot: &ProtectionStyle) -> Protection {
929 Protection {
930 locked: Some(prot.locked),
931 hidden: Some(prot.hidden),
932 }
933}
934
935fn xml_protection_to_style(prot: &Protection) -> ProtectionStyle {
937 ProtectionStyle {
938 locked: prot.locked.unwrap_or(true), hidden: prot.hidden.unwrap_or(false),
940 }
941}
942
943fn fonts_equal(a: &Font, b: &Font) -> bool {
945 a.b.is_some() == b.b.is_some()
946 && a.i.is_some() == b.i.is_some()
947 && a.strike.is_some() == b.strike.is_some()
948 && a.u.is_some() == b.u.is_some()
949 && a.sz == b.sz
950 && a.color == b.color
951 && a.name == b.name
952}
953
954fn fills_equal(a: &Fill, b: &Fill) -> bool {
956 a.pattern_fill == b.pattern_fill && a.gradient_fill == b.gradient_fill
957}
958
959fn borders_equal(a: &Border, b: &Border) -> bool {
961 a.left == b.left
962 && a.right == b.right
963 && a.top == b.top
964 && a.bottom == b.bottom
965 && a.diagonal == b.diagonal
966}
967
968fn xfs_equal(a: &Xf, b: &Xf) -> bool {
970 a.num_fmt_id == b.num_fmt_id
971 && a.font_id == b.font_id
972 && a.fill_id == b.fill_id
973 && a.border_id == b.border_id
974 && a.alignment == b.alignment
975 && a.protection == b.protection
976}
977
978fn add_or_find_font(fonts: &mut Fonts, font: &FontStyle) -> u32 {
981 let xml_font = font_style_to_xml(font);
982
983 for (i, existing) in fonts.fonts.iter().enumerate() {
984 if fonts_equal(existing, &xml_font) {
985 return i as u32;
986 }
987 }
988
989 let id = fonts.fonts.len() as u32;
990 fonts.fonts.push(xml_font);
991 fonts.count = Some(fonts.fonts.len() as u32);
992 id
993}
994
995fn add_or_find_fill(fills: &mut Fills, fill: &FillStyle) -> u32 {
998 let xml_fill = fill_style_to_xml(fill);
999
1000 for (i, existing) in fills.fills.iter().enumerate() {
1001 if fills_equal(existing, &xml_fill) {
1002 return i as u32;
1003 }
1004 }
1005
1006 let id = fills.fills.len() as u32;
1007 fills.fills.push(xml_fill);
1008 fills.count = Some(fills.fills.len() as u32);
1009 id
1010}
1011
1012fn add_or_find_border(borders: &mut Borders, border: &BorderStyle) -> u32 {
1015 let xml_border = border_style_to_xml(border);
1016
1017 for (i, existing) in borders.borders.iter().enumerate() {
1018 if borders_equal(existing, &xml_border) {
1019 return i as u32;
1020 }
1021 }
1022
1023 let id = borders.borders.len() as u32;
1024 borders.borders.push(xml_border);
1025 borders.count = Some(borders.borders.len() as u32);
1026 id
1027}
1028
1029fn add_or_find_num_fmt(stylesheet: &mut StyleSheet, fmt: &str) -> u32 {
1032 let num_fmts = stylesheet.num_fmts.get_or_insert_with(|| NumFmts {
1033 count: Some(0),
1034 num_fmts: Vec::new(),
1035 });
1036
1037 for nf in &num_fmts.num_fmts {
1038 if nf.format_code == fmt {
1039 return nf.num_fmt_id;
1040 }
1041 }
1042
1043 let next_id = num_fmts
1044 .num_fmts
1045 .iter()
1046 .map(|nf| nf.num_fmt_id)
1047 .max()
1048 .map(|max_id| max_id + 1)
1049 .unwrap_or(CUSTOM_NUM_FMT_BASE);
1050
1051 let next_id = next_id.max(CUSTOM_NUM_FMT_BASE);
1052
1053 num_fmts.num_fmts.push(NumFmt {
1054 num_fmt_id: next_id,
1055 format_code: fmt.to_string(),
1056 });
1057 num_fmts.count = Some(num_fmts.num_fmts.len() as u32);
1058
1059 next_id
1060}
1061
1062pub fn add_style(stylesheet: &mut StyleSheet, style: &Style) -> Result<u32> {
1065 if stylesheet.cell_xfs.xfs.len() >= MAX_CELL_XFS {
1066 return Err(Error::CellStylesExceeded { max: MAX_CELL_XFS });
1067 }
1068
1069 let font_id = match &style.font {
1070 Some(font) => add_or_find_font(&mut stylesheet.fonts, font),
1071 None => 0, };
1073
1074 let fill_id = match &style.fill {
1075 Some(fill) => add_or_find_fill(&mut stylesheet.fills, fill),
1076 None => 0, };
1078
1079 let border_id = match &style.border {
1080 Some(border) => add_or_find_border(&mut stylesheet.borders, border),
1081 None => 0, };
1083
1084 let num_fmt_id = match &style.num_fmt {
1085 Some(NumFmtStyle::Builtin(id)) => *id,
1086 Some(NumFmtStyle::Custom(code)) => add_or_find_num_fmt(stylesheet, code),
1087 None => 0, };
1089
1090 let alignment = style.alignment.as_ref().map(alignment_style_to_xml);
1091 let protection = style.protection.as_ref().map(protection_style_to_xml);
1092
1093 let xf = Xf {
1094 num_fmt_id: Some(num_fmt_id),
1095 font_id: Some(font_id),
1096 fill_id: Some(fill_id),
1097 border_id: Some(border_id),
1098 xf_id: Some(0),
1099 apply_number_format: if num_fmt_id != 0 { Some(true) } else { None },
1100 apply_font: if font_id != 0 { Some(true) } else { None },
1101 apply_fill: if fill_id != 0 { Some(true) } else { None },
1102 apply_border: if border_id != 0 { Some(true) } else { None },
1103 apply_alignment: if alignment.is_some() {
1104 Some(true)
1105 } else {
1106 None
1107 },
1108 alignment,
1109 protection,
1110 };
1111
1112 for (i, existing) in stylesheet.cell_xfs.xfs.iter().enumerate() {
1113 if xfs_equal(existing, &xf) {
1114 return Ok(i as u32);
1115 }
1116 }
1117
1118 let id = stylesheet.cell_xfs.xfs.len() as u32;
1119 stylesheet.cell_xfs.xfs.push(xf);
1120 stylesheet.cell_xfs.count = Some(stylesheet.cell_xfs.xfs.len() as u32);
1121
1122 Ok(id)
1123}
1124
1125pub fn get_style(stylesheet: &StyleSheet, style_id: u32) -> Option<Style> {
1127 let xf = stylesheet.cell_xfs.xfs.get(style_id as usize)?;
1128
1129 let font = xf
1130 .font_id
1131 .and_then(|id| stylesheet.fonts.fonts.get(id as usize))
1132 .map(xml_font_to_style);
1133
1134 let fill = xf
1135 .fill_id
1136 .and_then(|id| stylesheet.fills.fills.get(id as usize))
1137 .and_then(xml_fill_to_style);
1138
1139 let border = xf
1140 .border_id
1141 .and_then(|id| stylesheet.borders.borders.get(id as usize))
1142 .map(xml_border_to_style);
1143
1144 let alignment = xf.alignment.as_ref().map(xml_alignment_to_style);
1145
1146 let num_fmt = xf.num_fmt_id.and_then(|id| {
1147 if id == 0 {
1148 return None;
1149 }
1150 if id < CUSTOM_NUM_FMT_BASE {
1152 Some(NumFmtStyle::Builtin(id))
1153 } else {
1154 stylesheet
1156 .num_fmts
1157 .as_ref()
1158 .and_then(|nfs| nfs.num_fmts.iter().find(|nf| nf.num_fmt_id == id))
1159 .map(|nf| NumFmtStyle::Custom(nf.format_code.clone()))
1160 }
1161 });
1162
1163 let protection = xf.protection.as_ref().map(xml_protection_to_style);
1164
1165 Some(Style {
1166 font,
1167 fill,
1168 border,
1169 alignment,
1170 num_fmt,
1171 protection,
1172 })
1173}
1174
1175pub fn compute_style_is_date(stylesheet: &StyleSheet) -> Vec<bool> {
1188 stylesheet
1189 .cell_xfs
1190 .xfs
1191 .iter()
1192 .map(|xf| {
1193 let num_fmt_id = xf.num_fmt_id.unwrap_or(0);
1194 if crate::cell::is_date_num_fmt(num_fmt_id) {
1195 return true;
1196 }
1197 if num_fmt_id >= CUSTOM_NUM_FMT_BASE {
1198 if let Some(nfs) = stylesheet.num_fmts.as_ref() {
1199 if let Some(nf) = nfs.num_fmts.iter().find(|nf| nf.num_fmt_id == num_fmt_id) {
1200 return crate::cell::is_date_format_code(&nf.format_code);
1201 }
1202 }
1203 }
1204 false
1205 })
1206 .collect()
1207}
1208
1209#[cfg(test)]
1210mod tests {
1211 use super::*;
1212
1213 fn default_stylesheet() -> StyleSheet {
1215 StyleSheet::default()
1216 }
1217
1218 fn xf_with_num_fmt(id: u32) -> Xf {
1219 Xf {
1220 num_fmt_id: Some(id),
1221 font_id: None,
1222 fill_id: None,
1223 border_id: None,
1224 xf_id: None,
1225 apply_number_format: None,
1226 apply_font: None,
1227 apply_fill: None,
1228 apply_border: None,
1229 apply_alignment: None,
1230 alignment: None,
1231 protection: None,
1232 }
1233 }
1234
1235 #[test]
1236 fn test_compute_style_is_date_builtin() {
1237 let mut ss = default_stylesheet();
1238 ss.cell_xfs.xfs = vec![
1240 xf_with_num_fmt(0),
1241 xf_with_num_fmt(14),
1242 xf_with_num_fmt(21),
1243 xf_with_num_fmt(2),
1244 ];
1245 ss.cell_xfs.count = Some(4);
1246 let flags = compute_style_is_date(&ss);
1247 assert_eq!(flags, vec![false, true, true, false]);
1248 }
1249
1250 #[test]
1251 fn test_compute_style_is_date_custom() {
1252 let mut ss = default_stylesheet();
1253 ss.num_fmts = Some(NumFmts {
1254 count: Some(2),
1255 num_fmts: vec![
1256 NumFmt {
1257 num_fmt_id: 164,
1258 format_code: "yyyy-mm-dd hh:mm".to_string(),
1259 },
1260 NumFmt {
1261 num_fmt_id: 165,
1262 format_code: "#,##0.00".to_string(),
1263 },
1264 ],
1265 });
1266 ss.cell_xfs.xfs = vec![xf_with_num_fmt(164), xf_with_num_fmt(165)];
1267 ss.cell_xfs.count = Some(2);
1268 let flags = compute_style_is_date(&ss);
1269 assert_eq!(flags, vec![true, false]);
1270 }
1271
1272 #[test]
1273 fn test_compute_style_is_date_missing_custom_format() {
1274 let mut ss = default_stylesheet();
1275 ss.cell_xfs.xfs = vec![xf_with_num_fmt(200)];
1277 ss.cell_xfs.count = Some(1);
1278 let flags = compute_style_is_date(&ss);
1279 assert_eq!(flags, vec![false]);
1280 }
1281
1282 #[test]
1283 fn test_compute_style_is_date_default_stylesheet() {
1284 let ss = default_stylesheet();
1287 let flags = compute_style_is_date(&ss);
1288 assert!(!flags.is_empty());
1289 assert!(flags.iter().all(|f| !*f));
1290 }
1291
1292 #[test]
1293 fn test_add_bold_font_style() {
1294 let mut ss = default_stylesheet();
1295 let style = Style {
1296 font: Some(FontStyle {
1297 bold: true,
1298 ..FontStyle::default()
1299 }),
1300 ..Style::default()
1301 };
1302
1303 let id = add_style(&mut ss, &style).unwrap();
1304 assert_eq!(id, 1);
1306 assert_eq!(ss.fonts.fonts.len(), 2);
1308 assert!(ss.fonts.fonts[1].b.is_some());
1309 }
1310
1311 #[test]
1312 fn test_add_same_style_twice_deduplication() {
1313 let mut ss = default_stylesheet();
1314 let style = Style {
1315 font: Some(FontStyle {
1316 bold: true,
1317 ..FontStyle::default()
1318 }),
1319 ..Style::default()
1320 };
1321
1322 let id1 = add_style(&mut ss, &style).unwrap();
1323 let id2 = add_style(&mut ss, &style).unwrap();
1324 assert_eq!(id1, id2, "same style should return the same ID");
1325 assert_eq!(ss.fonts.fonts.len(), 2);
1327 assert_eq!(ss.cell_xfs.xfs.len(), 2);
1329 }
1330
1331 #[test]
1332 fn test_add_different_styles_different_ids() {
1333 let mut ss = default_stylesheet();
1334
1335 let bold_style = Style {
1336 font: Some(FontStyle {
1337 bold: true,
1338 ..FontStyle::default()
1339 }),
1340 ..Style::default()
1341 };
1342 let italic_style = Style {
1343 font: Some(FontStyle {
1344 italic: true,
1345 ..FontStyle::default()
1346 }),
1347 ..Style::default()
1348 };
1349
1350 let id1 = add_style(&mut ss, &bold_style).unwrap();
1351 let id2 = add_style(&mut ss, &italic_style).unwrap();
1352 assert_ne!(id1, id2);
1353 }
1354
1355 #[test]
1356 fn test_font_italic() {
1357 let mut ss = default_stylesheet();
1358 let style = Style {
1359 font: Some(FontStyle {
1360 italic: true,
1361 ..FontStyle::default()
1362 }),
1363 ..Style::default()
1364 };
1365
1366 let id = add_style(&mut ss, &style).unwrap();
1367 assert!(id > 0);
1368 let font_id = ss.cell_xfs.xfs[id as usize].font_id.unwrap();
1369 assert!(ss.fonts.fonts[font_id as usize].i.is_some());
1370 }
1371
1372 #[test]
1373 fn test_font_underline() {
1374 let mut ss = default_stylesheet();
1375 let style = Style {
1376 font: Some(FontStyle {
1377 underline: true,
1378 ..FontStyle::default()
1379 }),
1380 ..Style::default()
1381 };
1382
1383 let id = add_style(&mut ss, &style).unwrap();
1384 assert!(id > 0);
1385 let font_id = ss.cell_xfs.xfs[id as usize].font_id.unwrap();
1386 assert!(ss.fonts.fonts[font_id as usize].u.is_some());
1387 }
1388
1389 #[test]
1390 fn test_font_strikethrough() {
1391 let mut ss = default_stylesheet();
1392 let style = Style {
1393 font: Some(FontStyle {
1394 strikethrough: true,
1395 ..FontStyle::default()
1396 }),
1397 ..Style::default()
1398 };
1399
1400 let id = add_style(&mut ss, &style).unwrap();
1401 let font_id = ss.cell_xfs.xfs[id as usize].font_id.unwrap();
1402 assert!(ss.fonts.fonts[font_id as usize].strike.is_some());
1403 }
1404
1405 #[test]
1406 fn test_font_custom_name_and_size() {
1407 let mut ss = default_stylesheet();
1408 let style = Style {
1409 font: Some(FontStyle {
1410 name: Some("Arial".to_string()),
1411 size: Some(14.0),
1412 ..FontStyle::default()
1413 }),
1414 ..Style::default()
1415 };
1416
1417 let id = add_style(&mut ss, &style).unwrap();
1418 let font_id = ss.cell_xfs.xfs[id as usize].font_id.unwrap();
1419 let xml_font = &ss.fonts.fonts[font_id as usize];
1420 assert_eq!(xml_font.name.as_ref().unwrap().val, "Arial");
1421 assert_eq!(xml_font.sz.as_ref().unwrap().val, 14.0);
1422 }
1423
1424 #[test]
1425 fn test_font_with_rgb_color() {
1426 let mut ss = default_stylesheet();
1427 let style = Style {
1428 font: Some(FontStyle {
1429 color: Some(StyleColor::Rgb("FFFF0000".to_string())),
1430 ..FontStyle::default()
1431 }),
1432 ..Style::default()
1433 };
1434
1435 let id = add_style(&mut ss, &style).unwrap();
1436 let font_id = ss.cell_xfs.xfs[id as usize].font_id.unwrap();
1437 let xml_font = &ss.fonts.fonts[font_id as usize];
1438 assert_eq!(
1439 xml_font.color.as_ref().unwrap().rgb,
1440 Some("FFFF0000".to_string())
1441 );
1442 }
1443
1444 #[test]
1445 fn test_fill_solid_color() {
1446 let mut ss = default_stylesheet();
1447 let style = Style {
1448 fill: Some(FillStyle {
1449 pattern: PatternType::Solid,
1450 fg_color: Some(StyleColor::Rgb("FFFFFF00".to_string())),
1451 bg_color: None,
1452 gradient: None,
1453 }),
1454 ..Style::default()
1455 };
1456
1457 let id = add_style(&mut ss, &style).unwrap();
1458 let fill_id = ss.cell_xfs.xfs[id as usize].fill_id.unwrap();
1459 let xml_fill = &ss.fills.fills[fill_id as usize];
1460 let pf = xml_fill.pattern_fill.as_ref().unwrap();
1461 assert_eq!(pf.pattern_type, Some("solid".to_string()));
1462 assert_eq!(
1463 pf.fg_color.as_ref().unwrap().rgb,
1464 Some("FFFFFF00".to_string())
1465 );
1466 }
1467
1468 #[test]
1469 fn test_fill_pattern() {
1470 let mut ss = default_stylesheet();
1471 let style = Style {
1472 fill: Some(FillStyle {
1473 pattern: PatternType::LightGray,
1474 fg_color: None,
1475 bg_color: None,
1476 gradient: None,
1477 }),
1478 ..Style::default()
1479 };
1480
1481 let id = add_style(&mut ss, &style).unwrap();
1482 let fill_id = ss.cell_xfs.xfs[id as usize].fill_id.unwrap();
1483 let xml_fill = &ss.fills.fills[fill_id as usize];
1484 let pf = xml_fill.pattern_fill.as_ref().unwrap();
1485 assert_eq!(pf.pattern_type, Some("lightGray".to_string()));
1486 }
1487
1488 #[test]
1489 fn test_fill_deduplication() {
1490 let mut ss = default_stylesheet();
1491 let style = Style {
1492 fill: Some(FillStyle {
1493 pattern: PatternType::Solid,
1494 fg_color: Some(StyleColor::Rgb("FFFF0000".to_string())),
1495 bg_color: None,
1496 gradient: None,
1497 }),
1498 ..Style::default()
1499 };
1500
1501 let id1 = add_style(&mut ss, &style).unwrap();
1502 let id2 = add_style(&mut ss, &style).unwrap();
1503 assert_eq!(id1, id2);
1504 assert_eq!(ss.fills.fills.len(), 3);
1506 }
1507
1508 #[test]
1509 fn test_border_thin_all_sides() {
1510 let mut ss = default_stylesheet();
1511 let style = Style {
1512 border: Some(BorderStyle {
1513 left: Some(BorderSideStyle {
1514 style: BorderLineStyle::Thin,
1515 color: None,
1516 }),
1517 right: Some(BorderSideStyle {
1518 style: BorderLineStyle::Thin,
1519 color: None,
1520 }),
1521 top: Some(BorderSideStyle {
1522 style: BorderLineStyle::Thin,
1523 color: None,
1524 }),
1525 bottom: Some(BorderSideStyle {
1526 style: BorderLineStyle::Thin,
1527 color: None,
1528 }),
1529 diagonal: None,
1530 }),
1531 ..Style::default()
1532 };
1533
1534 let id = add_style(&mut ss, &style).unwrap();
1535 let border_id = ss.cell_xfs.xfs[id as usize].border_id.unwrap();
1536 let xml_border = &ss.borders.borders[border_id as usize];
1537 assert_eq!(
1538 xml_border.left.as_ref().unwrap().style,
1539 Some("thin".to_string())
1540 );
1541 assert_eq!(
1542 xml_border.right.as_ref().unwrap().style,
1543 Some("thin".to_string())
1544 );
1545 assert_eq!(
1546 xml_border.top.as_ref().unwrap().style,
1547 Some("thin".to_string())
1548 );
1549 assert_eq!(
1550 xml_border.bottom.as_ref().unwrap().style,
1551 Some("thin".to_string())
1552 );
1553 }
1554
1555 #[test]
1556 fn test_border_medium() {
1557 let mut ss = default_stylesheet();
1558 let style = Style {
1559 border: Some(BorderStyle {
1560 left: Some(BorderSideStyle {
1561 style: BorderLineStyle::Medium,
1562 color: Some(StyleColor::Rgb("FF000000".to_string())),
1563 }),
1564 right: None,
1565 top: None,
1566 bottom: None,
1567 diagonal: None,
1568 }),
1569 ..Style::default()
1570 };
1571
1572 let id = add_style(&mut ss, &style).unwrap();
1573 let border_id = ss.cell_xfs.xfs[id as usize].border_id.unwrap();
1574 let xml_border = &ss.borders.borders[border_id as usize];
1575 let left = xml_border.left.as_ref().unwrap();
1576 assert_eq!(left.style, Some("medium".to_string()));
1577 assert_eq!(
1578 left.color.as_ref().unwrap().rgb,
1579 Some("FF000000".to_string())
1580 );
1581 }
1582
1583 #[test]
1584 fn test_border_thick() {
1585 let mut ss = default_stylesheet();
1586 let style = Style {
1587 border: Some(BorderStyle {
1588 bottom: Some(BorderSideStyle {
1589 style: BorderLineStyle::Thick,
1590 color: None,
1591 }),
1592 ..BorderStyle::default()
1593 }),
1594 ..Style::default()
1595 };
1596
1597 let id = add_style(&mut ss, &style).unwrap();
1598 let border_id = ss.cell_xfs.xfs[id as usize].border_id.unwrap();
1599 let xml_border = &ss.borders.borders[border_id as usize];
1600 assert_eq!(
1601 xml_border.bottom.as_ref().unwrap().style,
1602 Some("thick".to_string())
1603 );
1604 }
1605
1606 #[test]
1607 fn test_num_fmt_builtin() {
1608 let mut ss = default_stylesheet();
1609 let style = Style {
1610 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::PERCENT)),
1611 ..Style::default()
1612 };
1613
1614 let id = add_style(&mut ss, &style).unwrap();
1615 let xf = &ss.cell_xfs.xfs[id as usize];
1616 assert_eq!(xf.num_fmt_id, Some(builtin_num_fmts::PERCENT));
1617 assert_eq!(xf.apply_number_format, Some(true));
1618 }
1619
1620 #[test]
1621 fn test_num_fmt_custom() {
1622 let mut ss = default_stylesheet();
1623 let style = Style {
1624 num_fmt: Some(NumFmtStyle::Custom("#,##0.000".to_string())),
1625 ..Style::default()
1626 };
1627
1628 let id = add_style(&mut ss, &style).unwrap();
1629 let xf = &ss.cell_xfs.xfs[id as usize];
1630 let fmt_id = xf.num_fmt_id.unwrap();
1631 assert!(fmt_id >= CUSTOM_NUM_FMT_BASE);
1632
1633 let num_fmts = ss.num_fmts.as_ref().unwrap();
1635 let nf = num_fmts
1636 .num_fmts
1637 .iter()
1638 .find(|nf| nf.num_fmt_id == fmt_id)
1639 .unwrap();
1640 assert_eq!(nf.format_code, "#,##0.000");
1641 }
1642
1643 #[test]
1644 fn test_num_fmt_custom_deduplication() {
1645 let mut ss = default_stylesheet();
1646 let style = Style {
1647 num_fmt: Some(NumFmtStyle::Custom("0.0%".to_string())),
1648 ..Style::default()
1649 };
1650
1651 let id1 = add_style(&mut ss, &style).unwrap();
1652 let id2 = add_style(&mut ss, &style).unwrap();
1653 assert_eq!(id1, id2);
1654
1655 let num_fmts = ss.num_fmts.as_ref().unwrap();
1657 assert_eq!(num_fmts.num_fmts.len(), 1);
1658 }
1659
1660 #[test]
1661 fn test_alignment_horizontal_center() {
1662 let mut ss = default_stylesheet();
1663 let style = Style {
1664 alignment: Some(AlignmentStyle {
1665 horizontal: Some(HorizontalAlign::Center),
1666 ..AlignmentStyle::default()
1667 }),
1668 ..Style::default()
1669 };
1670
1671 let id = add_style(&mut ss, &style).unwrap();
1672 let xf = &ss.cell_xfs.xfs[id as usize];
1673 assert_eq!(xf.apply_alignment, Some(true));
1674 let align = xf.alignment.as_ref().unwrap();
1675 assert_eq!(align.horizontal, Some("center".to_string()));
1676 }
1677
1678 #[test]
1679 fn test_alignment_vertical_top() {
1680 let mut ss = default_stylesheet();
1681 let style = Style {
1682 alignment: Some(AlignmentStyle {
1683 vertical: Some(VerticalAlign::Top),
1684 ..AlignmentStyle::default()
1685 }),
1686 ..Style::default()
1687 };
1688
1689 let id = add_style(&mut ss, &style).unwrap();
1690 let xf = &ss.cell_xfs.xfs[id as usize];
1691 let align = xf.alignment.as_ref().unwrap();
1692 assert_eq!(align.vertical, Some("top".to_string()));
1693 }
1694
1695 #[test]
1696 fn test_alignment_wrap_text() {
1697 let mut ss = default_stylesheet();
1698 let style = Style {
1699 alignment: Some(AlignmentStyle {
1700 wrap_text: true,
1701 ..AlignmentStyle::default()
1702 }),
1703 ..Style::default()
1704 };
1705
1706 let id = add_style(&mut ss, &style).unwrap();
1707 let xf = &ss.cell_xfs.xfs[id as usize];
1708 let align = xf.alignment.as_ref().unwrap();
1709 assert_eq!(align.wrap_text, Some(true));
1710 }
1711
1712 #[test]
1713 fn test_alignment_text_rotation() {
1714 let mut ss = default_stylesheet();
1715 let style = Style {
1716 alignment: Some(AlignmentStyle {
1717 text_rotation: Some(90),
1718 ..AlignmentStyle::default()
1719 }),
1720 ..Style::default()
1721 };
1722
1723 let id = add_style(&mut ss, &style).unwrap();
1724 let xf = &ss.cell_xfs.xfs[id as usize];
1725 let align = xf.alignment.as_ref().unwrap();
1726 assert_eq!(align.text_rotation, Some(90));
1727 }
1728
1729 #[test]
1730 fn test_protection_locked() {
1731 let mut ss = default_stylesheet();
1732 let style = Style {
1733 protection: Some(ProtectionStyle {
1734 locked: true,
1735 hidden: false,
1736 }),
1737 ..Style::default()
1738 };
1739
1740 let id = add_style(&mut ss, &style).unwrap();
1741 let xf = &ss.cell_xfs.xfs[id as usize];
1742 let prot = xf.protection.as_ref().unwrap();
1743 assert_eq!(prot.locked, Some(true));
1744 assert_eq!(prot.hidden, Some(false));
1745 }
1746
1747 #[test]
1748 fn test_protection_hidden() {
1749 let mut ss = default_stylesheet();
1750 let style = Style {
1751 protection: Some(ProtectionStyle {
1752 locked: false,
1753 hidden: true,
1754 }),
1755 ..Style::default()
1756 };
1757
1758 let id = add_style(&mut ss, &style).unwrap();
1759 let xf = &ss.cell_xfs.xfs[id as usize];
1760 let prot = xf.protection.as_ref().unwrap();
1761 assert_eq!(prot.locked, Some(false));
1762 assert_eq!(prot.hidden, Some(true));
1763 }
1764
1765 #[test]
1766 fn test_combined_style_all_components() {
1767 let mut ss = default_stylesheet();
1768 let style = Style {
1769 font: Some(FontStyle {
1770 name: Some("Arial".to_string()),
1771 size: Some(12.0),
1772 bold: true,
1773 italic: false,
1774 underline: false,
1775 strikethrough: false,
1776 color: Some(StyleColor::Rgb("FF0000FF".to_string())),
1777 }),
1778 fill: Some(FillStyle {
1779 pattern: PatternType::Solid,
1780 fg_color: Some(StyleColor::Rgb("FFFFFF00".to_string())),
1781 bg_color: None,
1782 gradient: None,
1783 }),
1784 border: Some(BorderStyle {
1785 left: Some(BorderSideStyle {
1786 style: BorderLineStyle::Thin,
1787 color: Some(StyleColor::Rgb("FF000000".to_string())),
1788 }),
1789 right: Some(BorderSideStyle {
1790 style: BorderLineStyle::Thin,
1791 color: Some(StyleColor::Rgb("FF000000".to_string())),
1792 }),
1793 top: Some(BorderSideStyle {
1794 style: BorderLineStyle::Medium,
1795 color: None,
1796 }),
1797 bottom: Some(BorderSideStyle {
1798 style: BorderLineStyle::Medium,
1799 color: None,
1800 }),
1801 diagonal: None,
1802 }),
1803 alignment: Some(AlignmentStyle {
1804 horizontal: Some(HorizontalAlign::Center),
1805 vertical: Some(VerticalAlign::Center),
1806 wrap_text: true,
1807 text_rotation: None,
1808 indent: None,
1809 shrink_to_fit: false,
1810 }),
1811 num_fmt: Some(NumFmtStyle::Custom("#,##0.00".to_string())),
1812 protection: Some(ProtectionStyle {
1813 locked: true,
1814 hidden: false,
1815 }),
1816 };
1817
1818 let id = add_style(&mut ss, &style).unwrap();
1819 assert!(id > 0);
1820
1821 let xf = &ss.cell_xfs.xfs[id as usize];
1822 assert!(xf.font_id.unwrap() > 0);
1823 assert!(xf.fill_id.unwrap() > 0);
1824 assert!(xf.border_id.unwrap() > 0);
1825 assert!(xf.num_fmt_id.unwrap() >= CUSTOM_NUM_FMT_BASE);
1826 assert!(xf.alignment.is_some());
1827 assert!(xf.protection.is_some());
1828 assert_eq!(xf.apply_font, Some(true));
1829 assert_eq!(xf.apply_fill, Some(true));
1830 assert_eq!(xf.apply_border, Some(true));
1831 assert_eq!(xf.apply_number_format, Some(true));
1832 assert_eq!(xf.apply_alignment, Some(true));
1833 }
1834
1835 #[test]
1836 fn test_get_style_default() {
1837 let ss = default_stylesheet();
1838 let style = get_style(&ss, 0);
1839 assert!(style.is_some());
1840 let style = style.unwrap();
1841 assert!(style.font.is_some());
1843 }
1844
1845 #[test]
1846 fn test_get_style_invalid_id() {
1847 let ss = default_stylesheet();
1848 let style = get_style(&ss, 999);
1849 assert!(style.is_none());
1850 }
1851
1852 #[test]
1853 fn test_get_style_roundtrip_bold() {
1854 let mut ss = default_stylesheet();
1855 let original = Style {
1856 font: Some(FontStyle {
1857 bold: true,
1858 ..FontStyle::default()
1859 }),
1860 ..Style::default()
1861 };
1862
1863 let id = add_style(&mut ss, &original).unwrap();
1864 let retrieved = get_style(&ss, id).unwrap();
1865 assert!(retrieved.font.is_some());
1866 assert!(retrieved.font.as_ref().unwrap().bold);
1867 }
1868
1869 #[test]
1870 fn test_get_style_roundtrip_fill() {
1871 let mut ss = default_stylesheet();
1872 let original = Style {
1873 fill: Some(FillStyle {
1874 pattern: PatternType::Solid,
1875 fg_color: Some(StyleColor::Rgb("FFFF0000".to_string())),
1876 bg_color: None,
1877 gradient: None,
1878 }),
1879 ..Style::default()
1880 };
1881
1882 let id = add_style(&mut ss, &original).unwrap();
1883 let retrieved = get_style(&ss, id).unwrap();
1884 assert!(retrieved.fill.is_some());
1885 let fill = retrieved.fill.unwrap();
1886 assert_eq!(fill.pattern, PatternType::Solid);
1887 assert_eq!(fill.fg_color, Some(StyleColor::Rgb("FFFF0000".to_string())));
1888 }
1889
1890 #[test]
1891 fn test_get_style_roundtrip_alignment() {
1892 let mut ss = default_stylesheet();
1893 let original = Style {
1894 alignment: Some(AlignmentStyle {
1895 horizontal: Some(HorizontalAlign::Right),
1896 vertical: Some(VerticalAlign::Bottom),
1897 wrap_text: true,
1898 text_rotation: Some(45),
1899 indent: Some(2),
1900 shrink_to_fit: false,
1901 }),
1902 ..Style::default()
1903 };
1904
1905 let id = add_style(&mut ss, &original).unwrap();
1906 let retrieved = get_style(&ss, id).unwrap();
1907 assert!(retrieved.alignment.is_some());
1908 let align = retrieved.alignment.unwrap();
1909 assert_eq!(align.horizontal, Some(HorizontalAlign::Right));
1910 assert_eq!(align.vertical, Some(VerticalAlign::Bottom));
1911 assert!(align.wrap_text);
1912 assert_eq!(align.text_rotation, Some(45));
1913 assert_eq!(align.indent, Some(2));
1914 }
1915
1916 #[test]
1917 fn test_get_style_roundtrip_protection() {
1918 let mut ss = default_stylesheet();
1919 let original = Style {
1920 protection: Some(ProtectionStyle {
1921 locked: false,
1922 hidden: true,
1923 }),
1924 ..Style::default()
1925 };
1926
1927 let id = add_style(&mut ss, &original).unwrap();
1928 let retrieved = get_style(&ss, id).unwrap();
1929 assert!(retrieved.protection.is_some());
1930 let prot = retrieved.protection.unwrap();
1931 assert!(!prot.locked);
1932 assert!(prot.hidden);
1933 }
1934
1935 #[test]
1936 fn test_get_style_roundtrip_num_fmt_builtin() {
1937 let mut ss = default_stylesheet();
1938 let original = Style {
1939 num_fmt: Some(NumFmtStyle::Builtin(builtin_num_fmts::DATE_MDY)),
1940 ..Style::default()
1941 };
1942
1943 let id = add_style(&mut ss, &original).unwrap();
1944 let retrieved = get_style(&ss, id).unwrap();
1945 assert!(retrieved.num_fmt.is_some());
1946 match retrieved.num_fmt.unwrap() {
1947 NumFmtStyle::Builtin(fid) => assert_eq!(fid, builtin_num_fmts::DATE_MDY),
1948 _ => panic!("expected Builtin num fmt"),
1949 }
1950 }
1951
1952 #[test]
1953 fn test_get_style_roundtrip_num_fmt_custom() {
1954 let mut ss = default_stylesheet();
1955 let original = Style {
1956 num_fmt: Some(NumFmtStyle::Custom("yyyy-mm-dd".to_string())),
1957 ..Style::default()
1958 };
1959
1960 let id = add_style(&mut ss, &original).unwrap();
1961 let retrieved = get_style(&ss, id).unwrap();
1962 assert!(retrieved.num_fmt.is_some());
1963 match retrieved.num_fmt.unwrap() {
1964 NumFmtStyle::Custom(code) => assert_eq!(code, "yyyy-mm-dd"),
1965 _ => panic!("expected Custom num fmt"),
1966 }
1967 }
1968
1969 #[test]
1970 fn test_builtin_num_fmt_constants() {
1971 assert_eq!(builtin_num_fmts::GENERAL, 0);
1972 assert_eq!(builtin_num_fmts::INTEGER, 1);
1973 assert_eq!(builtin_num_fmts::DECIMAL_2, 2);
1974 assert_eq!(builtin_num_fmts::THOUSANDS, 3);
1975 assert_eq!(builtin_num_fmts::THOUSANDS_DECIMAL, 4);
1976 assert_eq!(builtin_num_fmts::PERCENT, 9);
1977 assert_eq!(builtin_num_fmts::PERCENT_DECIMAL, 10);
1978 assert_eq!(builtin_num_fmts::SCIENTIFIC, 11);
1979 assert_eq!(builtin_num_fmts::DATE_MDY, 14);
1980 assert_eq!(builtin_num_fmts::DATE_DMY, 15);
1981 assert_eq!(builtin_num_fmts::DATE_DM, 16);
1982 assert_eq!(builtin_num_fmts::DATE_MY, 17);
1983 assert_eq!(builtin_num_fmts::TIME_HM_AP, 18);
1984 assert_eq!(builtin_num_fmts::TIME_HMS_AP, 19);
1985 assert_eq!(builtin_num_fmts::TIME_HM, 20);
1986 assert_eq!(builtin_num_fmts::TIME_HMS, 21);
1987 assert_eq!(builtin_num_fmts::DATETIME, 22);
1988 assert_eq!(builtin_num_fmts::TEXT, 49);
1989 }
1990
1991 #[test]
1992 fn test_pattern_type_roundtrip() {
1993 let types = [
1994 PatternType::None,
1995 PatternType::Solid,
1996 PatternType::Gray125,
1997 PatternType::DarkGray,
1998 PatternType::MediumGray,
1999 PatternType::LightGray,
2000 ];
2001 for pt in &types {
2002 let s = pt.as_str();
2003 let back = PatternType::from_str(s);
2004 assert_eq!(*pt, back);
2005 }
2006 }
2007
2008 #[test]
2009 fn test_border_line_style_roundtrip() {
2010 let styles = [
2011 BorderLineStyle::Thin,
2012 BorderLineStyle::Medium,
2013 BorderLineStyle::Thick,
2014 BorderLineStyle::Dashed,
2015 BorderLineStyle::Dotted,
2016 BorderLineStyle::Double,
2017 BorderLineStyle::Hair,
2018 BorderLineStyle::MediumDashed,
2019 BorderLineStyle::DashDot,
2020 BorderLineStyle::MediumDashDot,
2021 BorderLineStyle::DashDotDot,
2022 BorderLineStyle::MediumDashDotDot,
2023 BorderLineStyle::SlantDashDot,
2024 ];
2025 for bls in &styles {
2026 let s = bls.as_str();
2027 let back = BorderLineStyle::from_str(s).unwrap();
2028 assert_eq!(*bls, back);
2029 }
2030 }
2031
2032 #[test]
2033 fn test_horizontal_align_roundtrip() {
2034 let aligns = [
2035 HorizontalAlign::General,
2036 HorizontalAlign::Left,
2037 HorizontalAlign::Center,
2038 HorizontalAlign::Right,
2039 HorizontalAlign::Fill,
2040 HorizontalAlign::Justify,
2041 HorizontalAlign::CenterContinuous,
2042 HorizontalAlign::Distributed,
2043 ];
2044 for ha in &aligns {
2045 let s = ha.as_str();
2046 let back = HorizontalAlign::from_str(s).unwrap();
2047 assert_eq!(*ha, back);
2048 }
2049 }
2050
2051 #[test]
2052 fn test_vertical_align_roundtrip() {
2053 let aligns = [
2054 VerticalAlign::Top,
2055 VerticalAlign::Center,
2056 VerticalAlign::Bottom,
2057 VerticalAlign::Justify,
2058 VerticalAlign::Distributed,
2059 ];
2060 for va in &aligns {
2061 let s = va.as_str();
2062 let back = VerticalAlign::from_str(s).unwrap();
2063 assert_eq!(*va, back);
2064 }
2065 }
2066
2067 #[test]
2068 fn test_style_color_rgb_roundtrip() {
2069 let color = StyleColor::Rgb("FF00FF00".to_string());
2070 let xml = style_color_to_xml(&color);
2071 let back = xml_color_to_style(&xml).unwrap();
2072 assert_eq!(color, back);
2073 }
2074
2075 #[test]
2076 fn test_style_color_theme_roundtrip() {
2077 let color = StyleColor::Theme(4);
2078 let xml = style_color_to_xml(&color);
2079 let back = xml_color_to_style(&xml).unwrap();
2080 assert_eq!(color, back);
2081 }
2082
2083 #[test]
2084 fn test_style_color_indexed_roundtrip() {
2085 let color = StyleColor::Indexed(10);
2086 let xml = style_color_to_xml(&color);
2087 let back = xml_color_to_style(&xml).unwrap();
2088 assert_eq!(color, back);
2089 }
2090
2091 #[test]
2092 fn test_font_deduplication() {
2093 let mut ss = default_stylesheet();
2094 let font = FontStyle {
2095 name: Some("Courier".to_string()),
2096 size: Some(10.0),
2097 bold: true,
2098 ..FontStyle::default()
2099 };
2100
2101 let id1 = add_or_find_font(&mut ss.fonts, &font);
2102 let id2 = add_or_find_font(&mut ss.fonts, &font);
2103 assert_eq!(id1, id2);
2104 assert_eq!(ss.fonts.fonts.len(), 2);
2106 }
2107
2108 #[test]
2109 fn test_multiple_custom_num_fmts() {
2110 let mut ss = default_stylesheet();
2111 let id1 = add_or_find_num_fmt(&mut ss, "0.0%");
2112 let id2 = add_or_find_num_fmt(&mut ss, "#,##0");
2113 assert_eq!(id1, 164);
2114 assert_eq!(id2, 165);
2115
2116 let id3 = add_or_find_num_fmt(&mut ss, "0.0%");
2118 assert_eq!(id3, 164);
2119 }
2120
2121 #[test]
2122 fn test_xf_count_maintained() {
2123 let mut ss = default_stylesheet();
2124 assert_eq!(ss.cell_xfs.count, Some(1));
2125
2126 let style = Style {
2127 font: Some(FontStyle {
2128 bold: true,
2129 ..FontStyle::default()
2130 }),
2131 ..Style::default()
2132 };
2133 add_style(&mut ss, &style).unwrap();
2134 assert_eq!(ss.cell_xfs.count, Some(2));
2135 }
2136
2137 #[test]
2140 fn test_style_builder_empty() {
2141 let style = StyleBuilder::new().build();
2142 assert!(style.font.is_none());
2143 assert!(style.fill.is_none());
2144 assert!(style.border.is_none());
2145 assert!(style.alignment.is_none());
2146 assert!(style.num_fmt.is_none());
2147 assert!(style.protection.is_none());
2148 }
2149
2150 #[test]
2151 fn test_style_builder_default_equivalent() {
2152 let style = StyleBuilder::default().build();
2153 assert!(style.font.is_none());
2154 assert!(style.fill.is_none());
2155 }
2156
2157 #[test]
2158 fn test_style_builder_font() {
2159 let style = StyleBuilder::new()
2160 .bold(true)
2161 .italic(true)
2162 .font_size(14.0)
2163 .font_name("Arial")
2164 .font_color_rgb("FF0000FF")
2165 .build();
2166 let font = style.font.unwrap();
2167 assert!(font.bold);
2168 assert!(font.italic);
2169 assert_eq!(font.size, Some(14.0));
2170 assert_eq!(font.name, Some("Arial".to_string()));
2171 assert_eq!(font.color, Some(StyleColor::Rgb("FF0000FF".to_string())));
2172 }
2173
2174 #[test]
2175 fn test_style_builder_font_underline_strikethrough() {
2176 let style = StyleBuilder::new()
2177 .underline(true)
2178 .strikethrough(true)
2179 .build();
2180 let font = style.font.unwrap();
2181 assert!(font.underline);
2182 assert!(font.strikethrough);
2183 }
2184
2185 #[test]
2186 fn test_style_builder_font_color_typed() {
2187 let style = StyleBuilder::new().font_color(StyleColor::Theme(4)).build();
2188 let font = style.font.unwrap();
2189 assert_eq!(font.color, Some(StyleColor::Theme(4)));
2190 }
2191
2192 #[test]
2193 fn test_style_builder_solid_fill() {
2194 let style = StyleBuilder::new().solid_fill("FFFF0000").build();
2195 let fill = style.fill.unwrap();
2196 assert_eq!(fill.pattern, PatternType::Solid);
2197 assert_eq!(fill.fg_color, Some(StyleColor::Rgb("FFFF0000".to_string())));
2198 }
2199
2200 #[test]
2201 fn test_style_builder_fill_pattern_and_colors() {
2202 let style = StyleBuilder::new()
2203 .fill_pattern(PatternType::Gray125)
2204 .fill_fg_color_rgb("FFAABBCC")
2205 .fill_bg_color(StyleColor::Indexed(64))
2206 .build();
2207 let fill = style.fill.unwrap();
2208 assert_eq!(fill.pattern, PatternType::Gray125);
2209 assert_eq!(fill.fg_color, Some(StyleColor::Rgb("FFAABBCC".to_string())));
2210 assert_eq!(fill.bg_color, Some(StyleColor::Indexed(64)));
2211 }
2212
2213 #[test]
2214 fn test_style_builder_border_individual_sides() {
2215 let style = StyleBuilder::new()
2216 .border_left(
2217 BorderLineStyle::Thin,
2218 StyleColor::Rgb("FF000000".to_string()),
2219 )
2220 .border_right(
2221 BorderLineStyle::Medium,
2222 StyleColor::Rgb("FF111111".to_string()),
2223 )
2224 .border_top(
2225 BorderLineStyle::Thick,
2226 StyleColor::Rgb("FF222222".to_string()),
2227 )
2228 .border_bottom(
2229 BorderLineStyle::Dashed,
2230 StyleColor::Rgb("FF333333".to_string()),
2231 )
2232 .build();
2233 let border = style.border.unwrap();
2234
2235 let left = border.left.unwrap();
2236 assert_eq!(left.style, BorderLineStyle::Thin);
2237 assert_eq!(left.color, Some(StyleColor::Rgb("FF000000".to_string())));
2238
2239 let right = border.right.unwrap();
2240 assert_eq!(right.style, BorderLineStyle::Medium);
2241
2242 let top = border.top.unwrap();
2243 assert_eq!(top.style, BorderLineStyle::Thick);
2244
2245 let bottom = border.bottom.unwrap();
2246 assert_eq!(bottom.style, BorderLineStyle::Dashed);
2247 }
2248
2249 #[test]
2250 fn test_style_builder_border_all() {
2251 let style = StyleBuilder::new()
2252 .border_all(
2253 BorderLineStyle::Thin,
2254 StyleColor::Rgb("FF000000".to_string()),
2255 )
2256 .build();
2257 let border = style.border.unwrap();
2258 assert!(border.left.is_some());
2259 assert!(border.right.is_some());
2260 assert!(border.top.is_some());
2261 assert!(border.bottom.is_some());
2262 assert!(border.diagonal.is_none());
2264
2265 let left = border.left.unwrap();
2266 assert_eq!(left.style, BorderLineStyle::Thin);
2267 assert_eq!(left.color, Some(StyleColor::Rgb("FF000000".to_string())));
2268 }
2269
2270 #[test]
2271 fn test_style_builder_alignment() {
2272 let style = StyleBuilder::new()
2273 .horizontal_align(HorizontalAlign::Center)
2274 .vertical_align(VerticalAlign::Center)
2275 .wrap_text(true)
2276 .text_rotation(45)
2277 .indent(2)
2278 .shrink_to_fit(true)
2279 .build();
2280 let align = style.alignment.unwrap();
2281 assert_eq!(align.horizontal, Some(HorizontalAlign::Center));
2282 assert_eq!(align.vertical, Some(VerticalAlign::Center));
2283 assert!(align.wrap_text);
2284 assert_eq!(align.text_rotation, Some(45));
2285 assert_eq!(align.indent, Some(2));
2286 assert!(align.shrink_to_fit);
2287 }
2288
2289 #[test]
2290 fn test_style_builder_num_format_builtin() {
2291 let style = StyleBuilder::new().num_format_builtin(2).build();
2292 match style.num_fmt.unwrap() {
2293 NumFmtStyle::Builtin(id) => assert_eq!(id, 2),
2294 _ => panic!("expected builtin format"),
2295 }
2296 }
2297
2298 #[test]
2299 fn test_style_builder_num_format_custom() {
2300 let style = StyleBuilder::new().num_format_custom("#,##0.00").build();
2301 match style.num_fmt.unwrap() {
2302 NumFmtStyle::Custom(fmt) => assert_eq!(fmt, "#,##0.00"),
2303 _ => panic!("expected custom format"),
2304 }
2305 }
2306
2307 #[test]
2308 fn test_style_builder_protection() {
2309 let style = StyleBuilder::new().locked(true).hidden(true).build();
2310 let prot = style.protection.unwrap();
2311 assert!(prot.locked);
2312 assert!(prot.hidden);
2313 }
2314
2315 #[test]
2316 fn test_style_builder_protection_unlock() {
2317 let style = StyleBuilder::new().locked(false).hidden(false).build();
2318 let prot = style.protection.unwrap();
2319 assert!(!prot.locked);
2320 assert!(!prot.hidden);
2321 }
2322
2323 #[test]
2324 fn test_style_builder_full_style() {
2325 let style = StyleBuilder::new()
2326 .bold(true)
2327 .font_size(12.0)
2328 .solid_fill("FFFFFF00")
2329 .border_all(
2330 BorderLineStyle::Thin,
2331 StyleColor::Rgb("FF000000".to_string()),
2332 )
2333 .horizontal_align(HorizontalAlign::Center)
2334 .num_format_builtin(2)
2335 .locked(true)
2336 .build();
2337 assert!(style.font.is_some());
2338 assert!(style.fill.is_some());
2339 assert!(style.border.is_some());
2340 assert!(style.alignment.is_some());
2341 assert!(style.num_fmt.is_some());
2342 assert!(style.protection.is_some());
2343
2344 assert!(style.font.as_ref().unwrap().bold);
2346 assert_eq!(style.font.as_ref().unwrap().size, Some(12.0));
2347 assert_eq!(style.fill.as_ref().unwrap().pattern, PatternType::Solid);
2348 }
2349
2350 #[test]
2351 fn test_style_builder_integrates_with_add_style() {
2352 let mut ss = default_stylesheet();
2353 let style = StyleBuilder::new()
2354 .bold(true)
2355 .font_size(11.0)
2356 .solid_fill("FFFF0000")
2357 .horizontal_align(HorizontalAlign::Center)
2358 .build();
2359
2360 let id = add_style(&mut ss, &style).unwrap();
2361 assert!(id > 0);
2362
2363 let retrieved = get_style(&ss, id).unwrap();
2365 assert!(retrieved.font.as_ref().unwrap().bold);
2366 assert_eq!(retrieved.fill.as_ref().unwrap().pattern, PatternType::Solid);
2367 assert_eq!(
2368 retrieved.alignment.as_ref().unwrap().horizontal,
2369 Some(HorizontalAlign::Center)
2370 );
2371 }
2372}