From 66a83972d8135c925d1558e5f06dde54fb4c41a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 6 May 2026 16:38:14 +0200 Subject: [PATCH 1/9] Fixed issue where drawings set to absolute positioning would crash --- src/EPPlus/Drawing/ExcelDrawing.cs | 49 ++++++++++++++++++++-- src/EPPlusTest/Drawing/CopyDrawingTests.cs | 13 ++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/EPPlus/Drawing/ExcelDrawing.cs b/src/EPPlus/Drawing/ExcelDrawing.cs index 1778afb660..7498d56e19 100644 --- a/src/EPPlus/Drawing/ExcelDrawing.cs +++ b/src/EPPlus/Drawing/ExcelDrawing.cs @@ -16,6 +16,7 @@ Date Author Change using OfficeOpenXml.Drawing.OleObject; using OfficeOpenXml.Drawing.Slicer; using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; +using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; using OfficeOpenXml.Packaging; using OfficeOpenXml.Utils.EnumUtils; using OfficeOpenXml.Utils.FileUtils; @@ -731,8 +732,8 @@ internal void GetFromBounds(out int fromRow, out int fromRowOff, out int fromCol { if (CellAnchor == eEditAs.Absolute) { - GetToRowFromPixels(Position.Y, out fromRow, out fromRowOff); - GetToColumnFromPixels(Position.X, out fromCol, out fromColOff); + GetToRowFromPixels(Position.Y / (double)EMU_PER_PIXEL, out fromRow, out fromRowOff); + GetToColumnFromPixels(Position.X / (double)EMU_PER_PIXEL, out fromCol, out fromColOff); } else { @@ -747,7 +748,7 @@ internal void GetToBounds(out int toRow, out int toRowOff, out int toCol, out in if (CellAnchor == eEditAs.Absolute) { GetToRowFromPixels((Position.Y + Size.Height) / EMU_PER_PIXEL, out toRow, out toRowOff); - GetToColumnFromPixels(Position.X + Size.Width / EMU_PER_PIXEL, out toCol, out toColOff); + GetToColumnFromPixels((Position.X + Size.Width) / EMU_PER_PIXEL, out toCol, out toColOff); } else { @@ -1033,6 +1034,26 @@ internal void SetPixelHeight(double pixels) internal void GetToRowFromPixels(double pixels, out int toRow, out int rowOff, int fromRow = -1, int fromRowOff = -1) { + if (From == null && this is not ExcelControl) + { + // Absolute anchor path + double remaining = pixels; + int currentRow = 1; + + while (true) + { + double rowPix = _drawings.Worksheet.GetRowHeight(currentRow) / 0.75; + if (remaining < rowPix) + break; + + remaining -= rowPix; + currentRow++; + } + + toRow = currentRow - 1; + rowOff = (int)(remaining); + return; + } if (fromRow < 0) { fromRow = From.Row; @@ -1087,7 +1108,27 @@ internal void GetToColumnFromPixels(double pixels, out int col, out int colOff, { ExcelWorksheet ws = _drawings.Worksheet; double mdw = ws.Workbook.MaxFontWidth; - if (fromColumn < 0) + if (From == null && this is not ExcelControl) + { + // Absolute anchor path + double remaining = pixels; + int currentCol = 1; + + while (true) + { + double colPix = (ws.GetColumnWidth(fromColumn) * mdw + 0.75d); + if (remaining < colPix) + break; + + remaining -= colPix; + currentCol++; + } + + col = currentCol-1; + colOff = (int)(remaining); + return; + } + if (From != null && fromColumn < 0) { fromColumn = From.Column; fromColumnOff = From.ColumnOff; diff --git a/src/EPPlusTest/Drawing/CopyDrawingTests.cs b/src/EPPlusTest/Drawing/CopyDrawingTests.cs index 171142befd..05cc5004de 100644 --- a/src/EPPlusTest/Drawing/CopyDrawingTests.cs +++ b/src/EPPlusTest/Drawing/CopyDrawingTests.cs @@ -799,5 +799,18 @@ public void s814CopySameImageTwiceToEmptyNamedRanges() SaveAndCleanup(targetPackage); } } + + + [TestMethod] + public void CopyDrawingWithAbsolutePosition () + { + using var p = OpenTemplatePackage("Test-file.xlsx"); + var sourceSheet = p.Workbook.Worksheets[1]; + using var destPackage = new ExcelPackage(); + var destSheet = destPackage.Workbook.Worksheets.Add("Dest"); + sourceSheet.Cells[1, 1, sourceSheet.Dimension.Rows, sourceSheet.Dimension.Columns].Copy(destSheet.Cells[1, 1], ExcelRangeCopyOptionFlags.ExcludeFormulas); + //Assert.AreEqual(1, destSheet.Drawings.Count); + destPackage.SaveAs($"C:\\epplusTest\\Testoutput\\AbsoluteDrawingCopy.xlsx"); + } } } From 99940b2eef06d8dff30a77f4d702d4d791f8575a Mon Sep 17 00:00:00 2001 From: AdrianEPPlus <162118292+AdrianEPPlus@users.noreply.github.com> Date: Thu, 7 May 2026 14:58:38 +0200 Subject: [PATCH 2/9] Update CopyDrawingTests.cs --- src/EPPlusTest/Drawing/CopyDrawingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EPPlusTest/Drawing/CopyDrawingTests.cs b/src/EPPlusTest/Drawing/CopyDrawingTests.cs index 05cc5004de..e71858de2e 100644 --- a/src/EPPlusTest/Drawing/CopyDrawingTests.cs +++ b/src/EPPlusTest/Drawing/CopyDrawingTests.cs @@ -810,7 +810,7 @@ public void CopyDrawingWithAbsolutePosition () var destSheet = destPackage.Workbook.Worksheets.Add("Dest"); sourceSheet.Cells[1, 1, sourceSheet.Dimension.Rows, sourceSheet.Dimension.Columns].Copy(destSheet.Cells[1, 1], ExcelRangeCopyOptionFlags.ExcludeFormulas); //Assert.AreEqual(1, destSheet.Drawings.Count); - destPackage.SaveAs($"C:\\epplusTest\\Testoutput\\AbsoluteDrawingCopy.xlsx"); + SaveAndCleanup(destPackage); } } } From 887e0ad995ab67fead8fc26ab561069c4fd35610 Mon Sep 17 00:00:00 2001 From: swmal <{ID}+username}@users.noreply.github.com> Date: Fri, 8 May 2026 16:12:51 +0200 Subject: [PATCH 3/9] #2346 Fix NRE and incorrect column resolution for absolute-anchored drawings.Extract duplicated pixel calculations to PixelHelper. Add tests. --- src/EPPlus/Drawing/ExcelDrawing.cs | 41 +++--- src/EPPlus/Utils/Drawing/PixelHelper.cs | 47 +++++++ src/EPPlusTest/Drawing/CopyDrawingTests.cs | 139 +++++++++++++++++++++ 3 files changed, 205 insertions(+), 22 deletions(-) create mode 100644 src/EPPlus/Utils/Drawing/PixelHelper.cs diff --git a/src/EPPlus/Drawing/ExcelDrawing.cs b/src/EPPlus/Drawing/ExcelDrawing.cs index 7498d56e19..1147ea1e9a 100644 --- a/src/EPPlus/Drawing/ExcelDrawing.cs +++ b/src/EPPlus/Drawing/ExcelDrawing.cs @@ -18,6 +18,8 @@ Date Author Change using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; using OfficeOpenXml.Packaging; +using OfficeOpenXml.Utils; +using OfficeOpenXml.Utils.Drawings; using OfficeOpenXml.Utils.EnumUtils; using OfficeOpenXml.Utils.FileUtils; using OfficeOpenXml.Utils.XML; @@ -26,6 +28,7 @@ Date Author Change using System.Globalization; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Xml; @@ -856,15 +859,14 @@ internal double GetPixelWidth() if (CellAnchor == eEditAs.TwoCell) { ExcelWorksheet ws = _drawings.Worksheet; - double mdw = ws.Workbook.MaxFontWidth; pix = -From.ColumnOff / (double)EMU_PER_PIXEL; for (int col = From.Column + 1; col <= To.Column; col++) { - pix += MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(col) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw); + pix += PixelHelper.GetColumnWidth(ws, col); } - var w = MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(To.Column + 1) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw); + var w = PixelHelper.GetColumnWidth(ws, To.Column + 1); pix += Math.Min(w, Convert.ToDouble(To.ColumnOff) / EMU_PER_PIXEL); } else @@ -899,9 +901,9 @@ internal double GetPixelHeight() pix = -(From.RowOff / (double)EMU_PER_PIXEL); for (int row = From.Row + 1; row <= To.Row; row++) { - pix += ws.GetRowHeight(row) / 0.75; + pix += PixelHelper.GetRowHeight(ws, row); } - var h = ws.GetRowHeight(To.Row + 1) / 0.75; + var h = PixelHelper.GetRowHeight(ws, To.Row + 1); pix += Math.Min(h, Convert.ToDouble(To.RowOff) / EMU_PER_PIXEL); } else @@ -940,12 +942,12 @@ internal void CalcRowFromPixelTop(double pixels, out int row, out int rowOff) ExcelWorksheet ws = _drawings.Worksheet; double mdw = ws.Workbook.MaxFontWidth; double prevPix = 0; - double pix = ws.GetRowHeight(1) / 0.75; + double pix = PixelHelper.GetRowHeight(ws, 1); int r = 2; while (pix < pixels) { prevPix = pix; - pix += (int)(ws.GetRowHeight(r++) / 0.75); + pix += (int)PixelHelper.GetRowHeight(ws, r++); } if (pix == pixels) @@ -988,15 +990,14 @@ internal void CalcColFromPixelLeft(double pixels, out int column, out int column { ExcelWorksheet ws = _drawings.Worksheet; - double mdw = ws.Workbook.MaxFontWidth; double prevPix = 0; - double pix = (int)MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(1) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw); + double pix = (int)PixelHelper.GetColumnWidth(ws, 1); int col = 2; while (pix < pixels) { prevPix = pix; - pix += (int)MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(col++) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw); + pix += (int)PixelHelper.GetColumnWidth(ws, col++); } if (pix == pixels) { @@ -1042,7 +1043,7 @@ internal void GetToRowFromPixels(double pixels, out int toRow, out int rowOff, i while (true) { - double rowPix = _drawings.Worksheet.GetRowHeight(currentRow) / 0.75; + double rowPix = PixelHelper.GetRowHeight(_drawings.Worksheet, currentRow); if (remaining < rowPix) break; @@ -1060,14 +1061,14 @@ internal void GetToRowFromPixels(double pixels, out int toRow, out int rowOff, i fromRowOff = From.RowOff; } ExcelWorksheet ws = _drawings.Worksheet; - var pixOff = pixels - ((ws.GetRowHeight(fromRow + 1) / 0.75) - (fromRowOff / (double)EMU_PER_PIXEL)); + var pixOff = pixels - (PixelHelper.GetRowHeight(ws, fromRow + 1) - (fromRowOff / (double)EMU_PER_PIXEL)); double prevPixOff = pixels; int row = fromRow + 1; while (pixOff >= 0) { prevPixOff = pixOff; - pixOff -= (ws.GetRowHeight(++row) / 0.75); + pixOff -= PixelHelper.GetRowHeight(ws, ++row); } toRow = row - 1; if (fromRow == toRow) @@ -1107,21 +1108,17 @@ internal void SetPixelWidth(double pixels) internal void GetToColumnFromPixels(double pixels, out int col, out int colOff, int fromColumn = -1, int fromColumnOff = -1) { ExcelWorksheet ws = _drawings.Worksheet; - double mdw = ws.Workbook.MaxFontWidth; if (From == null && this is not ExcelControl) { // Absolute anchor path double remaining = pixels; int currentCol = 1; - - while (true) + double colPix = PixelHelper.GetColumnWidth(ws, currentCol); + while (remaining >= colPix) { - double colPix = (ws.GetColumnWidth(fromColumn) * mdw + 0.75d); - if (remaining < colPix) - break; - remaining -= colPix; currentCol++; + colPix = PixelHelper.GetColumnWidth(ws, currentCol); } col = currentCol-1; @@ -1133,13 +1130,13 @@ internal void GetToColumnFromPixels(double pixels, out int col, out int colOff, fromColumn = From.Column; fromColumnOff = From.ColumnOff; } - double pixOff = pixels - (MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(fromColumn + 1) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw) - fromColumnOff / EMU_PER_PIXEL); + double pixOff = pixels - (PixelHelper.GetColumnWidth(ws, fromColumn + 1) - fromColumnOff / EMU_PER_PIXEL); double offset = (double)fromColumnOff / EMU_PER_PIXEL + pixels; col = fromColumn + 2; while (pixOff >= 0) { offset = pixOff; - pixOff -= MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(col++) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw); + pixOff -= PixelHelper.GetColumnWidth(ws, col++); } colOff = (int)offset; } diff --git a/src/EPPlus/Utils/Drawing/PixelHelper.cs b/src/EPPlus/Utils/Drawing/PixelHelper.cs new file mode 100644 index 0000000000..3057de3c87 --- /dev/null +++ b/src/EPPlus/Utils/Drawing/PixelHelper.cs @@ -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 + ************************************************************************************************* + 05/08/2026 EPPlus Software AB Initial release + *************************************************************************************************/ +using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; + +namespace OfficeOpenXml.Utils.Drawings +{ + /// + /// Helper methods for converting between worksheet coordinates and pixels. + /// + internal static class PixelHelper + { + /// + /// Returns the width of a column in pixels, using the same formula + /// Excel uses internally. + /// + /// The worksheet. + /// The 1-based column index. + /// The column width in pixels. + internal static double GetColumnWidth(ExcelWorksheet ws, int column) + { + double mdw = ws.Workbook.MaxFontWidth; + return MathHelper.TruncateDouble( + ((256 * ws.GetColumnWidth(column) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw); + } + + /// + /// Returns the height of a row in pixels. + /// + /// The worksheet. + /// The 1-based row index. + /// The row height in pixels. + internal static double GetRowHeight(ExcelWorksheet ws, int row) + { + return ws.GetRowHeight(row) / 0.75; + } + } +} \ No newline at end of file diff --git a/src/EPPlusTest/Drawing/CopyDrawingTests.cs b/src/EPPlusTest/Drawing/CopyDrawingTests.cs index e71858de2e..1ab47a2d51 100644 --- a/src/EPPlusTest/Drawing/CopyDrawingTests.cs +++ b/src/EPPlusTest/Drawing/CopyDrawingTests.cs @@ -812,5 +812,144 @@ public void CopyDrawingWithAbsolutePosition () //Assert.AreEqual(1, destSheet.Drawings.Count); SaveAndCleanup(destPackage); } + + [TestMethod] + public void GetFromAndToBounds_AbsoluteAnchor_FromCornerAndToRow_ResolveCorrectly() + { + // Reproduces the customer ticket where calling GetFromBounds / + // GetToBounds on an absolute-anchored drawing throws NRE because + // From is null after the workbook has been read from disk. + // + // This test verifies the From corner and the To row resolve to the + // correct cell coordinates. The To column is verified separately + // in GetFromAndToBounds_AbsoluteAnchor_ToColumn_ResolvesCorrectly. + // + // The drawing is positioned at (6 px, 136 px) with size (1375 px, + // 20 px). Column widths are explicit; row heights use the workbook + // default (15 pt = 20 px per row). MaxFontWidth is the default 7. + const string fileName = "AbsoluteAnchorBounds.xlsx"; + + // Build and save the workbook. Saving and reloading is required to + // produce the From == null state that triggers the customer's NRE. + using (var p = OpenPackage(fileName, delete: true)) + { + var ws = p.Workbook.Worksheets.Add("AbsoluteAnchorTest"); + + ws.Column(1).Width = 10.6640625; + ws.Column(2).Width = 5.5546875; + ws.Column(3).Width = 5; + ws.Column(4).Width = 7; + ws.Column(5).Width = 8.109375; + ws.Column(6).Width = 9; + ws.Column(7).Width = 28; + ws.Column(8).Width = 8.88671875; + ws.Column(9).Width = 8.88671875; + ws.Column(10).Width = 8.33203125; + ws.Column(11).Width = 8.33203125; + ws.Column(12).Width = 27; + ws.Column(13).Width = 7.5546875; + ws.Column(14).Width = 8.33203125; + ws.Column(15).Width = 19.5546875; + ws.Column(16).Width = 9; + ws.Column(17).Width = 7.6640625; + ws.Column(18).Width = 6.109375; + // Column 19 keeps the default width. + + var pic = ws.Drawings.AddPicture("AbsBar", GetResourceFile("EPPlus.png")); + pic.ChangeCellAnchor(eEditAs.Absolute, PixelTop: 136, PixelLeft: 6, + width: 1375, height: 20); + p.Save(); + } + + using (var p = OpenPackage(fileName)) + { + var ws = p.Workbook.Worksheets["AbsoluteAnchorTest"]; + var pic = ws.Drawings[0]; + + Assert.AreEqual(eEditAs.Absolute, pic.CellAnchor); + Assert.IsNull(pic.From, "Reloaded absolute-anchored drawings should have null From."); + Assert.AreEqual(7, p.Workbook.MaxFontWidth, "Test assumes default MaxFontWidth = 7."); + + pic.GetFromBounds(out int fromRow, out int fromRowOff, + out int fromCol, out int fromColOff); + pic.GetToBounds(out int toRow, out int toRowOff, + out int toCol, out int toColOff); + + // From corner at pixel (6, 136). + // Column: pixel 6 falls inside column 1 (0-indexed: 0), 6 px in. + // Row: pixel 136 with row height 20 px; six full rows consume + // 120 px, leaving 16 px offset in row 7. + Assert.AreEqual(0, fromCol, "From column should be A (0-indexed)."); + Assert.AreEqual(6, fromColOff, "From column offset should be 6 px."); + Assert.AreEqual(6, fromRow, "From row should be 7 (0-indexed)."); + Assert.AreEqual(16, fromRowOff, "From row offset should be 16 px."); + + // To corner at pixel (1381, 156). + // Row: pixel 156 falls inside row 8 (0-indexed: 7), 16 px in. + Assert.AreEqual(7, toRow, "To row should be 8 (0-indexed)."); + Assert.AreEqual(16, toRowOff, "To row offset should be 16 px."); + } + } + + [TestMethod] + public void GetFromAndToBounds_AbsoluteAnchor_ToColumn_ResolvesCorrectly() + { + // Companion test to GetFromAndToBounds_AbsoluteAnchor_FromCornerAndToRow_ResolveCorrectly. + // + // Verifies that the To column resolves correctly for an absolute- + // anchored drawing. This test currently FAILS due to a known bug + // in the absolute-anchor branch of GetToColumnFromPixels: the loop + // measures column width using the unset 'fromColumn' parameter + // (-1) instead of the iterating 'currentCol', so every iteration + // uses the workbook default column width and the result is wrong + // whenever the worksheet has columns of varying widths. + // + // Expected values are derived from walking the actual column + // widths set up in the workbook below: pixel 1381 lands in + // column 19 (0-indexed: 18) with a 30 px offset. + // + // Once the bug is fixed this test should pass. + const string fileName = "AbsoluteAnchorBounds.xlsx"; + + using (var p = OpenPackage(fileName, delete: true)) + { + var ws = p.Workbook.Worksheets.Add("AbsoluteAnchorTest"); + + ws.Column(1).Width = 10.6640625; + ws.Column(2).Width = 5.5546875; + ws.Column(3).Width = 5; + ws.Column(4).Width = 7; + ws.Column(5).Width = 8.109375; + ws.Column(6).Width = 9; + ws.Column(7).Width = 28; + ws.Column(8).Width = 8.88671875; + ws.Column(9).Width = 8.88671875; + ws.Column(10).Width = 8.33203125; + ws.Column(11).Width = 8.33203125; + ws.Column(12).Width = 27; + ws.Column(13).Width = 7.5546875; + ws.Column(14).Width = 8.33203125; + ws.Column(15).Width = 19.5546875; + ws.Column(16).Width = 9; + ws.Column(17).Width = 7.6640625; + ws.Column(18).Width = 6.109375; + + var pic = ws.Drawings.AddPicture("AbsBar", GetResourceFile("EPPlus.png")); + pic.ChangeCellAnchor(eEditAs.Absolute, PixelTop: 136, PixelLeft: 6, + width: 1375, height: 20); + p.Save(); + } + + using (var p = OpenPackage(fileName)) + { + var ws = p.Workbook.Worksheets["AbsoluteAnchorTest"]; + var pic = ws.Drawings[0]; + + pic.GetToBounds(out _, out _, out int toCol, out int toColOff); + + Assert.AreEqual(18, toCol, "To column should be S (0-indexed)."); + Assert.AreEqual(30, toColOff, "To column offset should be ~30 px."); + } + } } } From 0822bf8287ec13bb50ae42c8f39b76e1623d8fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Tue, 12 May 2026 14:28:44 +0200 Subject: [PATCH 4/9] Fixed positioning for absolute drawings when copying drawings. Performance fix for copy of full column/row addresses. The ArrayToText function did not work if supplied to a formula with a prefix. Added update of version, calculation engine id and calculation features in the workbook xml for new workbooks and updating existing. --- src/EPPlus/Core/RangeCopyHelper.cs | 34 ++++++----- src/EPPlus/Drawing/ExcelDrawing.cs | 48 +++++++++++++-- src/EPPlus/ExcelRangeBase.cs | 81 +++++++++++++++++++++----- src/EPPlus/ExcelWorkbook.cs | 17 +++++- src/EPPlusTest/Issues/DrawingIssues.cs | 20 +++++++ 5 files changed, 168 insertions(+), 32 deletions(-) diff --git a/src/EPPlus/Core/RangeCopyHelper.cs b/src/EPPlus/Core/RangeCopyHelper.cs index 2238c7ddb8..da70afee85 100644 --- a/src/EPPlus/Core/RangeCopyHelper.cs +++ b/src/EPPlus/Core/RangeCopyHelper.cs @@ -925,23 +925,27 @@ private void CopyMergedCells(Dictionary copiedMergedCells) private void CopyFullRow() { - if (_sourceRange._fromRow == 1 && _sourceRange._toRow == ExcelPackage.MaxRows) + _sourceRange.GetAddressDimensionFullRowAndColumn(out int dimFromRow, out int dimFromCol, out int dimToRow, out int dimToCol); + if (dimFromRow == 0 && dimFromCol==0) return; + if (_sourceRange._fromRow == 1 && _sourceRange._toRow == ExcelPackage.MaxRows && dimFromCol > 0) { - for (int col = 0; col < _sourceRange.Columns; col++) + var diff = dimFromCol - _sourceRange._fromCol; + for (int col = 0; col < (dimToCol-dimFromCol + 1); col++) { - _destinationRange.Worksheet.Column(_destinationRange.Start.Column + col).OutlineLevel = _sourceRange.Worksheet.Column(_sourceRange._fromCol + col).OutlineLevel; + _destinationRange.Worksheet.Column(_destinationRange.Start.Column + col + diff).OutlineLevel = _sourceRange.Worksheet.Column(_sourceRange._fromCol + col + diff).OutlineLevel; } } - if (EnumUtil.HasFlag(_copyOptions, ExcelRangeCopyOptionFlags.IncludeFullRow)) + if (EnumUtil.HasFlag(_copyOptions, ExcelRangeCopyOptionFlags.IncludeFullRow) && dimFromRow > 0) { var sourceRowOrig = _sourceRange._fromRow; var destRowOrig = _destinationRange._fromRow; - for (int i = 0; i < _sourceRange.Rows; i++) + var diff = dimFromRow - _sourceRange._fromRow; + for (int i = 0; i < (dimToRow - dimFromRow + 1); i++) { - var sourceRow = _sourceRange.Worksheet.Row(sourceRowOrig + i); - var destRow = _destinationRange.Worksheet.Row(destRowOrig + i); + var sourceRow = _sourceRange.Worksheet.Row(sourceRowOrig + i + diff); + var destRow = _destinationRange.Worksheet.Row(destRowOrig + i + diff); destRow.Height = sourceRow.Height; } @@ -950,23 +954,27 @@ private void CopyFullRow() private void CopyFullColumn() { + _sourceRange.GetAddressDimensionFullRowAndColumn(out int dimFromRow, out int dimFromCol, out int dimToRow, out int dimToCol); + if (dimFromRow == 0 && dimFromCol == 0) return; if (_sourceRange._fromCol == 1 && _sourceRange._toCol == ExcelPackage.MaxColumns) { - for (int row = 0; row < _sourceRange.Rows; row++) + var diff = dimFromRow - _sourceRange._fromRow; + for (int row = 0; row < (dimToRow - dimFromRow + 1); row++) { - _destinationRange.Worksheet.Row(_destinationRange.Start.Row + row).OutlineLevel = _sourceRange.Worksheet.Row(_sourceRange._fromRow + row).OutlineLevel; + _destinationRange.Worksheet.Row(_destinationRange.Start.Row + row + diff).OutlineLevel = _sourceRange.Worksheet.Row(_sourceRange._fromRow + row + diff).OutlineLevel; } } - if(EnumUtil.HasFlag(_copyOptions, ExcelRangeCopyOptionFlags.IncludeFullColumn)) + if(EnumUtil.HasFlag(_copyOptions, ExcelRangeCopyOptionFlags.IncludeFullColumn) && dimFromCol > 0) { var destColOrig = _destinationRange._fromCol; var sourceColOrig = _sourceRange._fromCol; - for (int i = 0; i < _sourceRange.Columns; i++) + var diff = dimFromCol - _sourceRange._fromCol; + for (int i = 0; i < (dimToCol - dimFromCol+1); i++) { - var sourceCol = _sourceRange.Worksheet.Column(sourceColOrig + i); - var destCol = _destinationRange.Worksheet.Column(destColOrig + i); + var sourceCol = _sourceRange.Worksheet.Column(sourceColOrig + i + diff); + var destCol = _destinationRange.Worksheet.Column(destColOrig + i + diff); destCol.Width = sourceCol.Width; } diff --git a/src/EPPlus/Drawing/ExcelDrawing.cs b/src/EPPlus/Drawing/ExcelDrawing.cs index 1147ea1e9a..933d23fd28 100644 --- a/src/EPPlus/Drawing/ExcelDrawing.cs +++ b/src/EPPlus/Drawing/ExcelDrawing.cs @@ -1422,10 +1422,20 @@ public void SetPosition(int Row, int RowOffsetPixels, int Column, int ColumnOffs _height = GetPixelHeight(); } - From.Row = Row; - From.RowOff = RowOffsetPixels * EMU_PER_PIXEL; - From.Column = Column; - From.ColumnOff = ColumnOffsetPixels * EMU_PER_PIXEL; + if (CellAnchor == eEditAs.Absolute) + { + GetPixelHeightFromRow(Row, RowOffsetPixels, out int pixelHeight); + + Position.Y = (int)(pixelHeight * EMU_PER_PIXEL); + Position.X = (int)(ColumnOffsetPixels * EMU_PER_PIXEL); + } + else + { + From.Row = Row; + From.RowOff = RowOffsetPixels * EMU_PER_PIXEL; + From.Column = Column; + From.ColumnOff = ColumnOffsetPixels * EMU_PER_PIXEL; + } if (CellAnchor == eEditAs.TwoCell) { _left = GetPixelLeft(); @@ -1437,6 +1447,36 @@ public void SetPosition(int Row, int RowOffsetPixels, int Column, int ColumnOffs _doNotAdjust = false; UpdatePositionAndSizeXml(); } + private void GetPixelWidthFromRow(int toCol, int colOffsetPixels, out int pixelWidth) + { + ExcelWorksheet ws = _drawings.Worksheet; + double mdw = ws.Workbook.MaxFontWidth; + + pixelWidth = 0; + for (int col = 0; col < toCol; col++) + { + pixelWidth += ws.GetColumnWidthPixels(col, mdw); + } + pixelWidth += colOffsetPixels; + } + private void GetPixelHeightFromRow(int toRow, int rowOffsetPixels, out int pixelHeight) + { + pixelHeight = 0; + var cache = _drawings.Worksheet.RowHeightCache; + for (int row = 0; row < toRow; row++) + { + lock (cache) + { + if (!cache.ContainsKey(row)) + { + cache.Add(row, _drawings.Worksheet.GetRowHeight(row + 1)); + } + } + pixelHeight += (int)(cache[row] / 0.75); + } + pixelHeight += rowOffsetPixels; + } + /// /// Set size in Percent. /// Note that resizing columns / rows after using this function will effect the size of the drawing diff --git a/src/EPPlus/ExcelRangeBase.cs b/src/EPPlus/ExcelRangeBase.cs index a681380e37..b358fe1037 100644 --- a/src/EPPlus/ExcelRangeBase.cs +++ b/src/EPPlus/ExcelRangeBase.cs @@ -771,31 +771,70 @@ private object GetValueArray() } return v; } - private ExcelAddressBase GetAddressDim(ExcelRangeBase addr) + internal ExcelAddressBase GetAddressDimension() { - int fromRow, fromCol, toRow, toCol; - var d = _worksheet.Dimension; - fromRow = addr._fromRow < d._fromRow ? d._fromRow : addr._fromRow; - fromCol = addr._fromCol < d._fromCol ? d._fromCol : addr._fromCol; - - toRow = addr._toRow > d._toRow ? d._toRow : addr._toRow; - toCol = addr._toCol > d._toCol ? d._toCol : addr._toCol; - - if (addr._fromRow == fromRow && addr._fromCol == fromCol && addr._toRow == toRow && addr._toCol == _toCol) + GetAddressDimensionFullRowAndColumn(out int dimFromRow, out int dimFromCol, out int dimToRow, out int dimToCol); + //If the range is only full column or full row the dimension of the worksheet, return null. + if (dimFromCol==0 || dimFromRow>dimToCol || dimFromCol > dimToCol) { - return addr; + return null; } else { - if (_fromRow > _toRow || _fromCol > _toCol) + return new ExcelAddressBase(dimFromRow, dimFromCol, dimToRow, dimToCol); + } + } + internal void GetAddressDimensionFullRowAndColumn(out int fromRow, out int fromCol, out int toRow, out int toCol) + { + var d = _worksheet.Dimension; + fromRow = toRow = fromCol = toCol = 0; + if (d == null) + { + if(_worksheet._values.ColumnCount==0) { - return null; + return; } else { - return new ExcelAddressBase(fromRow, fromCol, toRow, toCol); + int row = 0, col = _worksheet._values.ColumnCount - 1; + + fromCol = _worksheet._values._columnIndex[0].Index; + if(_worksheet._values.GetPrevCell(ref row, ref col, 0, 0, col)) + { + var lastCol = _worksheet._values.GetValue(row, col)._value as ExcelColumn; + toCol = lastCol.ColumnMax; + } + + fromRow = ExcelPackage.MaxRows; + toRow = 0; + for (int c=0;c<_worksheet._values.ColumnCount;c++) + { + var pMin = _worksheet._values._columnIndex[c]._pages[0].MinIndex; + if (pMin < fromRow) + { + fromRow = pMin; + } + var pMax = _worksheet._values._columnIndex[c]._pages[_worksheet._values._columnIndex[c].PageCount-1].MaxIndex; + if (pMax > toRow) + { + toRow = pMax; + } + } } } + else + { + fromRow = d._fromRow; + fromCol = d._fromCol; + toRow = d._toRow; + toCol = d._toCol; + } + + if(fromRow > 0) fromRow = _fromRow < fromRow ? fromRow : _fromRow; + if(fromCol > 0) fromCol = _fromCol < fromCol ? fromCol : _fromCol; + + if(toRow > 0) toRow = _toRow > toRow ? toRow : _toRow; + if(toCol > 0) toCol = _toCol > toCol ? toCol : _toCol; } private object GetSingleValue() @@ -1505,6 +1544,20 @@ public ExcelWorksheet Worksheet return _worksheet; } } + public ExcelAddressBase DimensionAdjustedAddress + { + get + { + if (_worksheet.Dimension == null) + { + return this; + } + else + { + return GetAddressDimension(); + } + } + } /// /// Address including sheet name /// diff --git a/src/EPPlus/ExcelWorkbook.cs b/src/EPPlus/ExcelWorkbook.cs index 96d54380b3..45f76baef2 100644 --- a/src/EPPlus/ExcelWorkbook.cs +++ b/src/EPPlus/ExcelWorkbook.cs @@ -1153,7 +1153,7 @@ private void CreateWorkbookXml(XmlNamespaceManager namespaceManager) { _workbookXml = _package.GetXmlFromUri(WorkbookUri); ValidateWorkbookNamespace(); - } + } else { // create a new workbook part and add to the package @@ -1171,12 +1171,27 @@ private void CreateWorkbookXml(XmlNamespaceManager namespaceManager) _workbookXml.AppendChild(wbElem); + XmlElement fileVersion = _workbookXml.CreateElement("fileVersion", ExcelPackage.schemaMain); + fileVersion.SetAttribute("appName", "xl"); + fileVersion.SetAttribute("lastEdited", "7"); //Set the last edited version to the latest known version. This will make sure that Excel does not downgrade the file and that new features are supported. + fileVersion.SetAttribute("lowestEdited", "7"); //Set the lowest edited version to the latest known version. This will make sure that Excel does not downgrade the file and that new features are supported. + wbElem.AppendChild(fileVersion); + // create the bookViews and workbooks element XmlElement bookViews = _workbookXml.CreateElement("bookViews", ExcelPackage.schemaMain); wbElem.AppendChild(bookViews); XmlElement workbookView = _workbookXml.CreateElement("workbookView", ExcelPackage.schemaMain); bookViews.AppendChild(workbookView); + XmlElement calcPr = _workbookXml.CreateElement("calcPr", ExcelPackage.schemaMain); + calcPr.SetAttribute("calcId", "191029"); //Set the version of the calc engine to the latest known version. This will make sure that Excel does not downgrade the calculation engine and that new functions are supported. + wbElem.AppendChild(bookViews); + + //Include the extLst with the calc features to make sure new functions are supported in Excel. + XmlElement extLst = _workbookXml.CreateElement("extLst", ExcelPackage.schemaMain); + extLst.InnerXml = $""; + wbElem.AppendChild(extLst); + // save it to the package StreamWriter stream = new StreamWriter(partWorkbook.GetStream(FileMode.Create, FileAccess.Write)); _workbookXml.Save(stream); diff --git a/src/EPPlusTest/Issues/DrawingIssues.cs b/src/EPPlusTest/Issues/DrawingIssues.cs index 00146aad97..8d52b8b5bb 100644 --- a/src/EPPlusTest/Issues/DrawingIssues.cs +++ b/src/EPPlusTest/Issues/DrawingIssues.cs @@ -196,6 +196,26 @@ public void i2303() SaveAndCleanup(package); } } + [TestMethod] + public void s1045() + { + using var p = OpenTemplatePackage("s1045.xlsx"); + var sourceSheet = p.Workbook.Worksheets[1]; + + using var destPackage = OpenPackage("s1045-copy.xlsx", true); + var destSheet = destPackage.Workbook.Worksheets.Add("Dest"); + + + + // This line will throw System.NullReferenceException in EPPlus + + // at OfficeOpenXml.Drawing.ExcelDrawing.GetToRowFromPixels -> GetFromBounds -> GetAddress -> CopyDrawings -> Copy + + sourceSheet.Cells.Copy(destSheet.Cells[1, 1], ExcelRangeCopyOptionFlags.ExcludeFormulas); + + Console.WriteLine("Done (no crash)."); + SaveAndCleanup(destPackage); + } } } From 473eec98311c75cc33980d1fe710d22dce83b92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Tue, 12 May 2026 14:40:38 +0200 Subject: [PATCH 5/9] Fixed missing uncommited files from earlier commit. --- src/EPPlus/Constants/Schemas.cs | 2 + src/EPPlus/ExcelPackage.cs | 1 + src/EPPlus/ExcelWorkbook.cs | 101 ++++++++++++++++-- .../DependencyChain/RpnFormulaExecution.cs | 2 +- .../Excel/Functions/Text/ArrayToText.cs | 2 +- 5 files changed, 98 insertions(+), 10 deletions(-) diff --git a/src/EPPlus/Constants/Schemas.cs b/src/EPPlus/Constants/Schemas.cs index 38e7557ba0..6241b3ed30 100644 --- a/src/EPPlus/Constants/Schemas.cs +++ b/src/EPPlus/Constants/Schemas.cs @@ -20,5 +20,7 @@ internal class Schemas internal const string schemaRichValueRel = "http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel"; internal const string schemaWebImage = "http://schemas.microsoft.com/office/spreadsheetml/2020/richdatawebimage"; internal const string schemaDataMashup = "http://schemas.microsoft.com/DataMashup"; + + internal const string schemaCalcFeature = "http://schemas.microsoft.com/office/spreadsheetml/2018/calcfeatures"; } } diff --git a/src/EPPlus/ExcelPackage.cs b/src/EPPlus/ExcelPackage.cs index 10dc020b79..6aec485c67 100644 --- a/src/EPPlus/ExcelPackage.cs +++ b/src/EPPlus/ExcelPackage.cs @@ -864,6 +864,7 @@ private XmlNamespaceManager CreateDefaultNSM() ns.AddNamespace("xda", schemaDynamicArrays); ns.AddNamespace("clbl", schemaMipLabelMetadata); ns.AddNamespace("xfpb", Schemas.schemaFeaturePropertyBag); + ns.AddNamespace("xcalcf", Schemas.schemaCalcFeature); return ns; } #region SavePart diff --git a/src/EPPlus/ExcelWorkbook.cs b/src/EPPlus/ExcelWorkbook.cs index 45f76baef2..02b0c79d79 100644 --- a/src/EPPlus/ExcelWorkbook.cs +++ b/src/EPPlus/ExcelWorkbook.cs @@ -16,6 +16,7 @@ Date Author Change using System.Collections.Generic; using System.Text; using System.Globalization; +using System.Linq; using OfficeOpenXml.VBA; using OfficeOpenXml.FormulaParsing; using OfficeOpenXml.FormulaParsing.LexicalAnalysis; @@ -26,7 +27,6 @@ Date Author Change using OfficeOpenXml.Drawing.Slicer; using OfficeOpenXml.ThreadedComments; using OfficeOpenXml.Table; -using System.Linq; using OfficeOpenXml.Table.PivotTable; using OfficeOpenXml.Drawing; using OfficeOpenXml.Constants; @@ -319,6 +319,20 @@ private void SetUris() internal int _nextDrawingId = 2; internal int _nextTableID = int.MinValue; internal int _nextPivotCacheId = 1; + + bool _workbookCreatedInEPPlus; + // xcalcf:feature entries + string[] _calcFeatureStrings = { + "microsoft.com:RD", + "microsoft.com:Single", + "microsoft.com:FV", + "microsoft.com:CNMTM", + "microsoft.com:LET_WF", + "microsoft.com:LAMBDA_WF", + "microsoft.com:ARRAYTEXT_WF" + }; + + internal int GetNewPivotCacheId() { return _nextPivotCacheId++; @@ -1156,6 +1170,7 @@ private void CreateWorkbookXml(XmlNamespaceManager namespaceManager) } else { + _workbookCreatedInEPPlus = true; // create a new workbook part and add to the package Packaging.ZipPackagePart partWorkbook = _package.ZipPackage.CreatePart(WorkbookUri, @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", _package.Compression); @@ -1172,9 +1187,7 @@ private void CreateWorkbookXml(XmlNamespaceManager namespaceManager) _workbookXml.AppendChild(wbElem); XmlElement fileVersion = _workbookXml.CreateElement("fileVersion", ExcelPackage.schemaMain); - fileVersion.SetAttribute("appName", "xl"); - fileVersion.SetAttribute("lastEdited", "7"); //Set the last edited version to the latest known version. This will make sure that Excel does not downgrade the file and that new features are supported. - fileVersion.SetAttribute("lowestEdited", "7"); //Set the lowest edited version to the latest known version. This will make sure that Excel does not downgrade the file and that new features are supported. + UpdateFileVersionAttributes(fileVersion); wbElem.AppendChild(fileVersion); // create the bookViews and workbooks element @@ -1185,11 +1198,10 @@ private void CreateWorkbookXml(XmlNamespaceManager namespaceManager) XmlElement calcPr = _workbookXml.CreateElement("calcPr", ExcelPackage.schemaMain); calcPr.SetAttribute("calcId", "191029"); //Set the version of the calc engine to the latest known version. This will make sure that Excel does not downgrade the calculation engine and that new functions are supported. - wbElem.AppendChild(bookViews); + wbElem.AppendChild(calcPr); - //Include the extLst with the calc features to make sure new functions are supported in Excel. XmlElement extLst = _workbookXml.CreateElement("extLst", ExcelPackage.schemaMain); - extLst.InnerXml = $""; + AddCalculationFeatures(extLst); wbElem.AppendChild(extLst); // save it to the package @@ -1199,6 +1211,77 @@ private void CreateWorkbookXml(XmlNamespaceManager namespaceManager) _package.ZipPackage.Flush(); } } + + private void AddCalculationFeatures(XmlElement extLst) + { + //Include the extLst with the calc features to make sure new functions are supported in Excel. + XmlElement ext = _workbookXml.CreateElement("ext", ExcelPackage.schemaMain); + ext.SetAttribute("uri", "{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}"); + extLst.AppendChild(ext); + + XmlElement calcFeatures = _workbookXml.CreateElement("xcalcf", "calcFeatures", Schemas.schemaCalcFeature); + + foreach (string name in _calcFeatureStrings) + { + XmlElement feature = _workbookXml.CreateElement("xcalcf", "feature", Schemas.schemaCalcFeature); + feature.SetAttribute("name", name); + calcFeatures.AppendChild(feature); + } + + ext.AppendChild(calcFeatures); + } + + private static void UpdateFileVersionAttributes(XmlElement fileVersion) + { + fileVersion.SetAttribute("appName", "xl"); //We write "xl" here to ensure compatibility with Excel. + fileVersion.SetAttribute("lastEdited", "7"); //Set the last edited version to the latest known version. This will make sure that Excel does not downgrade the file and that new features are supported. + fileVersion.SetAttribute("lowestEdited", "7"); //Set the lowest edited version to the latest known version. This will make sure that Excel does not downgrade the file and that new features are supported. + } + /// + /// To support functions introduced in newer versions of Excel, we need to make sure that the file version and calculation engine version are set to the latest known version and that the calculation features are included in the file. + /// This method ensures that this is the case. + /// It is called when creating a new workbook and when loading a template. + /// + internal void EnsureCalculationFeatures() + { + var fileVersion= (XmlElement)GetNode("d:fileVersion"); + if(fileVersion == null) + { + fileVersion=CreateNode("d:fileVersion") as XmlElement; + } + UpdateFileVersionAttributes(fileVersion); + + var calcPr = (XmlElement)CreateNode("d:calcPr"); + calcPr.SetAttribute("calcId", "191029"); //Set the version of the calc engine to the latest known version. This will make sure that Excel does not downgrade the calculation engine and that new functions are supported. + + var extLst = (XmlElement)GetNode("d:extLst"); + if(extLst == null) + { + extLst = CreateNode("d:extLst") as XmlElement; + AddCalculationFeatures(extLst); + } + else + { + var calcFeatures = (XmlElement)GetNode("d:extLst/d:ext[@uri='{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}']/xcalcf:calcFeatures"); + if(calcFeatures==null) + { + var extNode = (XmlElement)CreateNode("d:extLst/d:ext", false, true); + extNode.SetAttribute("uri", "{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}"); + calcFeatures = _workbookXml.CreateElement("xcalcf", "calcFeatures", Schemas.schemaCalcFeature); + extNode.AppendChild(calcFeatures); + } + + foreach (string name in _calcFeatureStrings) + { + if (calcFeatures.SelectSingleNode($"xcalcf:feature[@name=\"{name}\"]", NameSpaceManager) == null) + { + XmlElement feature = _workbookXml.CreateElement("xcalcf", "feature", Schemas.schemaCalcFeature); + feature.SetAttribute("name", name); + calcFeatures.AppendChild(feature); + } + } + } + } #endregion #region StylesXml private XmlDocument _stylesXml; @@ -1481,7 +1564,9 @@ internal void Save() // Workbook Save DeleteCalcChain(); SetXmlNodeBool("d:calcPr/@fullPrecision", FullPrecision, false); - + + if(_workbookCreatedInEPPlus == false) EnsureCalculationFeatures(); //Ensure that the calculation features are included in the file to make sure that new functions are supported. + if (_vba == null && !_package.ZipPackage.PartExists(new Uri(ExcelVbaProject.PartUri, UriKind.Relative))) { if (Part.ContentType != ContentTypes.contentTypeWorkbookDefault && diff --git a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs index d8ccdb85ea..0fe2970505 100644 --- a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs +++ b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs @@ -56,7 +56,7 @@ internal static RpnOptimizedDependencyChain Execute(ExcelWorkbook wb, ExcelCalcu } } ExecuteChain(depChain, wb.Names, options, true); - + return depChain; } internal static RpnOptimizedDependencyChain Execute(ExcelWorksheet ws, ExcelCalculationOption options) diff --git a/src/EPPlus/FormulaParsing/Excel/Functions/Text/ArrayToText.cs b/src/EPPlus/FormulaParsing/Excel/Functions/Text/ArrayToText.cs index 242f4dd593..d2b5d214b9 100644 --- a/src/EPPlus/FormulaParsing/Excel/Functions/Text/ArrayToText.cs +++ b/src/EPPlus/FormulaParsing/Excel/Functions/Text/ArrayToText.cs @@ -98,7 +98,7 @@ public override CompileResult Execute(IList arguments, Parsing } return CreateResult(resultStr, DataType.String); } - + public override string NamespacePrefix => "_xlfn."; private static string GetStringVal(object val, int format) { string strVal = string.Empty; From 112197f68778a2c56d7f53bc9e6c7cce893b3a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Wed, 13 May 2026 10:30:31 +0200 Subject: [PATCH 6/9] Added checks for max row/columns when calculating row/column height/width. Add xml comments. --- src/EPPlus/Drawing/ExcelDrawing.cs | 6 ++--- src/EPPlus/ExcelRangeBase.cs | 5 ++++ .../Core/Worksheet/WorksheetCoreTests.cs | 27 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/EPPlus/Drawing/ExcelDrawing.cs b/src/EPPlus/Drawing/ExcelDrawing.cs index 933d23fd28..cf0ff3a838 100644 --- a/src/EPPlus/Drawing/ExcelDrawing.cs +++ b/src/EPPlus/Drawing/ExcelDrawing.cs @@ -1041,7 +1041,7 @@ internal void GetToRowFromPixels(double pixels, out int toRow, out int rowOff, i double remaining = pixels; int currentRow = 1; - while (true) + while (true && currentRow <= ExcelPackage.MaxRows) { double rowPix = PixelHelper.GetRowHeight(_drawings.Worksheet, currentRow); if (remaining < rowPix) @@ -1065,7 +1065,7 @@ internal void GetToRowFromPixels(double pixels, out int toRow, out int rowOff, i double prevPixOff = pixels; int row = fromRow + 1; - while (pixOff >= 0) + while (pixOff >= 0 && row < ExcelPackage.MaxRows) { prevPixOff = pixOff; pixOff -= PixelHelper.GetRowHeight(ws, ++row); @@ -1114,7 +1114,7 @@ internal void GetToColumnFromPixels(double pixels, out int col, out int colOff, double remaining = pixels; int currentCol = 1; double colPix = PixelHelper.GetColumnWidth(ws, currentCol); - while (remaining >= colPix) + while (remaining >= colPix && currentCol < ExcelPackage.MaxColumns) { remaining -= colPix; currentCol++; diff --git a/src/EPPlus/ExcelRangeBase.cs b/src/EPPlus/ExcelRangeBase.cs index b358fe1037..de070d2d6a 100644 --- a/src/EPPlus/ExcelRangeBase.cs +++ b/src/EPPlus/ExcelRangeBase.cs @@ -1544,6 +1544,11 @@ public ExcelWorksheet Worksheet return _worksheet; } } + /// + /// Gets the range address adjusted within the worksheet dimension address. + /// If the worksheet dimension is null, the current address will be returned. + /// If the range is outside the worksheet dimension it will be adjusted to fit inside the dimension. + /// public ExcelAddressBase DimensionAdjustedAddress { get diff --git a/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs b/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs index eccffefcfc..8449970835 100644 --- a/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs +++ b/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs @@ -224,6 +224,33 @@ public void ValidateDimensionValueTest() Assert.AreEqual("B6:C7", ws.DimensionByValue.Address); } } + [TestMethod] + public void ValidateAdjustedDimensionTest() + { + using (var p = new ExcelPackage()) + { + var ws = p.Workbook.Worksheets.Add("Sheet1"); + ws.Cells["A4:H10"].Style.Numberformat.Format = "0"; + ws.Cells["B6:C7"].Value = 1; + + var range = ws.Cells["A1:K100"]; + Assert.AreEqual("A4:H10", range.DimensionAdjustedAddress.Address); + } + } + [TestMethod] + public void ValidateAdjustedDimensionEmptyTest() + { + using (var p = new ExcelPackage()) + { + var ws = p.Workbook.Worksheets.Add("Sheet1"); + ws.Column(8).Width = 100; + ws.Row(2).Height = 100; + + var range = ws.Cells["A1:K100"]; + Assert.AreEqual("A4:H10", range.DimensionAdjustedAddress.Address); + } + } + [TestMethod] public void ValidateDimensionValue2Test() { From 1add6830c78cdbc135a39e8c773fa63c232265bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Wed, 13 May 2026 10:37:17 +0200 Subject: [PATCH 7/9] Added more checks for MaxRows & MaxColums --- src/EPPlus/Drawing/ExcelDrawing.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EPPlus/Drawing/ExcelDrawing.cs b/src/EPPlus/Drawing/ExcelDrawing.cs index cf0ff3a838..07f0ea368d 100644 --- a/src/EPPlus/Drawing/ExcelDrawing.cs +++ b/src/EPPlus/Drawing/ExcelDrawing.cs @@ -944,7 +944,7 @@ internal void CalcRowFromPixelTop(double pixels, out int row, out int rowOff) double prevPix = 0; double pix = PixelHelper.GetRowHeight(ws, 1); int r = 2; - while (pix < pixels) + while (pix < pixels && r <= ExcelPackage.MaxRows) { prevPix = pix; pix += (int)PixelHelper.GetRowHeight(ws, r++); @@ -994,7 +994,7 @@ internal void CalcColFromPixelLeft(double pixels, out int column, out int column double pix = (int)PixelHelper.GetColumnWidth(ws, 1); int col = 2; - while (pix < pixels) + while (pix < pixels && col <= ExcelPackage.MaxColumns) { prevPix = pix; pix += (int)PixelHelper.GetColumnWidth(ws, col++); From d05f26a68afe5089a2df770ea2613c88d8d9e2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Wed, 13 May 2026 12:25:24 +0200 Subject: [PATCH 8/9] Fixed incorrect test --- src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs b/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs index 8449970835..ce93fc28fb 100644 --- a/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs +++ b/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs @@ -247,7 +247,7 @@ public void ValidateAdjustedDimensionEmptyTest() ws.Row(2).Height = 100; var range = ws.Cells["A1:K100"]; - Assert.AreEqual("A4:H10", range.DimensionAdjustedAddress.Address); + Assert.AreEqual("A1:K100", range.DimensionAdjustedAddress.Address); //If Dimension is null, the original address is returned. } } From 1a6d3ddced785c68157c7e2eb1dc5adb487186ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Wed, 13 May 2026 12:36:08 +0200 Subject: [PATCH 9/9] Changed DimensionAdjustedAddress to return null if Dimension is null --- src/EPPlus/ExcelRangeBase.cs | 6 +++--- .../Core/Worksheet/WorksheetCoreTests.cs | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/EPPlus/ExcelRangeBase.cs b/src/EPPlus/ExcelRangeBase.cs index de070d2d6a..1723b3e9d6 100644 --- a/src/EPPlus/ExcelRangeBase.cs +++ b/src/EPPlus/ExcelRangeBase.cs @@ -1546,8 +1546,8 @@ public ExcelWorksheet Worksheet } /// /// Gets the range address adjusted within the worksheet dimension address. - /// If the worksheet dimension is null, the current address will be returned. - /// If the range is outside the worksheet dimension it will be adjusted to fit inside the dimension. + /// If the worksheet dimension is null or the range is outside of the dimension, null will be returned. + /// If the range is partly outside the worksheet dimension it will be adjusted to fit inside the dimension. /// public ExcelAddressBase DimensionAdjustedAddress { @@ -1555,7 +1555,7 @@ public ExcelAddressBase DimensionAdjustedAddress { if (_worksheet.Dimension == null) { - return this; + return null; } else { diff --git a/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs b/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs index ce93fc28fb..242e356d38 100644 --- a/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs +++ b/src/EPPlusTest/Core/Worksheet/WorksheetCoreTests.cs @@ -238,16 +238,26 @@ public void ValidateAdjustedDimensionTest() } } [TestMethod] + public void ValidateAdjustedDimensionOutsideDimension() + { + using (var p = new ExcelPackage()) + { + var ws = p.Workbook.Worksheets.Add("Sheet1"); + + ws.Cells["B6"].Value = 1; + var range = ws.Cells["D6"]; + Assert.IsNull(range.DimensionAdjustedAddress); //If range is outside Dimension, null should be returned. + } + } + [TestMethod] public void ValidateAdjustedDimensionEmptyTest() { using (var p = new ExcelPackage()) { var ws = p.Workbook.Worksheets.Add("Sheet1"); - ws.Column(8).Width = 100; - ws.Row(2).Height = 100; var range = ws.Cells["A1:K100"]; - Assert.AreEqual("A1:K100", range.DimensionAdjustedAddress.Address); //If Dimension is null, the original address is returned. + Assert.IsNull(range.DimensionAdjustedAddress); //If Dimension is null, null should be returned. } }