iText 7 - Image in Cell of Table fill full height - java

I add images in my cells of a table, but it doesn't fit the whole height.
The height is not fixed and can change between 2 rows.
How can my images fit the full height of my cells ?
Here is my problem :
Here is the code of creation of my table :
int nbColumns = 7 + planning.size() *4;
Table table = new Table(nbColumns);
table.setWidthPercent(100);
table.addCell(createCell(""));
table.addCell(createCell("Col1"));
table.addCell(createCell("Col2"));
DateFormat hourFormat = new SimpleDateFormat("HH", Locale.FRENCH);
for(Date hourDate : planning){
table.addCell(new Cell(1,4).setTextAlignment(TextAlignment.CENTER).add(hourFormat.format(hourDate)).setFont(regular).setFontSize(10));
}
table.addCell(createCell("Long"));
table.addCell(createCell("A"));
table.addCell(createCell("B"));
table.addCell(createCell(""));
Here is how I add my images for each cell :
String IMG = // my img path
table.addCell(createImageCell(IMG));
public static Cell createImageCell(String path) throws MalformedURLException {
Image img = new Image(ImageDataFactory.create(path));
Cell cell = new Cell().add(img.setMargins(0,0,0,0).setAutoScaleHeight(true).setAutoScale(true)).setPadding(0);
cell.setBorder(null);
return cell;
}

Posting this as an answer for the sake of visibility.
With regards to autoscaling:
setAutoScaleHeight() is currently bugged in iText7 on the develop branch (so the bug will be present in 7.0.5 and prior versions). It currently sets the AUTO_SCALE_WIDTH(probably due to a copy-paste oversight), unless the AUTO_SCALE_WIDTHproperty is already set, then it will put them both on false and set the AUTO_SCALE to true.
Fixing the typo does not result in expected behaviour, since we're now aware of the issue, a ticket for it has been added to our backlog.
Bi-directional autoscaling (via the AUTO_SCALE-property) works correctly, but will scale uniformly and thus only scale to width in these case where the cell is greater in height than width.
As for a temporary solutions or trick to bypass it, I don't have a generic one :( for now, apart from waiting for the fix.
I did a quick test with relative height declarations (which should be included in 7.0.5), but that scaled the image uniformly again. Some trial&error and Image.scaleAbsolute() can get a desired result but that's hardly automate-able. You could in theory hook into the layout-process by writing your own custom CellRenderer, extracting the height of the largest Cell in to row to use in conjuction with scaleAbsolute(), but you're kinda writing the auto-scaling logic yourself at that point.
Some remarks on the OP's posted code as well, in the interest of spreading good practice:
int nbColumns = 7 + planning.size() *4;
Table table = new Table(nbColumns);
The constructor Table(int) is deprecated (since 7.0.2) and can lead to unexpected behaviour in later versions (this was done when improving the table-layouting mechanisms). It's better to pass a UnitValue[], preferably created using UnitValue.createPercentArray(float[]) or UnitValue.createPercentArray(float[])
img.setMargins(0,0,0,0).setAutoScaleHeight(true).setAutoScale(true))
setAutoScale after setAutoScaleHeight makes the latter redundant.

Related

Apache Poi cell not returning the correct value

I have a excel file with a cell that generates the number 3.69 (based on calculations from proceeding numbers)
However when pulling that number in java using
if (brightCell.getNumericCellValue()) > 0 )
{
double brightness = brightCell.getNumericCellValue();
return brightness;
}
I've also tried:
if (Double.parseDouble(brightCell.getStringCellValue()) > 0 )
{
double brightness = Double.parseDouble(brightCell.getStringCellValue());
return brightness;
}
brightCell is instantiated with :
brightCell = spreadsheet.getRow(new CellReference(brightString).getRow()).getCell(new CellReference(brightString).getCol());
brightString is String brightString = "BV29"
But with both solutions, brightness receives the value, 3.2133....
So thanks to #Igor I managed to figure it out but it led to more issues.
So the solution was creating an evaluator
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
evaluator.setIgnoreMissingWorkbooks(true); //if you need it
when you finish setting the required cells and want to evaluate.
evaluator.EvaluateAll();
The problem for me is I'm doing this multiple times and my 1st resut is correct but upon the second iteration it becomes skewed, and more skewed.
What I'm doing is setting various cells (via java) then before I retrieve the value for a cell (that contains a formula) I run EvaluateAll. Now, I'm not sure if I should be evaluating after EVERY change or after I make all my changes to the excel sheet (via java).
I can't evaluate a specific cell at a time because there's over 38 sheets with multitudes of formulas. So EvaluateAll is the best option for me
EDIT 26/10/2018*
So the issue was not clearing the cache after making inputs. The solution was after each input as specified in the javaDoc that:
Should be called whenever there are changes to input cells in the evaluated workbook.
Failure to call this method after changing cell values will cause incorrect behaviour
of the evaluate~ methods of this class
therefore after making an input on a cell you should call evaluator.clearAllCachedResultValues();

How to keep track of inserted page numbers or get a row reference when a page number is changing in .docx file using Apache POI

I am creating a Java application in which I am interacting with document file .docx. I am using Apache POI to generate it and modify into it.
The existing file is having tables like:
Tables that are created initially.
I have to add the rows in the first table so that the output is something like: The way the 2 tables should be shown
So this is like I am allowing table 1 to occupy the space till it can go in document page, it is followed by another table. This type of table is needed because after that I am removing its inside borders and it shows something like: how it should be shown with inside borders removed.
I am adding the blank rows using this piece of code..
for (int i = 0; i < itemCount; i++) {
oldRow = table.getRow(i);
newRow = table.insertNewTableRow(i + 1);
for (int j = 0; j < oldRow.getTableCells().size(); j++) {
cell = newRow.createCell();
CTTcPr ctTcPr = cell.getCTTc().addNewTcPr();
ctTcPr.addNewTcBorders().addNewTop().setVal(STBorder.NIL);
ctTcPr.addNewTcBorders().addNewBottom().setVal(STBorder.NIL);
CTTblWidth cellWidth = ctTcPr.addNewTcW();
cellWidth.setType(oldRow.getCell(j).getCTTc().getTcPr().getTcW().getType());
// sets type of width
BigInteger width = oldRow.getCell(j).getCTTc().getTcPr().getTcW().getW();
cellWidth.setW(width); // sets width
if (oldRow.getCell(j).getCTTc().getTcPr().getGridSpan() != null) {
ctTcPr.setGridSpan(oldRow.getCell(j).
getCTTc().getTcPr().getGridSpan()); // sets grid span if any
}
}
}
In few of the lines in starting, I am adding value as something like:
paragraph = row.getCell(0).getParagraphArray(0);
if (paragraph == null) {
paragraph = row.getCell(0).addParagraph();
}
run = setRunAndParagraph(paragraph);
run.setText(itemNames[i]);
All this works nicely, this code was helped by a great coder here who helped me to solve earlier issue.
Now, when I am inserting the rows, I can manually count that for A4 size page, this many rows will be there in table 1, and can iterate likewise. But, the issue is, some item names are wider than column length and as word wrap is on, row for that particular item takes height twice as normal. So, I can get number of rows but not exactly decide how many lines occupied by the items.
The rows appended are for better look and application specific so I thought of an approach that may work:
I am adding page numbers in footer, after inserting a blank row, I am checking whether total page numbers of a document changed or not, if yes that means the code stops there. And as there's 1 excess row I am removing it.
But the issue here arise is I am not able to get page number. I tried
document.getProperties().getExtendedProperties().getUnderlyingProperties().getPages();
But that always shows 1 even if total pages becomes 2. I searched for an approach stating to check form-feed but I think I am having tables here so I don't know it will be helpful approach or not. So I went for adding page number in the page as a field but that also gave 1 every time. Then I tried to do it with footer and this is how I am writing the page number.
XWPFHeaderFooterPolicy p = document.createHeaderFooterPolicy();
XWPFFooter f = p.createFooter(XWPFHeaderFooterPolicy.DEFAULT);
paragraph = f.createParagraph();
//paragraph.createRun().setText("Page: ");
paragraph.createRun();
paragraph.getCTP().addNewFldSimple().setInstr("PAGE \\* MERGEFORMAT");
Now I don't know how to get value of this field, I tried getText() on footer but it did not gave the number. I read that fields are related to paragraph so I tried to get the paragraph from the footer and tried to call getText() but the output is nothing. So can someone help me with this scenario? Right now I am getting the response like this: some part of second table going in next page.
I tried to implement solution I found, but I could not get it resolved yet. so if someone can help me in this scenario like how to fetch this page numbers from the field inserted in the document, it would be really great. I tried to provide more details so that if there's a better approach than this then also someone can suggest the same. Thank you.. :)

Java Advanced Imaging: How to get the ImageLayout from a huge image?

I have a couple of huge images which can't be loaded into the memory in whole. I know that the images are tiled and all the methods in the class ImageReader give me plausible non zero return values for
getTileGridXOffset(int),
getTileGridYOffset(int),
getTileWidth(int) and
getTileHeight(int).
My problem now is that I want to read one tile only to avoid having to load the entire image into memory using the ImageReader.readtTile(int, int, int) method. But how do I determine what the valid values for the tile coordinates are?
There is the method getNumXTiles() and getNumYTiles() in the interface RenderedImage but all attempts to create a rendered image from the source results into a out of memory/java heap space error.
The tile coordinates can theoretically be anything and I tried readtTile(0, -1, -1) which also works for a few images I tested.
I also tried to reach the metadata for those images but I didn't find any useful information regarding the image layout.
Is there anyone who can tell me how to get the values for the tile coordinates without having to read the entire image into memory? Is there another way which does not require an instance of ImageLayout?
Thank you very much for your assistance.
First of all, you should check that the ImageReader in question supports tiling for the given image, using the isImageTiled(imageIndex). If it doesn't, you can't expect useful values from the other methods.
Then if it does, all tiles for a given image must be equal in size (but the last tile in each column/the last row may be truncated). This is also the case for all tiled file formats that I know of (ie. TIFF). So, using this knowledge, the number of tiles in both dimensions can be calculated:
// Calculate number of x tiles/y tiles:
int cols = (int) Math.ceil(reader.getWidth(imageIndex) / (double) reader.getTileWidth(imageIndex));
int rows = (int) Math.ceil(reader.getHeight(imageIndex) / (double) reader.getTileHeight(imageIndex));
You can then, loop over the tile indexes (the first tile is always 0,0):
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
BufferedImage tile = reader.readTile(imageIndex, col, row);
// ...do more processing...
}
}
Or, if you only want to get a single tile, you obviously don't need the double for loops. :-)
Note: For ImageReaders/images that don't support tiling, the getTileWidth and getTileHeight methods will just return the same as getWidthand getHeight, respectively.
Also, the readTile API docs says:
If the arguments are out of range, an IllegalArgumentException is thrown. If the image is not tiled, the values 0, 0 will return the entire image; any other values will cause an IllegalArgumentException to be thrown.
This means your example, readtTile(0, -1, -1) should always throw an IllegalArgumentException regardless of the tiling... I suspect some implementations may disregard the tile coordinates completely, and give you the entire image anyway.
PS: The RenderedImage interface could in theory help you. But it would require a special implementation in the ImageReader. In most cases you will just get a normal BufferedImage (which implements RenderedImage), and is a single (1x1) tile.

POI: setCellType(Cell.CELL_TYPE_FORMULA) fails because of Cell.CELL_TYPE_ERROR

My Java application reads an xls file and presents it on a JTable. So far so good.
When I try to save my worksheet, I iterate over row,col in my JTable and:
String str = (String) Table.getValueAt(row, col);
HSSFRow thisrow = sheet.getRow(row);
HSSFCell thiscell = thisrow.getCell(col);
if(thiscell==null) thiscell = thisrow.createCell(col);
switch(inferType(str)) {
case "formula":
thiscell.setCellType(Cell.CELL_TYPE_FORMULA);
thiscell.setCellFormula(str.substring(1));
break;
case "numeric":
thiscell.setCellType(Cell.CELL_TYPE_NUMERIC);
thiscell.setCellValue(Double.parseDouble(str));
break;
case "text":
thiscell.setCellType(Cell.CELL_TYPE_STRING);
thiscell.setCellValue(str);
break;
}
But when I run over a cell which was originally a formula, say A1/B1, that is #DIV/0! at the moment, setCellType fails.
With much investigation I found out that when setCellType is called, it tries to convert the old content to the new type. BUT, this didn't seem a problem to me, since every table formula cell was already a formula in the xls. Hence, I am never actually changing types.
Even so, when I call setCellType(Cell.CELL_TYPE_FORMULA) on a cell that is already a formula, but it is evaluated to #DIV/0!, I get an conversion exception.
Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Cannot get a numeric value from a error formula cell
at org.apache.poi.hssf.usermodel.HSSFCell.typeMismatch(HSSFCell.java:648)
at org.apache.poi.hssf.usermodel.HSSFCell.checkFormulaCachedValueType(HSSFCell.java:653)
at org.apache.poi.hssf.usermodel.HSSFCell.getNumericCellValue(HSSFCell.java:678)
at org.apache.poi.hssf.usermodel.HSSFCell.setCellType(HSSFCell.java:317)
at org.apache.poi.hssf.usermodel.HSSFCell.setCellType(HSSFCell.java:283)
Actually my only workaround is, before setCellType:
if(thiscell.getCachedFormulaResultType()==Cell.CELL_TYPE_ERROR)
thiscell = thisrow.createCell(col);
This IS working, but I lose the original layout of the cell, e.g. its colors.
How can I properly setCellType if the Cell is a formula with evaluation error?
I found this in the mailing list of poi-apache:
There are two possible scenarios when setting value for a formula
cell;
Update the pre-calculated value of the formula. If a cell contains formula then cell.setCellValue just updates the pre-calculated
(cached) formula value, the formula itself remains and the cell type
is not changed
Remove the formula and change the cell type to String or Number:
cell.setCellFormula(null); //Remove the formula
then cell.setCellValue("I changed! My type is CELL_TYPE_STRING now"");
or cell.setCellValue(200); //NA() is gone, the real value is 200
I think we can improve cell.setCellValue for the case (1). If the new
value conflicts with formula type then IllegalArgumentException should
be thrown.
Regards, Yegor
Still, it does feel like a workaround to me. But everything is now working.
cell.setCellFormula(null) before any setCellType should prevent conversion failure, because the first will discard the cached content.

Redefine Named Excel Range then Save using Apache POI

Using Apache POI, I'm able to find a named range:
XSSFName[] ranges = new XSSFName[workbook.getNumberOfNames()];
for (int i = 0; i < _wb.getNumberOfNames(); i++)
ranges[i] = workbook.getNameAt(i);
With that, I'm able to cell an AreaReference:
AreaReference area = new AreaReference(ranges[0].getRefersToFormula());
And then finally I can get all the cells within that range:
CellReference[] cells = area.getAllReferencedCells();
That all works just fine. Burt I have a use case where I have to redefine the area that the range covers. Is there a way to do that? I notice that the range.getRefersToFormula() method return a String, something like MySheet!$A$1:$B$8. There is a range.setRefersToFormula(String formula), but I've got to believe there's a way other than resorting to writing an excel range formula parser on my own. Is there no way to generate an AreaReference with a set to Cell references of something more type-safe? Do I actually have to generate a String to represent the new range? I would think there would be API somewhere to help me with this but I can't seem to find it.
Update
I found some API, but it doesn't seem to work, at least it doesn't save properly. Here's what I did.
AreaReference newArea = new AreaReference(firstCell, lastCell);
ranges[0].setRefersToFormula(newArea.formatAsString())
It seems to set the formula correctly, but when I stream the workbook back out to disk, the range is completely wrong.
you can update the existing Reference and set it as per your requirement.
Suppose the reference contains TestSheet!$A$1:$B$8and you want to change it to MySheet!$B$5:$C$12
For any cell, say "B5", at runtime,
cell.getReference();
will give you cell reference (like in example... it will return you "B5")
char startCellColRef = cell.getReference().toString().charAt(0);
will give you the Column Reference (will give you "B" if the current cell is B5). Now
int startCellRowRef = cell.getReference().toString().charAt(1);
will give you Row Index (will give you "5" if the current cell is B5).
By the same way you can get your start and end cell references (say B5 and C12).
Now comes how can I update the existing references. Just update its value with newly created reference string
Name reference = wb.getName("NameReferenceInExcelSheet");
referenceString = sheetName+"!$"+startCellColRef+"$"+startCellRowRef+":$"+endCellColRef+"$"+endCellRowRef;
reference.setRefersToFormula(referenceString);

Categories

Resources