diff --git a/benchmarks/NPOI.Benchmarks/AutoSizeColumnBenchmark.cs b/benchmarks/NPOI.Benchmarks/AutoSizeColumnBenchmark.cs new file mode 100644 index 000000000..ee4d48f6a --- /dev/null +++ b/benchmarks/NPOI.Benchmarks/AutoSizeColumnBenchmark.cs @@ -0,0 +1,57 @@ +using BenchmarkDotNet.Attributes; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; + +namespace NPOI.Benchmarks; + +[MemoryDiagnoser] +public class AutoSizeColumnBenchmark +{ + private static readonly string[] lorem = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut +labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip +ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat +nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id +est laborum.".Split(' ', '\r', '\n'); + + private XSSFWorkbook workbook; + private ISheet sheet1; + + [Params(1_000)] + public int RowCount { get; set; } + + [Params(5)] + public int ColumnCount { get; set; } + + [IterationSetup] + public void Setup() + { + var ipsum = 0; + + workbook = new XSSFWorkbook(); + sheet1 = workbook.CreateSheet("Sheet1"); + + for (var rowNum = 1; rowNum <= RowCount; rowNum++) + { + var row = sheet1.CreateRow(rowNum); + for (int col = 1; col <= ColumnCount; col++) + { + row.CreateCell(col).SetCellValue(lorem[ipsum++ % lorem.Length]); + } + } + } + + [Benchmark] + public void AutoSizeColumn() + { + for (var col = 1; col <= ColumnCount; col++) + { + sheet1.AutoSizeColumn(col); + } + } + + [IterationCleanup] + public void Cleanup() + { + workbook.Dispose(); + } +} \ No newline at end of file diff --git a/main/SS/Util/SheetUtil.cs b/main/SS/Util/SheetUtil.cs index 3dc8dfb56..630fc2b0e 100644 --- a/main/SS/Util/SheetUtil.cs +++ b/main/SS/Util/SheetUtil.cs @@ -20,6 +20,7 @@ namespace NPOI.SS.Util using System; using NPOI.SS.UserModel; + using System.Collections.Concurrent; using System.Collections.Generic; using SixLabors.Fonts; using System.Linq; @@ -680,9 +681,47 @@ public static bool CanComputeColumnWidth(IFont font) // str.AddAttribute(TextAttribute.SIZE, (float)font.FontHeightInPoints); // if (font.Boldweight == (short)FontBoldWeight.BOLD) str.AddAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, startIdx, endIdx); // if (font.IsItalic) str.AddAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, startIdx, endIdx); - // TODO-Fonts: not supported: if (font.Underline == (byte)FontUnderlineType.SINGLE) str.AddAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx); + // TODO-Fonts: not supported: if (font.Underline == (byte)FontUnderlineType.SINGLE) str.AddAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx); //} - + + private readonly struct FontCacheKey : IEquatable + { + public FontCacheKey(string fontName, float fontHeightInPoints, FontStyle style) + { + FontName = fontName; + FontHeightInPoints = fontHeightInPoints; + Style = style; + } + + public readonly string FontName; + public readonly float FontHeightInPoints; + public readonly FontStyle Style; + + public bool Equals(FontCacheKey other) + { + return FontName == other.FontName && FontHeightInPoints.Equals(other.FontHeightInPoints) && Style == other.Style; + } + + public override bool Equals(object obj) + { + return obj is FontCacheKey other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = FontName != null ? FontName.GetHashCode() : 0; + hashCode = (hashCode * 397) ^ FontHeightInPoints.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)Style; + return hashCode; + } + } + } + + private static readonly CultureInfo StartupCulture = CultureInfo.CurrentCulture; + private static readonly ConcurrentDictionary FontCache = new(); + /// /// Convert HSSFFont to Font. /// @@ -692,11 +731,6 @@ public static bool CanComputeColumnWidth(IFont font) /// found by SixLabors in the current environment. internal static Font IFont2Font(IFont font1) { - if (SystemFonts.Families == null || SystemFonts.Families.Count() == 0) - { - throw new FontException("No fonts found installed on the machine."); - } - FontStyle style = FontStyle.Regular; if (font1.IsBold) { @@ -712,21 +746,39 @@ internal static Font IFont2Font(IFont font1) } */ + var key = new FontCacheKey(font1.FontName, (float)font1.FontHeightInPoints, style); + + // only use cache if font size is an integer and culture is original to prevent cache size explosion + if (font1.FontHeightInPoints == (int) font1.FontHeightInPoints && CultureInfo.CurrentCulture.Equals(StartupCulture)) + { + return FontCache.GetOrAdd(key, IFont2FontImpl); + } + + // skip cache + return IFont2FontImpl(key); + } + + private static Font IFont2FontImpl(FontCacheKey cacheKey) + { // Try to find font in system fonts. If we can not find out, // use "Arial". TODO-Fonts: More fallbacks. - SixLabors.Fonts.FontFamily fontFamily; - if (false == SystemFonts.TryGet(font1.FontName, CultureInfo.CurrentCulture, out fontFamily)) + if (!SystemFonts.TryGet(cacheKey.FontName, CultureInfo.CurrentCulture, out var fontFamily)) { - if (false == SystemFonts.TryGet("Arial", CultureInfo.CurrentCulture, out fontFamily)) + if (!SystemFonts.TryGet("Arial", CultureInfo.CurrentCulture, out fontFamily)) { + if (!SystemFonts.Families.Any()) + { + throw new FontException("No fonts found installed on the machine."); + } + fontFamily = SystemFonts.Families.First(); } } - - Font font = new Font(fontFamily, (float)font1.FontHeightInPoints, style); - return font; + + return new Font(fontFamily, cacheKey.FontHeightInPoints, cacheKey.Style); } + /// /// Check if the cell is in the specified cell range ///