From f8b80d4eef01440aaa9d8d760caf081c214feba0 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 12:01:19 +0000 Subject: [PATCH] xlsx: share each custom number format across cell formats, cap font names at 31 characters Every cell format record copied its font (name included) and built its own copy of its number-format code, so many short records naming one long font or code multiplied it in memory. Custom codes are now built once and shared, and font names are cut to Excel's 31 characters. --- .wt-consolidate | 1 + .wt-delim | 1 + .wt-display | 1 + .wt-engine | 1 + .wt-fill | 1 + .wt-fonts | 1 + .wt-hf | 1 + .wt-keytips | 1 + .wt-numval | 1 + .wt-passthrough | 1 + .wt-pivotfilter | 1 + .wt-textfn | 1 + crates/xlsx/src/styles.rs | 29 +++++++++++++++++++---------- crates/xlsx/src/tests/malformed.rs | 19 +++++++++++++++++++ 14 files changed, 50 insertions(+), 10 deletions(-) create mode 160000 .wt-consolidate create mode 160000 .wt-delim create mode 160000 .wt-display create mode 160000 .wt-engine create mode 160000 .wt-fill create mode 160000 .wt-fonts create mode 160000 .wt-hf create mode 160000 .wt-keytips create mode 160000 .wt-numval create mode 160000 .wt-passthrough create mode 160000 .wt-pivotfilter create mode 160000 .wt-textfn diff --git a/.wt-consolidate b/.wt-consolidate new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-consolidate @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-delim b/.wt-delim new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-delim @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-display b/.wt-display new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-display @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-engine b/.wt-engine new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-engine @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-fill b/.wt-fill new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-fill @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-fonts b/.wt-fonts new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-fonts @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-hf b/.wt-hf new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-hf @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-keytips b/.wt-keytips new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-keytips @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-numval b/.wt-numval new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-numval @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-passthrough b/.wt-passthrough new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-passthrough @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-pivotfilter b/.wt-pivotfilter new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-pivotfilter @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/.wt-textfn b/.wt-textfn new file mode 160000 index 000000000..e1604d179 --- /dev/null +++ b/.wt-textfn @@ -0,0 +1 @@ +Subproject commit e1604d17915fe5a1f8d75fe9a5eef449226ec867 diff --git a/crates/xlsx/src/styles.rs b/crates/xlsx/src/styles.rs index c1f93aca9..d962881d3 100644 --- a/crates/xlsx/src/styles.rs +++ b/crates/xlsx/src/styles.rs @@ -59,11 +59,14 @@ fn bool_prop(e: &El, name: &str) -> bool { const DEFAULT_FONT_FAMILY: u8 = 2; const MAX_FONT_FAMILY: u8 = 14; +/// Longest font name kept, in characters (Excel's own limit). Every cell format copies its +/// font, so a long name repeated by many formats would otherwise multiply in memory. +const MAX_FONT_NAME: usize = 31; pub fn read_font(e: &El, palette: &[u32]) -> Font { let mut f = Font::default(); if let Some(n) = e.child("name").or_else(|| e.child("rFont")).and_then(|n| n.attr("val")) { - f.name = n.to_string(); + f.name = n.chars().take(MAX_FONT_NAME).collect(); } if let Some(sz) = e.child("sz").and_then(|s| s.attr_f64("val")) { f.size = sz.clamp(1.0, 409.0) as f32; @@ -277,14 +280,14 @@ fn read_protection(p: &El) -> Protection { Protection { locked: p.flag("locked", true), hidden: p.flag("hidden", false) } } -fn num_fmt_code(id: u32, custom: &HashMap) -> NumFmt { +fn num_fmt_code(id: u32, custom: &HashMap) -> NumFmt { if let Some(c) = custom.get(&id) { - return NumFmt::new(c); + return c.clone(); } NumFmt::new(builtin_format(id).unwrap_or("General")) } -fn read_xf(xf: &El, fonts: &[Font], fills: &[Fill], borders: &[Borders], fmts: &HashMap, checkboxes: &[bool]) -> Style { +fn read_xf(xf: &El, fonts: &[Font], fills: &[Fill], borders: &[Borders], fmts: &HashMap, checkboxes: &[bool]) -> Style { let mut s = Style::default(); if let Some(f) = xf.attr_u32("fontId").and_then(|i| fonts.get(i as usize)) { s.font = f.clone(); @@ -336,6 +339,8 @@ pub fn read_styles(root: &El, checkboxes: &[bool], minor_font: &str) -> StylesIn } } } + // One shared copy of each custom code, which every format using it points to. + let shared_fmts: HashMap = fmts.iter().map(|(id, code)| (*id, NumFmt::new(code))).collect(); // The scheme link only means something on a font in the body face; elsewhere it stays as a new font has it. let body_font = |e: &El| { let mut f = read_font(e, &palette); @@ -345,10 +350,14 @@ pub fn read_styles(root: &El, checkboxes: &[bool], minor_font: &str) -> StylesIn let fonts: Vec = root.child("fonts").map(|f| f.kids("font").map(body_font).collect()).unwrap_or_default(); let fills: Vec = root.child("fills").map(|f| f.kids("fill").map(|e| read_fill(e, &palette, false)).collect()).unwrap_or_default(); let borders: Vec = root.child("borders").map(|f| f.kids("border").map(|e| read_border(e, &palette)).collect()).unwrap_or_default(); - let xfs = - root.child("cellXfs").map(|x| x.kids("xf").map(|xf| read_xf(xf, &fonts, &fills, &borders, &fmts, checkboxes)).collect()).unwrap_or_default(); - let style_xfs: Vec