Skip to content
Open
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
12 changes: 10 additions & 2 deletions src/EPPlus.Interfaces/Drawing/Text/ITextMeasurer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Date Author Change
1/4/2021 EPPlus Software AB EPPlus Interfaces 1.0
*************************************************************************************************/

using System;

namespace OfficeOpenXml.Interfaces.Drawing.Text
{
/// <summary>
Expand All @@ -31,9 +33,15 @@ public interface ITextMeasurer
/// <returns></returns>
TextMeasurement MeasureText(string text, MeasurementFont font);
/// <summary>
/// If the text measurer should measure wrap text cells.
/// Only CR, LF or CRLF should be considered.
/// If the text measurer should measure wrapped text cells.
/// </summary>
[Obsolete("Use WrappedTextAutofitMode instead. This property will be removed in a future major version.")]
bool MeasureWrappedTextCells { get; set; }

/// <summary>
/// Determines how cells with WrapText enabled are measured when calculating
/// column width in AutoFitColumns. Default is <see cref="eWrappedTextAutofitMode.Skip"/>.
/// </summary>
eWrappedTextAutofitMode WrappedTextAutofitMode { get; set; }
}
}
47 changes: 47 additions & 0 deletions src/EPPlus.Interfaces/Drawing/Text/eWrappedTextAutofitMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*************************************************************************************************
Required Notice: Copyright (C) EPPlus Software AB.
This software is licensed under PolyForm Noncommercial License 1.0.0
and may only be used for noncommercial purposes
https://polyformproject.org/licenses/noncommercial/1.0.0/

A commercial license to use this software can be purchased at https://epplussoftware.com
*************************************************************************************************
Date Author Change
*************************************************************************************************
1/4/2021 EPPlus Software AB Added wrapped text autofit mode
*************************************************************************************************/
namespace OfficeOpenXml.Interfaces.Drawing.Text
{
/// <summary>
/// Determines how cells with WrapText enabled are measured when calculating
/// column width in AutoFitColumns.
/// </summary>
public enum eWrappedTextAutofitMode
{
/// <summary>
/// Cells with WrapText enabled are ignored when calculating column width.
/// This is the default and matches the behaviour of earlier versions.
/// </summary>
Skip = 0,
/// <summary>
/// The entire cell text is measured as a single line, ignoring wrapping.
/// </summary>
FullText = 1,
/// <summary>
/// The text is split on explicit line breaks (CR, LF, CRLF) and the width
/// of the widest resulting line determines the cell's contribution to the
/// column width.
/// </summary>
SplitNewLine = 2,
/// <summary>
/// The text is split on whitespace (space, tab, and line breaks) and on
/// hyphen characters (U+002D hyphen-minus and U+2010 hyphen). The width of
/// the widest resulting segment determines the cell's contribution to the
/// column width. Hyphens are visible and their width is included in the
/// segment they terminate; whitespace is not. Note: this yields the minimum
/// column width at which no single word overflows, and is not a simulation
/// of Excel's line wrapping.
/// </summary>
SplitWord = 3
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ public class SystemDrawingTextMeasurer : ITextMeasurer, IDisposable
/// If the text measurer should measure wrap text cells.
/// Only CR, LF or CRLF should be considered.
/// </summary>
#pragma warning disable 618
public bool MeasureWrappedTextCells
{
get;
set;
}
#pragma warning restore 618
public eWrappedTextAutofitMode WrappedTextAutofitMode
{
get;
set;
}

public SystemDrawingTextMeasurer()
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
Expand Down
2 changes: 1 addition & 1 deletion src/EPPlus/Core/AutofitHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ internal void AutofitColumn(double MinimumWidth, double MaximumWidth)
{
var cellStyleId = styles.CellXfs[cell.StyleID];
if (cell.Merge == true) continue;
if (cellStyleId.WrapText && _textSettings.MeasureWrappedTextCells == false) continue;
if (cellStyleId.WrapText && _textSettings.WrappedTextAutofitMode == eWrappedTextAutofitMode.Skip) continue;
currentMaxWidth = GetTextLength(cell, textLengthCache, styles, cellStyleId, normalSize, MaximumWidth, currentMaxWidth);
if (currentMaxWidth >= MaximumWidth)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,24 @@ internal class GenericFontMetricsTextMeasurer : GenericFontMetricsTextMeasurerBa
/// If the text measurer should measure wrap text cells.
/// Only CR, LF or CRLF should be considered.
/// </summary>
#pragma warning disable 618
public bool MeasureWrappedTextCells
{
{
get;
set;
}
#pragma warning restore 618
///
/// <summary>
///
/// </summary>
public eWrappedTextAutofitMode WrappedTextAutofitMode
{
get;
set;
}


/// <summary>
/// Measures the supplied text
/// </summary>
Expand All @@ -38,7 +51,7 @@ public TextMeasurement MeasureText(string text, MeasurementFont font)
{
var fontKey = GetKey(font.FontFamily, font.Style);
if (!IsValidFont(fontKey)) return TextMeasurement.Empty;
return MeasureTextInternal(text, fontKey, font.Style, font.Size, MeasureWrappedTextCells);
return MeasureTextInternal(text, fontKey, font.Style, font.Size, WrappedTextAutofitMode);
}

public bool ValidForEnvironment()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,53 @@ internal protected bool IsValidFont(uint fontKey)
return _fonts.ContainsKey(fontKey);
}

internal protected TextMeasurement MeasureTextInternal(string text, uint fontKey, MeasurementFontStyles style, float size, bool wrapText = false)
internal protected TextMeasurement MeasureTextInternal(string text, uint fontKey, MeasurementFontStyles style, float size, eWrappedTextAutofitMode mode = eWrappedTextAutofitMode.Skip)
{
var sFont = _fonts[fontKey];

// Width of the current segment (a "segment" is a line in SplitNewLine mode,
// or a word in SplitWord mode). In FullText/Skip the whole text is one segment.
var width = 0f;
var maxWidth = 0f;
var widthEA = 0f;

// Width of the widest segment seen so far.
var maxWidth = 0f;
var maxWidthEA = 0f;

for (var x = 0; x < text.Length; x++)
{
var fnt = sFont;
var c = text[x];
if(wrapText && (c=='\n' || c=='\r'))

if (IsSegmentBoundary(c, mode))
{
if(x>0 && c=='\r' && text[x-1]=='\n')
// A CRLF pair is a single line break, not two.
if (x > 0 && c == '\r' && text[x - 1] == '\n')
{
continue; //CRLF should be handled as one new line.
continue;
}
if(width>maxWidth)

// A visible boundary character (hyphen) remains at the end of the
// segment it terminates, so its own width is added before the break.
if (IsVisibleBoundary(c) && sFont.CharMetrics.ContainsKey(c))
{
width += fnt.ClassWidths[sFont.CharMetrics[c]];
}

// Close the current segment: keep it if it is the widest so far.
if ((width + widthEA) > (maxWidth + maxWidthEA))
{
maxWidth = width;
width = 0;
maxWidthEA = widthEA;
}

// Start a new, empty segment.
width = 0f;
widthEA = 0f;

// The boundary character itself is not part of the next segment.
// (Visible boundaries were already counted into the closed segment above.)
continue;
}

//If east Asian char use default regardless of actual font.
Expand All @@ -83,16 +109,23 @@ internal protected TextMeasurement MeasureTextInternal(string text, uint fontKey
if (Char.IsDigit(c)) fw *= FontScaleFactors.DigitsScalingFactor;
width += fw;
}
else if (char.IsControl(c)==false)
else if (char.IsControl(c) == false)
{
width += sFont.ClassWidths[fnt.DefaultWidthClass];
}
}
}
if(maxWidth > width)

// Close the final segment.
if ((width + widthEA) > (maxWidth + maxWidthEA))
{
width = maxWidth;
maxWidth = width;
maxWidthEA = widthEA;
}

width = maxWidth;
widthEA = maxWidthEA;

width *= size;
widthEA *= size;
var sf = _fontScaleFactors.GetScaleFactor(fontKey, width);
Expand All @@ -102,6 +135,35 @@ internal protected TextMeasurement MeasureTextInternal(string text, uint fontKey
return new TextMeasurement(width, height);
}

/// <summary>
/// Returns true if the character ends the current measurement segment for the given mode.
/// </summary>
private static bool IsSegmentBoundary(char c, eWrappedTextAutofitMode mode)
{
switch (mode)
{
case eWrappedTextAutofitMode.SplitNewLine:
return c == '\n' || c == '\r';
case eWrappedTextAutofitMode.SplitWord:
return c == '\n' || c == '\r' || c == ' ' || c == '\t'
|| c == '\u002D' // hyphen-minus
|| c == '\u2010'; // hyphen
default:
// FullText and Skip: the whole string is a single segment.
return false;
}
}

/// <summary>
/// Returns true if the boundary character is visible and therefore contributes
/// its own width to the segment it terminates (hyphens). Whitespace and line
/// breaks are invisible and contribute no width.
/// </summary>
private static bool IsVisibleBoundary(char c)
{
return c == '\u002D' || c == '\u2010';
}

static Dictionary<char, uint> AlphabetChars = new Dictionary<char, uint>
{
{'a', 0x06 },
Expand Down
28 changes: 17 additions & 11 deletions src/EPPlus/ExcelTextSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public ITextMeasurer PrimaryTextMeasurer
set
{
_primaryTextMeasurer = value;
_primaryTextMeasurer.MeasureWrappedTextCells = _measureWrappedTextCells;
_primaryTextMeasurer.WrappedTextAutofitMode = WrappedTextAutofitMode;
}
}
/// <summary>
Expand All @@ -56,7 +56,10 @@ public ITextMeasurer FallbackTextMeasurer
set
{
_fallbackTextMeasurer = value;
_fallbackTextMeasurer.MeasureWrappedTextCells = _measureWrappedTextCells;
if(value != null)
{
_fallbackTextMeasurer.WrappedTextAutofitMode = WrappedTextAutofitMode;
}
}
}
/// <summary>
Expand Down Expand Up @@ -87,22 +90,25 @@ public ITextMeasurer GenericTextMeasurer
/// Measures a text with default settings when there is no other option left...
/// </summary>
internal DefaultTextMeasurer DefaultTextMeasurer { get; set; }

private eWrappedTextAutofitMode _wrappedTextAutofitMode = eWrappedTextAutofitMode.Skip;

/// <summary>
/// Should return true if the text measurer should measure wrap text cells. Only CR, LF or CRLF should be considered
/// Determines how cells with <see cref="Style.ExcelStyle.WrapText"/> enabled are measured
/// when calculating column width in AutoFitColumns. The default is <see cref="eWrappedTextAutofitMode.Skip"/>,
/// which ignores wrapped cells during autofit.
/// </summary>
/// <returns>True if the measurer can be .</returns>
bool _measureWrappedTextCells=false;
internal bool MeasureWrappedTextCells
{
public eWrappedTextAutofitMode WrappedTextAutofitMode
{
get
{
return _measureWrappedTextCells;
return _wrappedTextAutofitMode;
}
set
{
_measureWrappedTextCells = value;
PrimaryTextMeasurer.MeasureWrappedTextCells = value;
if (FallbackTextMeasurer != null) FallbackTextMeasurer.MeasureWrappedTextCells = value;
_wrappedTextAutofitMode = value;
PrimaryTextMeasurer.WrappedTextAutofitMode = value;
if(FallbackTextMeasurer != null) FallbackTextMeasurer.WrappedTextAutofitMode = value;
}
}
}
Expand Down
Loading
Loading