Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add caching to IFont2Font #1179

Merged
merged 1 commit into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions benchmarks/NPOI.Benchmarks/AutoSizeColumnBenchmark.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
78 changes: 65 additions & 13 deletions main/SS/Util/SheetUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<FontCacheKey>
{
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<FontCacheKey, Font> FontCache = new();

/// <summary>
/// Convert HSSFFont to Font.
/// </summary>
Expand All @@ -692,11 +731,6 @@ public static bool CanComputeColumnWidth(IFont font)
/// found by SixLabors in the current environment.</exception>
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)
{
Expand All @@ -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);
}

/// <summary>
/// Check if the cell is in the specified cell range
/// </summary>
Expand Down