Skip to main content

office_rs/xlsx/styles/
mod.rs

1mod xml;
2
3pub use xml::*;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, PartialEq)]
7pub struct Font {
8    pub name: String,
9    pub size: f64,
10    pub bold: bool,
11    pub italic: bool,
12    pub underline: Option<UnderlineStyle>,
13    pub color: Option<Color>,
14}
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum UnderlineStyle {
18    Single,
19    Double,
20    SingleAccounting,
21    DoubleAccounting,
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub struct Color {
26    pub rgb: String, // ARGB format: "FFRRGGBB"
27}
28
29impl Color {
30    pub fn new(rgb: &str) -> Self {
31        Self {
32            rgb: rgb.to_string(),
33        }
34    }
35
36    pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
37        Self {
38            rgb: format!("FF{:02X}{:02X}{:02X}", r, g, b),
39        }
40    }
41
42    pub fn from_argb(a: u8, r: u8, g: u8, b: u8) -> Self {
43        Self {
44            rgb: format!("{:02X}{:02X}{:02X}{:02X}", a, r, g, b),
45        }
46    }
47
48    pub fn is_valid(&self) -> bool {
49        self.rgb.len() == 8 && self.rgb.chars().all(|c| c.is_ascii_hexdigit())
50    }
51
52    // 常用颜色常量
53    pub const BLACK: &'static str = "FF000000";
54    pub const WHITE: &'static str = "FFFFFFFF";
55    pub const RED: &'static str = "FFFF0000";
56    pub const GREEN: &'static str = "FF00FF00";
57    pub const BLUE: &'static str = "FF0000FF";
58    pub const GRAY: &'static str = "FF808080";
59
60    // 从常量创建颜色
61    pub fn black() -> Self {
62        Self::new(Self::BLACK)
63    }
64
65    pub fn white() -> Self {
66        Self::new(Self::WHITE)
67    }
68
69    pub fn red() -> Self {
70        Self::new(Self::RED)
71    }
72
73    pub fn green() -> Self {
74        Self::new(Self::GREEN)
75    }
76
77    pub fn blue() -> Self {
78        Self::new(Self::BLUE)
79    }
80
81    pub fn gray() -> Self {
82        Self::new(Self::GRAY)
83    }
84
85    // 获取颜色分量
86    pub fn get_components(&self) -> Option<(u8, u8, u8, u8)> {
87        if !self.is_valid() {
88            return None;
89        }
90
91        let a = u8::from_str_radix(&self.rgb[0..2], 16).ok()?;
92        let r = u8::from_str_radix(&self.rgb[2..4], 16).ok()?;
93        let g = u8::from_str_radix(&self.rgb[4..6], 16).ok()?;
94        let b = u8::from_str_radix(&self.rgb[6..8], 16).ok()?;
95
96        Some((a, r, g, b))
97    }
98
99    // 设置透明度
100    pub fn with_alpha(&self, alpha: u8) -> Self {
101        if let Some((_, r, g, b)) = self.get_components() {
102            Self::from_argb(alpha, r, g, b)
103        } else {
104            self.clone()
105        }
106    }
107
108    // 检查颜色是否完全透明
109    pub fn is_transparent(&self) -> bool {
110        self.get_components()
111            .map(|(a, _, _, _)| a == 0)
112            .unwrap_or(false)
113    }
114}
115
116#[derive(Debug, Clone, PartialEq)]
117pub struct Border {
118    pub left: BorderStyle,
119    pub right: BorderStyle,
120    pub top: BorderStyle,
121    pub bottom: BorderStyle,
122    pub diagonal: BorderStyle,
123}
124
125#[derive(Debug, Clone, PartialEq)]
126pub struct BorderStyle {
127    pub style: Option<LineStyle>,
128    pub color: Option<Color>,
129}
130
131#[derive(Debug, Clone, PartialEq)]
132pub enum LineStyle {
133    None,
134    Thin,
135    Medium,
136    Thick,
137    Double,
138    Dotted,
139    Dashed,
140}
141
142#[derive(Debug, Clone, PartialEq)]
143pub struct Fill {
144    pub pattern_type: PatternType,
145    pub fg_color: Option<Color>,
146    pub bg_color: Option<Color>,
147}
148
149#[derive(Debug, Clone, PartialEq)]
150pub enum PatternType {
151    None,
152    Solid,
153    MediumGray,
154    DarkGray,
155    LightGray,
156    // 可以添加更多填充模式
157}
158
159#[derive(Debug, Clone, PartialEq)]
160pub struct Alignment {
161    pub horizontal: HorizontalAlignment,
162    pub vertical: VerticalAlignment,
163    pub wrap_text: bool,
164    pub text_rotation: i32,
165    pub indent: u32,
166}
167
168#[derive(Debug, Clone, PartialEq)]
169pub enum HorizontalAlignment {
170    Left,
171    Center,
172    Right,
173    Fill,
174    Justify,
175    CenterContinuous,
176    Distributed,
177}
178
179#[derive(Debug, Clone, PartialEq)]
180pub enum VerticalAlignment {
181    Top,
182    Center,
183    Bottom,
184    Justify,
185    Distributed,
186}
187
188#[derive(Debug, Clone)]
189pub struct CellStyle {
190    pub font: Option<Font>,
191    pub fill: Option<Fill>,
192    pub border: Option<Border>,
193    pub alignment: Option<Alignment>,
194    pub number_format: Option<String>,
195    pub protection: Option<Protection>,
196}
197
198#[derive(Debug, Clone, PartialEq)]
199pub struct Protection {
200    pub locked: bool,
201    pub hidden: bool,
202}
203
204pub struct StylesManager {
205    styles: Vec<CellStyle>,
206    style_index_map: HashMap<String, usize>,
207}
208
209impl StylesManager {
210    pub fn new() -> Self {
211        Self {
212            styles: Vec::new(),
213            style_index_map: HashMap::new(),
214        }
215    }
216
217    pub fn add_style(&mut self, style: CellStyle) -> usize {
218        let style_hash = self.compute_style_hash(&style);
219        if let Some(&index) = self.style_index_map.get(&style_hash) {
220            return index;
221        }
222
223        let index = self.styles.len();
224        self.styles.push(style);
225        self.style_index_map.insert(style_hash, index);
226        index
227    }
228
229    pub fn get_style(&self, index: usize) -> Option<&CellStyle> {
230        self.styles.get(index)
231    }
232
233    fn compute_style_hash(&self, style: &CellStyle) -> String {
234        use std::collections::hash_map::DefaultHasher;
235        use std::hash::{ Hash, Hasher };
236
237        let mut hasher = DefaultHasher::new();
238
239        // Hash font properties
240        if let Some(font) = &style.font {
241            font.name.hash(&mut hasher);
242            font.size.to_bits().hash(&mut hasher);
243            font.bold.hash(&mut hasher);
244            font.italic.hash(&mut hasher);
245            if let Some(color) = &font.color {
246                color.rgb.hash(&mut hasher);
247            }
248        }
249
250        // Hash fill properties
251        if let Some(fill) = &style.fill {
252            std::mem::discriminant(&fill.pattern_type).hash(&mut hasher);
253            if let Some(fg) = &fill.fg_color {
254                fg.rgb.hash(&mut hasher);
255            }
256            if let Some(bg) = &fill.bg_color {
257                bg.rgb.hash(&mut hasher);
258            }
259        }
260
261        // Hash border properties
262        if let Some(border) = &style.border {
263            for side in [
264                &border.left,
265                &border.right,
266                &border.top,
267                &border.bottom,
268                &border.diagonal,
269            ] {
270                if let Some(style) = &side.style {
271                    std::mem::discriminant(style).hash(&mut hasher);
272                }
273                if let Some(color) = &side.color {
274                    color.rgb.hash(&mut hasher);
275                }
276            }
277        }
278
279        // Hash alignment properties
280        if let Some(align) = &style.alignment {
281            std::mem::discriminant(&align.horizontal).hash(&mut hasher);
282            std::mem::discriminant(&align.vertical).hash(&mut hasher);
283            align.wrap_text.hash(&mut hasher);
284            align.text_rotation.hash(&mut hasher);
285            align.indent.hash(&mut hasher);
286        }
287
288        format!("{:016x}", hasher.finish())
289    }
290}
291
292impl Default for CellStyle {
293    fn default() -> Self {
294        Self {
295            font: None,
296            fill: None,
297            border: None,
298            alignment: None,
299            number_format: None,
300            protection: None,
301        }
302    }
303}
304
305impl CellStyle {
306    pub fn new() -> Self {
307        Self::default()
308    }
309
310    pub fn with_font(mut self, font: Font) -> Self {
311        self.font = Some(font);
312        self
313    }
314
315    pub fn with_fill(mut self, fill: Fill) -> Self {
316        self.fill = Some(fill);
317        self
318    }
319
320    pub fn with_border(mut self, border: Border) -> Self {
321        self.border = Some(border);
322        self
323    }
324
325    pub fn with_alignment(mut self, alignment: Alignment) -> Self {
326        self.alignment = Some(alignment);
327        self
328    }
329
330    pub fn with_number_format(mut self, format: String) -> Self {
331        self.number_format = Some(format);
332        self
333    }
334
335    pub fn with_protection(mut self, protection: Protection) -> Self {
336        self.protection = Some(protection);
337        self
338    }
339
340    pub fn default_header() -> Self {
341        Self::new()
342            .with_font(Font {
343                name: "Arial".to_string(),
344                size: 12.0,
345                bold: true,
346                italic: false,
347                underline: None,
348                color: Some(Color::from_rgb(0, 0, 0)),
349            })
350            .with_alignment(Alignment {
351                horizontal: HorizontalAlignment::Center,
352                vertical: VerticalAlignment::Center,
353                wrap_text: true,
354                text_rotation: 0,
355                indent: 0,
356            })
357            .with_border(Border {
358                left: BorderStyle {
359                    style: Some(LineStyle::Thin),
360                    color: Some(Color::from_rgb(0, 0, 0)),
361                },
362                right: BorderStyle {
363                    style: Some(LineStyle::Thin),
364                    color: Some(Color::from_rgb(0, 0, 0)),
365                },
366                top: BorderStyle {
367                    style: Some(LineStyle::Thin),
368                    color: Some(Color::from_rgb(0, 0, 0)),
369                },
370                bottom: BorderStyle {
371                    style: Some(LineStyle::Thin),
372                    color: Some(Color::from_rgb(0, 0, 0)),
373                },
374                diagonal: BorderStyle {
375                    style: None,
376                    color: None,
377                },
378            })
379    }
380
381    pub fn default_body() -> Self {
382        Self::new()
383            .with_font(Font {
384                name: "Arial".to_string(),
385                size: 11.0,
386                bold: false,
387                italic: false,
388                underline: None,
389                color: Some(Color::from_rgb(0, 0, 0)),
390            })
391            .with_alignment(Alignment {
392                horizontal: HorizontalAlignment::Left,
393                vertical: VerticalAlignment::Center,
394                wrap_text: false,
395                text_rotation: 0,
396                indent: 0,
397            })
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404
405    #[test]
406    fn test_color_creation() {
407        let color = Color::from_rgb(255, 128, 0);
408        assert_eq!(color.rgb, "FFFF8000");
409
410        let color = Color::from_argb(128, 255, 128, 0);
411        assert_eq!(color.rgb, "80FF8000");
412    }
413
414    #[test]
415    fn test_style_manager() {
416        let mut manager = StylesManager::new();
417
418        let style1 = CellStyle::default_header();
419        let style2 = CellStyle::default_header();
420        let style3 = CellStyle::default_body();
421
422        let index1 = manager.add_style(style1.clone());
423        let index2 = manager.add_style(style2);
424        let index3 = manager.add_style(style3);
425
426        // Same styles should return same index
427        assert_eq!(index1, index2);
428        // Different styles should have different indices
429        assert_ne!(index1, index3);
430    }
431
432    #[test]
433    fn test_color_components() {
434        let color = Color::from_argb(128, 255, 128, 0);
435        assert_eq!(color.get_components(), Some((128, 255, 128, 0)));
436    }
437
438    #[test]
439    fn test_color_constants() {
440        assert_eq!(Color::black().rgb, "FF000000");
441        assert_eq!(Color::white().rgb, "FFFFFFFF");
442        assert_eq!(Color::red().rgb, "FFFF0000");
443    }
444
445    #[test]
446    fn test_color_transparency() {
447        let color = Color::red();
448        assert!(!color.is_transparent());
449
450        let transparent = color.with_alpha(0);
451        assert!(transparent.is_transparent());
452    }
453
454    #[test]
455    fn test_invalid_color() {
456        let invalid_color = Color::new("invalid");
457        assert!(!invalid_color.is_valid());
458        assert_eq!(invalid_color.get_components(), None);
459    }
460}