In iText5, we can get the PdfPTable's height when we need "public float calculateHeights(boolean firsttime)".
But in iText7, how can we get current table height value (especially before adding the table to its parent element)?
I already tested "table.getHeight()" method, but it returns null.
And I also found that in a table render object I can get this value, but the limitation is that the render need to be triggered when the table is adding into its parent element, so the time is not my need.
Cause sometimes we need this value for calculation to decide the "y-axis" value.
In iText5, elements and information about their position/size were a bit mixed together, which allowed you to call calculateWidths
on a PdfPTable
element.
In iText7, this functionality is separated, which allows different kind of flexibility for rendering/layouting elements.
Thus, model elements, which a Table
instance is an example of, do not know anything about their position or size. And calling table.getHeight
results in null
because table
did not have HEIGHT
property previously set to it.
To calculate table height, one would have to make use of the rendering functionality.
For a model element, you can get the subtree of renderers representing this model element and all its children, and layout
it in any given area. To really know the height of a table, you would want to create an area which knowingly will be sufficient to place the whole contents of the element.
PdfDocument pdfDoc = ...
Document doc = ...
Table table = new Table(2)
.addCell(new Cell().add(new Paragraph("cell 1, 1")))
.addCell(new Cell().add(new Paragraph("cell 1, 2")));
LayoutResult result = table.createRendererSubTree().setParent(doc.getRenderer()).layout(
new LayoutContext(new LayoutArea(1, new Rectangle(0, 0, 400, 1e4f))));
System.out.println(result.getOccupiedArea().getBBox().getHeight());
The code above prints 22.982422O
for me, but the results may vary depending on the configuration and properties of elements.
I would like to point out two important parts of the code:
We pass 1e4f
as the height of the LayoutArea
, considering that this will be sufficient to place the whole table. Note that if the table cannot be placed into that height, the result will never exceed this given height and thus it will not be correct for your usecase (know the total height of the table). So make sure to pass the height which will be sufficient for placement of the whole table.
.setParent(doc.getRenderer())
part is important here and is used for retrieving inheritant properties. Note that we did not set a lot of properties to table
element, even font, but this information is essential to know the area this element would occupy. So this information will be inherited from the parent chain during layout
. You can test this by changing the document's font: document.setFont(newFont);
, or font size: document.setFontSize(24);
and watching the resultant height change
Well, due to the way the renderer framework is written in iText7, there isn't a way (yet) to calculate the height of a layout object before it is added to a parent document, since the actual calculation of the height for the layout objects happens when they are added to the a Document
object.
You can however relayout a Document
, allowing you to change the content of previously added elements. Using this, you can simulate the rendering of tables and get a hold of their heights when you add new elements. The table.getHeight()
still won't work, since it retrieves the height property, and that property is currently not set anywhere in the table rendering process.
In the example below, I've written a convenience method that iterates over the renderer-tree and prints out the area each table occupies in the document, to show you how you can get the calculated heights.
The example itself adds some tables to the document, displays the occupied areas, adds some cells to each table, displays the occupied areas (they're the same since adding to an element that has been added before doesn't trigger a layout), and finally, manually triggers a relayout and displays the final occupied areas.
public class DelayedLayout {
public static String DEST = "target/output/StackOverflow/DelayedLayout/delayed.pdf";
public static void main(String[] args)throws IOException, FileNotFoundException{
File file = new File(DEST);
file.getParentFile().mkdirs();
new DelayedLayout().createPdf(DEST);
}
public void createPdf(String dest) throws IOException, FileNotFoundException{
PdfWriter writer = new PdfWriter(dest);
PdfDocument pdfDoc = new PdfDocument(writer);
boolean immediateFlush = false;
boolean relayout = true;
//Set immediate layout to false, so the document doesn't immediatly write render-results to its outputstream
Document doc = new Document(pdfDoc, PageSize.A4,immediateFlush);
Table tOne = createSimpleTable();
for(int i= 0; i< 5; i++) {
//Add a table and some whitespace
doc.add(tOne);
doc.add(new Paragraph(""));
}
System.out.println("\nInitial layout results");
printOccupiedAreasOfTableRenderers(doc.getRenderer());
System.out.println("\nAdding extra cells to the table");
addToTable(tOne);
printOccupiedAreasOfTableRenderers(doc.getRenderer());
System.out.println("\nForcing the document to redo the layout");
if(relayout)doc.relayout();
printOccupiedAreasOfTableRenderers(doc.getRenderer());
doc.close();
}
/**
* Create a very simple table
* @return simple table
*/
private Table createSimpleTable(){
int nrOfCols = 3;
int nrOfRows = 5;
Table res = new Table(nrOfCols);
for(int i= 0; i<nrOfRows;i++){
for(int j = 0; j<nrOfCols;j++){
Cell c = new Cell();
c.add(new Paragraph("["+i+", "+j+"]"));
res.addCell(c);
}
}
return res;
}
/**
* Add some extra cells to an exisiting table
* @param tab table to add cells to
*/
private void addToTable(Table tab){
int nrOfRows = 5;
int nrOfCols = tab.getNumberOfColumns();
for(int i=0; i<nrOfRows*nrOfCols;i++){
Cell c = new Cell();
c.add(new Paragraph("Extra cell"+ i));
tab.addCell(c);
}
}
/**
* Recursively iterate over the renderer tree, writing the occupied area to the console
* @param currentNode current renderer-node to check
*/
private void printOccupiedAreasOfTableRenderers(IRenderer currentNode){
if(currentNode.getClass().equals(TableRenderer.class)){
System.out.println("Table renderer with occupied area: " + currentNode.getOccupiedArea());
}
for (IRenderer child:currentNode.getChildRenderers()) {
printOccupiedAreasOfTableRenderers(child);
}
}