Skip to main content

ironcalc_base/
styles.rs

1use crate::{
2    model::Model,
3    number_format::{get_default_num_fmt_id, get_new_num_fmt_index, get_num_fmt},
4    types::{
5        Border, CellStyleXfs, CellStyles, CellXfs, Dxf, Fill, Font, NumFmt, Style, StyleIncludes,
6        Styles,
7    },
8};
9
10impl Styles {
11    fn get_font_index(&self, font: &Font) -> Option<i32> {
12        for (font_index, item) in self.fonts.iter().enumerate() {
13            if item == font {
14                return Some(font_index as i32);
15            }
16        }
17        None
18    }
19    fn get_fill_index(&self, fill: &Fill) -> Option<i32> {
20        for (fill_index, item) in self.fills.iter().enumerate() {
21            if item == fill {
22                return Some(fill_index as i32);
23            }
24        }
25        None
26    }
27    fn get_border_index(&self, border: &Border) -> Option<i32> {
28        for (border_index, item) in self.borders.iter().enumerate() {
29            if item == border {
30                return Some(border_index as i32);
31            }
32        }
33        None
34    }
35    fn get_num_fmt_index(&self, format_code: &str) -> Option<i32> {
36        if let Some(index) = get_default_num_fmt_id(format_code) {
37            return Some(index);
38        }
39        for item in self.num_fmts.iter() {
40            if item.format_code == format_code {
41                return Some(item.num_fmt_id);
42            }
43        }
44        None
45    }
46
47    // Returns `(num_fmt_id, font_id, fill_id, border_id)` for the style,
48    // adding any missing components to the pools.
49    fn get_or_create_component_ids(&mut self, style: &Style) -> (i32, i32, i32, i32) {
50        let font = &style.font;
51        let font_id = if let Some(index) = self.get_font_index(font) {
52            index
53        } else {
54            self.fonts.push(font.clone());
55            self.fonts.len() as i32 - 1
56        };
57        let fill = &style.fill;
58        let fill_id = if let Some(index) = self.get_fill_index(fill) {
59            index
60        } else {
61            self.fills.push(fill.clone());
62            self.fills.len() as i32 - 1
63        };
64        let border = &style.border;
65        let border_id = if let Some(index) = self.get_border_index(border) {
66            index
67        } else {
68            self.borders.push(border.clone());
69            self.borders.len() as i32 - 1
70        };
71        let num_fmt = &style.num_fmt;
72        let num_fmt_id;
73        if let Some(index) = self.get_num_fmt_index(num_fmt) {
74            num_fmt_id = index;
75        } else {
76            num_fmt_id = get_new_num_fmt_index(&self.num_fmts);
77            self.num_fmts.push(NumFmt {
78                format_code: num_fmt.to_string(),
79                num_fmt_id,
80            });
81        }
82        (num_fmt_id, font_id, fill_id, border_id)
83    }
84
85    // Creates the base record of a named style: a new entry in `cell_style_xfs`
86    // together with its "plain representative" in `cell_xfs` (the xf cells get
87    // when the style is applied to them). Returns the new `xf_id`.
88    fn create_base_style(&mut self, style: &Style, includes: StyleIncludes) -> i32 {
89        let (num_fmt_id, font_id, fill_id, border_id) = self.get_or_create_component_ids(style);
90        // The apply* flags on a cellStyleXfs record mark which formatting
91        // categories the style includes.
92        self.cell_style_xfs.push(CellStyleXfs {
93            num_fmt_id,
94            font_id,
95            fill_id,
96            border_id,
97            apply_number_format: includes.number_format,
98            apply_font: includes.font,
99            apply_fill: includes.fill,
100            apply_border: includes.border,
101            apply_alignment: includes.alignment,
102            apply_protection: includes.protection,
103        });
104        let xf_id = self.cell_style_xfs.len() as i32 - 1;
105        self.cell_xfs.push(CellXfs {
106            xf_id,
107            num_fmt_id,
108            font_id,
109            fill_id,
110            border_id,
111            apply_number_format: false,
112            apply_border: false,
113            apply_alignment: false,
114            apply_protection: false,
115            apply_font: false,
116            apply_fill: false,
117            // A quote prefix marks a cell's own apostrophe-escaped content;
118            // it is not a named-style category in Excel and is normalized out.
119            quote_prefix: false,
120            alignment: style.alignment.clone(),
121        });
122        xf_id
123    }
124
125    pub fn create_new_style(&mut self, style: &Style) -> i32 {
126        let (num_fmt_id, font_id, fill_id, border_id) = self.get_or_create_component_ids(style);
127        self.cell_xfs.push(CellXfs {
128            xf_id: 0,
129            num_fmt_id,
130            font_id,
131            fill_id,
132            border_id,
133            apply_number_format: false,
134            apply_border: false,
135            apply_alignment: false,
136            apply_protection: false,
137            apply_font: false,
138            apply_fill: false,
139            quote_prefix: style.quote_prefix,
140            alignment: style.alignment.clone(),
141        });
142        self.cell_xfs.len() as i32 - 1
143    }
144
145    pub fn get_style_index(&self, style: &Style) -> Option<i32> {
146        for (index, cell_xf) in self.cell_xfs.iter().enumerate() {
147            // Only anonymous formats qualify: an xf parented to a named style
148            // (xf_id != 0) changes when the style is updated, so visually equal
149            // formatting must not be deduplicated into it.
150            if cell_xf.xf_id != 0 {
151                continue;
152            }
153            let border_id = cell_xf.border_id as usize;
154            let fill_id = cell_xf.fill_id as usize;
155            let font_id = cell_xf.font_id as usize;
156            let num_fmt_id = cell_xf.num_fmt_id;
157            let quote_prefix = cell_xf.quote_prefix;
158            if style
159                == &(Style {
160                    alignment: cell_xf.alignment.clone(),
161                    num_fmt: get_num_fmt(num_fmt_id, &self.num_fmts),
162                    fill: self.fills[fill_id].clone(),
163                    font: self.fonts[font_id].clone(),
164                    border: self.borders[border_id].clone(),
165                    quote_prefix,
166                })
167            {
168                return Some(index as i32);
169            }
170        }
171        None
172    }
173
174    pub(crate) fn get_style_index_or_create(&mut self, style: &Style) -> i32 {
175        // Check if style exist. If so sets style cell number to that otherwise create a new style.
176        if let Some(index) = self.get_style_index(style) {
177            index
178        } else {
179            self.create_new_style(style)
180        }
181    }
182
183    /// Adds a named cell style pointing to an existing `cell_style_xfs` record.
184    /// Fails if the named style already exists or if `xf_id` is not a valid index.
185    pub(crate) fn add_named_cell_style(
186        &mut self,
187        style_name: &str,
188        xf_id: i32,
189    ) -> Result<(), String> {
190        if self.get_xf_id_by_name(style_name).is_ok() {
191            return Err("A style with that name already exists".to_string());
192        }
193        if xf_id < 0 || xf_id as usize >= self.cell_style_xfs.len() {
194            return Err("There is no cell style xf with that index".to_string());
195        }
196        let cell_style = CellStyles {
197            name: style_name.to_string(),
198            xf_id,
199            builtin_id: 0,
200        };
201        self.cell_styles.push(cell_style);
202        Ok(())
203    }
204
205    // Returns the `xf_id` (index into `cell_style_xfs`) of the named style or fails.
206    // NB: this method is case sensitive
207    pub(crate) fn get_xf_id_by_name(&self, style_name: &str) -> Result<i32, String> {
208        for cell_style in &self.cell_styles {
209            if cell_style.name == style_name {
210                return Ok(cell_style.xf_id);
211            }
212        }
213        Err(format!("Style '{style_name}' not found"))
214    }
215
216    // Returns the index in `cell_xfs` of the "plain representative" of the named
217    // style: an xf parented to the style's `xf_id` carrying no local overrides.
218    // This is the index cells get when the style is applied to them.
219    // NB: this method is case sensitive
220    pub fn get_style_index_by_name(&self, style_name: &str) -> Result<i32, String> {
221        let xf_id = self.get_xf_id_by_name(style_name)?;
222        for (index, cell_xf) in self.cell_xfs.iter().enumerate() {
223            if cell_xf.xf_id == xf_id && !Self::cell_xf_has_overrides(cell_xf) {
224                return Ok(index as i32);
225            }
226        }
227        Err(format!("Style '{style_name}' has no plain cell xf"))
228    }
229
230    fn cell_xf_has_overrides(cell_xf: &CellXfs) -> bool {
231        cell_xf.apply_number_format
232            || cell_xf.apply_font
233            || cell_xf.apply_fill
234            || cell_xf.apply_border
235            || cell_xf.apply_alignment
236            || cell_xf.apply_protection
237            // A quote prefix is a cell's own apostrophe-escaped state, never
238            // part of a named style, so such an xf is not a plain representative.
239            || cell_xf.quote_prefix
240    }
241
242    // Same as `get_style_index_by_name` but creates the plain representative if
243    // the workbook lacks one (e.g. an imported named style applied to no cell).
244    pub(crate) fn get_or_create_style_index_by_name(
245        &mut self,
246        style_name: &str,
247    ) -> Result<i32, String> {
248        if let Ok(index) = self.get_style_index_by_name(style_name) {
249            return Ok(index);
250        }
251        let xf_id = self.get_xf_id_by_name(style_name)?;
252        let style_xf = self
253            .cell_style_xfs
254            .get(xf_id as usize)
255            .ok_or_else(|| format!("Style '{style_name}' points to an invalid xf id"))?;
256        self.cell_xfs.push(CellXfs {
257            xf_id,
258            num_fmt_id: style_xf.num_fmt_id,
259            font_id: style_xf.font_id,
260            fill_id: style_xf.fill_id,
261            border_id: style_xf.border_id,
262            apply_number_format: false,
263            apply_border: false,
264            apply_alignment: false,
265            apply_protection: false,
266            apply_font: false,
267            apply_fill: false,
268            quote_prefix: false,
269            alignment: None,
270        });
271        Ok(self.cell_xfs.len() as i32 - 1)
272    }
273
274    // Returns the index in `cell_xfs` that results from applying the named
275    // style on top of the format at `current_index` (Excel semantics):
276    // * categories the style includes take the style's components and stay
277    //   inherited (`apply_* = false`);
278    // * the rest keep the current format's components, marked as the cell's
279    //   own (`apply_* = true`);
280    // * the current quote prefix is always kept — it belongs to the cell's
281    //   content, not to the style.
282    // An existing identical xf is reused, otherwise one is created.
283    pub(crate) fn get_style_index_for_applied_style(
284        &mut self,
285        style_name: &str,
286        current_index: i32,
287    ) -> Result<i32, String> {
288        let xf_id = self.get_xf_id_by_name(style_name)?;
289        let record = self
290            .cell_style_xfs
291            .get(xf_id as usize)
292            .ok_or_else(|| format!("Style '{style_name}' points to an invalid xf id"))?
293            .clone();
294        let current = self
295            .cell_xfs
296            .get(current_index as usize)
297            .ok_or("Invalid index provided".to_string())?
298            .clone();
299        // The style's alignment lives on its plain representative.
300        let style_alignment = self
301            .get_style_index_by_name(style_name)
302            .ok()
303            .and_then(|index| self.cell_xfs[index as usize].alignment.clone());
304
305        let new_xf = CellXfs {
306            xf_id,
307            num_fmt_id: if record.apply_number_format {
308                record.num_fmt_id
309            } else {
310                current.num_fmt_id
311            },
312            font_id: if record.apply_font {
313                record.font_id
314            } else {
315                current.font_id
316            },
317            fill_id: if record.apply_fill {
318                record.fill_id
319            } else {
320                current.fill_id
321            },
322            border_id: if record.apply_border {
323                record.border_id
324            } else {
325                current.border_id
326            },
327            alignment: if record.apply_alignment {
328                style_alignment
329            } else {
330                current.alignment.clone()
331            },
332            apply_number_format: !record.apply_number_format,
333            apply_font: !record.apply_font,
334            apply_fill: !record.apply_fill,
335            apply_border: !record.apply_border,
336            apply_alignment: !record.apply_alignment,
337            apply_protection: !record.apply_protection,
338            quote_prefix: current.quote_prefix,
339        };
340        if let Some(index) = self.cell_xfs.iter().position(|xf| xf == &new_xf) {
341            return Ok(index as i32);
342        }
343        self.cell_xfs.push(new_xf);
344        Ok(self.cell_xfs.len() as i32 - 1)
345    }
346
347    /// Returns which formatting categories the named style includes
348    /// (the `apply*` flags of its `cellStyleXfs` record).
349    pub fn get_style_includes(&self, style_name: &str) -> Result<StyleIncludes, String> {
350        let xf_id = self.get_xf_id_by_name(style_name)?;
351        let record = self
352            .cell_style_xfs
353            .get(xf_id as usize)
354            .ok_or_else(|| format!("Style '{style_name}' points to an invalid xf id"))?;
355        Ok(StyleIncludes {
356            number_format: record.apply_number_format,
357            font: record.apply_font,
358            fill: record.apply_fill,
359            border: record.apply_border,
360            alignment: record.apply_alignment,
361            protection: record.apply_protection,
362        })
363    }
364
365    // Returns the `Style` of a named style. Reads the plain representative in
366    // `cell_xfs` when there is one (it also carries alignment); otherwise the
367    // style is reconstructed from its `cell_style_xfs` record.
368    pub(crate) fn get_style_by_name(&self, style_name: &str) -> Result<Style, String> {
369        if let Ok(index) = self.get_style_index_by_name(style_name) {
370            return self.get_style(index);
371        }
372        let xf_id = self.get_xf_id_by_name(style_name)?;
373        let style_xf = self
374            .cell_style_xfs
375            .get(xf_id as usize)
376            .ok_or_else(|| format!("Style '{style_name}' points to an invalid xf id"))?;
377        Ok(Style {
378            alignment: None,
379            num_fmt: get_num_fmt(style_xf.num_fmt_id, &self.num_fmts),
380            fill: self.fills[style_xf.fill_id as usize].clone(),
381            font: self.fonts[style_xf.font_id as usize].clone(),
382            border: self.borders[style_xf.border_id as usize].clone(),
383            quote_prefix: false,
384        })
385    }
386
387    /// Creates a named style. `includes` selects which formatting categories
388    /// the style carries (Excel's "Style Includes" checkboxes); use
389    /// `StyleIncludes::default()` for a full style. `style.quote_prefix` is
390    /// ignored: a quote prefix belongs to a cell's content, not to a named style.
391    pub fn create_named_style(
392        &mut self,
393        style_name: &str,
394        style: &Style,
395        includes: StyleIncludes,
396    ) -> Result<(), String> {
397        if self.get_xf_id_by_name(style_name).is_ok() {
398            return Err("A style with that name already exists".to_string());
399        }
400        let xf_id = self.create_base_style(style, includes);
401        self.add_named_cell_style(style_name, xf_id)
402    }
403
404    /// Returns the names of all named styles
405    pub fn get_named_style_list(&self) -> Vec<String> {
406        self.cell_styles.iter().map(|cs| cs.name.clone()).collect()
407    }
408
409    /// Returns true if the named style is built-in and cannot be deleted or modified.
410    /// In OOXML, only "Normal" has builtinId=0; all other built-ins have builtinId > 0.
411    /// Custom styles also have builtinId=0 (absent attribute), so we distinguish by name for the zero case.
412    pub fn is_builtin_style(&self, style_name: &str) -> bool {
413        self.cell_styles.iter().any(|cs| {
414            cs.name == style_name && (cs.builtin_id > 0 || cs.name.eq_ignore_ascii_case("Normal"))
415        })
416    }
417
418    /// Removes a named style entry. Does not remove the underlying cell_xfs entry.
419    /// Fails if the style does not exist or is a built-in.
420    pub(crate) fn delete_named_style_entry(&mut self, style_name: &str) -> Result<(), String> {
421        let pos = self
422            .cell_styles
423            .iter()
424            .position(|cs| cs.name == style_name)
425            .ok_or_else(|| format!("Style '{style_name}' not found"))?;
426        self.cell_styles.remove(pos);
427        Ok(())
428    }
429
430    /// Renames an existing named style entry. Its `xf_id` never changes.
431    /// Fails if the style does not exist.
432    pub(crate) fn rename_named_style_entry(
433        &mut self,
434        style_name: &str,
435        new_name: &str,
436    ) -> Result<(), String> {
437        let cs = self
438            .cell_styles
439            .iter_mut()
440            .find(|cs| cs.name == style_name)
441            .ok_or_else(|| format!("Style '{style_name}' not found"))?;
442        cs.name = new_name.to_string();
443        Ok(())
444    }
445
446    // Returns the style index of the style with `quote_prefix=true`
447    // If there is no such style it creates it
448    // TODO: It needs to be mutable, the name could reflect that
449    pub(crate) fn get_style_with_quote_prefix(&mut self, index: i32) -> Result<i32, String> {
450        let mut style = self.get_style(index)?;
451        style.quote_prefix = true;
452        Ok(self.get_style_index_or_create(&style))
453    }
454
455    // Returns the index of the style with the provided format.
456    // If there is no style with that format, it creates a new one based on the style with the provided index.
457    // TODO: It needs to be mutable, the name could reflect that
458    pub(crate) fn get_style_with_format(
459        &mut self,
460        index: i32,
461        num_fmt: &str,
462    ) -> Result<i32, String> {
463        let mut style = self.get_style(index)?;
464        style.num_fmt = num_fmt.to_string();
465        Ok(self.get_style_index_or_create(&style))
466    }
467
468    // Returns the style index of the style with `quote_prefix=false`
469    // If there is no such style it creates it
470    // TODO: It needs to be mutable, the name could reflect that
471    pub(crate) fn get_style_without_quote_prefix(&mut self, index: i32) -> Result<i32, String> {
472        let mut style = self.get_style(index)?;
473        style.quote_prefix = false;
474        Ok(self.get_style_index_or_create(&style))
475    }
476
477    pub(crate) fn style_is_quote_prefix(&self, index: i32) -> bool {
478        let cell_xf = &self.cell_xfs[index as usize];
479        cell_xf.quote_prefix
480    }
481
482    pub(crate) fn get_style(&self, index: i32) -> Result<Style, String> {
483        let cell_xf = &self
484            .cell_xfs
485            .get(index as usize)
486            .ok_or("Invalid index provided".to_string())?;
487        let border_id = cell_xf.border_id as usize;
488        let fill_id = cell_xf.fill_id as usize;
489        let font_id = cell_xf.font_id as usize;
490        let num_fmt_id = cell_xf.num_fmt_id;
491        let quote_prefix = cell_xf.quote_prefix;
492        let alignment = cell_xf.alignment.clone();
493
494        Ok(Style {
495            alignment,
496            num_fmt: get_num_fmt(num_fmt_id, &self.num_fmts),
497            fill: self.fills[fill_id].clone(),
498            font: self.fonts[font_id].clone(),
499            border: self.borders[border_id].clone(),
500            quote_prefix,
501        })
502    }
503}
504
505// TODO: Try to find a better spot for styles setters
506impl<'a> Model<'a> {
507    pub fn set_cell_style(
508        &mut self,
509        sheet: u32,
510        row: i32,
511        column: i32,
512        style: &Style,
513    ) -> Result<(), String> {
514        let style_index = self.workbook.styles.get_style_index_or_create(style);
515        self.workbook
516            .worksheet_mut(sheet)?
517            .set_cell_style(row, column, style_index)
518    }
519
520    pub fn copy_cell_style(
521        &mut self,
522        source_cell: (u32, i32, i32),
523        destination_cell: (u32, i32, i32),
524    ) -> Result<(), String> {
525        let source_style_index = self
526            .workbook
527            .worksheet(source_cell.0)?
528            .get_style(source_cell.1, source_cell.2);
529
530        self.workbook
531            .worksheet_mut(destination_cell.0)?
532            .set_cell_style(destination_cell.1, destination_cell.2, source_style_index)
533    }
534
535    /// Applies the named style "style_name" to the cell. Only the categories
536    /// the style includes are stamped; the rest of the cell's formatting is
537    /// kept as local overrides.
538    pub fn set_cell_style_by_name(
539        &mut self,
540        sheet: u32,
541        row: i32,
542        column: i32,
543        style_name: &str,
544    ) -> Result<(), String> {
545        let current_index = self.workbook.worksheet(sheet)?.get_style(row, column);
546        let style_index = self
547            .workbook
548            .styles
549            .get_style_index_for_applied_style(style_name, current_index)?;
550        self.workbook
551            .worksheet_mut(sheet)?
552            .set_cell_style(row, column, style_index)
553    }
554
555    pub fn set_sheet_style(&mut self, sheet: u32, style_name: &str) -> Result<(), String> {
556        let style_index = self
557            .workbook
558            .styles
559            .get_or_create_style_index_by_name(style_name)?;
560        self.workbook.worksheet_mut(sheet)?.set_style(style_index)?;
561        Ok(())
562    }
563
564    pub fn set_sheet_row_style(
565        &mut self,
566        sheet: u32,
567        row: i32,
568        style_name: &str,
569    ) -> Result<(), String> {
570        let style_index = self
571            .workbook
572            .styles
573            .get_or_create_style_index_by_name(style_name)?;
574        self.workbook
575            .worksheet_mut(sheet)?
576            .set_row_style(row, style_index)?;
577        Ok(())
578    }
579
580    pub fn set_sheet_column_style(
581        &mut self,
582        sheet: u32,
583        column: i32,
584        style_name: &str,
585    ) -> Result<(), String> {
586        let style_index = self
587            .workbook
588            .styles
589            .get_or_create_style_index_by_name(style_name)?;
590        self.workbook
591            .worksheet_mut(sheet)?
592            .set_column_style(column, style_index)?;
593        Ok(())
594    }
595
596    /// Returns the list of all named style names.
597    pub fn get_named_style_list(&self) -> Vec<String> {
598        self.workbook.styles.get_named_style_list()
599    }
600
601    /// Returns the `Style` associated with the named style.
602    pub fn get_named_style(&self, name: &str) -> Result<Style, String> {
603        self.workbook.styles.get_style_by_name(name)
604    }
605
606    /// Returns which formatting categories the named style includes.
607    pub fn get_named_style_includes(&self, name: &str) -> Result<StyleIncludes, String> {
608        self.workbook.styles.get_style_includes(name)
609    }
610
611    /// Creates a new named style. Fails if a style with that name already exists.
612    pub fn create_named_style(
613        &mut self,
614        name: &str,
615        style: &Style,
616        includes: StyleIncludes,
617    ) -> Result<(), String> {
618        self.workbook
619            .styles
620            .create_named_style(name, style, includes)
621    }
622
623    /// Deletes a named style. Fails if the style does not exist or is built-in.
624    /// Cells that used this style keep their formatting; only the name association is removed.
625    pub fn delete_named_style(&mut self, name: &str) -> Result<(), String> {
626        if self.workbook.styles.is_builtin_style(name) {
627            return Err(format!("Cannot delete built-in style '{name}'"));
628        }
629        self.workbook.styles.delete_named_style_entry(name)
630    }
631
632    /// Updates the formatting and optionally the name of a named style.
633    /// The style's `xf_id` never changes:
634    /// * The style's `cell_style_xfs` record is rewritten in place.
635    /// * The new components are propagated to every `cell_xfs` entry parented to
636    ///   the style, except those a cell overrides locally (its `apply_*` flags).
637    ///
638    /// Cells keep their style index, so they pick up the new formatting without
639    /// being touched. `style.quote_prefix` is ignored: a quote prefix belongs
640    /// to a cell's content, not to a named style, and is never propagated.
641    ///
642    /// `includes` replaces the style's category set (the record's `apply*`
643    /// flags). It governs exporting and future applications of the style;
644    /// dependent cells keep their own `apply_*` ownership flags, so changing
645    /// the includes never rewrites which components a cell owns.
646    pub fn update_named_style(
647        &mut self,
648        name: &str,
649        new_name: &str,
650        style: &Style,
651        includes: StyleIncludes,
652    ) -> Result<(), String> {
653        let styles = &mut self.workbook.styles;
654        if styles.is_builtin_style(name) {
655            return Err(format!("Cannot modify built-in style '{name}'"));
656        }
657        let xf_id = styles.get_xf_id_by_name(name)?;
658        if name != new_name && styles.get_xf_id_by_name(new_name).is_ok() {
659            return Err(format!("A style named '{new_name}' already exists"));
660        }
661        if xf_id < 0 || xf_id as usize >= styles.cell_style_xfs.len() {
662            return Err(format!("Style '{name}' points to an invalid xf id"));
663        }
664
665        let (num_fmt_id, font_id, fill_id, border_id) = styles.get_or_create_component_ids(style);
666
667        let record = &mut styles.cell_style_xfs[xf_id as usize];
668        record.num_fmt_id = num_fmt_id;
669        record.font_id = font_id;
670        record.fill_id = fill_id;
671        record.border_id = border_id;
672        record.apply_number_format = includes.number_format;
673        record.apply_font = includes.font;
674        record.apply_fill = includes.fill;
675        record.apply_border = includes.border;
676        record.apply_alignment = includes.alignment;
677        record.apply_protection = includes.protection;
678
679        for cell_xf in styles.cell_xfs.iter_mut().filter(|xf| xf.xf_id == xf_id) {
680            if !cell_xf.apply_number_format {
681                cell_xf.num_fmt_id = num_fmt_id;
682            }
683            if !cell_xf.apply_font {
684                cell_xf.font_id = font_id;
685            }
686            if !cell_xf.apply_fill {
687                cell_xf.fill_id = fill_id;
688            }
689            if !cell_xf.apply_border {
690                cell_xf.border_id = border_id;
691            }
692            if !cell_xf.apply_alignment {
693                cell_xf.alignment = style.alignment.clone();
694            }
695        }
696
697        if name != new_name {
698            styles.rename_named_style_entry(name, new_name)?;
699        }
700        Ok(())
701    }
702}
703
704impl Dxf {
705    /// Applies this differential format on top of `base`, returning the merged style.
706    /// Only fields present in the Dxf (i.e. `Some`) override the base.
707    pub fn apply_to(&self, base: &Style) -> Style {
708        let mut style = base.clone();
709
710        if let Some(ref dxf_fill) = self.fill {
711            style.fill = dxf_fill.clone();
712        }
713
714        if let Some(ref dxf_font) = self.font {
715            // Override color only when it differs from the default (#000000)
716            if dxf_font.color != Font::default().color {
717                style.font.color = dxf_font.color.clone();
718            }
719            if let Some(b) = dxf_font.b {
720                style.font.b = b;
721            }
722            if let Some(i) = dxf_font.i {
723                style.font.i = i;
724            }
725            if let Some(u) = dxf_font.u {
726                style.font.u = u;
727            }
728            if let Some(strike) = dxf_font.strike {
729                style.font.strike = strike;
730            }
731        }
732
733        if let Some(ref dxf_border) = self.border {
734            style.border = dxf_border.clone();
735        }
736
737        if let Some(ref dxf_num_fmt) = self.num_fmt {
738            style.num_fmt = dxf_num_fmt.format_code.clone();
739        }
740
741        if let Some(ref dxf_alignment) = self.alignment {
742            style.alignment = Some(dxf_alignment.clone());
743        }
744
745        style
746    }
747}