Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Itext7 CellRenderer does not always work

Tags:

java

itext7

I'm using Itext7 to create a pdf with a table in it.
The table occupies multiple pages.
I'm also overriding the cell renderer to add a formfield to some cell.
I noticed that when the table goes to a new page the first line doesn't trigger the draw method of the CellRender class.
I put some code below for better understanding.

...
//adding the cells to table
for (int i = 0; i < 10; i++) {
    addRow(table,i);
}
...
//addRow implementation
private void addRow(Table table, int row) throws IOException {
    table.startNewRow();

    PdfFont zapfdingbats = PdfFontFactory.createFont(FontConstants.ZAPFDINGBATS);

    Cell checkBoxCell = new Cell().add(new Paragraph("o").setFont(zapfdingbats).setMargins(9, 0, 0, 11)).setKeepTogether(true);
    checkBoxCell.setNextRenderer(new CheckboxCellRenderer(checkBoxCell, "cb"+row));
    table.addCell(checkBoxCell);
    ...
    //other cells here
}
...
//The implementation of the custom CellRender
protected class CheckboxCellRenderer extends CellRenderer {
    protected String name;

    public CheckboxCellRenderer(Cell modelElement, String name) {
        super(modelElement);
        this.name = name;
    }
    //this method is not triggered for the first row when a new page is created
    @Override
    public void draw(DrawContext drawContext) {
        System.out.println(name);
        super.draw(drawContext);
        float x = getOccupiedAreaBBox().getLeft();
        float y = getOccupiedAreaBBox().getBottom();
        float width = getOccupiedAreaBBox().getRight()-getOccupiedAreaBBox().getLeft();
        float height = getOccupiedAreaBBox().getTop()-getOccupiedAreaBBox().getBottom();
        Rectangle rect = new Rectangle(x+5, y+5, width-10, height-10);
        PdfTextFormField pdfTextFormField = PdfFormField.createText(drawContext.getDocument(), rect, name);
        PdfAcroForm.getAcroForm(drawContext.getDocument(), true).addField(pdfTextFormField);
    }
}

What am I doing wrong?

like image 400
scatolone Avatar asked Dec 08 '22 17:12

scatolone


2 Answers

You do not override one very important method: public IRenderer getNextRenderer().

If a cell cannot be placed on a page, iText creates an overflow cell (see protected AbstractRenderer createOverflowRenderer(int layoutResult) method. If you do not override getNextRenderer iText by default will create simple CellRenderer using its getModelElement().

So I suggest you should add summat: @Override public IRenderer getNextRenderer() { return new CheckboxCellRenderer(getModelElement(), name); }

I've created the next sample using your code and it works as you expect. `

public class ArrayToTable extends GenericTest {
public static final String DEST = "./target/test/resources/sandbox/tables/array_to_table.pdf";

public static void main(String[] args) throws Exception {
    File file = new File(DEST);
    file.getParentFile().mkdirs();
    new CustomRendererTest().manipulatePdf(DEST);
}

@Override
protected void manipulatePdf(String dest) throws Exception {
    PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
    Document doc = new Document(pdfDoc);

    Table table = new Table(1);
    //adding the cells to table
    for (int i = 0; i < 25; i++) {
        addRow(table, i);
    }

    doc.add(table);
    doc.close();
}

    //addRow implementation
    private void addRow(Table table, int row) throws IOException {
        table.startNewRow();

        PdfFont zapfdingbats = PdfFontFactory.createFont(FontConstants.ZAPFDINGBATS);

        Cell checkBoxCell = new Cell().add(new Paragraph("o").setFont(zapfdingbats).setMargins(9, 0, 0, 11)).setKeepTogether(true);
        checkBoxCell.setNextRenderer(new CheckboxCellRenderer(checkBoxCell, "cb"+row));
        table.addCell(checkBoxCell);
        //other cells here
    }

    //The implementation of the custom CellRender
    protected class CheckboxCellRenderer extends CellRenderer {
        protected String name;

        public CheckboxCellRenderer(Cell modelElement, String name) {
            super(modelElement);
            this.name = name;
        }
        //this method is not triggered for the first row when a new page is created
        @Override
        public void draw(DrawContext drawContext) {
            System.out.println(name);
            super.draw(drawContext);
            float x = getOccupiedAreaBBox().getLeft();
            float y = getOccupiedAreaBBox().getBottom();
            float width = getOccupiedAreaBBox().getRight()-getOccupiedAreaBBox().getLeft();
            float height = getOccupiedAreaBBox().getTop()-getOccupiedAreaBBox().getBottom();
            Rectangle rect = new Rectangle(x+5, y+5, width-10, height-10);
            PdfTextFormField pdfTextFormField = PdfFormField.createText(drawContext.getDocument(), rect, name);
            PdfAcroForm.getAcroForm(drawContext.getDocument(), true).addField(pdfTextFormField);
        }

        @Override
        public IRenderer getNextRenderer() {
            return new CheckboxCellRenderer(getModelElement(), name);
        }

    }

} `

like image 124
Uladzimir Asipchuk Avatar answered Dec 10 '22 06:12

Uladzimir Asipchuk


If you go to a new page, then your Renderer is discarded and a new Renderer object is created. This is done in order to avoid muddying up the state in case something changes in the model object. In this case, the method getNextRenderer is called:

/**
 * Gets a new instance of this class to be used as a next renderer, after this renderer is used, if
 * {@link #layout(LayoutContext)} is called more than once.
 * @return new renderer instance
 */
IRenderer getNextRenderer();

So you should be OK if you just override this method in your custom CheckboxCellRenderer class:

@Override
public IRenderer getNextRenderer() {
    return new CheckboxCellRenderer(getModelElement(), name);
}
like image 27
blagae Avatar answered Dec 10 '22 07:12

blagae