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);
}
}
Related
As seen above,i used pdfTable as the body part of the page. Now i want to fill the entire body with the border,column,row of the table, if the table does not fill the entire body part.As shown in the picture below.
Thanks very much!
You can sort of cheat to accomplish this if you do it while you are creating the table.
I'll offer a partial solution that takes advantage of one of the complexities tables in PDFs: Tables are just lines in PDFs. They aren't structured content.
You can take advantage of this though- keep track of where you are are drawing vertical lines while rendering the table and simply continue them to the bottom of the page.
Let's create a new cell event. It keeps track of 4 things: left which is the far left x coordinate of the table, right which is the far right x coordinate of the table, xCoordinates which is a set of all the x coordinates we draw vertical lines, and finally cellHeights which is a list off all the cell heights.
class CellMarginEvent implements PdfPCellEvent {
Set<Float> xCoordinates = new HashSet<Float>();
Set<Float> cellHeights = new HashSet<Float>();
Float left = Float.MAX_VALUE;
Float right = Float.MIN_VALUE;
public void cellLayout(PdfPCell pdfPCell, Rectangle rectangle, PdfContentByte[] pdfContentBytes) {
this.xCoordinates.add(rectangle.getLeft());
this.xCoordinates.add(rectangle.getRight());
this.cellHeights.add(rectangle.getHeight());
left = Math.min(left,rectangle.getLeft());
right = Math.max(right, rectangle.getRight());
}
public Set<Float> getxCoordinates() {
return xCoordinates;
}
}
We'll then add all of our cells to the table, but not add the table to the document just yet
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(OUTPUT_FILE));
document.open();
PdfPTable table = new PdfPTable(4);
CellMarginEvent cellMarginEvent = new CellMarginEvent();
for (int aw = 0; aw < 320; aw++) {
PdfPCell cell = new PdfPCell();
cell.addElement(new Paragraph("Cell: " + aw));
cell.setCellEvent(cellMarginEvent);
table.addCell(cell);
}
No we add get top- the top position of our table, and add the table to the document.
float top = writer.getVerticalPosition(false);
document.add(table);
Then we draw the vertical and horizontal lines of the completed table. For the height of each cell I just used the first element in cellHeights.
Set<Float> xCoordinates = cellMarginEvent.getxCoordinates();
//Draw the column lines
PdfContentByte canvas = writer.getDirectContent();
for (Float x : xCoordinates) {
canvas.moveTo(x, top);
canvas.lineTo(x, 0 + document.bottomMargin());
canvas.closePathStroke();
}
Set<Float> cellHeights = cellMarginEvent.cellHeights;
Float cellHeight = (Float)cellHeights.toArray()[0];
float currentPosition = writer.getVerticalPosition(false);
//Draw the row lines
while (currentPosition >= document.bottomMargin()) {
canvas.moveTo(cellMarginEvent.left,currentPosition);
canvas.lineTo(cellMarginEvent.right,currentPosition);
canvas.closePathStroke();
currentPosition -= cellHeight;
}
And finally close the document:
document.close()
Example output:
Note that the only reason I say this is an incomplete examples is because there may be some adjustments you need to make to top in the case of header cells, or there may be custom cell styling (background color, line color, etc) you need to account for yourself.
I'll also note another downfall that I just thought of- in the case of tagged PDFs this solution fails to add tagged table cells, and thus would break compliance if you have that requirement.
In my iText document, I have a lot of tables scattered around, each with only one row of two columns. I would like to automatically shrink the leftmost column to fit its contents, and expand the rightmost column to fill the remaining space.
The exact contents of these two columns varies greatly, so there's no way to determine ahead of time what the exact width should be.
All of the content in this screenshot is wrapped in one outer table. Each nested table has its two columns highlighted red and blue. I would like to shrink the red columns as narrow as they can get without forcing the text to take up more lines than it has to.
In this case, the contents of the red cells are just a paragraph each, but it's possible they may contain a further-nested table with two cells of its own (which probably faces the same problem).
Is there a simple way to expand one column and shrink another without specifying exact or relative widths?
If you're using iText7 (and ditching the table for layout altogether), you can achieve this look and layout by building on the following example:
Output looks like this:
Code used to generate output above:
public void createPdf(String dest) throws IOException, FileNotFoundException{
PdfWriter writer = new PdfWriter(dest);
PdfDocument pdfDoc = new PdfDocument(writer);
Document doc = new Document(pdfDoc);
Paragraph p = new Paragraph();
Text t = new Text("Date:").setBold();
p.add(t);
t= new Text("10/12/17").setUnderline();
p.add(t);
p.add(new Tab());
p.add(createTwoPartBorderedText("Catalog Year: ","2017"));
p.add(new Tab());
p.add(createTwoPartBorderedText("L Number","2019284"));
doc.add(p);
doc.close();
}
public Paragraph createTwoPartBorderedText(String contentOne, String contentTwo){
Paragraph container= new Paragraph();
Text one = new Text(contentOne).setBold();
Border solidRed = new SolidBorder(Color.RED,1f);
one.setBorder(solidRed);
container.add(one);
Text two =new Text(contentTwo);
two.setUnderline();
Border solidBlue = new SolidBorder(Color.BLUE,1f);
two.setBorder(solidBlue);
container.add(two);
return container;
}
I have a table, with one column. Each cell contains a paragraph.
How can I stop paragraphs from splitting across two pages?
PdfPtable table = new PdfPTable(1);
//report must be printed as compat as possible
table.setSplitLate(false);
//I can't set keep together, because table can be larger than page size
//table.setKeepTogether(true);
for (int i = 0; i < 100; i++) {
//Random text. Can contain ~400 chars.
String text = "aaaaaaaaaaaaaaa sssssssssssss ddddddddddd ffffffffff";
Paragraph p = new Paragraph(text);
//That instruction does not work. I don't know why, may be because paragraph printed in cell.
p.setKeepTogether(true);
table.addCell(p);
}
Change
table.setSplitLate(false);
into
table.setSplitLate(true);
This way, your cell will not be split unless the complete cell doesn't fit on a single page.
I have the requirement to generate PDF file with 120 columns(all columns should be on a single page).I am using iText. When I generate pdf columns gets overlapped.
My Questions are:
1)Is there any way to put horizontal scroll bar to navigate between the columns ?
2)How do we prevent overlapping of columns?
I have seen the similar question in this site
(How to export n number columns as headings for PDF in Java?)
but the link mentioned in the answer there
(http://itext-general.2136553.n4.nabble.com/What-is-the-maximum-page-size-for-PDF-in-iText-td2150839.html%22) is inactive.
Please suggest me on this.
Following code will set the page size. So, If we have large number of columns (for example: 120 as in my case), it will automatically show horizontal bar in pdf to navigate.
Rectangle pageSize=new Rectangle(9400f,9400f);
Document doc=new Document(pageSize);
PdfWriter writer=PdfWriter.getInstance(document,baos);
writer.setUserunit(9900f);
// To prevent overlapping of columns, set the column widths as follows
PdfPTable table=new PdfPTable(120); // 120 is number of columns in table
table.setWidths(600f);
I had the similar problem but thanks to this question answer series i got the solution as stated by Mr.Harjinder, but here's a tip for those who face the same problem create the document after you create the PDF elements and keep the track of the width you want and the finally when you have the track of horizontal scrolling create your document below is the example
a PDF with lists and with horizontal scroll bar
public class Sample {
public static void main(String[] args) {
try {
float indentationLeft = 20f;
float indentationLeftTemp = 20f;
List prtLst = new List(List.UNORDERED);
prtLst.setListSymbol("\u2022");
prtLst.add("P1");
prtLst.setListSymbol("+");
prtLst.add("P2");
prtLst.setListSymbol("\u2022");
List prevList= prtLst;
for(int i=0;i<500;i++){
List chldLst2 = new List(List.UNORDERED);
chldLst2.setIndentationLeft(indentationLeft);
indentationLeftTemp+=20f;
chldLst2.setListSymbol("\u2022");
chldLst2.add("YY"+i);
prevList.add(chldLst2);
prevList=chldLst2;
}
prtLst.add("P3");
Document d = null;
if(indentationLeftTemp>400f){
Rectangle pageSize=new Rectangle(indentationLeftTemp+200f,indentationLeftTemp+200f);
d = new Document(pageSize);
}else{
d = new Document();
}
PdfWriter.getInstance(d, new FileOutputStream("D:/test.pdf"));
d.open();
d.add(prtLst);d.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
I am looking for a method to split wide tables so that they span across multiple pages. The goal is to make tables with large number of columns readable. I found one discussion thread where this topic is covered; however, the example referenced in there is not available. Manning's "iText in Action" (2006) doesn't cover this topic.
Can this be done in version 1.4.8, if not, to which version of iText should I upgrade to?
Please take a look at the examples of chapter 4 of my book, more specifically at the Zhang example. In this example, I have a table with four columns: (1) year, (2) movie title in English, (3) movie title in Chinese, and (4) run length. If you look at the resulting PDF, you will see that this table is split vertically.
Achieving this requires more work then simply adding a table and allowing iText to decide how to split it in between rows. When you want to split in between columns, you need to organize the layout in your code. This is done using the writeSelectedRows()) method.
In my simple book example, I use these lines:
// draw the first two columns on one page
table.writeSelectedRows(0, 2, 0, -1, 236, 806, canvas);
document.newPage();
// draw the remaining two columns on the next page
table.writeSelectedRows(2, -1, 0, -1, 36, 806, canvas);
First I draw the columns from index 0 to index 2. The column with index 0 is the first column, the column with index 2 is the first column that isn't included, namely the third column. I draw the rows from index 0 (first row) until -1. Minus one means: draw all the remaining rows.
You also see minus one on the next page, where I draw the column with index 2 (the third column) until the column with index -1 (meaning: the rest of the columns).
The values (236, 806) and (36, 806) are coordinates: that's where you want the table to start. You can't define "end coordinates". If the table doesn't fit on the page, iText will just continue drawing the table, even if that means that some content exceeds the visible area of the page. This means that you'll have to be very careful when using this method: you'll need to calculate widths and heights of rows and columns before adding the table, otherwise you may end up with parts of the table that aren't visible.
This is the source code of a class that splits your table over multiple pages when your columns don't fit in a single page
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
/**
* Class that writes a <code>PdfPTable</code>, and spans it across multiple
* pages if the columns won't fit on one page
*/
public class PdfPTableWriter {
// Instance variables
private PdfPTable table;
private PdfWriter writer;
private Document document;
// List of how many columns per horizontal page
private List numberOfColumnsPerPage;
// List of how many rows per vertical page
private List numberOfRowsPerPage;
// Offsets if given
private float widthOffset = 20;
private float heightOffset = 70;
/**
* Class Constructor
*/
public PdfPTableWriter(Document document, PdfWriter writer, PdfPTable table) {
this.document = document;
this.writer = writer;
this.table = table;
calculateColumns();
calculateRows();
}
/**
* Writes the table to the document
*/
public void writeTable() throws DocumentException {
// Begin at row 1 (row after the header)
int rowBegin = 1;
int rowEnd = 0;
// Note the size of numberOfRowsPerPage is how many vertical
// pages there are.
Iterator rowsIter = numberOfRowsPerPage.iterator();
while (rowsIter.hasNext()) {
rowEnd = ((Integer) rowsIter.next()).intValue();
writeSelectedRows(rowBegin, rowEnd);
rowBegin = rowEnd;
}
}
/**
* Prints the Report's columns (splitting horizontally if necessary) and
* subsequent rows
*
* #param rowBegin
* #param rowEnd
* #throws DocumentException
*/
private void writeSelectedRows(int rowBegin, int rowEnd) throws DocumentException {
int colBegin = 0;
int colEnd = 0;
float pageHeight = document.getPageSize().getHeight() - heightOffset;
PdfContentByte contentByte = writer.getDirectContent();
Iterator columnsIter = numberOfColumnsPerPage.iterator();
while (columnsIter.hasNext()) {
colEnd = colBegin + ((Integer) columnsIter.next()).intValue();
// Writer table header
writeSelectedRows(colBegin, colEnd, 0, 1, widthOffset, pageHeight);
// Writes selected rows to the document
writeSelectedRows(colBegin, colEnd, rowBegin, rowEnd, widthOffset, pageHeight - table.getRowHeight(0) /*table.getHeaderHeight()*/);
// Add a new page
document.newPage();
colBegin = colEnd;
}
}
public int getTotalPages() {
return numberOfColumnsPerPage.size() * numberOfRowsPerPage.size();
}
public void setHeightOffset(float heightOffset) {
this.heightOffset = heightOffset;
}
public void setWidthOffset(float widthOffset) {
this.widthOffset = widthOffset;
}
private void writeSelectedRows(int colBegin, int colEnd, int rowBegin, int rowEnd, float x, float y) {
PdfContentByte cb = writer.getDirectContent();
table.writeSelectedRows(colBegin, colEnd, rowBegin, rowEnd, x, y, cb);
}
private void calculateColumns() {
numberOfColumnsPerPage = new ArrayList();
float pageWidth = document.getPageSize().getWidth() - widthOffset;
float[] widths = table.getAbsoluteWidths();
if (table.getTotalWidth() > pageWidth) {
// tmp variable for amount of total width thus far
float tmp = 0f;
// How many columns for this page
int columnCount = 0;
// Current page we're on
int currentPage = 0;
// Iterate through the column widths
for (int i = 0; i < widths.length; i++) {
// Add to the temporary total
tmp += widths[i];
// If this column will not fit on the page
if (tmp > pageWidth) {
// Add the current column count to this page
numberOfColumnsPerPage.add(new Integer(columnCount));
// Since this column won't fit, the tmp variable should
// start off the next iteration
// as this column's width
tmp = widths[i];
// Set column count to 1, since we have moved this column to
// the next page
columnCount = 1;
}
// If this is will fit on the page
else {
// Increase the column count
columnCount++;
}
}
// Save the remaining columns
numberOfColumnsPerPage.add(new Integer(columnCount));
}
// All the columns will fit on one horizontal page
// Note: -1 means all the columns
else {
numberOfColumnsPerPage.add(new Integer(-1));
}
}
private void calculateRows() {
numberOfRowsPerPage = new ArrayList();
float pageHeight = document.getPageSize().getHeight() - heightOffset - table.getRowHeight(0) /*table.getHeaderHeight()*/;
// If the table won't fit on the first page
if (table.getTotalHeight() > pageHeight /*table.getHeaderHeight()*/) {
// Temp variables
float tmp = 0f;
// Determine the start and end rows for each page
for (int i = 1; i < table.size(); i++) {
// Add this row's height to the tmp total
tmp += table.getRowHeight(i);
if (tmp > pageHeight - (heightOffset/2)) {
// This row won't fit so end at previous row
numberOfRowsPerPage.add(new Integer(i - 1));
// Since this row won't fit, the tmp variable should start
// off the next iteration
// as this row's height
tmp = table.getRowHeight(i);
}
}
// Last page always ends on totalRows
numberOfRowsPerPage.add(new Integer(table.size()));
}
// All the rows will fit on one vertical page
// Note: -1 means all the rows
else {
numberOfRowsPerPage.add(new Integer(-1));
}
}
}
Well, I'll try to give you some kind of response. First at all, as #mkl says, 1.4.8 is ancient version. Look at http://sourceforge.net/projects/itext/files/iText/ to get something better, the last version is 5.4.5. And as I know, there is no way to split wide table in two pages. If the document is "a bit" wider than the page - rotate the page, but if you have many columns that don't fit - you have to do it your way and this could be painful. There is no automatic function that can check if your columns have too much text and don't fit the page.
Hope this helps you.