From 0e8b8fd370b7a60258ac824b9ae05a556d7c1663 Mon Sep 17 00:00:00 2001 From: Taylor Buchanan Date: Thu, 11 Oct 2018 08:32:00 -0500 Subject: [PATCH 1/2] Fix column width calculations for AutoSizeColumn --- src/NPOI/SS/Util/SheetUtil.cs | 60 +++++++++++++++++------------------ src/NPOI/TextRenderer.cs | 26 --------------- 2 files changed, 30 insertions(+), 56 deletions(-) delete mode 100644 src/NPOI/TextRenderer.cs diff --git a/src/NPOI/SS/Util/SheetUtil.cs b/src/NPOI/SS/Util/SheetUtil.cs index 1469e5f..ac368bd 100644 --- a/src/NPOI/SS/Util/SheetUtil.cs +++ b/src/NPOI/SS/Util/SheetUtil.cs @@ -36,7 +36,7 @@ namespace NPOI.SS.Util // * but the docs say nothing about what particular character is used. // * '0' looks to be a good choice. // */ - private static char defaultChar = '0'; + private const char defaultChar = '0'; // /** // * This is the multiple that the font height is scaled by when determining the @@ -300,19 +300,18 @@ namespace NPOI.SS.Util ICellStyle style = cell.CellStyle; CellType cellType = cell.CellType; - IFont defaultFont = wb.GetFontAt((short)0); - Font windowsFont = IFont2Font(defaultFont); + // for formula cells we compute the cell width for the cached formula result if (cellType == CellType.Formula) cellType = cell.CachedFormulaResultType; IFont font = wb.GetFontAt(style.FontIndex); + Font windowsFont = IFont2Font(font); //AttributedString str; //TextLayout layout; double width = -1; - using (Bitmap bmp = new Bitmap(1, 1)) - using (Graphics g = Graphics.FromImage(bmp)) + using (Graphics g = Graphics.FromHwnd(IntPtr.Zero)) { if (cellType == CellType.String) { @@ -324,7 +323,6 @@ namespace NPOI.SS.Util //str = new AttributedString(txt); //copyAttributes(font, str, 0, txt.length()); - windowsFont = IFont2Font(font); if (rt.NumFormattingRuns > 0) { // TODO: support rich text fragments @@ -345,11 +343,11 @@ namespace NPOI.SS.Util // AffineTransform.getScaleInstance(1, fontHeightMultiple) // ); double angle = style.Rotation * 2.0 * Math.PI / 360.0; - SizeF sf = g.MeasureString(txt, windowsFont); + SizeF sf = MeasureString(g, txt, windowsFont); double x1 = Math.Abs(sf.Height * Math.Sin(angle)); double x2 = Math.Abs(sf.Width * Math.Cos(angle)); double w = Math.Round(x1 + x2, 0, MidpointRounding.ToEven); - width = Math.Max(width, (w / colspan / defaultCharWidth) * 2 + cell.CellStyle.Indention); + width = Math.Max(width, (w / colspan / defaultCharWidth) + cell.CellStyle.Indention); //width = Math.Max(width, // ((layout.getOutline(trans).getBounds().getWidth()/colspan)/defaultCharWidth) + // cell.getCellStyle().getIndention()); @@ -359,8 +357,8 @@ namespace NPOI.SS.Util //width = Math.Max(width, // ((layout.getBounds().getWidth()/colspan)/defaultCharWidth) + // cell.getCellStyle().getIndention()); - double w = Math.Round(g.MeasureString(txt, windowsFont).Width, 0, MidpointRounding.ToEven); - width = Math.Max(width, (w / colspan / defaultCharWidth) * 2 + cell.CellStyle.Indention); + double w = Math.Round(MeasureString(g, txt, windowsFont).Width, 0, MidpointRounding.ToEven); + width = Math.Max(width, (w / colspan / defaultCharWidth) + cell.CellStyle.Indention); } } } @@ -388,7 +386,6 @@ namespace NPOI.SS.Util String txt = sval + defaultChar; //str = new AttributedString(txt); //copyAttributes(font, str, 0, txt.length()); - windowsFont = IFont2Font(font); //layout = new TextLayout(str.getIterator(), fontRenderContext); if (style.Rotation != 0) { @@ -407,19 +404,19 @@ namespace NPOI.SS.Util // ((layout.getOutline(trans).getBounds().getWidth()/colspan)/defaultCharWidth) + // cell.getCellStyle().getIndention()); double angle = style.Rotation * 2.0 * Math.PI / 360.0; - SizeF sf = g.MeasureString(txt, windowsFont); + SizeF sf = MeasureString(g, txt, windowsFont); double x1 = sf.Height * Math.Sin(angle); double x2 = sf.Width * Math.Cos(angle); double w = Math.Round(x1 + x2, 0, MidpointRounding.ToEven); - width = Math.Max(width, (w / colspan / defaultCharWidth) * 2 + cell.CellStyle.Indention); + width = Math.Max(width, (w / colspan / defaultCharWidth) + cell.CellStyle.Indention); } else { //width = Math.max(width, // ((layout.getBounds().getWidth()/colspan)/defaultCharWidth) + // cell.getCellStyle().getIndention()); - double w = Math.Round(g.MeasureString(txt, windowsFont).Width, 0, MidpointRounding.ToEven); - width = Math.Max(width, (w * 1.0 / colspan / defaultCharWidth) * 2 + cell.CellStyle.Indention); + double w = Math.Round(MeasureString(g, txt, windowsFont).Width, 0, MidpointRounding.ToEven); + width = Math.Max(width, (w / colspan / defaultCharWidth) + cell.CellStyle.Indention); } } } @@ -449,14 +446,12 @@ namespace NPOI.SS.Util IWorkbook wb = sheet.Workbook; DataFormatter formatter = new DataFormatter(); - IFont defaultFont = wb.GetFontAt((short)0); //str = new AttributedString((defaultChar)); //copyAttributes(defaultFont, str, 0, 1); //layout = new TextLayout(str.Iterator, fontRenderContext); //int defaultCharWidth = (int)layout.Advance; - Font font = IFont2Font(defaultFont); - int defaultCharWidth = TextRenderer.MeasureText("" + new String(defaultChar, 1), font).Width; + int defaultCharWidth = getDefaultCharWidth(wb); //DummyEvaluator dummyEvaluator = new DummyEvaluator(); double width = -1; @@ -491,14 +486,11 @@ namespace NPOI.SS.Util IWorkbook wb = sheet.Workbook; DataFormatter formatter = new DataFormatter(); - IFont defaultFont = wb.GetFontAt((short)0); - //str = new AttributedString((defaultChar)); //copyAttributes(defaultFont, str, 0, 1); //layout = new TextLayout(str.Iterator, fontRenderContext); //int defaultCharWidth = (int)layout.Advance; - Font font = IFont2Font(defaultFont); - int defaultCharWidth = TextRenderer.MeasureText("" + new String(defaultChar, 1), font).Width; + int defaultCharWidth = getDefaultCharWidth(wb); double width = -1; for (int rowIdx = firstRow; rowIdx <= lastRow; ++rowIdx) @@ -662,15 +654,23 @@ namespace NPOI.SS.Util public static int getDefaultCharWidth(IWorkbook wb) { - //TODO: Implement! - return 1; - //throw new NotImplementedException(); - //IFont defaultFont = wb.GetFontAt(0); + IFont defaultFont = wb.GetFontAt(0); + Font font = IFont2Font(defaultFont); + using (Graphics g = Graphics.FromHwnd(IntPtr.Zero)) + { + double width = MeasureString(g, new string(defaultChar, 1), font).Width; + return (int)Math.Round(width, 0, MidpointRounding.ToEven); + } + } + + private static SizeF MeasureString(Graphics g, string text, Font font) + { + var size = g.MeasureString(text, font); - //IAttributedString str = new AttributedString(defaultChar.ToString()); - //copyAttributes(defaultFont, str, 0, 1); - //ITextLayout layout = new TextLayout(str.getIterator(), fontRenderContext); - //return (int)layout.getAdvance(); + // MeasureString adds padding so we do this to remove it + // https://stackoverflow.com/a/11708952/1409101 + var actualWidth = g.MeasureString(text + text, font).Width - size.Width; + return new SizeF(actualWidth, size.Height); } } } diff --git a/src/NPOI/TextRenderer.cs b/src/NPOI/TextRenderer.cs deleted file mode 100644 index 513d454..0000000 --- a/src/NPOI/TextRenderer.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Drawing; - -namespace NPOI -{ - /// Provides methods used to measure and render text. This class cannot be inherited. - public sealed class TextRenderer - { - private TextRenderer() - { - } - - /// Provides the size, in pixels, of the specified text when drawn with the specified font. - /// The text to measure. - /// The to apply to the measured text. - /// The , in pixels, of drawn on a single line with the specified . You can manipulate how the text is drawn by using one of the overloads that takes a parameter. For example, the default behavior of the is to add padding to the bounding rectangle of the drawn text to accommodate overhanging glyphs. If you need to draw a line of text without these extra spaces you should use the versions of and that take a and parameter. For an example, see . - public static Size MeasureText(string text, Font font) - { - if (string.IsNullOrEmpty(text)) - { - return Size.Empty; - } - Size result = new Size(19, 19); - return result; - } - } -} \ No newline at end of file From 4b4e80b160a9158e361fa49b242bf075792cd894 Mon Sep 17 00:00:00 2001 From: Taylor Buchanan Date: Thu, 11 Oct 2018 08:34:37 -0500 Subject: [PATCH 2/2] Fix General format of large numbers for AutoSizeColumn --- src/NPOI/SS/UserModel/DataFormatter.cs | 4 ++-- src/NPOI/SS/Util/Format.cs | 30 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/NPOI/SS/UserModel/DataFormatter.cs b/src/NPOI/SS/UserModel/DataFormatter.cs index 379e903..97d7724 100644 --- a/src/NPOI/SS/UserModel/DataFormatter.cs +++ b/src/NPOI/SS/UserModel/DataFormatter.cs @@ -118,10 +118,10 @@ namespace NPOI.SS.UserModel invalidDateTimeString = buf.ToString(); } /** General FormatBase for whole numbers. */ - private static DecimalFormat generalWholeNumFormat = new DecimalFormat("0"); + private static DecimalFormat generalWholeNumFormat = new DecimalFormat("0", true); /** General FormatBase for decimal numbers. */ - private static DecimalFormat generalDecimalNumFormat = new DecimalFormat("#.##########"); + private static DecimalFormat generalDecimalNumFormat = new DecimalFormat("#.##########", true); /** A default FormatBase to use when a number pattern cannot be Parsed. */ private FormatBase defaultNumFormat; diff --git a/src/NPOI/SS/Util/Format.cs b/src/NPOI/SS/Util/Format.cs index 24c0445..21c7065 100644 --- a/src/NPOI/SS/Util/Format.cs +++ b/src/NPOI/SS/Util/Format.cs @@ -173,12 +173,20 @@ namespace NPOI.SS.Util private string pattern; + private bool isGeneral; + public DecimalFormat(string pattern) { if (pattern.IndexOf("'", StringComparison.Ordinal) != -1) throw new ArgumentException("invalid pattern"); this.pattern = pattern; } + + internal DecimalFormat(string pattern, bool isGeneral) : this(pattern) + { + this.isGeneral = isGeneral; + } + public string Pattern { get @@ -194,19 +202,19 @@ namespace NPOI.SS.Util } public override string Format(object obj, CultureInfo culture) { - //invalide fraction + // Invalid fraction pattern = RegexFraction.Replace(pattern, "/"); - + double n = Convert.ToDouble(obj, CultureInfo.InvariantCulture); + if (pattern.IndexOf("'", StringComparison.Ordinal) != -1) - { - //return ((double)obj).ToString(); - return Convert.ToDouble(obj, CultureInfo.InvariantCulture).ToString(culture); - } - else - { - return Convert.ToDouble(obj, CultureInfo.InvariantCulture).ToString(pattern, culture); - //return ((double)obj).ToString(pattern) ; - } + return n.ToString(culture); + + // Excel displays in scientific notation if the cell form is General and there are 12 or more digits + // https://support.office.com/en-us/article/display-numbers-in-scientific-exponential-notation-f85a96c0-18a1-4249-81c3-e934cd2aae25 + if (isGeneral && Math.Floor(Math.Log10(n) + 1) >= 12) + pattern = "E4"; + + return n.ToString(pattern, culture); } public override StringBuilder Format(object obj, StringBuilder toAppendTo, CultureInfo culture)