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);
Related
I am parsing a XML that has HTML tags in it. After parsing the XML I am trying to create a table that is within the cell of another table and create a word document with these contents. I implemented the table within a table with the help of the answer of this Question. However, I am unable to set the width of the columns of the nested table. When the word document is generated the generated nested table is so ugly with super narrow columns that has to be manually adjusted to have a desired column width. I would like to set some default values as the desired column width. The generated word document with the nested table has been provided in the picture. I am using the following code to set the column width but no luck so far.
for (int i = 0; i < nestedtable.getNumberOfRows(); i++) {
XWPFTableRow row = nestedtable.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(400));
}
}
How should I get out of this problem? The generated word document is looking so ugly due to this nested table structure.
The question you linked was about how to insert a table into a table cell in a Word-table using Apache POI. Nothing about how to set column widths. And, since it is from 2017, it answers this using Apache POI versions of 2017. But Apache POI is highly in development. So current versions will have more straightforward methods to do things.
Especially to set widths of XWPFTable and/or XWPFTableCell, current Apache POI version 5.2.3 provides setWidth methods in XWPFTable as well as in XWPFTableCell. The most straightforward is public void setWidth(java.lang.String widthValue). There the String widthValue may be the width to the value "auto", an integer value (20ths of a point), or a percentage ("nn.nn%").
Also setting borders is much more straightforward now as there are XWPFTable.set*Border methods now.
So the code should look like so now using current Apache POI.
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
public class CreateWordTableInTable {
static void setAllBorders(XWPFTable table, XWPFTable.XWPFBorderType borderType, int size, int space, java.lang.String rgbColor) {
table.setTopBorder(borderType, size, space, rgbColor);
table.setRightBorder(borderType, size, space, rgbColor);
table.setBottomBorder(borderType, size, space, rgbColor);
table.setLeftBorder(borderType, size, space, rgbColor);
table.setInsideHBorder(borderType, size, space, rgbColor);
table.setInsideVBorder(borderType, size, space, rgbColor);
}
public static void main(String[] args) throws Exception {
XWPFDocument document = new XWPFDocument();
XWPFTable tableOne = document.createTable(2,2);
tableOne.setWidth("100%");
XWPFTableRow tablerow = tableOne.getRow(0);
tablerow.getCell(0).setWidth("40%");
tablerow.getCell(1).setWidth("60%");
tablerow.getCell(0).setText("Test");
tablerow.getCell(1).setText("Test");
tablerow = tableOne.getRow(1);
tablerow.getCell(0).setText("Test");
XWPFParagraph paragraph = tablerow.getCell(1).getParagraphArray(0);
XWPFTable tableTwo = tablerow.getCell(1).insertNewTbl(paragraph.getCTP().newCursor());
tableTwo.setWidth(0); // This is necessary because a XWPFTable created by insertNewTbl seems not to have full internally structure. It lacks the cell width field in this case.
tableTwo.setWidth("100%");
setAllBorders(tableTwo, XWPFTable.XWPFBorderType.SINGLE, 4, 0, "000000");
tablerow = tableTwo.createRow();
tablerow.createCell().setText("aaaaaaaaaa");
tablerow.createCell().setText("jjjjjjjj");
tablerow = tableTwo.createRow();
tablerow.getCell(0).setText("bbbbbbbbbb");
tablerow.getCell(1).setText("gggggggggg");
try (FileOutputStream out = new FileOutputStream("./CreateWordTableInTable.docx")) {
document.write(out);
}
document.close();
}
}
Note: A XWPFTable created by insertNewTbl seems not to have full internally structure. It has no default border setting for example and it lacks the internally cell width field. That's why explicit border setting is necessary. And calling setWidth(int width) is necessary before calling setWidth(java.lang.String widthValue) as the first sets the internally cell width field while the second relies on the presence of that field and fails if it is not present.
Result looks like so for me:
I have a large table created with Java SWT which I can sort columnwise in various ways. But whenever I sort a column, the table starts displaying the 1st row and I could not find a way to move the table to display a specific row, e.g. the last selected row.
Any idea is appreciated!
Gerald
You can use `Table.setTopIndex" to set the row shown at the top of the table window. So to centre a row you might use something like:
static void showCentredRow(Table table, int rowToShow)
{
Rectangle clientArea = table.getClientArea();
int itemHeight = table.getItemHeight();
int visibleRows = clientArea.height / itemHeight;
int topRow = Math.max(rowToShow - (visibleRows / 2), 0);
table.setTopIndex(topRow);
}
I've seen that the best way of handling large tables in itext 7 is explained in http://developers.itextpdf.com/examples/tables/clone-large-tables
I have a table inside another one. What if the large table is the inner one?
Table outTable = new Table(new float[]{1f},true);
Cell cellHeader1 = new Cell();
cellHeader1.add(new Paragraph("Header 1").addStyle(style));
outTable.addHeaderCell(cellHeader1);
document.add(outTable);
for (int i=0; i<smallArray.size();i++) {
Table innerTable = new Table(new float[]{0.5f,0.5f},true);
Cell cellHeader2 = new Cell(1,2);
cellHeader2.add(new Paragraph("Header 2").addStyle(style));
innerTable.addHeaderCell(cellHeader2);
Cell cellInnerTable = new Cell();
cellInnerTable.add(innerTable);
outTable.addCell(cellInnerTable);
for(int j=0;j<bigArray.size();j++){
//add cells to innerTable;
if (j%20==0){
innerTable.flush(); (1)
outTable.flush(); (2)
}
}
innerTable.complete(); (1)
}
outTable.complete();
(2) This flush doesn't solve the memory problem.
(1) These lines return a NullPointerException in the Table object, line 539 because document is null. The outTable's parent is the document so the 'flush' method flushes the data into the document, but the innerTable's parent is the outTable, not the document. Is there a way of having the innerTable flushed into the outTable and the outTable into the document?
If I set the document to the innerTable so I don't get the NullPointerException:
innerTable.setDocument(document);
it doesn't behave as it should, because now the innerTable is being flushed into the document, not into the outTable and it does weird things.
Thank you very much!
Unfortunately, large tables are only supported if you add them directly to the Document. Inner large tables are not supported and there are no plans to add support for inner large tables in the nearest future.
Try:
for(int j=0;j<bigArray.size();j++){
//add cells to innerTable;
if (j%20==0){
Cell cellContent = new Cell(1,2).add(innerTable);
outTable.addCell(cellContent);
innerTable.flushContent(); // API says is internal but is public and works OK
outTable.flush();
}
}
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);
I am trying to set column width to the length of the column name. My problem is, I am not able to set it. When I use the following code the table is becoming like this,
tableA.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
for(int i = 0; i < tableA.getColumnCount(); i++) {
int columnLength = (modelA.getColumnName(i)).length();
tableA.getColumnModel().getColumn(i).setPreferredWidth(columnLength);
// tableA.getColumnModel().getColumn(i).setMinWidth(columnLength);
// tableA.getColumnModel().getColumn(i).setMaxWidth(columnLength);
}
I am getting the column length correctly from my table model.
When I use the above code the table is getting packed like this,
I am not able to understand where I am doing wrong.
As Dan has pointed out, you need to calculate the actual width of the column name based on the Font that is being used.
Something like:
String name = modelA.getColumnName(i);
Font f = tableA.getFont();
FontMetrics fm = tableA.getFontMetrics(f);
int width = fm.stringWidth(name);
tableA.getColumnModel().getColumn(i).setPreferredWidth(width);
Note that this is an extremely abbreviated example.
If you want to get it completely correct, you will need to query each column's renderer for the Font and the FontMetrics in order to get the correct values.
If all your renderers use the same font as the JTable, then this should be OK.
The setPreferredWidth expects a value in pixels while the length of the column name is the length of a String (number of characters in a String).
If you do not mind using SwingX, you can use the TableColumnExt#setPrototypeValue method which allows you to set a 'prototype' value which will be used to determine the column width.
columnLength == number or columns from ColumnModel,
standard size is 80pixels
minimum columns size is at 10pixels
see How to get/set PreferredSize