XSSFWorkbook make part of cell content to bold using apache poi - apache-poi

My project need is to make part of a string bold leaving any OR and AND like the below example.
TOWING PACK 11 OR TOWING PACK 13 AND TOWING PACK 14 OR TOWING PACK 15
I tried to follow the reverse approach.
I tried to make the entire cell BOLD // This works
Then using RichTextString make "OR" and "AND" to normal Italics. //The issue - After the first "OR" all the rest of the string is formatted to normal format.
Output I am getting:
TOWING PACK 11 OR TOWING PACK 13 AND TOWING PACK 14 OR TOWING PACK 15
I am using poi 5.2.3 and below is the code sample. Can anyone point out what is wrong here.
CreationHelper creationHelper = workbook.getCreationHelper();
XSSFFont fontBold = workbook.createFont();
fontBold.setBold(true);
XSSFFont fontItalic = workbook.createFont();
fontItalic.setItalic(true);
fontItalic.setBold(false);
XSSFCellStyle boldstyle = workbook.createCellStyle();
boldstyle.setFont(fontBold);
int startrow = 2;
Iterator<Row> boldrowIterator = spreadsheet.iterator();
while (boldrowIterator.hasNext()) {
Row boldrow = boldrowIterator.next();
if (boldrow.getRowNum()==startrow) {
out.println(boldrow.getCell(9));
Cell boldcell = boldrow.getCell(9);
boldcell.setCellStyle(boldstyle);
startrow = startrow+1;
String Featuredescription = boldrow.getCell(9).getStringCellValue();
if (Featuredescription.contains("OR")) {
RichTextString richTextString = creationHelper.createRichTextString(Featuredescription);
String word = " OR ";
int startIndex = Featuredescription.indexOf(word);
int endIndex = startIndex + word.length();
out.println("Featuredescription: " + Featuredescription + startIndex + endIndex);
richTextString.applyFont(startIndex, endIndex, fontItalic);
boldcell.setCellValue(richTextString);
}
} }
EDIT
XSSFCellStyle linstyle = workbook.createCellStyle();
Font linfont = workbook.createFont();
linfont.setColor(IndexedColors.ORANGE.getIndex());
linstyle.setFont(linfont);
Iterator<Row> linrowIterator = spreadsheet.iterator();
while (linrowIterator.hasNext())
{
Row linrow = linrowIterator.next();
Iterator <Cell> lincellIterator = linrow.cellIterator();
if (linrow.getRowNum()==linrowcount) {
if (linrow.getCell(13).getStringCellValue().contains("LIN")) {
while (lincellIterator.hasNext())
{
Cell lincell = lincellIterator.next();
lincell.setCellStyle(linstyle);
} } linrowcount = linrowcount+1; }
}

I would recommend using a simple regular expression to find all the occurrences of AND and OR (note the spaces included in these strings). Doing this lets you easily determine the location of each occurrence within the overall string (the indexes of where each word starts and ends). You can use this to set everything to bold (like you are already doing) and then set each OR and AND to normal.
My code assumes your test text is in cell A1 - and that is the only cell I test. You can add back your looping logic to handle more cells.
You will also need:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
I have added comments to the code to explain specific lines:
FileInputStream file = new FileInputStream(new File("C:/temp/poi/rich_formatting_in.xlsx"));
Workbook wb = new XSSFWorkbook(file);
Sheet sheet = wb.getSheet("Sheet1");
CreationHelper creationHelper = wb.getCreationHelper();
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);
String content = cell.getStringCellValue();
Font bold = wb.createFont();
bold.setBold(true);
Font normal = wb.createFont();
normal.setBold(false);
//normal.setItalic(true); // uncomment, if you need italics, as well.
RichTextString richStr = creationHelper.createRichTextString(content);
richStr.applyFont(bold); // set everything to bold
String regex = "( AND | OR )"; // note the spaces in the strings
Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
// process each found group (one group for each AND and OR):
for (int i = 1; i <= matcher.groupCount(); i++) {
// matcher.start(i) finds where the start of the match is
// matcher.end(i) finds the position of the end of the match
// we can use these start and end positions to set that text to normal:
richStr.applyFont(matcher.start(i), matcher.end(i), normal);
}
}
// write the final string to the spreadsheet:
cell.setCellValue(richStr);
// write the spreadsheet to a file so we can see the results:
try (FileOutputStream out = new FileOutputStream(new File("C:/temp/poi/rich_formatting_out.xlsx"))) {
wb.write(out);
}
The results are:
The regex ( AND | OR ) is very basic - it assumes every occurrence of the words AND and OR surrounded by spaces are what need to be adjusted.

Related

C# NPOI set cell style to Text / string 1-19 is formatted as a date / disable any formating

I am creating an excel, when i write some values example 1-19, when I open the excel doc, i see 1-19, but if i click on it then excel tries to format it as a date
IS THERE A WAY to force the sheet to not use any formulas or formatting?
I have checked and the dataformat is string.
private void Test1(ref ISheet worksheet, string[] array, IWorkbook workbook, int iRow, XSSFFont font2)
{
var format = HSSFDataFormat.GetBuiltinFormats();
ICellStyle _TextCellStyle = workbook.CreateCellStyle();
_TextCellStyle.DataFormat = workbook.CreateDataFormat().GetFormat("#");
IRow file = worksheet.CreateRow(iRow);
int iCol = 0;
for (int y = 0; y < array.Length; y++)
{
ICellStyle style = workbook.CreateCellStyle();
style.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Grey25Percent.Index;
//style.DataFormat = HSSFDataFormat.
ICell cell = file.CreateCell(iCol, CellType.String);
cell.SetCellValue(array[y]);
style.SetFont(font2);
// cell.CellStyle = style;
var getst = cell.CellType;
cell.CellStyle = _TextCellStyle;
iCol++;
getst = cell.CellType;
}
}
Your data remains in "General" format even after you are using correct format string "#" as per documentation. Some time library methods don't work in NPOI so you'll have to try different approach.
You can try one of these
_TextCellStyle.DataFormat = workbook.CreateDataFormat().GetFormat("text"); //Instead of "#"
Or prefix single quote for data when writing it for excel file like '1-19

How do I get character count from cell

Goal: I'm trying to add a feature to my Excel 2016 VSTO plugin. The feature will get 1 column from the active sheet, and iterate over it changing the background color based on string length.
Problem: I'm having trouble getting string length from the cells. I can not figure out the proper syntax I currently have var count = row.Item[1].Value2.Text.Length;
Code: Here is what I have
public void CharacterLengthCheck(int length = 24, int Column = 3)
{
Worksheet sheet = Globals.ThisAddIn.Application.ActiveSheet;
var RowCount = sheet.UsedRange.Rows.Count;
Range column = sheet.Range[sheet.Cells[1, Column], sheet.Cells[RowCount, Column]];
foreach (Range row in column)
{
var count = row.Item[1].Value2.Text.Length;
if (count > length)
{
row.Item[1].Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red);
}
else
{
row.Item[1].Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Green);
}
}
}
I think your problem is here:
row.Item[1].Value2.Text.Length
The length function is Len(x), so try Len(row.Item[1].Value2.Text)
Thank you to #Jeeped for their advice on using conditional formatting. However the answer to my original question is.
Change
var count = row.Item[1].Value2.Text.Length;
to
string text = (row.Item[1].Value).ToString();
var count = text.length;
I believe this is because value is dynamic and needs to be explicitly cast to string

Replacing unknown substring?

public static String updatedStr(){
String [] ar= {"green","red","purple","black"};
String str="The colors are (blue), (blue), and (yellow). I prefer (orange)";
I would like a final output string of "The colors are green, red, and purple. I prefer black."
You can do it without using replace. Just iterate over the input String and add to a StringBuilder parts of the original String (that are not contained in parentheses) and the replacement words instead of the parts contained in parentheses.
public static String updatedStr()
{
String [] ar= {"green","red","purple","black"};
String str="The colors are (blue), (blue), and (yellow). I prefer (orange)";
StringBuilder out = new StringBuilder ();
int x = 0;
int pos = 0;
for(int i = str.indexOf('(', 0); i != -1; i = str.indexOf('(', i + 1)) {
out.append (str.substring(pos,i)); // add the part between the last ) and the next (
out.append (ar[x++]); // add replacement word
pos = str.indexOf(')', i) + 1;
}
out.append (str.substring(pos)); // add the part after the final )
return out.toString ();
}
This method returns :
The colors are green, red, and purple. I prefer black
This code makes some simplifying assumptions. For example, the number of elements in the replacements array should be at least as high as the number of words to be replaced. A more complete implementation should contain additional checks.
You can do this by calculating the position of where you are replacing and saving them in an array as follows:
public static String updatedStr(){
String [] ar= {"green","red","purple","black"};
String str="The colors are (blue), (blue), and (yellow). I prefer (orange)";
ArrayList<String> arr = new ArrayList<String>();
int pos [] = new int[ar.length]; // save locations here
for(int i = str.indexOf('(', 0); i != -1; i = str.indexOf('(', i + 1)) {
arr.add(str.substring(i + 1, str.indexOf(')', i)));
pos[arr.size()-1] = i; // save it!
}
// replace from right to left
for (int j=pos.length-1;j>=0;j--){
String newStr = str.substring(0, pos[j]+1) + ar[j] + str.substring(str.indexOf(')',pos[j]+1), str.length());
str = newStr;
}
return str;
}
The trick here is that I'm replacing from right to left so that the positions of where I need to replace do not move when I am replacing them.

How to override PrimeFaces p:dataExporter wrongly exporting numbers as text in Excel?

The PrimeFace p:dataExporter tag exports numeric data as text by default, which results in a cell with a green triangle in the upper left corner. This can be seen in the PrimeFaces showcase example as well, if you click the Excel export under the cars table.
How can I override this default to make sure my numeric columns are not exported as text? I tried using the postProcessor attribute pointing to my method that sets the Excel format for all the data cells using POI API but that did not take effect (did not change anything):
public void formatExcel(Object doc) {
HSSFWorkbook book = (HSSFWorkbook)doc;
HSSFSheet sheet = book.getSheetAt(0);
HSSFRow header = sheet.getRow(0);
int colCount = header.getPhysicalNumberOfCells();
int rowCount = sheet.getPhysicalNumberOfRows();
HSSFCellStyle numStyle = book.createCellStyle();
numStyle.setDataFormat((short)1);
for(int rowInd = 1; rowInd < rowCount; rowInd++) {
HSSFRow row = sheet.getRow(rowInd);
for(int cellInd = 1; cellInd < colCount; cellInd++) {
HSSFCell cell = row.getCell(cellInd);
String val = cell.getStringCellValue();
cell.setCellStyle(numStyle);
}
}
}
I also tried
cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
but that gives me
java.lang.IllegalStateException: Cannot get a numeric value from a text cell
So that means that all data is indiscriminately exported as text and then you can't even change it afterwards.
This is what ended up working for me. It is far from elegant but it works:
HSSFCellStyle intStyle = book.createCellStyle();
intStyle.setDataFormat((short)1);
HSSFCellStyle decStyle = book.createCellStyle();
decStyle.setDataFormat((short)2);
HSSFCellStyle dollarStyle = book.createCellStyle();
dollarStyle.setDataFormat((short)5);
for(int rowInd = 1; rowInd < rowCount; rowInd++) {
HSSFRow row = sheet.getRow(rowInd);
for(int cellInd = 1; cellInd < colCount; cellInd++) {
HSSFCell cell = row.getCell(cellInd);
//This is sortof a hack to counter PF exporting all data as text
//We capture the existing value as string, convert to int,
//then format the cell to be numeric and reset the value to be int
String strVal = cell.getStringCellValue();
//this has to be done to temporarily blank out the cell value
//because setting the type to numeric directly will cause
//an IllegalStateException because POI stupidly thinks
//the cell is text because it was exported as such by PF...
cell.setCellType(HSSFCell.CELL_TYPE_BLANK);
cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
strVal = strVal.replace(",", StringUtils.EMPTY);
if(strVal.indexOf('.') == -1) {
//integer
//numStyle.setDataFormat((short)1);
int intVal = Integer.valueOf(strVal);
cell.setCellStyle(intStyle);
cell.setCellValue(intVal);
} else {
//double
if(strVal.startsWith("$")) {
strVal = strVal.replace("$", StringUtils.EMPTY);
//numStyle.setDataFormat((short)5);
cell.setCellStyle(dollarStyle);
} else {
//numStyle.setDataFormat((short)2);
cell.setCellStyle(decStyle);
}
double dblVal = Double.valueOf(strVal);
cell.setCellValue(dblVal);
}
}
}
In your postProcessor, you nowhere set the value of the cell to an integer. You set the type, but not the value. Setting the type is not enough. You have to convert value to a number and set it again

Epplus SetPosition picture issue

I am using Epplus library to generate Excel 2010 and up compatible files in Asp.Net C#.
I am using version 3.1.2 which is the latest at this moment.
I am setting the row height first, before adding any pictures like this:
ExcelPackage pck = new ExcelPackage();
var ws = pck.Workbook.Worksheets.Add("sheet 1");
while (i < dt.Rows.Count + offset)
{
ws.Row(i).Height = 84;
i++;
}
dt is my DataTable with DataRows.
After setting the height, I am looping again through the rows to add the pictures
while (i < dt.Rows.Count + offset)
{
var prodImg = ws.Drawings.AddPicture(dr["code"].ToString(), new FileInfo(path));
prodImg.SetPosition(i - 1, 0, 14, 0);
prodImg.SetSize(75);
}
This works, but this does not:
var prodImg = ws.Drawings.AddPicture(dr["code"].ToString(), new FileInfo(path));
int w = prodImg.Image.Width;
int h = prodImg.Image.Height;
if (h > 140) // because height of 84 is 140 pixels in excel
{
double scale = h / 140.0;
w = (int)Math.Floor(w / scale);
h = 140;
}
int xOff = (150 - w) / 2;
int yOff = (140 - h) / 2;
prodImg.SetPosition(i - 1, xOff, 11, yOff);
prodImg.SetSize(w, h);
This results in off center pictures and unresized images. And this code then which is in the same loop:
var prodImgDm = ws.Drawings.AddPicture("bcdm" + dr["code"].ToString(), new FileInfo(pathDm));
prodImgDm.SetPosition(i - 1, 25, 15, 40);
prodImgDm.SetSize(100);
This does work sometimes. the pictures prodImgDm are datamatrix images with a static width and height and do not need to be resized because they are always small/tiny. So also without the SetSize in some rows, it works and in some other rows, it does not work. Really strange because the code is the same. It might be something in the library and/or Excel. Perhaps I am using it wrong? Any epplus picture expert?
Thanks in advance!!
edit sometimes a picture is worth a thousand words, so here is the screenshot. As you can see the product images are not horizontal and vertical aligned in the cell. And the datamatrix on the far right is sometimes scaled about 120% even when I set SetSize(100) so it is really strange to me. So the last datamatrix has the correct size... I already found this SO thread but that does not help me out, I think.
edit 2013/04/09 Essenpillai gave me a hint to set
pck.DoAdjustDrawings = false;
but that gave me even stranger images:
the datamatrix is still changing on row basis. on row is ok, the other is not. and the ean13 code is too wide.
public static void CreatePicture(ExcelWorksheet worksheet, string name, Image image, int firstColumn, int lastColumn, int firstRow, int lastRow, int defaultOffsetPixels)
{
int columnWidth = GetWidthInPixels(worksheet.Cells[firstRow, firstColumn]);
int rowHeight = GetHeightInPixels(worksheet.Cells[firstRow, firstColumn]);
int totalColumnWidth = columnWidth * (lastColumn - firstColumn + 1);
int totalRowHeight = rowHeight * (lastRow - firstRow + 1);
double cellAspectRatio = Convert.ToDouble(totalColumnWidth) / Convert.ToDouble(totalRowHeight);
int imageWidth = image.Width;
int imageHeight = image.Height;
double imageAspectRatio = Convert.ToDouble(imageWidth) / Convert.ToDouble(imageHeight);
int pixelWidth;
int pixelHeight;
if (imageAspectRatio > cellAspectRatio)
{
pixelWidth = totalColumnWidth - defaultOffsetPixels * 2;
pixelHeight = pixelWidth * imageHeight / imageWidth;
}
else
{
pixelHeight = totalRowHeight - defaultOffsetPixels * 2;
pixelWidth = pixelHeight * imageWidth / imageHeight;
}
int rowOffsetPixels = (totalRowHeight - pixelHeight) / 2;
int columnOffsetPixels = (totalColumnWidth - pixelWidth) / 2;
int rowOffsetCount = 0;
int columnOffsetCount = 0;
if (rowOffsetPixels > rowHeight)
{
rowOffsetCount = (int)Math.Floor(Convert.ToDouble(rowOffsetPixels) / Convert.ToDouble(rowHeight));
rowOffsetPixels -= rowHeight * rowOffsetCount;
}
if (columnOffsetPixels > columnWidth)
{
columnOffsetCount = (int)Math.Floor(Convert.ToDouble(columnOffsetPixels) / Convert.ToDouble(columnWidth));
columnOffsetPixels -= columnWidth * columnOffsetCount;
}
int row = firstRow + rowOffsetCount - 1;
int column = firstColumn + columnOffsetCount - 1;
ExcelPicture pic = worksheet.Drawings.AddPicture(name, image);
pic.SetPosition(row, rowOffsetPixels, column, columnOffsetPixels);
pic.SetSize(pixelWidth, pixelHeight);
}
public static int GetHeightInPixels(ExcelRange cell)
{
using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero))
{
float dpiY = graphics.DpiY;
return (int)(cell.Worksheet.Row(cell.Start.Row).Height * (1 / 72.0) * dpiY);
}
}
public static float MeasureString(string s, Font font)
{
using (var g = Graphics.FromHwnd(IntPtr.Zero))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
return g.MeasureString(s, font, int.MaxValue, StringFormat.GenericTypographic).Width;
}
}
public static int GetWidthInPixels(ExcelRange cell)
{
double columnWidth = cell.Worksheet.Column(cell.Start.Column).Width;
Font font = new Font(cell.Style.Font.Name, cell.Style.Font.Size, FontStyle.Regular);
double pxBaseline = Math.Round(MeasureString("1234567890", font) / 10);
return (int)(columnWidth * pxBaseline);
}
enter image description here
I have the same problem with Epplus library.
After I find no solution how to solve this in my code, I checked source code of this library.
Epplus create excel picture always as twoCellAnchor drawing. In xlsx files you can find drawingXYZ.xml with this code:
<xdr:twoCellAnchor editAs="oneCell">
<xdr:from> ... </xdr:from>
<xdr:to> ... </xdr:to>
<xdr:pic>
...
</xdr:twoCellAnchor>
So, picture is always connected to two cells, and this is not variable part of Epplus library. You can find this part of code in ExcelDrawing.cs file.
XmlElement drawNode = _drawingsXml.CreateElement(
"xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
colNode.AppendChild(drawNode);
You can easy create your own copy of this dll. The good news is that you need to modify only two files to fix this problem. So..
Download your copy of source codes for Epplus library from this site and open in Visual Studio.
We need to generate code of drawing as oneCellAnchor, so we must remove <xdr:to> element for pictures and create element <xdr:ext /> with picture dimensions as parameters.
New xml structure will looks like:
<xdr:oneCellAnchor editAs="oneCell">
<xdr:from> ... </xdr:from>
<xdr:ext cx="1234567" cy="7654321" />
<xdr:pic>
...
</xdr:oneCellAnchor>
Ok, so, how to do this?
Changes in Epplus code
ExcelDrawings.cs (link to file here)
At first we modify method CreateDrawingXml() inside ExcelDrawings.cs. Order to preserve the original functionality we add an optional parameter (if create oneCellAnchor) with default value. And in method, based this parameter, we create one or tow cell anchor and create or not <xdr:to> element.
Important part of this method code:
private XmlElement CreateDrawingXml(bool twoCell = true) {
if (DrawingXml.OuterXml == "")
{ ... } // not changed
XmlNode colNode= _drawingsXml.SelectSingleNode("//xdr:wsDr", NameSpaceManager);
//First change in method code
XmlElement drawNode;
if (twoCell)
drawNode = _drawingsXml.CreateElement(
"xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
else
drawNode = _drawingsXml.CreateElement(
"xdr", "oneCellAnchor", ExcelPackage.schemaSheetDrawings);
colNode.AppendChild(drawNode);
//Add from position Element; // Not changed
XmlElement fromNode = _drawingsXml.CreateElement(
"xdr", "from", ExcelPackage.schemaSheetDrawings);
drawNode.AppendChild(fromNode);
fromNode.InnerXml = "<xdr:col>0</xdr:col><xdr:colOff>0</xdr:colOff>"
+ "<xdr:row>0</xdr:row><xdr:rowOff>0</xdr:rowOff>";
//Add to position Element;
//Second change in method
if (twoCell)
{
XmlElement toNode = _drawingsXml.CreateElement(
"xdr", "to", ExcelPackage.schemaSheetDrawings);
drawNode.AppendChild(toNode);
toNode.InnerXml = "<xdr:col>10</xdr:col><xdr:colOff>0</xdr:colOff>"
+ "<xdr:row>10</xdr:row><xdr:rowOff>0</xdr:rowOff>";
}
return drawNode;
}
Then we modify two methods for AddPicture inside the same file:
public ExcelPicture AddPicture(string Name, Image image, Uri Hyperlink)
{
if (image != null) {
if (_drawingNames.ContainsKey(Name.ToLower())) {
throw new Exception("Name already exists in the drawings collection");
}
XmlElement drawNode = CreateDrawingXml(false);
// Change: we need create element with dimensions
// like: <xdr:ext cx="3857625" cy="1047750" />
XmlElement xdrext = _drawingsXml.CreateElement(
"xdr", "ext", ExcelPackage.schemaSheetDrawings);
xdrext.SetAttribute("cx",
(image.Width * ExcelDrawing.EMU_PER_PIXEL).ToString());
xdrext.SetAttribute("cy",
(image.Height * ExcelDrawing.EMU_PER_PIXEL).ToString());
drawNode.AppendChild(xdrext);
// End of change, next part of method is the same:
drawNode.SetAttribute("editAs", "oneCell");
...
}
}
And this method with FileInfo as input parameter:
public ExcelPicture AddPicture(string Name, FileInfo ImageFile, Uri Hyperlink)
{
if (ImageFile != null) {
if (_drawingNames.ContainsKey(Name.ToLower())) {
throw new Exception("Name already exists in the drawings collection");
}
XmlElement drawNode = CreateDrawingXml(false);
// Change: First create ExcelPicture object and calculate EMU dimensions
ExcelPicture pic = new ExcelPicture(this, drawNode, ImageFile, Hyperlink);
XmlElement xdrext = _drawingsXml.CreateElement(
"xdr", "ext", ExcelPackage.schemaSheetDrawings);
xdrext.SetAttribute("cx",
(pic.Image.Width * ExcelDrawing.EMU_PER_PIXEL).ToString());
xdrext.SetAttribute("cy",
(pic.Image.Height * ExcelDrawing.EMU_PER_PIXEL).ToString());
drawNode.AppendChild(xdrext);
// End of change, next part of method is the same (without create pic object)
drawNode.SetAttribute("editAs", "oneCell");
...
}
}
So, this are all important code. Now we must change code for searching nodes and preserve order in elements.
In private void AddDrawings() we change xpath from:
XmlNodeList list = _drawingsXml.SelectNodes(
"//xdr:twoCellAnchor", NameSpaceManager);
To this:
XmlNodeList list = _drawingsXml.SelectNodes(
"//(xdr:twoCellAnchor or xdr:oneCellAnchor)", NameSpaceManager);
It is all in this file, now we change
ExcelPicture.cs (link to file here)
Original code find node for append next code in constructor like this:
node.SelectSingleNode("xdr:to",NameSpaceManager);
Because we do not create <xdr:to> element always, we change this code:
internal ExcelPicture(ExcelDrawings drawings, XmlNode node
, Image image, Uri hyperlink)
: base(drawings, node, "xdr:pic/xdr:nvPicPr/xdr:cNvPr/#name")
{
XmlElement picNode = node.OwnerDocument.CreateElement(
"xdr", "pic", ExcelPackage.schemaSheetDrawings);
// Edited: find xdr:to, or xdr:ext if xdr:to not exists
XmlNode befor = node.SelectSingleNode("xdr:to",NameSpaceManager);
if (befor != null && befor.Name == "xdr:to")
node.InsertAfter(picNode, befor);
else {
befor = node.SelectSingleNode("xdr:ext", NameSpaceManager);
node.InsertAfter(picNode, befor);
}
// End of change, next part of constructor is unchanged
_hyperlink = hyperlink;
...
}
And the same for second constructor with FileInfo as input parameter:
internal ExcelPicture(ExcelDrawings drawings, XmlNode node
, FileInfo imageFile, Uri hyperlink)
: base(drawings, node, "xdr:pic/xdr:nvPicPr/xdr:cNvPr/#name")
{
XmlElement picNode = node.OwnerDocument.CreateElement(
"xdr", "pic", ExcelPackage.schemaSheetDrawings);
// Edited: find xdr:to, or xdr:ext if xdr:to not exists
XmlNode befor = node.SelectSingleNode("xdr:to", NameSpaceManager);
if (befor != null && befor.Name == "xdr:to")
node.InsertAfter(picNode, befor);
else {
befor = node.SelectSingleNode("xdr:ext", NameSpaceManager);
node.InsertAfter(picNode, befor);
}
// End of change, next part of constructor is unchanged
_hyperlink = hyperlink;
...
Now, pictures are created as oneCellAnchor. If you want, you can create multiple AddPicture methods for booth variants. Last step is build this project and create your own custom EPPlus.dll. Then close your project which use this dll and copy new files EPPlus.dll, EPPlus.pdb, EPPlus.XML inside your project (backup and replace your original dll files) at the same place (so you don't need do any change in your project references or settings).
Then open and rebuild your project and try if this solve your problem.
Maybe I am too late, but here is mine answer..
you can read it on codeplex issue as well
(https://epplus.codeplex.com/workitem/14846)
I got this problem as well.
And after some research I figured out where the bug is.
It's in the ExcelRow class on the 149 line of code (ExcelRow.cs file).
There is a mistake, when row's Height got changed it recalcs all pictures heights but uses pictures widths inplace of heights, so it's easy to fix.
Just change the line
var pos = _worksheet.Drawings.GetDrawingWidths();
to
var pos = _worksheet.Drawings.GetDrawingHeight();
See the code changes on image
P.S. Actual for version 4.0.4

Resources