Reputation: 4252
How can I disable the splitting of rows in an Itext7-Table if the row is the last one on a page and does not fit to the first page?
The only thing I found is an example for Itext5 where the method setSplitLate()
is set to false
:
http://what-when-how.com/itext-5/dealing-with-large-tables-itext-5/ (Listing 4.19 HeaderFooter2.java)
Does anyone have a solution for this?
Upvotes: 1
Views: 1387
Reputation: 7871
Here's my solution based on Uladzimir's one.
Basically you have to save a status, on a cell basis, that allow to nullify the SplitRenderer on the cells only the first time the layout method is called on that specific cell.
@Test
public void tableWithCellNotSplitted() {
File fOut = getFileOutput("table-cellsNotSplitter.pdf");
try {
final float MARGIN = 80;
Document document = getDocumentForOutput(fOut, true);
document.setMargins(MARGIN, MARGIN, MARGIN, MARGIN);
final String text = "Enjoyed minutes related as at on on. Is fanny dried as often me. Goodness as reserved raptures to mistaken steepest oh screened he.";
final String bigText = text + "\n" + text + "\n" + text + "\n" + text + "\n" + text + "\n" + text;
Table t = new Table(2);
t.setWidth(new UnitValue(UnitValue.PERCENT, 60));
for (int nRow = 0; nRow < 10; nRow++) {
for (int iCol = 0; iCol < 2; iCol++) {
String content = nRow == 1 ? bigText : text;
t.addCell(createNonSplittingCell2(content));
}
}
document.add(t);
document.close();
openOutputWithReader(fOut);
} catch (Exception e) {
Assert.fail(e.toString());
}
}
private Cell createPlainCell(String text) {
Cell c = new Cell().add(new Paragraph(text));
return c;
}
private Cell createNonSplittingCell1(String text) {
Cell c = new Cell().add(new Paragraph(text));
c.setNextRenderer(new NonSplittingRenderer1(c));
return c;
}
private Cell createNonSplittingCell2(String text) {
Cell c = new NotSplittingCell().add(new Paragraph(text));
c.setNextRenderer(new NonSplittingRenderer2(c));
return c;
}
public class NonSplittingRenderer1 extends CellRenderer {
public NonSplittingRenderer1(Cell modelElement) {
super(modelElement);
}
@Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
if (LayoutResult.FULL != result.getStatus()) {
result.setStatus(LayoutResult.NOTHING);
result.setSplitRenderer(null);
result.setOverflowRenderer(this);
}
return result;
}
@Override
public IRenderer getNextRenderer() {
return new NonSplittingRenderer1((Cell) getModelElement());
}
}
public class NonSplittingRenderer2 extends CellRenderer {
public NonSplittingRenderer2(Cell modelElement) {
super(modelElement);
}
@Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
Cell c = (Cell) modelElement;
if (c instanceof NotSplittingCell) {
if (LayoutResult.FULL != result.getStatus()) {
NotSplittingCell nsc = (NotSplittingCell) c;
if ( !nsc.isSplit()) {
nsc.setSplit(true);
result.setStatus(LayoutResult.NOTHING);
result.setSplitRenderer(null);
result.setOverflowRenderer(this);
}
}
}
return result;
}
@Override
public IRenderer getNextRenderer() {
return new NonSplittingRenderer2((Cell) getModelElement());
}
}
public class NotSplittingCell extends Cell {
private boolean _split;
public boolean isSplit() {
return _split;
}
public void setSplit(boolean split) {
_split = split;
}
}
this code has three ways to create cells:
Solution 1 obviously split cells.
Solution 2 stops writing cells after the firts row: the layout method nullify the splitrender every time he try to render the second row's cells: simply the table vanishes.
Solution 3 uses a subclass of Cell
, NotSplittingCell
, that simply store the split status of the cell: the splitrenderer is nullified only if the cell is never split. That way correctly keep cells unsplit, but only if it's possible
Upvotes: 0
Reputation: 2458
In general you should be advised to implement your own custom TableRenderer and handle the layout by your own. However since the TableRenderer's layout algo is indeed very difficult, I advise you to use the next CellRenderer instead:
class CustomCellRenderer extends CellRenderer {
public CustomCellRenderer(Cell modelElement) {
super(modelElement);
}
@Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
if (LayoutResult.FULL != result.getStatus()) {
result.setStatus(LayoutResult.NOTHING);
result.setSplitRenderer(null);
result.setOverflowRenderer(this);
}
return result;
}
@Override
public IRenderer getNextRenderer() {
return new CustomCellRenderer((Cell)getModelElement());
}
}
As you can see here, if the cell cannot fit on the page, CustomCellRenderer ensures that the layout result will be NOTHING (nothing can be placed on the current area) rather than PARTIAL (the cell can be splitted).
In your question you've mentioned HeaderFooter2 sample. That is the same sample ported to iText7: https://github.com/itext/i7js-book/blob/develop/src/test/java/com/itextpdf/samples/book/part1/chapter04/Listing_04_19_HeaderFooter2.java
That's how you could update it using your CustomCellRendrer:
for (Screening screening : screenings) {
movie = screening.getMovie();
cell = new Cell().add(new Paragraph(screening.getLocation()));
cell.setNextRenderer(new CustomCellRenderer(cell));
table.addCell(cell);
cell = new Cell().add(new Paragraph(String.format("%1$tH:%1$tM", screening.getTime())));
cell.setNextRenderer(new CustomCellRenderer(cell));
table.addCell(cell);
cell = new Cell().add(new Paragraph(String.format("%d '", movie.getDuration())));
cell.setNextRenderer(new CustomCellRenderer(cell));
table.addCell(cell);
cell = new Cell().add(new Paragraph(movie.getMovieTitle()));
cell.setNextRenderer(new CustomCellRenderer(cell));
table.addCell(cell);
cell = new Cell().add(new Paragraph(String.valueOf(movie.getYear())));
cell.setNextRenderer(new CustomCellRenderer(cell));
table.addCell(cell);
cell = new Cell();
cell.setNextRenderer(new CustomCellRenderer(cell));
cell.add(PojoToElementFactory.getDirectorList(movie));
table.addCell(cell);
cell = new Cell();
cell.setNextRenderer(new CustomCellRenderer(cell));
cell.add(PojoToElementFactory.getCountryList(movie));
table.addCell(cell);
}
As you can see, I set the renderer on the cells via setNextRenderer
method. (Note that only "body" cells should be processed with your custom renderer, since we suppose that header and footer will not split).
Now let's look at the results. That's how split has been processed before:
And that's how it's processed now:
The code I've used in the answer is in Java, but since iText's api is the same in C# as in Java, there should be no problem for you to port it.
Upvotes: 6