Fit iText 7 Table to Single Page with Images using JPype - java

I am attempting to fit all the content of a table to a single A4 PDF.
I found another SO article linked to the itextpdf page here on the same topic
However, I am not certain how it is supposed to be implemented. I have the above code converted to JPype and it seems to run. But I do not get the desired effect.
I want to be able to add images to this table, and have it size appropriately so that it maintains a single A4 page.
Source and example on my github page here:
https://github.com/krowvin/jpypeitext7example
Example PDF
Source

Suppose that this is the table to be fully fit into an A4 page:
Table table = new Table(2);
for (int i = 0; i < 100; i++) {
table.addCell(new Cell().add(new Paragraph(i + " Hello")));
table.addCell(new Cell().add(new Paragraph(i + " World")));
}
Since it's too long to be fully fit via usual layout flow (e.g. Document#add), we should somehow scale it. But first of all, let us find how much space this table occupies if the page to be placed upon is boundless:
LayoutResult result = table.createRendererSubTree().setParent(doc.getRenderer())
.layout(new LayoutContext(new LayoutArea(1, new Rectangle(10000, 10000))));
Rectangle occupiedRectangle = result.getOccupiedArea().getBBox();
Now let's create a form xobject of this table, which we will scale a few lines below:
PdfFormXObject xObject = new PdfFormXObject(new Rectangle(occupiedRectangle.getWidth(), occupiedRectangle.getHeight()));
new Canvas(xObject, pdfDoc).add(table).close();
So now we have the xObject of the table, the only question is how to fit it, e.g. which scale coefficients to apply:
double coefficient = Math.min(PageSize.A4.getWidth() / occupiedRectangle.getWidth(),
PageSize.A4.getHeight() / occupiedRectangle.getHeight());
We're almost done: now let's add the scaled version of the table to the document's page:
new PdfCanvas(pdfDoc.addNewPage())
.saveState()
.concatMatrix(coefficient, 0, 0, coefficient, 0, 0)
.addXObject(xObject)
.restoreState();
And that's it:

Related

iText PDF adding image issue [duplicate]

I would like to create a table that has a list of dots. I don't know ahead of time how many dots I have, but if they overflow the cell, I want them to wrap, just like text would. My code is something like this:
PdfPTable table = new PdfPTable(1);
table.setTotalWidth(new float[]{80});
table.setLockedWidth(true);
Phrase listOfDots = new Phrase();
for (int i = 0; i < 40; i++) {
listOfDots.add(new Chunk(pdf.correct, 0, 0));
listOfDots.add(new Chunk(" "));
}
table.addCell(listOfDots);
outerCell.addElement(table);
The dots wrap, like I expect, but they don't all have the same size. There are 7 rows of 5 dots each, and all 35 dots have the same size. The last row of 5 dots are roughly half the size of the others.
(I tried to post an image, but I'm not veteran enough on this site.)
Is there a way to make all the images the same size?
Please take a look at the ImagesInChunkInCell example. Instead of a bullet, I took the image of a light bulb. I was able to reproduce the problem you described, but as you can see in list_with_images.pdf, I was able to add one extra line:
Image image = Image.getInstance(IMG);
image.setScaleToFitHeight(false);
PdfPTable table = new PdfPTable(1);
table.setTotalWidth(new float[]{120});
table.setLockedWidth(true);
Phrase listOfDots = new Phrase();
for (int i = 0; i < 40; i++) {
listOfDots.add(new Chunk(image, 0, 0));
listOfDots.add(new Chunk(" "));
}
table.addCell(listOfDots);
The extra line is:
image.setScaleToFitHeight(false);
This prevents that the image is scaled.

Table.keepRowsTogether() in iText 5.5.1 doesn't seem to work correctly

I'm getting unexpected results when I try to keep rows in an iText table together. Below is some standalone code that creates a PDF with the results I'm seeing.
FileOutputStream out = new FileOutputStream(new File("table.pdf"));
Document document = new Document(new Rectangle(612, 242));
PdfWriter.getInstance(document, out);
document.open();
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
for (int i = 0; i < 10; i++) {
PdfPCell cell;
if (i == 9) {
cell = new PdfPCell(new Phrase("Two\nLines"));
}
else {
cell = new PdfPCell(new Phrase(Integer.toString(i)));
}
table.addCell(cell);
}
table.keepRowsTogether(new int[] { 8, 9 });
document.add(table);
document.close();
The example creates a table that is two pixels too small to fit on the first page, forcing the last row onto the next page. I would expect, however, since I added the array using keepRowsTogether, to see the first eight rows to stay on one page and the last two to stay together on the next page but that isn't the case as shown by the example images below. Instead the seventh row (counting from zero) is also carried over to the next page.
According to the API documentation found here, keepRowsTogether "Defines which rows should not allow a page break (if possible)." That indicates to me that row seven, which isn't included in the array, should allow a page break.
Does anyone have any ideas how to keep the seventh row from getting carried over to the next page when it definitely fits on the first?
Solution: After talking to Bruno, I realized I misunderstood how keepRowsTogether works. All I need to do for the example above is change table.keepRowsTogether(new int[] { 8, 9 }); to table.keepRowsTogether(9).
I think there's a misunderstanding about what keepRowsTogether() means. If you use:
table.keepRowsTogether(new int[] { 8, 9 });
You indicate that the table won't split at row 8 or 9. This is consistent with what I see.
Maybe you want something like this: split_at_row.pdf
This PDF is created using SplitRowAtSpecificRow. Instead of keepRowsTogether(), it uses:
table.setBreakPoints(8);
This means that iText will give preference to splitting the table after row 8 (start counting at 0). You can introduce more than one breakpoint. For instance:
table.setBreakPoints(4, 8, 12);

PDFBox - how to create table of contents

Is there a way to create a table of contents using Java PDFBox library?
The table of contents should be clickable (jump to the right page)
Thanks.
There's no simple method for doing this, but here's an approach. I haven't figured out how to attach links directly to text, so my approach means you have to draw the annotations as rectangles and the text separately. It's a bit rough around the edges, but it works.
// there are other types of destinations, choose what is appropriate
PDPageXYZDestination dest = new PDPageXYZDestination();
// the indexing is odd here. if you are doing this on the first page of the pdf
// that page is -1, the next is 0, the next is 1 and so on. odd.
dest.setPageNumber(3);
dest.setLeft(0);
dest.setTop(0); // link to top of page, this is the XYZ part
PDActionGoTo action = new PDActionGoTo();
action.setDestination(dest);
PDAnnotationLink link = new PDAnnotationLink();
link.setAction(action);
link.setDestination(dest);
PDRectangle rect = new PDRectangle();
// just making these x,y coords up for sample
rect.setLowerLeftX(72);
rect.setLowerLeftY(600);
rect.setUpperRightX(144);
rect.setUpperRightY(620);
PDPage page = // however you are getting your table of contents page, eg new PDPage() or doc.getDocumentCatalog().getAllPages().get(0)
page.getAnnotations().add(link);
PDPageContentStream stream = new PDPageContentStream(doc, page, true, true);
stream.beginText();
stream.setTextTranslation(85, 600); // made these up, have to test to see if padding is correct
stream.drawString("Page 1");
stream.endText();
stream.close();
Phew! That's a lotta code. That should get you on your way. You can make the rectangle the same color as your document background if you want it to look like they are just clicking a link, but that requires more experimentation.

Set columnwidth of a table in XWPFTableCell (docx)

I'm generating a docx file with apache-poi. In the wordfile, I add tables, whose columns have a width I would like to see fixed.
Currently, I use the technique described here: http://apache-poi.1045710.n5.nabble.com/Is-there-a-way-to-set-the-width-of-a-column-in-XWPFTableCell-td5711491.html
Basically, this entails setting
cell.getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(cols[j]));
on each cell of that column.
The problem is that while the file opens perfectly in MS Word, open office interprets the values I set to the columnwidth differently. Whereas MS Word apparantly assumes 20-th of a point as units, open office seems to use points instead and therefore all columns are 20 times wider when I open the generated document in OO.
Usually when I see something weird in the generated output, I unpack the docx file, see what the value should be and change my code. But open office does not seem to be able to save to docx, so I can't change the value in OO save it back and see if Word still interprets the document correctly in order to find a cross-application solution.
Any idea how I set the width of the table column so that both OO and MS Wordt interprets it the same?
Don't touch single cells.
Add a GRID:
XWPFDocument doc = new XWPFDocument();
XWPFTable table = doc.createTable(1,2);
table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(6000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(2000));
table.getRow(0).getCell(0).setText("1A");
table.getRow(0).getCell(1).setText("1B");
XWPFTableRow newrow = table.createRow();
newrow.getCell(0).setText("2A");
newrow.getCell(1).setText("2B");
The grid sets widths for entire columns. You don't need to do any cycles to set a width for every cell. It works in LibreOffice and GoogleDocs.
To watch the seted width in MS Word too, you may set widths of cells in the first row:
widthCellsAcrossRow(table, 0, 0, 4000);
widthCellsAcrossRow(table, 0, 0, 5000);
private static void widthCellsAcrossRow (XWPFTable table, int rowNum, int colNum, int width) {
XWPFTableCell cell = table.getRow(rowNum).getCell(colNum);
if (cell.getCTTc().getTcPr() == null)
cell.getCTTc().addNewTcPr();
if (cell.getCTTc().getTcPr().getTcW()==null)
cell.getCTTc().getTcPr().addNewTcW();
cell.getCTTc().getTcPr().getTcW().setW(BigInteger.valueOf((long) width));
}
Answer extracted from question:
It was recently pointed out to me that LibreOffice is able to save to docx. By changing the generated file and saving it back and decompiling the result, I have been able to resolve the issue.
Key is to put an explicit width to the table itself first. Word doesn't seem to care about its presence, and OpenOffice/LibreOffice are able to render the table correctly.
So, after creation of the table, I did as follows:
CTTblWidth width = table.getCTTbl().addNewTblPr().addNewTblW();
width.setType(STTblWidth.DXA);
width.setW(BigInteger.valueOf(9072));
Upon creation of the table, the layout is set to "auto" by default hence the width of the cell will always increase to follow the length of the string. As per OpenXML markup, it look's like
w:tblPr
w:tblLayout w:type="auto"
the solution is to set the layout to fixed and set the individual column length
w:tblPr
w:tblLayout w:type="fixed"
Here's the poi code for setting table layout:
XWPFTable table = document.createTable();
CTTblLayoutType type = table.getCTTbl().getTblPr().addNewTblLayout();
type.setType(STTblLayoutType.FIXED);
Here's how to set the individual width:
int[] cols = {
4896,
1872,
4032,
1728,
1440
};
for (int i = 0; i < table.getNumberOfRows(); i++) {
XWPFTableRow row = table.getRow(i);
int numCells = row.getTableCells().size();
for (int j = 0; j < numCells; j++) {
XWPFTableCell cell = row.getCell(j);
CTTblWidth cellWidth = cell.getCTTc().addNewTcPr().addNewTcW();
CTTcPr pr = cell.getCTTc().addNewTcPr();
pr.addNewNoWrap();
cellWidth.setW(BigInteger.valueOf(cols[j]));
}
}
column lengths are in twentieths of a point (dxa) or 1/1440 inch.
This is a major and very tricky element. I solved it using this own generic method of setting the widths of a table cell.
private static void setTableColumnWidths(XWPFTable table) {
table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(2000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(3200));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1105));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1105));
}
Based on other answers...
public static void setTableColumnsWidth(XWPFTable table, long... widths) {
CTTblGrid grid = table.getCTTbl().addNewTblGrid();
for (long w : widths) {
grid.addNewGridCol().setW(BigInteger.valueOf(w));
}
}
Usage:
setTableColumnsWidth(table, 1440, 2700, 3000, 1440);

iText formatting problems

Context
I've been working on dynamic PDF generation for a while now, and I've come across my final requirement. Alas, it's a nightmare. I'm using the iText library, version 2.1.7.
I'm basically trying to get a formatting like the top row (which I just mocked up in paint). The other rows are how it looks at the moment, but I'm having some real trouble with getting them to line up properly!
Code
The code being used to generate each color coded block is here:
String currentPrice = p.getPrice();
String timeStr = p.getTime();
Chunk price = new Chunk(currentPrice);
Chunk time = (Chunk) generatePdfElement("Timestamp", timeStr);
if (priceDbl > lastPrice) {
// set color to blue.
price.setBackground(WebColors.getRGBColor("#7777FF"));
time.setBackground(WebColors.getRGBColor("#7777FF"));
} else if (priceDbl < lastPrice) {
// set to red.
price.setBackground(WebColors.getRGBColor("#FF0000"));
time.setBackground(WebColors.getRGBColor("#FF0000"));
}
Paragraph pricePara = new Paragraph();
pricePara.add(price);
pricePara.add(generateBreakLine());
pricePara.add(time);
pricePara.add(generateBreakLine());
// Add the new price data to the list of all the prices for this cell.
allPrices.add(pricePara);
allPrices is then added to a paragraph and put into the cell:
Paragraph pricesCellValue = new Paragraph();
pricesCellValue.addAll(allPrices);
PdfPCell pricesCell = new PdfPCell(pricesCellValue);
pricesCell.setBackgroundColor(WebColors.getRGBColor(getRowStr()));
selectionsTable.addCell(pricesCell);
// Add each cell to the table to create the row.
The approaches I've tried
I tried the obvious, which was removing the last breakline from each Chunk. This didn't work, and it just looked exactly the same, although each Chunk was closer together.
I also tried changing from Paragraph to Phrase, which means the code looked like this:
Phrase pricePara = new Phrase();
pricePara.add(price);
pricePara.add(generateBreakLine());
pricePara.add(time);
//pricePara.add(generateBreakLine());
// Add the new price data to the list of all the prices for this cell.
allPrices.add(pricePara);
And this was the result:
So now I'm fresh out of ideas! Does anyone else have any suggestions, or some experience with iText in this area?
Edit
Just for clarity, generateBreakLine() generates a new empty Paragraph object.
I used a nested PdfPTable in the last cell, to format the positioning of each Phrase. Works like a dream!

Categories

Resources