Skip to content

Commit 6723424

Browse files
AdrianEPPlusswmalJanKallman
authored
Fix/copy drawing absolute position (#2346)
* Fixed issue where drawings set to absolute positioning would crash * Update CopyDrawingTests.cs * #2346 Fix NRE and incorrect column resolution for absolute-anchored drawings.Extract duplicated pixel calculations to PixelHelper. Add tests. * 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. * Fixed missing uncommited files from earlier commit. * Added checks for max row/columns when calculating row/column height/width. Add xml comments. * Added more checks for MaxRows & MaxColums * Fixed incorrect test * Changed DimensionAdjustedAddress to return null if Dimension is null --------- Co-authored-by: swmal <{ID}+username}@users.noreply.github.com> Co-authored-by: Jan Källman <jan.kallman@epplussoftware.com>
1 parent bd1f183 commit 6723424

12 files changed

Lines changed: 561 additions & 58 deletions

File tree

src/EPPlus/Constants/Schemas.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@ internal class Schemas
2020
internal const string schemaRichValueRel = "http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel";
2121
internal const string schemaWebImage = "http://schemas.microsoft.com/office/spreadsheetml/2020/richdatawebimage";
2222
internal const string schemaDataMashup = "http://schemas.microsoft.com/DataMashup";
23+
24+
internal const string schemaCalcFeature = "http://schemas.microsoft.com/office/spreadsheetml/2018/calcfeatures";
2325
}
2426
}

src/EPPlus/Core/RangeCopyHelper.cs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -925,23 +925,27 @@ private void CopyMergedCells(Dictionary<int, ExcelAddress> copiedMergedCells)
925925

926926
private void CopyFullRow()
927927
{
928-
if (_sourceRange._fromRow == 1 && _sourceRange._toRow == ExcelPackage.MaxRows)
928+
_sourceRange.GetAddressDimensionFullRowAndColumn(out int dimFromRow, out int dimFromCol, out int dimToRow, out int dimToCol);
929+
if (dimFromRow == 0 && dimFromCol==0) return;
930+
if (_sourceRange._fromRow == 1 && _sourceRange._toRow == ExcelPackage.MaxRows && dimFromCol > 0)
929931
{
930-
for (int col = 0; col < _sourceRange.Columns; col++)
932+
var diff = dimFromCol - _sourceRange._fromCol;
933+
for (int col = 0; col < (dimToCol-dimFromCol + 1); col++)
931934
{
932-
_destinationRange.Worksheet.Column(_destinationRange.Start.Column + col).OutlineLevel = _sourceRange.Worksheet.Column(_sourceRange._fromCol + col).OutlineLevel;
935+
_destinationRange.Worksheet.Column(_destinationRange.Start.Column + col + diff).OutlineLevel = _sourceRange.Worksheet.Column(_sourceRange._fromCol + col + diff).OutlineLevel;
933936
}
934937
}
935938

936-
if (EnumUtil.HasFlag(_copyOptions, ExcelRangeCopyOptionFlags.IncludeFullRow))
939+
if (EnumUtil.HasFlag(_copyOptions, ExcelRangeCopyOptionFlags.IncludeFullRow) && dimFromRow > 0)
937940
{
938941
var sourceRowOrig = _sourceRange._fromRow;
939942
var destRowOrig = _destinationRange._fromRow;
940943

941-
for (int i = 0; i < _sourceRange.Rows; i++)
944+
var diff = dimFromRow - _sourceRange._fromRow;
945+
for (int i = 0; i < (dimToRow - dimFromRow + 1); i++)
942946
{
943-
var sourceRow = _sourceRange.Worksheet.Row(sourceRowOrig + i);
944-
var destRow = _destinationRange.Worksheet.Row(destRowOrig + i);
947+
var sourceRow = _sourceRange.Worksheet.Row(sourceRowOrig + i + diff);
948+
var destRow = _destinationRange.Worksheet.Row(destRowOrig + i + diff);
945949

946950
destRow.Height = sourceRow.Height;
947951
}
@@ -950,23 +954,27 @@ private void CopyFullRow()
950954

951955
private void CopyFullColumn()
952956
{
957+
_sourceRange.GetAddressDimensionFullRowAndColumn(out int dimFromRow, out int dimFromCol, out int dimToRow, out int dimToCol);
958+
if (dimFromRow == 0 && dimFromCol == 0) return;
953959
if (_sourceRange._fromCol == 1 && _sourceRange._toCol == ExcelPackage.MaxColumns)
954960
{
955-
for (int row = 0; row < _sourceRange.Rows; row++)
961+
var diff = dimFromRow - _sourceRange._fromRow;
962+
for (int row = 0; row < (dimToRow - dimFromRow + 1); row++)
956963
{
957-
_destinationRange.Worksheet.Row(_destinationRange.Start.Row + row).OutlineLevel = _sourceRange.Worksheet.Row(_sourceRange._fromRow + row).OutlineLevel;
964+
_destinationRange.Worksheet.Row(_destinationRange.Start.Row + row + diff).OutlineLevel = _sourceRange.Worksheet.Row(_sourceRange._fromRow + row + diff).OutlineLevel;
958965
}
959966
}
960967

961-
if(EnumUtil.HasFlag(_copyOptions, ExcelRangeCopyOptionFlags.IncludeFullColumn))
968+
if(EnumUtil.HasFlag(_copyOptions, ExcelRangeCopyOptionFlags.IncludeFullColumn) && dimFromCol > 0)
962969
{
963970
var destColOrig = _destinationRange._fromCol;
964971
var sourceColOrig = _sourceRange._fromCol;
965972

966-
for (int i = 0; i < _sourceRange.Columns; i++)
973+
var diff = dimFromCol - _sourceRange._fromCol;
974+
for (int i = 0; i < (dimToCol - dimFromCol+1); i++)
967975
{
968-
var sourceCol = _sourceRange.Worksheet.Column(sourceColOrig + i);
969-
var destCol = _destinationRange.Worksheet.Column(destColOrig + i);
976+
var sourceCol = _sourceRange.Worksheet.Column(sourceColOrig + i + diff);
977+
var destCol = _destinationRange.Worksheet.Column(destColOrig + i + diff);
970978

971979
destCol.Width = sourceCol.Width;
972980
}

src/EPPlus/Drawing/ExcelDrawing.cs

Lines changed: 104 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@ Date Author Change
1616
using OfficeOpenXml.Drawing.OleObject;
1717
using OfficeOpenXml.Drawing.Slicer;
1818
using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions;
19+
using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup;
1920
using OfficeOpenXml.Packaging;
21+
using OfficeOpenXml.Utils;
22+
using OfficeOpenXml.Utils.Drawings;
2023
using OfficeOpenXml.Utils.EnumUtils;
2124
using OfficeOpenXml.Utils.FileUtils;
2225
using OfficeOpenXml.Utils.XML;
2326
using System;
2427
using System.Globalization;
2528
using System.IO;
2629
using System.Linq;
30+
using System.Security.Cryptography;
2731
using System.Text;
2832
using System.Xml;
2933

@@ -741,8 +745,8 @@ internal void GetFromBounds(out int fromRow, out int fromRowOff, out int fromCol
741745
{
742746
if (CellAnchor == eEditAs.Absolute)
743747
{
744-
GetToRowFromPixels(Position.Y, out fromRow, out fromRowOff);
745-
GetToColumnFromPixels(Position.X, out fromCol, out fromColOff);
748+
GetToRowFromPixels(Position.Y / (double)EMU_PER_PIXEL, out fromRow, out fromRowOff);
749+
GetToColumnFromPixels(Position.X / (double)EMU_PER_PIXEL, out fromCol, out fromColOff);
746750
}
747751
else
748752
{
@@ -757,7 +761,7 @@ internal void GetToBounds(out int toRow, out int toRowOff, out int toCol, out in
757761
if (CellAnchor == eEditAs.Absolute)
758762
{
759763
GetToRowFromPixels((Position.Y + Size.Height) / EMU_PER_PIXEL, out toRow, out toRowOff);
760-
GetToColumnFromPixels(Position.X + Size.Width / EMU_PER_PIXEL, out toCol, out toColOff);
764+
GetToColumnFromPixels((Position.X + Size.Width) / EMU_PER_PIXEL, out toCol, out toColOff);
761765
}
762766
else
763767
{
@@ -865,15 +869,14 @@ internal double GetPixelWidth()
865869
if (CellAnchor == eEditAs.TwoCell)
866870
{
867871
ExcelWorksheet ws = _drawings.Worksheet;
868-
double mdw = ws.Workbook.MaxFontWidth;
869872

870873
pix = -From.ColumnOff / (double)EMU_PER_PIXEL;
871874
for (int col = From.Column + 1; col <= To.Column; col++)
872875
{
873-
pix += MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(col) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw);
876+
pix += PixelHelper.GetColumnWidth(ws, col);
874877
}
875878

876-
var w = MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(To.Column + 1) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw);
879+
var w = PixelHelper.GetColumnWidth(ws, To.Column + 1);
877880
pix += Math.Min(w, Convert.ToDouble(To.ColumnOff) / EMU_PER_PIXEL);
878881
}
879882
else
@@ -908,9 +911,9 @@ internal double GetPixelHeight()
908911
pix = -(From.RowOff / (double)EMU_PER_PIXEL);
909912
for (int row = From.Row + 1; row <= To.Row; row++)
910913
{
911-
pix += ws.GetRowHeight(row) / 0.75;
914+
pix += PixelHelper.GetRowHeight(ws, row);
912915
}
913-
var h = ws.GetRowHeight(To.Row + 1) / 0.75;
916+
var h = PixelHelper.GetRowHeight(ws, To.Row + 1);
914917
pix += Math.Min(h, Convert.ToDouble(To.RowOff) / EMU_PER_PIXEL);
915918
}
916919
else
@@ -949,12 +952,12 @@ internal void CalcRowFromPixelTop(double pixels, out int row, out int rowOff)
949952
ExcelWorksheet ws = _drawings.Worksheet;
950953
double mdw = ws.Workbook.MaxFontWidth;
951954
double prevPix = 0;
952-
double pix = ws.GetRowHeight(1) / 0.75;
955+
double pix = PixelHelper.GetRowHeight(ws, 1);
953956
int r = 2;
954-
while (pix < pixels)
957+
while (pix < pixels && r <= ExcelPackage.MaxRows)
955958
{
956959
prevPix = pix;
957-
pix += (int)(ws.GetRowHeight(r++) / 0.75);
960+
pix += (int)PixelHelper.GetRowHeight(ws, r++);
958961
}
959962

960963
if (pix == pixels)
@@ -997,15 +1000,14 @@ internal void CalcColFromPixelLeft(double pixels, out int column, out int column
9971000
{
9981001

9991002
ExcelWorksheet ws = _drawings.Worksheet;
1000-
double mdw = ws.Workbook.MaxFontWidth;
10011003
double prevPix = 0;
1002-
double pix = (int)MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(1) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw);
1004+
double pix = (int)PixelHelper.GetColumnWidth(ws, 1);
10031005
int col = 2;
10041006

1005-
while (pix < pixels)
1007+
while (pix < pixels && col <= ExcelPackage.MaxColumns)
10061008
{
10071009
prevPix = pix;
1008-
pix += (int)MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(col++) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw);
1010+
pix += (int)PixelHelper.GetColumnWidth(ws, col++);
10091011
}
10101012
if (pix == pixels)
10111013
{
@@ -1043,20 +1045,40 @@ internal void SetPixelHeight(double pixels)
10431045

10441046
internal void GetToRowFromPixels(double pixels, out int toRow, out int rowOff, int fromRow = -1, int fromRowOff = -1)
10451047
{
1048+
if (From == null && this is not ExcelControl)
1049+
{
1050+
// Absolute anchor path
1051+
double remaining = pixels;
1052+
int currentRow = 1;
1053+
1054+
while (true && currentRow <= ExcelPackage.MaxRows)
1055+
{
1056+
double rowPix = PixelHelper.GetRowHeight(_drawings.Worksheet, currentRow);
1057+
if (remaining < rowPix)
1058+
break;
1059+
1060+
remaining -= rowPix;
1061+
currentRow++;
1062+
}
1063+
1064+
toRow = currentRow - 1;
1065+
rowOff = (int)(remaining);
1066+
return;
1067+
}
10461068
if (fromRow < 0)
10471069
{
10481070
fromRow = From.Row;
10491071
fromRowOff = From.RowOff;
10501072
}
10511073
ExcelWorksheet ws = _drawings.Worksheet;
1052-
var pixOff = pixels - ((ws.GetRowHeight(fromRow + 1) / 0.75) - (fromRowOff / (double)EMU_PER_PIXEL));
1074+
var pixOff = pixels - (PixelHelper.GetRowHeight(ws, fromRow + 1) - (fromRowOff / (double)EMU_PER_PIXEL));
10531075
double prevPixOff = pixels;
10541076
int row = fromRow + 1;
10551077

1056-
while (pixOff >= 0)
1078+
while (pixOff >= 0 && row < ExcelPackage.MaxRows)
10571079
{
10581080
prevPixOff = pixOff;
1059-
pixOff -= (ws.GetRowHeight(++row) / 0.75);
1081+
pixOff -= PixelHelper.GetRowHeight(ws, ++row);
10601082
}
10611083
toRow = row - 1;
10621084
if (fromRow == toRow)
@@ -1096,19 +1118,35 @@ internal void SetPixelWidth(double pixels)
10961118
internal void GetToColumnFromPixels(double pixels, out int col, out int colOff, int fromColumn = -1, int fromColumnOff = -1)
10971119
{
10981120
ExcelWorksheet ws = _drawings.Worksheet;
1099-
double mdw = ws.Workbook.MaxFontWidth;
1100-
if (fromColumn < 0)
1121+
if (From == null && this is not ExcelControl)
1122+
{
1123+
// Absolute anchor path
1124+
double remaining = pixels;
1125+
int currentCol = 1;
1126+
double colPix = PixelHelper.GetColumnWidth(ws, currentCol);
1127+
while (remaining >= colPix && currentCol < ExcelPackage.MaxColumns)
1128+
{
1129+
remaining -= colPix;
1130+
currentCol++;
1131+
colPix = PixelHelper.GetColumnWidth(ws, currentCol);
1132+
}
1133+
1134+
col = currentCol-1;
1135+
colOff = (int)(remaining);
1136+
return;
1137+
}
1138+
if (From != null && fromColumn < 0)
11011139
{
11021140
fromColumn = From.Column;
11031141
fromColumnOff = From.ColumnOff;
11041142
}
1105-
double pixOff = pixels - (MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(fromColumn + 1) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw) - fromColumnOff / EMU_PER_PIXEL);
1143+
double pixOff = pixels - (PixelHelper.GetColumnWidth(ws, fromColumn + 1) - fromColumnOff / EMU_PER_PIXEL);
11061144
double offset = (double)fromColumnOff / EMU_PER_PIXEL + pixels;
11071145
col = fromColumn + 2;
11081146
while (pixOff >= 0)
11091147
{
11101148
offset = pixOff;
1111-
pixOff -= MathHelper.TruncateDouble(((256 * ws.GetColumnWidth(col++) + MathHelper.TruncateDouble(128 / mdw)) / 256) * mdw);
1149+
pixOff -= PixelHelper.GetColumnWidth(ws, col++);
11121150
}
11131151
colOff = (int)offset;
11141152
}
@@ -1394,10 +1432,20 @@ public void SetPosition(int Row, int RowOffsetPixels, int Column, int ColumnOffs
13941432
_height = GetPixelHeight();
13951433
}
13961434

1397-
From.Row = Row;
1398-
From.RowOff = RowOffsetPixels * EMU_PER_PIXEL;
1399-
From.Column = Column;
1400-
From.ColumnOff = ColumnOffsetPixels * EMU_PER_PIXEL;
1435+
if (CellAnchor == eEditAs.Absolute)
1436+
{
1437+
GetPixelHeightFromRow(Row, RowOffsetPixels, out int pixelHeight);
1438+
1439+
Position.Y = (int)(pixelHeight * EMU_PER_PIXEL);
1440+
Position.X = (int)(ColumnOffsetPixels * EMU_PER_PIXEL);
1441+
}
1442+
else
1443+
{
1444+
From.Row = Row;
1445+
From.RowOff = RowOffsetPixels * EMU_PER_PIXEL;
1446+
From.Column = Column;
1447+
From.ColumnOff = ColumnOffsetPixels * EMU_PER_PIXEL;
1448+
}
14011449
if (CellAnchor == eEditAs.TwoCell)
14021450
{
14031451
_left = GetPixelLeft();
@@ -1409,6 +1457,36 @@ public void SetPosition(int Row, int RowOffsetPixels, int Column, int ColumnOffs
14091457
_doNotAdjust = false;
14101458
UpdatePositionAndSizeXml();
14111459
}
1460+
private void GetPixelWidthFromRow(int toCol, int colOffsetPixels, out int pixelWidth)
1461+
{
1462+
ExcelWorksheet ws = _drawings.Worksheet;
1463+
double mdw = ws.Workbook.MaxFontWidth;
1464+
1465+
pixelWidth = 0;
1466+
for (int col = 0; col < toCol; col++)
1467+
{
1468+
pixelWidth += ws.GetColumnWidthPixels(col, mdw);
1469+
}
1470+
pixelWidth += colOffsetPixels;
1471+
}
1472+
private void GetPixelHeightFromRow(int toRow, int rowOffsetPixels, out int pixelHeight)
1473+
{
1474+
pixelHeight = 0;
1475+
var cache = _drawings.Worksheet.RowHeightCache;
1476+
for (int row = 0; row < toRow; row++)
1477+
{
1478+
lock (cache)
1479+
{
1480+
if (!cache.ContainsKey(row))
1481+
{
1482+
cache.Add(row, _drawings.Worksheet.GetRowHeight(row + 1));
1483+
}
1484+
}
1485+
pixelHeight += (int)(cache[row] / 0.75);
1486+
}
1487+
pixelHeight += rowOffsetPixels;
1488+
}
1489+
14121490
/// <summary>
14131491
/// Set size in Percent.
14141492
/// Note that resizing columns / rows after using this function will effect the size of the drawing

src/EPPlus/ExcelPackage.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ private XmlNamespaceManager CreateDefaultNSM()
864864
ns.AddNamespace("xda", schemaDynamicArrays);
865865
ns.AddNamespace("clbl", schemaMipLabelMetadata);
866866
ns.AddNamespace("xfpb", Schemas.schemaFeaturePropertyBag);
867+
ns.AddNamespace("xcalcf", Schemas.schemaCalcFeature);
867868
return ns;
868869
}
869870
#region SavePart

0 commit comments

Comments
 (0)