Skip to content
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
2 changes: 1 addition & 1 deletion src/MiniExcel.Core/Attributes/MiniExcelColumnAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class MiniExcelColumnAttribute : Attribute
public bool Ignore { get; set; }

internal int FormatId { get; private set; } = -1;
public double Width { get; set; } = 9.28515625;
public double Width { get; set; } = 8.42857143;
public ColumnType Type { get; set; } = ColumnType.Value;

public int Index
Expand Down
6 changes: 0 additions & 6 deletions src/MiniExcel.OpenXml/Models/DrawingDto.cs

This file was deleted.

68 changes: 68 additions & 0 deletions src/MiniExcel.OpenXml/Models/ExcelColumnWidth.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
namespace MiniExcelLib.OpenXml.Models;

public sealed class ExcelColumnWidth(int index, double width)
{
// Aptos is the default font for Office 2023 and onwards, over which the width of cells are calculated at the size of 11pt.
// Priorly it was Calibri, which had very similar parameters, so no visual differences should be noticed.
// todo: consider making other fonts available
private const double DefaultCellPadding = 5;
private const double Aptos11MaxDigitWidth = 7;
public const double Aptos11Padding = DefaultCellPadding / Aptos11MaxDigitWidth;

public int Index { get; } = index;
public double Width { get; set; } = width;

public static double GetWidthFromTextLength(double characters)
=> Math.Round(characters + Aptos11Padding, 8);
}


public sealed class ExcelColumnWidthCollection : IReadOnlyCollection<ExcelColumnWidth>
{
private readonly Dictionary<int, ExcelColumnWidth> _columnWidths;
private readonly double _maxWidth;

public IReadOnlyCollection<ExcelColumnWidth> Columns => _columnWidths.Values;

private ExcelColumnWidthCollection(ICollection<ExcelColumnWidth> columnWidths, double maxWidth)
{
_maxWidth = ExcelColumnWidth.GetWidthFromTextLength(maxWidth);
_columnWidths = columnWidths.ToDictionary(x => x.Index);
}

internal static ExcelColumnWidthCollection GetFromMappings(ICollection<MiniExcelColumnInfo?> mappings, double? minWidth = null, double maxWidth = 200)
{
var i = 1;
List<ExcelColumnWidth> columnWidths = [];

foreach (var map in mappings)
{
if (map?.ExcelColumnWidth is not null || minWidth is not null)
{
var colIndex = map?.ExcelColumnIndex + 1 ?? i;
var width = map?.ExcelColumnWidth ?? minWidth!.Value;

columnWidths.Add(new ExcelColumnWidth(colIndex, width + ExcelColumnWidth.Aptos11Padding));
}

i++;
}

return new ExcelColumnWidthCollection(columnWidths, maxWidth);
}

internal void AdjustWidth(int columnIndex, string columnValue)
{
if (!string.IsNullOrEmpty(columnValue) && _columnWidths.TryGetValue(columnIndex, out var currentWidth))
{
var desiredWidth = ExcelColumnWidth.GetWidthFromTextLength(columnValue.Length);
var adjustedWidth = Math.Max(currentWidth.Width, desiredWidth);
currentWidth.Width = Math.Min(_maxWidth, adjustedWidth);
}
}

public IEnumerator<ExcelColumnWidth> GetEnumerator() => _columnWidths.Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public int Count => _columnWidths.Count;
}
64 changes: 0 additions & 64 deletions src/MiniExcel.OpenXml/Models/ExcelWidthCollection.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/MiniExcel.OpenXml/OpenXmlConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class OpenXmlConfiguration : MiniExcelBaseConfiguration
/// Calculate column widths automatically from each column value.
/// </summary>
public bool EnableAutoWidth { get; set; }
public double MinWidth { get; set; } = 9.28515625;
public double MinWidth { get; set; } = 8.42857143;
public double MaxWidth { get; set; } = 200;
}

Expand Down
11 changes: 6 additions & 5 deletions src/MiniExcel.OpenXml/OpenXmlWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,16 +281,17 @@ private async Task<int> WriteValuesAsync(EnhancedStreamWriter writer, object val
await writer.WriteAsync(GetSheetViews(), cancellationToken).ConfigureAwait(false);

//cols:width
ExcelWidthCollection? widths = null;
ExcelColumnWidthCollection? widths = null;
long columnWidthsPlaceholderPosition = 0;
if (_configuration.EnableAutoWidth)
{
columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, maxColumnIndex, cancellationToken).ConfigureAwait(false);
widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props);
widths = ExcelColumnWidthCollection.GetFromMappings(props, _configuration.MinWidth, _configuration.MaxWidth);
}
else
{
await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props), cancellationToken).ConfigureAwait(false);
var colWidths = ExcelColumnWidthCollection.GetFromMappings(props);
await WriteColumnsWidthsAsync(writer, colWidths.Columns, cancellationToken).ConfigureAwait(false);
}

//header
Expand Down Expand Up @@ -451,7 +452,7 @@ private async Task WriteCellAsync(EnhancedStreamWriter writer, string cellRefere
}

[CreateSyncVersion]
private async Task WriteCellAsync(EnhancedStreamWriter writer, int rowIndex, int cellIndex, object? value, MiniExcelColumnInfo columnInfo, ExcelWidthCollection? widthCollection, CancellationToken cancellationToken = default)
private async Task WriteCellAsync(EnhancedStreamWriter writer, int rowIndex, int cellIndex, object? value, MiniExcelColumnInfo columnInfo, ExcelColumnWidthCollection? widthCollection, CancellationToken cancellationToken = default)
{
if (columnInfo?.CustomFormatter is not null)
{
Expand Down Expand Up @@ -482,7 +483,7 @@ private async Task WriteCellAsync(EnhancedStreamWriter writer, int rowIndex, int
var columnType = columnInfo.ExcelColumnType;

/*Prefix and suffix blank space will lost after SaveAs #294*/
var preserveSpace = cellValue is [' ', ..] or [.., ' '];
var preserveSpace = cellValue.StartsWith(" ") || cellValue.EndsWith(" ");

await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, GetCellXfId(styleIndex), cellValue, preserveSpace: preserveSpace, columnType: columnType), cancellationToken).ConfigureAwait(false);
widthCollection?.AdjustWidth(cellIndex, cellValue);
Expand Down
56 changes: 27 additions & 29 deletions tests/MiniExcel.OpenXml.Tests/MiniExcelAutoAdjustWidthTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public void AutoAdjustWidthEnumerable()
var path = file.ToString();

var configuration = AutoAdjustTestParameters.GetConfiguration();
_excelExporter.Export(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: configuration);
var data = AutoAdjustTestParameters.GetDictionaryTestData();
_excelExporter.Export(path, data, configuration: configuration);

AssertExpectedWidth(path, configuration);
}
Expand All @@ -70,7 +71,7 @@ public async Task AutoAdjustWidthDataReader_Async()
await using var command = new SQLiteCommand(Db.GenerateDummyQuery(AutoAdjustTestParameters.GetDictionaryTestData()), connection);
connection.Open();
await using var reader = await command.ExecuteReaderAsync();
await _excelExporter.ExportAsync(path, reader, configuration: configuration);
await _excelExporter.ExportAsync(path, reader, configuration: configuration, overwriteFile: true);
}

AssertExpectedWidth(path, configuration);
Expand Down Expand Up @@ -149,43 +150,41 @@ private static void AssertExpectedWidth(string path, OpenXmlConfiguration config

var columns = worksheetPart?.Worksheet.GetFirstChild<Columns>();
Assert.False(columns is null, "No column width information was written.");

foreach (var column in columns.Elements<Column>())
{
var expectedWidth = column.Min?.Value switch
{
1 => ExcelWidthCollection.GetApproximateTextWidth(AutoAdjustTestParameters.Column1MaxStringLength),
2 => ExcelWidthCollection.GetApproximateTextWidth(AutoAdjustTestParameters.Column2MaxStringLength),
1 => AutoAdjustTestParameters.Column1MaLen,
2 => AutoAdjustTestParameters.Column2MaxLen,
3 => configuration.MinWidth,
4 => configuration.MaxWidth,
_ => throw new Exception("Unexpected column"),
_ => throw new UnreachableException()
};

Assert.Equal(expectedWidth, column.Width?.Value);
Assert.Equal(ExcelColumnWidth.GetWidthFromTextLength(expectedWidth), Math.Round(column.Width!.Value, 8));
}
}

private static class AutoAdjustTestParameters
{
public const int Column1MaxStringLength = 32;
public const int Column2MaxStringLength = 16;
public const int Column3MaxStringLength = 2;
public const int Column4MaxStringLength = 100;
public const int MinStringLength = 8;
public const int MaxStringLength = 50;

public static List<string[]> GetTestData() =>
internal const int Column1MaLen = 32;
internal const int Column2MaxLen = 16;
private const int Column3MaxLen = 2;
private const int Column4MaxLen = 100;

public static List<string[]> GetTestData() => [
[
new string[]
{
new('1', Column1MaxStringLength), new('2', Column2MaxStringLength / 2),
new('3', Column3MaxStringLength / 2), new('4', Column1MaxStringLength)
},
new string[]
{
new('1', Column1MaxStringLength / 2), new('2', Column2MaxStringLength),
new('3', Column3MaxStringLength), new('4', Column4MaxStringLength)
}
];
new('1', Column1MaLen),
new('2', Column2MaxLen / 2),
new('3', Column3MaxLen / 2),
new('4', Column4MaxLen)
],
[
new('1', Column1MaLen / 2),
new('2', Column2MaxLen),
new('3', Column3MaxLen),
new('4', Column4MaxLen)
] ];

public static List<Dictionary<string, object>> GetDictionaryTestData() => GetTestData()
.Select(row => row
Expand All @@ -197,8 +196,7 @@ public static List<Dictionary<string, object>> GetDictionaryTestData() => GetTes
{
EnableAutoWidth = true,
FastMode = true,
MinWidth = ExcelWidthCollection.GetApproximateTextWidth(MinStringLength),
MaxWidth = ExcelWidthCollection.GetApproximateTextWidth(MaxStringLength)
MaxWidth = 50
};
}
}
}
Loading